Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the entity id to allow using Datadog origin detection #58

Merged
merged 5 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import zio.{ULayer, ZLayer}

/**
* Datadog Specific configuration
*
* @param host
* Agent host name
* @param port
Expand All @@ -20,6 +21,8 @@ import zio.{ULayer, ZLayer}
* The maximum number of metrics stored in the queue. This affects memory usage
* @param containerId
* An optional docker container ID
* @param entityId
* An optional entity ID value used with an internal tag for tracking client entity
*/
final case class DatadogConfig(
host: String,
Expand All @@ -28,6 +31,7 @@ final case class DatadogConfig(
maxBatchedMetrics: Int = 10,
maxQueueSize: Int = 100000,
containerId: Option[String] = None,
entityId: Option[String] = None,
sendUnchanged: Boolean = false)

object DatadogConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,28 @@ case object DatadogEncoder {
private val BUF_PER_METRIC = 128

def encoder(config: DatadogConfig): MetricEvent => Task[Chunk[Byte]] = {
val base = StatsdEncoder.encodeEvent _
val withContainerId = config.containerId match {
case Some(cid) =>
val s = cidString(cid)
(event: MetricEvent) => base(event).append(s)
case None =>
base
}
event => ZIO.attempt(Chunk.fromArray(withContainerId(event).toString().getBytes()))
val encoder = makeStatsdEncoder(config)
event => ZIO.attempt(Chunk.fromArray(encoder.encodeEvent(event).toString().getBytes()))
}

def histogramEncoder(
config: DatadogConfig,
): (MetricKey[MetricKeyType.Histogram], NonEmptyChunk[Double]) => Chunk[Byte] = {
val encoder = makeStatsdEncoder(config)

def encodeHistogramValues(key: MetricKey[MetricKeyType.Histogram], values: NonEmptyChunk[Double]): StringBuilder = {
val result = new StringBuilder(BUF_PER_METRIC)
StatsdEncoder.appendMetric(result, key.name, values, "d", key.tags)
encoder.appendMetric(result, key.name, values, "d", key.tags)
}

val base = encodeHistogramValues _
val withContainerId = config.containerId match {
case Some(cid) =>
val s = cidString(cid)
(key: MetricKey[MetricKeyType.Histogram], values: NonEmptyChunk[Double]) => base(key, values).append(s)
case None =>
base
}
(key, values) => Chunk.fromArray(withContainerId(key, values).toString().getBytes())
(key, values) => Chunk.fromArray(encodeHistogramValues(key, values).toString().getBytes())
}

private def makeStatsdEncoder(config: DatadogConfig): StatsdEncoder =
StatsdEncoder(
config.entityId.map(eid => MetricLabel("dd.internal.entity_id", eid)).toList,
config.containerId.map(cidString),
)

private def cidString(cid: String) = s"|c:$cid"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import zio.metrics._
import zio.metrics.MetricKeyType.Histogram.Boundaries
import zio.metrics.connectors.MetricEvent
import zio.test._
import zio.test.Assertion._
import zio.test.TestAspect._

object DatadogEncoderSpec extends ZIOSpecDefault {
Expand All @@ -16,7 +15,9 @@ object DatadogEncoderSpec extends ZIOSpecDefault {
sendHistogram,
sendHistograms,
encodeContainerId,
encodeEntityId,
encodeHistogramWithContainerId,
encodeHistogramWithEntityId,
) @@ timed @@ timeoutWarning(60.seconds)

private val sendHistogram = test("send histogram update") {
Expand All @@ -26,7 +27,7 @@ object DatadogEncoderSpec extends ZIOSpecDefault {
val encoder = DatadogEncoder.histogramEncoder(DatadogConfig.default)
val key = MetricKey.histogram(name, Boundaries(Chunk.empty)).tagged(tagName, tagValue)
val encoded = new String(encoder(key, NonEmptyChunk(1.0)).toArray)
assert(encoded)(equalTo(s"$name:1|d|#$tagName:$tagValue"))
assertTrue(encoded == s"$name:1|d|#$tagName:$tagValue")
}

private val sendHistograms = test("send histogram updates") {
Expand All @@ -36,7 +37,7 @@ object DatadogEncoderSpec extends ZIOSpecDefault {
val encoder = DatadogEncoder.histogramEncoder(DatadogConfig.default)
val key = MetricKey.histogram(name, Boundaries(Chunk.empty)).tagged(tagName, tagValue)
val encoded = new String(encoder(key, NonEmptyChunk(1.0, 2.0)).toArray)
assert(encoded)(equalTo(s"$name:1:2|d|#$tagName:$tagValue"))
assertTrue(encoded == s"$name:1:2|d|#$tagName:$tagValue")
}

private val encodeContainerId = test("encode container ID") {
Expand All @@ -47,7 +48,18 @@ object DatadogEncoderSpec extends ZIOSpecDefault {
for {
encoded <- encoder(event)
s = new String(encoded.toArray)
} yield assert(s)(equalTo(s"$name:1|g|c:$containerId"))
} yield assertTrue(s == s"$name:1|g|c:$containerId")
}

private val encodeEntityId = test("encode entity ID") {
val entityId = "aaa"
val name = "m1"
val encoder = DatadogEncoder.encoder(DatadogConfig.default.copy(entityId = Some(entityId)))
val event = MetricEvent.New(MetricKey.gauge(name), MetricState.Gauge(1), Instant.now())
for {
encoded <- encoder(event)
s = new String(encoded.toArray)
} yield assertTrue(s == s"$name:1|g|#dd.internal.entity_id:aaa")
}

private val encodeHistogramWithContainerId = test("encode histogram with container ID") {
Expand All @@ -58,7 +70,18 @@ object DatadogEncoderSpec extends ZIOSpecDefault {
val encoder = DatadogEncoder.histogramEncoder(DatadogConfig.default.copy(containerId = Some(containerId)))
val key = MetricKey.histogram(name, Boundaries(Chunk.empty)).tagged(tagName, tagValue)
val encoded = new String(encoder(key, NonEmptyChunk(1.0, 2.0)).toArray)
assert(encoded)(equalTo(s"$name:1:2|d|#$tagName:$tagValue|c:$containerId"))
assertTrue(encoded == s"$name:1:2|d|#$tagName:$tagValue|c:$containerId")
}

private val encodeHistogramWithEntityId = test("encode histogram with entity ID") {
val entityId = "aaa"
val name = "testHistogram"
val tagName = "testTag"
val tagValue = "tagValue"
val encoder = DatadogEncoder.histogramEncoder(DatadogConfig.default.copy(entityId = Some(entityId)))
val key = MetricKey.histogram(name, Boundaries(Chunk.empty)).tagged(tagName, tagValue)
val encoded = new String(encoder(key, NonEmptyChunk(1.0, 2.0)).toArray)
assertTrue(encoded == s"$name:1:2|d|#dd.internal.entity_id:aaa,$tagName:$tagValue")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import zio._
import zio.metrics._
import zio.metrics.connectors._

case object StatsdEncoder {
case class StatsdEncoder(constantTags: List[MetricLabel], suffix: Option[String]) {

private val BUF_PER_METRIC = 128

private val constantTagsFormatted: String = {
val tagBuf = new StringBuilder()
val withTags = appendTags(tagBuf, constantTags)
withTags.toString()
}

def encode(event: MetricEvent): Task[Chunk[Byte]] =
ZIO.attempt(Chunk.fromArray(encodeEvent(event).toString().getBytes()))

Expand Down Expand Up @@ -96,7 +102,7 @@ case object StatsdEncoder {
tags: Set[MetricLabel],
extraTags: MetricLabel*,
): StringBuilder = {
val tagBuf = new StringBuilder()
val tagBuf = new StringBuilder(constantTagsFormatted)
val withTags = appendTags(tagBuf, tags)
val withAllTags = appendTags(withTags, extraTags)

Expand All @@ -114,9 +120,11 @@ case object StatsdEncoder {
.append("|")
.append(metricType)

if (withAllTags.nonEmpty) {
val result = if (withAllTags.nonEmpty) {
withMetric.append("|#").append(tagBuf)
} else withMetric

suffix.fold(result)(result.append)
}

private def appendTag(buf: StringBuilder, tag: MetricLabel): StringBuilder = {
Expand All @@ -130,3 +138,5 @@ case object StatsdEncoder {
private lazy val format = new DecimalFormat("0.################")

}

object StatsdEncoder extends StatsdEncoder(Nil, None)
Loading