-
Notifications
You must be signed in to change notification settings - Fork 16
Optimisation: Share state between view-generators #108
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When will this happen in view generators?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, just an additional check
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's not
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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
ViewGeneratorStateclassThere was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)