zio.metrics.connectors.newrelic.NewRelicEncoder.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zio-metrics-connectors-newrelic_3 Show documentation
Show all versions of zio-metrics-connectors-newrelic_3 Show documentation
zio.metrics.connectors.newrelic
/*
* Copyright 2022 John A. De Goes and the ZIO Contributors
*
* 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 zio.metrics.connectors.newrelic
import java.time.Instant
import zio._
import zio.json.ast._
import zio.metrics._
import zio.metrics.MetricState._
import zio.metrics.connectors._
object NewRelicEncoder {
private[newrelic] val frequencyTagName = "zio.frequency.name"
}
final case class NewRelicEncoder(startedAt: Instant) {
def encode(event: MetricEvent): ZIO[Any, Throwable, Chunk[Json]] =
event match {
case MetricEvent.New(key, state, timestamp) => encodeMetric(key, None, state, timestamp)
case MetricEvent.Unchanged(key, state: MetricState.Gauge, timestamp) => encodeMetric(key, None, state, timestamp)
case MetricEvent.Unchanged(_, _, _) => ZIO.succeed(Chunk.empty)
case MetricEvent.Updated(metricKey, oldState, newState, timestamp) =>
encodeMetric(metricKey, Some(oldState), newState, timestamp)
}
/**
* The assumed time window for a counter is from when the application started to the timestamp of the most
* recent event.
*/
private def calculateIntervalMs(timestamp: Instant): Long =
(timestamp.toEpochMilli - startedAt.toEpochMilli).toLong.abs
private def encodeMetric(
metricKey: MetricKey.Untyped,
oldMetric: Option[MetricState.Untyped],
newMetric: MetricState.Untyped,
timestamp: Instant,
): ZIO[Any, Throwable, Chunk[Json]] =
ZIO.succeed {
(newMetric, oldMetric) match {
case (Frequency(newOccurrences), oldFrequency) =>
val oldOccurrences = oldFrequency.asInstanceOf[Option[Frequency]].fold(Map.empty[String, Long])(_.occurrences)
encodeFrequency(
oldOccurrences,
newOccurrences,
metricKey,
calculateIntervalMs(timestamp),
timestamp,
)
case (newSummary @ Summary(_, _, _, _, _, _), oldSummary) =>
encodeSummary(
oldSummary.asInstanceOf[Option[Summary]],
newSummary,
metricKey,
calculateIntervalMs(timestamp),
timestamp,
)
case (Counter(count), oldCounter) =>
Chunk(
encodeCounter(
oldCounter.asInstanceOf[Option[Counter]].fold(0.0)(_.count),
count,
metricKey,
calculateIntervalMs(timestamp),
timestamp,
Set(makeTypeTag("Counter")),
),
)
case (Histogram(buckets, count, min, max, sum), _) =>
encodeHistogram(
buckets,
count,
min,
max,
sum,
metricKey,
calculateIntervalMs(timestamp),
timestamp,
)
case (Gauge(value), _) =>
Chunk(encodeGauge(value, metricKey, timestamp, Set(makeTypeTag("Gauge"))))
}
}
private[connectors] def encodeAttributes(labels: Set[MetricLabel], additionalAttributes: Set[(String, Json)]): Json =
Json.Obj(
"attributes" -> Json.Obj(Chunk.fromIterable(labels.map { case MetricLabel(name, value) =>
sanitzeLabelName(name) -> Json.Str(value)
} ++ additionalAttributes.map { case (name, value) =>
sanitzeLabelName(name) -> value
})),
)
private[connectors] def encodeCommon(name: String, newRelicMetricType: String, timestamp: Instant): Json.Obj =
Json.Obj(
"name" -> Json.Str(name),
"type" -> Json.Str(newRelicMetricType),
"timestamp" -> Json.Num(timestamp.toEpochMilli()),
)
private[connectors] def encodeCounter(
oldCount: Double,
newCount: Double,
key: MetricKey.Untyped,
interval: Long,
timestamp: Instant,
additionalAttributes: Set[(String, Json)],
): Json = {
val count = (oldCount - newCount).abs // oldCount.fold(newCount)(_ - newCount).abs
encodeCommon(key.name, "count", timestamp) merge Json.Obj(
"value" -> Json.Num(count),
"interval.ms" -> Json.Num(interval),
) merge encodeAttributes(key.tags, additionalAttributes)
}
private[connectors] def encodeFrequency(
oldOccurrences: Map[String, Long],
newOccurrences: Map[String, Long],
key: MetricKey.Untyped,
interval: Long,
timestamp: Instant,
): Chunk[Json] = {
val grouped = (oldOccurrences.toList ++ newOccurrences.toList).groupBy(_._1)
val deltas = grouped.map {
case (key, oldCount :: newCount :: Nil) => (key, (oldCount._2, newCount._2))
case (key, count :: Nil) => (key, (0L, count._2))
case (key, _) => (key, (0L, 0L))
}
Chunk.fromIterable(deltas.map { case (frequencyName, (oldCount, newCount)) =>
val tags: Set[(String, Json)] =
Set(NewRelicEncoder.frequencyTagName -> Json.Str(frequencyName), makeTypeTag("Frequency"))
encodeCounter(oldCount.toDouble, newCount.toDouble, key, interval, timestamp, tags)
})
}
private[connectors] def encodeGauge(
value: Double,
key: MetricKey.Untyped,
timestamp: Instant,
additionalAttributes: Set[(String, Json)],
): Json =
encodeCommon(key.name, "gauge", timestamp) merge Json.Obj(
"value" -> Json.Num(value),
) merge encodeAttributes(key.tags, additionalAttributes)
private[connectors] def encodeHistogram(
buckets: Chunk[(Double, Long)],
count: Long,
min: Double,
max: Double,
sum: Double,
key: MetricKey.Untyped,
interval: Long,
timestamp: Instant,
): Chunk[Json] = {
val metricType = makeTypeTag("Histogram")
val histogram = encodeCommon(key.name, "summary", timestamp) merge
makeNewRelicSummary(count, sum, interval, min, max) merge
encodeAttributes(key.tags, Set(metricType))
Chunk(histogram)
}
private[connectors] def encodeSummary(
oldSummary: Option[Summary],
newSummary: Summary,
key: MetricKey.Untyped,
interval: Long,
timestamp: Instant,
): Chunk[Json] = {
val metricType = makeTypeTag("Summary")
val oldCount = oldSummary.fold(0L)(_.count)
val count = (newSummary.count - oldCount).abs
val error = newSummary.error
val min = newSummary.min
val max = newSummary.max
// val quantiles = newSummary.quantiles
val sum = newSummary.sum
// val quantilesUpdate = oldSummary.map { oldSummary =>
// val value = sum - oldSummary.sum
// }
val summary = encodeCommon(key.name, "summary", timestamp) merge
makeNewRelicSummary(count, sum, interval, min, max) merge
encodeAttributes(key.tags, Set(metricType, "zio.error.margin" -> Json.Num(error)))
Chunk(summary)
}
private[connectors] def makeNewRelicSummary(
count: Long,
sum: Double,
intervalInMillis: Long,
min: Double,
max: Double,
): Json.Obj = Json.Obj(
"value" -> Json
.Obj("count" -> Json.Num(count), "sum" -> Json.Num(sum), "min" -> Json.Num(min), "max" -> Json.Num(max)),
"interval.ms" -> Json.Num(intervalInMillis),
)
private[connectors] def makeTypeTag(metricType: String): (String, Json) = "zio.metric.type" -> Json.Str(metricType)
private[connectors] def reservedWords = Chunk(
"ago",
"and",
"as",
"auto",
"begin",
"begintime",
"compare",
"day",
"days",
"end",
"endtime",
"explain",
"facet",
"from",
"hour",
"hours",
"in",
"is",
"like",
"limit",
"minute",
"minutes",
"month",
"months",
"not",
"null",
"offset",
"or",
"raw",
"second",
"seconds",
"select",
"since",
"timeseries",
"until",
"week",
"weeks",
"where",
"with",
)
private[connectors] def sanitzeLabelName(name: String): String =
if (!name.startsWith("zio") && reservedWords.contains(name.toLowerCase())) s"`$name`" else name
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy