diff --git a/hypertrace-core-graphql-attribute-scope/src/main/java/org/hypertrace/core/graphql/atttributes/scopes/HypertraceCoreAttributeScopeModule.java b/hypertrace-core-graphql-attribute-scope/src/main/java/org/hypertrace/core/graphql/atttributes/scopes/HypertraceCoreAttributeScopeModule.java index 342f29b2..30e6478d 100644 --- a/hypertrace-core-graphql-attribute-scope/src/main/java/org/hypertrace/core/graphql/atttributes/scopes/HypertraceCoreAttributeScopeModule.java +++ b/hypertrace-core-graphql-attribute-scope/src/main/java/org/hypertrace/core/graphql/atttributes/scopes/HypertraceCoreAttributeScopeModule.java @@ -2,6 +2,7 @@ import static org.hypertrace.core.graphql.attributes.IdMapping.forForeignId; import static org.hypertrace.core.graphql.attributes.IdMapping.forId; +import static org.hypertrace.core.graphql.atttributes.scopes.HypertraceCoreAttributeScopeString.LOG_EVENT; import static org.hypertrace.core.graphql.atttributes.scopes.HypertraceCoreAttributeScopeString.SPAN; import static org.hypertrace.core.graphql.atttributes.scopes.HypertraceCoreAttributeScopeString.TRACE; @@ -21,7 +22,7 @@ protected void configure() { Multibinder idBinder = Multibinder.newSetBinder(binder(), IdMapping.class); idBinder.addBinding().toInstance(forId(SPAN, "id")); idBinder.addBinding().toInstance(forForeignId(SPAN, TRACE, "traceId")); - + idBinder.addBinding().toInstance(forForeignId(LOG_EVENT, SPAN, "spanId")); idBinder.addBinding().toInstance(forId(TRACE, "id")); } } diff --git a/hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/schema/attributes/AttributeQueryable.java b/hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/schema/attributes/AttributeQueryable.java index 6557abe6..c52da8df 100644 --- a/hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/schema/attributes/AttributeQueryable.java +++ b/hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/schema/attributes/AttributeQueryable.java @@ -6,6 +6,7 @@ import org.hypertrace.core.graphql.common.schema.attributes.arguments.AttributeKeyArgument; public interface AttributeQueryable { + String ATTRIBUTE_FIELD_NAME = "attribute"; @GraphQLField diff --git a/hypertrace-core-graphql-gateway-service-utils/build.gradle.kts b/hypertrace-core-graphql-gateway-service-utils/build.gradle.kts index 7c93ca31..ad696b43 100644 --- a/hypertrace-core-graphql-gateway-service-utils/build.gradle.kts +++ b/hypertrace-core-graphql-gateway-service-utils/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { api(project(":hypertrace-core-graphql-attribute-store")) api("io.reactivex.rxjava3:rxjava") api(project(":hypertrace-core-graphql-common-schema")) + implementation(project(":hypertrace-core-graphql-grpc-utils")) testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core") diff --git a/hypertrace-core-graphql-gateway-service-utils/src/main/java/org/hypertrace/core/graphql/utils/gateway/GatewayServiceFutureStubProvider.java b/hypertrace-core-graphql-gateway-service-utils/src/main/java/org/hypertrace/core/graphql/utils/gateway/GatewayServiceFutureStubProvider.java new file mode 100644 index 00000000..55363260 --- /dev/null +++ b/hypertrace-core-graphql-gateway-service-utils/src/main/java/org/hypertrace/core/graphql/utils/gateway/GatewayServiceFutureStubProvider.java @@ -0,0 +1,34 @@ +package org.hypertrace.core.graphql.utils.gateway; + +import io.grpc.CallCredentials; +import javax.inject.Inject; +import javax.inject.Provider; +import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig; +import org.hypertrace.core.graphql.utils.grpc.GrpcChannelRegistry; +import org.hypertrace.gateway.service.GatewayServiceGrpc; +import org.hypertrace.gateway.service.GatewayServiceGrpc.GatewayServiceFutureStub; + +class GatewayServiceFutureStubProvider implements Provider { + + private final GraphQlServiceConfig serviceConfig; + private final CallCredentials credentials; + private final GrpcChannelRegistry channelRegistry; + + @Inject + GatewayServiceFutureStubProvider( + GraphQlServiceConfig serviceConfig, + CallCredentials credentials, + GrpcChannelRegistry channelRegistry) { + this.serviceConfig = serviceConfig; + this.credentials = credentials; + this.channelRegistry = channelRegistry; + } + + @Override + public GatewayServiceFutureStub get() { + return GatewayServiceGrpc.newFutureStub( + channelRegistry.forAddress( + serviceConfig.getGatewayServiceHost(), serviceConfig.getGatewayServicePort())) + .withCallCredentials(credentials); + } +} diff --git a/hypertrace-core-graphql-gateway-service-utils/src/main/java/org/hypertrace/core/graphql/utils/gateway/GatewayUtilsModule.java b/hypertrace-core-graphql-gateway-service-utils/src/main/java/org/hypertrace/core/graphql/utils/gateway/GatewayUtilsModule.java index 3bbfcc31..bc57c149 100644 --- a/hypertrace-core-graphql-gateway-service-utils/src/main/java/org/hypertrace/core/graphql/utils/gateway/GatewayUtilsModule.java +++ b/hypertrace-core-graphql-gateway-service-utils/src/main/java/org/hypertrace/core/graphql/utils/gateway/GatewayUtilsModule.java @@ -2,6 +2,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Key; +import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import java.util.Collection; import java.util.List; @@ -15,6 +16,7 @@ import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderDirection; import org.hypertrace.core.graphql.common.utils.BiConverter; import org.hypertrace.core.graphql.common.utils.Converter; +import org.hypertrace.gateway.service.GatewayServiceGrpc.GatewayServiceFutureStub; import org.hypertrace.gateway.service.v1.common.ColumnIdentifier; import org.hypertrace.gateway.service.v1.common.Expression; import org.hypertrace.gateway.service.v1.common.Filter; @@ -62,5 +64,9 @@ protected void configure() { bind(Key.get(new TypeLiteral>() {})) .to(SortOrderConverter.class); + + bind(GatewayServiceFutureStub.class) + .toProvider(GatewayServiceFutureStubProvider.class) + .in(Singleton.class); } } diff --git a/hypertrace-core-graphql-log-event-schema/build.gradle.kts b/hypertrace-core-graphql-log-event-schema/build.gradle.kts index 5323b844..9b844437 100644 --- a/hypertrace-core-graphql-log-event-schema/build.gradle.kts +++ b/hypertrace-core-graphql-log-event-schema/build.gradle.kts @@ -28,6 +28,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation(project(":hypertrace-core-graphql-gateway-service-utils")) + testImplementation("org.mockito:mockito-core") + testImplementation("org.mockito:mockito-junit-jupiter") + testAnnotationProcessor("org.projectlombok:lombok") testCompileOnly("org.projectlombok:lombok") } diff --git a/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsRequestBuilderTest.java b/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsRequestBuilderTest.java index ba021f17..53aa4bff 100644 --- a/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsRequestBuilderTest.java +++ b/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsRequestBuilderTest.java @@ -1,11 +1,14 @@ package org.hypertrace.core.graphql.log.event.dao; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; +import io.grpc.CallCredentials; import java.time.Duration; import java.time.Instant; import java.util.Collection; @@ -18,7 +21,9 @@ import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument; import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; import org.hypertrace.core.graphql.common.utils.Converter; +import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig; import org.hypertrace.core.graphql.utils.gateway.GatewayUtilsModule; +import org.hypertrace.core.graphql.utils.grpc.GrpcChannelRegistry; import org.hypertrace.gateway.service.v1.common.Expression; import org.hypertrace.gateway.service.v1.common.Filter; import org.hypertrace.gateway.service.v1.common.OrderByExpression; @@ -32,7 +37,17 @@ class GatewayServiceLogEventsRequestBuilderTest extends BaseDaoTest { @BeforeEach void setup() { - Injector injector = Guice.createInjector(new GatewayUtilsModule()); + Injector injector = + Guice.createInjector( + new GatewayUtilsModule(), + new AbstractModule() { + @Override + protected void configure() { + bind(CallCredentials.class).toInstance(mock(CallCredentials.class)); + bind(GraphQlServiceConfig.class).toInstance(mock(GraphQlServiceConfig.class)); + bind(GrpcChannelRegistry.class).toInstance(mock(GrpcChannelRegistry.class)); + } + }); Converter>, Filter> filterConverter = injector.getInstance( diff --git a/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsResponseConverterTest.java b/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsResponseConverterTest.java index cbf93a9a..f8255252 100644 --- a/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsResponseConverterTest.java +++ b/hypertrace-core-graphql-log-event-schema/src/test/java/org/hypertrace/core/graphql/log/event/dao/GatewayServiceLogEventsResponseConverterTest.java @@ -1,11 +1,14 @@ package org.hypertrace.core.graphql.log.event.dao; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; +import io.grpc.CallCredentials; import java.time.Duration; import java.time.Instant; import java.util.Collection; @@ -16,7 +19,9 @@ import org.hypertrace.core.graphql.common.request.AttributeRequest; import org.hypertrace.core.graphql.common.utils.BiConverter; import org.hypertrace.core.graphql.log.event.schema.LogEventResultSet; +import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig; import org.hypertrace.core.graphql.utils.gateway.GatewayUtilsModule; +import org.hypertrace.core.graphql.utils.grpc.GrpcChannelRegistry; import org.hypertrace.gateway.service.v1.common.Value; import org.hypertrace.gateway.service.v1.common.ValueType; import org.hypertrace.gateway.service.v1.log.events.LogEvent; @@ -30,7 +35,17 @@ class GatewayServiceLogEventsResponseConverterTest extends BaseDaoTest { @BeforeEach void setup() { - Injector injector = Guice.createInjector(new GatewayUtilsModule()); + Injector injector = + Guice.createInjector( + new GatewayUtilsModule(), + new AbstractModule() { + @Override + protected void configure() { + bind(CallCredentials.class).toInstance(mock(CallCredentials.class)); + bind(GraphQlServiceConfig.class).toInstance(mock(GraphQlServiceConfig.class)); + bind(GrpcChannelRegistry.class).toInstance(mock(GrpcChannelRegistry.class)); + } + }); BiConverter, Map, Map> attributeMapConverter = injector.getInstance( diff --git a/hypertrace-core-graphql-span-schema/build.gradle.kts b/hypertrace-core-graphql-span-schema/build.gradle.kts index 664c7473..ab5ed343 100644 --- a/hypertrace-core-graphql-span-schema/build.gradle.kts +++ b/hypertrace-core-graphql-span-schema/build.gradle.kts @@ -22,4 +22,21 @@ dependencies { implementation(project(":hypertrace-core-graphql-grpc-utils")) implementation(project(":hypertrace-core-graphql-common-schema")) implementation(project(":hypertrace-core-graphql-attribute-store")) + implementation(project(":hypertrace-core-graphql-log-event-schema")) + implementation(project(":hypertrace-core-graphql-deserialization")) + implementation(project(":hypertrace-core-graphql-schema-utils")) + implementation(project(":hypertrace-core-graphql-attribute-scope-constants")) + + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("com.fasterxml.jackson.core:jackson-databind") + testImplementation(project(":hypertrace-core-graphql-gateway-service-utils")) + testImplementation("org.mockito:mockito-core") + testImplementation("org.mockito:mockito-junit-jupiter") + + testAnnotationProcessor("org.projectlombok:lombok") + testCompileOnly("org.projectlombok:lombok") +} + +tasks.test { + useJUnitPlatform() } diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/SpanSchemaModule.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/SpanSchemaModule.java index 5f69f194..bbc21668 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/SpanSchemaModule.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/SpanSchemaModule.java @@ -4,6 +4,7 @@ import com.google.inject.multibindings.Multibinder; import org.hypertrace.core.graphql.common.request.ResultSetRequestBuilder; import org.hypertrace.core.graphql.span.dao.SpanDaoModule; +import org.hypertrace.core.graphql.span.request.SpanRequestModule; import org.hypertrace.core.graphql.spi.schema.GraphQlSchemaFragment; public class SpanSchemaModule extends AbstractModule { @@ -15,5 +16,6 @@ protected void configure() { requireBinding(ResultSetRequestBuilder.class); install(new SpanDaoModule()); + install(new SpanRequestModule()); } } diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanConverter.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanConverter.java index faa91b9d..25f43029 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanConverter.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanConverter.java @@ -3,18 +3,20 @@ import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import javax.inject.Inject; import lombok.experimental.Accessors; import org.hypertrace.core.graphql.common.request.AttributeRequest; -import org.hypertrace.core.graphql.common.request.ResultSetRequest; import org.hypertrace.core.graphql.common.utils.BiConverter; +import org.hypertrace.core.graphql.log.event.schema.LogEvent; +import org.hypertrace.core.graphql.log.event.schema.LogEventResultSet; +import org.hypertrace.core.graphql.span.request.SpanRequest; import org.hypertrace.core.graphql.span.schema.Span; import org.hypertrace.core.graphql.span.schema.SpanResultSet; import org.hypertrace.gateway.service.v1.common.Value; import org.hypertrace.gateway.service.v1.span.SpanEvent; -import org.hypertrace.gateway.service.v1.span.SpansResponse; class GatewayServiceSpanConverter { @@ -28,22 +30,27 @@ class GatewayServiceSpanConverter { this.attributeMapConverter = attributeMapConverter; } - public Single convert(ResultSetRequest request, SpansResponse response) { - int total = response.getTotal(); + public Single convert(SpanRequest request, SpanLogEventsResponse response) { + int total = response.spansResponse().getTotal(); - return Observable.fromIterable(response.getSpansList()) - .flatMapSingle(spanEvent -> this.convert(request, spanEvent)) + return Observable.fromIterable(response.spansResponse().getSpansList()) + .flatMapSingle(spanEvent -> this.convert(request, spanEvent, response.spanIdToLogEvents())) .toList() .map(spans -> new ConvertedSpanResultSet(spans, total, spans.size())); } - private Single convert(ResultSetRequest request, SpanEvent spanEvent) { + private Single convert( + SpanRequest request, SpanEvent spanEvent, Map> spanIdToLogEvents) { return this.attributeMapConverter - .convert(request.attributes(), spanEvent.getAttributesMap()) + .convert(request.spanEventsRequest().attributes(), spanEvent.getAttributesMap()) .map( attrMap -> new ConvertedSpan( - attrMap.get(request.idAttribute().attribute().key()).toString(), attrMap)); + attrMap + .get(request.spanEventsRequest().idAttribute().attribute().key()) + .toString(), + attrMap, + spanIdToLogEvents)); } @lombok.Value @@ -51,11 +58,18 @@ private Single convert(ResultSetRequest request, SpanEvent spanEvent) { private static class ConvertedSpan implements Span { String id; Map attributeValues; + Map> spanIdToLogEvents; @Override public Object attribute(String key) { return this.attributeValues.get(key); } + + @Override + public LogEventResultSet logEvents() { + List list = spanIdToLogEvents.getOrDefault(id, Collections.emptyList()); + return new ConvertedLogEventResultSet(list, list.size(), list.size()); + } } @lombok.Value @@ -65,4 +79,12 @@ private static class ConvertedSpanResultSet implements SpanResultSet { long total; long count; } + + @lombok.Value + @Accessors(fluent = true) + private static class ConvertedLogEventResultSet implements LogEventResultSet { + List results; + long total; + long count; + } } diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanDao.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanDao.java index 07a1c8eb..f6d93a1a 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanDao.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanDao.java @@ -2,18 +2,13 @@ import static java.util.concurrent.TimeUnit.SECONDS; -import io.grpc.CallCredentials; import io.reactivex.rxjava3.core.Single; import javax.inject.Inject; import javax.inject.Singleton; -import org.hypertrace.core.graphql.common.request.ResultSetRequest; -import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; import org.hypertrace.core.graphql.context.GraphQlRequestContext; +import org.hypertrace.core.graphql.span.request.SpanRequest; import org.hypertrace.core.graphql.span.schema.SpanResultSet; -import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig; import org.hypertrace.core.graphql.utils.grpc.GraphQlGrpcContextBuilder; -import org.hypertrace.core.graphql.utils.grpc.GrpcChannelRegistry; -import org.hypertrace.gateway.service.GatewayServiceGrpc; import org.hypertrace.gateway.service.GatewayServiceGrpc.GatewayServiceFutureStub; import org.hypertrace.gateway.service.v1.span.SpansRequest; import org.hypertrace.gateway.service.v1.span.SpansResponse; @@ -25,32 +20,31 @@ class GatewayServiceSpanDao implements SpanDao { private final GraphQlGrpcContextBuilder grpcContextBuilder; private final GatewayServiceSpanRequestBuilder requestBuilder; private final GatewayServiceSpanConverter spanConverter; + private final SpanLogEventDao spanLogEventDao; @Inject GatewayServiceSpanDao( - GraphQlServiceConfig serviceConfig, - CallCredentials credentials, + GatewayServiceFutureStub gatewayServiceFutureStub, GraphQlGrpcContextBuilder grpcContextBuilder, - GrpcChannelRegistry channelRegistry, GatewayServiceSpanRequestBuilder requestBuilder, - GatewayServiceSpanConverter spanConverter) { + GatewayServiceSpanConverter spanConverter, + SpanLogEventDao spanLogEventDao) { this.grpcContextBuilder = grpcContextBuilder; this.requestBuilder = requestBuilder; this.spanConverter = spanConverter; - - this.gatewayServiceStub = - GatewayServiceGrpc.newFutureStub( - channelRegistry.forAddress( - serviceConfig.getGatewayServiceHost(), serviceConfig.getGatewayServicePort())) - .withCallCredentials(credentials); + this.spanLogEventDao = spanLogEventDao; + this.gatewayServiceStub = gatewayServiceFutureStub; } @Override - public Single getSpans(ResultSetRequest request) { + public Single getSpans(SpanRequest request) { return this.requestBuilder .buildRequest(request) - .flatMap(serverRequest -> this.makeRequest(request.context(), serverRequest)) - .flatMap(serverResponse -> this.spanConverter.convert(request, serverResponse)); + .flatMap( + serverRequest -> this.makeRequest(request.spanEventsRequest().context(), serverRequest)) + .flatMap(serverResponse -> spanLogEventDao.fetchLogEvents(request, serverResponse)) + .flatMap( + spanLogEventsResponse -> this.spanConverter.convert(request, spanLogEventsResponse)); } private Single makeRequest(GraphQlRequestContext context, SpansRequest request) { diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanRequestBuilder.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanRequestBuilder.java index a38de1df..48798841 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanRequestBuilder.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanRequestBuilder.java @@ -9,10 +9,10 @@ import javax.inject.Inject; import org.hypertrace.core.graphql.common.request.AttributeAssociation; import org.hypertrace.core.graphql.common.request.AttributeRequest; -import org.hypertrace.core.graphql.common.request.ResultSetRequest; import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument; import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; import org.hypertrace.core.graphql.common.utils.Converter; +import org.hypertrace.core.graphql.span.request.SpanRequest; import org.hypertrace.gateway.service.v1.common.Expression; import org.hypertrace.gateway.service.v1.common.Filter; import org.hypertrace.gateway.service.v1.common.OrderByExpression; @@ -35,21 +35,27 @@ class GatewayServiceSpanRequestBuilder { this.attributeConverter = attributeConverter; } - Single buildRequest(ResultSetRequest gqlRequest) { + Single buildRequest(SpanRequest gqlRequest) { return zip( - this.attributeConverter.convert(gqlRequest.attributes()), - this.orderConverter.convert(gqlRequest.orderArguments()), - this.filterConverter.convert(gqlRequest.filterArguments()), + this.attributeConverter.convert(gqlRequest.spanEventsRequest().attributes()), + this.orderConverter.convert(gqlRequest.spanEventsRequest().orderArguments()), + this.filterConverter.convert(gqlRequest.spanEventsRequest().filterArguments()), (selections, orderBys, filters) -> SpansRequest.newBuilder() - .setStartTimeMillis(gqlRequest.timeRange().startTime().toEpochMilli()) - .setEndTimeMillis(gqlRequest.timeRange().endTime().toEpochMilli()) + .setStartTimeMillis( + gqlRequest.spanEventsRequest().timeRange().startTime().toEpochMilli()) + .setEndTimeMillis( + gqlRequest.spanEventsRequest().timeRange().endTime().toEpochMilli()) .addAllSelection(selections) .addAllOrderBy(orderBys) - .setLimit(gqlRequest.limit()) - .setOffset(gqlRequest.offset()) + .setLimit(gqlRequest.spanEventsRequest().limit()) + .setOffset(gqlRequest.spanEventsRequest().offset()) .setFilter(filters) - .setSpaceId(gqlRequest.spaceId().orElse("")) // String proto default value + .setSpaceId( + gqlRequest + .spanEventsRequest() + .spaceId() + .orElse("")) // String proto default value .build()); } } diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDao.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDao.java index 7a92783f..7e55f74b 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDao.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDao.java @@ -1,11 +1,10 @@ package org.hypertrace.core.graphql.span.dao; import io.reactivex.rxjava3.core.Single; -import org.hypertrace.core.graphql.common.request.ResultSetRequest; -import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; +import org.hypertrace.core.graphql.span.request.SpanRequest; import org.hypertrace.core.graphql.span.schema.SpanResultSet; public interface SpanDao { - Single getSpans(ResultSetRequest request); + Single getSpans(SpanRequest request); } diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDaoModule.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDaoModule.java index 5e1a8f1d..64eaa410 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDaoModule.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanDaoModule.java @@ -8,15 +8,19 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.hypertrace.core.graphql.attributes.AttributeStore; import org.hypertrace.core.graphql.common.request.AttributeAssociation; import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.FilterRequestBuilder; import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument; import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; import org.hypertrace.core.graphql.common.utils.BiConverter; import org.hypertrace.core.graphql.common.utils.Converter; +import org.hypertrace.core.graphql.deserialization.ArgumentDeserializer; import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig; import org.hypertrace.core.graphql.utils.grpc.GraphQlGrpcContextBuilder; import org.hypertrace.core.graphql.utils.grpc.GrpcChannelRegistry; +import org.hypertrace.gateway.service.GatewayServiceGrpc.GatewayServiceFutureStub; import org.hypertrace.gateway.service.v1.common.Expression; import org.hypertrace.gateway.service.v1.common.Filter; import org.hypertrace.gateway.service.v1.common.OrderByExpression; @@ -27,11 +31,14 @@ public class SpanDaoModule extends AbstractModule { @Override protected void configure() { bind(SpanDao.class).to(GatewayServiceSpanDao.class); - + requireBinding(GatewayServiceFutureStub.class); requireBinding(CallCredentials.class); requireBinding(GraphQlServiceConfig.class); requireBinding(GraphQlGrpcContextBuilder.class); requireBinding(GrpcChannelRegistry.class); + requireBinding(FilterRequestBuilder.class); + requireBinding(ArgumentDeserializer.class); + requireBinding(AttributeStore.class); requireBinding( Key.get(new TypeLiteral, Set>>() {})); diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventDao.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventDao.java new file mode 100644 index 00000000..ff9b168d --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventDao.java @@ -0,0 +1,79 @@ +package org.hypertrace.core.graphql.span.dao; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import io.reactivex.rxjava3.core.Single; +import java.util.Map; +import javax.inject.Inject; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; +import org.hypertrace.core.graphql.span.request.SpanRequest; +import org.hypertrace.core.graphql.utils.grpc.GraphQlGrpcContextBuilder; +import org.hypertrace.gateway.service.GatewayServiceGrpc.GatewayServiceFutureStub; +import org.hypertrace.gateway.service.v1.log.events.LogEventsRequest; +import org.hypertrace.gateway.service.v1.log.events.LogEventsResponse; +import org.hypertrace.gateway.service.v1.span.SpansResponse; + +class SpanLogEventDao { + + private static final int DEFAULT_DEADLINE_SEC = 10; + + private final GatewayServiceFutureStub gatewayServiceStub; + private final GraphQlGrpcContextBuilder grpcContextBuilder; + private final SpanLogEventRequestBuilder spanLogEventRequestBuilder; + private final SpanLogEventResponseConverter spanLogEventResponseConverter; + + @Inject + SpanLogEventDao( + GatewayServiceFutureStub gatewayServiceFutureStub, + GraphQlGrpcContextBuilder grpcContextBuilder, + SpanLogEventRequestBuilder spanLogEventRequestBuilder, + SpanLogEventResponseConverter spanLogEventResponseConverter) { + this.gatewayServiceStub = gatewayServiceFutureStub; + this.grpcContextBuilder = grpcContextBuilder; + this.spanLogEventRequestBuilder = spanLogEventRequestBuilder; + this.spanLogEventResponseConverter = spanLogEventResponseConverter; + } + + /** + * + * + *
    + *
  • 1. Fetch log event attributes from {@code gqlRequest} + *
  • 2. Build log event request using attribute and spanIds as filter + *
  • 3. Query log events + *
  • 4. Processed log events response to build mapping from spanId to logEvent + *
+ */ + Single fetchLogEvents( + SpanRequest gqlRequest, SpansResponse spansResponse) { + if (null == gqlRequest.spanEventsRequest().idAttribute() + || null == gqlRequest.logEventAttributes() + || gqlRequest.logEventAttributes().isEmpty()) { + return Single.just(new SpanLogEventsResponse(spansResponse, Map.of())); + } + return spanLogEventRequestBuilder + .buildLogEventsRequest(gqlRequest, spansResponse) + .flatMap( + logEventsRequest -> + makeRequest(gqlRequest.spanEventsRequest().context(), logEventsRequest)) + .flatMap( + logEventsResponse -> + spanLogEventResponseConverter.buildResponse( + gqlRequest.spanEventsRequest().context(), + gqlRequest.logEventAttributes(), + spansResponse, + logEventsResponse)); + } + + private Single makeRequest( + GraphQlRequestContext context, LogEventsRequest request) { + return Single.fromFuture( + this.grpcContextBuilder + .build(context) + .callInContext( + () -> + this.gatewayServiceStub + .withDeadlineAfter(DEFAULT_DEADLINE_SEC, SECONDS) + .getLogEvents(request))); + } +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventRequestBuilder.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventRequestBuilder.java new file mode 100644 index 00000000..46d1d4f1 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventRequestBuilder.java @@ -0,0 +1,88 @@ +package org.hypertrace.core.graphql.span.dao; + +import static io.reactivex.rxjava3.core.Single.zip; + +import io.reactivex.rxjava3.core.Single; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.experimental.Accessors; +import org.hypertrace.core.graphql.atttributes.scopes.HypertraceCoreAttributeScopeString; +import org.hypertrace.core.graphql.common.request.AttributeAssociation; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.FilterRequestBuilder; +import org.hypertrace.core.graphql.common.schema.attributes.AttributeScope; +import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument; +import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterOperatorType; +import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterType; +import org.hypertrace.core.graphql.common.utils.Converter; +import org.hypertrace.core.graphql.span.request.SpanRequest; +import org.hypertrace.gateway.service.v1.common.Expression; +import org.hypertrace.gateway.service.v1.common.Filter; +import org.hypertrace.gateway.service.v1.log.events.LogEventsRequest; +import org.hypertrace.gateway.service.v1.span.SpansResponse; + +class SpanLogEventRequestBuilder { + + private final Converter, Set> attributeConverter; + private final Converter>, Filter> filterConverter; + private final FilterRequestBuilder filterRequestBuilder; + + @Inject + SpanLogEventRequestBuilder( + Converter, Set> attributeConverter, + Converter>, Filter> filterConverter, + FilterRequestBuilder filterRequestBuilder) { + this.attributeConverter = attributeConverter; + this.filterConverter = filterConverter; + this.filterRequestBuilder = filterRequestBuilder; + } + + Single buildLogEventsRequest( + SpanRequest gqlRequest, SpansResponse spansResponse) { + return zip( + this.attributeConverter.convert(gqlRequest.logEventAttributes()), + buildLogEventsQueryFilter(gqlRequest, spansResponse).flatMap(filterConverter::convert), + (selections, filter) -> + LogEventsRequest.newBuilder() + .setStartTimeMillis( + gqlRequest.spanEventsRequest().timeRange().startTime().toEpochMilli()) + .setEndTimeMillis( + gqlRequest.spanEventsRequest().timeRange().endTime().toEpochMilli()) + .addAllSelection(selections) + .setFilter(filter) + .build()); + } + + private Single>> buildLogEventsQueryFilter( + SpanRequest gqlRequest, SpansResponse spansResponse) { + List spanIds = + spansResponse.getSpansList().stream() + .map( + spanEvent -> + spanEvent + .getAttributesMap() + .get(gqlRequest.spanEventsRequest().idAttribute().attribute().id()) + .getString()) + .collect(Collectors.toList()); + + return filterRequestBuilder.build( + gqlRequest.spanEventsRequest().context(), + HypertraceCoreAttributeScopeString.LOG_EVENT, + Set.of(new LogEventFilter(spanIds))); + } + + @lombok.Value + @Accessors(fluent = true) + private static class LogEventFilter implements FilterArgument { + + FilterType type = FilterType.ID; + String key = null; + FilterOperatorType operator = FilterOperatorType.IN; + Collection value; + AttributeScope idType = null; + String idScope = HypertraceCoreAttributeScopeString.SPAN; + } +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventResponseConverter.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventResponseConverter.java new file mode 100644 index 00000000..5b9a59ce --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventResponseConverter.java @@ -0,0 +1,83 @@ +package org.hypertrace.core.graphql.span.dao; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Single; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.experimental.Accessors; +import org.hypertrace.core.graphql.attributes.AttributeStore; +import org.hypertrace.core.graphql.atttributes.scopes.HypertraceCoreAttributeScopeString; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.utils.BiConverter; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; +import org.hypertrace.core.graphql.log.event.schema.LogEvent; +import org.hypertrace.gateway.service.v1.common.Value; +import org.hypertrace.gateway.service.v1.log.events.LogEventsResponse; +import org.hypertrace.gateway.service.v1.span.SpansResponse; + +class SpanLogEventResponseConverter { + + private final BiConverter, Map, Map> + attributeMapConverter; + private final AttributeStore attributeStore; + + @Inject + SpanLogEventResponseConverter( + BiConverter, Map, Map> + attributeMapConverter, + AttributeStore attributeStore) { + this.attributeMapConverter = attributeMapConverter; + this.attributeStore = attributeStore; + } + + Single buildResponse( + GraphQlRequestContext graphQlRequestContext, + Collection attributeRequests, + SpansResponse spansResponse, + LogEventsResponse logEventsResponse) { + return this.attributeStore + .getForeignIdAttribute( + graphQlRequestContext, + HypertraceCoreAttributeScopeString.LOG_EVENT, + HypertraceCoreAttributeScopeString.SPAN) + .flatMap( + spanId -> + buildResponse(spanId.key(), attributeRequests, spansResponse, logEventsResponse)); + } + + private Single buildResponse( + String foreignIdAttribute, + Collection attributeRequests, + SpansResponse spansResponse, + LogEventsResponse logEventsResponse) { + return Observable.fromIterable(logEventsResponse.getLogEventsList()) + .concatMapSingle( + logEventsResponseVar -> this.convert(attributeRequests, logEventsResponseVar)) + .collect(Collectors.groupingBy(logEvent -> (String) logEvent.attribute(foreignIdAttribute))) + .map( + spanIdVsLogEventsMap -> new SpanLogEventsResponse(spansResponse, spanIdVsLogEventsMap)); + } + + private Single convert( + Collection request, + org.hypertrace.gateway.service.v1.log.events.LogEvent logEvent) { + return this.attributeMapConverter + .convert(request, logEvent.getAttributesMap()) + .map(ConvertedLogEvent::new); + } + + @lombok.Value + @Accessors(fluent = true) + private static class ConvertedLogEvent + implements org.hypertrace.core.graphql.log.event.schema.LogEvent { + + Map attributeValues; + + @Override + public Object attribute(String key) { + return this.attributeValues.get(key); + } + } +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventsResponse.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventsResponse.java new file mode 100644 index 00000000..947b1fa9 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/SpanLogEventsResponse.java @@ -0,0 +1,15 @@ +package org.hypertrace.core.graphql.span.dao; + +import java.util.List; +import java.util.Map; +import lombok.experimental.Accessors; +import org.hypertrace.core.graphql.log.event.schema.LogEvent; +import org.hypertrace.gateway.service.v1.span.SpansResponse; + +@lombok.Value +@Accessors(fluent = true) +class SpanLogEventsResponse { + + SpansResponse spansResponse; + Map> spanIdToLogEvents; +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/fetcher/SpanFetcher.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/fetcher/SpanFetcher.java index 7b07805f..f7b9cd2b 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/fetcher/SpanFetcher.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/fetcher/SpanFetcher.java @@ -6,8 +6,8 @@ import javax.inject.Inject; import org.hypertrace.core.graphql.atttributes.scopes.HypertraceCoreAttributeScopeString; import org.hypertrace.core.graphql.common.fetcher.InjectableDataFetcher; -import org.hypertrace.core.graphql.common.request.ResultSetRequestBuilder; import org.hypertrace.core.graphql.span.dao.SpanDao; +import org.hypertrace.core.graphql.span.request.SpanRequestBuilder; import org.hypertrace.core.graphql.span.schema.SpanResultSet; public class SpanFetcher extends InjectableDataFetcher { @@ -17,11 +17,11 @@ public SpanFetcher() { } static final class SpanFetcherImpl implements DataFetcher> { - private final ResultSetRequestBuilder requestBuilder; + private final SpanRequestBuilder requestBuilder; private final SpanDao spanDao; @Inject - SpanFetcherImpl(ResultSetRequestBuilder requestBuilder, SpanDao spanDao) { + SpanFetcherImpl(SpanRequestBuilder requestBuilder, SpanDao spanDao) { this.requestBuilder = requestBuilder; this.spanDao = spanDao; } diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/DefaultSpanRequestBuilder.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/DefaultSpanRequestBuilder.java new file mode 100644 index 00000000..06869569 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/DefaultSpanRequestBuilder.java @@ -0,0 +1,52 @@ +package org.hypertrace.core.graphql.span.request; + +import static io.reactivex.rxjava3.core.Single.zip; + +import graphql.schema.DataFetchingFieldSelectionSet; +import io.reactivex.rxjava3.core.Single; +import java.util.Collection; +import java.util.Map; +import javax.inject.Inject; +import lombok.Value; +import lombok.experimental.Accessors; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.ResultSetRequest; +import org.hypertrace.core.graphql.common.request.ResultSetRequestBuilder; +import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; + +class DefaultSpanRequestBuilder implements SpanRequestBuilder { + + private final ResultSetRequestBuilder resultSetRequestBuilder; + private final LogEventAttributeRequestBuilder logEventAttributeRequestBuilder; + + @Inject + public DefaultSpanRequestBuilder( + ResultSetRequestBuilder resultSetRequestBuilder, + LogEventAttributeRequestBuilder logEventAttributeRequestBuilder) { + this.resultSetRequestBuilder = resultSetRequestBuilder; + this.logEventAttributeRequestBuilder = logEventAttributeRequestBuilder; + } + + @Override + public Single build( + GraphQlRequestContext context, + String requestScope, + Map arguments, + DataFetchingFieldSelectionSet selectionSet) { + return zip( + resultSetRequestBuilder.build( + context, requestScope, arguments, selectionSet, OrderArgument.class), + logEventAttributeRequestBuilder.buildAttributeRequest(context, selectionSet), + (resultSetRequest, logEventAttributeRequest) -> + Single.just(new DefaultSpanRequest(resultSetRequest, logEventAttributeRequest))) + .flatMap(single -> single); + } + + @Value + @Accessors(fluent = true) + private static class DefaultSpanRequest implements SpanRequest { + ResultSetRequest spanEventsRequest; + Collection logEventAttributes; + } +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/LogEventAttributeRequestBuilder.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/LogEventAttributeRequestBuilder.java new file mode 100644 index 00000000..3e311fc3 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/LogEventAttributeRequestBuilder.java @@ -0,0 +1,51 @@ +package org.hypertrace.core.graphql.span.request; + +import static org.hypertrace.core.graphql.common.schema.results.ResultSet.RESULT_SET_RESULTS_NAME; +import static org.hypertrace.core.graphql.span.schema.Span.LOG_EVENT_KEY; + +import graphql.schema.DataFetchingFieldSelectionSet; +import graphql.schema.SelectedField; +import io.reactivex.rxjava3.core.Single; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.inject.Inject; +import org.hypertrace.core.graphql.atttributes.scopes.HypertraceCoreAttributeScopeString; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.AttributeRequestBuilder; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; +import org.hypertrace.core.graphql.utils.schema.GraphQlSelectionFinder; +import org.hypertrace.core.graphql.utils.schema.SelectionQuery; + +class LogEventAttributeRequestBuilder { + + private final GraphQlSelectionFinder selectionFinder; + private final AttributeRequestBuilder attributeRequestBuilder; + + @Inject + LogEventAttributeRequestBuilder( + GraphQlSelectionFinder selectionFinder, AttributeRequestBuilder attributeRequestBuilder) { + this.selectionFinder = selectionFinder; + this.attributeRequestBuilder = attributeRequestBuilder; + } + + Single> buildAttributeRequest( + GraphQlRequestContext context, DataFetchingFieldSelectionSet selectionSet) { + return attributeRequestBuilder + .buildForAttributeQueryableFields( + context, + HypertraceCoreAttributeScopeString.LOG_EVENT, + getLogEventSelectionFields(selectionSet)) + .collect(Collectors.toUnmodifiableSet()); + } + + private Stream getLogEventSelectionFields( + DataFetchingFieldSelectionSet selectionSet) { + return this.selectionFinder.findSelections( + selectionSet, + SelectionQuery.builder() + .selectionPath(List.of(RESULT_SET_RESULTS_NAME, LOG_EVENT_KEY, RESULT_SET_RESULTS_NAME)) + .build()); + } +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequest.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequest.java new file mode 100644 index 00000000..73312ca9 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequest.java @@ -0,0 +1,12 @@ +package org.hypertrace.core.graphql.span.request; + +import java.util.Collection; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.ResultSetRequest; +import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; + +public interface SpanRequest { + ResultSetRequest spanEventsRequest(); + + Collection logEventAttributes(); +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequestBuilder.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequestBuilder.java new file mode 100644 index 00000000..c3e62eac --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequestBuilder.java @@ -0,0 +1,15 @@ +package org.hypertrace.core.graphql.span.request; + +import graphql.schema.DataFetchingFieldSelectionSet; +import io.reactivex.rxjava3.core.Single; +import java.util.Map; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; + +public interface SpanRequestBuilder { + + Single build( + GraphQlRequestContext context, + String requestScope, + Map arguments, + DataFetchingFieldSelectionSet selectionSet); +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequestModule.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequestModule.java new file mode 100644 index 00000000..a1d5a39d --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/request/SpanRequestModule.java @@ -0,0 +1,26 @@ +package org.hypertrace.core.graphql.span.request; + +import com.google.inject.AbstractModule; +import org.hypertrace.core.graphql.attributes.AttributeStore; +import org.hypertrace.core.graphql.common.request.AttributeRequestBuilder; +import org.hypertrace.core.graphql.common.request.FilterRequestBuilder; +import org.hypertrace.core.graphql.common.utils.attributes.AttributeAssociator; +import org.hypertrace.core.graphql.common.utils.attributes.AttributeScopeStringTranslator; +import org.hypertrace.core.graphql.deserialization.ArgumentDeserializer; +import org.hypertrace.core.graphql.utils.schema.GraphQlSelectionFinder; + +public class SpanRequestModule extends AbstractModule { + + @Override + protected void configure() { + bind(SpanRequestBuilder.class).to(DefaultSpanRequestBuilder.class); + requireBinding(ArgumentDeserializer.class); + requireBinding(AttributeRequestBuilder.class); + requireBinding(FilterRequestBuilder.class); + requireBinding(AttributeStore.class); + requireBinding(AttributeAssociator.class); + requireBinding(GraphQlSelectionFinder.class); + requireBinding(AttributeScopeStringTranslator.class); + requireBinding(AttributeRequestBuilder.class); + } +} diff --git a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/schema/Span.java b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/schema/Span.java index fe11ac6b..c591e167 100644 --- a/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/schema/Span.java +++ b/hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/schema/Span.java @@ -1,10 +1,19 @@ package org.hypertrace.core.graphql.span.schema; +import graphql.annotations.annotationTypes.GraphQLField; import graphql.annotations.annotationTypes.GraphQLName; +import graphql.annotations.annotationTypes.GraphQLNonNull; import org.hypertrace.core.graphql.common.schema.attributes.AttributeQueryable; import org.hypertrace.core.graphql.common.schema.id.Identifiable; +import org.hypertrace.core.graphql.log.event.schema.LogEventResultSet; @GraphQLName(Span.TYPE_NAME) public interface Span extends AttributeQueryable, Identifiable { String TYPE_NAME = "Span"; + String LOG_EVENT_KEY = "logEvents"; + + @GraphQLField + @GraphQLNonNull + @GraphQLName(LOG_EVENT_KEY) + LogEventResultSet logEvents(); } diff --git a/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/DaoTestUtil.java b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/DaoTestUtil.java new file mode 100644 index 00000000..cb626826 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/DaoTestUtil.java @@ -0,0 +1,220 @@ +package org.hypertrace.core.graphql.span.dao; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.Value; +import lombok.experimental.Accessors; +import org.hypertrace.core.graphql.attributes.AttributeModel; +import org.hypertrace.core.graphql.attributes.AttributeModelMetricAggregationType; +import org.hypertrace.core.graphql.attributes.AttributeModelType; +import org.hypertrace.core.graphql.common.request.AttributeAssociation; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.ResultSetRequest; +import org.hypertrace.core.graphql.common.schema.arguments.TimeRangeArgument; +import org.hypertrace.core.graphql.common.schema.attributes.AttributeScope; +import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument; +import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterOperatorType; +import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterType; +import org.hypertrace.core.graphql.common.schema.results.arguments.order.OrderArgument; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; +import org.hypertrace.core.graphql.span.request.SpanRequest; +import org.hypertrace.gateway.service.v1.common.ValueType; +import org.hypertrace.gateway.service.v1.log.events.LogEvent; +import org.hypertrace.gateway.service.v1.log.events.LogEventsResponse; +import org.hypertrace.gateway.service.v1.span.SpanEvent; +import org.hypertrace.gateway.service.v1.span.SpansResponse; + +class DaoTestUtil { + + @Value + @Accessors(fluent = true) + static class DefaultAttributeRequest implements AttributeRequest { + + AttributeModel attribute; + + @Override + public String alias() { + return attribute.id(); + } + } + + @Value + @Accessors(fluent = true) + static class DefaultAttributeModel implements AttributeModel { + + String id; + String scope; + String key; + String displayName; + AttributeModelType type; + String units; + boolean onlySupportsGrouping; + boolean onlySupportsAggregation; + List supportedMetricAggregationTypes; + boolean groupable; + } + + @Value + @Accessors(fluent = true) + static class NormalizedFilter implements FilterArgument { + + FilterType type = FilterType.ATTRIBUTE; + String key; + FilterOperatorType operator; + Object value; + String idScope = null; + AttributeScope idType = null; + } + + @Value + @Accessors(fluent = true) + static class DefaultSpanRequest implements SpanRequest { + + ResultSetRequest spanEventsRequest; + Collection logEventAttributes; + } + + @Value + @Accessors(fluent = true) + static class DefaultResultSetRequest implements ResultSetRequest { + + GraphQlRequestContext context; + Collection attributes; + TimeRangeArgument timeRange; + AttributeRequest idAttribute; + int limit; + int offset; + List orderArguments; + Collection> filterArguments; + Optional spaceId; + } + + @Value + @Accessors(fluent = true) + static class DefaultTimeRange implements TimeRangeArgument { + + @JsonProperty(TIME_RANGE_ARGUMENT_START_TIME) + Instant startTime; + + @JsonProperty(TIME_RANGE_ARGUMENT_END_TIME) + Instant endTime; + } + + static AttributeRequest traceIdAttribute = + new DefaultAttributeRequest( + new DefaultAttributeModel( + "traceId", + "LOG_EVENT", + "traceId", + "Trace Id", + AttributeModelType.STRING, + "", + false, + false, + Collections.emptyList(), + false)); + + static AttributeRequest spanIdAttribute = + new DefaultAttributeRequest( + new DefaultAttributeModel( + "spanId", + "LOG_EVENT", + "spanId", + "Span Id", + AttributeModelType.STRING, + "", + false, + false, + Collections.emptyList(), + false)); + + static AttributeRequest attributesAttribute = + new DefaultAttributeRequest( + new DefaultAttributeModel( + "attributes", + "LOG_EVENT", + "attributes", + "Attributes", + AttributeModelType.STRING, + "", + false, + false, + Collections.emptyList(), + false)); + + static AttributeRequest eventIdAttribute = + new DefaultAttributeRequest( + new DefaultAttributeModel( + "id", + "EVENT", + "id", + "Id", + AttributeModelType.STRING, + "", + false, + false, + Collections.emptyList(), + false)); + + static SpansResponse spansResponse = + SpansResponse.newBuilder() + .addSpans( + SpanEvent.newBuilder() + .putAllAttributes(Map.of("id", getValue("span1"), "traceId", getValue("trace1"))) + .build()) + .addSpans( + SpanEvent.newBuilder() + .putAllAttributes(Map.of("id", getValue("span2"), "traceId", getValue("trace1"))) + .build()) + .addSpans( + SpanEvent.newBuilder() + .putAllAttributes(Map.of("id", getValue("span3"), "traceId", getValue("trace1"))) + .build()) + .build(); + + static LogEventsResponse logEventsResponse = + LogEventsResponse.newBuilder() + .addLogEvents( + LogEvent.newBuilder() + .putAllAttributes( + Map.of( + "traceId", + getValue("trace1"), + "attributes", + getValue("event: error"), + "spanId", + getValue("span1")))) + .addLogEvents( + LogEvent.newBuilder() + .putAllAttributes( + Map.of( + "traceId", + getValue("trace1"), + "attributes", + getValue("event: error"), + "spanId", + getValue("span1")))) + .addLogEvents( + LogEvent.newBuilder() + .putAllAttributes( + Map.of( + "traceId", + getValue("trace1"), + "attributes", + getValue("event: error"), + "spanId", + getValue("span2")))) + .build(); + + static org.hypertrace.gateway.service.v1.common.Value getValue(String value) { + return org.hypertrace.gateway.service.v1.common.Value.newBuilder() + .setValueType(ValueType.STRING) + .setString(value) + .build(); + } +} diff --git a/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/SpanLogEventRequestBuilderTest.java b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/SpanLogEventRequestBuilderTest.java new file mode 100644 index 00000000..3e1db88a --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/SpanLogEventRequestBuilderTest.java @@ -0,0 +1,155 @@ +package org.hypertrace.core.graphql.span.dao; + +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.attributesAttribute; +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.spanIdAttribute; +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.spansResponse; +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.traceIdAttribute; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import io.grpc.CallCredentials; +import io.reactivex.rxjava3.core.Single; +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import org.hypertrace.core.graphql.common.request.AttributeAssociation; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.FilterRequestBuilder; +import org.hypertrace.core.graphql.common.request.ResultSetRequest; +import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument; +import org.hypertrace.core.graphql.common.utils.Converter; +import org.hypertrace.core.graphql.span.dao.DaoTestUtil.DefaultResultSetRequest; +import org.hypertrace.core.graphql.span.dao.DaoTestUtil.DefaultSpanRequest; +import org.hypertrace.core.graphql.span.dao.DaoTestUtil.DefaultTimeRange; +import org.hypertrace.core.graphql.span.dao.DaoTestUtil.NormalizedFilter; +import org.hypertrace.core.graphql.span.request.SpanRequest; +import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig; +import org.hypertrace.core.graphql.utils.gateway.GatewayUtilsModule; +import org.hypertrace.core.graphql.utils.grpc.GrpcChannelRegistry; +import org.hypertrace.gateway.service.v1.common.Expression; +import org.hypertrace.gateway.service.v1.common.Filter; +import org.hypertrace.gateway.service.v1.common.Operator; +import org.hypertrace.gateway.service.v1.log.events.LogEventsRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SpanLogEventRequestBuilderTest { + + @Mock private FilterRequestBuilder filterRequestBuilder; + + private SpanLogEventRequestBuilder spanLogEventRequestBuilder; + + @BeforeEach + void beforeEach() { + Injector injector = + Guice.createInjector( + new GatewayUtilsModule(), + new AbstractModule() { + @Override + protected void configure() { + bind(CallCredentials.class).toInstance(mock(CallCredentials.class)); + bind(GraphQlServiceConfig.class).toInstance(mock(GraphQlServiceConfig.class)); + bind(GrpcChannelRegistry.class).toInstance(mock(GrpcChannelRegistry.class)); + } + }); + + Converter>, Filter> filterConverter = + injector.getInstance( + Key.get( + new TypeLiteral< + Converter>, Filter>>() {})); + + Converter, Set> attributeConverter = + injector.getInstance( + Key.get( + new TypeLiteral, Set>>() {})); + + spanLogEventRequestBuilder = + new SpanLogEventRequestBuilder(attributeConverter, filterConverter, filterRequestBuilder); + } + + @Test + void testBuildRequest() { + doAnswer( + invocation -> { + Set filterArguments = invocation.getArgument(2, Set.class); + FilterArgument filterArgument = filterArguments.iterator().next(); + return Single.just( + List.of( + AttributeAssociation.of( + spanIdAttribute.attribute(), + new NormalizedFilter( + spanIdAttribute.attribute().key(), + filterArgument.operator(), + filterArgument.value())))); + }) + .when(filterRequestBuilder) + .build(any(), any(), anyCollection()); + + long startTime = System.currentTimeMillis(); + long endTime = System.currentTimeMillis() + Duration.ofHours(1).toMillis(); + + Collection logAttributeRequests = + List.of(spanIdAttribute, traceIdAttribute, attributesAttribute); + ResultSetRequest resultSetRequest = + new DefaultResultSetRequest( + null, + List.of(DaoTestUtil.eventIdAttribute), + new DefaultTimeRange(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime)), + DaoTestUtil.eventIdAttribute, + 0, + 0, + List.of(), + Collections.emptyList(), + Optional.empty()); + SpanRequest spanRequest = new DefaultSpanRequest(resultSetRequest, logAttributeRequests); + + LogEventsRequest logEventsRequest = + spanLogEventRequestBuilder.buildLogEventsRequest(spanRequest, spansResponse).blockingGet(); + + assertEquals(Operator.IN, logEventsRequest.getFilter().getChildFilter(0).getOperator()); + assertEquals( + spanIdAttribute.attribute().id(), + logEventsRequest + .getFilter() + .getChildFilter(0) + .getLhs() + .getColumnIdentifier() + .getColumnName()); + assertEquals( + List.of("span1", "span2", "span3"), + logEventsRequest + .getFilter() + .getChildFilter(0) + .getRhs() + .getLiteral() + .getValue() + .getStringArrayList() + .stream() + .collect(Collectors.toList())); + assertEquals(startTime, logEventsRequest.getStartTimeMillis()); + assertEquals(endTime, logEventsRequest.getEndTimeMillis()); + assertEquals( + Set.of("attributes", "traceId", "spanId"), + logEventsRequest.getSelectionList().stream() + .map(v -> v.getColumnIdentifier().getColumnName()) + .collect(Collectors.toSet())); + } +} diff --git a/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/SpanLogEventResponseConverterTest.java b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/SpanLogEventResponseConverterTest.java new file mode 100644 index 00000000..c980e441 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/dao/SpanLogEventResponseConverterTest.java @@ -0,0 +1,78 @@ +package org.hypertrace.core.graphql.span.dao; + +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.attributesAttribute; +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.logEventsResponse; +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.spanIdAttribute; +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.spansResponse; +import static org.hypertrace.core.graphql.span.dao.DaoTestUtil.traceIdAttribute; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import io.reactivex.rxjava3.core.Single; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; +import org.hypertrace.core.graphql.attributes.AttributeStore; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.utils.BiConverter; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; +import org.hypertrace.gateway.service.v1.common.Value; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SpanLogEventResponseConverterTest { + + @Mock + BiConverter, Map, Map> + attributeMapConverter; + + @Mock AttributeStore attributeStore; + @Mock GraphQlRequestContext requestContext; + + private SpanLogEventResponseConverter spanLogEventResponseConverter; + + @BeforeEach + void beforeEach() { + spanLogEventResponseConverter = + new SpanLogEventResponseConverter(attributeMapConverter, attributeStore); + } + + @Test + void testBuildResponse() { + Collection attributeRequests = + List.of(spanIdAttribute, traceIdAttribute, attributesAttribute); + + when(attributeStore.getForeignIdAttribute(any(), anyString(), anyString())) + .thenReturn(Single.just(spanIdAttribute.attribute())); + + doAnswer( + invocation -> { + Map map = invocation.getArgument(1, Map.class); + return Single.just( + map.entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, v -> v.getValue().getString()))); + }) + .when(attributeMapConverter) + .convert(anyCollection(), anyMap()); + + SpanLogEventsResponse response = + spanLogEventResponseConverter + .buildResponse(requestContext, attributeRequests, spansResponse, logEventsResponse) + .blockingGet(); + + assertEquals(spansResponse, response.spansResponse()); + assertEquals(Set.of("span1", "span2"), response.spanIdToLogEvents().keySet()); + } +} diff --git a/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/request/LogEventAttributeRequestBuilderTest.java b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/request/LogEventAttributeRequestBuilderTest.java new file mode 100644 index 00000000..d5efcca0 --- /dev/null +++ b/hypertrace-core-graphql-span-schema/src/test/java/org/hypertrace/core/graphql/span/request/LogEventAttributeRequestBuilderTest.java @@ -0,0 +1,59 @@ +package org.hypertrace.core.graphql.span.request; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import graphql.schema.DataFetchingFieldSelectionSet; +import graphql.schema.SelectedField; +import io.reactivex.rxjava3.core.Observable; +import java.util.Set; +import java.util.stream.Stream; +import org.hypertrace.core.graphql.common.request.AttributeRequest; +import org.hypertrace.core.graphql.common.request.AttributeRequestBuilder; +import org.hypertrace.core.graphql.context.GraphQlRequestContext; +import org.hypertrace.core.graphql.utils.schema.GraphQlSelectionFinder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class LogEventAttributeRequestBuilderTest { + + @Mock GraphQlRequestContext mockContext; + @Mock GraphQlSelectionFinder mockSelectionFinder; + @Mock AttributeRequestBuilder mockAttributeRequestBuilder; + @Mock DataFetchingFieldSelectionSet mockSelectionSet; + @Mock Stream mockAttributeQueryableStream; + @Mock AttributeRequest mockFooAttributeRequest; + @Mock AttributeRequest mockBarAttributeRequest; + + private LogEventAttributeRequestBuilder requestBuilder; + + @BeforeEach + void beforeEach() { + this.requestBuilder = + new LogEventAttributeRequestBuilder( + this.mockSelectionFinder, this.mockAttributeRequestBuilder); + } + + @Test + void canBuildRequest() { + when(this.mockSelectionFinder.findSelections(eq(this.mockSelectionSet), any())) + .thenReturn(mockAttributeQueryableStream); + when(this.mockAttributeRequestBuilder.buildForAttributeQueryableFields( + any(), any(), eq(this.mockAttributeQueryableStream))) + .thenReturn(Observable.just(this.mockFooAttributeRequest, this.mockBarAttributeRequest)); + + Set request = + this.requestBuilder + .buildAttributeRequest(this.mockContext, this.mockSelectionSet) + .blockingGet(); + + Assertions.assertEquals( + Set.of(this.mockBarAttributeRequest, this.mockFooAttributeRequest), request); + } +}