Operations 15 min read

Visualizing JMeter Test Results in Real‑Time with Grafana, InfluxDB & Prometheus

This tutorial walks through the end‑to‑end monitoring pipeline—from JMeter’s Backend Listener sending metrics to InfluxDB, through storing and querying those metrics, to configuring Grafana dashboards for real‑time performance visualization, and also covers OS monitoring with node_exporter, Prometheus, and Grafana.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Visualizing JMeter Test Results in Real‑Time with Grafana, InfluxDB & Prometheus

Overview

The article organizes common monitoring components (OS, application server, middleware, queue, cache, database, network, frontend, load balancer, web server, storage, code) and focuses on the most frequently used ones, explaining the logical flow and concrete implementation.

JMeter + InfluxDB + Grafana Data Flow

Instead of relying on JMeter’s console, HTML reports, or plugins—which are time‑consuming, impractical for high concurrency, and memory‑intensive—the article proposes using JMeter’s Backend Listener to stream metrics asynchronously to InfluxDB (or Graphite). This enables real‑time visualization in Grafana that matches console data.

Backend Listener Configuration

Configure the InfluxDB Backend Listener by setting the URL and application (scene name). The core code that adds metrics is shown below:

private void addMetrics(String transaction, SamplerMetric metric) {
    // FOR ALL STATUS
    addMetric(transaction, metric.getTotal(), metric.getSentBytes(), metric.getReceivedBytes(), TAG_ALL, metric.getAllMean(), metric.getAllMinTime(),
            metric.getAllMaxTime(), allPercentiles.values(), metric::getAllPercentile);
    // FOR OK STATUS
    addMetric(transaction, metric.getSuccesses(), null, null, TAG_OK, metric.getOkMean(), metric.getOkMinTime(),
            metric.getOkMaxTime(), okPercentiles.values(), metric::getOkPercentile);
    // FOR KO STATUS
    addMetric(transaction, metric.getFailures(), null, null, TAG_KO, metric.getKoMean(), metric.getKoMinTime(),
            metric.getKoMaxTime(), koPercentiles.values(), metric::getKoPercentile);
    metric.getErrors().forEach((error, count) -> addErrorMetric(transaction, error.getResponseCode(),
            error.getResponseMessage(), count));
}

The sending logic is also provided:

@Override
public void writeAndSendMetrics() {
    if (!copyMetrics.isEmpty()) {
        try {
            if (httpRequest == null) {
                httpRequest = createRequest(url);
            }
            StringBuilder sb = new StringBuilder(copyMetrics.size() * 35);
            for (MetricTuple metric : copyMetrics) {
                sb.append(metric.measurement)
                  .append(metric.tag)
                  .append(" ")
                  .append(metric.field)
                  .append(" ")
                  .append(metric.timestamp + "000000")
                  .append("
");
            }
            StringEntity entity = new StringEntity(sb.toString(), StandardCharsets.UTF_8);
            httpRequest.setEntity(entity);
            lastRequest = httpClient.execute(httpRequest, new FutureCallback<HttpResponse>() {
                @Override
                public void completed(final HttpResponse response) {
                    int code = response.getStatusLine().getStatusCode();
                    if (MetricUtils.isSuccessCode(code)) {
                        if (log.isDebugEnabled()) {
                            log.debug("Success, number of metrics written: {}", copyMetrics.size());
                        }
                    } else {
                        log.error("Error writing metrics to influxDB Url: {}, responseCode: {}, responseBody: {}", url, code, getBody(response));
                    }
                }
                @Override
                public void failed(final Exception ex) {
                    log.error("failed to send data to influxDB server : {}", ex.getMessage());
                }
                @Override
                public void cancelled() {
                    log.warn("Request to influxDB server was cancelled");
                }
            });
        } catch (Exception e) {
            log.error("Exception while sending metrics", e);
        }
    }
}

InfluxDB Storage Structure

Two measurements are created: events (stores test lifecycle events) and jmeter (stores per‑transaction statistics). Sample queries illustrate how to retrieve raw events and aggregated metrics.

> show databases
name: databases
_name_
----
_internal
jmeter
> use jmeter
Using database jmeter
> show measurements
name: measurements
_name_
----
events
jmeter
> select * from jmeter where application='7ddemo' limit 10
name: jmeter
time               application  avg   count  ...
----               -----------  ---   -----  ...
1575255462821000000 7ddemo      0     0      ...

Grafana Configuration

Add an InfluxDB data source (URL, database, user, password) and import the official JMeter dashboard (ID 5496). After saving, the dashboard displays real‑time TPS, response time percentiles, and per‑transaction metrics.

Running a simple JMeter scenario (10 threads, 10 iterations, 2 HTTP requests) generates 200 requests; the Grafana panels correctly reflect the total TPS and per‑transaction statistics.

node_exporter + Prometheus + Grafana

The article introduces node_exporter as the standard exporter for OS metrics, lists supported operating systems, and shows how to start it (e.g., ./node_exporter --web.listen-address=:9200 &).

Prometheus is downloaded, extracted, and configured to scrape node_exporter. A sample Prometheus configuration file is referenced.

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets: ['localhost:9200']

In Grafana, the official node_exporter dashboard (ID 11074) is imported. The CPU usage panel uses the following Prometheus query:

avg(irate(node_cpu_seconds_total{instance=~"$node",mode="system"}[30m])) by (instance)
avg(irate(node_cpu_seconds_total{instance=~"$node",mode="user"}[30m])) by (instance)
avg(irate(node_cpu_seconds_total{instance=~"$node",mode="iowait"}[30m])) by (instance)
1 - avg(irate(node_cpu_seconds_total{instance=~"$node",mode="idle"}[30m])) by (instance)

The article compares node_exporter metrics with traditional top output, emphasizing that both derive values from /proc counters.

Conclusion

Understanding the source and meaning of monitoring data—whether collected via JMeter → InfluxDB → Grafana or node_exporter → Prometheus → Grafana—is essential for accurate performance analysis and for making informed decisions during testing.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Performance MonitoringPrometheusJMeterInfluxDBGrafananode_exporter
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.