SENDLINE open source
open source / metrics4s / defining metrics

Defining metrics

A definition is a value with no effect and no registry. It is checked when it is written:

Constructor Name must Records Exposed as
Counter not end with _total, _seconds, _bytes Long <name>_total
Counter.seconds, Counter.bytes end with the unit FiniteDuration, Long <name>_total
Gauge not end with _seconds, _bytes Double <name>
Gauge.seconds, Gauge.bytes end with the unit FiniteDuration, Long <name>
Histogram.seconds, Histogram.bytes end with the unit FiniteDuration, Long _bucket, _sum, _count
Histogram.sizes not end with _seconds, _bytes Long _bucket, _sum, _count
Info not end with _seconds, _bytes its labels <name>_info, always 1

On top of that, every name is snake case with no __, help is never empty, and none of the suffixes the exposition adds (_bucket, _count, _sum, _created, _info, _total) can be written by hand. The compiler says why:

Invalid metric name "processor_items_total": it must not end with _total: every counter is exposed with _total added

Buckets are checked the same way (Buckets(0.1, 0.5, 1): positive, strictly increasing). Computed ones go through Buckets.from(seq): Either[MetricError, Buckets]. Buckets.Fast, Buckets.Normal and Buckets.Sizes are the sets the services already use. Every histogram is registered classicOnly.

Labels

Labels are added with .labelled[L]. They always come from a closed set:

scala
enum Source derives LabelValue:
  case Kafka, Rabbit

final case class ByOutcome(outcome: Outcome, source: Source) derives Labels

val bySource = Counter("processor_source_items", "Items by outcome and source").labelled[ByOutcome]
val skipped  = Counter("processor_skipped", "Skipped items by reason").labelled[(reason: SkipReason)]
  • derives LabelValue works for an enum whose cases take no parameters. The value is the case name in snake case: NotFound becomes not_found. An enum that carries its own string says so instead: given LabelValue[Level] = LabelValue.enumerated(Level.values)(_.value).
  • derives Labels works for case classes, and named tuples need nothing at all. Label names are the field names in snake case.
  • A single label needs its name: given Labels[Outcome] = Labels.single("outcome"). A label name is never guessed from a type name.
  • A value bounded by its type but not enumerable, such as a provider configured at startup, is LabelValue.bounded(render). Its series appear as they are used.
  • There is no LabelValue[String]. An id, a path or an error message would be a new series forever.

When every label is enumerated, registration creates every combination at zero. A product larger than MetricsConfig.preCreateLimit (512 by default) fails registration with MetricError.DomainTooLarge, unless the definition says .withoutPreCreation. Registration also refuses reserved label names (le, quantile, __*), a label declared twice and two values that render the same.

built from README.md at v0.2.0 edit this page on GitHub