Skip to content

Conversation

@itsh3sam
Copy link

@itsh3sam itsh3sam commented Jan 8, 2026

The current Prometheus output in stats_over_http dumps flattened metric names (e.g., proxy.process.http.200_responses), which makes it difficult to aggregate data in Grafana or Prometheus. This adds a new "v2" output format that parses these names into labels like status="200" or method="GET".

The new format can be accessed via /_stats/prometheus_v2 or by passing an Accept header with version=2.0.0.

To keep this efficient, the parsing uses swoc::TextView for a zero- allocation approach. It recognizes common ATS patterns such as status codes, HTTP methods, cache results, and thread/volume indices. I've also added deduplication for the HELP and TYPE metadata lines, so that metrics sharing the same base name (but different labels) are grouped correctly in the output.

Includes compile-time unit tests (static_assert) to verify the parsing logic and updated AuTests for the new endpoint.

Related issue: #12778

Copilot AI review requested due to automatic review settings January 8, 2026 16:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a new Prometheus v2 output format for the stats_over_http plugin that transforms flat ATS metric names into labeled metrics for better aggregation in Prometheus/Grafana. The new format can be accessed via /_stats/prometheus_v2 endpoint or by passing Accept: text/plain; version=2.0.0 header.

Key changes:

  • Implements metric name parsing logic to extract labels (status codes, HTTP methods, cache results, volume/thread indices, time buckets) from flat metric names
  • Adds deduplication of HELP/TYPE metadata lines for metrics sharing the same base name
  • Includes compile-time unit tests and comprehensive AuTests for validation

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
plugins/stats_over_http/stats_over_http.cc Core implementation: adds parse_metric_v2 function, prometheus_v2_out_stat callback, new output format enum, HTTP headers, routing logic, and compile-time tests
tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py Adds test cases for Prometheus v2 format via both endpoint path and Accept header, with assertions validating label extraction
tests/gold_tests/pluginTest/stats_over_http/gold/stats_over_http_prometheus_v2_stderr.gold Gold file for expected stderr output when accessing /_stats/prometheus_v2 endpoint
tests/gold_tests/pluginTest/stats_over_http/gold/stats_over_http_prometheus_v2_accept_stderr.gold Gold file for expected stderr output when using Accept header for Prometheus v2
doc/admin-guide/plugins/stats_over_http.en.rst Documentation updates explaining the new v2 format, endpoints, and content-type headers

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

The current Prometheus output in stats_over_http dumps flattened metric
names (e.g., proxy.process.http.200_responses), which makes it difficult
to aggregate data in Grafana or Prometheus. This adds a new "v2" output
format that parses these names into proper labels like
status="200" or method="GET".

The new format can be accessed via /_stats/prometheus_v2 or by passing
an Accept header with version=2.0.0.

To keep this efficient, the parsing uses swoc::TextView for a zero-
allocation approach. It recognizes common ATS patterns such as status
codes, HTTP methods, cache results, and thread/volume indices. I've
also added deduplication for the HELP and TYPE metadata lines, so that
metrics sharing the same base name (but different labels) are grouped
correctly in the output.

