diff --git a/.github/workflows/deploy-init-cassandra.yml b/.github/workflows/deploy-init-cassandra.yml new file mode 100644 index 0000000..9da33af --- /dev/null +++ b/.github/workflows/deploy-init-cassandra.yml @@ -0,0 +1,26 @@ +name: Init-Cassandra Pipeline + +on: + push: + branches: ["main"] + +jobs: + deploy-init-cassandra: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to Docker Container Registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Build and push multi-arch Docker image + run: | + docker buildx build \ + --file init-cassandra.Dockerfile \ + --platform linux/amd64,linux/arm64 \ + --tag explorviz/init-cassandra . \ + --push diff --git a/README.md b/README.md index 07d34e2..bee7611 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,6 @@ Scalable service that processes, persists, aggregates and queries the observed traces of method executions within monitored software applications. -## Features - -This replaces the [trace-service](https://git.se.informatik.uni-kiel.de/ExplorViz/code/trace-service) and -[landscape-service](https://git.se.informatik.uni-kiel.de/ExplorViz/code/landscape-service) previously used to process -spans. - ## Prerequisites - Java 17 or higher diff --git a/build.gradle b/build.gradle index dbd186d..b664a19 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ dependencies { implementation 'io.quarkus:quarkus-smallrye-openapi' testImplementation 'io.quarkus:quarkus-junit5' + testImplementation 'io.quarkus:quarkus-junit5-mockito' // Integration / Api Tests testImplementation 'io.rest-assured:rest-assured' diff --git a/gradle.properties b/gradle.properties index ce97b22..174722c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,10 @@ #Gradle properties #Thu Apr 20 09:07:19 CEST 2023 #quarkusPluginVersion=3.2.9.Final -quarkusPluginVersion=3.6.4 +quarkusPluginVersion=3.8.3 quarkusPlatformArtifactId=quarkus-bom quarkusPluginId=io.quarkus quarkusPlatformGroupId=io.quarkus.platform #quarkusPlatformVersion=3.2.9.Final -quarkusPlatformVersion=3.6.4 +quarkusPlatformVersion=3.8.3 confluentAvroSerdeVersion=7.3.3 diff --git a/init-cassandra.Dockerfile b/init-cassandra.Dockerfile new file mode 100644 index 0000000..def0cda --- /dev/null +++ b/init-cassandra.Dockerfile @@ -0,0 +1,6 @@ +FROM cassandra:3.11.14 + +COPY src/main/resources/init_script.cql ./init.cql + +CMD ["cqlsh", "cassandra-explorviz", "-f", "init.cql"] + diff --git a/src/integrationTest/java/net/explorviz/span/api/LandscapeResourceIt.java b/src/integrationTest/java/net/explorviz/span/api/LandscapeResourceIt.java index 96781df..e9d81c5 100644 --- a/src/integrationTest/java/net/explorviz/span/api/LandscapeResourceIt.java +++ b/src/integrationTest/java/net/explorviz/span/api/LandscapeResourceIt.java @@ -10,10 +10,13 @@ import java.util.List; import java.util.UUID; import net.explorviz.span.kafka.KafkaTestResource; +import net.explorviz.span.landscape.Application; import net.explorviz.span.landscape.Landscape; import net.explorviz.span.landscape.Method; +import net.explorviz.span.landscape.Node; import net.explorviz.span.persistence.PersistenceSpan; import net.explorviz.span.persistence.PersistenceSpanProcessor; +import net.explorviz.span.trace.Trace; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -25,6 +28,8 @@ public class LandscapeResourceIt { @Inject PersistenceSpanProcessor spanProcessor; + final String gitCommitChecksum = "testGitCommitChecksum"; + @Test void testLoadAllStructureSpans() { final long startEarly = 1701081827000L; @@ -37,25 +42,25 @@ void testLoadAllStructureSpans() { final UUID uuidExpected = UUID.randomUUID(); final PersistenceSpan differentTokenSpan = new PersistenceSpan( - UUID.randomUUID(), "123L", "", "1L", startEarly, - endEarly, "nodeIp", "app-name", "java", 0, "net.explorviz.Class.myMethod()", "847", + UUID.randomUUID(), gitCommitChecksum, "123L", "", "1L", startEarly, + endEarly, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class.myMethod()", "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); final String duplicateMethodName = "myMethodName()"; final String otherMethodName = "myOtherMethodName()"; - final PersistenceSpan firstOccurenceSpan = new PersistenceSpan(uuidExpected, - "123L", "", "1L", startEarly, endEarly, "nodeIp", "app-name", "java", 0, + final PersistenceSpan firstOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, + "123L", "", "1L", startEarly, endEarly, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); - final PersistenceSpan secondOccurenceSpan = new PersistenceSpan(uuidExpected, - "789L", "", "3L", startLate, endLate, "nodeIp", "app-name", "java", 0, + final PersistenceSpan secondOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, + "789L", "", "3L", startLate, endLate, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); - final PersistenceSpan otherSpan = new PersistenceSpan(uuidExpected, "456L", - "0L", "", startExpected, endExpected, "nodeIp", "app-name", "java", 0, + final PersistenceSpan otherSpan = new PersistenceSpan(uuidExpected, "456L", gitCommitChecksum, + "0L", "", startExpected, endExpected, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + otherMethodName, "321", "iamnotapod", "iamnotanode", "iamnotanamespace", "iamnotadeployment"); @@ -69,7 +74,20 @@ void testLoadAllStructureSpans() { final Landscape result = response.getBody().as(Landscape.class); - final List resultMethodList = result.nodes().get(0).applications().get(0).packages() + final List node = result.nodes(); + + Assertions.assertEquals(1, node.size()); + Assertions.assertEquals("host-name", node.get(0).hostName()); + Assertions.assertEquals("nodeIp", node.get(0).ipAddress()); + + final List applications = result.nodes().get(0).applications(); + + Assertions.assertEquals(1, applications.size()); + Assertions.assertEquals("app-name", applications.get(0).name()); + Assertions.assertEquals("java", applications.get(0).language()); + Assertions.assertEquals(0, applications.get(0).instanceId()); + + final List resultMethodList = applications.get(0).packages() .get(0).subPackages().get(0).classes().get(0).methods(); Assertions.assertEquals(2, resultMethodList.size()); @@ -89,25 +107,25 @@ void testLoadStructureSpansByTimeRange() { final UUID uuidExpected = UUID.randomUUID(); final PersistenceSpan differentTokenSpan = new PersistenceSpan( - UUID.randomUUID(), "123L", "", "1L", startEarly, - endEarly, "nodeIp", "app-name", "java", 0, "net.explorviz.Class.myMethod()", "847", - "iamapod", "iamanode", "iamanamespace", "iamadeployment" ); + UUID.randomUUID(), gitCommitChecksum, "123L", "", "1L", startEarly, + endEarly, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class.myMethod()", "847", + "iamapod", "iamanode", "iamanamespace", "iamadeployment"); final String duplicateMethodName = "myMethodName()"; final String otherMethodName = "myOtherMethodName()"; - final PersistenceSpan firstOccurenceSpan = new PersistenceSpan(uuidExpected, - "123L", "", "1L", startEarly, endEarly, "nodeIp", "app-name", "java", 0, + final PersistenceSpan firstOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, + "123L", "", "1L", startEarly, endEarly, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); - final PersistenceSpan secondOccurenceSpan = new PersistenceSpan(uuidExpected, - "789L", "", "3L", startLate, endLate, "nodeIp", "app-name", "java", 0, + final PersistenceSpan secondOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, + "789L", "", "3L", startLate, endLate, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); - final PersistenceSpan otherSpan = new PersistenceSpan(uuidExpected, "456L", - "", "2L", startExpected, endExpected, "nodeIp", "app-name", "java", 0, + final PersistenceSpan otherSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, "456L", + "", "2L", startExpected, endExpected, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + otherMethodName, "321", "iamnotapod", "iamnotanode", "iamnotanamespace", "iamnotadeployment"); @@ -132,4 +150,55 @@ void testLoadStructureSpansByTimeRange() { Assertions.assertEquals(otherMethodName, resultMethodList.get(0).name()); } + @Test + void testLoadTracesByTimeRange() { + final long startEarly = 1701081837000L; + final long endEarly = 1701081838000L; + final long startExpected = 1701081840000L; + final long endExpected = 1701081841000L; + final long startLate = 1701081843000L; + final long endLate = 1701081844000L; + + final UUID uuidExpected = UUID.randomUUID(); + + final PersistenceSpan differentTokenSpan = new PersistenceSpan( + UUID.randomUUID(), gitCommitChecksum, "123L", "", "1L", startEarly, + endEarly, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class.myMethod()", + "847"); + + final String duplicateMethodName = "myMethodName()"; + final String otherMethodName = "myOtherMethodName()"; + + final PersistenceSpan firstOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, + "123L", "", "1L", startEarly, endEarly, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + + final PersistenceSpan secondOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, + "789L", "", "3L", startLate, endLate, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + + final PersistenceSpan otherSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, "456L", + "", "2L", startExpected, endExpected, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + otherMethodName, "321"); + + spanProcessor.accept(differentTokenSpan); + spanProcessor.accept(firstOccurenceSpan); + spanProcessor.accept(secondOccurenceSpan); + spanProcessor.accept(otherSpan); + + final long from = startExpected; + final long to = endExpected; + + final Response response = given().pathParam("token", uuidExpected) + .queryParam("from", from).queryParam("to", to).when() + .get("/v2/landscapes/{token}/dynamic"); + + final Trace[] result = response.getBody().as(Trace[].class); + + // ATTENTION: For the moment, we only filter based on the starting point of + // traces + Assertions.assertEquals(2, result.length); + Assertions.assertEquals(gitCommitChecksum, result[0].gitCommitChecksum()); + } + } diff --git a/src/integrationTest/java/net/explorviz/span/api/TimestampResourceIt.java b/src/integrationTest/java/net/explorviz/span/api/TimestampResourceIt.java index 6e23e08..18a7711 100644 --- a/src/integrationTest/java/net/explorviz/span/api/TimestampResourceIt.java +++ b/src/integrationTest/java/net/explorviz/span/api/TimestampResourceIt.java @@ -8,7 +8,9 @@ import io.restassured.common.mapper.TypeRef; import io.restassured.response.Response; import jakarta.inject.Inject; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; import net.explorviz.span.kafka.KafkaTestResource; @@ -26,6 +28,8 @@ public class TimestampResourceIt { @Inject PersistenceSpanProcessor spanProcessor; + final String gitCommitChecksum = "testGitCommitChecksum"; + @Test void testLoadAllTimestampsForToken() { final long startEarly = 1702545564404L; @@ -37,34 +41,33 @@ void testLoadAllTimestampsForToken() { final UUID uuidExpected = UUID.randomUUID(); - final PersistenceSpan differentTokenSpan = - new PersistenceSpan(UUID.randomUUID(), "123L", "", - "1L", startEarly, endEarly, "nodeIp", "app-name", "java", 0, - "net.explorviz.Class.myMethod()", "847", - "iamapod", "iamanode", "iamanamespace", "iamadeployment"); + final PersistenceSpan differentTokenSpan = new PersistenceSpan(UUID.randomUUID(), gitCommitChecksum, "123L", "", + "1L", startEarly, endEarly, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class.myMethod()", "847", + "iamapod", "iamanode", "iamanamespace", "iamadeployment"); final String duplicateMethodName = "myMethodName()"; final String otherMethodName = "myOtherMethodName()"; - final PersistenceSpan firstOccurenceSpan = - new PersistenceSpan(uuidExpected, "123L", "", "1L", startEarly, endEarly, - "nodeIp", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", - "iamapod", "iamanode", "iamanamespace", "iamadeployment"); + final PersistenceSpan firstOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, "123L", "", "1L", + startEarly, endEarly, + "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", + "iamapod", "iamanode", "iamanamespace", "iamadeployment"); - final PersistenceSpan secondOccurenceSpan = - new PersistenceSpan(uuidExpected, "789L", "", "3L", startLate, endLate, - "nodeIp", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", - "iamapod", "iamanode", "iamanamespace", "iamadeployment"); + final PersistenceSpan secondOccurenceSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, "789L", "", "3L", + startLate, endLate, + "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + duplicateMethodName, "847", + "iamapod", "iamanode", "iamanamespace", "iamadeployment"); - final PersistenceSpan otherSpan = - new PersistenceSpan(uuidExpected, "456L", "0L", "", startExpected, - endExpected, "nodeIp", "app-name", "java", 0, "net.explorviz.Class." + otherMethodName, - "321", "iamnotapod", "iamnotanode", "iamnotanamespace", "iamnotadeployment"); + final PersistenceSpan otherSpan = new PersistenceSpan(uuidExpected, gitCommitChecksum, "456L", "0L", "", + startExpected, + endExpected, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class." + otherMethodName, + "321", "iamnotapod", "iamnotanode", "iamnotanamespace", "iamnotadeployment"); - spanProcessor.accept(differentTokenSpan); - spanProcessor.accept(firstOccurenceSpan); - spanProcessor.accept(secondOccurenceSpan); - spanProcessor.accept(otherSpan); + spanProcessor.accept(firstSpanOfFirstBucket); + spanProcessor.accept(firstSpanOfSecondBuckec); + spanProcessor.accept(firstSpanOfThirdBucket); + spanProcessor.accept(secondSpanOfSecondBucket); final Response response = given().pathParam("token", uuidExpected).when() .get("/v2/landscapes/{token}/timestamps"); @@ -76,30 +79,235 @@ void testLoadAllTimestampsForToken() { Assertions.assertEquals(2, resultList.size()); // Check that there are the correct timestamp buckets with correct span count - Optional optionalTimestamp = - resultList.stream().filter(timestamp -> timestamp.epochMilli() == 1702545560000L) - .findFirst(); - - if (optionalTimestamp.isEmpty()) { - Assertions.fail( - "Found no timestamp for time bucket 1702545560000L, but there should be one."); - } else { - Assertions.assertEquals(optionalTimestamp.get().spanCount(), 2); - } + // Check that there are the correct timestamp buckets with correct span count + Map expectedValuesMap = new HashMap() { + { + put(1702545560000L, 2); + put(1702545570000L, 1); + } + }; - optionalTimestamp = - resultList.stream().filter(timestamp -> timestamp.epochMilli() == 1702545570000L) - .findFirst(); + for (final Map.Entry entry : expectedValuesMap.entrySet()) { + long key = entry.getKey().longValue(); + int value = entry.getValue().intValue(); - if (optionalTimestamp.isEmpty()) { - Assertions.fail( - "Found no timestamp for time bucket 1702545570000L, but there should be one."); - } else { - Assertions.assertEquals(optionalTimestamp.get().spanCount(), 1); + Optional optionalTimestamp = resultList.stream().filter(timestamp -> timestamp.epochMilli() == key) + .findFirst(); + + if (optionalTimestamp.isEmpty()) { + Assertions.fail( + "Found no timestamp for time bucket " + key + ", but there should be one."); + } else { + Assertions.assertEquals(value, optionalTimestamp.get().spanCount()); + } } } + @Test + void testLoadNewerTimestampsForToken() { + final long firstBucketStart = 1702545554404L; + final long firstBucketEnd = firstBucketStart + 1000; + final long secondBucketStart = 1702545564404L; + final long secondBucketEnd = secondBucketStart + 1000; + final long thirdBucketStart = secondBucketStart + 10_000; + final long thirdBucketEnd = thirdBucketStart + 1000; + + final UUID uuidExpected = UUID.randomUUID(); -} + final PersistenceSpan firstSpanOfFirstBucket = new PersistenceSpan(uuidExpected, gitCommitChecksum, "0123L", "", + "1L", firstBucketStart, firstBucketEnd, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class.myMethod()", "847"); + + final String duplicateMethodName = "myMethodName()"; + final String otherMethodName = "myOtherMethodName()"; + + final PersistenceSpan firstSpanOfSecondBuckec = new PersistenceSpan(uuidExpected, gitCommitChecksum, "123L", "", + "1L", secondBucketStart, + secondBucketEnd, + "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + + final PersistenceSpan firstSpanOfThirdBucket = new PersistenceSpan(uuidExpected, gitCommitChecksum, "789L", "", + "3L", thirdBucketStart, + thirdBucketEnd, + "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + + final PersistenceSpan secondSpanOfSecondBucket = new PersistenceSpan(uuidExpected, gitCommitChecksum, "456L", "0L", + "", secondBucketStart, + secondBucketEnd, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + otherMethodName, + "321"); + + spanProcessor.accept(firstSpanOfFirstBucket); + spanProcessor.accept(firstSpanOfSecondBuckec); + spanProcessor.accept(firstSpanOfThirdBucket); + spanProcessor.accept(secondSpanOfSecondBucket); + + final Response response = given().pathParam("token", uuidExpected).queryParam("newest", firstBucketStart - 10000) + .when() + .get("/v2/landscapes/{token}/timestamps"); + + final List resultList = response.getBody().as(new TypeRef>() { + }); + + // System.out.println("HIER DA " + Arrays.deepToString(resultList.toArray())); + + Assertions.assertEquals(3, resultList.size()); + + // Check that there are the correct timestamp buckets with correct span count + Map expectedValuesMap = new HashMap() { + { + put(1702545550000L, 1); + put(1702545560000L, 2); + put(1702545570000L, 1); + } + }; + + testResultListAgainstExpectedValues(resultList, expectedValuesMap); + } + + @Test + void testLoadAllTimestampsForTokenAndCommit() { + final long firstBucketStart = 1702545554404L; + final long firstBucketEnd = firstBucketStart + 1000; + final long secondBucketStart = 1702545564404L; + final long secondBucketEnd = secondBucketStart + 1000; + final long thirdBucketStart = secondBucketStart + 10_000; + final long thirdBucketEnd = thirdBucketStart + 1000; + + final UUID uuidExpected = UUID.randomUUID(); + final String expectedCommit = "testCommit"; + + final PersistenceSpan firstSpanOfFirstBucket = new PersistenceSpan(uuidExpected, "notTestCommit-1", "0123L", "", + "1L", firstBucketStart, firstBucketEnd, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class.myMethod()", "847"); + + final String duplicateMethodName = "myMethodName()"; + final String otherMethodName = "myOtherMethodName()"; + + final PersistenceSpan firstSpanOfSecondBuckec = new PersistenceSpan(uuidExpected, expectedCommit, "123L", "", "1L", + secondBucketStart, + secondBucketEnd, + "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + + final PersistenceSpan firstSpanOfThirdBucket = new PersistenceSpan(uuidExpected, "notTestCommit-2", "789L", "", + "3L", thirdBucketStart, + thirdBucketEnd, + "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + + final PersistenceSpan secondSpanOfSecondBucket = new PersistenceSpan(uuidExpected, "notTestCommit-2", "456L", "0L", + "", secondBucketStart, + secondBucketEnd, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + otherMethodName, + "321"); + + spanProcessor.accept(firstSpanOfFirstBucket); + spanProcessor.accept(firstSpanOfSecondBuckec); + spanProcessor.accept(firstSpanOfThirdBucket); + spanProcessor.accept(secondSpanOfSecondBucket); + final Response response = given().pathParam("token", uuidExpected).queryParam("commit", expectedCommit) + .when() + .get("/v2/landscapes/{token}/timestamps"); + + final List resultList = response.getBody().as(new TypeRef>() { + }); + + Assertions.assertEquals(1, resultList.size()); + + // Check that there are the correct timestamp buckets with correct span count + Map expectedValuesMap = new HashMap() { + { + put(1702545560000L, 1); + } + }; + + testResultListAgainstExpectedValues(resultList, expectedValuesMap); + } + + @Test + void testLoadNewerTimestampsForTokenAndCommit() { + final long firstBucketStart = 1702545554404L; + final long firstBucketEnd = firstBucketStart + 1000; + final long secondBucketStart = 1702545564404L; + final long secondBucketEnd = secondBucketStart + 1000; + final long thirdBucketStart = secondBucketStart + 10_000; + final long thirdBucketEnd = thirdBucketStart + 1000; + + final UUID uuidExpected = UUID.randomUUID(); + final String expectedCommit = "testCommit"; + + final PersistenceSpan firstSpanOfFirstBucket = new PersistenceSpan(uuidExpected, expectedCommit, "0123L", "", + "1L", firstBucketStart, firstBucketEnd, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class.myMethod()", "847"); + + final String duplicateMethodName = "myMethodName()"; + final String otherMethodName = "myOtherMethodName()"; + + final PersistenceSpan firstSpanOfSecondBuckec = new PersistenceSpan(uuidExpected, expectedCommit, "123L", "", "1L", + secondBucketStart, + secondBucketEnd, + "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + + final PersistenceSpan firstSpanOfThirdBucket = new PersistenceSpan(uuidExpected, expectedCommit, "789L", "", "3L", + thirdBucketStart, + thirdBucketEnd, + "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + duplicateMethodName, "847"); + final PersistenceSpan secondSpanOfSecondBucket = new PersistenceSpan(uuidExpected, expectedCommit, "456L", "0L", "", + secondBucketStart + 1000, + secondBucketEnd + 1000, "nodeIp", "host-name", "app-name", "java", 0, + "net.explorviz.Class." + otherMethodName, + "321"); + + spanProcessor.accept(firstSpanOfFirstBucket); + spanProcessor.accept(firstSpanOfSecondBuckec); + spanProcessor.accept(firstSpanOfThirdBucket); + spanProcessor.accept(secondSpanOfSecondBucket); + + final Response response = given().pathParam("token", uuidExpected) + .queryParam("newest", secondBucketEnd - 10000) + .queryParam("commit", expectedCommit) + .when() + .get("/v2/landscapes/{token}/timestamps"); + + final List resultList = response.getBody().as(new TypeRef>() { + }); + + Assertions.assertEquals(2, resultList.size()); + + // Check that there are the correct timestamp buckets with correct span count + Map expectedValuesMap = new HashMap() { + { + put(1702545560000L, 2); + put(1702545570000L, 1); + } + }; + + testResultListAgainstExpectedValues(resultList, expectedValuesMap); + } + + private void testResultListAgainstExpectedValues(final List resultList, + final Map expectedValuesMap) { + for (Map.Entry entry : expectedValuesMap.entrySet()) { + long key = entry.getKey(); + int value = entry.getValue(); + + Optional optionalTimestamp = resultList.stream().filter(timestamp -> timestamp.epochMilli() == key) + .findFirst(); + + if (optionalTimestamp.isEmpty()) { + Assertions.fail( + "Found no timestamp for time bucket " + key + ", but there should be one."); + } else { + Assertions.assertEquals(value, optionalTimestamp.get().spanCount()); + } + } + } + +} diff --git a/src/integrationTest/java/net/explorviz/span/persistence/TraceLoaderIt.java b/src/integrationTest/java/net/explorviz/span/persistence/TraceLoaderIt.java index 6bc6126..b1e98a5 100644 --- a/src/integrationTest/java/net/explorviz/span/persistence/TraceLoaderIt.java +++ b/src/integrationTest/java/net/explorviz/span/persistence/TraceLoaderIt.java @@ -41,30 +41,32 @@ void testLoadTracesByTimeRange() { final UUID landscapeToken = UUID.randomUUID(); - final PersistenceSpan earlySpan = - new PersistenceSpan(landscapeToken, "123L", "", "1L", startEarly, - endEarly, "nodeIp", "app-name", "java", 0, "net.explorviz.Class.myMethod()", - "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); + final String gitCommitChecksum = "testGitCommitChecksum"; - final PersistenceSpan expectedSpan = - new PersistenceSpan(landscapeToken, "456L", "", "2L", startExpected, - endExpected, "nodeIp", "app-name", "java", 0, "net.explorviz.Class.myMethod()", - "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); + final PersistenceSpan earlySpan = new PersistenceSpan(landscapeToken, gitCommitChecksum, "123L", "", "1L", + startEarly, + endEarly, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class.myMethod()", + "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); - final PersistenceSpan lateSpan = - new PersistenceSpan(landscapeToken, "789L", "", "3L", startLate, - endLate, "nodeIp", "app-name", "java", 0, "net.explorviz.Class.myMethod()", - "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); + final PersistenceSpan expectedSpan = new PersistenceSpan(landscapeToken, gitCommitChecksum, "456L", "", "2L", + startExpected, + endExpected, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class.myMethod()", + "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); + + final PersistenceSpan lateSpan = new PersistenceSpan(landscapeToken, gitCommitChecksum, "789L", "", "3L", startLate, + endLate, "nodeIp", "host-name", "app-name", "java", 0, "net.explorviz.Class.myMethod()", + "847", "iamapod", "iamanode", "iamanamespace", "iamadeployment"); spanProcessor.accept(earlySpan); spanProcessor.accept(expectedSpan); spanProcessor.accept(lateSpan); - List result = - traceLoader.loadTracesStartingInRange(landscapeToken, 1701081830000L).collect().asList() - .await().indefinitely(); + List result = traceLoader.loadTracesStartingInRange(landscapeToken, 1701081830000L).collect().asList() + .await().indefinitely(); Assertions.assertEquals(1, result.size(), "List of traces has wrong size."); + Assertions.assertEquals(9, result.get(0).getClass().getDeclaredFields().length, + "Trace has wrong number of fields."); Assertions.assertEquals(1, result.get(0).spanList().size(), "List of spans has wrong size."); Assertions.assertEquals(convertPersistenceSpanToSpan(expectedSpan), result.get(0).spanList().get(0), "Wrong span in trace."); diff --git a/src/main/avro/ExplorvizProtocol.avdl b/src/main/avro/ExplorvizProtocol.avdl index 5f4a1d6..8267839 100644 --- a/src/main/avro/ExplorvizProtocol.avdl +++ b/src/main/avro/ExplorvizProtocol.avdl @@ -24,6 +24,7 @@ protocol ExplorvizProtocol { record Span { string landscapeToken; + string gitCommitChecksum = "cross-commit"; string spanId; string parentSpanId; string traceId; diff --git a/src/main/java/net/explorviz/span/api/LandscapeResource.java b/src/main/java/net/explorviz/span/api/LandscapeResource.java index f10c62a..d933a85 100644 --- a/src/main/java/net/explorviz/span/api/LandscapeResource.java +++ b/src/main/java/net/explorviz/span/api/LandscapeResource.java @@ -91,6 +91,7 @@ public Multi getDynamic(@PathParam("token") final String token, return traceLoader.loadAllTraces(parseUuid(token)); } + // ATTENTION: For the moment, we only filter based on the starting point of traces return traceLoader.loadTracesStartingInRange(parseUuid(token), from); } diff --git a/src/main/java/net/explorviz/span/api/TimestampResource.java b/src/main/java/net/explorviz/span/api/TimestampResource.java index f0a55c0..14d4f70 100644 --- a/src/main/java/net/explorviz/span/api/TimestampResource.java +++ b/src/main/java/net/explorviz/span/api/TimestampResource.java @@ -9,28 +9,42 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; +import java.util.Optional; import java.util.UUID; import net.explorviz.span.persistence.PersistenceSpan; import net.explorviz.span.timestamp.Timestamp; import net.explorviz.span.timestamp.TimestampLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Path("/v2/landscapes") @Produces(MediaType.APPLICATION_JSON) public class TimestampResource { - @Inject + private static final Logger LOGGER = LoggerFactory.getLogger(TimestampResource.class); + + TimestampLoader timestampLoader; + @Inject + public TimestampResource(final TimestampLoader timestampLoader) { + this.timestampLoader = timestampLoader; + } + @GET @Path("/{token}/timestamps") - public Multi getStructure(@PathParam("token") final String token, - @QueryParam("newest") final long newest, @QueryParam("oldest") final long oldest) { + public Multi getTimestamps(@PathParam("token") final String token, + @QueryParam("newest") final long newest, @QueryParam("oldest") final long oldest, + @QueryParam("commit") final Optional commit) { + LOGGER.atInfo().addArgument(token).addArgument(commit.orElse("all-commits")) + .addArgument(newest).log("Loading timestamps for token {}, commit {}, and from epoch {}"); + if (newest == 0 && oldest == 0) { - return this.timestampLoader.loadAllTimestampsForToken(parseUuid(token)); + return this.timestampLoader.loadAllTimestampsForToken(parseUuid(token), commit); } if (newest != 0) { - return this.timestampLoader.loadNewerTimestampsForToken(parseUuid(token), newest); + return this.timestampLoader.loadNewerTimestampsForToken(parseUuid(token), newest, commit); } return Multi.createFrom().empty(); } diff --git a/src/main/java/net/explorviz/span/application/V2ApiApplication.java b/src/main/java/net/explorviz/span/application/V2ApiApplication.java index 63850af..16a0fe9 100644 --- a/src/main/java/net/explorviz/span/application/V2ApiApplication.java +++ b/src/main/java/net/explorviz/span/application/V2ApiApplication.java @@ -5,7 +5,7 @@ import org.eclipse.microprofile.openapi.annotations.info.Info; @OpenAPIDefinition(info = @Info(title = "ExplorViz Span API", - description = "Exposes endpoints to retrieve spans stored in this ExplorViz instance.", + description = "Exposes endpoints to retrieve spans stored in this ExplorViz instanceId.", version = "2.0")) public class V2ApiApplication extends Application { diff --git a/src/main/java/net/explorviz/span/hash/HighwayHash.java b/src/main/java/net/explorviz/span/hash/HighwayHash.java index c2b17db..9c1cea6 100644 --- a/src/main/java/net/explorviz/span/hash/HighwayHash.java +++ b/src/main/java/net/explorviz/span/hash/HighwayHash.java @@ -82,7 +82,7 @@ public void updatePacket(final byte[] packet, final int pos) { */ public void update(final long a0, final long a1, final long a2, final long a3) { if (done) { - throw new IllegalStateException("Can compute a hash only once per instance"); + throw new IllegalStateException("Can compute a hash only once per instanceId"); } v1[0] += mul0[0] + a0; v1[1] += mul0[1] + a1; diff --git a/src/main/java/net/explorviz/span/landscape/Application.java b/src/main/java/net/explorviz/span/landscape/Application.java index 25357ff..3008212 100644 --- a/src/main/java/net/explorviz/span/landscape/Application.java +++ b/src/main/java/net/explorviz/span/landscape/Application.java @@ -5,7 +5,7 @@ public record Application( String name, String language, - int instance, // TODO: Deviation from frontend, expects `String instanceId` + int instanceId, // TODO: Deviation from frontend, expects `String instanceId` List packages ) { diff --git a/src/main/java/net/explorviz/span/landscape/Node.java b/src/main/java/net/explorviz/span/landscape/Node.java index cf69a38..276414e 100644 --- a/src/main/java/net/explorviz/span/landscape/Node.java +++ b/src/main/java/net/explorviz/span/landscape/Node.java @@ -4,7 +4,7 @@ public record Node( String ipAddress, - // TODO: Deviation from frontend, missing `String hostName` + String hostName, List applications ) { diff --git a/src/main/java/net/explorviz/span/landscape/assembler/impl/AssemblyUtils.java b/src/main/java/net/explorviz/span/landscape/assembler/impl/AssemblyUtils.java index 0dfa7ef..d30dff8 100644 --- a/src/main/java/net/explorviz/span/landscape/assembler/impl/AssemblyUtils.java +++ b/src/main/java/net/explorviz/span/landscape/assembler/impl/AssemblyUtils.java @@ -38,13 +38,13 @@ public static Optional findNode(final Landscape landscape, final String ip * Searches for an {@link Application} in a node. * * @param node the node - * @param instance the instance id of the application to search for + * @param instance the instanceId id of the application to search for * @return an optional that contains the app if it is included in the node, and is empty otherwise */ public static Optional findApplication(final Node node, final String name, final int instance) { for (final Application a : node.applications()) { - if (a.instance() == instance && a.name().equals(name)) { + if (a.instanceId() == instance && a.name().equals(name)) { return Optional.of(a); } } diff --git a/src/main/java/net/explorviz/span/landscape/assembler/impl/DefaultLandscapeAssembler.java b/src/main/java/net/explorviz/span/landscape/assembler/impl/DefaultLandscapeAssembler.java index 7a6693c..17bccea 100644 --- a/src/main/java/net/explorviz/span/landscape/assembler/impl/DefaultLandscapeAssembler.java +++ b/src/main/java/net/explorviz/span/landscape/assembler/impl/DefaultLandscapeAssembler.java @@ -73,7 +73,7 @@ private Node getNodeForRecord(final Landscape landscape, final LandscapeRecord r if (foundNode.isPresent()) { node = foundNode.get(); } else { - node = new Node(ipAddress, new ArrayList<>()); + node = new Node(ipAddress, record.hostName(), new ArrayList<>()); landscape.nodes().add(node); } @@ -135,7 +135,7 @@ private Optional getK8sConstructsForRecord(final LandscapeRecord final String applicationLanguage = record.applicationLanguage(); var app = pod.applications().stream() .filter( - a -> Objects.equals(a.name(), applicationName) && a.instance() == applicationInstance) + a -> Objects.equals(a.name(), applicationName) && a.instanceId() == applicationInstance) .findFirst().orElse(null); if (app == null) { app = new Application(applicationName, applicationLanguage, applicationInstance, diff --git a/src/main/java/net/explorviz/span/landscape/loader/LandscapeRecord.java b/src/main/java/net/explorviz/span/landscape/loader/LandscapeRecord.java index 998a678..2a4a658 100644 --- a/src/main/java/net/explorviz/span/landscape/loader/LandscapeRecord.java +++ b/src/main/java/net/explorviz/span/landscape/loader/LandscapeRecord.java @@ -9,6 +9,7 @@ public record LandscapeRecord( UUID landscapeToken, String methodHash, String nodeIpAddress, + String hostName, String applicationName, String applicationLanguage, int applicationInstance, @@ -19,13 +20,13 @@ public record LandscapeRecord( String k8sNodeName, String k8sNamespace, String k8sDeploymentName, - long timeSeen -) { + long timeSeen) { public static LandscapeRecord fromRow(final Row row) { final UUID landscapeToken = row.getUuid("landscape_token"); final String methodHash = row.getString("method_hash"); final String nodeIpAddress = row.getString("node_ip_address"); + final String hostName = row.getString("host_name"); final String applicationName = row.getString("application_name"); final String applicationLanguage = row.getString("application_language"); final int applicationInstance = row.getInt("application_instance"); @@ -36,20 +37,19 @@ public static LandscapeRecord fromRow(final Row row) { final String k8sNamespace = row.getString("k8s_namespace"); final String k8sDeploymentName = row.getString("k8s_deployment_name"); - // TODO: Error handling /* - * By definition getFullyQualifiedOperationName().split("."): Last entry is method name, next to + * By definition getFullyQualifiedOperationName().split("."): Last entry is + * method name, next to * last is class name, remaining elements form the package name */ final String[] operationFqnSplit = methodFqn.split("\\."); - final String packageName = - String.join(".", Arrays.copyOf(operationFqnSplit, operationFqnSplit.length - 2)); + final String packageName = String.join(".", Arrays.copyOf(operationFqnSplit, operationFqnSplit.length - 2)); final String className = operationFqnSplit[operationFqnSplit.length - 2]; final String methodName = operationFqnSplit[operationFqnSplit.length - 1]; - return new LandscapeRecord(landscapeToken, methodHash, nodeIpAddress, applicationName, + return new LandscapeRecord(landscapeToken, methodHash, nodeIpAddress, hostName, applicationName, applicationLanguage, applicationInstance, packageName, className, methodName, k8sPodName, k8sNodeName, k8sNamespace, k8sDeploymentName, timeSeen); } diff --git a/src/main/java/net/explorviz/span/persistence/PersistenceSpan.java b/src/main/java/net/explorviz/span/persistence/PersistenceSpan.java index 5e3ee79..dda5d82 100644 --- a/src/main/java/net/explorviz/span/persistence/PersistenceSpan.java +++ b/src/main/java/net/explorviz/span/persistence/PersistenceSpan.java @@ -4,12 +4,14 @@ public record PersistenceSpan( UUID landscapeToken, + String gitCommitChecksum, String spanId, String parentSpanId, String traceId, long startTime, long endTime, String nodeIpAddress, // TODO: Convert into InetAddress type? + String hostName, String applicationName, String applicationLanguage, int applicationInstance, diff --git a/src/main/java/net/explorviz/span/persistence/PersistenceSpanProcessor.java b/src/main/java/net/explorviz/span/persistence/PersistenceSpanProcessor.java index de0f2b6..7316ab7 100644 --- a/src/main/java/net/explorviz/span/persistence/PersistenceSpanProcessor.java +++ b/src/main/java/net/explorviz/span/persistence/PersistenceSpanProcessor.java @@ -38,6 +38,8 @@ public class PersistenceSpanProcessor implements Consumer { private final PreparedStatement insertTraceByTimeStatement; private final PreparedStatement insertSpanStructureStatement; private final PreparedStatement updateSpanBucketCounter; + private final PreparedStatement updateSpanBucketCounterForCommits; + @Inject public PersistenceSpanProcessor(final QuarkusCqlSession session) { @@ -55,17 +57,21 @@ public PersistenceSpanProcessor(final QuarkusCqlSession session) { + "(landscape_token, method_hash, time_bucket, trace_id) " + "VALUES (?, ?, ?, ?)");*/ this.insertTraceByTimeStatement = session.prepare("INSERT INTO trace_by_time " - + "(landscape_token, tenth_second_epoch, start_time, end_time, trace_id) " - + "VALUES (?, ?, ?, ?, ?)"); + + "(landscape_token, git_commit_checksum, tenth_second_epoch, " + + "start_time, end_time, trace_id) " + + "VALUES (?, ?, ?, ?, ?, ?)"); this.insertSpanStructureStatement = session.prepare("INSERT INTO span_structure " - + "(landscape_token, method_hash, node_ip_address, application_name, application_language, " - + "application_instance, method_fqn, time_seen, " + + "(landscape_token, method_hash, node_ip_address, host_name, application_name, " + + "application_language, application_instance, method_fqn, time_seen, " + "k8s_pod_name, k8s_node_name, k8s_namespace, k8s_deployment_name) " - + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + "USING TIMESTAMP ?"); this.updateSpanBucketCounter = session.prepare("UPDATE span_count_per_time_bucket_and_token " + "SET span_count = span_count + 1 " + "WHERE landscape_token = ? AND tenth_second_epoch = ?"); + this.updateSpanBucketCounterForCommits = session.prepare("UPDATE " + + "span_count_for_token_and_commit_and_time_bucket SET span_count = span_count + 1 " + + "WHERE landscape_token = ? AND git_commit_checksum = ? AND tenth_second_epoch = ?"); } @Override @@ -93,17 +99,25 @@ public void accept(final PersistenceSpan span) { private void updateSpanBucketCounter(final PersistenceSpan span) { final long tenSecondBucket = span.startTime() - (span.startTime() % 10_000); - final BoundStatement updateStmt = + BoundStatement updateStmt = this.updateSpanBucketCounter.bind(span.landscapeToken(), tenSecondBucket); this.session.executeAsync(updateStmt); + + updateStmt = + this.updateSpanBucketCounterForCommits.bind(span.landscapeToken(), span.gitCommitChecksum(), + tenSecondBucket + ); + + this.session.executeAsync(updateStmt); } private void insertSpanStructure(final PersistenceSpan span) { final BoundStatement stmtStructure = insertSpanStructureStatement.bind(span.landscapeToken(), span.methodHash(), - span.nodeIpAddress(), span.applicationName(), span.applicationLanguage(), + span.nodeIpAddress(), span.hostName(), span.applicationName(), + span.applicationLanguage(), span.applicationInstance(), span.methodFqn(), span.startTime(), span.k8sPodName(), span.k8sNodeName(), span.k8sNamespace(), span.k8sDeploymentName(), Instant.now().toEpochMilli()); @@ -169,7 +183,8 @@ private void insertTrace(final PersistenceSpan span) { final long tenSecondBucket = span.startTime() - (span.startTime() % 10_000); final BoundStatement stmtByTime = - insertTraceByTimeStatement.bind(span.landscapeToken(), tenSecondBucket, + insertTraceByTimeStatement.bind(span.landscapeToken(), span.gitCommitChecksum(), + tenSecondBucket, span.startTime(), span.endTime(), span.traceId()); session.executeAsync(stmtByTime).whenComplete((result, failure) -> { diff --git a/src/main/java/net/explorviz/span/persistence/SpanConverter.java b/src/main/java/net/explorviz/span/persistence/SpanConverter.java index 421c67d..aaecee5 100644 --- a/src/main/java/net/explorviz/span/persistence/SpanConverter.java +++ b/src/main/java/net/explorviz/span/persistence/SpanConverter.java @@ -12,6 +12,7 @@ public class SpanConverter implements ValueMapper { @Override public PersistenceSpan apply(final Span span) { final String landscapeTokenRaw = span.getLandscapeToken(); + final String gitCommitChecksum = span.getGitCommitChecksum(); // TODO: Remove invalid UUID hotfix UUID landscapeToken = PersistenceSpan.DEFAULT_UUID; if (!"mytokenvalue".equals(landscapeTokenRaw)) { @@ -21,6 +22,7 @@ public PersistenceSpan apply(final Span span) { final long startTime = span.getStartTimeEpochMilli(); final long endTime = span.getEndTimeEpochMilli(); final String nodeIpAddress = span.getHostIpAddress(); + final String nodeHostName = span.getHostname(); final String applicationName = span.getAppName(); final int applicationInstance = Integer.parseInt(span.getAppInstanceId()); final String applicationLanguage = span.getAppLanguage(); @@ -30,14 +32,14 @@ public PersistenceSpan apply(final Span span) { final String k8sNamespace = span.getK8sNamespace(); final String k8sDeploymentName = span.getK8sDeploymentName(); - final String methodHashCode = - HashHelper.calculateSpanHash(landscapeToken, nodeIpAddress, applicationName, - applicationInstance, methodFqn, k8sPodName, k8sNodeName, k8sNamespace, - k8sDeploymentName); + final String methodHashCode = HashHelper.calculateSpanHash(landscapeToken, nodeIpAddress, applicationName, + applicationInstance, methodFqn, k8sPodName, k8sNodeName, k8sNamespace, + k8sDeploymentName); - return new PersistenceSpan(landscapeToken, span.getSpanId(), span.getParentSpanId(), + return new PersistenceSpan(landscapeToken, gitCommitChecksum, span.getSpanId(), + span.getParentSpanId(), span.getTraceId(), startTime, endTime, - nodeIpAddress, applicationName, applicationLanguage, applicationInstance, methodFqn, - methodHashCode, k8sPodName, k8sNodeName, k8sNamespace, k8sDeploymentName); + nodeIpAddress, nodeHostName, applicationName, applicationLanguage, applicationInstance, + methodFqn, methodHashCode, k8sPodName, k8sNodeName, k8sNamespace, k8sDeploymentName); } } diff --git a/src/main/java/net/explorviz/span/timestamp/TimestampLoader.java b/src/main/java/net/explorviz/span/timestamp/TimestampLoader.java index 1c774dd..fc5a10d 100644 --- a/src/main/java/net/explorviz/span/timestamp/TimestampLoader.java +++ b/src/main/java/net/explorviz/span/timestamp/TimestampLoader.java @@ -5,6 +5,7 @@ import io.smallrye.mutiny.Multi; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import java.util.Optional; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +18,9 @@ public class TimestampLoader { private final QuarkusCqlSession session; private final PreparedStatement selectAllTimestampsForToken; + private final PreparedStatement selectAllTimestampsForTokenAndCommit; private final PreparedStatement selectNewerTimestampsForToken; + private final PreparedStatement selectNewerTimestampsForTokenAndCommit; @Inject public TimestampLoader(final QuarkusCqlSession session) { @@ -30,20 +33,49 @@ public TimestampLoader(final QuarkusCqlSession session) { this.selectNewerTimestampsForToken = session.prepare( "SELECT * " + "FROM span_count_per_time_bucket_and_token " + "WHERE landscape_token = ? AND tenth_second_epoch > ?"); + + this.selectAllTimestampsForTokenAndCommit = session.prepare( + "SELECT * " + "FROM span_count_for_token_and_commit_and_time_bucket " + + "WHERE landscape_token = ? AND git_commit_checksum = ?"); + + this.selectNewerTimestampsForTokenAndCommit = session.prepare( + "SELECT * " + "FROM span_count_for_token_and_commit_and_time_bucket " + + "WHERE landscape_token = ? AND git_commit_checksum = ? AND " + + "tenth_second_epoch > ?"); } - public Multi loadAllTimestampsForToken(final UUID landscapeToken) { - LOGGER.atTrace().addArgument(landscapeToken).log("Loading all timestamps for token {}"); + public Multi loadAllTimestampsForToken(final UUID landscapeToken, + final Optional commit) { + LOGGER.atTrace().addArgument(landscapeToken).addArgument(commit.orElse("all-commits")) + .log("Loading all timestamps for token {} and commit {}"); - return session.executeReactive(this.selectAllTimestampsForToken.bind(landscapeToken)) - .map(Timestamp::fromRow); + if (commit.isPresent()) { + return session.executeReactive( + this.selectAllTimestampsForTokenAndCommit.bind(landscapeToken, commit.get())) + .map(Timestamp::fromRow); + } else { + return session.executeReactive(this.selectAllTimestampsForToken.bind(landscapeToken)) + .map(Timestamp::fromRow); + } } - public Multi loadNewerTimestampsForToken(UUID landscapeToken, long newest) { + public Multi loadNewerTimestampsForToken(UUID landscapeToken, long newest, + Optional commit) { LOGGER.atTrace().addArgument(landscapeToken).addArgument(newest) - .log("Loading newer timestamps for token {} and newest timestamp {}."); + .addArgument(commit.orElse("all-commits")) + .log("Loading newer timestamps for token {} and newest timestamp {} and commit {}."); + + if (commit.isPresent()) { + return session.executeReactive( + this.selectNewerTimestampsForTokenAndCommit.bind(landscapeToken, commit.get(), + newest)) + .map(Timestamp::fromRow); + } else { + return session.executeReactive( + this.selectNewerTimestampsForToken.bind(landscapeToken, newest)) + .map(Timestamp::fromRow); + } + - return session.executeReactive(this.selectNewerTimestampsForToken.bind(landscapeToken, newest)) - .map(Timestamp::fromRow); } } diff --git a/src/main/java/net/explorviz/span/trace/Trace.java b/src/main/java/net/explorviz/span/trace/Trace.java index 5cfbe94..8166028 100644 --- a/src/main/java/net/explorviz/span/trace/Trace.java +++ b/src/main/java/net/explorviz/span/trace/Trace.java @@ -2,14 +2,13 @@ import com.datastax.oss.driver.api.core.cql.Row; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.UUID; public record Trace( UUID landscapeToken, String traceId, + String gitCommitChecksum, long startTime, long endTime, long duration, // TODO: Pointless? @@ -21,6 +20,7 @@ public record Trace( public static Trace fromRow(final Row row) { final UUID landscapeToken = row.getUuid("landscape_token"); final String traceId = row.getString("trace_id"); + final String gitCommitChecksum = row.getString("git_commit_checksum"); // TODO: Remove millisecond/nanosecond mismatch hotfix final long startTime = row.getLong("start_time"); final long endTime = row.getLong("end_time"); @@ -29,7 +29,8 @@ public static Trace fromRow(final Row row) { final int traceCount = 1; final List spanList = new ArrayList<>(); - return new Trace(landscapeToken, traceId, startTime, endTime, duration, overallRequestCount, + return new Trace(landscapeToken, traceId, gitCommitChecksum, startTime, endTime, duration, + overallRequestCount, traceCount, spanList); } diff --git a/src/main/resources/init_script.cql b/src/main/resources/init_script.cql index fc3e7c6..10f0bf5 100644 --- a/src/main/resources/init_script.cql +++ b/src/main/resources/init_script.cql @@ -24,6 +24,14 @@ CREATE TABLE IF NOT EXISTS explorviz.span_count_per_time_bucket_and_token PRIMARY KEY (landscape_token, tenth_second_epoch) ); +CREATE TABLE IF NOT EXISTS explorviz.span_count_for_token_and_commit_and_time_bucket ( + landscape_token UUID, + git_commit_checksum text, + tenth_second_epoch bigint, + span_count counter, + PRIMARY KEY ((landscape_token, git_commit_checksum), tenth_second_epoch) +); + /* SELECT trace_id FROM traceid_by_time WHERE landscape_token = X AND start_time_s = X; */ /* Get complete traces by their trace id */ @@ -55,6 +63,7 @@ CREATE TABLE IF NOT EXISTS explorviz.trace_by_hash CREATE TABLE IF NOT EXISTS explorviz.trace_by_time ( landscape_token uuid, + git_commit_checksum text, tenth_second_epoch bigint, start_time bigint, end_time bigint, @@ -70,6 +79,7 @@ CREATE TABLE IF NOT EXISTS explorviz.span_structure landscape_token uuid, method_hash text, node_ip_address text, + host_name text, application_name text, application_language text, application_instance int, diff --git a/src/test/java/net/explorviz/span/api/TimestampResourceTest.java b/src/test/java/net/explorviz/span/api/TimestampResourceTest.java new file mode 100644 index 0000000..cc969dc --- /dev/null +++ b/src/test/java/net/explorviz/span/api/TimestampResourceTest.java @@ -0,0 +1,66 @@ +package net.explorviz.span.api; + +import java.util.Optional; +import java.util.UUID; +import net.explorviz.span.persistence.PersistenceSpan; +import net.explorviz.span.timestamp.TimestampLoader; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class TimestampResourceTest { + + TimestampResource timestampResource; + TimestampLoader mockedTimestampLoader; + + @BeforeEach + public void setUp() { + mockedTimestampLoader = Mockito.mock(TimestampLoader.class); + this.timestampResource = new TimestampResource(mockedTimestampLoader); + } + + @Test + public void testGetAllTimestampsForToken() { + final String expectedToken = PersistenceSpan.DEFAULT_UUID.toString(); + + this.timestampResource.getTimestamps(expectedToken, 0L, 0L, Optional.empty()); + + Mockito.verify(mockedTimestampLoader, Mockito.times(1)) + .loadAllTimestampsForToken(UUID.fromString(expectedToken), Optional.empty()); + } + + @Test + public void testGetAllTimestampsForTokenAndCommit() { + final String expectedToken = PersistenceSpan.DEFAULT_UUID.toString(); + + this.timestampResource.getTimestamps(expectedToken, 0L, 0L, Optional.of("commit")); + + Mockito.verify(mockedTimestampLoader, Mockito.times(1)) + .loadAllTimestampsForToken(UUID.fromString(expectedToken), Optional.of("commit")); + } + + @Test + public void testGetNewerTimestampsForTokenAndCommit() { + final String expectedToken = PersistenceSpan.DEFAULT_UUID.toString(); + + this.timestampResource.getTimestamps(expectedToken, 1715367170000L, 0L, Optional.of("commit")); + + Mockito.verify(mockedTimestampLoader, Mockito.times(1)) + .loadNewerTimestampsForToken(UUID.fromString(expectedToken), 1715367170000L, + Optional.of("commit")); + } + + @Test + public void testGetNewerTimestampsForToken() { + final String expectedToken = PersistenceSpan.DEFAULT_UUID.toString(); + + this.timestampResource.getTimestamps(expectedToken, 1715367170000L, 0L, Optional.empty()); + + Mockito.verify(mockedTimestampLoader, Mockito.times(1)) + .loadNewerTimestampsForToken(UUID.fromString(expectedToken), 1715367170000L, + Optional.empty()); + } + + + +} diff --git a/src/test/java/net/explorviz/span/persistence/SpanConverterTest.java b/src/test/java/net/explorviz/span/persistence/SpanConverterTest.java new file mode 100644 index 0000000..b7d2537 --- /dev/null +++ b/src/test/java/net/explorviz/span/persistence/SpanConverterTest.java @@ -0,0 +1,62 @@ +package net.explorviz.span.persistence; + +import java.util.UUID; +import net.explorviz.avro.Span; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SpanConverterTest { + + SpanConverter converter; + + @BeforeEach + public void setUp() { + this.converter = new SpanConverter(); + } + + private Span sampleSpan() { + return Span.newBuilder() + .setLandscapeToken(PersistenceSpan.DEFAULT_UUID.toString()) + .setGitCommitChecksum("gitchecksum") + .setTraceId("50c246ad9c9883d1558df9f19b9ae7a6") + .setSpanId("7ef83c66eabd5fbb") + .setParentSpanId("7ef83c66efe42aaa") + .setHostIpAddress("1.2.3.4") + .setHostname("testhostname") + .setAppName("testappname") + .setAppInstanceId("42") + .setAppLanguage("java") + .setFullyQualifiedOperationName("asd.bfd") + .setHashCode("-909819732013219679") + .setStartTimeEpochMilli(1668069002431000000L) + .setEndTimeEpochMilli(1668072086000000000L) + .build(); + } + + private PersistenceSpan convertSpanToPersistenceSpan(final Span span) { + return new PersistenceSpan(UUID.fromString(span.getLandscapeToken()), + span.getGitCommitChecksum(), + span.getSpanId(), + span.getParentSpanId(), + span.getTraceId(), span.getStartTimeEpochMilli(), span.getEndTimeEpochMilli(), + span.getHostIpAddress(), span.getHostname(), span.getAppName(), span.getAppLanguage(), + Integer.valueOf(span.getAppInstanceId()), + span.getFullyQualifiedOperationName(), + span.getHashCode(), + span.getK8sPodName(), + span.getK8sNodeName(), + span.getK8sNamespace(), + span.getK8sDeploymentName()); + } + + @Test + public void testSpanToPersistenceSpanConversion() { + final Span testSpan = this.sampleSpan(); + final PersistenceSpan expectedSpan = this.convertSpanToPersistenceSpan(testSpan); + + PersistenceSpan resultSpan = this.converter.apply(testSpan); + + Assertions.assertEquals(expectedSpan, resultSpan); + } +}