Skip to content

docs(observability/metrics): document the metrics package#158

Open
xytan0056 wants to merge 1 commit into
mainfrom
pr0-docs
Open

docs(observability/metrics): document the metrics package#158
xytan0056 wants to merge 1 commit into
mainfrom
pr0-docs

Conversation

@xytan0056

@xytan0056 xytan0056 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

add Tango Metrics Inventory RFC into the repo. Covers the Emitter contract, metrics invetnory, context propagation design and other utils and their usage.

@xytan0056 xytan0056 force-pushed the pr0-docs branch 5 times, most recently from ea9e0ef to d01c936 Compare July 7, 2026 23:37
@xytan0056 xytan0056 marked this pull request as ready for review July 7, 2026 23:39
@xytan0056 xytan0056 requested review from a team as code owners July 7, 2026 23:39
Comment thread docs/observability/metrics.md Outdated
- shared metric-name, op-name, and tag-key constants
- default histogram buckets sized for RPC latency and large target counts
- an in-flight gauge helper that owns the atomic counter callers otherwise reinvent
- a failure classifier that reuses the errors package

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justinwon777 place holder for errors package design RFC

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why metrics should own a classifier?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just discussed that we will just emit classified error code instead. Will remove

@xytan0056 xytan0056 force-pushed the pr0-docs branch 4 times, most recently from a1249f6 to cf7326b Compare July 7, 2026 23:58
Comment thread docs/observability/metrics.md Outdated
Comment thread docs/observability/metrics.md Outdated

Options are variadic and compose. `WithTags` merges into the base tag set with later-wins semantics on key collision. `WithDurationBuckets` / `WithValueBuckets` override the emitter's default histogram buckets on a single call.

## Op-as-subscope

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this mean??

@xytan0056 xytan0056 Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Op-as-subscope means passed op will be a subscope, called like

func (e *defaultEmitter) Inc(op, name string, opts ...Option) {
	...
	e.Subscope(op).Counter(name).Inc(1)
}

so the metric name is controller.get_target_graph.TreehashCacheLookup

Comment thread docs/observability/metrics.md Outdated
Comment thread docs/observability/metrics.md Outdated

Every RPC calls one of:

- `e.Inc(op, Requests, WithTags({result: success}))` on the happy path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inc and RecordFailure aren't intuitive names for counterparts of each other. can we rename?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, not sure why is Inc and failure/success is a thing. Do we need this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. added symmetrical helper and documented generic purpose of Inc

Comment thread docs/observability/metrics.md Outdated
@xytan0056 xytan0056 force-pushed the pr0-docs branch 6 times, most recently from 4f4387b to 54a049f Compare July 8, 2026 21:47

@sbalabanov sbalabanov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing pieces:

  • error classification integration
  • working with histograms for both status and duration and an example on custom buckets


Tango is a library. Consumers wire it into their own `fx` graph with their own `tally.Scope`, and often deploy multiple flavors (long-running server, batch job, one-shot CLI) side-by-side. Without a shared convention every deployment invents its own metric names and tag layout, and dashboards devolve into per-name merges.

This package owns:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this file be README.md under observability/metrics then?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread docs/observability/metrics.md Outdated
- shared metric-name, op-name, and tag-key constants
- default histogram buckets sized for RPC latency and large target counts
- an in-flight gauge helper that owns the atomic counter callers otherwise reinvent
- a failure classifier that reuses the errors package

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why metrics should own a classifier?

Comment thread docs/observability/metrics.md Outdated
- default histogram buckets sized for RPC latency and large target counts
- an in-flight gauge helper that owns the atomic counter callers otherwise reinvent
- a failure classifier that reuses the errors package
- context-based emitter propagation so downstream helpers do not need the emitter threaded through every signature

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they still need to thread context object though.
For the record, I do not necessarily agree that this is better approach than explicit signature-based, but not going to make it blocking.