Includes compile-time unit tests (static_assert) to verify the parsing
logic and updated AuTests for the new endpoint.
@bneradt bneradt added this to the 10.2.0 milestone Jan 8, 2026
@bneradt bneradt added the stats_over_http stats_over_http label Jan 8, 2026
Comment on lines +109 to +112
p.Streams.stdout += Testers.ContainsExpression(
'proxy_process_http_requests{method="completed"}',
"Verify that method labels are extracted correctly.",
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad you added this test.

method="completed"

completed isn't a method, POST, GET, PUT, etc., are methods. It seems like having completed would be misleading for users viewing this data.

@bneradt
Copy link
Contributor

bneradt commented Jan 8, 2026

In case it's helpful, here's the fedora build failure:

FAILED: plugins/stats_over_http/CMakeFiles/stats_over_http.dir/stats_over_http.cc.o 
/usr/sbin/ccache /usr/sbin/c++ -DDEBUG -DOPENSSL_API_COMPAT=10002 -DOPENSSL_IS_OPENSSL3 -DPACKAGE_NAME="\"Apache Traffic Server\"" -DPACKAGE_VERSION=\"10.2.0\" -D_DEBUG -Dlinux -Dstats_over_http_EXPORTS -I/home/jenkins/workspace/Github_Builds/fedora/src/include -I/home/jenkins/workspace/Github_Builds/fedora/src/build/include -I/home/jenkins/workspace/Github_Builds/fedora/src/lib/swoc/include -I/home/jenkins/workspace/Github_Builds/fedora/src/lib/yamlcpp/include -I/home/jenkins/workspace/Github_Builds/fedora/src/lib/yamlcpp/src -pthread -g -std=c++20 -fPIC -Wno-invalid-offsetof -pipe -Wall -Wextra -Wno-noexcept-type -Wsuggest-override -Wno-vla-extension -fno-strict-aliasing -Wno-format-truncation -Werror -MD -MT plugins/stats_over_http/CMakeFiles/stats_over_http.dir/stats_over_http.cc.o -MF plugins/stats_over_http/CMakeFiles/stats_over_http.dir/stats_over_http.cc.o.d -o plugins/stats_over_http/CMakeFiles/stats_over_http.dir/stats_over_http.cc.o -c /home/jenkins/workspace/Github_Builds/fedora/src/plugins/stats_over_http/stats_over_http.cc
../plugins/stats_over_http/stats_over_http.cc: In function 'constexpr void test_parse_metric_v2()':
../plugins/stats_over_http/stats_over_http.cc:1477:68: error: non-constant condition for static assertion
 1477 |   static_assert(parse_metric_v2("proxy.process.http.get_requests") ==
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
 1478 |                 prometheus_v2_metric{"proxy.process.http.requests", "method=\"get\""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1477:68: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1481:69: error: non-constant condition for static assertion
 1481 |   static_assert(parse_metric_v2("proxy.process.http.200_responses") ==
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
 1482 |                 prometheus_v2_metric{"proxy.process.http.responses", "status=\"200\""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1481:69: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1485:71: error: non-constant condition for static assertion
 1485 |   static_assert(parse_metric_v2("proxy.process.http.cache_hit_fresh") ==
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
 1486 |                 prometheus_v2_metric{"proxy.process.http.cache_fresh", "result=\"hit\""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1485:71: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1489:80: error: non-constant condition for static assertion
 1489 |   static_assert(parse_metric_v2("proxy.process.cache.volume_0.lookup.success") ==
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
 1490 |                 prometheus_v2_metric{"proxy.process.cache.volume.lookup.success", "volume=\"0\""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1489:80: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1494:68: error: non-constant condition for static assertion
 1494 |   static_assert(parse_metric_v2("proxy.process.http.avg_close_ms") == prometheus_v2_metric{"proxy.process.http.avg_close.ms", ""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1494:68: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1497:65: error: non-constant condition for static assertion
 1497 |   static_assert(parse_metric_v2("proxy.process.http.time_10ms") == prometheus_v2_metric{"proxy.process.http.time", "le=\"10ms\""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1497:65: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1501:73: error: non-constant condition for static assertion
 1501 |   static_assert(parse_metric_v2("proxy.process.http.get.200_responses") ==
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
 1502 |                 prometheus_v2_metric{"proxy.process.http.responses", "method=\"get\", status=\"200\""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1501:73: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1505:78: error: non-constant condition for static assertion
 1505 |   static_assert(parse_metric_v2("proxy.process.http.connection_errors[500]") ==
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
 1506 |                 prometheus_v2_metric{"proxy.process.http.connection_errors", "status=\"500\""});
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../plugins/stats_over_http/stats_over_http.cc:1505:78: error: call to non-'constexpr' function 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const'
../plugins/stats_over_http/stats_over_http.cc:103:3: note: 'bool prometheus_v2_metric::operator==(const prometheus_v2_metric&) const' declared here
  103 |   operator==(const prometheus_v2_metric &other) const
      |   ^~~~~~~~
At global scope:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stats_over_http stats_over_http

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants