You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ez...@apache.org on 2020/05/29 14:59:14 UTC
[trafficserver] branch master updated: Add CSV output as an
optional format for stats_over_http (#6818)
This is an automated email from the ASF dual-hosted git repository.
eze 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 0eed3a6 Add CSV output as an optional format for stats_over_http (#6818)
0eed3a6 is described below
commit 0eed3a664e3018b6d2cd3747631ba4f81ba457fc
Author: Evan Zelkowitz <ez...@apache.org>
AuthorDate: Fri May 29 07:59:02 2020 -0700
Add CSV output as an optional format for stats_over_http (#6818)
stats_over_http can now take an `Accept` header. By default it will still return a json formatted output with the (now correct) `text/json` `Content-Type` header.
With these changes you can also send a `Accept: text/csv` header and the output will be in CSV format along with the corresponding `Content-Type` header
---
doc/admin-guide/plugins/stats_over_http.en.rst | 16 +++-
plugins/stats_over_http/stats_over_http.c | 113 ++++++++++++++++++++++---
2 files changed, 116 insertions(+), 13 deletions(-)
diff --git a/doc/admin-guide/plugins/stats_over_http.en.rst b/doc/admin-guide/plugins/stats_over_http.en.rst
index 5bdca0e..01f96db 100644
--- a/doc/admin-guide/plugins/stats_over_http.en.rst
+++ b/doc/admin-guide/plugins/stats_over_http.en.rst
@@ -23,8 +23,9 @@ Stats Over HTTP Plugin
**********************
This plugin implements an HTTP interface to all Traffic Server statistics. The
-metrics returned are in a JSON format, for easy processing. This plugin is now
-part of the standard ATS build process, and should be available after install.
+metrics returned are in a JSON format by default, for easy processing. You can
+also output the stats in CSV format as well. This plugin is now part of the
+standard ATS build process, and should be available after install.
Enabling Stats Over HTTP
========================
@@ -92,3 +93,14 @@ A comma separated white list of ipv4 addresses allowed to accesss the endpoint
.. option:: allow_ip6=
A comma separated white list of ipv6 addresses allowed to access the endpoint
+
+Output Format
+=============
+
+By default stats_over_http.so will output all the stats in json format. However
+if you wish to have it in CSV format you can do so by passing an ``Accept`` header:
+
+.. option:: Accept: text/csv
+
+In either case the ``Content-Type`` header returned by stats_over_http.so will reflect
+the content that has been returned, either ``text/json`` or ``text/csv``.
diff --git a/plugins/stats_over_http/stats_over_http.c b/plugins/stats_over_http/stats_over_http.c
index cdfc8f9..1be2d54 100644
--- a/plugins/stats_over_http/stats_over_http.c
+++ b/plugins/stats_over_http/stats_over_http.c
@@ -72,6 +72,8 @@ typedef struct {
config_t *config;
} config_holder_t;
+typedef enum { JSON_OUTPUT, CSV_OUTPUT } output_format;
+
int configReloadRequests = 0;
int configReloads = 0;
time_t lastReloadRequest = 0;
@@ -95,6 +97,7 @@ typedef struct stats_state_t {
int output_bytes;
int body_written;
+ output_format output;
} stats_state;
static char *
@@ -141,12 +144,24 @@ stats_add_data_to_resp_buffer(const char *s, stats_state *my_state)
return s_len;
}
-static const char RESP_HEADER[] = "HTTP/1.0 200 Ok\r\nContent-Type: text/javascript\r\nCache-Control: no-cache\r\n\r\n";
+static const char RESP_HEADER_JSON[] = "HTTP/1.0 200 Ok\r\nContent-Type: text/json\r\nCache-Control: no-cache\r\n\r\n";
+static const char RESP_HEADER_CSV[] = "HTTP/1.0 200 Ok\r\nContent-Type: text/csv\r\nCache-Control: no-cache\r\n\r\n";
static int
stats_add_resp_header(stats_state *my_state)
{
- return stats_add_data_to_resp_buffer(RESP_HEADER, my_state);
+ switch (my_state->output) {
+ case JSON_OUTPUT:
+ return stats_add_data_to_resp_buffer(RESP_HEADER_JSON, my_state);
+ break;
+ case CSV_OUTPUT:
+ return stats_add_data_to_resp_buffer(RESP_HEADER_CSV, my_state);
+ break;
+ default:
+ TSError("stats_add_resp_header: Unknown output format");
+ break;
+ }
+ return stats_add_data_to_resp_buffer(RESP_HEADER_JSON, my_state);
}
static void
@@ -171,13 +186,13 @@ stats_process_read(TSCont contp, TSEvent event, stats_state *my_state)
}
#define APPEND(a) my_state->output_bytes += stats_add_data_to_resp_buffer(a, my_state)
-#define APPEND_STAT(a, fmt, v) \
+#define APPEND_STAT_JSON(a, fmt, v) \
do { \
char b[256]; \
if (snprintf(b, sizeof(b), "\"%s\": \"" fmt "\",\n", a, v) < (int)sizeof(b)) \
APPEND(b); \
} while (0)
-#define APPEND_STAT_NUMERIC(a, fmt, v) \
+#define APPEND_STAT_JSON_NUMERIC(a, fmt, v) \
do { \
char b[256]; \
if (integer_counters) { \
@@ -191,6 +206,20 @@ stats_process_read(TSCont contp, TSEvent event, stats_state *my_state)
} \
} while (0)
+#define APPEND_STAT_CSV(a, fmt, v) \
+ do { \
+ char b[256]; \
+ if (snprintf(b, sizeof(b), "%s," fmt "\n", a, v) < (int)sizeof(b)) \
+ APPEND(b); \
+ } while (0)
+#define APPEND_STAT_CSV_NUMERIC(a, fmt, v) \
+ do { \
+ char b[256]; \
+ if (snprintf(b, sizeof(b), "%s," fmt "\n", a, v) < (int)sizeof(b)) { \
+ APPEND(b); \
+ } \
+ } while (0)
+
// This wraps uint64_t values to the int64_t range to fit into a Java long. Java 8 has an unsigned long which
// can interoperate with a full uint64_t, but it's unlikely that much of the ecosystem supports that yet.
static uint64_t
@@ -211,22 +240,47 @@ json_out_stat(TSRecordType rec_type ATS_UNUSED, void *edata, int registered ATS_
switch (data_type) {
case TS_RECORDDATATYPE_COUNTER:
- APPEND_STAT_NUMERIC(name, "%" PRIu64, wrap_unsigned_counter(datum->rec_counter));
+ APPEND_STAT_JSON_NUMERIC(name, "%" PRIu64, wrap_unsigned_counter(datum->rec_counter));
break;
case TS_RECORDDATATYPE_INT:
- APPEND_STAT_NUMERIC(name, "%" PRIu64, wrap_unsigned_counter(datum->rec_int));
+ APPEND_STAT_JSON_NUMERIC(name, "%" PRIu64, wrap_unsigned_counter(datum->rec_int));
break;
case TS_RECORDDATATYPE_FLOAT:
- APPEND_STAT_NUMERIC(name, "%f", datum->rec_float);
+ APPEND_STAT_JSON_NUMERIC(name, "%f", datum->rec_float);
break;
case TS_RECORDDATATYPE_STRING:
- APPEND_STAT(name, "%s", datum->rec_string);
+ APPEND_STAT_JSON(name, "%s", datum->rec_string);
break;
default:
TSDebug(PLUGIN_NAME, "unknown type for %s: %d", name, data_type);
break;
}
}
+
+static void
+csv_out_stat(TSRecordType rec_type ATS_UNUSED, void *edata, int registered ATS_UNUSED, const char *name, TSRecordDataType data_type,
+ TSRecordData *datum)
+{
+ stats_state *my_state = edata;
+ switch (data_type) {
+ case TS_RECORDDATATYPE_COUNTER:
+ APPEND_STAT_CSV_NUMERIC(name, "%" PRIu64, wrap_unsigned_counter(datum->rec_counter));
+ break;
+ case TS_RECORDDATATYPE_INT:
+ APPEND_STAT_CSV_NUMERIC(name, "%" PRIu64, wrap_unsigned_counter(datum->rec_int));
+ break;
+ case TS_RECORDDATATYPE_FLOAT:
+ APPEND_STAT_CSV_NUMERIC(name, "%f", datum->rec_float);
+ break;
+ case TS_RECORDDATATYPE_STRING:
+ APPEND_STAT_CSV(name, "%s", datum->rec_string);
+ break;
+ default:
+ TSDebug(PLUGIN_NAME, "unknown type for %s: %d", name, data_type);
+ break;
+ }
+}
+
static void
json_out_stats(stats_state *my_state)
{
@@ -242,13 +296,32 @@ json_out_stats(stats_state *my_state)
}
static void
+csv_out_stats(stats_state *my_state)
+{
+ const char *version;
+ TSRecordDump((TSRecordType)(TS_RECORDTYPE_PLUGIN | TS_RECORDTYPE_NODE | TS_RECORDTYPE_PROCESS), csv_out_stat, my_state);
+ version = TSTrafficServerVersionGet();
+ APPEND_STAT_CSV("version", "%s", version);
+}
+
+static void
stats_process_write(TSCont contp, TSEvent event, stats_state *my_state)
{
if (event == TS_EVENT_VCONN_WRITE_READY) {
if (my_state->body_written == 0) {
TSDebug(PLUGIN_NAME, "plugin adding response body");
my_state->body_written = 1;
- json_out_stats(my_state);
+ switch (my_state->output) {
+ case JSON_OUTPUT:
+ json_out_stats(my_state);
+ break;
+ case CSV_OUTPUT:
+ csv_out_stats(my_state);
+ break;
+ default:
+ TSError("stats_process_write: Unknown output type\n");
+ break;
+ }
TSVIONBytesSet(my_state->write_vio, my_state->output_bytes);
}
TSVIOReenable(my_state->write_vio);
@@ -286,7 +359,7 @@ stats_origin(TSCont contp ATS_UNUSED, TSEvent event ATS_UNUSED, void *edata)
config_t *config;
TSHttpTxn txnp = (TSHttpTxn)edata;
TSMBuffer reqp;
- TSMLoc hdr_loc = NULL, url_loc = NULL;
+ TSMLoc hdr_loc = NULL, url_loc = NULL, accept_field = NULL;
TSEvent reenable = TS_EVENT_HTTP_CONTINUE;
TSDebug(PLUGIN_NAME, "in the read stuff");
@@ -322,6 +395,22 @@ stats_origin(TSCont contp ATS_UNUSED, TSEvent event ATS_UNUSED, void *edata)
icontp = TSContCreate(stats_dostuff, TSMutexCreate());
my_state = (stats_state *)TSmalloc(sizeof(*my_state));
memset(my_state, 0, sizeof(*my_state));
+
+ accept_field = TSMimeHdrFieldFind(reqp, hdr_loc, TS_MIME_FIELD_ACCEPT, TS_MIME_LEN_ACCEPT);
+ my_state->output = JSON_OUTPUT; // default to json output
+ // accept header exists, use it to determine response type
+ if (accept_field != TS_NULL_MLOC) {
+ int len = -1;
+ const char *str = TSMimeHdrFieldValueStringGet(reqp, hdr_loc, accept_field, -1, &len);
+
+ // Parse the Accept header, default to JSON output unless its another supported format
+ if (!strncasecmp(str, "text/csv", len)) {
+ my_state->output = CSV_OUTPUT;
+ } else {
+ my_state->output = JSON_OUTPUT;
+ }
+ }
+
TSContDataSet(icontp, my_state);
TSHttpTxnIntercept(icontp, txnp);
goto cleanup;
@@ -335,7 +424,9 @@ cleanup:
if (hdr_loc) {
TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
}
-
+ if (accept_field) {
+ TSHandleMLocRelease(reqp, TS_NULL_MLOC, accept_field);
+ }
TSHttpTxnReenable(txnp, reenable);
return 0;
}