diff --git a/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/DatapointConverter.scala b/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/DatapointConverter.scala index a822b45f0..2b5579763 100644 --- a/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/DatapointConverter.scala +++ b/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/DatapointConverter.scala @@ -85,16 +85,16 @@ private[events] object DatapointConverter { case _ => event => toDouble(event.extractValue(k), event.value) } case None => - event => event.value + event => toDouble(event.value, 1.0) } } - private def squared(value: Any, dflt: Double): Double = { + private def squared(value: Any, dflt: Any): Double = { val v = toDouble(value, dflt) v * v } - private[events] def toDouble(value: Any, dflt: Double): Double = { + private[events] def toDouble(value: Any, dflt: Any): Double = { value match { case v: Boolean => if (v) 1.0 else 0.0 case v: Byte => v.toDouble @@ -106,7 +106,7 @@ private[events] object DatapointConverter { case v: Number => v.doubleValue() case v: String => parseDouble(v) case v: Duration => v.toNanos / 1e9 - case _ => dflt + case _ => toDouble(dflt, 1.0) } } diff --git a/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/LwcEvent.scala b/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/LwcEvent.scala index 5770a5479..39b285182 100644 --- a/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/LwcEvent.scala +++ b/atlas-lwc-events/src/main/scala/com/netflix/atlas/lwc/events/LwcEvent.scala @@ -40,7 +40,7 @@ trait LwcEvent { * Value to use for the event when mapping to a time series. By default it will be * 1.0 same as incrementing a counter by 1. */ - def value: Double = 1.0 + def value: Any = 1.0 /** * Extract a tag value for a given key. Returns `null` if there is no value for diff --git a/atlas-lwc-events/src/test/scala/com/netflix/atlas/lwc/events/DatapointConverterSuite.scala b/atlas-lwc-events/src/test/scala/com/netflix/atlas/lwc/events/DatapointConverterSuite.scala index 22b484eb5..0e29061da 100644 --- a/atlas-lwc-events/src/test/scala/com/netflix/atlas/lwc/events/DatapointConverterSuite.scala +++ b/atlas-lwc-events/src/test/scala/com/netflix/atlas/lwc/events/DatapointConverterSuite.scala @@ -47,6 +47,7 @@ class DatapointConverterSuite extends FunSuite { assertEquals(DatapointConverter.toDouble(Duration.ofMillis(42131), -1.0), 42.131) assertEquals(DatapointConverter.toDouble(Duration.ofSeconds(42131), -1.0), 42131.0) assertEquals(DatapointConverter.toDouble(Duration.ofMinutes(2), -1.0), 120.0) + assertEquals(DatapointConverter.toDouble(null, Duration.ofMillis(42)), 0.042) assert(DatapointConverter.toDouble("foo", -1.0).isNaN) } diff --git a/atlas-lwc-events/src/test/scala/com/netflix/atlas/lwc/events/LwcEventDurationSuite.scala b/atlas-lwc-events/src/test/scala/com/netflix/atlas/lwc/events/LwcEventDurationSuite.scala new file mode 100644 index 000000000..2284903ed --- /dev/null +++ b/atlas-lwc-events/src/test/scala/com/netflix/atlas/lwc/events/LwcEventDurationSuite.scala @@ -0,0 +1,115 @@ +/* + * Copyright 2014-2024 Netflix, Inc. + * + * 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 com.netflix.atlas.lwc.events + +import com.netflix.atlas.core.util.SortedTagMap +import munit.FunSuite + +import java.lang.System.Logger +import java.time.Duration + +class LwcEventDurationSuite extends FunSuite { + + import LwcEventDurationSuite.* + + private val sampleSpan: TestEvent = { + TestEvent(SortedTagMap("app" -> "www", "node" -> "i-123"), Duration.ofMillis(42)) + } + + private val sampleLwcEvent: LwcEvent = new TestSpan(sampleSpan) + + test("tagValue: exists") { + assertEquals(sampleLwcEvent.tagValue("app"), "www") + assertEquals(sampleLwcEvent.tagValue("node"), "i-123") + } + + test("tagValue: enum") { + assertEquals(sampleLwcEvent.tagValue("level"), "TRACE") + } + + test("tagValue: missing") { + assertEquals(sampleLwcEvent.tagValue("foo"), null) + } + + test("tagValue: wrong type") { + assertEquals(sampleLwcEvent.tagValue("duration"), null) + } + + test("extractValue: exists") { + assertEquals(sampleLwcEvent.extractValue("app"), "www") + assertEquals(sampleLwcEvent.extractValue("node"), "i-123") + assertEquals(sampleLwcEvent.extractValue("duration"), Duration.ofMillis(42)) + } + + test("extractValue: missing") { + assertEquals(sampleLwcEvent.extractValue("foo"), null) + } + + test("defautl value") { + assertEquals(sampleLwcEvent.value, Duration.ofMillis(42)) + } + + test("toJson: raw event") { + val expected = """{"tags":{"app":"www","node":"i-123"},"duration":42}""" + assertEquals(sampleLwcEvent.toJson, expected) + } + + test("toJson: row no columns") { + val expected = """[]""" + assertEquals(sampleLwcEvent.toJson(List.empty), expected) + } + + test("toJson: row nested object") { + val expected = """[42,{"app":"www","node":"i-123"}]""" + assertEquals(sampleLwcEvent.toJson(List("duration", "tags")), expected) + } + + test("toJson: row simple") { + val expected = """[42,"www"]""" + assertEquals(sampleLwcEvent.toJson(List("duration", "app")), expected) + } +} + +object LwcEventDurationSuite { + + case class TestEvent(tags: Map[String, String], duration: Duration) + + def extractSpanValue(span: TestEvent)(key: String): Any = { + key match { + case "tags" => span.tags + case "duration" => span.duration + case "level" => Logger.Level.TRACE + case k => span.tags.getOrElse(k, null) + } + } + + class TestSpan(event: TestEvent) extends LwcEvent.Span { + + override def spanId: String = "test" + + override def parentId: String = "parent" + + override def rawEvent: Any = event + + override def timestamp: Long = 0L + + override def value: Any = event.duration + + override def extractValue(key: String): Any = { + extractSpanValue(event)(key) + } + } +}