You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2020/05/27 14:26:49 UTC

[trafficcontrol] branch master updated: Add csv as an output format option for astats via header (#4713)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 0b4b024  Add csv as an output format option for astats via header (#4713)
0b4b024 is described below

commit 0b4b024f29ce3c92a0b56b42193e30336a1c3be4
Author: Evan Zelkowitz <ez...@apache.org>
AuthorDate: Wed May 27 07:26:39 2020 -0700

    Add csv as an output format option for astats via header (#4713)
    
    This adds the ability to output CSV formatted stats via the `Accept` header.  If set to `text/csv` then the output will be in csv format.  Any other values will output in the old `text/json` format.  The `Content-Type` field has also been fixed since previously it was reported as `application/json`
---
 .../plugins/astats_over_http/astats_over_http.c    | 179 ++++++++++++++++++---
 1 file changed, 156 insertions(+), 23 deletions(-)

diff --git a/traffic_server/plugins/astats_over_http/astats_over_http.c b/traffic_server/plugins/astats_over_http/astats_over_http.c
index e39bcc8..8efd242 100644
--- a/traffic_server/plugins/astats_over_http/astats_over_http.c
+++ b/traffic_server/plugins/astats_over_http/astats_over_http.c
@@ -40,6 +40,11 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
+typedef enum {
+	JSON_OUTPUT,
+	CSV_OUTPUT
+} output_format;
+
 typedef struct {
 	unsigned int recordTypes;
 	char *stats_path;
@@ -82,6 +87,7 @@ typedef struct stats_state_t {
 	char *interfaceName;
 	char *query;
 	unsigned int recordTypes;
+	output_format output;
 } stats_state;
 
 int configReloadRequests = 0;
@@ -193,14 +199,25 @@ 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 void
 stats_process_read(TSCont contp, TSEvent event, stats_state *my_state) {
 	TSDebug(PLUGIN_TAG, "stats_process_read(%d)", event);
 
 	if (event == TS_EVENT_VCONN_READ_READY) {
-		my_state->output_bytes = stats_add_data_to_resp_buffer(RESP_HEADER, my_state);
+		switch (my_state->output) {
+			case JSON_OUTPUT:
+				my_state->output_bytes = stats_add_data_to_resp_buffer(RESP_HEADER_JSON, my_state);
+				break;
+			case CSV_OUTPUT:
+				my_state->output_bytes = stats_add_data_to_resp_buffer(RESP_HEADER_CSV, my_state);
+				break;
+			default:
+				TSError("stats_process_read: Unknown output format\n");
+				break;
+		}
 		TSVConnShutdown(my_state->net_vc, 1, 0);
 		my_state->write_vio = TSVConnWrite(my_state->net_vc, contp, my_state->resp_reader, INT64_MAX);
 	}
@@ -218,13 +235,20 @@ 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) do { \
+#define APPEND_STAT_JSON(a, fmt, v) do { \
 		char b[3048]; \
 		int nbytes = snprintf(b, sizeof(b), "   \"%s\": " fmt ",\n", a, v); \
 		if (0 < nbytes && nbytes < (int)sizeof(b)) \
 			APPEND(b); \
 } while(0)
 
+#define APPEND_STAT_CSV(a, fmt, v) do { \
+		char b[3048]; \
+		int nbytes = snprintf(b, sizeof(b), "%s," fmt "\n", a, v); \
+		if (0 < nbytes && nbytes < (int)sizeof(b)) \
+			APPEND(b); \
+} while(0)
+
 static void
 json_out_stat(TSRecordType rec_type, void *edata, int registered, const char *name, TSRecordDataType data_type, TSRecordData *datum) {
 	stats_state *my_state = edata;
@@ -245,13 +269,46 @@ json_out_stat(TSRecordType rec_type, void *edata, int registered, const char *na
 
 	switch(data_type) {
 	case TS_RECORDDATATYPE_COUNTER:
-		APPEND_STAT(name, "%" PRIu64, datum->rec_counter); break;
+		APPEND_STAT_JSON(name, "%" PRIu64, datum->rec_counter); break;
+	case TS_RECORDDATATYPE_INT:
+		APPEND_STAT_JSON(name, "%" PRIu64, datum->rec_int); break;
+	case TS_RECORDDATATYPE_FLOAT:
+		APPEND_STAT_JSON(name, "%f", datum->rec_float); break;
+	case TS_RECORDDATATYPE_STRING:
+		APPEND_STAT_JSON(name, "\"%s\"", datum->rec_string); break;
+	default:
+		TSDebug(PLUGIN_TAG, "unkown type for %s: %d", name, data_type);
+		break;
+	}
+}
+
+static void
+csv_out_stat(TSRecordType rec_type, void *edata, int registered, const char *name, TSRecordDataType data_type, TSRecordData *datum) {
+	stats_state *my_state = edata;
+	int found = 0;
+	int i;
+
+	if (my_state->globals_cnt) {
+		for (i = 0; i < my_state->globals_cnt; i++) {
+			if (strstr(name, my_state->globals[i])) {
+				found = 1;
+				break;
+			}
+		}
+
+		if (!found)
+			return; // skip
+	}
+
+	switch(data_type) {
+	case TS_RECORDDATATYPE_COUNTER:
+		APPEND_STAT_CSV(name, "%" PRIu64, datum->rec_counter); break;
 	case TS_RECORDDATATYPE_INT:
-		APPEND_STAT(name, "%" PRIu64, datum->rec_int); break;
+		APPEND_STAT_CSV(name, "%" PRIu64, datum->rec_int); break;
 	case TS_RECORDDATATYPE_FLOAT:
-		APPEND_STAT(name, "%f", datum->rec_float); break;
+		APPEND_STAT_CSV(name, "%f", datum->rec_float); break;
 	case TS_RECORDDATATYPE_STRING:
-		APPEND_STAT(name, "\"%s\"", datum->rec_string); break;
+		APPEND_STAT_CSV(name, "%s", datum->rec_string); break;
 	default:
 		TSDebug(PLUGIN_TAG, "unkown type for %s: %d", name, data_type);
 		break;
@@ -297,18 +354,18 @@ static int getSpeed(char *inf, char *buffer, int bufferSize) {
 	return speed;
 }
 
-static void appendSystemState(stats_state *my_state) {
+static void appendSystemStateJson(stats_state *my_state) {
 	char *interface = my_state->interfaceName;
 	char buffer[16384];
 	char *str;
 	char *end;
 	int speed = 0;
 
-	APPEND_STAT("inf.name", "\"%s\"", interface);
+	APPEND_STAT_JSON("inf.name", "\"%s\"", interface);
 
 	speed = getSpeed(interface, buffer, sizeof(buffer));
 
-	APPEND_STAT("inf.speed", "%d", speed);
+	APPEND_STAT_JSON("inf.speed", "%d", speed);
 
 	str = getFile("/proc/net/dev", buffer, sizeof(buffer));
 	if (str && interface) {
@@ -317,7 +374,7 @@ static void appendSystemState(stats_state *my_state) {
 			end = strstr(str, "\n");
 			if (end)
 				*end = 0;
-			APPEND_STAT("proc.net.dev", "\"%s\"", str);
+			APPEND_STAT_JSON("proc.net.dev", "\"%s\"", str);
 		}
 	}
 
@@ -326,7 +383,40 @@ static void appendSystemState(stats_state *my_state) {
 		end = strstr(str, "\n");
 		if (end)
 			*end = 0;
-		APPEND_STAT("proc.loadavg", "\"%s\"", str);
+		APPEND_STAT_JSON("proc.loadavg", "\"%s\"", str);
+	}
+}
+
+static void appendSystemStateCsv(stats_state *my_state) {
+	char *interface = my_state->interfaceName;
+	char buffer[16384];
+	char *str;
+	char *end;
+	int speed = 0;
+
+	APPEND_STAT_CSV("inf.name", "%s", interface);
+
+	speed = getSpeed(interface, buffer, sizeof(buffer));
+
+	APPEND_STAT_CSV("inf.speed", "%d", speed);
+
+	str = getFile("/proc/net/dev", buffer, sizeof(buffer));
+	if (str && interface) {
+		str = strstr(str, interface);
+		if (str) {
+			end = strstr(str, "\n");
+			if (end)
+				*end = 0;
+			APPEND_STAT_CSV("proc.net.dev", "%s", str);
+		}
+	}
+
+	str = getFile("/proc/loadavg", buffer, sizeof(buffer));
+	if (str) {
+		end = strstr(str, "\n");
+		if (end)
+			*end = 0;
+		APPEND_STAT_CSV("proc.loadavg", "%s", str);
 	}
 }
 
@@ -343,12 +433,12 @@ static void json_out_stats(stats_state *my_state) {
 
 	if (my_state->recordTypes & SYSTEM_RECORD_TYPE) {
 		APPEND(",\n \"system\": {\n");
-		appendSystemState(my_state);
-		APPEND_STAT("configReloadRequests", "%d", configReloadRequests);
-		APPEND_STAT("lastReloadRequest", "%" PRIu64, lastReloadRequest);
-		APPEND_STAT("configReloads", "%d", configReloads);
-		APPEND_STAT("lastReload", "%" PRIu64, lastReload);
-		APPEND_STAT("astatsLoad", "%" PRIu64, astatsLoad);
+		appendSystemStateJson(my_state);
+		APPEND_STAT_JSON("configReloadRequests", "%d", configReloadRequests);
+		APPEND_STAT_JSON("lastReloadRequest", "%" PRIu64, lastReloadRequest);
+		APPEND_STAT_JSON("configReloads", "%d", configReloads);
+		APPEND_STAT_JSON("lastReload", "%" PRIu64, lastReload);
+		APPEND_STAT_JSON("astatsLoad", "%" PRIu64, astatsLoad);
 		APPEND("\"something\": \"here\"");
 		APPEND("\n  }");
 	}
@@ -356,12 +446,41 @@ static void json_out_stats(stats_state *my_state) {
 	APPEND("\n}\n");
 }
 
+static void csv_out_stats(stats_state *my_state) {
+	const char *version;
+	TSDebug(PLUGIN_TAG, "recordTypes: '0x%x'", my_state->recordTypes);
+    TSRecordDump(my_state->recordTypes, csv_out_stat, my_state);
+	version = TSTrafficServerVersionGet();
+	//APPEND("version","%s",version);
+	APPEND_STAT_CSV("version","%s", version);
+	if (my_state->recordTypes & SYSTEM_RECORD_TYPE) {
+		//APPEND(",\n \"system\": {\n");
+		appendSystemStateCsv(my_state);
+		APPEND_STAT_CSV("configReloadRequests", "%d", configReloadRequests);
+		APPEND_STAT_CSV("lastReloadRequest", "%" PRIu64, lastReloadRequest);
+		APPEND_STAT_CSV("configReloads", "%d", configReloads);
+		APPEND_STAT_CSV("lastReload", "%" PRIu64, lastReload);
+		APPEND_STAT_CSV("astatsLoad", "%" PRIu64, astatsLoad);
+		APPEND("something,here\n");
+	}
+}
+
 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_TAG, "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);
@@ -398,7 +517,7 @@ static int astats_origin(TSCont cont, TSEvent event, 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;
 	config = get_config(cont);
 
@@ -416,7 +535,6 @@ static int astats_origin(TSCont cont, TSEvent event, void *edata) {
 	TSDebug(PLUGIN_TAG,"Path: %.*s",path_len,path);
 
 	if (!(path_len == config->stats_path_len && !memcmp(path, config->stats_path, config->stats_path_len))) {
-//		TSDebug(PLUGIN_TAG, "not right path: %.*s",path_len,path);
 		goto notforme;
 	}
 
@@ -425,7 +543,6 @@ static int astats_origin(TSCont cont, TSEvent event, void *edata) {
 		TSDebug(PLUGIN_TAG, "not right ip");
 		goto notforme;
 	}
-//	TSDebug(PLUGIN_TAG,"Path...: %.*s",path_len,path);
 
 	int query_len;
 	char *query = (char*)TSUrlHttpQueryGet(reqp,url_loc,&query_len);
@@ -440,6 +557,21 @@ static int astats_origin(TSCont cont, TSEvent event, void *edata) {
 	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;
+		}
+	}
+
 	my_state->recordTypes = config->recordTypes;
 	if (query_len) {
 		my_state->query = nstrl(query, query_len);
@@ -463,6 +595,8 @@ static int astats_origin(TSCont cont, TSEvent event, void *edata) {
 		TSHandleMLocRelease(reqp, hdr_loc, url_loc);
 	if (hdr_loc)
 		TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
+	if (accept_field)
+		TSHandleMLocRelease(reqp, TS_NULL_MLOC, accept_field);
 
 	TSHttpTxnReenable(txnp, reenable);
 
@@ -677,7 +811,6 @@ static config_t* new_config(TSFile fh) {
         config->allowIps6 = 0;
         config->ip6Count = 0;
         config->recordTypes = DEFAULT_RECORD_TYPES;
-	//	TSmalloc(6);
 
 	if(!fh) {
 		config->stats_path = nstr("_astats");