From 6938065fd08d455b4db7cff6d04a51d3a17a9b44 Mon Sep 17 00:00:00 2001 From: Xiaoyang Tan Date: Fri, 17 Jul 2026 16:09:55 -0700 Subject: [PATCH] refactor(graphrunner): route metrics through observability/metrics Emitter Port the native graph runner off raw tally.Scope to the emitter. - Params keeps Scope tally.Scope; the runner subscopes to graph_runner and builds its own emitter, emitting under op compute (snake of GraphRunner.Compute). - Three phase Timers -> DurationHistogram (bazel_query/git_file_hashes/ target_hash_duration); target_count Gauge -> ValueHistogram. - Op const + buckets live in graphrunner/metrics.go. --- graphrunner/BUILD.bazel | 2 ++ graphrunner/metrics.go | 35 +++++++++++++++++++++++++++++++++++ graphrunner/native.go | 14 ++++++++------ 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 graphrunner/metrics.go diff --git a/graphrunner/BUILD.bazel b/graphrunner/BUILD.bazel index ed50781d..99f8b0a7 100644 --- a/graphrunner/BUILD.bazel +++ b/graphrunner/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "graphrunner", srcs = [ "graph_runner.go", + "metrics.go", "native.go", "shell.go", ], @@ -15,6 +16,7 @@ go_library( "//core/git", "//core/targethasher", "//core/workspace", + "//observability/metrics", "@com_github_uber_go_tally//:tally", ], ) diff --git a/graphrunner/metrics.go b/graphrunner/metrics.go new file mode 100644 index 00000000..5714687d --- /dev/null +++ b/graphrunner/metrics.go @@ -0,0 +1,35 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package graphrunner + +import ( + "time" + + "github.com/uber-go/tally" +) + +// opCompute is snake_cased after the GraphRunner.Compute method. +const opCompute = "compute" + +var ( + // phaseDurationBuckets covers the compute phases (bazel query, git file + // hashes, target hashing). A bazel query on a large repo can run for hours, + // so the range extends to ~4.9h: exponential 100ms..~4.9h. + phaseDurationBuckets = tally.MustMakeExponentialDurationBuckets(100*time.Millisecond, 3, 12) + + // targetCountBuckets covers the computed graph size. Graphs already reach + // ~4M targets: exponential 1..~100M. + targetCountBuckets = tally.MustMakeExponentialValueBuckets(1, 10, 9) +) diff --git a/graphrunner/native.go b/graphrunner/native.go index cf5582d4..75d7d470 100644 --- a/graphrunner/native.go +++ b/graphrunner/native.go @@ -24,6 +24,7 @@ import ( "github.com/uber/tango/core/git" "github.com/uber/tango/core/targethasher" "github.com/uber/tango/core/workspace" + "github.com/uber/tango/observability/metrics" ) type nativeGraphRunner struct { @@ -31,7 +32,7 @@ type nativeGraphRunner struct { git git.Interface config config.RepositoryConfig extraExcludedFiles []string - scope tally.Scope + emitter *metrics.Emitter } type NativeGraphRunnerParams struct { @@ -48,12 +49,13 @@ func NewNativeGraphRunner(p NativeGraphRunnerParams) GraphRunner { if scope == nil { scope = tally.NoopScope } + emitter, _ := metrics.New(scope.SubScope("graph_runner")) return &nativeGraphRunner{ bazel: p.BazelClient, git: p.GitClient, config: p.Config, extraExcludedFiles: p.ExtraExcludedFiles, - scope: scope.SubScope("graph_runner"), + emitter: emitter, } } @@ -78,14 +80,14 @@ func (g *nativeGraphRunner) Compute(ctx context.Context, ws workspace.Workspace) AdditionalArgs: additionalArgs, }) - g.scope.Timer("bazel_query_duration").Record(time.Since(bazelStart)) + g.emitter.DurationHistogram(opCompute, "bazel_query_duration", phaseDurationBuckets).RecordDuration(time.Since(bazelStart)) if err != nil { return targethasher.EmptyResult(), err } gitStart := time.Now() knownSourceHashes, err := g.git.FileHashes(ctx, "HEAD") - g.scope.Timer("git_file_hashes_duration").Record(time.Since(gitStart)) + g.emitter.DurationHistogram(opCompute, "git_file_hashes_duration", phaseDurationBuckets).RecordDuration(time.Since(gitStart)) if err != nil { return targethasher.EmptyResult(), err } @@ -99,11 +101,11 @@ func (g *nativeGraphRunner) Compute(ctx context.Context, ws workspace.Workspace) hashStart := time.Now() res, err := targethasher.FromProto(ctx, queryResult.Result, ws.Path(), hashConfig) - g.scope.Timer("target_hash_duration").Record(time.Since(hashStart)) + g.emitter.DurationHistogram(opCompute, "target_hash_duration", phaseDurationBuckets).RecordDuration(time.Since(hashStart)) if err != nil { return targethasher.EmptyResult(), err } - g.scope.Gauge("target_count").Update(float64(len(res.Targets))) + g.emitter.ValueHistogram(opCompute, "target_count", targetCountBuckets).RecordValue(float64(len(res.Targets))) return res, nil }