Skip to content
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 @@ -33,9 +33,9 @@ public class ApiTraceGraph {
EnrichedSpanConstants.getValue(AttributeValue.ATTRIBUTE_VALUE_UNKNOWN);

private List<ApiNode<Event>> nodeList;
private List<ApiNodeEventEdge> apiNodeEventEdgeList;
private StructuredTrace trace;
private Map<String, Integer> eventIdToIndexInTrace;
private final List<ApiNodeEventEdge> apiNodeEventEdgeList;
private final StructuredTrace trace;
private final Map<String, Integer> eventIdToIndexInTrace;

public ApiTraceGraph(StructuredTrace trace) {
this.trace = trace;
Expand All @@ -44,6 +44,10 @@ public ApiTraceGraph(StructuredTrace trace) {
eventIdToIndexInTrace = buildEventIdToIndexInTrace(trace);
}

public StructuredTrace getTrace() {
return trace;
}

public List<ApiNode<Event>> getNodeList() {
return nodeList;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hypertrace.core.viewgenerator.JavaCodeBasedViewGenerator;
import org.hypertrace.traceenricher.enrichedspan.constants.EnrichedSpanConstants;
import org.hypertrace.traceenricher.enrichedspan.constants.v1.CommonAttribute;
import org.hypertrace.viewgenerator.generators.ViewGeneratorState.TraceState;

/**
* Pre-processing for View Generators, for data that are mostly needed by the the view generators
Expand Down Expand Up @@ -66,43 +67,17 @@ static String getTransactionName(StructuredTrace trace) {
public List<OUT> process(StructuredTrace trace) {
DataflowMetricUtils.reportArrivalLagAndInsertTimestamp(trace, viewGeneratorArrivalTimer,
VIEW_GENERATION_ARRIVAL_TIME);
Map<String, Entity> entityMap = new HashMap<>();
Map<ByteBuffer, Event> eventMap = new HashMap<>();
Map<ByteBuffer, List<ByteBuffer>> parentToChildrenEventIds = new HashMap<>();
Map<ByteBuffer, ByteBuffer> childToParentEventIds = new HashMap<>();

for (Entity entity : trace.getEntityList()) {
entityMap.put(entity.getEntityId(), entity);
}
for (Event event : trace.getEventList()) {
eventMap.put(event.getEventId(), event);
}

for (Event event : trace.getEventList()) {
ByteBuffer childEventId = event.getEventId();
List<EventRef> eventRefs = eventMap.get(childEventId).getEventRefList();
if (eventRefs != null) {
eventRefs.stream()
.filter(eventRef -> EventRefType.CHILD_OF == eventRef.getRefType())
.forEach(
eventRef -> {
ByteBuffer parentEventId = eventRef.getEventId();
if (!parentToChildrenEventIds.containsKey(parentEventId)) {
parentToChildrenEventIds.put(parentEventId, new ArrayList<>());
}
parentToChildrenEventIds.get(parentEventId).add(childEventId);
childToParentEventIds.put(childEventId, parentEventId);
});
}
// expected only 1 childOf relationship
}
TraceState traceState = ViewGeneratorState.getTraceState(trace);
Map<String, Entity> entityMap = Collections.unmodifiableMap(traceState.getEntityMap());
Map<ByteBuffer, Event> eventMap = Collections.unmodifiableMap(traceState.getEventMap());
Map<ByteBuffer, List<ByteBuffer>> parentToChildrenEventIds = Collections.unmodifiableMap(traceState.getParentToChildrenEventIds());
Map<ByteBuffer, ByteBuffer> childToParentEventIds = Collections.unmodifiableMap(traceState.getChildToParentEventIds());

return generateView(
trace,
Collections.unmodifiableMap(entityMap),
Collections.unmodifiableMap(eventMap),
Collections.unmodifiableMap(parentToChildrenEventIds),
Collections.unmodifiableMap(childToParentEventIds));
entityMap, eventMap,
parentToChildrenEventIds,
childToParentEventIds);
}

abstract List<OUT> generateView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ List<RawServiceView> generateView(
List<RawServiceView> list = new ArrayList<>();

// Construct ApiTraceGraph and look at all the head spans within each ApiNode
ApiTraceGraph apiTraceGraph = new ApiTraceGraph(structuredTrace);
apiTraceGraph = apiTraceGraph.build();
ApiTraceGraph apiTraceGraph = ViewGeneratorState.getApiTraceGraph(structuredTrace);
Copy link
Contributor

@skjindal93 skjindal93 Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be easy not to make functions static, and use ViewGeneratorState as a singleton class. It would really simplify writing the unit tests for the class, because then you can easily mock ViewGeneratorState class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will give it a try, and if it works will make the changes in a subsequent pr.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is made a singleton, remember that it should be stateless :)


List<ApiNode<Event>> apiNodes = apiTraceGraph.getNodeList();
for (ApiNode<Event> apiNode : apiNodes) {
Event event = apiNode.getHeadEvent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ List<ServiceCallView> generateView(
Map<ByteBuffer, Event> eventMap,
Map<ByteBuffer, List<ByteBuffer>> parentToChildrenEventIds,
Map<ByteBuffer, ByteBuffer> childToParentEventIds) {
ApiTraceGraph apiTraceGraph = new ApiTraceGraph(structuredTrace).build();
ApiTraceGraph apiTraceGraph = ViewGeneratorState.getApiTraceGraph(structuredTrace);

// Scenario #1: Go through the apiNode edges and create a record for each edge. Should be easy
// to get to the events
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.hypertrace.viewgenerator.generators;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hypertrace.core.datamodel.Entity;
import org.hypertrace.core.datamodel.Event;
import org.hypertrace.core.datamodel.EventRef;
import org.hypertrace.core.datamodel.EventRefType;
import org.hypertrace.core.datamodel.StructuredTrace;
import org.hypertrace.traceenricher.trace.util.ApiTraceGraph;

public class ViewGeneratorState {

private static final ThreadLocal<TraceState> traceStateThreadLocal = new ThreadLocal<>();
private static final ThreadLocal<ApiTraceGraph> apiTraceGraphThreadLocal = new ThreadLocal<>();

public static ApiTraceGraph getApiTraceGraph(StructuredTrace trace) {
if (apiTraceGraphThreadLocal.get() == null
|| isDifferentTrace(apiTraceGraphThreadLocal.get().getTrace(), trace)) {
apiTraceGraphThreadLocal.set(new ApiTraceGraph(trace).build());
}
return apiTraceGraphThreadLocal.get();
}

public static TraceState getTraceState(StructuredTrace trace) {
if (traceStateThreadLocal.get() == null
|| isDifferentTrace(traceStateThreadLocal.get().getTrace(), trace)) {
traceStateThreadLocal.set(new TraceState(trace));
}
return traceStateThreadLocal.get();
}

private static boolean isDifferentTrace(StructuredTrace cached, StructuredTrace trace) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will this happen in view generators?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the same trace is getting processed by multiple view-generators, this would return false, and we save on some repeated operations.
When a new trace starts getting processed, this would return false, leading to rebuilding of TraceState and ApiTraceGraph

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mainly concerned/curious about the customer id check, but I guess that's just done for safety? Because, a trace being different means, a different trace id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, just an additional check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think checking for traceId is also not required, since the input trace doesn't get modified in this service. So all we need is to check for same object.

return cached != trace;
}

public static class TraceState {
private final StructuredTrace trace;
private final Map<String, Entity> entityMap = new HashMap<>();
private final Map<ByteBuffer, Event> eventMap = new HashMap<>();
private final Map<ByteBuffer, List<ByteBuffer>> parentToChildrenEventIds = new HashMap<>();
private final Map<ByteBuffer, ByteBuffer> childToParentEventIds = new HashMap<>();

public TraceState(StructuredTrace trace) {
this.trace = trace;
for (Entity entity : trace.getEntityList()) {
entityMap.put(entity.getEntityId(), entity);
}
for (Event event : trace.getEventList()) {
eventMap.put(event.getEventId(), event);
}

for (Event event : trace.getEventList()) {
ByteBuffer childEventId = event.getEventId();
List<EventRef> eventRefs = eventMap.get(childEventId).getEventRefList();
if (eventRefs != null) {
eventRefs.stream()
.filter(eventRef -> EventRefType.CHILD_OF == eventRef.getRefType())
.forEach(
eventRef -> {
ByteBuffer parentEventId = eventRef.getEventId();
parentToChildrenEventIds.computeIfAbsent(
parentEventId, v -> new ArrayList<>()).add(childEventId);
childToParentEventIds.put(childEventId, parentEventId);
});
}
// expected only 1 childOf relationship
}
}

public StructuredTrace getTrace() {
return trace;
}

public Map<String, Entity> getEntityMap() {
return entityMap;
}

public Map<ByteBuffer, Event> getEventMap() {
return eventMap;
}

public Map<ByteBuffer, List<ByteBuffer>> getParentToChildrenEventIds() {
return parentToChildrenEventIds;
}

public Map<ByteBuffer, ByteBuffer> getChildToParentEventIds() {
return childToParentEventIds;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package org.hypertrace.viewgenerator.generators;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import org.hypertrace.core.datamodel.Entity;
import org.hypertrace.core.datamodel.Event;
import org.hypertrace.core.datamodel.EventRef;
import org.hypertrace.core.datamodel.EventRefType;
import org.hypertrace.core.datamodel.StructuredTrace;
import org.hypertrace.traceenricher.trace.util.ApiTraceGraph;
import org.hypertrace.viewgenerator.generators.ViewGeneratorState.TraceState;
import org.junit.jupiter.api.Test;

public class ViewGeneratorStateTest {

ByteBuffer span1 = ByteBuffer.wrap(("span-1".getBytes())), span2 = ByteBuffer.wrap(("span-2".getBytes()));
String customerId = "customer-1";
ByteBuffer traceId1 = ByteBuffer.wrap(("trace-1".getBytes())), traceId2 = ByteBuffer.wrap(("trace-2".getBytes()));

@Test
public void testTraceState() {
TraceState traceState = new TraceState(getTestTrace(customerId, traceId1));
assertEquals(1, traceState.getEntityMap().size());
assertEquals(2, traceState.getEventMap().size());
assertEquals(1, traceState.getChildToParentEventIds().size());
assertEquals(1, traceState.getParentToChildrenEventIds().size());

assertTrue(traceState.getParentToChildrenEventIds().containsKey(span1));
assertEquals(1, traceState.getParentToChildrenEventIds().get(span1).size());
assertTrue(traceState.getChildToParentEventIds().containsKey(span2));
assertEquals(span1, traceState.getChildToParentEventIds().get(span2));
}

@Test
public void testGetTraceState() {
StructuredTrace trace = getTestTrace(customerId, traceId1);
TraceState traceState = ViewGeneratorState.getTraceState(trace);
assertNotNull(traceState);
assertEquals(trace, traceState.getTrace());

TraceState sameTraceState = ViewGeneratorState.getTraceState(trace);
assertEquals(sameTraceState, traceState);

StructuredTrace modifiedTrace = getTestTrace(customerId, traceId1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not modifiedTrace, its a new one, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it, it's the same one.

modifiedTrace.setEntityList(Arrays.asList(
Entity.newBuilder()
.setCustomerId(customerId)
.setEntityId("entity-2")
.setEntityName("entity-2")
.setEntityType("service")
.build()));

// same trace id but different object should result in rebuilding of trace state
TraceState differentTraceState1 = ViewGeneratorState.getTraceState(modifiedTrace);
assertNotEquals(traceState, differentTraceState1);

StructuredTrace differentTrace = getTestTrace(customerId, traceId2);
TraceState differentTraceState2 = ViewGeneratorState.getTraceState(differentTrace);
assertEquals(differentTrace, differentTraceState2.getTrace());
}

@Test
public void testGetApiTraceGraph() {
StructuredTrace trace = getTestTrace(customerId, traceId1);
ApiTraceGraph apiTraceGraph = ViewGeneratorState.getApiTraceGraph(trace);
assertNotNull(apiTraceGraph);

ApiTraceGraph sameApiTraceGraph = ViewGeneratorState.getApiTraceGraph(trace);
assertEquals(sameApiTraceGraph, apiTraceGraph);

StructuredTrace modifiedTrace = getTestTrace(customerId, traceId1);
modifiedTrace.setEntityList(Arrays.asList(
Entity.newBuilder()
.setCustomerId(customerId)
.setEntityId("entity-2")
.setEntityName("entity-2")
.setEntityType("service")
.build()));


// same trace id but different object should result in rebuilding of trace state
ApiTraceGraph differentApiTraceGraph1 = ViewGeneratorState.getApiTraceGraph(modifiedTrace);
assertNotEquals(apiTraceGraph, differentApiTraceGraph1);

StructuredTrace differentTrace = getTestTrace(customerId, traceId2);
ApiTraceGraph differentApiTraceGraph2 = ViewGeneratorState.getApiTraceGraph(differentTrace);
assertNotEquals(apiTraceGraph, differentApiTraceGraph2);
}

private StructuredTrace getTestTrace(String customerId, ByteBuffer traceId) {
return StructuredTrace.newBuilder()
.setCustomerId(customerId)
.setTraceId(traceId)
.setStartTimeMillis(20)
.setEndTimeMillis(30)
.setEntityList(Arrays.asList(
Entity.newBuilder()
.setCustomerId(customerId)
.setEntityId("entity-1")
.setEntityName("entity-1")
.setEntityType("service")
.build()))
.setEventList(Arrays.asList(
Event.newBuilder()
.setCustomerId(customerId)
.setEventId(ByteBuffer.wrap(("span-1".getBytes())))
.setEventName("span-1")
.build(),
Event.newBuilder()
.setCustomerId(customerId)
.setEventId(ByteBuffer.wrap(("span-2".getBytes())))
.setEventName("span-2")
.setEventRefList(Arrays.asList(
EventRef.newBuilder()
.setTraceId(traceId)
.setRefType(EventRefType.CHILD_OF)
.setEventId(ByteBuffer.wrap(("span-1".getBytes())))
.build()
))
.build()
))
.setEntityEdgeList(Collections.EMPTY_LIST)
.setEntityEventEdgeList(Collections.EMPTY_LIST)
.setEventEdgeList(Collections.EMPTY_LIST)
.setEntityEntityGraph(null)
.setEventEventGraph(null)
.setEntityEventGraph(null)
.setMetrics(null)
.build();
}
}