zio.metrics.connectors.statsd.StatsdEncoder.scala Maven / Gradle / Ivy
package zio.metrics.connectors.statsd
import java.text.DecimalFormat
import zio._
import zio.metrics._
import zio.metrics.connectors._
case object StatsdEncoder {
private val BUF_PER_METRIC = 128
def encode(event: MetricEvent): Task[Chunk[Byte]] =
ZIO.attempt(encodeEvent(event))
def encodeEvent(
event: MetricEvent,
): Chunk[Byte] = {
val result = new StringBuilder(BUF_PER_METRIC)
event.current match {
case MetricState.Counter(_) => appendCounter(result, event)
case g: MetricState.Gauge => appendGauge(result, event.metricKey, g)
case h: MetricState.Histogram => appendHistogram(result, event.metricKey, h)
case s: MetricState.Summary => appendSummary(result, event.metricKey, s)
case f: MetricState.Frequency => appendFrequency(result, event.metricKey, f)
}
Chunk.fromArray(result.toString().getBytes())
}
// TODO: We need to determine the delta for the counter since we have last reported it
// Perhaps we can see the rate for gauges in the backend, so we could report just theses
// For a counter we only report the last observed value to statsd
private def appendCounter(buf: StringBuilder, event: MetricEvent): StringBuilder = {
val delta: Double = event match {
case MetricEvent.New(_, current, _) => current.asInstanceOf[MetricState.Counter].count
case MetricEvent.Unchanged(_, _, _) => 0.0d
case MetricEvent.Updated(_, old, current, _) =>
current.asInstanceOf[MetricState.Counter].count - old.asInstanceOf[MetricState.Counter].count
}
appendMetric(buf, event.metricKey.name, NonEmptyChunk(delta), "c", event.metricKey.tags)
}
// For a gauge we report the current value to statsd
private def appendGauge(buf: StringBuilder, key: MetricKey.Untyped, g: MetricState.Gauge): StringBuilder =
appendMetric(buf, key.name, NonEmptyChunk(g.value), "g", key.tags)
// A Histogram is reported to statsd as a set of related gauges, distinguished by an additional label
private def appendHistogram(buf: StringBuilder, key: MetricKey.Untyped, h: MetricState.Histogram): StringBuilder =
h.buckets.foldLeft(buf) { case (cur, (boundary, count)) =>
val bucket = if (boundary < Double.MaxValue) boundary.toString() else "Inf"
appendMetric(cur, key.name, NonEmptyChunk(count.doubleValue()), "g", key.tags, MetricLabel("le", bucket))
}
// A Summary is reported to statsd as a set of related gauges, distinguished by an additional label
// for the quantile and another label for the error margin
private def appendSummary(buf: StringBuilder, key: MetricKey.Untyped, s: MetricState.Summary): StringBuilder =
s.quantiles.foldLeft(buf) { case (cur, (q, v)) =>
v match {
case None => cur
case Some(v) =>
appendMetric(
buf,
key.name,
NonEmptyChunk(v),
"g",
key.tags,
MetricLabel("quantile", q.toString()),
MetricLabel("error", s.error.toString()),
)
}
}
// For each individual observed String we are going to report a counter to statsd with an
// additional label with key "bucket" and the observed String as a value
private def appendFrequency(buf: StringBuilder, key: MetricKey.Untyped, f: MetricState.Frequency): StringBuilder =
f.occurrences.foldLeft(buf) { case (cur, (b, c)) =>
appendMetric(
cur,
key.name,
NonEmptyChunk(c.doubleValue()),
"g",
key.tags,
MetricLabel("bucket", b),
)
}
private[connectors] def appendMetric(
buf: StringBuilder,
name: String,
values: NonEmptyChunk[Double],
metricType: String,
tags: Set[MetricLabel],
extraTags: MetricLabel*,
): StringBuilder = {
val tagBuf = new StringBuilder()
val withTags = appendTags(tagBuf, tags)
val withAllTags = appendTags(withTags, extraTags)
val withLF = if (buf.nonEmpty) buf.append("\n") else buf
val withMetric = withLF
.append(name)
.append(":")
buf.append(format.format(values.head))
values.tail.foreach(value => buf.append(":").append(format.format(value)))
buf
.append("|")
.append(metricType)
if (withAllTags.nonEmpty) {
withMetric.append("|#").append(tagBuf)
} else withMetric
}
private def appendTag(buf: StringBuilder, tag: MetricLabel): StringBuilder = {
if (buf.nonEmpty) buf.append(",")
buf.append(tag.key).append(":").append(tag.value)
}
private def appendTags(buf: StringBuilder, tags: Iterable[MetricLabel]): StringBuilder =
tags.foldLeft(buf) { case (cur, tag) => appendTag(cur, tag) }
private lazy val format = new DecimalFormat("0.################")
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy