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

add Id view for SortedTagMap #1516

Merged
merged 1 commit into from
Jan 31, 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 @@ -15,6 +15,11 @@
*/
package com.netflix.atlas.core.util

import com.netflix.spectator.api.Id
import com.netflix.spectator.api.Tag

import java.lang

/**
* Immutable map implementation for tag maps using a sorted array as the underlying storage.
*/
Expand Down Expand Up @@ -251,6 +256,15 @@ final class SortedTagMap private (private val data: Array[String], private val l
}
vs
}

/**
* Returns a view of this map as a Spectator Id. The map must have a `name` dimension. For
* read only use-cases, such as using with the Spectator QueryIndex, this can be more efficient
* than converting to the default implementations of Id.
*/
def toSpectatorId: Id = {
new SortedTagMap.IdView(this)
}
}

/** Helper functions for working with sorted tag maps. */
Expand Down Expand Up @@ -417,4 +431,64 @@ object SortedTagMap {
}
}
}

/** Wraps a SortedTagMap to conform to the Spectator Id interface. */
private class IdView(map: SortedTagMap) extends Id {

private val namePos = {
val pos = map.find("name")
if (pos < 0) {
throw new IllegalArgumentException(s"`name` key is not present: $map")
}
pos / 2
}

override def name(): String = {
map.value(namePos)
}

override def tags(): lang.Iterable[Tag] = {
import scala.jdk.CollectionConverters._
map.toSeq.filter(_._1 != "name").map(t => Tag.of(t._1, t._2)).asJava
}

/**
* For an Id, the first element is the name and then the rest of the tag list is
* sorted. The SortedTagMap has everything sorted by the key. The method adjusts the
* index used for an Id based on the position of the name key to that used by the
* SortedTagMap.
*/
private def computeIndex(i: Int): Int = {
if (i == 0)
namePos
else if (i <= namePos)
i - 1
else
i
}

override def getKey(i: Int): String = {
map.key(computeIndex(i))
}

override def getValue(i: Int): String = {
map.value(computeIndex(i))
}

override def size(): Int = {
map.size
}

override def withTag(k: String, v: String): Id = {
toDefaultId.withTag(k, v)
}

override def withTag(t: Tag): Id = {
toDefaultId.withTag(t)
}

private def toDefaultId: Id = {
Id.create(name()).withTags(tags())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.netflix.atlas.core.util

import com.netflix.spectator.api.Tag
import munit.FunSuite

class SortedTagMapSuite extends FunSuite {
Expand Down Expand Up @@ -304,4 +305,74 @@ class SortedTagMapSuite extends FunSuite {
assertEquals(a.hashCode, c.hashCode)
assertEquals(a.hashCode, d.hashCode)
}

test("toSpectatorId: empty") {
intercept[IllegalArgumentException] {
SortedTagMap.empty.toSpectatorId
}
}

test("toSpectatorId: missing name") {
intercept[IllegalArgumentException] {
SortedTagMap("a" -> "1", "b" -> "2").toSpectatorId
}
}

test("toSpectatorId: name only") {
val id = SortedTagMap("name" -> "foo").toSpectatorId
assertEquals(id.name(), "foo")
assertEquals(id.size(), id.size())
assertEquals(id.getKey(0), "name")
assertEquals(id.getValue(0), "foo")
assert(!id.tags().iterator().hasNext)
}

test("toSpectatorId: name and tags") {
val map = SortedTagMap("name" -> "foo", "a" -> "1", "b" -> "2", "p" -> "3", "q" -> "4")
val id = map.toSpectatorId
assertEquals(id.name(), "foo")
assertEquals(id.size(), id.size())
assertEquals(id.getKey(0), "name")
assertEquals(id.getValue(0), "foo")

var expectedTuples = List("a" -> "1", "b" -> "2", "p" -> "3", "q" -> "4")
var i = 1
val it = id.tags().iterator()
while (it.hasNext) {
val t = it.next()
val expected = expectedTuples.head
expectedTuples = expectedTuples.tail
assert("name" != t.key())
assertEquals(expected._1, t.key())
assertEquals(expected._2, t.value())
assertEquals(expected._1, id.getKey(i))
assertEquals(expected._2, id.getValue(i))
i += 1
}
}

test("toSpectatorId: withTag") {
val map = SortedTagMap("name" -> "foo", "a" -> "1", "b" -> "2", "p" -> "3", "q" -> "4")
val id = map.toSpectatorId.withTag("r", "5").withTag(Tag.of("s", "6"))
assertEquals(id.name(), "foo")
assertEquals(id.size(), id.size())
assertEquals(id.getKey(0), "name")
assertEquals(id.getValue(0), "foo")

var expectedTuples =
List("a" -> "1", "b" -> "2", "p" -> "3", "q" -> "4", "r" -> "5", "s" -> "6")
var i = 1
val it = id.tags().iterator()
while (it.hasNext) {
val t = it.next()
val expected = expectedTuples.head
expectedTuples = expectedTuples.tail
assert("name" != t.key())
assertEquals(expected._1, t.key())
assertEquals(expected._2, t.value())
assertEquals(expected._1, id.getKey(i))
assertEquals(expected._2, id.getValue(i))
i += 1
}
}
}