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 command name to count metrics #437

Merged
merged 16 commits into from
May 31, 2023
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
10 changes: 10 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,13 @@ Here are some Stargate-relevant property groups that are necessary for correct s
| `stargate.jsonapi.operations.max-document-delete-count` | `int` | `20` | The maximum amount of documents that can be deleted in a single operation. In case there are more documents that could be deleted, the operation will set the `moreData` response status to `true`. |
| `stargate.jsonapi.operations.max-in-operator-value-size` | `int` | `100` | The maximum number of _id values that can be passed for `$in` operator. |
| `stargate.jsonapi.operations.lwt.retries` | `int` | `3` | The amount of client side retries in case of a LWT failure. |

## Jsonapi metering configuration
*Configuration for jsonapi metering, defined by [JsonApiMetricsConfig.java](io/stargate/sgv2/jsonapi/api/v1/metrics/JsonApiMetricsConfig.java).*

| Property | Type | Default | Description |
|---------------------------------------|----------|---------------|--------------------------------------------------------------|
| `stargate.jsonapi.metric.error-class` | `string` | `error.class` | Metrics tag that provides information about the error class. |
| `stargate.jsonapi.metric.error-code` | `string` | `error.code` | Metrics tag that provides information about the error code. |
| `stargate.jsonapi.metric.command` | `string` | `command` | Metrics tag that provides information about the command. |
| `stargate.jsonapi.metric.metrics.name`| `string` | `jsonapi` | Metrics name prefix. |
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.Response;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -184,15 +183,6 @@ public record Error(
"Error fields can not contain the reserved message key.");
}
}

/**
* Constructor that sets documents only the message.
*
* @param message Error message.
*/
public Error(String message) {
this(message, Collections.emptyMap(), Response.Status.OK);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import jakarta.inject.Inject;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.Collections;
import java.util.List;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.slf4j.Logger;
Expand All @@ -34,12 +36,12 @@ public ErrorChallengeSender(
String headerName,
ObjectMapper objectMapper) {
this.objectMapper = objectMapper;

// create the response
String message =
"Role unauthorized for operation: Missing token, expecting one in the %s header."
.formatted(headerName);
CommandResult.Error error = new CommandResult.Error(message);
CommandResult.Error error =
new CommandResult.Error(message, Collections.emptyMap(), Response.Status.UNAUTHORIZED);
commandResult = new CommandResult(List.of(error));
}

Expand All @@ -59,8 +61,8 @@ public Uni<Boolean> apply(RoutingContext context, ChallengeData challengeData) {
.headers()
.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(response.getBytes().length));

// always set status to 200
context.response().setStatusCode(200);
// Return the status code from the challenge data
context.response().setStatusCode(challengeData.status);

// write and map to true
return Uni.createFrom()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import io.stargate.sgv2.jsonapi.api.model.command.impl.UpdateManyCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.UpdateOneCommand;
import io.stargate.sgv2.jsonapi.config.constants.OpenApiConstants;
import io.stargate.sgv2.jsonapi.service.processor.CommandProcessor;
import io.stargate.sgv2.jsonapi.service.processor.MeteredCommandProcessor;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
Expand Down Expand Up @@ -51,11 +51,11 @@ public class CollectionResource {

public static final String BASE_PATH = "/v1/{namespace}/{collection}";

private final CommandProcessor commandProcessor;
private final MeteredCommandProcessor meteredCommandProcessor;

@Inject
public CollectionResource(CommandProcessor commandProcessor) {
this.commandProcessor = commandProcessor;
public CollectionResource(MeteredCommandProcessor meteredCommandProcessor) {
maheshrajamani marked this conversation as resolved.
Show resolved Hide resolved
this.meteredCommandProcessor = meteredCommandProcessor;
}

@Operation(
Expand Down Expand Up @@ -144,7 +144,7 @@ public Uni<RestResponse<CommandResult>> postCommand(
CommandContext commandContext = new CommandContext(namespace, collection);

// call processor
return commandProcessor
return meteredCommandProcessor
.processCommand(commandContext, command)
// map to 2xx unless overridden by error
.map(commandResult -> commandResult.map());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import io.stargate.sgv2.jsonapi.api.model.command.GeneralCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.CreateNamespaceCommand;
import io.stargate.sgv2.jsonapi.config.constants.OpenApiConstants;
import io.stargate.sgv2.jsonapi.service.processor.CommandProcessor;
import io.stargate.sgv2.jsonapi.service.processor.MeteredCommandProcessor;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
Expand Down Expand Up @@ -35,11 +35,11 @@ public class GeneralResource {

public static final String BASE_PATH = "/v1";

private final CommandProcessor commandProcessor;
private final MeteredCommandProcessor meteredCommandProcessor;

@Inject
public GeneralResource(CommandProcessor commandProcessor) {
this.commandProcessor = commandProcessor;
public GeneralResource(MeteredCommandProcessor meteredCommandProcessor) {
this.meteredCommandProcessor = meteredCommandProcessor;
}

@Operation(summary = "Execute command", description = "Executes a single general command.")
Expand Down Expand Up @@ -72,7 +72,7 @@ public GeneralResource(CommandProcessor commandProcessor) {
public Uni<RestResponse<CommandResult>> postCommand(@NotNull @Valid GeneralCommand command) {

// call processor
return commandProcessor
return meteredCommandProcessor
.processCommand(CommandContext.empty(), command)
// map to 2xx unless overridden by error
.map(commandResult -> commandResult.map());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import io.stargate.sgv2.jsonapi.api.model.command.NamespaceCommand;
import io.stargate.sgv2.jsonapi.api.model.command.impl.CreateCollectionCommand;
import io.stargate.sgv2.jsonapi.config.constants.OpenApiConstants;
import io.stargate.sgv2.jsonapi.service.processor.CommandProcessor;
import io.stargate.sgv2.jsonapi.service.processor.MeteredCommandProcessor;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
Expand Down Expand Up @@ -40,11 +40,11 @@ public class NamespaceResource {

public static final String BASE_PATH = "/v1/{namespace}";

private final CommandProcessor commandProcessor;
private final MeteredCommandProcessor meteredCommandProcessor;

@Inject
public NamespaceResource(CommandProcessor commandProcessor) {
this.commandProcessor = commandProcessor;
public NamespaceResource(MeteredCommandProcessor meteredCommandProcessor) {
this.meteredCommandProcessor = meteredCommandProcessor;
}

@Operation(
Expand Down Expand Up @@ -88,7 +88,7 @@ public Uni<RestResponse<CommandResult>> postCommand(
CommandContext commandContext = new CommandContext(namespace, null);

// call processor
return commandProcessor
return meteredCommandProcessor
.processCommand(commandContext, command)
// map to 2xx unless overridden by error
.map(commandResult -> commandResult.map());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.stargate.sgv2.jsonapi.api.v1.metrics;

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;
import jakarta.validation.constraints.NotBlank;

@ConfigMapping(prefix = "stargate.jsonapi.metric")
public interface JsonApiMetricsConfig {
@NotBlank
@WithDefault("error.class")
String errorClass();

@NotBlank
@WithDefault("error.code")
String errorCode();

@NotBlank
@WithDefault("command")
String command();

@NotBlank
@WithDefault("command.processor.process")
String metricsName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.stargate.sgv2.jsonapi.service.processor;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.smallrye.mutiny.Uni;
import io.stargate.sgv2.api.common.StargateRequestInfo;
import io.stargate.sgv2.api.common.config.MetricsConfig;
import io.stargate.sgv2.jsonapi.api.model.command.Command;
import io.stargate.sgv2.jsonapi.api.model.command.CommandContext;
import io.stargate.sgv2.jsonapi.api.model.command.CommandResult;
import io.stargate.sgv2.jsonapi.api.v1.metrics.JsonApiMetricsConfig;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class MeteredCommandProcessor {

private static final String UNKNOWN_VALUE = "unknown";

private static final String NA = "NA";

private final CommandProcessor commandProcessor;

private final MeterRegistry meterRegistry;

private final StargateRequestInfo stargateRequestInfo;

private final JsonApiMetricsConfig jsonApiMetricsConfig;

private final MetricsConfig.TenantRequestCounterConfig tenantConfig;

/** The tag for error being true, created only once. */
private final Tag errorTrue;

/** The tag for error being false, created only once. */
private final Tag errorFalse;

/** The tag for tenant being unknown, created only once. */
private final Tag tenantUnknown;

private final Tag defaultErrorCode;

private final Tag defaultErrorClass;

@Inject
public MeteredCommandProcessor(
CommandProcessor commandProcessor,
MeterRegistry meterRegistry,
StargateRequestInfo stargateRequestInfo,
JsonApiMetricsConfig jsonApiMetricsConfig,
MetricsConfig metricsConfig) {
this.commandProcessor = commandProcessor;
this.meterRegistry = meterRegistry;
this.jsonApiMetricsConfig = jsonApiMetricsConfig;
tenantConfig = metricsConfig.tenantRequestCounter();
this.stargateRequestInfo = stargateRequestInfo;
errorTrue = Tag.of(tenantConfig.errorTag(), "true");
errorFalse = Tag.of(tenantConfig.errorTag(), "false");
tenantUnknown = Tag.of(tenantConfig.tenantTag(), UNKNOWN_VALUE);
defaultErrorCode = Tag.of(jsonApiMetricsConfig.errorCode(), NA);
defaultErrorClass = Tag.of(jsonApiMetricsConfig.errorClass(), NA);
}

/**
* Processes a single command in a given command context.
*
* @param commandContext {@link CommandContext}
* @param command {@link Command}
* @return Uni emitting the result of the command execution.
* @param <T> Type of the command.
*/
public <T extends Command> Uni<CommandResult> processCommand(
CommandContext commandContext, T command) {
Timer.Sample sample = Timer.start(meterRegistry);
// start by resolving the command, get resolver
return commandProcessor
.processCommand(commandContext, command)
.onItem()
.invoke(
result -> {
Tags tags = getCustomTags(command, result);
// add metrics
sample.stop(meterRegistry.timer(jsonApiMetricsConfig.metricsName(), tags));
});
}

/**
* Generate custom tags based on the command and result.
*
* @param command - request command
* @param result - response command result
* @return
*/
private Tags getCustomTags(Command command, CommandResult result) {
Tag commandTag = Tag.of(jsonApiMetricsConfig.command(), command.getClass().getSimpleName());
String tenant = stargateRequestInfo.getTenantId().orElse(UNKNOWN_VALUE);
Tag tenantTag = Tag.of(tenantConfig.tenantTag(), tenant);
Tag errorTag = errorFalse;
Tag errorClassTag = defaultErrorClass;
Tag errorCodeTag = defaultErrorCode;
// if error is present, add error tags else use defaults
if (null != result.errors() && !result.errors().isEmpty()) {
errorTag = errorTrue;
String errorClass =
(String) result.errors().get(0).fields().getOrDefault("exceptionClass", UNKNOWN_VALUE);
errorClassTag = Tag.of(jsonApiMetricsConfig.errorClass(), errorClass);
String errorCode =
(String) result.errors().get(0).fields().getOrDefault("errorCode", UNKNOWN_VALUE);
errorCodeTag = Tag.of(jsonApiMetricsConfig.errorCode(), errorCode);
}
Tags tags = Tags.of(commandTag, tenantTag, errorTag, errorClassTag, errorCodeTag);
maheshrajamani marked this conversation as resolved.
Show resolved Hide resolved
return tags;
}
}
Loading