This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 00f641b5d3 stats_over_http: Add HINT and TYPE Prometheus annotations 
(#12406)
00f641b5d3 is described below

commit 00f641b5d3c4d8c93e88c28b4d78aff83aa15ddf
Author: Brian Neradt <[email protected]>
AuthorDate: Mon Aug 4 14:18:27 2025 -0500

    stats_over_http: Add HINT and TYPE Prometheus annotations (#12406)
    
    This makes use of #12392 to add TYPE annotations to stats_over_http
    Prometheus formatted output. While we're at it, it also adds a HINT
    string to indicate what the original ATS metric name was that the
    Prometheus metric is for since Prometheus forces us to modify the metric
    pretty signifantly from its original form (periods replaced with
    underscores, etc.).
---
 plugins/stats_over_http/stats_over_http.cc         | 11 +++++++
 .../stats_over_http/stats_over_http.test.py        | 36 ++++++++++++++++++----
 2 files changed, 41 insertions(+), 6 deletions(-)

diff --git a/plugins/stats_over_http/stats_over_http.cc 
b/plugins/stats_over_http/stats_over_http.cc
index 6e67c80f24..bd826f5d04 100644
--- a/plugins/stats_over_http/stats_over_http.cc
+++ b/plugins/stats_over_http/stats_over_http.cc
@@ -496,14 +496,25 @@ prometheus_out_stat(TSRecordType /* rec_type ATS_UNUSED 
*/, void *edata, int /*
 {
   stats_state *my_state       = static_cast<stats_state *>(edata);
   std::string  sanitized_name = sanitize_metric_name_for_prometheus(name);
+  char         type_buffer[256];
+  char         help_buffer[256];
+
+  snprintf(help_buffer, sizeof(help_buffer), "# HELP %s %s\n", 
sanitized_name.c_str(), name);
   switch (data_type) {
   case TS_RECORDDATATYPE_COUNTER:
+    APPEND(help_buffer);
+    snprintf(type_buffer, sizeof(type_buffer), "# TYPE %s counter\n", 
sanitized_name.c_str());
+    APPEND(type_buffer);
     APPEND_STAT_PROMETHEUS_NUMERIC(sanitized_name.c_str(), "%" PRIu64, 
wrap_unsigned_counter(datum->rec_counter));
     break;
   case TS_RECORDDATATYPE_INT:
+    APPEND(help_buffer);
+    snprintf(type_buffer, sizeof(type_buffer), "# TYPE %s gauge\n", 
sanitized_name.c_str());
+    APPEND(type_buffer);
     APPEND_STAT_PROMETHEUS_NUMERIC(sanitized_name.c_str(), "%" PRIu64, 
wrap_unsigned_counter(datum->rec_int));
     break;
   case TS_RECORDDATATYPE_FLOAT:
+    APPEND(help_buffer);
     APPEND_STAT_PROMETHEUS_NUMERIC(sanitized_name.c_str(), "%f", 
datum->rec_float);
     break;
   case TS_RECORDDATATYPE_STRING:
diff --git 
a/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py 
b/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py
index e70cdbf64e..2afa1a468b 100644
--- a/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py
+++ b/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py
@@ -63,6 +63,33 @@ class StatsOverHttpPluginTest:
         assert (self.state == self.State.RUNNING)
         tr.StillRunningAfter = self.ts
 
+    def __checkPrometheusMetrics(self, p: 'Test.Process', from_prometheus: 
bool):
+        '''Check the Prometheus metrics output.
+        :param p: The process whose output to check.
+        :param from_prometheus: Whether the output is from Prometheus. 
Otherwise it's from ATS.
+        '''
+        p.Streams.stdout += Testers.ContainsExpression(
+            'HELP proxy_process_http2_current_client_connections 
proxy.process.http2.current_client_connections',
+            'Output should have a help line for a gauge.')
+        p.Streams.stdout += Testers.ContainsExpression(
+            'TYPE proxy_process_http2_current_client_connections gauge', 
'Output should have a type line for a gauge.')
+        p.Streams.stdout += Testers.ContainsExpression(
+            'proxy_process_http2_current_client_connections 0', 'Verify the 
successful parsing of Prometheus metrics for a gauge.')
+
+        p.Streams.stdout += Testers.ContainsExpression(
+            'HELP proxy_process_http_delete_requests 
proxy.process.http.delete_requests',
+            'Output should have a help line for a counter.')
+        p.Streams.stdout += Testers.ContainsExpression(
+            'TYPE proxy_process_http_delete_requests counter', 'Output should 
have a type line for a counter.')
+
+        # Curiosly, Prometheus appaneds _total to counter metrics.
+        if from_prometheus:
+            p.Streams.stdout += Testers.ContainsExpression(
+                'proxy_process_http_delete_requests_total 0', 'Verify the 
successful parsing of Prometheus metrics for a counter.')
+        else:
+            p.Streams.stdout += Testers.ContainsExpression(
+                'proxy_process_http_delete_requests 0', 'Verify the successful 
parsing of Prometheus metrics for a counter.')
+
     def __testCaseNoAccept(self):
         tr = Test.AddTestRun('Fetch stats over HTTP in JSON format: no Accept 
and default path')
         self.__checkProcessBefore(tr)
@@ -92,8 +119,7 @@ class StatsOverHttpPluginTest:
         tr.MakeCurlCommand(
             f"-vs -H'Accept: text/plain; version=0.0.4' --http1.1 
http://127.0.0.1:{self.ts.Variables.port}/_stats";, ts=self.ts)
         tr.Processes.Default.ReturnCode = 0
-        tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-            'proxy_process_http_delete_requests 0', 'Output should be 
Prometheus formatted.')
+        self.__checkPrometheusMetrics(tr.Processes.Default, 
from_prometheus=False)
         tr.Processes.Default.Streams.stderr = 
"gold/stats_over_http_prometheus_stderr.gold"
         tr.Processes.Default.TimeOut = 3
         self.__checkProcessAfter(tr)
@@ -126,8 +152,7 @@ class StatsOverHttpPluginTest:
         self.__checkProcessBefore(tr)
         tr.MakeCurlCommand(f"-vs --http1.1 
http://127.0.0.1:{self.ts.Variables.port}/_stats/prometheus";, ts=self.ts)
         tr.Processes.Default.ReturnCode = 0
-        tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-            'proxy_process_http_delete_requests 0', 'Prometheus output 
expected.')
+        self.__checkPrometheusMetrics(tr.Processes.Default, 
from_prometheus=False)
         tr.Processes.Default.Streams.stderr = 
"gold/stats_over_http_prometheus_stderr.gold"
         tr.Processes.Default.TimeOut = 3
         self.__checkProcessAfter(tr)
@@ -155,8 +180,7 @@ class StatsOverHttpPluginTest:
         p = tr.Processes.Default
         p.Command = f'{sys.executable} {ingester} 
http://127.0.0.1:{self.ts.Variables.port}/_stats/prometheus'
         p.ReturnCode = 0
-        p.Streams.stdout += Testers.ContainsExpression(
-            'proxy_process_http_delete_requests 0', 'Verify the successful 
parsing of Prometheus metrics.')
+        self.__checkPrometheusMetrics(p, from_prometheus=True)
 
     def run(self):
         self.__testCaseNoAccept()

Reply via email to