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>

Reply via email to