@xytan0056 xytan0056 Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Yes, if a function needs to send metrics, it should explictly extract from context, but that should be the standard idiom (context has to be there, no context is worse that no metrics). I'll add this bit in the doc
This is the same for all other request scoped things like logger, tracings, signals etc. Emitter is what all downstream functions need but not required for business logic.
Signature based will just require the arg in every callsite -- like passing scope into hashing even though it's not needed in the actual logic, plus we'll have to create a subscope and be careful not to mix up with the one from parent

Comment thread docs/observability/metrics.md Outdated

## Emitter

`Emitter` is an interface. `New(scope)` returns the default tally-backed implementation; `New(nil)` returns one backed by `tally.NoopScope`, so callers do not need to special-case metrics using alternative metrics library than tally. This also serves as a no-op fallback which could be useful for tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is unlikely that clients could use anything other than Tally; tally is used for all of the internal functions that clients won't override.
The extension point is typically inverted: clients use Tally library but can provide a custom backend.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, reworded

Comment thread docs/observability/metrics.md Outdated
Inc(op, name string, opts ...Option)
Gauge(op, name string, v float64, opts ...Option)
RecordDur(op, name string, d time.Duration, opts ...Option)
RecordCount(op, name string, v int64, opts ...Option)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is a difference between this and Inc ?

@xytan0056 xytan0056 Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inc is counter for counting calls, RecordCount is histogram for recording # of targets. Added explanation to make it clear

Comment thread docs/observability/metrics.md Outdated

// RecordFailure emits a requests counter tagged with result=fail plus
// failure information derived from err. No-op on nil.
func RecordFailure(e Emitter, op string, err error)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a cross reference to error classification.
what is "additional failure information"?

@xytan0056 xytan0056 Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RecordFailure needs to call classifier to get errorCode to count reasons for failure. The err needs to be passed in. But fold this in RecordRequest as suggested below

Comment thread docs/observability/metrics.md Outdated
These are helper functions wrapping `Inc` calls for top-level request recording.

```go
// RecordSuccess emits a requests counter tagged with result=success.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the number of reported error types is limited, it is more efficient for the metrics backend to store the outcome in the same tag, i.e. result=success|infra_error|infra_retryable_error|user_error

@xytan0056 xytan0056 Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep that's the intention, RecordSuccess RecordFailure will both use result= tag.
I folded these in RecordRequest as suggested below


## TrackInFlight

Gauges are update-only, so an in-flight counter needs an owning atomic somewhere. Rather than force every call site to allocate its own `atomic.Int64` and remember to update the gauge, the Emitter.TrackInFlight owns a per-op `*int64`:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the practical example where gauges need to be used for Tango?
Gauge is a pretty nasty metric type when it comes to distributed systems with multiple running instances, and should be in general avoided.

@xytan0056 xytan0056 Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering how heavy graph computation could be, I think it's beneficial to track inflight requests/leased workspaces per instance. Gauge is the natural fit here, this way users can have visibility into how much scale the service needs (e.g. how many workers exhausted workspace pool). Especially for native_orchestrator, where the computation happen on hosts.

}
func FromContext(ctx context.Context) *Emitter { // should never return nil
e, ok := ctx.Value(emitterKey{}).(*Emitter)
if !ok { return noopEmitter }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

panic or error ? :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at least, let RFC discuss tradeoffs

@xytan0056 xytan0056 Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure I'll add some explanations.
imo,

  • observability is not busisness logic, not having metrics emission should not crash the server or fail the request.
  • in practice, a missing emitter means someone forgot to call WithEmitter in the handler -> lose metrics -> notice in dashboards -> fix the settitng, it
  • if someone don't want metrics, don't set it. Rather than having to explicitly set it to noop in context
  • other wise every callsite has to handle this error, if err := FromContext(ctx); err != nil {} but there's nothing meaninful they can do for it

Comment thread docs/observability/metrics.md Outdated
Comment thread docs/observability/metrics.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants