diff --git a/.gitignore b/.gitignore
index 9a4af6d92a..99e245988a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,7 @@ app/client/yalc.lock
.idea
.fleet/*
app/client/.fleet/*
+
+# Observability related local storage
+utils/observability/tempo-data/*
+
diff --git a/app/server/appsmith-interfaces/pom.xml b/app/server/appsmith-interfaces/pom.xml
index 23a28b9a1f..24b91ad3e5 100644
--- a/app/server/appsmith-interfaces/pom.xml
+++ b/app/server/appsmith-interfaces/pom.xml
@@ -236,6 +236,10 @@
spring-test
test
+
+ io.projectreactor
+ reactor-core-micrometer
+
diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ActionSpans.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ActionSpans.java
new file mode 100644
index 0000000000..3badfd9a93
--- /dev/null
+++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ActionSpans.java
@@ -0,0 +1,17 @@
+package com.appsmith.external.constants.spans;
+
+public final class ActionSpans {
+
+ // Action execution spans
+ public static final String ACTION_EXECUTION_REQUEST_PARSING = "request.parsing";
+ public static final String ACTION_EXECUTION_CACHED_ACTION = "get.action.cached";
+ public static final String ACTION_EXECUTION_CACHED_DATASOURCE = "get.datasource.cached";
+ public static final String ACTION_EXECUTION_CACHED_PLUGIN = "get.plugin.cached";
+ public static final String ACTION_EXECUTION_DATASOURCE_CONTEXT = "get.datasource.context";
+ public static final String ACTION_EXECUTION_DATASOURCE_CONTEXT_REMOTE = "get.datasource.context.remote";
+ public static final String ACTION_EXECUTION_EDITOR_CONFIG = "get.editorConfig.cached";
+ public static final String ACTION_EXECUTION_VALIDATE_AUTHENTICATION = "validate.authentication";
+ public static final String ACTION_EXECUTION_PLUGIN_EXECUTION = "total.plugin.execution";
+ public static final String ACTION_EXECUTION_SERVER_EXECUTION = "total.server.execution";
+
+}
diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java
index cde55ffa22..6fe9640901 100644
--- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java
+++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java
@@ -12,8 +12,10 @@ import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.TriggerRequestDTO;
import com.appsmith.external.models.TriggerResultDTO;
+import io.micrometer.observation.ObservationRegistry;
import org.pf4j.ExtensionPoint;
import org.springframework.util.CollectionUtils;
+import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuple2;
@@ -24,6 +26,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import static com.appsmith.external.constants.spans.ActionSpans.ACTION_EXECUTION_PLUGIN_EXECUTION;
import static com.appsmith.external.helpers.PluginUtils.getHintMessageForLocalhostUrl;
public interface PluginExecutor extends ExtensionPoint, CrudTemplateService {
@@ -171,6 +174,17 @@ public interface PluginExecutor extends ExtensionPoint, CrudTemplateService {
return this.execute(connection, datasourceConfiguration, actionConfiguration);
}
+ default Mono executeParameterizedWithMetrics(C connection,
+ ExecuteActionDTO executeActionDTO,
+ DatasourceConfiguration datasourceConfiguration,
+ ActionConfiguration actionConfiguration,
+ ObservationRegistry observationRegistry) {
+ return this.executeParameterized(connection, executeActionDTO, datasourceConfiguration, actionConfiguration)
+ .tag("plugin", this.getClass().getName())
+ .name(ACTION_EXECUTION_PLUGIN_EXECUTION)
+ .tap(Micrometer.observation(observationRegistry));
+ }
+
/**
* This function is responsible for preparing the action and datasource configurations to be ready for execution.
*
diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml
index 70ac59b0af..3099d3a79d 100644
--- a/app/server/appsmith-server/pom.xml
+++ b/app/server/appsmith-server/pom.xml
@@ -203,10 +203,15 @@
1.0.0
- io.micrometer
- context-propagation
- 1.0.0
+ io.zipkin.reporter2
+ zipkin-reporter-brave
+
+
+
+
+
+
org.junit.jupiter
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java
index 648dcfe486..63a0705d4b 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java
@@ -101,7 +101,7 @@ public class InstanceConfig implements ApplicationListener)
- pluginExecutor.executeParameterized(resourceContext.getConnection(),
+ pluginExecutor.executeParameterizedWithMetrics(resourceContext.getConnection(),
executeActionDTO,
validatedDatasource.getDatasourceConfiguration(),
- actionDTO.getActionConfiguration()))
+ actionDTO.getActionConfiguration(),
+ observationRegistry))
.map(actionExecutionResult -> {
ActionExecutionRequest actionExecutionRequest = actionExecutionResult.getRequest();
if (actionExecutionRequest == null) {
@@ -853,7 +869,10 @@ public class NewActionServiceCEImpl extends BaseService getValidatedDatasourceForActionExecution(Datasource datasource, String environmentId) {
// the environmentName argument is not consumed over here
// See EE override for usage of variable
- return authenticationValidator.validateAuthentication(datasource, environmentId).cache();
+ return authenticationValidator.validateAuthentication(datasource, environmentId)
+ .name(ACTION_EXECUTION_VALIDATE_AUTHENTICATION)
+ .tap(Micrometer.observation(observationRegistry))
+ .cache();
}
/**
@@ -869,9 +888,15 @@ public class NewActionServiceCEImpl extends BaseService environmentMap) {
if (plugin.isRemotePlugin()) {
- return datasourceContextService.getRemoteDatasourceContext(plugin, validatedDatasource);
+ return datasourceContextService.getRemoteDatasourceContext(plugin, validatedDatasource)
+ .tag("plugin", plugin.getPackageName())
+ .name(ACTION_EXECUTION_DATASOURCE_CONTEXT_REMOTE)
+ .tap(Micrometer.observation(observationRegistry));
}
- return datasourceContextService.getDatasourceContext(validatedDatasource, datasourceContextIdentifier, environmentMap);
+ return datasourceContextService.getDatasourceContext(validatedDatasource, datasourceContextIdentifier, environmentMap)
+ .tag("plugin", plugin.getPackageName())
+ .name(ACTION_EXECUTION_DATASOURCE_CONTEXT)
+ .tap(Micrometer.observation(observationRegistry));
}
/**
@@ -1188,7 +1213,9 @@ public class NewActionServiceCEImpl extends BaseService this.executeAction(executeActionDTO, environmentName));
+ .flatMap(executeActionDTO -> this.executeAction(executeActionDTO, environmentName))
+ .name(ACTION_EXECUTION_SERVER_EXECUTION)
+ .tap(Micrometer.observation(observationRegistry));
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java
index 6bf45f4f70..f665fd9dae 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java
@@ -788,7 +788,8 @@ public class PageLoadActionsUtilCEImpl implements PageLoadActionsUtilCE {
Set bindingPaths = actionBindingMap.keySet();
- return Flux.fromIterable(bindingPaths).flatMap(bindingPath -> {
+ return Flux.fromIterable(bindingPaths)
+ .flatMap(bindingPath -> {
EntityDependencyNode actionDependencyNode = new EntityDependencyNode(entityDependencyNode.getEntityReferenceType(), entityDependencyNode.getValidEntityName(), bindingPath, null, false, action);
return getPossibleEntityReferences(actionNameToActionMapMono, actionBindingMap.get(bindingPath), evalVersion, bindingsInDsl)
.flatMapMany(Flux::fromIterable)
diff --git a/app/server/appsmith-server/src/main/resources/application.properties b/app/server/appsmith-server/src/main/resources/application.properties
index fd55deb063..037dc04c82 100644
--- a/app/server/appsmith-server/src/main/resources/application.properties
+++ b/app/server/appsmith-server/src/main/resources/application.properties
@@ -50,7 +50,7 @@ segment.ce.key = ${APPSMITH_SEGMENT_CE_KEY:}
# Sentry
sentry.dsn=${APPSMITH_SENTRY_DSN:}
sentry.send-default-pii=true
-sentry.debug=off
+sentry.debug=false
sentry.environment=${APPSMITH_SENTRY_ENVIRONMENT:}
# Redis Properties
@@ -87,13 +87,12 @@ encrypt.password=${APPSMITH_ENCRYPTION_PASSWORD:}
encrypt.salt=${APPSMITH_ENCRYPTION_SALT:}
# The following configurations are to help support prometheus scraping for monitoring
-management.endpoints.web.exposure.include=prometheus
-management.metrics.web.server.request.autotime.enabled=true
+management.endpoints.web.exposure.include=prometheus,metrics
+management.tracing.enabled=${APPSMITH_TRACING_ENABLED:false}
+management.zipkin.tracing.endpoint=${APPSMITH_TRACING_ENDPOINT:http://localhost:9411}/api/v2/spans
+management.tracing.sampling.probability=${APPSMITH_SAMPLING_PROBABILITY:0.1}
management.prometheus.metrics.export.descriptions=true
-management.metrics.web.server.request.ignore-trailing-slash=true
-management.metrics.web.server.request.autotime.percentiles=0.5, 0.9, 0.95, 0.99
-management.metrics.web.server.request.autotime.percentiles-histogram=true
-management.metrics.distribution.sla.[http.server.requests]=1s
+management.metrics.distribution.percentiles-histogram.http.server.requests=true
# Support disabling signup with an environment variable
signup.disabled = ${APPSMITH_SIGNUP_DISABLED:false}
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationTemplateServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationTemplateServiceTest.java
index 4834df3b98..025748307d 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationTemplateServiceTest.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationTemplateServiceTest.java
@@ -144,15 +144,14 @@ public class ApplicationTemplateServiceTest {
// make sure we've received the response returned by the mockCloudServices
StepVerifier.create(applicationTemplateService.getRecentlyUsedTemplates())
- .assertNext(applicationTemplates -> assertThat(applicationTemplates.size()).isEqualTo(1))
+ .assertNext(applicationTemplates -> assertThat(applicationTemplates).hasSize(1))
.verifyComplete();
// verify that mockCloudServices was called with the query param id i.e. id=id-one&id=id-two
RecordedRequest recordedRequest = mockCloudServices.takeRequest();
+ assert recordedRequest.getRequestUrl() != null;
List queryParameterValues = recordedRequest.getRequestUrl().queryParameterValues("id");
- assertThat(queryParameterValues).contains("id-one");
- assertThat(queryParameterValues).contains("id-two");
- assertThat(queryParameterValues.size()).isEqualTo(2);
+ assertThat(queryParameterValues).containsExactly("id-one", "id-two");
}
@Test
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java
index 810ad3a920..5f00f48b81 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java
@@ -839,7 +839,7 @@ public class ActionServiceCE_Test {
AppsmithPluginException pluginException = new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR);
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor));
- Mockito.when(pluginExecutor.executeParameterized(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(Mono.error(pluginException));
+ Mockito.when(pluginExecutor.executeParameterizedWithMetrics(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(Mono.error(pluginException));
Mockito.when(pluginExecutor.datasourceCreate(Mockito.any())).thenReturn(Mono.empty());
Mono executionResultMono = newActionService.executeAction(executeActionDTO, null);
@@ -889,7 +889,7 @@ public class ActionServiceCE_Test {
AppsmithPluginException pluginException = new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR);
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor));
- Mockito.when(pluginExecutor.executeParameterized(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(Mono.error(pluginException));
+ Mockito.when(pluginExecutor.executeParameterizedWithMetrics(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(Mono.error(pluginException));
Mockito.when(pluginExecutor.datasourceCreate(Mockito.any())).thenReturn(Mono.empty());
Mono executionResultMono = newActionService.executeAction(executeActionDTO, null);
@@ -932,7 +932,7 @@ public class ActionServiceCE_Test {
executeActionDTO.setViewMode(false);
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor));
- Mockito.when(pluginExecutor.executeParameterized(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
+ Mockito.when(pluginExecutor.executeParameterizedWithMetrics(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
.thenReturn(Mono.error(new StaleConnectionException())).thenReturn(Mono.error(new StaleConnectionException()));
Mockito.when(pluginExecutor.datasourceCreate(Mockito.any())).thenReturn(Mono.empty());
@@ -976,7 +976,7 @@ public class ActionServiceCE_Test {
executeActionDTO.setViewMode(false);
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor));
- Mockito.when(pluginExecutor.executeParameterized(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
+ Mockito.when(pluginExecutor.executeParameterizedWithMetrics(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
.thenAnswer(x -> Mono.delay(Duration.ofMillis(1000)).ofType(ActionExecutionResult.class));
Mockito.when(pluginExecutor.datasourceCreate(Mockito.any())).thenReturn(Mono.empty());
@@ -1061,7 +1061,7 @@ public class ActionServiceCE_Test {
mockResult.setBody("response-body");
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor));
- Mockito.when(pluginExecutor.executeParameterized(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
+ Mockito.when(pluginExecutor.executeParameterizedWithMetrics(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
.thenThrow(new StaleConnectionException())
.thenReturn(Mono.just(mockResult));
Mockito.when(pluginExecutor.datasourceCreate(Mockito.any())).thenReturn(Mono.empty());
@@ -1114,7 +1114,7 @@ public class ActionServiceCE_Test {
private Mono executeAction(ExecuteActionDTO executeActionDTO, ActionConfiguration actionConfiguration, ActionExecutionResult mockResult) {
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor));
- Mockito.when(pluginExecutor.executeParameterized(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(Mono.just(mockResult));
+ Mockito.when(pluginExecutor.executeParameterizedWithMetrics(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(Mono.just(mockResult));
Mockito.when(pluginExecutor.datasourceCreate(Mockito.any())).thenReturn(Mono.empty());
Mono actionExecutionResultMono = newActionService.executeAction(executeActionDTO, null);
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java
index 007b12c58a..9d140a1545 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java
@@ -165,7 +165,11 @@ public class NewActionServiceCEImplTest {
datasourcePermission,
applicationPermission,
pagePermission,
- actionPermission);
+ actionPermission,
+ observationRegistry);
+
+ ObservationRegistry.ObservationConfig mockObservationConfig = Mockito.mock(ObservationRegistry.ObservationConfig.class);
+ Mockito.when(observationRegistry.observationConfig()).thenReturn(mockObservationConfig);
}
@BeforeEach
@@ -349,8 +353,8 @@ public class NewActionServiceCEImplTest {
StepVerifier
.create(actionExecutionResultMono)
.assertNext(response -> {
- assertTrue(response.getIsExecutionSuccess());
assertTrue(response instanceof ActionExecutionResult);
+ assertTrue(response.getIsExecutionSuccess());
assertEquals(mockResult.getBody().toString(), response.getBody().toString());
})
.verifyComplete();
@@ -399,8 +403,8 @@ public class NewActionServiceCEImplTest {
StepVerifier
.create(actionExecutionResultMono)
.assertNext(response -> {
- assertTrue(response.getIsExecutionSuccess());
assertTrue(response instanceof ActionExecutionResult);
+ assertTrue(response.getIsExecutionSuccess());
assertEquals(mockResult.getBody().toString(), response.getBody().toString());
})
.verifyComplete();
diff --git a/utils/observability/docker-compose.yml b/utils/observability/docker-compose.yml
new file mode 100644
index 0000000000..c1b99a9486
--- /dev/null
+++ b/utils/observability/docker-compose.yml
@@ -0,0 +1,54 @@
+networks:
+ default:
+ name: operations-dc
+
+services:
+ tempo:
+ image: grafana/tempo
+ extra_hosts: ['host.docker.internal:host-gateway']
+ command: [ "-config.file=/etc/tempo.yaml" ]
+ volumes:
+ - ./docker/tempo/tempo-local.yaml:/etc/tempo.yaml:ro
+ - ./tempo-data:/tmp/tempo
+ ports:
+ - "14268" # jaeger ingest
+ - "9411:9411" # zipkin
+ - "3200:3200"
+
+ # loki:
+ # image: grafana/loki
+ # extra_hosts: ['host.docker.internal:host-gateway']
+ # command: [ "-config.file=/etc/loki/local-config.yaml" ]
+ # ports:
+ # - "3100:3100" # loki needs to be exposed so it receives logs
+ # environment:
+ # - JAEGER_AGENT_HOST=tempo
+ # - JAEGER_ENDPOINT=http://tempo:14268/api/traces # send traces to Tempo
+ # - JAEGER_SAMPLER_TYPE=const
+ # - JAEGER_SAMPLER_PARAM=1
+
+ prometheus:
+ image: prom/prometheus
+ extra_hosts: ['host.docker.internal:host-gateway']
+ command:
+ - --enable-feature=exemplar-storage
+ - --config.file=/etc/prometheus/prometheus.yml
+ volumes:
+ - ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
+ ports:
+ - "9090:9090"
+
+ grafana:
+ image: grafana/grafana
+ extra_hosts: ['host.docker.internal:host-gateway']
+ volumes:
+ - ./docker/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
+ - ./docker/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
+ environment:
+ - GF_AUTH_ANONYMOUS_ENABLED=true
+ - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
+ - GF_AUTH_DISABLE_LOGIN_FORM=true
+ ports:
+ - "3001:3000"
+# Prometheus: http://localhost:9090/
+# Grafana: http://localhost:3000/
diff --git a/utils/observability/docker/grafana/provisioning/dashboards/dashboard.yml b/utils/observability/docker/grafana/provisioning/dashboards/dashboard.yml
new file mode 100644
index 0000000000..31d21dbf56
--- /dev/null
+++ b/utils/observability/docker/grafana/provisioning/dashboards/dashboard.yml
@@ -0,0 +1,10 @@
+apiVersion: 1
+
+providers:
+ - name: dashboards
+ type: file
+ disableDeletion: true
+ editable: true
+ options:
+ path: /etc/grafana/provisioning/dashboards
+ foldersFromFilesStructure: true
diff --git a/utils/observability/docker/grafana/provisioning/dashboards/logs_traces_metrics.json b/utils/observability/docker/grafana/provisioning/dashboards/logs_traces_metrics.json
new file mode 100644
index 0000000000..21d6233f45
--- /dev/null
+++ b/utils/observability/docker/grafana/provisioning/dashboards/logs_traces_metrics.json
@@ -0,0 +1,294 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": 6,
+ "iteration": 1654517000502,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "description": "",
+ "gridPos": {
+ "h": 10,
+ "w": 23,
+ "x": 0,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "dedupStrategy": "none",
+ "enableLogDetails": true,
+ "prettifyLogMessage": true,
+ "showCommonLabels": true,
+ "showLabels": true,
+ "showTime": true,
+ "sortOrder": "Ascending",
+ "wrapLogMessage": true
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "editorMode": "builder",
+ "expr": "{traceID=\"$traceID\"}",
+ "queryType": "range",
+ "refId": "A"
+ }
+ ],
+ "title": "Logs with trace ID $traceID",
+ "type": "logs"
+ },
+ {
+ "datasource": {
+ "type": "tempo",
+ "uid": "tempo"
+ },
+ "description": "",
+ "gridPos": {
+ "h": 15,
+ "w": 23,
+ "x": 0,
+ "y": 10
+ },
+ "id": 6,
+ "targets": [
+ {
+ "datasource": {
+ "type": "tempo",
+ "uid": "tempo"
+ },
+ "query": "$traceID",
+ "queryType": "traceId",
+ "refId": "A"
+ }
+ ],
+ "title": "Trace View for trace with id $traceID",
+ "type": "traces"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 23,
+ "x": 0,
+ "y": 25
+ },
+ "id": 4,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom"
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editorMode": "code",
+ "exemplar": true,
+ "expr": "histogram_quantile(1.00, sum(rate(http_server_requests_seconds_bucket{uri=~\".*\"}[$__rate_interval])) by (le))",
+ "legendFormat": "max",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editorMode": "code",
+ "exemplar": true,
+ "expr": "histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{uri=~\".*\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "tp99",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editorMode": "code",
+ "exemplar": true,
+ "expr": "histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{uri=~\".*\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "tp95",
+ "range": true,
+ "refId": "C"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editorMode": "code",
+ "exemplar": true,
+ "expr": "histogram_quantile(1.00, sum(rate(server_job_seconds_bucket[$__rate_interval])) by (le))",
+ "legendFormat": "max",
+ "range": true,
+ "refId": "D"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editorMode": "code",
+ "exemplar": true,
+ "expr": "histogram_quantile(0.99, sum(rate(server_job_seconds_bucket[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "tp99",
+ "range": true,
+ "refId": "E"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "editorMode": "code",
+ "exemplar": true,
+ "expr": "histogram_quantile(0.95, sum(rate(server_job_seconds_bucket[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "tp95",
+ "range": true,
+ "refId": "F"
+ }
+ ],
+ "title": "latency for All",
+ "type": "timeseries"
+ }
+ ],
+ "schemaVersion": 36,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": false,
+ "text": "0003776c79e02b6c",
+ "value": "0003776c79e02b6c"
+ },
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "definition": "label_values(traceID)",
+ "hide": 0,
+ "includeAll": false,
+ "label": "Trace ID",
+ "multi": false,
+ "name": "traceID",
+ "options": [],
+ "query": "label_values(traceID)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-15m",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "Logs, Traces, Metrics",
+ "uid": "szVLMe97z",
+ "version": 7,
+ "weekStart": ""
+}
diff --git a/utils/observability/docker/grafana/provisioning/datasources/datasource.yml b/utils/observability/docker/grafana/provisioning/datasources/datasource.yml
new file mode 100644
index 0000000000..6d95a8c9af
--- /dev/null
+++ b/utils/observability/docker/grafana/provisioning/datasources/datasource.yml
@@ -0,0 +1,45 @@
+apiVersion: 1
+
+datasources:
+ - name: Prometheus
+ type: prometheus
+ access: proxy
+ url: http://host.docker.internal:9090
+ editable: false
+ jsonData:
+ httpMethod: POST
+ exemplarTraceIdDestinations:
+ - name: trace_id
+ datasourceUid: 'tempo'
+ - name: Tempo
+ type: tempo
+ access: proxy
+ orgId: 1
+ url: http://tempo:3200
+ basicAuth: false
+ isDefault: true
+ version: 1
+ editable: false
+ apiVersion: 1
+ uid: tempo
+ jsonData:
+ httpMethod: GET
+ tracesToLogs:
+ datasourceUid: 'loki'
+ - name: Loki
+ type: loki
+ uid: loki
+ access: proxy
+ orgId: 1
+ url: http://loki:3100
+ basicAuth: false
+ isDefault: false
+ version: 1
+ editable: false
+ apiVersion: 1
+ jsonData:
+ derivedFields:
+ - datasourceUid: 'tempo'
+ matcherRegex: \[.+,(.+?),
+ name: TraceID
+ url: $${__value.raw}
diff --git a/utils/observability/docker/prometheus/prometheus.yml b/utils/observability/docker/prometheus/prometheus.yml
new file mode 100644
index 0000000000..fa40d133a5
--- /dev/null
+++ b/utils/observability/docker/prometheus/prometheus.yml
@@ -0,0 +1,12 @@
+global:
+ scrape_interval: 2s
+ evaluation_interval: 2s
+
+scrape_configs:
+ - job_name: 'prometheus'
+ static_configs:
+ - targets: ['host.docker.internal:9090']
+ - job_name: 'apps'
+ metrics_path: '/actuator/prometheus'
+ static_configs:
+ - targets: ['host.docker.internal:8080','host.docker.internal:8989']
diff --git a/utils/observability/docker/tempo/tempo-local.yaml b/utils/observability/docker/tempo/tempo-local.yaml
new file mode 100644
index 0000000000..1d23b51c89
--- /dev/null
+++ b/utils/observability/docker/tempo/tempo-local.yaml
@@ -0,0 +1,12 @@
+server:
+ http_listen_port: 3200
+
+distributor:
+ receivers:
+ zipkin:
+
+storage:
+ trace:
+ backend: local
+ local:
+ path: /tmp/tempo/blocks