meters

package
v2.10.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2024 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package meters implements lightweight metrics for internal use.

A meter is a name, a set of key/value pairs (set by the underlying logger), and a value. A value can be an absolute value ("gauge"), an incremental value ("counter"), or a set of samples (like "histogram", though lossless in this implementation). Meters are most useful when additional code analyzes the entire log of a program's run; this is called "analysis code" or "anaylsis software" throughout the documentation.

Values are stored by writing the set of operations to build the value to the logs. For example, each time a sample is added to a sampler with Sample, a log line is produced. Reading the logs will allow analysis software to recover the entire set of samples. Counters are similar; each increment event emits a log line, and and analysis code can add the deltas to see the final value, or the value at a particular point in time.

Values are logically associated with key=value metadata. Each value is uniquely identified by the set of key=value metadata; a meter foo with field foo=bar is logically a different value from the meter foo with field foo=baz. The metadata is defined by the fields applied to the underlying logger. For example, if you declare a counter `tx_bytes` on each HTTP request you handle, each time the count of transmitted bytes is updated, the log message will include fields inherited from the default HTTP logger, which includes a per-request ID. Therefore, analysis code can calculate a tx_byte count for every request handled by the system by observing the value of the x-request-id field on log lines matching the tx_byte counter format. And, of course, it can ignore the extra fields and add everything up to show a program-wide count of bytes transmitted.

Because pctx.Child allows the caller to define a namespace for logs and metrics, meter names do not need to be namespaced. Prefer a meter named `tx_bytes` in the `chunk_storage.Upload` logger over one named `chunk_storage.upload.tx_bytes`.

Normally it's considered "too expensive" to store metrics with such a high cardinality (per-user, per-IP, per-request), but this system has no such limitation. Cardinality has no impact on write performance. Analysis code can make the decision on which fields to discard to decrease the cardinality for long-term storage, if desired. Be aware that the usual log sampling rules apply to logged meters.

Aggregating each operation on a meter over time recovers the value of the meter at a particular time. Any sort of smartness or validation comes from the reader, not from this writing code. If you want to treat a certain meter name as a string gauge, integer counter, and sampler of proto messages, that is OK. The analysis code that processes the logs will need to be ready for that, or at least ready to ignore values it doesn't think are valid.

To emit meters, simply call these public functions in this package:

Set(ctx, "helpful_name", value) // Set the current value of the meter to value.
Inc(ctx, "helpful_name", delta) // Increment the current value of the meter by delta.
Sample(ctx, "helpful_name", sample) // Add sample to the set of samples captured by the meter.

It is safe to write to the same meter from different goroutines concurrently.

Some minimal aggregation can be done in-process. This is a compromise to reduce the "noise" in the logs. If you were uploading a 1GB file, it would make sense to increment the byte count meter 1 billion times, as each byte of the file is passed to the TCP stack. This, however, would be very noisy and make the logs difficult to read. So, we offer aggregates to flush the value of gauges (Set) and counters (Inc) periodically, grouping many value-changing operations into a single log message.

An aggregator can be registered on a context (with pctx.Child), causing all future writes to that meter on that context to be aggregated. (The code that writes the meter need not be aware of the aggregated nature; the public Set/Inc/Sample API automatically does the right thing.)

Aggregated meters are emitted to the logs based on a time interval or value delta threshold set at registration time. If a write occurs, and the meter hasn't been logged for that interval, then a log line will be produced showing the current value of the meter. If unflushed data exists when the controlling context is Done, a log line representing the final value is also emitted. From an analysis standpoint, nothing changes; the emitted aggregated meters are indistinguishable from immediately emitted meters. It just is a little bit easier on the eyes of the human reader.

Aggregated meters can only be one type of meter (gauge/counter) with one type of value; if you create a counter named tx_bytes and then call the gauge operatoin "Set" on it, no aggregation will be done on that data. (Inc calls continue to be aggregated; it doesn't "break" the aggregation to accidentally call the wrong value-changing function.) Similarly, the Go type of the value is set at registration time; calls writing values of a different type will not be aggregated, but again, do not break any ongoing aggregation.

Finally, a couple of implementation notes. This package never panics or returns an error; every possible argument you can pass to any public function is valid. Also, functions must never create or contend on any global locks (except the lock on the logger to prevent goroutines from writing partial lines). This is so that heavy use of meters in one component does not delay other more crticial code. Locks scoped to one context chain are OK, but a global lock on something popular like `tx_bytes` is not desirable.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Inc

func Inc[T Monoid](ctx context.Context, meter string, delta T)

Inc changes the value of a meter by the provided delta.

func NewAggregatedCounter

func NewAggregatedCounter[T Monoid](ctx context.Context, meter string, zero T, options ...Option) context.Context

NewAggregatedCounter returns a context configured in such a way as to cause all calls to Inc on this meter to be aggregated.

Do not call this directly; use pctx.WithCounter.

func NewAggregatedDelta

func NewAggregatedDelta[T Signed](ctx context.Context, meter string, threshold T, options ...Option) context.Context

NewAggregatedDelta returns a context configured in such a way as to cause all calls to Set on this meter to be aggregated, and output as a counter instead of a gauge. For example, Set(0), Set(10), Set(20) will result in delta=10 being logged twice. The parameter threshold will cause an immediate flush when the delta equals or exceeds this value; 0 turns off immediate flushing and the delta is only logged when it is time to do so (based on WithFlushInterval/Deferred).

Note that deltas are constrained to signed types; that's because while the underlying value might be unsigned, the sign appears while taking deltas. (Consider Set(100), Set(50); the delta is now no longer the same as the underlying type!)

Do not call this directly; use pctx.WithDelta.

func NewAggregatedGauge

func NewAggregatedGauge[T any](ctx context.Context, meter string, zero T, options ...Option) context.Context

NewAggregatedGauge returns a context configured in such a way as to cause all calls to Set on this meter to be aggregated.

Do not call this directly; use pctx.WithGauge.

func Sample

func Sample[T any](ctx context.Context, meter string, val T)

Sample adds a sample to the value of the meter.

func Set

func Set[T Signed](ctx context.Context, meter string, val T)

Set sets the value of a signed numeric meter. (This exists because Delta gauges have more constraints than gauges in general.)

func SetGauge

func SetGauge[T any](ctx context.Context, meter string, val T)

SetGauge sets the value of an arbitrary-type gauge meter.

func WithNewFields

func WithNewFields(ctx context.Context) context.Context

WithNewFields returns a context with new copies of all aggregated meters contained in the context chain.

Types

type Monoid

type Monoid interface {
	constraints.Integer | constraints.Float | constraints.Complex
}

Monoid is a constraint matching types that have an "empty" value and "append" operation. Consider an integer; 0 is "empty", and "append" is addition. Any Monoid can be a Counter meter value.

type Option

type Option func(o *aggregateOptions)

Option supplies optional configuration to aggregated meters.

func Deferred

func Deferred() Option

Deferred sets a meter to be aggregated until the underlying context is Done.

func WithFlushInterval

func WithFlushInterval(interval time.Duration) Option

WithFlushInterval is an Option that sets the amount of time to aggregate a meter for before emitting.

type Signed

type Signed interface {
	constraints.Signed | constraints.Float
}

Signed is a constraint over signed numbers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL