Skip to content

Commit dcd8938

Browse files
committed
fix histograms & add tests
1 parent 10b3dc8 commit dcd8938

File tree

2 files changed

+84
-3
lines changed

2 files changed

+84
-3
lines changed

reporters/kamon-opentelemetry/src/main/scala/kamon/otel/MetricsConverter.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import java.lang.{Double => JDouble, Long => JLong}
2828
import java.time.Instant
2929
import java.util.{Collection => JCollection}
3030
import scala.collection.JavaConverters._
31+
import scala.collection.mutable.ArrayBuffer
3132

3233
class WithResourceMetricsConverter(resource: Resource, kamonVersion: String, from: Instant, to: Instant) {
3334
private val fromNs = from.toEpochMilli * 1000000
@@ -53,17 +54,24 @@ class WithResourceMetricsConverter(resource: Resource, kamonVersion: String, fro
5354
toString(gauge.settings.unit),
5455
toGaugeData(gauge.instruments))
5556

56-
def toHistogramDatum(s: Snapshot[Distribution]): HistogramPointData =
57+
def toHistogramDatum(s: Snapshot[Distribution]): HistogramPointData = {
58+
val boundaries = ArrayBuffer.newBuilder[JDouble]
59+
val counts = ArrayBuffer.newBuilder[JLong]
60+
for (el <- s.value.bucketsIterator) {
61+
counts += el.frequency
62+
boundaries += el.value.toDouble
63+
}
5764
ImmutableHistogramPointData.create(
5865
fromNs,
5966
toNs,
6067
SpanConverter.toAttributes(s.tags),
6168
JDouble valueOf s.value.sum.toDouble,
6269
JDouble valueOf s.value.min.toDouble,
6370
JDouble valueOf s.value.max.toDouble,
64-
s.value.buckets.map(JDouble valueOf _.value.toDouble).asJava,
65-
s.value.buckets.map(JLong valueOf _.frequency).asJava
71+
boundaries.result().dropRight(1).asJava,
72+
counts.result().asJava
6673
)
74+
}
6775

6876
def toHistogramData(distributions: Seq[Snapshot[Distribution]]): Option[HistogramData] =
6977
distributions.filter(_.value.buckets.nonEmpty) match {

reporters/kamon-opentelemetry/src/test/scala/kamon/otel/OpenTelemetryMetricReporterSpec.scala

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import kamon.testkit.Reconfigure
2727
import org.scalatest.matchers.should.Matchers
2828
import org.scalatest.wordspec.AnyWordSpec
2929

30+
import java.lang.{Double => JDouble, Long => JLong}
3031
import java.time.Instant
3132
import java.util.{Collection => JCollection}
3233
import scala.collection.JavaConverters._
@@ -103,8 +104,80 @@ class OpenTelemetryMetricReporterSpec extends AnyWordSpec
103104
points.head.getAttributes.get(AttributeKey.stringKey("tag1")) should equal("value1")
104105
points.head.getValue shouldEqual 42L
105106
}
107+
"send histogram metrics" in {
108+
val (reporter, mockService) = openTelemetryMetricsReporter()
109+
val now = Instant.now()
110+
reporter.reportPeriodSnapshot(
111+
PeriodSnapshot.apply(
112+
now.minusMillis(1000),
113+
now,
114+
Nil,
115+
Nil,
116+
MetricSnapshot.ofDistributions(
117+
"test.histogram",
118+
"test",
119+
Metric.Settings.ForDistributionInstrument(MeasurementUnit.none, java.time.Duration.ZERO, DynamicRange.Default),
120+
Instrument.Snapshot(
121+
TagSet.from(Map("tag1" -> "value1")),
122+
buildHistogramDist(Seq(1L -> 2L, 2L -> 2L, 3L -> 3L))
123+
) :: Nil) :: Nil,
124+
Nil,
125+
Nil
126+
)
127+
)
128+
// basic sanity
129+
mockService.exportMetricsServiceRequest should not be empty
130+
mockService.exportMetricsServiceRequest.get should have size 1
131+
val exportedMetrics: Seq[MetricData] = mockService.exportMetricsServiceRequest.get.asScala.toSeq
132+
exportedMetrics should have size 1
133+
val metricData = exportedMetrics.head
134+
135+
136+
// check value
137+
metricData.getName should equal("test.histogram")
138+
metricData.getDescription should equal("test")
139+
val sumData = metricData.getHistogramData
140+
val points = sumData.getPoints.asScala.toSeq
141+
points should have size 1
142+
points.head.getAttributes should have size 1
143+
points.head.getAttributes.get(AttributeKey.stringKey("tag1")) should equal("value1")
144+
points.head.getMin shouldEqual 1L
145+
points.head.getMax shouldEqual 3L
146+
points.head.getSum shouldEqual 15L
147+
points.head.getCount shouldEqual 7L
148+
points.head.getBoundaries.asScala shouldEqual Seq[JDouble](1d, 2d)
149+
points.head.getCounts.asScala shouldEqual Seq[JDouble](2d, 2d, 3d)
150+
}
106151
}
107152

153+
private def buildHistogramDist(_buckets: Seq[(Long, Long)]): Distribution = {
154+
155+
val distribution: Distribution = new Distribution() {
156+
override def dynamicRange: DynamicRange = DynamicRange.Default
157+
158+
override def min: Long = _buckets.minBy(_._1)._1
159+
160+
override def max: Long = _buckets.maxBy(_._1)._1
161+
162+
override def sum: Long = _buckets.foldLeft(0L) { case (a, (v, f)) => a + (v * f) }
163+
164+
override def count: Long = _buckets.foldLeft(0L) { case (a, (_, f)) => a + f }
165+
166+
override def percentile(rank: Double): Distribution.Percentile = ??? //percentileValues.get(rank).map(r => r.toPercentile).orNull
167+
168+
override def percentiles: Seq[Distribution.Percentile] = ??? //Seq(perc.toPercentile)
169+
170+
override def percentilesIterator: Iterator[Distribution.Percentile] = null
171+
172+
override def buckets: Seq[Distribution.Bucket] = bucketsIterator.toSeq
173+
174+
override def bucketsIterator: Iterator[Distribution.Bucket] = _buckets.iterator.map { case (v, f) => PureDistributionBucket(v, f) }
175+
}
176+
distribution
177+
}
178+
179+
case class PureDistributionBucket(value: Long, frequency: Long) extends Distribution.Bucket
180+
108181
private class MockMetricsService extends MetricsService {
109182
var exportMetricsServiceRequest: Option[JCollection[MetricData]] = None
110183
var hasBeenClosed = false

0 commit comments

Comments
 (0)