You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by je...@apache.org on 2010/11/04 22:54:21 UTC

svn commit: r1031277 - in /trafficserver/plugins/stats: ./ README stats.c

Author: jesus
Date: Thu Nov  4 21:54:21 2010
New Revision: 1031277

URL: http://svn.apache.org/viewvc?rev=1031277&view=rev
Log:
JSON stats over HTTP

Added:
    trafficserver/plugins/stats/
    trafficserver/plugins/stats/README
    trafficserver/plugins/stats/stats.c

Added: trafficserver/plugins/stats/README
URL: http://svn.apache.org/viewvc/trafficserver/plugins/stats/README?rev=1031277&view=auto
==============================================================================
--- trafficserver/plugins/stats/README (added)
+++ trafficserver/plugins/stats/README Thu Nov  4 21:54:21 2010
@@ -0,0 +1,16 @@
+Compile:
+  tsxs -c stats.c -o stats.so
+Install:
+  sudo tsxs -o stats.so -i
+
+Add to the plugins.conf:
+
+  stats.so
+
+Add to the remap.conf:
+
+  regex_map http://[0-9.]+/_stats http://[stats]/_stats
+
+
+
+start traffic server and visit http://IP:port/_stats

Added: trafficserver/plugins/stats/stats.c
URL: http://svn.apache.org/viewvc/trafficserver/plugins/stats/stats.c?rev=1031277&view=auto
==============================================================================
--- trafficserver/plugins/stats/stats.c (added)
+++ trafficserver/plugins/stats/stats.c Thu Nov  4 21:54:21 2010
@@ -0,0 +1,340 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+/* stats.c:  expose traffic server stats over http
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <ts/ts.h>
+#include <string.h>
+
+typedef struct stats_state_t
+{
+  INKVConn net_vc;
+  INKVIO read_vio;
+  INKVIO write_vio;
+
+  INKIOBuffer req_buffer;
+  INKIOBufferReader req_reader;
+
+  INKIOBuffer resp_buffer;
+  INKIOBufferReader resp_reader;
+
+  INKHttpTxn http_txnp;
+
+  int output_bytes;
+  int body_written;
+} stats_state;
+
+static void
+stats_cleanup(INKCont contp, stats_state * my_state)
+{
+  if (my_state->req_buffer) {
+    if (INKIOBufferDestroy(my_state->req_buffer) == INK_ERROR) {
+      INKDebug("istats", "INKIOBufferDestroy");
+    }
+    my_state->req_buffer = NULL;
+  }
+
+  if (my_state->resp_buffer) {
+    if (INKIOBufferDestroy(my_state->resp_buffer) == INK_ERROR) {
+      INKDebug("istats", "INKIOBufferDestroy");
+    }
+    my_state->resp_buffer = NULL;
+  }
+  if (INKVConnClose(my_state->net_vc) == INK_ERROR) {
+    INKDebug("istats", "INKVConnClose");
+  }
+  INKfree(my_state);
+  INKContDestroy(contp);
+}
+
+static void
+stats_process_accept(INKCont contp, stats_state * my_state)
+{
+
+  my_state->req_buffer = INKIOBufferCreate();
+  if (my_state->req_buffer == INK_ERROR_PTR) {
+    INKDebug("istats", "INKIOBufferCreate");
+    return;
+  }
+  my_state->req_reader = INKIOBufferReaderAlloc(my_state->req_buffer);
+  if (my_state->req_reader == INK_ERROR_PTR) {
+    INKDebug("istats", "INKIOBufferReaderAlloc");
+    return;
+  }
+  my_state->resp_buffer = INKIOBufferCreate();
+  if (my_state->resp_buffer == INK_ERROR_PTR) {
+    INKDebug("istats", "INKIOBufferCreate");
+    return;
+  }
+  my_state->resp_reader = INKIOBufferReaderAlloc(my_state->resp_buffer);
+  if (my_state->resp_reader == INK_ERROR_PTR) {
+    INKDebug("istats", "INKIOBufferReaderAlloc");
+    return;
+  }
+
+  my_state->read_vio = INKVConnRead(my_state->net_vc, contp, my_state->req_buffer, INT_MAX);
+  if (my_state->read_vio == INK_ERROR_PTR) {
+    INKDebug("istats", "INKVConnRead");
+    return;
+  }
+}
+
+static int
+stats_add_data_to_resp_buffer(const char *s, stats_state * my_state)
+{
+  int s_len = strlen(s);
+  char *buf = (char *) INKmalloc(s_len);
+
+  memcpy(buf, s, s_len);
+  INKIOBufferWrite(my_state->resp_buffer, buf, s_len);
+
+  INKfree(buf);
+  buf = NULL;
+  return s_len;
+}
+
+static int
+stats_add_resp_header(stats_state * my_state)
+{
+  char resp[] = "HTTP/1.0 200 Ok\r\n"
+    "Content-Type: text/javascript\r\nCache-Control: no-cache\r\n\r\n";
+  return stats_add_data_to_resp_buffer(resp, my_state);
+}
+
+static void
+stats_process_read(INKCont contp, INKEvent event, stats_state * my_state)
+{
+  INKDebug("istats", "stats_process_read(%d)", event);
+  if (event == INK_EVENT_VCONN_READ_READY) {
+    my_state->output_bytes = stats_add_resp_header(my_state);
+    if (INKVConnShutdown(my_state->net_vc, 1, 0) == INK_ERROR) {
+      INKDebug("istats", "INKVConnShutdown");
+      return;
+    }
+    my_state->write_vio = INKVConnWrite(my_state->net_vc, contp, my_state->resp_reader, INT_MAX);
+    if (my_state->write_vio == INK_ERROR_PTR) {
+      INKDebug("istats", "INKVConnWrite");
+      return;
+    }
+  } else if (event == INK_EVENT_ERROR) {
+    INKError("stats_process_read: Received INK_EVENT_ERROR\n");
+  } else if (event == INK_EVENT_VCONN_EOS) {
+    /* client may end the connection, simply return */
+    return;
+  } else if (event == INK_EVENT_NET_ACCEPT_FAILED) {
+    INKError("stats_process_read: Received INK_EVENT_NET_ACCEPT_FAILED\n");
+  } else {
+    printf("Unexpected Event %d\n", event);
+    INKReleaseAssert(!"Unexpected Event");
+  }
+}
+
+#define APPEND(a) my_state->output_bytes += stats_add_data_to_resp_buffer(a, my_state)
+#define APPEND_STAT(a, fmt, v) do { \
+  char b[128]; \
+  snprintf(b, sizeof(b), "\"%s\": \"" fmt "\",\n", a, v); \
+  APPEND(b); \
+} while(0)
+
+static void
+json_out_stat(INKRecordType rec_type, void *edata, int registered,
+              const char *name, INKRecordDataType data_type,
+              INKRecordData *datum) {
+  stats_state *my_state = edata;
+
+  switch(data_type) {
+  case INK_RECORDDATATYPE_COUNTER:
+    APPEND_STAT(name, "%llu", datum->rec_counter); break;
+  case INK_RECORDDATATYPE_INT:
+    APPEND_STAT(name, "%llu", datum->rec_int); break;
+  case INK_RECORDDATATYPE_FLOAT:
+    APPEND_STAT(name, "%f", datum->rec_float); break;
+  case INK_RECORDDATATYPE_STRING:
+    APPEND_STAT(name, "%s", datum->rec_string); break;
+  default:
+    INKDebug("istats", "unkown type for %s: %d", name, data_type);
+    break;
+  }
+}
+static void
+json_out_stats(stats_state * my_state)
+{
+  const char *version;
+  APPEND("{ \"global\": {\n");
+
+  INKRecordDump(INK_RECORDTYPE_PROCESS, json_out_stat, my_state);
+  version = INKTrafficServerVersionGet();
+  APPEND("\"server\": \"");
+  APPEND(version);
+  APPEND("\"\n");
+  APPEND("  }\n}\n");
+}
+
+static void
+stats_process_write(INKCont contp, INKEvent event, stats_state * my_state)
+{
+  if (event == INK_EVENT_VCONN_WRITE_READY) {
+    if (my_state->body_written == 0) {
+      INKDebug("istats", "plugin adding response body");
+      my_state->body_written = 1;
+      json_out_stats(my_state);
+      if (INKVIONBytesSet(my_state->write_vio, my_state->output_bytes) == INK_ERROR) {
+        INKDebug("istats", "INKVIONBytesSet");
+        return;
+      }
+    }
+    if (INKVIOReenable(my_state->write_vio) == INK_ERROR) {
+      INKDebug("istats", "INKVIOReenable");
+      return;
+    }
+  } else if (INK_EVENT_VCONN_WRITE_COMPLETE) {
+    stats_cleanup(contp, my_state);
+  } else if (event == INK_EVENT_ERROR) {
+    INKError("stats_process_write: Received INK_EVENT_ERROR\n");
+  } else {
+    INKReleaseAssert(!"Unexpected Event");
+  }
+}
+
+static int
+stats_dostuff(INKCont contp, INKEvent event, void *edata)
+{
+  stats_state *my_state = INKContDataGet(contp);
+  if (event == INK_EVENT_NET_ACCEPT) {
+    my_state->net_vc = (INKVConn) edata;
+    stats_process_accept(contp, my_state);
+  } else if (edata == my_state->read_vio) {
+    stats_process_read(contp, event, my_state);
+  } else if (edata == my_state->write_vio) {
+    stats_process_write(contp, event, my_state);
+  } else {
+    INKReleaseAssert(!"Unexpected Event");
+  }
+  return 0;
+}
+static int
+stats_origin(INKCont contp, INKEvent event, void *edata)
+{
+  INKCont icontp;
+  stats_state *my_state;
+  INKHttpTxn txnp = (INKHttpTxn) edata;
+  INKMBuffer reqp;
+  INKMLoc hdr_loc = NULL, field = NULL;
+  int host_header_len;
+  const char *host_header_ptr;
+
+  INKDebug("istats", "in the read stuff");
+ 
+  INKHttpTxnClientReqGet(txnp, &reqp, &hdr_loc); 
+  field = INKMimeHdrFieldFind(reqp, hdr_loc,
+                              INK_MIME_FIELD_HOST, INK_MIME_LEN_HOST);
+  if(field) {
+    INKMimeHdrFieldValueStringGet(reqp, hdr_loc, field, 0,
+                                  &host_header_ptr, &host_header_len);
+    if(host_header_len != 7 || memcmp(host_header_ptr, "[stats]", 7))
+      goto notforme;
+  }
+
+  /* This is us -- register our intercept */
+  INKDebug("istats", "Intercepting request");
+
+  icontp = INKContCreate(stats_dostuff, INKMutexCreate());
+  if (icontp == INK_ERROR_PTR) goto notforme;
+  my_state = (stats_state *) INKmalloc(sizeof(*my_state));
+  memset(my_state, 0, sizeof(*my_state));
+  if (INK_ERROR == INKContDataSet(icontp, my_state)) goto error;
+  if (INK_ERROR == INKHttpTxnIntercept(icontp, txnp)) goto error;
+  goto cleanup;
+
+ error:
+  INKError("[PluginInit] Error while intercepting stuff");
+  INKContDestroy(icontp);
+  INKHttpTxnReenable(txnp, INK_EVENT_HTTP_ERROR);
+  goto cleanup;
+
+ notforme:
+
+ cleanup:
+  INKHttpTxnReenable(txnp, INK_EVENT_HTTP_CONTINUE);
+  if(hdr_loc) INKHandleMLocRelease(reqp, INK_NULL_MLOC, hdr_loc);
+  if(field) INKHandleMLocRelease(reqp, INK_NULL_MLOC, field);
+  return 0;
+}
+
+int
+check_ts_version()
+{
+
+  const char *ts_version = INKTrafficServerVersionGet();
+  int result = 0;
+
+  if (ts_version) {
+    int major_ts_version = 0;
+    int minor_ts_version = 0;
+    int patch_ts_version = 0;
+
+    if (sscanf(ts_version, "%d.%d.%d", &major_ts_version, &minor_ts_version, &patch_ts_version) != 3) {
+      return 0;
+    }
+
+    /* Need at least TS 2.0 */
+    if (major_ts_version >= 2) {
+      result = 1;
+    }
+  }
+
+  return result;
+}
+
+void
+INKPluginInit(int argc, const char *argv[])
+{
+  INKPluginRegistrationInfo info;
+
+  info.plugin_name = "stats";
+  info.vendor_name = "Apache Software Foundation";
+  info.support_email = "jesus@omniti.com";
+
+  if (!INKPluginRegister(INK_SDK_VERSION_2_0, &info))
+    INKError("Plugin registration failed. \n");
+
+  if (!check_ts_version()) {
+    INKError("Plugin requires Traffic Server 2.0 or later\n");
+    return;
+  }
+
+  /* Create a continuation with a mutex as there is a shared global structure
+     containing the headers to add */
+  if (INK_ERROR == INKHttpHookAdd(INK_HTTP_READ_REQUEST_HDR_HOOK,
+                                  INKContCreate(stats_origin, NULL))) {
+    INKError("[PluginInit] Error while registering to hook");
+    return;
+  }
+
+  INKDebug("istats", "stats module registered");
+}