FinagleStatsReceiverWrapper Metrics: Monitoring Graph Operations in Twitter's Algorithm
FinagleStatsReceiverWrapper exposes a dynamic metrics interface through scope() and counter() methods that enables GraphJet-based services to emit performance counters like poll, pollLatency, failure, and domain-specific stats such as queryTweetDegree and response_size in the twitter/the-algorithm repository.
The FinagleStatsReceiverWrapper serves as the telemetry bridge between Twitter's Finagle statistics framework and the GraphJet graph processing library. This lightweight adapter, located in src/scala/com/twitter/recos/graph_common/FinagleStatsReceiverWrapper.scala, allows recommendation services to instrument graph operations without hard-coding metric definitions, enabling granular observability into queue latencies, computation failures, and recommendation quality across the user-user and user-tweet entity graphs.
Core Architecture of the Wrapper
FinagleStatsReceiverWrapper implements the GraphJet StatsReceiver interface while delegating to Finagle's native StatsReceiver. The adaptor provides two primary operations for metric creation:
| Method | Signature | Purpose |
|---|---|---|
| scope | scope(namespace: String): FinagleStatsReceiverWrapper |
Returns a new wrapper instance whose metrics are prefixed with the given namespace, creating hierarchical metric paths like UserGraph/poll. |
| counter | counter(name: String): Counter |
Returns a Finagle counter that can be incremented to track discrete events such as failures or queue operations. |
In practice, services typically access the underlying Finagle StatsReceiver via the statsReceiver field to utilize both counters and statistics (histograms). This pattern allows components to define custom metrics while the wrapper handles the Finagle-specific implementation details.
Graph-Specific Metrics by Service
The wrapper itself does not ship with hard-coded metrics. Instead, each recommendation service injects the wrapper and defines domain-specific counters and stats. Below are the specific metrics exposed by the major GraphJet services.
User-User Graph Operations (RecommendUsersHandler)
Located in src/scala/com/twitter/recos/user_user_graph/RecommendUsersHandler.scala, the user-user graph service tracks queue performance and recommendation quality through the wrapper:
failure(counter): Incremented when GraphJet computation throws an exception (lines 42-49).recs_count(stat): Histogram tracking the number of recommendations returned per request.empty(counter): Tracks requests that produce no recommendations.poll(counter): Counts how many times a GraphJet runner is retrieved from the queue.pollTimeout(counter): Incremented when queue polling exceeds the configured timeout (lines 78-87).offer(counter): Tracks when a runner is returned to the queue after use.pollLatency(stat): Measures latency in milliseconds for each queue poll operation.
User-Tweet Entity Graph (TweetRecommendationsRunner)
The TweetRecommendationsRunner in src/scala/com/twitter/recos/user_tweet_entity_graph/TweetRecommendationsRunner.scala manages MagicRecs computation with these metrics:
failure(counter): Tracks exceptions thrown during GraphJet MagicRecs computation.magicRecsFailureCounter(counter): An alias scoped inside the runner that tracks the same failures.poll/offer(counters): Track runner acquisition and return to theAsyncQueue.pollTimeout(counter): Counts queue polling timeouts.pollLatency(stat): Records queue poll latency in milliseconds.- Filter-specific counters: Result filtering components like
RecentTweetFilterandTweetAuthorFiltercreate scoped counters (e.g.,RecentTweetFilter.filterPass) through the wrapper.
Tweet-Based Related Tweet Service (TweetBasedRelatedTweetsHandler)
For the related-tweet endpoints defined in src/scala/com/twitter/recos/user_video_graph/relatedTweetHandlers/TweetBasedRelatedTweetsHandler.scala, the wrapper exposes:
queryTweetDegree(stat): Histogram of the source tweet's degree (connection count) used for scoring.requestTweetDegreeLessThanMinQueryDegree(counter): Tracks when a query tweet's degree falls below the minimum threshold required for processing (lines 24-30).response_size(stat): Measures the number of related tweets returned in responses.requestLatency(stat): Implicitly tracked viatrackFutureBlockStats, measuring end-to-end request latency.
Implementation Patterns and Code Examples
Services follow a consistent pattern: inject the wrapper, create a scoped namespace using the class name, then define counters and stats.
Creating Scoped Metrics
import com.twitter.recos.graph_common.FinagleStatsReceiverWrapper
import com.twitter.finagle.stats.StatsReceiver
// Wrap the injected Finagle StatsReceiver
val finagleReceiver: StatsReceiver = ???
val wrapper = FinagleStatsReceiverWrapper(finagleReceiver)
// Create a scoped namespace for the component
val userGraphStats = wrapper.scope("UserGraph").statsReceiver
// Define specific metrics
val pollCounter = userGraphStats.counter("poll")
val pollLatency = userGraphStats.stat("pollLatency")
val failureCounter = userGraphStats.counter("failure")
// Record metrics during operation
pollCounter.incr()
pollLatency.add(42.0)
Source: FinagleStatsReceiverWrapper.scala, lines 12-16
Injecting into Recommendation Handlers
The RecommendUsersHandler demonstrates dependency injection of the wrapper:
case class RecommendUsersHandlerImpl(
bipartiteGraph: NodeMetadataLeftIndexedPowerLawMultiSegmentBipartiteGraph,
salsaRunnerConfig: SalsaRunnerConfig,
decider: UserUserGraphDecider,
statsReceiverWrapper: FinagleStatsReceiverWrapper
) {
private val stats = statsReceiverWrapper.statsReceiver
.scope(this.getClass.getSimpleName)
private val pollCounter = stats.counter("poll")
private val pollLatency = stats.stat("pollLatency")
}
Source: RecommendUsersHandler.scala, lines 38-44
Queue-Based GraphJet Runners
For async queue management in the tweet-entity graph:
private val magicRecsQueue = new AsyncQueue[TopSecondDegreeByCountForTweet]
(0 until salsaRunnerConfig.numSalsaRunners).foreach { _ =>
magicRecsQueue.offer(
new TopSecondDegreeByCountForTweet(
bipartiteGraph,
salsaRunnerConfig.expectedNodesToHitInSalsa,
// Scoped StatsReceiver allows GraphJet algorithms to emit metrics
statsReceiverWrapper.scope(this.getClass.getSimpleName)
)
)
}
Source: TweetRecommendationsRunner.scala, lines 14-22
Summary
FinagleStatsReceiverWrapperacts as a bridge between GraphJet's statistics interface and Finagle's metrics infrastructure, defined insrc/scala/com/twitter/recos/graph_common/FinagleStatsReceiverWrapper.scala.- The wrapper exposes
scope()andcounter()methods, but services typically access the underlyingstatsReceiverfield to create hierarchical metric namespaces using class names as prefixes. - User-User Graph services track queue performance (
poll,pollLatency,pollTimeout) and recommendation quality (recs_count,empty) via the wrapper. - User-Tweet Entity Graph services emit MagicRecs-specific metrics including failure counters and filter-pass statistics scoped to individual runner instances.
- Tweet-Based services expose domain-specific histograms like
queryTweetDegreeandresponse_sizeto monitor data quality and response volumes.
Frequently Asked Questions
How does FinagleStatsReceiverWrapper differ from Finagle's native StatsReceiver?
FinagleStatsReceiverWrapper implements GraphJet's StatsReceiver interface while internally delegating to Finagle's native implementation. This allows GraphJet algorithms—which expect a specific interface—to emit metrics that are actually recorded by Finagle's statistics system, enabling seamless integration of the graph processing library into Twitter's service infrastructure.
Where are the metric names defined in the Twitter algorithm source code?
Metric names are not hard-coded in FinagleStatsReceiverWrapper.scala. Instead, each service defines its own metrics where the wrapper is injected. For example, RecommendUsersHandler.scala defines failure and poll counters (lines 42-87), while TweetBasedRelatedTweetsHandler.scala defines queryTweetDegree stats (lines 24-30). This decentralized approach allows each component to expose relevant operational metrics.
What is the difference between a counter and a stat in FinagleStatsReceiverWrapper?
Counters track discrete, incrementing events such as failure or pollTimeout occurrences. Stats (statistics) track distributions of values over time, such as pollLatency or response_size, which Finagle exports as histograms percentiles (p50, p90, p99). Services access these via stats.counter("name") and stats.stat("name") respectively on the scoped StatsReceiver instance.
Why are metrics scoped by class name in the recommendation services?
Services call scope(this.getClass.getSimpleName) to create a namespace hierarchy that prevents metric collisions between different graph components. This results in metric paths like RecommendUsersHandler/poll or TweetRecommendationsRunner/failure, making it easier to isolate performance data by service component in monitoring dashboards.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →