Skip to content
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

Add GraphQL operation metrics #12441

Closed
Closed
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.wso2.carbon.apimgt.common.gateway.graphql;

import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLError;
Expand All @@ -29,7 +30,9 @@
import org.apache.commons.logging.LogFactory;
import org.json.simple.parser.ParseException;
import org.wso2.carbon.apimgt.common.gateway.dto.QueryAnalyzerResponseDTO;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This class contains methods using for Graphql query depth and complexity analysis.
Expand All @@ -52,6 +55,18 @@ public QueryAnalyzer(GraphQLSchema schema) {
*/
public QueryAnalyzerResponseDTO analyseQueryDepth(int maxQueryDepth, String payload) {

return analyseQueryDepth(maxQueryDepth, payload, new HashMap<>());
}

/**
* @param maxQueryDepth maximum query depth
* @param payload payload of the request
* @param variableMap external variable map
* @return true, if the query depth does not exceed the maximum value or false, if query depth exceeds the maximum
*/
public QueryAnalyzerResponseDTO analyseQueryDepth(int maxQueryDepth, String payload,
Map<String, Object> variableMap) {

if (log.isDebugEnabled()) {
log.debug("Analyzing query depth for " + payload + " and max query depth:" + maxQueryDepth);
}
Expand All @@ -63,7 +78,12 @@ public QueryAnalyzerResponseDTO analyseQueryDepth(int maxQueryDepth, String payl
new MaxQueryDepthInstrumentation(maxQueryDepth);
GraphQL runtime = GraphQL.newGraphQL(schema).instrumentation(maxQueryDepthInstrumentation).build();

ExecutionResult executionResult = runtime.execute(payload);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(payload)
.variables(variableMap)
.build();

ExecutionResult executionResult = runtime.execute(executionInput);
List<GraphQLError> errors = executionResult.getErrors();
if (errors.size() > 0) {
for (GraphQLError error : errors) {
Expand Down Expand Up @@ -98,6 +118,22 @@ public QueryAnalyzerResponseDTO analyseQueryDepth(int maxQueryDepth, String payl
*/
public QueryAnalyzerResponseDTO analyseQueryComplexity(int maxQueryComplexity, String payload,
FieldComplexityCalculator fieldComplexityCalculator) {
return analyseQueryComplexity(maxQueryComplexity, payload, fieldComplexityCalculator, new HashMap<>());
}

/**
* This method analyses the query complexity.
*
* @param fieldComplexityCalculator Field Complexity Calculator
* @param maxQueryComplexity Maximum query complexity value
* @param payload payload of the request
* @param variableMap external variable map
* @return true, if query complexity does not exceed the maximum or false, if query complexity exceeds the maximum
*/

public QueryAnalyzerResponseDTO analyseQueryComplexity(int maxQueryComplexity, String payload,
FieldComplexityCalculator fieldComplexityCalculator,
Map<String, Object> variableMap) {

if (log.isDebugEnabled()) {
log.debug("Analyzing query complexity for " + payload + " and max complexity: " + maxQueryComplexity);
Expand All @@ -111,7 +147,12 @@ public QueryAnalyzerResponseDTO analyseQueryComplexity(int maxQueryComplexity, S
new MaxQueryComplexityInstrumentation(maxQueryComplexity, fieldComplexityCalculator);
GraphQL runtime = GraphQL.newGraphQL(schema).instrumentation(maxQueryComplexityInstrumentation).build();

ExecutionResult executionResult = runtime.execute(payload);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(payload)
.variables(variableMap)
.build();

ExecutionResult executionResult = runtime.execute(executionInput);
List<GraphQLError> errors = executionResult.getErrors();
if (errors.size() > 0) {
for (GraphQLError error : errors) {
Expand Down Expand Up @@ -152,6 +193,24 @@ public QueryAnalyzerResponseDTO analyseQueryMutationComplexity(String payload, i
return analyseQueryComplexity(maxQueryComplexity, payload, fieldComplexityCalculator);
}

/**
* This method analyses the query complexity
*
* @param payload payload of the request
* @param complexityInfoJson gql complexity info in json string format
* @param variableMap
* @return true, if query complexity does not exceed the maximum or false, if query complexity exceeds the maximum
*/

public QueryAnalyzerResponseDTO analyseQueryMutationComplexity(String payload, int maxQueryComplexity,
String complexityInfoJson,
Map<String, Object> variableMap)
throws ParseException {
FieldComplexityCalculatorImpl fieldComplexityCalculator = new FieldComplexityCalculatorImpl();
fieldComplexityCalculator.parseAccessControlPolicy(complexityInfoJson);
return analyseQueryComplexity(maxQueryComplexity, payload, fieldComplexityCalculator, variableMap);
}

public GraphQLSchema getSchema() {
return schema;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
System.currentTimeMillis());
if (msg instanceof TextWebSocketFrame) {
WebSocketUtils.setApiPropertyToChannel(ctx, Constants.RESPONSE_SIZE,
((TextWebSocketFrame) msg).text().length());
((TextWebSocketFrame) msg).text().length());
}
}
// publish analytics events if analytics is enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.wso2.carbon.apimgt.common.analytics.collectors.AnalyticsDataProvider;
import org.wso2.carbon.apimgt.common.analytics.collectors.impl.GenericRequestDataCollector;
import org.wso2.carbon.apimgt.common.analytics.exceptions.AnalyticsException;
import org.wso2.carbon.apimgt.gateway.handlers.DataPublisherUtil;
import org.wso2.carbon.apimgt.gateway.handlers.graphQL.analytics.GraphQLOperationHandler;
import org.wso2.carbon.apimgt.gateway.handlers.streaming.AsyncAnalyticsDataProvider;
import org.wso2.carbon.apimgt.gateway.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.gateway.utils.GatewayUtils;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.inbound.endpoint.protocol.websocket.InboundWebsocketConstants;

import java.util.Map;

import static org.wso2.carbon.apimgt.gateway.APIMgtGatewayConstants.TRANSPORT_HEADERS;
/**
* Global synapse handler to publish analytics data to analytics cloud.
*/
Expand Down Expand Up @@ -82,19 +82,28 @@ public boolean handleResponseOutFlow(MessageContext messageContext) {
if (messageContext.getPropertyKeySet().contains(InboundWebsocketConstants.WEBSOCKET_SUBSCRIBER_PATH)) {
return true;
}
messageContext.setProperty(TRANSPORT_HEADERS, ((Axis2MessageContext) messageContext).getAxis2MessageContext().
HimanshiDeSilva marked this conversation as resolved.
Show resolved Hide resolved
getProperty(TRANSPORT_HEADERS));

AnalyticsDataProvider provider;
Object skipPublishMetrics = messageContext.getProperty(Constants.SKIP_DEFAULT_METRICS_PUBLISHING);
if (skipPublishMetrics != null && (Boolean) skipPublishMetrics) {
provider = new AsyncAnalyticsDataProvider(messageContext);
if (messageContext.getProperty(APIConstants.API_TYPE) == "GRAPHQL"){
HimanshiDeSilva marked this conversation as resolved.
Show resolved Hide resolved
GraphQLOperationHandler operationInfoAnalyzer = new GraphQLOperationHandler();
operationInfoAnalyzer.handleGraphQLOperation(messageContext);
} else {
provider = new SynapseAnalyticsDataProvider(messageContext,
ServiceReferenceHolder.getInstance().getAnalyticsCustomDataProvider());
}
GenericRequestDataCollector dataCollector = new GenericRequestDataCollector(provider);
try {
dataCollector.collectData();
} catch (Exception e) {
log.error("Error Occurred when collecting data", e);
if (skipPublishMetrics != null && (Boolean) skipPublishMetrics) {
provider = new AsyncAnalyticsDataProvider(messageContext);
} else {
provider = new SynapseAnalyticsDataProvider(messageContext,
ServiceReferenceHolder.getInstance().getAnalyticsCustomDataProvider());
}

GenericRequestDataCollector dataCollector = new GenericRequestDataCollector(provider);
try {
dataCollector.collectData();
} catch (Exception e) {
log.error("Error Occurred when collecting data", e);
}
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ public class Constants {
public static final String API_USER_NAME_KEY = "userName";
public static final String API_CONTEXT_KEY = "apiContext";
public static final String RESPONSE_SIZE = "responseSize";
public static final String OPERATION_NAME = "operationName";

public static final String OPERATION_INFO = "operationInfo";
public static final String QUERY_NAME = "queryName";
public static final String RESPONSE_CONTENT_TYPE = "responseContentType";
public static final String API_ANALYTICS_CUSTOM_DATA_PROVIDER_CLASS = "publisher.custom.data.provider.class";

public static final String ACCESSED_FIELDS = "accessedFields";
public static final String MUTATED_FIELDS = "mutatedFields";
public static final String REGION_ID_PROP = "apim.gw.region";
public static final String DEFAULT_REGION_ID = "default";
public static final String SUCCESS_EVENT_TYPE = "response";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public class SynapseAnalyticsDataProvider implements AnalyticsDataProvider {
private MessageContext messageContext;
private AnalyticsCustomDataProvider analyticsCustomDataProvider;
private Boolean buildResponseMessage = null;
private static final String GRAPHQL = "GRAPHQL";
private static final String QUERY_NAME = "QUERY_NAME";

public SynapseAnalyticsDataProvider(MessageContext messageContext) {

Expand Down Expand Up @@ -405,9 +407,30 @@ public Map<String, Object> getProperties() {
customProperties.put(Constants.API_CONTEXT_KEY, getApiContext());
customProperties.put(Constants.RESPONSE_SIZE, getResponseSize());
customProperties.put(Constants.RESPONSE_CONTENT_TYPE, getResponseContentType());
if (messageContext.getProperty(APIConstants.API_TYPE) == GRAPHQL) {
customProperties.put(Constants.QUERY_NAME, messageContext.getProperty(QUERY_NAME));
customProperties.put(Constants.OPERATION_INFO, getOperationInfo());
customProperties.put(Constants.ACCESSED_FIELDS, getAccessedFields());
customProperties.put(Constants.MUTATED_FIELDS, getMutatedFields());
}
return customProperties;
}

private Map<String, Object> getOperationInfo() {
Object fieldUsage = messageContext.getProperty(APIConstants.OPERATION_INFO);
return (Map<String, Object>) fieldUsage;
}

private Object getAccessedFields() {
Object accessedFields = messageContext.getProperty(APIConstants.ACCESSED_FIELDS);
return accessedFields;
}

private Object getMutatedFields() {
Object mutatedFields = messageContext.getProperty(APIConstants.MUTATED_FIELDS);
return mutatedFields;
}

private String getApiContext() {

if (messageContext.getPropertyKeySet().contains(JWTConstants.REST_API_CONTEXT)) {
Expand Down Expand Up @@ -508,15 +531,15 @@ public long getResponseMediationLatency() {
public int getResponseSize() {
int responseSize = 0;
if (buildResponseMessage == null) {
Map<String,String> configs = APIManagerConfiguration.getAnalyticsProperties();
Map<String, String> configs = APIManagerConfiguration.getAnalyticsProperties();
if (configs.containsKey(Constants.BUILD_RESPONSE_MESSAGE_CONFIG)) {
buildResponseMessage = Boolean.parseBoolean(configs.get(Constants.BUILD_RESPONSE_MESSAGE_CONFIG));
} else {
buildResponseMessage = false;
}
}
Map headers = (Map) messageContext.getProperty(TRANSPORT_HEADERS);
if (headers != null && headers.get(HttpHeaders.CONTENT_LENGTH) != null) {
if (headers != null && headers.get(HttpHeaders.CONTENT_LENGTH) != null) {
responseSize = Integer.parseInt(headers.get(HttpHeaders.CONTENT_LENGTH).toString());
}
if (responseSize == 0 && buildResponseMessage) {
Expand All @@ -536,7 +559,7 @@ public int getResponseSize() {
SOAPBody soapbody = env.getBody();
if (soapbody != null) {
byte[] size = soapbody.toString().getBytes(Charset.defaultCharset());
responseSize = size.length;
responseSize = size.length;
}
}
}
Expand Down
Loading