This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 1d06fa09c3b4 CAMEL-24051: Add observability test-infra module (#24689)
1d06fa09c3b4 is described below
commit 1d06fa09c3b441d347f635c13f8a575d50718bad
Author: Federico Mariani <[email protected]>
AuthorDate: Thu Jul 16 18:50:47 2026 +0200
CAMEL-24051: Add observability test-infra module (#24689)
* CAMEL-24051: Add observability test-infra module
New test-infra module composing Prometheus, VictoriaTraces, VictoriaLogs,
and Perses for local observability via `camel infra run observability`.
- Prometheus scrapes Camel metrics at :9876/observe/metrics (zero-config
with --observe)
- VictoriaTraces receives OTLP traces at :10428
- VictoriaLogs receives OTLP logs at :9428
- Perses auto-provisioned with Camel project and overview dashboard at :3000
- New --open-telemetry-agent-export=observability target for traces+logs
- Deprecated --open-telemetry-agent-export=jaeger in favor of otlp
- Generalized OpenTelemetryTracer.exportTarget check to accept any value
- Fixed fork version propagation in Run.java for camel run with OTel agent
* CAMEL-24051: Fix review feedback and add upgrade guide
- Fix: observability export now treated as external in Run.java (no dead
embedded receiver)
- Fix: set camel.opentelemetry2.exportTarget=observability in fork path
- Revert: fork version propagation change (separate concern, separate JIRA)
- Use mirror.gcr.io instead of docker.io for container images
- Add upgrade guide entry for export target changes and new infra command
* CAMEL-24051: Align with CAMEL-24087 UI support
- Add uiSupported=true to @InfraService annotation
- Add uiUrl() default method returning Perses dashboard URL
- Fix: add observability to externalExport check in Run.java
- Fix: set camel.opentelemetry2.exportTarget=observability in fork path
* CAMEL-24051: Address gnodet review feedback
- Fix FQCN: import StandardCharsets instead of inline fully qualified name
- Add skipITs.ppc64le and skipITs.s390x (Perses/Victoria images lack these
archs)
- Safe shutdown: wrap each container stop in try-catch to prevent resource
leaks
---
.../apache/camel/catalog/test-infra/metadata.json | 11 +
.../camel/opentelemetry2/OpenTelemetryTracer.java | 4 +-
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 14 +
.../pages/jbang-commands/camel-jbang-debug.adoc | 2 +-
.../ROOT/pages/jbang-commands/camel-jbang-dev.adoc | 2 +-
.../ROOT/pages/jbang-commands/camel-jbang-run.adoc | 2 +-
.../META-INF/camel-jbang-commands-metadata.json | 6 +-
.../apache/camel/dsl/jbang/core/commands/Run.java | 32 +-
test-infra/camel-test-infra-all/pom.xml | 11 +
.../src/generated/resources/META-INF/metadata.json | 11 +
test-infra/camel-test-infra-observability/pom.xml | 46 ++
.../common/ObservabilityProperties.java | 40 ++
.../services/ObservabilityInfraService.java | 64 +++
.../ObservabilityLocalContainerInfraService.java | 208 +++++++
.../observability/services/PersesContainer.java | 36 ++
.../services/PrometheusContainer.java | 58 ++
.../services/VictoriaLogsContainer.java | 35 ++
.../services/VictoriaTracesContainer.java | 36 ++
.../observability/services/container.properties | 27 +
.../observability/services/perses-dashboard.json | 607 +++++++++++++++++++++
.../observability/services/perses-datasource.json | 20 +
.../observability/services/perses-project.json | 7 +
.../infra/observability/services/prometheus.yml | 29 +
test-infra/pom.xml | 1 +
24 files changed, 1293 insertions(+), 16 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
index a751e0b1d99e..d0efd2259650 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
@@ -207,6 +207,17 @@
"version" : "4.22.0-SNAPSHOT",
"serviceVersion" : "latest",
"uiSupported" : false
+}, {
+ "service" :
"org.apache.camel.test.infra.observability.services.ObservabilityInfraService",
+ "description" : "Local observability stack (Prometheus + VictoriaTraces +
VictoriaLogs + Perses)",
+ "implementation" :
"org.apache.camel.test.infra.observability.services.ObservabilityLocalContainerInfraService",
+ "alias" : [ "observability" ],
+ "aliasImplementation" : [ ],
+ "groupId" : "org.apache.camel",
+ "artifactId" : "camel-test-infra-observability",
+ "version" : "4.22.0-SNAPSHOT",
+ "serviceVersion" : null,
+ "uiSupported" : true
}, {
"service" : "org.apache.camel.test.infra.solr.services.SolrInfraService",
"description" : "Apache Solr is an open source search platform",
diff --git
a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java
b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java
index 0c6db886484c..2fb9a3804203 100644
---
a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java
+++
b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java
@@ -95,8 +95,8 @@ public class OpenTelemetryTracer extends
org.apache.camel.telemetry.Tracer {
private void initDevSpanExporter() {
if (isOpenTelemetryAgentPresent()) {
- if ("jaeger".equals(exportTarget)) {
- LOG.info("OpenTelemetry Java Agent detected, exporting traces
to Jaeger");
+ if (exportTarget != null && !exportTarget.isEmpty()) {
+ LOG.info("OpenTelemetry Java Agent detected, exporting traces
externally (target: {})", exportTarget);
return;
}
LOG.info("OpenTelemetry Java Agent detected, using embedded OTLP
receiver");
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 86c29ff5905b..c98427b5f5f1 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -91,6 +91,20 @@ arguments to the adjacent `camel.bat` (preserving spaces and
Unicode) and return
its exit code. It exists so package managers that require a genuine executable
users may continue to invoke `bin\camel.bat`; both behave identically.
+==== OpenTelemetry agent export target changes
+
+The `--open-telemetry-agent-export` option has been extended with new values
and a deprecation:
+
+* `observability` (new) — exports traces to VictoriaTraces and logs to
VictoriaLogs
+ when the observability infra stack is running (`camel infra run
observability`).
+* `otlp` (new) — generic external OTLP export for traces. Replaces `jaeger` as
the
+ recommended value for sending traces to any external OTLP-compatible
collector.
+* `jaeger` (deprecated) — still works as an alias for `otlp`. Use `otlp`
instead.
+* `tui` (unchanged) — embedded receiver in the TUI (default).
+
+The `camel.opentelemetry2.exportTarget` property in `OpenTelemetryTracer` now
accepts any
+non-empty value to signal external export mode (previously only `"jaeger"` was
recognized).
+
=== camel-langchain4j-agent
The `Agent.chat()` method return type has changed from `String` to
`Result<String>` (from `dev.langchain4j.service.Result`).
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
index 8302578f9113..2874c142b913 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
@@ -67,7 +67,7 @@ camel debug [options]
| `--observe` | Enable observability services (health, metrics, dev console,
and lightweight Camel-only tracing in the TUI Spans tab) | false | boolean
| `--open-api` | Adds an OpenAPI spec from the given file (json or yaml file)
| | String
| `--open-telemetry-agent` | Enable OpenTelemetry Java Agent for
auto-instrumentation of third-party libraries (HTTP clients, JDBC, Kafka
clients, gRPC, etc.). Traces are shown in the TUI Spans tab with Camel spans in
cyan and 3rd-party agent spans in magenta. | false | boolean
-| `--open-telemetry-agent-export` | Where to export OpenTelemetry Agent
traces: tui (embedded receiver in TUI) or jaeger (external Jaeger). With
jaeger, start Jaeger first via 'camel infra run jaeger' and view traces at
\http://localhost:16686 | tui | String
+| `--open-telemetry-agent-export` | Where to export OpenTelemetry Agent
telemetry: tui (embedded receiver in TUI), otlp (external OTLP collector for
traces), or observability (full stack with traces and logs). With otlp, start a
trace backend first (e.g. 'camel infra run jaeger'). With observability, start
the stack via 'camel infra run observability'. The value 'jaeger' is deprecated
and works as an alias for 'otlp'. | tui | String
| `--output` | File to store the current message body (will override). This
allows for manual inspecting the message later. | | String
| `--package-scan-jars` | Whether to automatic package scan JARs for custom
Spring or Quarkus beans making them available for Camel CLI | false | boolean
| `--port` | Embeds a local HTTP server on this port (port 8080 by default;
use 0 to dynamic assign a free random port number) | | int
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
index 7e2fa362fbb3..69218ec9f4a0 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
@@ -63,7 +63,7 @@ camel dev [options]
| `--observe` | Enable observability services (health, metrics, dev console,
and lightweight Camel-only tracing in the TUI Spans tab) | false | boolean
| `--open-api` | Adds an OpenAPI spec from the given file (json or yaml file)
| | String
| `--open-telemetry-agent` | Enable OpenTelemetry Java Agent for
auto-instrumentation of third-party libraries (HTTP clients, JDBC, Kafka
clients, gRPC, etc.). Traces are shown in the TUI Spans tab with Camel spans in
cyan and 3rd-party agent spans in magenta. | false | boolean
-| `--open-telemetry-agent-export` | Where to export OpenTelemetry Agent
traces: tui (embedded receiver in TUI) or jaeger (external Jaeger). With
jaeger, start Jaeger first via 'camel infra run jaeger' and view traces at
\http://localhost:16686 | tui | String
+| `--open-telemetry-agent-export` | Where to export OpenTelemetry Agent
telemetry: tui (embedded receiver in TUI), otlp (external OTLP collector for
traces), or observability (full stack with traces and logs). With otlp, start a
trace backend first (e.g. 'camel infra run jaeger'). With observability, start
the stack via 'camel infra run observability'. The value 'jaeger' is deprecated
and works as an alias for 'otlp'. | tui | String
| `--package-scan-jars` | Whether to automatic package scan JARs for custom
Spring or Quarkus beans making them available for Camel CLI | false | boolean
| `--port` | Embeds a local HTTP server on this port (port 8080 by default;
use 0 to dynamic assign a free random port number) | | int
| `--profile` | Profile to run (dev, test, prod). | dev | String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
index 554c5aab82b5..ef505512255b 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
@@ -63,7 +63,7 @@ camel run [options]
| `--observe` | Enable observability services (health, metrics, dev console,
and lightweight Camel-only tracing in the TUI Spans tab) | false | boolean
| `--open-api` | Adds an OpenAPI spec from the given file (json or yaml file)
| | String
| `--open-telemetry-agent` | Enable OpenTelemetry Java Agent for
auto-instrumentation of third-party libraries (HTTP clients, JDBC, Kafka
clients, gRPC, etc.). Traces are shown in the TUI Spans tab with Camel spans in
cyan and 3rd-party agent spans in magenta. | false | boolean
-| `--open-telemetry-agent-export` | Where to export OpenTelemetry Agent
traces: tui (embedded receiver in TUI) or jaeger (external Jaeger). With
jaeger, start Jaeger first via 'camel infra run jaeger' and view traces at
\http://localhost:16686 | tui | String
+| `--open-telemetry-agent-export` | Where to export OpenTelemetry Agent
telemetry: tui (embedded receiver in TUI), otlp (external OTLP collector for
traces), or observability (full stack with traces and logs). With otlp, start a
trace backend first (e.g. 'camel infra run jaeger'). With observability, start
the stack via 'camel infra run observability'. The value 'jaeger' is deprecated
and works as an alias for 'otlp'. | tui | String
| `--package-scan-jars` | Whether to automatic package scan JARs for custom
Spring or Quarkus beans making them available for Camel CLI | false | boolean
| `--port` | Embeds a local HTTP server on this port (port 8080 by default;
use 0 to dynamic assign a free random port number) | | int
| `--profile` | Profile to run (dev, test, prod). | dev | String
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index b19e55604a5c..42de90d02fe7 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -6,9 +6,9 @@
{ "name": "cmd", "fullName": "cmd", "description": "Performs commands in
the running Camel integrations, such as start\/stop route, or change logging
levels.", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"browse", "fullName": "cmd browse", "description": "Browse pending messages on
endpoints [...]
{ "name": "completion", "fullName": "completion", "description": "Generate
completion script for bash\/zsh", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "config", "fullName": "config", "description": "Get and set user
configuration values", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get",
"fullName": "config get", "description": "Display user configuration value",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
- { "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
+ { "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
{ "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
- { "name": "dev", "fullName": "dev", "description": "Run in dev mode with
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, [...]
+ { "name": "dev", "fullName": "dev", "description": "Run in dev mode with
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, [...]
{ "name": "dirty", "fullName": "dirty", "description": "Check if there are
dirty files from previous Camel runs that did not terminate gracefully",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty",
"options": [ { "names": "--clean", "description": "Clean dirty files which are
no longer in use", "defaultValue": "false", "javaType": "boolean", "type":
"boolean" }, { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", " [...]
{ "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the internet [...]
{ "name": "doctor", "fullName": "doctor", "description": "Checks the
environment and reports potential issues", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Doctor", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
@@ -26,7 +26,7 @@
{ "name": "plugin", "fullName": "plugin", "description": "Manage plugins
that add sub-commands to this CLI", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "add",
"fullName": "plugin add", "description": "Add new plugin", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginA [...]
{ "name": "ps", "fullName": "ps", "description": "List running Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.ListProcess", "options": [ {
"names": "--json", "description": "Output in JSON Format", "javaType":
"boolean", "type": "boolean" }, { "names": "--pid", "description": "List only
pid in the output", "javaType": "boolean", "type": "boolean" }, { "names":
"--remote", "description": "Break down counters into remote\/total pairs",
"javaType": [...]
{ "name": "restart", "fullName": "restart", "description": "Restarts
running Camel integrations (stop + re-launch)", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.RestartProcess", "options": [
{ "names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ] },
- { "name": "run", "fullName": "run", "description": "Run as local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, { [...]
+ { "name": "run", "fullName": "run", "description": "Run as local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, { [...]
{ "name": "sbom", "fullName": "sbom", "description": "Generate a CycloneDX
or SPDX SBOM for a specific project", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.SBOMGenerator", "options": [ {
"names": "--build-property", "description": "Maven build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--camel-spring-boot-version", "description": "Camel version to use
with Spring Boot", "javaType": "java.lang.String", "type" [...]
{ "name": "script", "fullName": "script", "description": "Run Camel
integration as shell script for terminal scripting", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Script", "options": [ { "names":
"--logging", "description": "Can be used to turn on logging (logs to file in
<user home>\/.camel directory)", "defaultValue": "false", "javaType":
"boolean", "type": "boolean" }, { "names": "--logging-level", "description":
"Logging level (ERROR, WARN, INFO, DEBUG, TRACE)", "d [...]
{ "name": "shell", "fullName": "shell", "description": "Interactive Camel
CLI shell.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Shell",
"options": [ { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", "type": "boolean" } ] },
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index a28cb1515706..e0e261bfb7f5 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -1223,8 +1223,10 @@ public class Run extends CamelCommand {
}
if (debugOptions.openTelemetryAgent) {
dependencies.add("camel:opentelemetry2");
- boolean jaegerExport =
"jaeger".equals(debugOptions.openTelemetryAgentExport);
- if (!jaegerExport) {
+ boolean externalExport =
"otlp".equals(debugOptions.openTelemetryAgentExport)
+ || "jaeger".equals(debugOptions.openTelemetryAgentExport)
+ ||
"observability".equals(debugOptions.openTelemetryAgentExport);
+ if (!externalExport) {
dependencies.add("camel:platform-http-main");
if (serverOptions.port == -1) {
serverOptions.port = 8080;
@@ -2073,18 +2075,29 @@ public class Run extends CamelCommand {
cmds.removeIf(a -> a.startsWith("--jvm-args"));
}
if (debugOptions.openTelemetryAgent) {
- boolean jaegerExport =
"jaeger".equals(debugOptions.openTelemetryAgentExport);
+ boolean otlpExport =
"otlp".equals(debugOptions.openTelemetryAgentExport)
+ || "jaeger".equals(debugOptions.openTelemetryAgentExport);
+ boolean observabilityExport =
"observability".equals(debugOptions.openTelemetryAgentExport);
jbangArgs.add("--javaagent=io.opentelemetry.javaagent:opentelemetry-javaagent:RELEASE");
jbangArgs.add("-Dotel.metrics.exporter=none");
- jbangArgs.add("-Dotel.logs.exporter=none");
jbangArgs.add("-Dotel.service.name=camel");
cmds.removeIf(arg -> arg.startsWith("--open-telemetry-agent"));
cmds.add("--dep=camel:opentelemetry2");
cmds.add("--prop=camel.opentelemetry2.enabled=true");
- if (jaegerExport) {
+ if (observabilityExport) {
+ jbangArgs.add("-Dotel.exporter.otlp.protocol=http/protobuf");
+ jbangArgs.add(
+
"-Dotel.exporter.otlp.traces.endpoint=http://localhost:10428/insert/opentelemetry/v1/traces");
+ jbangArgs.add("-Dotel.logs.exporter=otlp");
+ jbangArgs.add(
+
"-Dotel.exporter.otlp.logs.endpoint=http://localhost:9428/insert/opentelemetry/v1/logs");
+
cmds.add("--prop=camel.opentelemetry2.exportTarget=observability");
+ } else if (otlpExport) {
+ jbangArgs.add("-Dotel.logs.exporter=none");
jbangArgs.add("-Dotel.exporter.otlp.traces.endpoint=http://localhost:4318/v1/traces");
- cmds.add("--prop=camel.opentelemetry2.exportTarget=jaeger");
+ cmds.add("--prop=camel.opentelemetry2.exportTarget=otlp");
} else {
+ jbangArgs.add("-Dotel.logs.exporter=none");
int port = serverOptions.port > 0 ? serverOptions.port : 8080;
jbangArgs.add("-Dotel.exporter.otlp.traces.endpoint=http://localhost:" + port +
"/v1/traces");
cmds.add("--dep=camel:platform-http-main");
@@ -2821,8 +2834,11 @@ public class Run extends CamelCommand {
boolean openTelemetryAgent;
@Option(names = { "--open-telemetry-agent-export" }, defaultValue =
"tui",
- description = "Where to export OpenTelemetry Agent traces: tui
(embedded receiver in TUI) or jaeger (external Jaeger). "
- + "With jaeger, start Jaeger first via 'camel
infra run jaeger' and view traces at http://localhost:16686")
+ description = "Where to export OpenTelemetry Agent telemetry:
tui (embedded receiver in TUI), "
+ + "otlp (external OTLP collector for traces), or
observability (full stack with traces and logs). "
+ + "With otlp, start a trace backend first (e.g.
'camel infra run jaeger'). "
+ + "With observability, start the stack via
'camel infra run observability'. "
+ + "The value 'jaeger' is deprecated and works as
an alias for 'otlp'.")
String openTelemetryAgentExport = "tui";
}
diff --git a/test-infra/camel-test-infra-all/pom.xml
b/test-infra/camel-test-infra-all/pom.xml
index 0ae3471faa8a..a9674528b4b6 100644
--- a/test-infra/camel-test-infra-all/pom.xml
+++ b/test-infra/camel-test-infra-all/pom.xml
@@ -281,6 +281,11 @@
<artifactId>camel-test-infra-jaeger</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-observability</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-infra-neo4j</artifactId>
@@ -589,6 +594,12 @@
<artifactId>camel-test-infra-jaeger</artifactId>
</dependency>
</fileSet>
+ <fileSet>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+
<artifactId>camel-test-infra-observability</artifactId>
+ </dependency>
+ </fileSet>
<fileSet>
<dependency>
<groupId>org.apache.camel</groupId>
diff --git
a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
index a751e0b1d99e..d0efd2259650 100644
---
a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
+++
b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
@@ -207,6 +207,17 @@
"version" : "4.22.0-SNAPSHOT",
"serviceVersion" : "latest",
"uiSupported" : false
+}, {
+ "service" :
"org.apache.camel.test.infra.observability.services.ObservabilityInfraService",
+ "description" : "Local observability stack (Prometheus + VictoriaTraces +
VictoriaLogs + Perses)",
+ "implementation" :
"org.apache.camel.test.infra.observability.services.ObservabilityLocalContainerInfraService",
+ "alias" : [ "observability" ],
+ "aliasImplementation" : [ ],
+ "groupId" : "org.apache.camel",
+ "artifactId" : "camel-test-infra-observability",
+ "version" : "4.22.0-SNAPSHOT",
+ "serviceVersion" : null,
+ "uiSupported" : true
}, {
"service" : "org.apache.camel.test.infra.solr.services.SolrInfraService",
"description" : "Apache Solr is an open source search platform",
diff --git a/test-infra/camel-test-infra-observability/pom.xml
b/test-infra/camel-test-infra-observability/pom.xml
new file mode 100644
index 000000000000..e80119ceb316
--- /dev/null
+++ b/test-infra/camel-test-infra-observability/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>camel-test-infra-parent</artifactId>
+ <groupId>org.apache.camel</groupId>
+ <relativePath>../camel-test-infra-parent/pom.xml</relativePath>
+ <version>4.22.0-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>camel-test-infra-observability</artifactId>
+ <name>Camel :: Test Infra :: Observability</name>
+
+ <properties>
+ <skipITs.ppc64le>true</skipITs.ppc64le>
+ <skipITs.s390x>true</skipITs.s390x>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git
a/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/common/ObservabilityProperties.java
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/common/ObservabilityProperties.java
new file mode 100644
index 000000000000..029062e4ef84
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/common/ObservabilityProperties.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.observability.common;
+
+public final class ObservabilityProperties {
+ public static final String HOST = "observability.host";
+
+ public static final String PROMETHEUS_CONTAINER =
"observability.prometheus.container";
+ public static final String PROMETHEUS_PORT =
"observability.prometheus.port";
+ public static final int DEFAULT_PROMETHEUS_PORT = 9090;
+
+ public static final String VICTORIA_TRACES_CONTAINER =
"observability.victoriatraces.container";
+ public static final String VICTORIA_TRACES_PORT =
"observability.victoria.traces.port";
+ public static final int DEFAULT_VICTORIA_TRACES_PORT = 10428;
+
+ public static final String VICTORIA_LOGS_CONTAINER =
"observability.victorialogs.container";
+ public static final String VICTORIA_LOGS_PORT =
"observability.victoria.logs.port";
+ public static final int DEFAULT_VICTORIA_LOGS_PORT = 9428;
+
+ public static final String PERSES_CONTAINER =
"observability.perses.container";
+ public static final String PERSES_PORT = "observability.perses.port";
+ public static final int DEFAULT_PERSES_PORT = 3000;
+
+ private ObservabilityProperties() {
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/ObservabilityInfraService.java
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/ObservabilityInfraService.java
new file mode 100644
index 000000000000..5f3532447238
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/ObservabilityInfraService.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.observability.services;
+
+import org.apache.camel.test.infra.common.services.InfrastructureService;
+
+public interface ObservabilityInfraService extends InfrastructureService {
+
+ String host();
+
+ int prometheusPort();
+
+ int victoriaTracesPort();
+
+ int victoriaLogsPort();
+
+ int persesPort();
+
+ default String prometheusUrl() {
+ return String.format("http://%s:%d", host(), prometheusPort());
+ }
+
+ default String victoriaTracesUrl() {
+ return String.format("http://%s:%d/select/vmui", host(),
victoriaTracesPort());
+ }
+
+ default String victoriaTracesOtlpEndpoint() {
+ return String.format("http://%s:%d/insert/opentelemetry/v1/traces",
host(), victoriaTracesPort());
+ }
+
+ default String victoriaLogsUrl() {
+ return String.format("http://%s:%d", host(), victoriaLogsPort());
+ }
+
+ default String victoriaLogsOtlpEndpoint() {
+ return String.format("http://%s:%d/insert/opentelemetry/v1/logs",
host(), victoriaLogsPort());
+ }
+
+ default String persesUrl() {
+ return String.format("http://%s:%d", host(), persesPort());
+ }
+
+ default String metricsTarget() {
+ return "http://host.docker.internal:9876/observe/metrics";
+ }
+
+ default String uiUrl() {
+ return persesUrl();
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/ObservabilityLocalContainerInfraService.java
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/ObservabilityLocalContainerInfraService.java
new file mode 100644
index 000000000000..d5da90cccb1a
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/ObservabilityLocalContainerInfraService.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.observability.services;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.spi.annotations.InfraService;
+import org.apache.camel.test.infra.common.services.ContainerEnvironmentUtil;
+import org.apache.camel.test.infra.common.services.ContainerService;
+import
org.apache.camel.test.infra.observability.common.ObservabilityProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.Network;
+
+@InfraService(service = ObservabilityInfraService.class,
+ description = "Local observability stack (Prometheus +
VictoriaTraces + VictoriaLogs + Perses)",
+ serviceAlias = { "observability" },
+ uiSupported = true)
+public class ObservabilityLocalContainerInfraService
+ implements ObservabilityInfraService,
ContainerService<PrometheusContainer> {
+ private static final Logger LOG =
LoggerFactory.getLogger(ObservabilityLocalContainerInfraService.class);
+
+ private final Network network;
+ private final PrometheusContainer prometheusContainer;
+ private final VictoriaTracesContainer victoriaTracesContainer;
+ private final VictoriaLogsContainer victoriaLogsContainer;
+ private final PersesContainer persesContainer;
+
+ public ObservabilityLocalContainerInfraService() {
+ network = Network.newNetwork();
+
+ prometheusContainer = new PrometheusContainer();
+ prometheusContainer.withNetwork(network);
+
+ victoriaTracesContainer = new VictoriaTracesContainer();
+ victoriaTracesContainer.withNetwork(network);
+
+ victoriaLogsContainer = new VictoriaLogsContainer();
+ victoriaLogsContainer.withNetwork(network);
+
+ persesContainer = new PersesContainer();
+ persesContainer.withNetwork(network);
+
+ initContainers();
+ }
+
+ private void initContainers() {
+ String name = ContainerEnvironmentUtil.containerName(this.getClass());
+ if (name != null) {
+ prometheusContainer.withCreateContainerCmdModifier(cmd ->
cmd.withName(name + "-prometheus"));
+ victoriaTracesContainer.withCreateContainerCmdModifier(cmd ->
cmd.withName(name + "-victoriatraces"));
+ victoriaLogsContainer.withCreateContainerCmdModifier(cmd ->
cmd.withName(name + "-victorialogs"));
+ persesContainer.withCreateContainerCmdModifier(cmd ->
cmd.withName(name + "-perses"));
+ }
+
+ boolean fixedPort =
ContainerEnvironmentUtil.isFixedPort(this.getClass());
+ if (fixedPort) {
+ ContainerEnvironmentUtil.configurePorts(prometheusContainer, true,
+
ContainerEnvironmentUtil.PortConfig.primary(ObservabilityProperties.DEFAULT_PROMETHEUS_PORT));
+
+ ContainerEnvironmentUtil.configurePorts(victoriaTracesContainer,
true,
+
ContainerEnvironmentUtil.PortConfig.primary(ObservabilityProperties.DEFAULT_VICTORIA_TRACES_PORT));
+
+ ContainerEnvironmentUtil.configurePorts(victoriaLogsContainer,
true,
+
ContainerEnvironmentUtil.PortConfig.primary(ObservabilityProperties.DEFAULT_VICTORIA_LOGS_PORT));
+
+ ContainerEnvironmentUtil.configurePorts(persesContainer, true,
+
ContainerEnvironmentUtil.PortConfig.primary(ObservabilityProperties.DEFAULT_PERSES_PORT));
+ }
+ }
+
+ @Override
+ public void registerProperties() {
+ System.setProperty(ObservabilityProperties.HOST, host());
+ System.setProperty(ObservabilityProperties.PROMETHEUS_PORT,
String.valueOf(prometheusPort()));
+ System.setProperty(ObservabilityProperties.VICTORIA_TRACES_PORT,
String.valueOf(victoriaTracesPort()));
+ System.setProperty(ObservabilityProperties.VICTORIA_LOGS_PORT,
String.valueOf(victoriaLogsPort()));
+ System.setProperty(ObservabilityProperties.PERSES_PORT,
String.valueOf(persesPort()));
+ }
+
+ @Override
+ public void initialize() {
+ LOG.info("Starting Prometheus");
+ prometheusContainer.start();
+
+ LOG.info("Starting VictoriaTraces");
+ victoriaTracesContainer.start();
+
+ LOG.info("Starting VictoriaLogs");
+ victoriaLogsContainer.start();
+
+ LOG.info("Starting Perses");
+ persesContainer.start();
+
+ registerProperties();
+ configurePerses();
+
+ LOG.info("Observability stack running:");
+ LOG.info(" Prometheus: {}", prometheusUrl());
+ LOG.info(" VictoriaTraces: {} (OTLP: {})", victoriaTracesUrl(),
victoriaTracesOtlpEndpoint());
+ LOG.info(" VictoriaLogs: {} (OTLP: {})", victoriaLogsUrl(),
victoriaLogsOtlpEndpoint());
+ LOG.info(" Perses: {}", persesUrl());
+ LOG.info(" Metrics target: {}", metricsTarget());
+ }
+
+ @Override
+ public void shutdown() {
+ LOG.info("Stopping observability stack");
+ safeStop(persesContainer::stop);
+ safeStop(victoriaLogsContainer::stop);
+ safeStop(victoriaTracesContainer::stop);
+ safeStop(prometheusContainer::stop);
+ safeStop(network::close);
+ }
+
+ private void safeStop(Runnable action) {
+ try {
+ action.run();
+ } catch (Exception e) {
+ LOG.warn("Shutdown step failed: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public PrometheusContainer getContainer() {
+ return prometheusContainer;
+ }
+
+ @Override
+ public String host() {
+ return prometheusContainer.getHost();
+ }
+
+ @Override
+ public int prometheusPort() {
+ return
prometheusContainer.getMappedPort(ObservabilityProperties.DEFAULT_PROMETHEUS_PORT);
+ }
+
+ @Override
+ public int victoriaTracesPort() {
+ return
victoriaTracesContainer.getMappedPort(ObservabilityProperties.DEFAULT_VICTORIA_TRACES_PORT);
+ }
+
+ @Override
+ public int victoriaLogsPort() {
+ return
victoriaLogsContainer.getMappedPort(ObservabilityProperties.DEFAULT_VICTORIA_LOGS_PORT);
+ }
+
+ @Override
+ public int persesPort() {
+ return
persesContainer.getMappedPort(ObservabilityProperties.DEFAULT_PERSES_PORT);
+ }
+
+ private void configurePerses() {
+ HttpClient client = HttpClient.newHttpClient();
+ persesPost(client, "/api/v1/globaldatasources",
loadResource("perses-datasource.json"));
+ persesPost(client, "/api/v1/projects",
loadResource("perses-project.json"));
+ persesPost(client, "/api/v1/projects/camel/dashboards",
loadResource("perses-dashboard.json"));
+ LOG.info("Perses configured with Camel project and dashboard");
+ }
+
+ private void persesPost(HttpClient client, String path, String body) {
+ try {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(persesUrl() + path))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body))
+ .build();
+ HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() < 200 || response.statusCode() >= 300) {
+ LOG.warn("Perses POST {} failed: {} {}", path,
response.statusCode(), response.body());
+ }
+ } catch (Exception e) {
+ LOG.warn("Perses POST {} failed: {}", path, e.getMessage());
+ }
+ }
+
+ private String loadResource(String name) {
+ try (InputStream is = getClass().getResourceAsStream(name)) {
+ if (is != null) {
+ return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ } catch (IOException e) {
+ LOG.warn("Failed to load resource {}: {}", name, e.getMessage());
+ }
+ return "{}";
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/PersesContainer.java
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/PersesContainer.java
new file mode 100644
index 000000000000..3b86a8c3f33e
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/PersesContainer.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.observability.services;
+
+import org.apache.camel.test.infra.common.LocalPropertyResolver;
+import
org.apache.camel.test.infra.observability.common.ObservabilityProperties;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+public class PersesContainer extends GenericContainer<PersesContainer> {
+ public static final String CONTAINER_NAME = "perses";
+
+ public PersesContainer() {
+ super(LocalPropertyResolver.getProperty(
+ ObservabilityLocalContainerInfraService.class,
ObservabilityProperties.PERSES_CONTAINER));
+
+ this.withNetworkAliases(CONTAINER_NAME)
+ .withExposedPorts(ObservabilityProperties.DEFAULT_PERSES_PORT)
+ .withCommand("-web.listen-address=:" +
ObservabilityProperties.DEFAULT_PERSES_PORT)
+
.waitingFor(Wait.forHttp("/api/v1/health").forPort(ObservabilityProperties.DEFAULT_PERSES_PORT));
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/PrometheusContainer.java
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/PrometheusContainer.java
new file mode 100644
index 000000000000..f2f441ed6b74
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/PrometheusContainer.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.observability.services;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.test.infra.common.LocalPropertyResolver;
+import
org.apache.camel.test.infra.observability.common.ObservabilityProperties;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.builder.Transferable;
+
+public class PrometheusContainer extends GenericContainer<PrometheusContainer>
{
+ public static final String CONTAINER_NAME = "prometheus";
+
+ public PrometheusContainer() {
+ super(LocalPropertyResolver.getProperty(
+ ObservabilityLocalContainerInfraService.class,
ObservabilityProperties.PROMETHEUS_CONTAINER));
+
+ this.withNetworkAliases(CONTAINER_NAME)
+
.withExposedPorts(ObservabilityProperties.DEFAULT_PROMETHEUS_PORT)
+ .withExtraHost("host.docker.internal", "host-gateway")
+
.waitingFor(Wait.forHttp("/-/healthy").forPort(ObservabilityProperties.DEFAULT_PROMETHEUS_PORT));
+
+ String config = loadPrometheusConfig();
+
this.withCopyToContainer(Transferable.of(config.getBytes(StandardCharsets.UTF_8)),
+ "/etc/prometheus/prometheus.yml");
+ }
+
+ private String loadPrometheusConfig() {
+ try (InputStream is =
getClass().getResourceAsStream("prometheus.yml")) {
+ if (is != null) {
+ return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ } catch (IOException e) {
+ // fall through to default
+ }
+ return "global:\n scrape_interval: 15s\n\nscrape_configs:\n"
+ + " - job_name: 'camel'\n metrics_path:
'/observe/metrics'\n"
+ + " static_configs:\n - targets:
['host.docker.internal:9876']\n";
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/VictoriaLogsContainer.java
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/VictoriaLogsContainer.java
new file mode 100644
index 000000000000..d4700750502c
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/VictoriaLogsContainer.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.observability.services;
+
+import org.apache.camel.test.infra.common.LocalPropertyResolver;
+import
org.apache.camel.test.infra.observability.common.ObservabilityProperties;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+public class VictoriaLogsContainer extends
GenericContainer<VictoriaLogsContainer> {
+ public static final String CONTAINER_NAME = "victorialogs";
+
+ public VictoriaLogsContainer() {
+ super(LocalPropertyResolver.getProperty(
+ ObservabilityLocalContainerInfraService.class,
ObservabilityProperties.VICTORIA_LOGS_CONTAINER));
+
+ this.withNetworkAliases(CONTAINER_NAME)
+
.withExposedPorts(ObservabilityProperties.DEFAULT_VICTORIA_LOGS_PORT)
+
.waitingFor(Wait.forHttp("/health").forPort(ObservabilityProperties.DEFAULT_VICTORIA_LOGS_PORT));
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/VictoriaTracesContainer.java
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/VictoriaTracesContainer.java
new file mode 100644
index 000000000000..7153cb76e36a
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/java/org/apache/camel/test/infra/observability/services/VictoriaTracesContainer.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.observability.services;
+
+import org.apache.camel.test.infra.common.LocalPropertyResolver;
+import
org.apache.camel.test.infra.observability.common.ObservabilityProperties;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+public class VictoriaTracesContainer extends
GenericContainer<VictoriaTracesContainer> {
+ public static final String CONTAINER_NAME = "victoriatraces";
+
+ public VictoriaTracesContainer() {
+ super(LocalPropertyResolver.getProperty(
+ ObservabilityLocalContainerInfraService.class,
ObservabilityProperties.VICTORIA_TRACES_CONTAINER));
+
+ this.withNetworkAliases(CONTAINER_NAME)
+
.withExposedPorts(ObservabilityProperties.DEFAULT_VICTORIA_TRACES_PORT)
+ .waitingFor(Wait.forHttp("/select/vmui")
+
.forPort(ObservabilityProperties.DEFAULT_VICTORIA_TRACES_PORT));
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/container.properties
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/container.properties
new file mode 100644
index 000000000000..508dc55b20de
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/container.properties
@@ -0,0 +1,27 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements. See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+observability.prometheus.container=quay.io/prometheus/prometheus:v3.4.1
+observability.prometheus.container.version.freeze.major=true
+
+observability.victoriatraces.container=mirror.gcr.io/victoriametrics/victoria-traces:v0.9.4
+observability.victoriatraces.container.version.freeze.major=true
+
+observability.victorialogs.container=mirror.gcr.io/victoriametrics/victoria-logs:v1.51.0
+observability.victorialogs.container.version.freeze.major=true
+
+observability.perses.container=mirror.gcr.io/persesdev/perses:v0.53.1
+observability.perses.container.version.freeze.major=true
diff --git
a/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-dashboard.json
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-dashboard.json
new file mode 100644
index 000000000000..aad38d987e18
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-dashboard.json
@@ -0,0 +1,607 @@
+{
+ "kind": "Dashboard",
+ "metadata": {
+ "name": "overview"
+ },
+ "spec": {
+ "display": {
+ "name": "Camel Overview"
+ },
+ "duration": "30m",
+ "layouts": [
+ {
+ "kind": "Grid",
+ "spec": {
+ "display": {
+ "title": "Summary",
+ "collapse": {
+ "open": true
+ }
+ },
+ "items": [
+ {
+ "x": 0, "y": 0, "width": 4, "height": 4,
+ "content": { "$ref": "#/spec/panels/uptime" }
+ },
+ {
+ "x": 4, "y": 0, "width": 4, "height": 4,
+ "content": { "$ref": "#/spec/panels/runningRoutes" }
+ },
+ {
+ "x": 8, "y": 0, "width": 4, "height": 4,
+ "content": { "$ref": "#/spec/panels/totalExchanges" }
+ },
+ {
+ "x": 12, "y": 0, "width": 4, "height": 4,
+ "content": { "$ref": "#/spec/panels/failedExchanges" }
+ },
+ {
+ "x": 16, "y": 0, "width": 4, "height": 4,
+ "content": { "$ref": "#/spec/panels/inflightExchanges" }
+ },
+ {
+ "x": 20, "y": 0, "width": 4, "height": 4,
+ "content": { "$ref": "#/spec/panels/cpuUsage" }
+ }
+ ]
+ }
+ },
+ {
+ "kind": "Grid",
+ "spec": {
+ "display": {
+ "title": "Exchanges",
+ "collapse": {
+ "open": true
+ }
+ },
+ "items": [
+ {
+ "x": 0, "y": 0, "width": 8, "height": 8,
+ "content": { "$ref": "#/spec/panels/exchangesRate" }
+ },
+ {
+ "x": 8, "y": 0, "width": 8, "height": 8,
+ "content": { "$ref": "#/spec/panels/exchangesTotal" }
+ },
+ {
+ "x": 16, "y": 0, "width": 8, "height": 8,
+ "content": { "$ref": "#/spec/panels/exchangesFailed" }
+ }
+ ]
+ }
+ },
+ {
+ "kind": "Grid",
+ "spec": {
+ "display": {
+ "title": "Performance",
+ "collapse": {
+ "open": true
+ }
+ },
+ "items": [
+ {
+ "x": 0, "y": 0, "width": 12, "height": 8,
+ "content": { "$ref": "#/spec/panels/processingTime" }
+ },
+ {
+ "x": 12, "y": 0, "width": 12, "height": 8,
+ "content": { "$ref": "#/spec/panels/processingTimeMax" }
+ }
+ ]
+ }
+ },
+ {
+ "kind": "Grid",
+ "spec": {
+ "display": {
+ "title": "JVM",
+ "collapse": {
+ "open": true
+ }
+ },
+ "items": [
+ {
+ "x": 0, "y": 0, "width": 8, "height": 8,
+ "content": { "$ref": "#/spec/panels/jvmMemory" }
+ },
+ {
+ "x": 8, "y": 0, "width": 8, "height": 8,
+ "content": { "$ref": "#/spec/panels/jvmThreads" }
+ },
+ {
+ "x": 16, "y": 0, "width": 8, "height": 8,
+ "content": { "$ref": "#/spec/panels/gcPauses" }
+ }
+ ]
+ }
+ },
+ {
+ "kind": "Grid",
+ "spec": {
+ "display": {
+ "title": "System",
+ "collapse": {
+ "open": false
+ }
+ },
+ "items": [
+ {
+ "x": 0, "y": 0, "width": 12, "height": 8,
+ "content": { "$ref": "#/spec/panels/cpuTimeline" }
+ },
+ {
+ "x": 12, "y": 0, "width": 12, "height": 8,
+ "content": { "$ref": "#/spec/panels/loadAverage" }
+ }
+ ]
+ }
+ }
+ ],
+ "panels": {
+ "uptime": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Uptime" },
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "format": { "unit": "seconds" },
+ "sparkline": {}
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": { "query": "process_uptime_seconds" }
+ }
+ }
+ }]
+ }
+ },
+ "runningRoutes": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Running Routes" },
+ "plugin": {
+ "kind": "StatChart",
+ "spec": { "calculation": "last-number", "format": { "unit":
"decimal" } }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": { "query": "camel_routes_running_routes" }
+ }
+ }
+ }]
+ }
+ },
+ "totalExchanges": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Total Exchanges" },
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "format": { "unit": "decimal", "shortValues": true },
+ "sparkline": {}
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": { "query":
"camel_exchanges_total{eventType=\"context\"}" }
+ }
+ }
+ }]
+ }
+ },
+ "failedExchanges": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Failed Exchanges" },
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "format": { "unit": "decimal" },
+ "sparkline": {}
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": { "query":
"camel_exchanges_failed_total{eventType=\"context\"}" }
+ }
+ }
+ }]
+ }
+ },
+ "inflightExchanges": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Inflight" },
+ "plugin": {
+ "kind": "StatChart",
+ "spec": { "calculation": "last-number", "format": { "unit":
"decimal" } }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": { "query":
"sum(camel_exchanges_inflight{eventType=\"route\"})" }
+ }
+ }
+ }]
+ }
+ },
+ "cpuUsage": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "CPU Usage" },
+ "plugin": {
+ "kind": "GaugeChart",
+ "spec": {
+ "calculation": "last-number",
+ "format": { "unit": "percent-decimal" },
+ "thresholds": { "steps": [{ "value": 0.7 }, { "value": 0.9 }] }
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": { "query": "process_cpu_usage" }
+ }
+ }
+ }]
+ }
+ },
+ "exchangesRate": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Throughput (exchanges/s)" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "decimal", "decimalPlaces": 2 } }
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query":
"rate(camel_exchanges_total{eventType=\"route\"}[1m])",
+ "seriesNameFormat": "{{routeId}}"
+ }
+ }
+ }
+ }]
+ }
+ },
+ "exchangesTotal": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Exchanges by Route" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "decimal", "shortValues": true } }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query":
"camel_exchanges_succeeded_total{eventType=\"route\"}",
+ "seriesNameFormat": "{{routeId}} (ok)"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query":
"camel_exchanges_failed_total{eventType=\"route\"}",
+ "seriesNameFormat": "{{routeId}} (failed)"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "exchangesFailed": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Failure Rate" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "percent-decimal",
"decimalPlaces": 1 } }
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query":
"rate(camel_exchanges_failed_total{eventType=\"route\"}[1m]) /
(rate(camel_exchanges_total{eventType=\"route\"}[1m]) > 0)",
+ "seriesNameFormat": "{{routeId}}"
+ }
+ }
+ }
+ }]
+ }
+ },
+ "processingTime": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Processing Time (mean)" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "seconds", "decimalPlaces": 3 },
"label": "seconds" }
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query":
"rate(camel_route_policy_seconds_sum{eventType=\"route\"}[1m]) /
rate(camel_route_policy_seconds_count{eventType=\"route\"}[1m])",
+ "seriesNameFormat": "{{routeId}}"
+ }
+ }
+ }
+ }]
+ }
+ },
+ "processingTimeMax": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "Processing Time (max)" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "seconds", "decimalPlaces": 3 },
"label": "seconds" }
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query":
"camel_route_policy_seconds_max{eventType=\"route\"}",
+ "seriesNameFormat": "{{routeId}}"
+ }
+ }
+ }
+ }]
+ }
+ },
+ "jvmMemory": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "JVM Heap Memory" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "bytes", "shortValues": true } }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "jvm_memory_used_bytes{area=\"heap\"}",
+ "seriesNameFormat": "Used ({{id}})"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "jvm_memory_max_bytes{area=\"heap\"}",
+ "seriesNameFormat": "Max ({{id}})"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "jvmThreads": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "JVM Threads" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "decimal" } }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "jvm_threads_live_threads",
+ "seriesNameFormat": "Live"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "jvm_threads_daemon_threads",
+ "seriesNameFormat": "Daemon"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "jvm_threads_peak_threads",
+ "seriesNameFormat": "Peak"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "gcPauses": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "GC Pauses" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "seconds", "decimalPlaces": 3 } }
+ }
+ },
+ "queries": [{
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "rate(jvm_gc_pause_seconds_sum[1m])",
+ "seriesNameFormat": "GC pause time/s"
+ }
+ }
+ }
+ }]
+ }
+ },
+ "cpuTimeline": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "CPU Usage" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "percent-decimal" } }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "process_cpu_usage",
+ "seriesNameFormat": "Process CPU"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "system_cpu_usage",
+ "seriesNameFormat": "System CPU"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "loadAverage": {
+ "kind": "Panel",
+ "spec": {
+ "display": { "name": "System Load Average (1m)" },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": { "position": "bottom" },
+ "yAxis": { "format": { "unit": "decimal", "decimalPlaces": 2 } }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "system_load_average_1m",
+ "seriesNameFormat": "Load avg"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "system_cpu_count",
+ "seriesNameFormat": "CPU cores"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-datasource.json
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-datasource.json
new file mode 100644
index 000000000000..143af20f8ffd
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-datasource.json
@@ -0,0 +1,20 @@
+{
+ "kind": "GlobalDatasource",
+ "metadata": {
+ "name": "prometheus"
+ },
+ "spec": {
+ "default": true,
+ "plugin": {
+ "kind": "PrometheusDatasource",
+ "spec": {
+ "proxy": {
+ "kind": "HTTPProxy",
+ "spec": {
+ "url": "http://prometheus:9090"
+ }
+ }
+ }
+ }
+ }
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-project.json
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-project.json
new file mode 100644
index 000000000000..e5f94a71771a
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/perses-project.json
@@ -0,0 +1,7 @@
+{
+ "kind": "Project",
+ "metadata": {
+ "name": "camel"
+ },
+ "spec": {}
+}
diff --git
a/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/prometheus.yml
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/prometheus.yml
new file mode 100644
index 000000000000..f98ea631df2a
--- /dev/null
+++
b/test-infra/camel-test-infra-observability/src/main/resources/org/apache/camel/test/infra/observability/services/prometheus.yml
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+global:
+ scrape_interval: 15s
+
+scrape_configs:
+ - job_name: 'camel'
+ metrics_path: '/observe/metrics'
+ static_configs:
+ - targets:
+ - 'host.docker.internal:9876'
+ - 'host.docker.internal:9877'
+ - 'host.docker.internal:9878'
+ - 'host.docker.internal:9879'
diff --git a/test-infra/pom.xml b/test-infra/pom.xml
index 83a7548d728b..77052dee985e 100644
--- a/test-infra/pom.xml
+++ b/test-infra/pom.xml
@@ -100,6 +100,7 @@
<module>camel-test-infra-docling</module>
<module>camel-test-infra-iggy</module>
<module>camel-test-infra-jaeger</module>
+ <module>camel-test-infra-observability</module>
<module>camel-test-infra-mcp-everything</module>
<module>camel-test-infra-all</module>
</modules>