Skip to content

Add tracing tags for headers and query parameters to GTFS API #6712

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

Merged
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
@@ -0,0 +1,17 @@
package org.opentripplanner.apis.gtfs;

import java.util.Collection;

/**
* GTFS API parameters. These parameters configure the behaviour of some aspects of the
* GTFS GraphQL API.
*/
public interface GtfsApiParameters {
/**
* Which HTTP headers or query parameters should be used as tags for performance metering in the
* Actuator API.
*
* @see org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation
*/
Collection<String> tracingTags();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.opentripplanner.apis.support.TracingUtils;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -50,12 +52,13 @@ public GtfsGraphQLAPIOldPath(
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response getGraphQL(
HashMap<String, Object> queryParameters,
HashMap<String, Object> jsonParameters,
@HeaderParam("OTPTimeout") @DefaultValue("30000") int timeout,
@HeaderParam("OTPMaxResolves") @DefaultValue("1000000") int maxResolves,
@Context HttpHeaders headers
@Context HttpHeaders headers,
@Context UriInfo uriInfo
) {
if (queryParameters == null || !queryParameters.containsKey("query")) {
if (jsonParameters == null || !jsonParameters.containsKey("query")) {
LOG.debug("No query found in body");
return Response.status(Response.Status.BAD_REQUEST)
.type(MediaType.TEXT_PLAIN_TYPE)
Expand All @@ -67,9 +70,9 @@ public Response getGraphQL(
? headers.getAcceptableLanguages().get(0)
: serverContext.defaultRouteRequest().preferences().locale();

String query = (String) queryParameters.get("query");
Object queryVariables = queryParameters.getOrDefault("variables", null);
String operationName = (String) queryParameters.getOrDefault("operationName", null);
String query = (String) jsonParameters.get("query");
Object queryVariables = jsonParameters.getOrDefault("variables", null);
String operationName = (String) jsonParameters.getOrDefault("operationName", null);
Map<String, Object> variables;

if (queryVariables instanceof Map) {
Expand All @@ -93,7 +96,12 @@ public Response getGraphQL(
maxResolves,
timeout,
locale,
GraphQLRequestContext.ofServerContext(serverContext)
GraphQLRequestContext.ofServerContext(serverContext),
TracingUtils.findTagsInHeadersOrQueryParameters(
serverContext.gtfsApiParameters().tracingTags(),
headers,
uriInfo.getQueryParameters()
)
);
}

Expand All @@ -103,7 +111,8 @@ public Response getGraphQL(
String query,
@HeaderParam("OTPTimeout") @DefaultValue("30000") int timeout,
@HeaderParam("OTPMaxResolves") @DefaultValue("1000000") int maxResolves,
@Context HttpHeaders headers
@Context HttpHeaders headers,
@Context UriInfo uriInfo
) {
Locale locale = headers.getAcceptableLanguages().size() > 0
? headers.getAcceptableLanguages().get(0)
Expand All @@ -115,7 +124,12 @@ public Response getGraphQL(
maxResolves,
timeout,
locale,
GraphQLRequestContext.ofServerContext(serverContext)
GraphQLRequestContext.ofServerContext(serverContext),
TracingUtils.findTagsInHeadersOrQueryParameters(
serverContext.gtfsApiParameters().tracingTags(),
headers,
uriInfo.getQueryParameters()
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import graphql.execution.instrumentation.ChainedInstrumentation;
import graphql.execution.instrumentation.Instrumentation;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import jakarta.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutionException;
Expand All @@ -30,13 +30,14 @@ static ExecutionResult getGraphQLExecutionResult(
int maxResolves,
int timeoutMs,
Locale locale,
GraphQLRequestContext requestContext
GraphQLRequestContext requestContext,
Iterable<Tag> tracingTags
) {
Instrumentation instrumentation = new MaxQueryComplexityInstrumentation(maxResolves);

if (OTPFeature.ActuatorAPI.isOn()) {
instrumentation = new ChainedInstrumentation(
new MicrometerGraphQLInstrumentation(Metrics.globalRegistry, List.of()),
new MicrometerGraphQLInstrumentation(Metrics.globalRegistry, tracingTags),
instrumentation
);
}
Expand Down Expand Up @@ -71,7 +72,8 @@ static Response getGraphQLResponse(
int maxResolves,
int timeoutMs,
Locale locale,
GraphQLRequestContext requestContext
GraphQLRequestContext requestContext,
Iterable<Tag> tracingTags
) {
ExecutionResult executionResult = getGraphQLExecutionResult(
query,
Expand All @@ -80,7 +82,8 @@ static Response getGraphQLResponse(
maxResolves,
timeoutMs,
locale,
requestContext
requestContext,
tracingTags
);

return Response.status(Response.Status.OK)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.opentripplanner.apis.support;

import io.micrometer.core.instrument.Tag;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Collection;

public final class TracingUtils {

private static final String UNKNOWN_VALUE = "__UNKNOWN__";

/**
* This method tries to find a tracing tag from a request's headers.
* If no value is found for a tag, the value is set to "__UNKNOWN__".
*
* @param tracingHeaderTags a collection of tag names to match against headers
* @param headers headers from a request
* @return a list of tracing tags with computed values
*/
public static Iterable<Tag> findTagsInHeaders(
Collection<String> tracingHeaderTags,
HttpHeaders headers
) {
return tracingHeaderTags
.stream()
.map(header -> {
String value = headers.getHeaderString(header);
return Tag.of(header, value == null ? UNKNOWN_VALUE : value);
})
.toList();
}

/**
* This method tries to find a tracing tag from either a request's headers or query parameters.
* The value from headers is favored if a value is present in both.
* If no value is found for a tag, the value is set to "__UNKNOWN__".
*
* @param tracingTags a collection of tag names to match against headers or query parameters
* @param headers headers from a request
* @param queryParameters query parameters from a request
* @return a list of tracing tags with computed values
*/
public static Iterable<Tag> findTagsInHeadersOrQueryParameters(
Collection<String> tracingTags,
HttpHeaders headers,
MultivaluedMap<String, String> queryParameters
) {
return tracingTags
.stream()
.map(header -> {
String headerValue = headers.getHeaderString(header);
String queryParameterValue = queryParameters.getFirst(header);
if (headerValue != null) {
return Tag.of(header, headerValue);
} else if (queryParameterValue != null) {
return Tag.of(header, queryParameterValue);
} else {
return Tag.of(header, UNKNOWN_VALUE);
}
})
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.SchemaPrinter;
import io.micrometer.core.instrument.Tag;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
Expand All @@ -20,7 +19,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.opentripplanner.apis.support.TracingUtils;
import org.opentripplanner.apis.support.graphql.injectdoc.ApiDocumentationProfile;
import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper;
import org.opentripplanner.routing.api.request.RouteRequest;
Expand Down Expand Up @@ -134,7 +133,7 @@ public Response getGraphQL(
variables,
operationName,
maxNumberOfResultFields,
getTagsFromHeaders(headers)
TracingUtils.findTagsInHeaders(tracingHeaderTags, headers)
);
}

Expand All @@ -147,7 +146,7 @@ public Response getGraphQL(String query, @Context HttpHeaders headers) {
null,
null,
maxNumberOfResultFields,
getTagsFromHeaders(headers)
TracingUtils.findTagsInHeaders(tracingHeaderTags, headers)
);
}

Expand All @@ -157,14 +156,4 @@ public Response getGraphQLSchema() {
var text = SCHEMA_DOC_HEADER + new SchemaPrinter().print(schema);
return Response.ok().encoding("UTF-8").entity(text).build();
}

private static Iterable<Tag> getTagsFromHeaders(HttpHeaders headers) {
return tracingHeaderTags
.stream()
.map(header -> {
String value = headers.getHeaderString(header);
return Tag.of(header, value == null ? "__UNKNOWN__" : value);
})
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.opentripplanner.apis.transmodel;

import java.util.Collection;
import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation;

/**
* Transmodel API parameters. These parameters configure the behaviour of some aspects of the
Expand All @@ -16,7 +15,7 @@ public interface TransmodelAPIParameters {
/**
* Which HTTP headers should be used as tags for performance metering in the Actuator API
*
* @see MicrometerGraphQLInstrumentation
* @see org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation
*/
Collection<String> tracingHeaderTags();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.micrometer.core.instrument.MeterRegistry;
import java.util.List;
import javax.annotation.Nullable;
import org.opentripplanner.apis.gtfs.GtfsApiParameters;
import org.opentripplanner.astar.spi.TraverseVisitor;
import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.ext.flex.FlexParameters;
Expand Down Expand Up @@ -128,6 +129,8 @@ default GraphFinder graphFinder() {

TriasApiParameters triasApiParameters();

GtfsApiParameters gtfsApiParameters();

/* Sandbox modules */

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.node.MissingNode;
import java.io.Serializable;
import java.util.List;
import org.opentripplanner.apis.gtfs.GtfsApiParameters;
import org.opentripplanner.ext.flex.FlexParameters;
import org.opentripplanner.ext.ridehailing.RideHailingServiceParameters;
import org.opentripplanner.ext.trias.config.TriasApiConfig;
Expand All @@ -20,6 +21,7 @@
import org.opentripplanner.standalone.config.routerconfig.UpdatersConfig;
import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig;
import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.standalone.config.sandbox.GtfsApiConfig;
import org.opentripplanner.standalone.config.sandbox.TransmodelAPIConfig;
import org.opentripplanner.updater.UpdatersParameters;
import org.slf4j.Logger;
Expand Down Expand Up @@ -50,6 +52,7 @@ public class RouterConfig implements Serializable {
private final RideHailingServicesConfig rideHailingConfig;
private final FlexConfig flexConfig;
private final TransmodelAPIConfig transmodelApi;
private final GtfsApiConfig gtfsApi;
private final VectorTileConfig vectorTileConfig;
private final TriasApiParameters triasApiParameters;

Expand All @@ -69,6 +72,7 @@ public RouterConfig(JsonNode node, String source, boolean logUnusedParams) {

this.server = new ServerConfig("server", root);
this.transmodelApi = new TransmodelAPIConfig("transmodelApi", root);
this.gtfsApi = new GtfsApiConfig("gtfsApi", root);
var request = mapDefaultRouteRequest("routingDefaults", root);
this.transitConfig = new TransitRoutingConfig("transit", root, request);
this.routingRequestDefaults = request
Expand Down Expand Up @@ -142,6 +146,10 @@ public TriasApiParameters triasApiParameters() {
return triasApiParameters;
}

public GtfsApiParameters gtfsApiParameters() {
return gtfsApi;
}

public NodeAdapter asNodeAdapter() {
return root;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.opentripplanner.standalone.config.sandbox;

import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_8;

import java.util.Collection;
import java.util.Set;
import org.opentripplanner.apis.gtfs.GtfsApiParameters;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;

/**
* @see GtfsApiParameters for documentation of parameters
*/
public class GtfsApiConfig implements GtfsApiParameters {

private final Collection<String> tracingTags;

public GtfsApiConfig(String parameterName, NodeAdapter root) {
var c = root
.of(parameterName)
.since(V2_8)
.summary("Configuration for the GTFS GraphQL API.")
.asObject();

tracingTags = c
.of("tracingTags")
.summary("Used to group requests based on headers or query parameters when monitoring OTP.")
.asStringList(Set.of());
}

@Override
public Collection<String> tracingTags() {
return tracingTags;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ OtpServerRequestContext providesServerContext(

var transitRoutingConfig = routerConfig.transitTuningConfig();
var triasApiParameters = routerConfig.triasApiParameters();
var gtfsApiConfig = routerConfig.gtfsApiParameters();
var vectorTileConfig = routerConfig.vectorTileConfig();
var flexParameters = routerConfig.flexParameters();

Expand All @@ -80,6 +81,7 @@ OtpServerRequestContext providesServerContext(
transitRoutingConfig,
transitService,
triasApiParameters,
gtfsApiConfig,
vectorTileConfig,
vehicleParkingService,
vehicleRentalService,
Expand Down
Loading
Loading