From d639a06bea8cbadd0fe6d7c83be50109e5bc1aec Mon Sep 17 00:00:00 2001 From: Roger Abelenda Date: Tue, 14 May 2024 16:50:22 -0300 Subject: [PATCH] Add support for prometheus listener --- docs/guide/reporting/real-time/index.md | 1 + docs/guide/reporting/real-time/prometheus.md | 86 + .../real-time/prometheus/docker-compose.yml | 24 + .../dashboards/dashboard.yml | 6 + .../dashboards/jmeter.json | 2806 +++++++++++++++++ .../datasources/datasource.yml | 12 + .../real-time/prometheus/prometheus.yml | 10 + jmeter-java-dsl-prometheus/pom.xml | 33 + .../prometheus/DslPrometheusListener.java | 348 ++ .../prometheus/DslPrometheusListenerTest.java | 110 + pom.xml | 1 + 11 files changed, 3437 insertions(+) create mode 100644 docs/guide/reporting/real-time/prometheus.md create mode 100644 docs/guide/reporting/real-time/prometheus/docker-compose.yml create mode 100644 docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/dashboard.yml create mode 100644 docs/guide/reporting/real-time/prometheus/grafana-provisioning/dashboards/jmeter.json create mode 100644 docs/guide/reporting/real-time/prometheus/grafana-provisioning/datasources/datasource.yml create mode 100644 docs/guide/reporting/real-time/prometheus/prometheus.yml create mode 100644 jmeter-java-dsl-prometheus/pom.xml create mode 100644 jmeter-java-dsl-prometheus/src/main/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListener.java create mode 100644 jmeter-java-dsl-prometheus/src/test/java/us/abstracta/jmeter/javadsl/prometheus/DslPrometheusListenerTest.java 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