diff --git a/docs/guide/reporting/real-time/index.md b/docs/guide/reporting/real-time/index.md
index a0a955a0..7c0e2289 100644
--- a/docs/guide/reporting/real-time/index.md
+++ b/docs/guide/reporting/real-time/index.md
@@ -5,4 +5,5 @@ When running tests with JMeter (and in particular with jmeter-java-dsl) a usual
+
diff --git a/docs/guide/reporting/real-time/prometheus.md b/docs/guide/reporting/real-time/prometheus.md
new file mode 100644
index 00000000..d1ab6fa2
--- /dev/null
+++ b/docs/guide/reporting/real-time/prometheus.md
@@ -0,0 +1,86 @@
+#### Prometheus
+
+As in previous scenarios, you can also use Prometheus and Grafana.
+
+To use the module, you will need to include the following dependency in your project:
+
+:::: code-group
+::: code-group-item Maven
+```xml
+
+ us.abstracta.jmeter
+ jmeter-java-dsl-prometheus
+ 1.27
+ test
+
+```
+:::
+::: code-group-item Gradle
+```groovy
+testImplementation 'us.abstracta.jmeter:jmeter-java-dsl-prometheus:1.27'
+```
+:::
+::::
+
+And use provided `prometheusListener()` method like in this example:
+
+```java
+import static org.assertj.core.api.Assertions.assertThat;
+import static us.abstracta.jmeter.javadsl.JmeterDsl.*;
+import static us.abstracta.jmeter.javadsl.prometheus.DslPrometheusListener.*;
+
+import java.io.IOException;
+import java.time.Duration;
+import org.junit.jupiter.api.Test;
+import us.abstracta.jmeter.javadsl.core.TestPlanStats;
+
+public class PerformanceTest {
+
+ @Test
+ public void testPerformance() throws IOException {
+ TestPlanStats stats = testPlan(
+ threadGroup(2, 10,
+ httpSampler("http://my.service")
+ ),
+ prometheusListener()
+ ).run();
+ assertThat(stats.overall().sampleTimePercentile99()).isLessThan(Duration.ofSeconds(5));
+ }
+
+}
+```
+
+As in previous cases, you can to try it locally by running `docker-compose up` inside [this directory](/docs/guide/reporting/real-time/prometheus). After containers are started, you can follow the same steps as in previous scenarios.
+
+::: warning
+Use the provided `docker-compose` settings for local tests only. It uses weak credentials and is not properly configured for production purposes.
+:::
+
+Check [DslPrometheusListener](/jmeter-java-dsl-prometheus/src/main/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListener.java) for details on listener settings.
+
+Here is an example that shows the default settings used by `prometheusListener`:
+
+```java
+import us.abstracta.jmeter.javadsl.prometheus.DslPrometheusListener.PrometheusMetric;
+...
+prometheusListener()
+ .metrics(
+ PrometheusMetric.responseTime("ResponseTime", "the response time of samplers")
+ .labels(PrometheusMetric.SAMPLE_LABEL, PrometheusMetric.RESPONSE_CODE)
+ .quantile(0.75, 0.5)
+ .quantile(0.95, 0.1)
+ .quantile(0.99, 0.01)
+ .maxAge(Duration.ofMinutes(1)),
+ PrometheusMetric.successRatio("Ratio", "the success ratio of samplers")
+ .labels(PrometheusMetric.SAMPLE_LABEL, PrometheusMetric.RESPONSE_CODE)
+ )
+ .port(9270)
+ .host("0.0.0.0")
+ .endWait(Duration.ofSeconds(10))
+...
+```
+> Note that the default settings are different from the used JMeter Prometheus Plugin, to allow easier usage and avoid missing metrics at the end of test plan execution.
+
+::: tip
+When configuring the `prometheusListener` always consider setting a `endWait` that is greater thant the Prometheus Server configured `scrape_interval` to avoid missing metrics at the end of test plan execution (e.g.: 2x the scrape interval value).
+:::
diff --git a/docs/guide/reporting/real-time/prometheus/docker-compose.yml b/docs/guide/reporting/real-time/prometheus/docker-compose.yml
new file mode 100644
index 00000000..f9d88699
--- /dev/null
+++ b/docs/guide/reporting/real-time/prometheus/docker-compose.yml
@@ -0,0 +1,24 @@
+services:
+ prometheus:
+ image: prom/prometheus:v2.51.2
+ ports:
+ - '9090:9090'
+ volumes:
+ - prometheus-data:/prometheus
+ - ./prometheus.yml:/etc/prometheus/prometheus.yml
+ grafana:
+ image: grafana/grafana:10.4.2
+ ports:
+ - '3000:3000'
+ volumes:
+ - grafana-data:/var/lib/grafana
+ - ./grafana-provisioning/:/etc/grafana/provisioning
+ depends_on:
+ - prometheus
+ environment:
+ - GF_SECURITY_ADMIN_USER=admin
+ - GF_SECURITY_ADMIN_PASSWORD=1234
+ - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards/jmeter.json
+volumes:
+ prometheus-data:
+ grafana-data:
diff --git a/docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/dashboard.yml b/docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/dashboard.yml
new file mode 100644
index 00000000..b43b50f8
--- /dev/null
+++ b/docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/dashboard.yml
@@ -0,0 +1,6 @@
+apiVersion: 1
+providers:
+ - name: JMeter Dashboards
+ allowUiUpdates: true
+ options:
+ path: /etc/grafana/provisioning/dashboards
\ No newline at end of file
diff --git a/docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/jmeter.json b/docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/jmeter.json
new file mode 100644
index 00000000..ceba14a0
--- /dev/null
+++ b/docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/jmeter.json
@@ -0,0 +1,2806 @@
+{
+ "annotations": {
+ "list": []
+ },
+ "description": "A grafana dashboard to inspect jmeter metrics via prometheus exporter",
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "gnetId": 13098,
+ "graphTooltip": 1,
+ "id": 2,
+ "links": [],
+ "panels": [
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "semi-dark-blue",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 2,
+ "w": 6,
+ "x": 0,
+ "y": 0
+ },
+ "id": 12,
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(jmeter_threads{state=\"active\"})",
+ "format": "time_series",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Threads",
+ "refId": "A",
+ "step": 4
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 1,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "semi-dark-purple",
+ "value": null
+ }
+ ]
+ },
+ "unit": "ops"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 2,
+ "w": 6,
+ "x": 6,
+ "y": 0
+ },
+ "id": 7,
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(irate(Ratio_success[$interval]))",
+ "format": "time_series",
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Throughput",
+ "refId": "E"
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 1,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "yellow",
+ "value": null
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 2,
+ "w": 6,
+ "x": 12,
+ "y": 0
+ },
+ "id": 14,
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "avg((rate(ResponseTime_sum{code=~\"2..\"}[$interval])/(rate(ResponseTime_count{code=~\"2..\"}[$interval])>0)))",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Response time",
+ "metric": "",
+ "range": true,
+ "refId": "A",
+ "step": 4
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 1,
+ "mappings": [
+ {
+ "options": {
+ "match": "null",
+ "result": {
+ "text": "N/A"
+ }
+ },
+ "type": "special"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "#37872D",
+ "value": null
+ },
+ {
+ "color": "semi-dark-red",
+ "value": 0.001
+ }
+ ]
+ },
+ "unit": "percentunit"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 2,
+ "w": 6,
+ "x": 18,
+ "y": 0
+ },
+ "id": 8,
+ "maxDataPoints": 100,
+ "options": {
+ "colorMode": "background",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "text": {},
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "(avg(rate(Ratio_failure[$interval]))/avg(rate(Ratio_total[$interval])))",
+ "format": "time_series",
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Failure",
+ "refId": "A",
+ "step": 4
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 2
+ },
+ "id": 17,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Summary",
+ "type": "row"
+ },
+ {
+ "datasource": null,
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "ops",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 3,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Request Fail/sec"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Request OK/sec"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Virtual Users"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-blue",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "custom.fillOpacity",
+ "value": 40
+ },
+ {
+ "id": "custom.drawStyle",
+ "value": "line"
+ },
+ {
+ "id": "custom.lineWidth",
+ "value": 3
+ },
+ {
+ "id": "unit",
+ "value": "short"
+ },
+ {
+ "id": "custom.axisLabel",
+ "value": "VU"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 12,
+ "x": 0,
+ "y": 3
+ },
+ "id": 36,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum(jmeter_threads{state=\"active\"})",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Virtual Users",
+ "refId": "B",
+ "step": 1
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(Ratio_success[$interval]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Request OK/sec",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(Ratio_failure[$interval])) ",
+ "legendFormat": "Request Fail/sec",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Virtual Users vs OK/Fail",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "ops",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 100,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "stepAfter",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Fail"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-red",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "custom.fillOpacity",
+ "value": 40
+ },
+ {
+ "id": "unit",
+ "value": "percentunit"
+ },
+ {
+ "id": "custom.lineInterpolation",
+ "value": "linear"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "OK"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-green",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "custom.fillOpacity",
+ "value": 40
+ },
+ {
+ "id": "unit",
+ "value": "percentunit"
+ },
+ {
+ "id": "custom.lineInterpolation",
+ "value": "linear"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Total/sec"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-purple",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "custom.fillOpacity",
+ "value": 0
+ },
+ {
+ "id": "custom.lineWidth",
+ "value": 3
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 12,
+ "x": 12,
+ "y": 3
+ },
+ "id": 3,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "(sum(rate(Ratio_success[$interval]))/sum(rate(Ratio_total[$interval])))",
+ "format": "time_series",
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "OK",
+ "range": true,
+ "refId": "A",
+ "step": 2
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "(sum(rate(Ratio_failure[$interval]))/sum(rate(Ratio_total[$interval])))",
+ "format": "time_series",
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Fail",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(Ratio_total[$interval]))",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "Total/sec",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Total Requests vs OK/Fail",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 3,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Avg Response Time"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Virtual Users"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-blue",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "custom.fillOpacity",
+ "value": 40
+ },
+ {
+ "id": "unit",
+ "value": "short"
+ },
+ {
+ "id": "custom.axisLabel",
+ "value": "VU"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 12,
+ "x": 0,
+ "y": 13
+ },
+ "id": 30,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "avg((rate(ResponseTime_sum{code=~\"2..\"}[$interval])/(rate(ResponseTime_count{code=~\"2..\"}[$interval])>0)))",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Avg Response Time",
+ "range": true,
+ "refId": "B",
+ "step": 1
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum(jmeter_threads{state=\"active\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Virtual Users",
+ "refId": "A"
+ }
+ ],
+ "title": "Avg. Response Time vs Virtual Users",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 3,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "OK"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-green",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "unit",
+ "value": "short"
+ },
+ {
+ "id": "custom.axisLabel",
+ "value": "ops"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Avg Response Time"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-yellow",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "custom.fillOpacity",
+ "value": 40
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Fail"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-red",
+ "mode": "fixed"
+ }
+ },
+ {
+ "id": "unit",
+ "value": "short"
+ },
+ {
+ "id": "custom.axisLabel",
+ "value": "ops"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 12,
+ "x": 12,
+ "y": 13
+ },
+ "id": 29,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "avg((rate(ResponseTime_sum{code=~\"2..\"}[$interval])/(rate(ResponseTime_count{code=~\"2..\"}[$interval])>0)))",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Avg Response Time",
+ "refId": "B",
+ "step": 1
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(Ratio_success[$interval]))",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "OK",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(Ratio_failure[$interval])) ",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "Fail",
+ "range": true,
+ "refId": "C"
+ }
+ ],
+ "title": "Avg. Response Time vs OK/Fail",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "cellOptions": {
+ "type": "auto"
+ },
+ "inspect": false
+ },
+ "decimals": 2,
+ "displayName": "",
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Time"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Time"
+ },
+ {
+ "id": "custom.align"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "label"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Label"
+ },
+ {
+ "id": "unit",
+ "value": "short"
+ },
+ {
+ "id": "decimals",
+ "value": 2
+ },
+ {
+ "id": "custom.align"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Value"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Count"
+ },
+ {
+ "id": "unit",
+ "value": "locale"
+ },
+ {
+ "id": "custom.align"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 23
+ },
+ "id": 33,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "showHeader": true,
+ "sortBy": []
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum(Ratio_success) by (label) ",
+ "format": "table",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "OK",
+ "transformations": [
+ {
+ "id": "merge",
+ "options": {
+ "reducers": []
+ }
+ }
+ ],
+ "type": "table"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "cellOptions": {
+ "type": "auto"
+ },
+ "inspect": false
+ },
+ "decimals": 2,
+ "displayName": "",
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Time"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Time"
+ },
+ {
+ "id": "custom.align"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "label"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Label"
+ },
+ {
+ "id": "unit",
+ "value": "short"
+ },
+ {
+ "id": "decimals",
+ "value": 2
+ },
+ {
+ "id": "custom.align"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Value"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Count"
+ },
+ {
+ "id": "unit",
+ "value": "none"
+ },
+ {
+ "id": "custom.align"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "rgba(245, 54, 54, 0.9)",
+ "value": null
+ },
+ {
+ "color": "rgba(237, 129, 40, 0.89)",
+ "value": null
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 23
+ },
+ "id": 34,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "showHeader": true
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum(Ratio_failure) by (label) ",
+ "format": "table",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "Fails",
+ "transformations": [
+ {
+ "id": "merge",
+ "options": {
+ "reducers": []
+ }
+ }
+ ],
+ "type": "table"
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 31
+ },
+ "id": 18,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Response times",
+ "type": "row"
+ },
+ {
+ "datasource": null,
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 3,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 11,
+ "w": 24,
+ "x": 0,
+ "y": 32
+ },
+ "id": 35,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "lastNotNull",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": " avg (rate(ResponseTime_sum[$interval]) / (rate(ResponseTime_count[$interval])>0)) by (label)",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{label}} ",
+ "refId": "A",
+ "step": 1
+ }
+ ],
+ "title": "Avg. response time (with fails)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 3,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byValue",
+ "options": {
+ "op": "gte",
+ "reducer": "allIsZero",
+ "value": 0
+ }
+ },
+ "properties": [
+ {
+ "id": "custom.hideFrom",
+ "value": {
+ "legend": true,
+ "tooltip": true,
+ "viz": false
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byValue",
+ "options": {
+ "op": "gte",
+ "reducer": "allIsNull",
+ "value": 0
+ }
+ },
+ "properties": [
+ {
+ "id": "custom.hideFrom",
+ "value": {
+ "legend": true,
+ "tooltip": true,
+ "viz": false
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 24,
+ "x": 0,
+ "y": 43
+ },
+ "id": 13,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "lastNotNull",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "avg(ResponseTime{code=~\"2..\"}) by (quantile)",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{quantile}}",
+ "range": true,
+ "refId": "B",
+ "step": 1
+ }
+ ],
+ "title": "Pct response times",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 53
+ },
+ "id": 19,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Requests per second",
+ "type": "row"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "bars",
+ "fillOpacity": 100,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 3,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Fail"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "OK"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 24,
+ "x": 0,
+ "y": 54
+ },
+ "id": 4,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "repeatDirection": "h",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum(rate(Ratio_success[$interval]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "OK",
+ "metric": "",
+ "refId": "A",
+ "step": 2
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(Ratio_failure[$interval]))",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "Fail",
+ "refId": "B",
+ "step": 2
+ }
+ ],
+ "title": "Total",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "bars",
+ "fillOpacity": 100,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 24,
+ "x": 0,
+ "y": 64
+ },
+ "id": 5,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum(rate(Ratio_total[$interval])) by (label)",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "{{label}}",
+ "refId": "A",
+ "step": 2
+ }
+ ],
+ "title": "by label",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "bars",
+ "fillOpacity": 100,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byRegexp",
+ "options": "HTTP 4|5.."
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-red",
+ "mode": "shades",
+ "seriesBy": "last"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byRegexp",
+ "options": "HTTP 1|2|3.."
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-green",
+ "mode": "shades"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 24,
+ "x": 0,
+ "y": 74
+ },
+ "id": 6,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum by (code) (rate(Ratio_total[$interval]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "HTTP {{code}}",
+ "refId": "A",
+ "step": 2
+ }
+ ],
+ "title": "by HTTP code",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 84
+ },
+ "id": 20,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Failures",
+ "type": "row"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "bars",
+ "fillOpacity": 100,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byRegexp",
+ "options": "HTTP 4|5.."
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byRegexp",
+ "options": "HTTP 1|2|3.."
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "semi-dark-green",
+ "mode": "shades"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 24,
+ "x": 0,
+ "y": 85
+ },
+ "id": 15,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum by(code) (rate(Ratio_failure[$interval]))",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP {{code}}",
+ "refId": "A",
+ "step": 2
+ }
+ ],
+ "title": "by HTTP code",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "bars",
+ "fillOpacity": 100,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 24,
+ "x": 0,
+ "y": 95
+ },
+ "id": 16,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "max",
+ "min"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "sum by (label) (rate(Ratio_failure[$interval]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{label}}",
+ "refId": "B",
+ "step": 2
+ }
+ ],
+ "title": "by label",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 105
+ },
+ "id": 22,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Test farm",
+ "type": "row"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "percentunit"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 106
+ },
+ "id": 24,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "rate(jvm_gc_collection_seconds_sum{job=~\"jmeter\"}[1m])",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}} - {{gc}} ",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "rate(jvm_gc_collection_seconds_sum{job=~\"jmeter\"}[$interval]) / ignoring(gc) group_left rate(process_cpu_seconds_total{job=~\"jmeter\"}[$interval])",
+ "format": "time_series",
+ "hide": true,
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}} - {{gc}} v2",
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "rate(jvm_gc_collection_seconds_sum{job=~\"jmeter\"}[1m]) / rate(jvm_gc_collection_seconds_count{job=~\"jmeter\"}[1m])",
+ "hide": true,
+ "legendFormat": "{{instance}} - {{gc}} v3",
+ "refId": "C"
+ }
+ ],
+ "title": "GC % time",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 114
+ },
+ "id": 27,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "lastNotNull"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "(jvm_memory_pool_bytes_committed {job=~\"jmeter\"}) - (jvm_memory_pool_bytes_used{job=~\"jmeter\"}) ",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}} - {{pool}}",
+ "refId": "A"
+ }
+ ],
+ "title": "JVM heap - free memory by pool",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 122
+ },
+ "id": 26,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "lastNotNull"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "(jvm_memory_pool_bytes_used{job=\"jmeter\"}) ",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}} - {{pool}}",
+ "refId": "A"
+ }
+ ],
+ "title": "JVM heap - used memory by pool",
+ "type": "timeseries"
+ },
+ {
+ "datasource": null,
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 130
+ },
+ "id": 25,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "lastNotNull"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.4.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "fdkx30k4vru2ob"
+ },
+ "expr": "(jvm_memory_bytes_max {job=\"jmeter\"}) - (jvm_memory_bytes_used{job=\"jmeter\"})",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}} - {{area}}",
+ "refId": "A"
+ }
+ ],
+ "title": "JVM free memory",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "5s",
+ "schemaVersion": 39,
+ "tags": [
+ "jmeter"
+ ],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": true,
+ "text": "30s",
+ "value": "30s"
+ },
+ "hide": 0,
+ "includeAll": false,
+ "label": "interval",
+ "multi": false,
+ "name": "interval",
+ "options": [
+ {
+ "selected": true,
+ "text": "30s",
+ "value": "30s"
+ },
+ {
+ "selected": false,
+ "text": "1m",
+ "value": "1m"
+ },
+ {
+ "selected": false,
+ "text": "5m",
+ "value": "5m"
+ },
+ {
+ "selected": false,
+ "text": "10m",
+ "value": "10m"
+ }
+ ],
+ "query": "30s,1m,5m,10m",
+ "queryValue": "",
+ "skipUrlSync": false,
+ "type": "custom"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-5m",
+ "to": "now"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "browser",
+ "title": "JMeter Prometheus",
+ "uid": "999999",
+ "version": 30,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/docs/guide/reporting/real-time/prometheus/grafana-provisioning/datasources/datasource.yml b/docs/guide/reporting/real-time/prometheus/grafana-provisioning/datasources/datasource.yml
new file mode 100644
index 00000000..8caba48e
--- /dev/null
+++ b/docs/guide/reporting/real-time/prometheus/grafana-provisioning/datasources/datasource.yml
@@ -0,0 +1,12 @@
+apiVersion: 1
+datasources:
+ - name: Prometheus
+ type: prometheus
+ access: proxy
+ url: http://prometheus:9090
+ isDefault: true
+ editable: true
+ jsonData:
+ httpMethod: "POST"
+ queryTimeout: "10s"
+ timeInterval: "5s"
diff --git a/docs/guide/reporting/real-time/prometheus/prometheus.yml b/docs/guide/reporting/real-time/prometheus/prometheus.yml
new file mode 100644
index 00000000..e4abfade
--- /dev/null
+++ b/docs/guide/reporting/real-time/prometheus/prometheus.yml
@@ -0,0 +1,10 @@
+global:
+ scrape_interval: 5s
+ evaluation_interval: 5s
+scrape_configs:
+ - job_name: "prometheus"
+ static_configs:
+ - targets: ["localhost:9090"]
+ - job_name: "jmeter"
+ static_configs:
+ - targets: ["host.docker.internal:9270"]
\ No newline at end of file
diff --git a/jmeter-java-dsl-prometheus/pom.xml b/jmeter-java-dsl-prometheus/pom.xml
new file mode 100644
index 00000000..0094d802
--- /dev/null
+++ b/jmeter-java-dsl-prometheus/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+ us.abstracta.jmeter
+ jmeter-java-dsl-parent
+ 1.28-SNAPSHOT
+ ../pom.xml
+
+ jmeter-java-dsl-prometheus
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+
+
+ us.abstracta.jmeter
+ jmeter-java-dsl
+ ${project.version}
+
+
+ com.github.johrstrom
+ jmeter-prometheus-plugin
+ 0.7.1
+
+
+
diff --git a/jmeter-java-dsl-prometheus/src/main/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListener.java b/jmeter-java-dsl-prometheus/src/main/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListener.java
new file mode 100644
index 00000000..68c949c4
--- /dev/null
+++ b/jmeter-java-dsl-prometheus/src/main/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListener.java
@@ -0,0 +1,348 @@
+package us.abstracta.jmeter.javadsl.prometheus;
+
+import com.github.johrstrom.collector.BaseCollectorConfig.JMeterCollectorType;
+import com.github.johrstrom.listener.ListenerCollectorConfig;
+import com.github.johrstrom.listener.ListenerCollectorConfig.Measurable;
+import com.github.johrstrom.listener.PrometheusListener;
+import com.github.johrstrom.listener.PrometheusServer;
+import com.github.johrstrom.listener.gui.PrometheusListenerGui;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.jmeter.testelement.TestElement;
+import org.apache.jmeter.util.JMeterUtils;
+import us.abstracta.jmeter.javadsl.core.listeners.BaseListener;
+
+/**
+ * Test element which publishes test run metrics in Prometheus endpoint.
+ *
+ * This element uses jmeter-prometheus-plugin.
+ *
+ * @since 1.28
+ */
+public class DslPrometheusListener extends BaseListener {
+
+ private final List> metrics = new ArrayList<>();
+ private int port = 9270;
+ private String host = "0.0.0.0";
+ private Duration delay = Duration.ofSeconds(10);
+
+ public DslPrometheusListener() {
+ super("Prometheus Listener", PrometheusListenerGui.class);
+ }
+
+ /**
+ * Creates a new Prometheus Listener publishing test run metrics in a Prometheus endpoint.
+ *
+ * By default, this element publishes a default set of metrics in endpoint
+ * http://0.0.0.0:9270/metrics. Use {@link #host(String)} and {@link #port(int)} to change the
+ * endpoint, and {@link #metrics(PrometheusMetric...)} to set your own metrics.
+ *
+ * The default published metrics are:
+ *
{@code
+ * PrometheusMetric.responseTime("ResponseTime", "the response time of samplers")
+ * .labels(PrometheusMetric.SAMPLE_LABEL, PrometheusMetric.RESPONSE_CODE)
+ * .quantile(0.75, 0.5)
+ * .quantile(0.95, 0.1)
+ * .quantile(0.99, 0.01)
+ * .maxAge(Duration.ofMinutes(1)),
+ * PrometheusMetric.successRatio("Ratio", "the success ratio of samplers")
+ * .labels(PrometheusMetric.SAMPLE_LABEL, PrometheusMetric.RESPONSE_CODE)
+ * }
+ *
+ * @return the listener instance for further configuration or usage.
+ */
+ public static DslPrometheusListener prometheusListener() {
+ return new DslPrometheusListener();
+ }
+
+ /**
+ * Specifies a custom set of metrics to publish.
+ *
+ * @param metrics specifies the set of metrics to publish.
+ * @return the listener instance for further configuration or usage.
+ * @see PrometheusMetric
+ */
+ public DslPrometheusListener metrics(PrometheusMetric>... metrics) {
+ this.metrics.addAll(Arrays.asList(metrics));
+ return this;
+ }
+
+ /**
+ * Specifies the port where to publish the metrics.
+ *
+ * Warning: port setting is test plan wide. This means that if you set different values in
+ * different prometheus listeners, then all the metrics will be published in the last set port.
+ *
+ * @param port specifies the port to publish the metrics. By default, it is 9270.
+ * @return the listener instance for further configuration or usage.
+ */
+ public DslPrometheusListener port(int port) {
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * Specifies the host the internal prometheus server whill listen to requests for metrics.
+ *
+ * Warning: host setting is test plan wide. This means that if you set different values in
+ * different prometheus listeners, then all the metrics will be published in the last set host.
+ *
+ * @param host specifies the host to publish the metrics. By default, it is 0.0.0.0.
+ * @return the listener instance for further configuration or usage.
+ */
+ public DslPrometheusListener host(String host) {
+ this.host = host;
+ return this;
+ }
+
+ /**
+ * Specifies a duration to wait after the test run ends, before stop publishing the metrics.
+ *
+ * Warning: It is very important to set this value greater than Prometheus server
+ * scrape_interval to avoid missing metrics at the end of test execution.
+ *
+ * Warning: this setting is test plan wide. This means that if you set different values in
+ * different prometheus listeners, then only the last set value will be used.
+ *
+ * @param duration specifies the duration to wait before stop publishing the metrics. Take into
+ * consideration that the JMeter plugin supports seconds granularity, so, if a
+ * finer value is used, only the provided seconds will be used. By default, it is
+ * set to 10 seconds.
+ * @return the listener instance for further configuration or usage.
+ */
+ public DslPrometheusListener endWait(Duration duration) {
+ this.delay = duration;
+ return this;
+ }
+
+ @Override
+ protected TestElement buildTestElement() {
+ if (metrics.isEmpty()) {
+ metrics(PrometheusMetric.responseTime("ResponseTime", "the response time of samplers")
+ .labels(PrometheusMetric.SAMPLE_LABEL, PrometheusMetric.RESPONSE_CODE)
+ .quantile(0.75, 0.5)
+ .quantile(0.95, 0.1)
+ .quantile(0.99, 0.01)
+ .maxAge(Duration.ofMinutes(1)),
+ PrometheusMetric.successRatio("Ratio", "the success ratio of samplers")
+ .labels(PrometheusMetric.SAMPLE_LABEL, PrometheusMetric.RESPONSE_CODE));
+ }
+ JMeterUtils.setProperty(PrometheusServer.PROMETHEUS_PORT, String.valueOf(port));
+ JMeterUtils.setProperty(PrometheusServer.PROMETHEUS_IP, host);
+ JMeterUtils.setProperty(PrometheusServer.PROMETHEUS_DELAY, String.valueOf(delay.getSeconds()));
+ PrometheusListener ret = new PrometheusListener();
+ ret.setCollectorConfigs(metrics.stream()
+ .map(PrometheusMetric::buildCollectorConfig)
+ .collect(Collectors.toList()));
+ return ret;
+ }
+
+ /**
+ * Allows configuring metrics to be published by the listener.
+ */
+ public abstract static class PrometheusMetric> {
+
+ public static final String SAMPLE_LABEL = "label";
+ public static final String RESPONSE_CODE = "code";
+
+ protected final String name;
+ protected final String help;
+ protected final Measurable measuring;
+ protected final List labels = new ArrayList<>();
+
+ protected PrometheusMetric(String name, String help, Measurable measuring) {
+ this.name = name;
+ this.help = help;
+ this.measuring = measuring;
+ }
+
+ /**
+ * Configures a metric collect information about the samples response time.
+ *
+ * By default, Prometheus Summary
+ * metrics are collected. Use {@link #labels(String...)},
+ * {@link SummaryPrometheusMetric#quantile(double, double)} or
+ * {@link SummaryPrometheusMetric#quantile(double, double)}{@link
+ * SummaryPrometheusMetric#histogram(double...)} to further configure details on response time
+ * collected metrics.
+ *
+ * @param name specifies the name of the metric.
+ * @return the metric instance for further configuration or usage.
+ */
+ public static SummaryPrometheusMetric responseTime(String name) {
+ return PrometheusMetric.responseTime(name, "");
+ }
+
+ /**
+ * Same as {@link #responseTime(String)} but allows to set a help text.
+ *
+ * @see #responseTime(String)
+ */
+ public static SummaryPrometheusMetric responseTime(String name, String help) {
+ return new SummaryPrometheusMetric(name, help, Measurable.ResponseTime);
+ }
+
+ /**
+ * Configures a metric collect information about samples success and failures.
+ *
+ * Review the JMeter
+ * plugin documentation for more details.
+ *
+ * @param name specifies the name of the metric.
+ * @return the metric instance for further configuration or usage.
+ */
+ public static SuccessRatioPrometheusMetric successRatio(String name) {
+ return PrometheusMetric.successRatio(name, "");
+ }
+
+ /**
+ * Same as {@link #successRatio(String)} but allows to set a help text.
+ *
+ * @see #successRatio(String)
+ */
+ public static SuccessRatioPrometheusMetric successRatio(String name, String help) {
+ return new SuccessRatioPrometheusMetric(name, help, Measurable.SuccessRatio);
+ }
+
+ /**
+ * Add labels that enrich the collected metric data.
+ *
+ * You can use pre-defined values {@link PrometheusMetric#SAMPLE_LABEL} and
+ * {@link PrometheusMetric#RESPONSE_CODE} to get labels for each sample label and response
+ * code.
+ *
+ * Additionally, you can use JMeter variables values as labels. Check this
+ * section of JMeter plugin documentation for more details.
+ *
+ * @param labels specifies the labels to add to the metric.
+ * @return the metric instance for further configuration or usage.
+ */
+ public T labels(String... labels) {
+ this.labels.addAll(Arrays.asList(labels));
+ return (T) this;
+ }
+
+ public ListenerCollectorConfig buildCollectorConfig() {
+ ListenerCollectorConfig ret = new ListenerCollectorConfig();
+ ret.setMetricName(name);
+ ret.setHelp(help);
+ ret.setLabels(labels.toArray(new String[0]));
+ configureCollectorConfig(ret);
+ ret.setListenTo(ListenerCollectorConfig.SAMPLES);
+ ret.setMeasuring(measuring.toString());
+ return ret;
+ }
+
+ protected abstract void configureCollectorConfig(ListenerCollectorConfig config);
+
+ }
+
+ public static class SummaryPrometheusMetric extends PrometheusMetric {
+
+ protected String quantiles = "";
+ protected Duration maxAge = Duration.ofMinutes(1);
+
+ protected SummaryPrometheusMetric(String name, String help, Measurable measuring) {
+ super(name, help, measuring);
+ }
+
+ /**
+ * Specifies to collect histogram data for the given metric.
+ *
+ * Check Prometheus Documentation
+ * for an explanation on differences between using histograms and summaries.
+ *
+ * @param buckets specifies the bucket upper bounds where information is collected. This
+ * determines the granularity of the generated histogram.
+ * @return the metric instance for further configuration or usage.
+ */
+ public HistogramPrometheusMetric histogram(double... buckets) {
+ return new HistogramPrometheusMetric(this.name, this.help, this.measuring, buckets)
+ .labels(this.labels.toArray(new String[0]));
+ }
+
+ /**
+ * Adds a quantile value to calculate for the summary metric.
+ *
+ * @param quantile specifies the quantile value to calculate. Eg: 0.95 for the 95th percentile.
+ * This must be a value between 0 and 1.
+ * @param error specifies the tolerance error for the calculated quantile. Smaller values
+ * require more resources (CPU and RAM). This must be a value between 0 and 1.
+ * Consider using 0.001 for quantiles greater than 0.99 or smaller than 0.01,
+ * and 0.01 or bigger for the rest.
+ * @return the metric instance for further configuration or usage.
+ */
+ public SummaryPrometheusMetric quantile(double quantile, double error) {
+ quantiles += "|" + String.format("%f,%f", quantile, error);
+ return this;
+ }
+
+ /**
+ * Sets the size of the moving time window to calculate the quantiles.
+ *
+ * This value depends on the period of time you want the quantiles calculation to be based on. A
+ * bigger value requires more resources (CPU & RAM).
+ *
+ * @param duration specifies the size of the moving time window. Prometheus supports up to
+ * seconds granularity for the time window, so if you use a more granular value
+ * (like millis) on ly the seconds will be taken into consideration. By default,
+ * this value is set to 1 minute.
+ * @return the metric instance for further configuration or usage.
+ */
+ public SummaryPrometheusMetric maxAge(Duration duration) {
+ maxAge = duration;
+ return this;
+ }
+
+ @Override
+ protected void configureCollectorConfig(ListenerCollectorConfig config) {
+ config.setType(JMeterCollectorType.SUMMARY.toString());
+ quantiles = !quantiles.isEmpty() ? quantiles.substring(1) : "";
+ quantiles += ";" + maxAge.getSeconds();
+ config.setQuantileOrBucket(quantiles);
+ }
+
+ }
+
+ public static class HistogramPrometheusMetric extends
+ PrometheusMetric {
+
+ protected final String buckets;
+
+ protected HistogramPrometheusMetric(String name, String help, Measurable measuring,
+ double... buckets) {
+ super(name, help, measuring);
+ this.buckets = Arrays.stream(buckets)
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining(","));
+ }
+
+ @Override
+ protected void configureCollectorConfig(ListenerCollectorConfig config) {
+ config.setType(JMeterCollectorType.HISTOGRAM.toString());
+ config.setQuantileOrBucket(buckets);
+ }
+
+ }
+
+ public static class SuccessRatioPrometheusMetric extends
+ PrometheusMetric {
+
+ protected SuccessRatioPrometheusMetric(String name, String help, Measurable measuring) {
+ super(name, help, measuring);
+ }
+
+ @Override
+ protected void configureCollectorConfig(ListenerCollectorConfig config) {
+ config.setType(JMeterCollectorType.SUCCESS_RATIO.toString());
+ }
+
+ }
+
+}
diff --git a/jmeter-java-dsl-prometheus/src/test/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListenerTest.java b/jmeter-java-dsl-prometheus/src/test/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListenerTest.java
new file mode 100644
index 00000000..be66ae6c
--- /dev/null
+++ b/jmeter-java-dsl-prometheus/src/test/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListenerTest.java
@@ -0,0 +1,110 @@
+package us.abstracta.jmeter.javadsl.prometheus;
+
+import static us.abstracta.jmeter.javadsl.JmeterDsl.dummySampler;
+import static us.abstracta.jmeter.javadsl.JmeterDsl.testPlan;
+import static us.abstracta.jmeter.javadsl.JmeterDsl.threadGroup;
+import static us.abstracta.jmeter.javadsl.prometheus.DslPrometheusListener.prometheusListener;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DslPrometheusListenerTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DslPrometheusListenerTest.class);
+
+ @Test
+ public void shouldPublishDefaultMetricsWhenPrometheusListenerWithoutConfig() throws Exception {
+ MetricsConditionChecker collector = new MetricsConditionChecker();
+ runConcurrently(
+ testPlanRunner(),
+ collector
+ );
+ }
+
+ @SafeVarargs
+ private final void runConcurrently(Callable... callables) throws Exception {
+ ExecutorService executor = Executors.newFixedThreadPool(callables.length);
+ try {
+ List> rets = executor.invokeAll(Arrays.asList(callables));
+ for (Future ret : rets) {
+ ret.get();
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ private Callable testPlanRunner() {
+ return () -> {
+ testPlan(
+ threadGroup(1, 1,
+ dummySampler("OK")
+ ),
+ prometheusListener()
+ ).run();
+ return null;
+ };
+ }
+
+ private static class MetricsConditionChecker implements Callable {
+
+ private static final Duration POLL_PERIOD = Duration.ofSeconds(1);
+ private static final Duration POLL_TIMEOUT = Duration.ofSeconds(15);
+
+ @Override
+ public Void call() throws InterruptedException, TimeoutException {
+ Instant start = Instant.now();
+ do {
+ Thread.sleep(POLL_PERIOD.toMillis());
+ try {
+ String response = urlQuery("http://localhost:9270/metrics");
+ if (response.matches(".*Ratio_success_total\\{[^}]+} 1\\.0.*")) {
+ return null;
+ }
+ } catch (IOException | IllegalStateException e) {
+ LOG.warn("Failed to poll metrics", e);
+ }
+ } while (Instant.now().isBefore(start.plus(POLL_TIMEOUT)));
+ throw new TimeoutException(
+ "Could not collect the expected metrics within the timeout of " + POLL_TIMEOUT
+ + " seconds");
+ }
+
+ private String urlQuery(String url) throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ try {
+ int responseCode = connection.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(connection.getInputStream()));
+ String inputLine;
+ StringBuilder response = new StringBuilder();
+ while ((inputLine = in.readLine()) != null) {
+ response.append(inputLine);
+ }
+ in.close();
+ return response.toString();
+ } else {
+ throw new IllegalStateException("HTTP error code : " + responseCode);
+ }
+ } finally {
+ connection.disconnect();
+ }
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index c3631f41..580c7034 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,6 +78,7 @@
jmeter-java-dsl-azure
jmeter-java-dsl-datadog
jmeter-java-dsl-bridge
+ jmeter-java-dsl-prometheus