You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2014/07/30 19:49:13 UTC

git commit: TS-2974: new epic metrics plugin

Repository: trafficserver
Updated Branches:
  refs/heads/master ff45432a8 -> bde10b778


TS-2974: new epic metrics plugin

Add a new plugin to publich metrics into the Epic monitoring system,
https://code.google.com/p/epicnms/.


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/bde10b77
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/bde10b77
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/bde10b77

Branch: refs/heads/master
Commit: bde10b7785bddd43a56ab1ef0a5f33463ed3e712
Parents: ff45432
Author: James Peach <jp...@apache.org>
Authored: Mon Jul 28 09:34:36 2014 -0700
Committer: James Peach <jp...@apache.org>
Committed: Wed Jul 30 10:48:27 2014 -0700

----------------------------------------------------------------------
 CHANGES                               |   2 +
 configure.ac                          |   1 +
 doc/reference/plugins/epic.en.rst     |  46 +++
 doc/reference/plugins/index.en.rst    |   1 +
 plugins/experimental/Makefile.am      |   1 +
 plugins/experimental/epic/Makefile.am |  22 ++
 plugins/experimental/epic/epic.cc     | 517 +++++++++++++++++++++++++++++
 7 files changed, 590 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bde10b77/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 405d2a7..69df453 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache Traffic Server 5.1.0
 
+  *) [TS-2974] Add a new metrics plugin to support the Epic monitoring system.
+
   *) [TS-2973] Add milestones support to the xdebug plugin.
 
   *) [TS-2957] Add new sslheaders plugin.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bde10b77/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 124fafd..b1b4cef 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1938,6 +1938,7 @@ AS_IF([test "x$enable_experimental_plugins" = xyes], [
     plugins/experimental/channel_stats/Makefile
     plugins/experimental/collapsed_connection/Makefile
     plugins/experimental/custom_redirect/Makefile
+    plugins/experimental/epic/Makefile
     plugins/experimental/escalate/Makefile
     plugins/experimental/esi/Makefile
     plugins/experimental/geoip_acl/Makefile

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bde10b77/doc/reference/plugins/epic.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/plugins/epic.en.rst b/doc/reference/plugins/epic.en.rst
new file mode 100644
index 0000000..b0cafa4
--- /dev/null
+++ b/doc/reference/plugins/epic.en.rst
@@ -0,0 +1,46 @@
+.. _epic-plugin:
+
+Epic Plugin
+***********
+
+.. 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.
+
+The `Epic` plugin emits Traffic Server metrics in a format that is
+consumed tby the `Epic Network Monitoring System
+<https://code.google.com/p/epicnms/>`.  It is a global plugin that
+is installed by adding it to the :file:`plugin.config` file.
+
+Plugin Options
+--------------
+
+--directory=DIR
+  Specify the directory the plugin will write sample files to. The
+  default is ``/usr/local/epic/cache/eapi``.
+
+--period=SECS
+  Specify the sample period in seconds. The default is to write samples every
+  30 seconds.
+
+Caveats
+-------
+
+The Traffic Server metrics system does not store the semantics of
+metrics, so it is not possible to programmatically determine whether
+a metrics can be rate converted. The plugin contains a static list
+of metrics that should not be rate converted (gauges in Epic
+terminilogy).

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bde10b77/doc/reference/plugins/index.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/plugins/index.en.rst b/doc/reference/plugins/index.en.rst
index d056326..3687025 100644
--- a/doc/reference/plugins/index.en.rst
+++ b/doc/reference/plugins/index.en.rst
@@ -68,6 +68,7 @@ directory of the Apache Traffic Server source tree. Exmperimental plugins can be
   balancer.en
   buffer_upload.en
   combo_handler.en
+  epic.en
   esi.en
   geoip_acl.en
   hipes.en

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bde10b77/plugins/experimental/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index 947273b..57fec8c 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -22,6 +22,7 @@ SUBDIRS = \
  channel_stats \
  collapsed_connection \
  custom_redirect \
+ epic \
  escalate \
  esi \
  geoip_acl \

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bde10b77/plugins/experimental/epic/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/epic/Makefile.am b/plugins/experimental/epic/Makefile.am
new file mode 100644
index 0000000..9fd33ed
--- /dev/null
+++ b/plugins/experimental/epic/Makefile.am
@@ -0,0 +1,22 @@
+#  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.
+
+include $(top_srcdir)/build/plugins.mk
+
+pkglib_LTLIBRARIES = epic.la
+epic_la_SOURCES = epic.cc
+epic_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bde10b77/plugins/experimental/epic/epic.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/epic/epic.cc b/plugins/experimental/epic/epic.cc
new file mode 100644
index 0000000..f6e7020
--- /dev/null
+++ b/plugins/experimental/epic/epic.cc
@@ -0,0 +1,517 @@
+/*
+ * 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.
+ */
+
+#define __STDC_LIMIT_MACROS 1
+#define __STDC_FORMAT_MACROS 1
+
+#include <ts/ts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <time.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <inttypes.h>
+#include <set>
+#include <string>
+
+#undef DEBUG
+
+#define unlikely(x) __builtin_expect(!!(x), 0)
+
+#define debug_tag(tag, fmt, ...) do { \
+  if (unlikely(TSIsDebugTagSet(tag))) { \
+    TSDebug(tag, fmt, ##__VA_ARGS__); \
+  } \
+} while(0)
+
+#define debug(fmt, ...) \
+    debug_tag("epic", "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+
+#if defined(DEBUG)
+#define error(fmt, ...) debug(fmt, ##__VA_ARGS__)
+#else
+#define error(fmt, ...) \
+    TSError("epic:%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#endif
+
+template < typename T, unsigned N > static unsigned
+countof(const T(&)[N])
+{
+  return N;
+}
+
+static TSHRTime epic_period = 30ll;     /* 30sec default */
+static const char *epic_prefix = "/usr/local/epic/cache/eapi";
+
+#define GAUGE_METRIC_NAMES \
+  "proxy.node.bandwidth_hit_ratio_avg_10s", \
+  "proxy.node.bandwidth_hit_ratio_avg_10s_int_pct", \
+  "proxy.node.bandwidth_hit_ratio_int_pct", \
+  "proxy.node.cache.bytes_free", \
+  "proxy.node.cache.bytes_free_mb", \
+  "proxy.node.cache.bytes_total", \
+  "proxy.node.cache.bytes_total_mb", \
+  "proxy.node.cache.bytes_used", \
+  "proxy.node.cache.bytes_used_mb", \
+  "proxy.node.cache.percent_free", \
+  "proxy.node.cache.percent_free_int_pct", \
+  "proxy.node.cache_hit_mem_ratio_avg_10s", \
+  "proxy.node.cache_hit_mem_ratio_avg_10s_int_pct", \
+  "proxy.node.cache_hit_mem_ratio_int_pct", \
+  "proxy.node.cache_hit_ratio_avg_10s", \
+  "proxy.node.cache_hit_ratio_avg_10s_int_pct", \
+  "proxy.node.cache_hit_ratio_int_pct", \
+  "proxy.node.cache_total_hits", \
+  "proxy.node.cache_total_hits_avg_10s", \
+  "proxy.node.cache_total_hits_mem", \
+  "proxy.node.cache_total_hits_mem_avg_10s", \
+  "proxy.node.cache_total_misses", \
+  "proxy.node.cache_total_misses_avg_10s", \
+  "proxy.node.client_throughput_out", \
+  "proxy.node.client_throughput_out_kbit", \
+  "proxy.node.cluster.nodes", \
+  "proxy.node.current_cache_connections", \
+  "proxy.node.current_client_connections", \
+  "proxy.node.current_server_connections", \
+  "proxy.node.dns.lookup_avg_time_ms", \
+  "proxy.node.dns.lookups_per_second", \
+  "proxy.node.dns.total_dns_lookups", \
+  "proxy.node.hostdb.hit_ratio_avg_10s", \
+  "proxy.node.hostdb.hit_ratio_int_pct", \
+  "proxy.node.hostdb.total_hits", \
+  "proxy.node.hostdb.total_hits_avg_10s", \
+  "proxy.node.hostdb.total_lookups", \
+  "proxy.node.hostdb.total_lookups_avg_10s", \
+  "proxy.node.http.cache_current_connections_count", \
+  "proxy.node.http.cache_hit_fresh_avg_10s", \
+  "proxy.node.http.cache_hit_ims_avg_10s", \
+  "proxy.node.http.cache_hit_mem_fresh_avg_10s", \
+  "proxy.node.http.cache_hit_revalidated_avg_10s", \
+  "proxy.node.http.cache_hit_stale_served_avg_10s", \
+  "proxy.node.http.cache_miss_changed_avg_10s", \
+  "proxy.node.http.cache_miss_client_no_cache_avg_10s", \
+  "proxy.node.http.cache_miss_cold_avg_10s", \
+  "proxy.node.http.cache_miss_ims_avg_10s", \
+  "proxy.node.http.cache_miss_not_cacheable_avg_10s", \
+  "proxy.node.http.cache_read_error_avg_10s", \
+  "proxy.node.http.current_parent_proxy_connections", \
+  "proxy.node.http.origin_server_current_connections_count", \
+  "proxy.node.http.origin_server_total_request_bytes", \
+  "proxy.node.http.origin_server_total_response_bytes", \
+  "proxy.node.http.origin_server_total_transactions_count", \
+  "proxy.node.http.parent_proxy_total_request_bytes", \
+  "proxy.node.http.parent_proxy_total_response_bytes", \
+  "proxy.node.http.transaction_counts_avg_10s.errors.aborts", \
+  "proxy.node.http.transaction_counts_avg_10s.errors.connect_failed", \
+  "proxy.node.http.transaction_counts_avg_10s.errors.early_hangups", \
+  "proxy.node.http.transaction_counts_avg_10s.errors.empty_hangups", \
+  "proxy.node.http.transaction_counts_avg_10s.errors.other", \
+  "proxy.node.http.transaction_counts_avg_10s.errors.possible_aborts", \
+  "proxy.node.http.transaction_counts_avg_10s.errors.pre_accept_hangups", \
+  "proxy.node.http.transaction_counts_avg_10s.hit_fresh", \
+  "proxy.node.http.transaction_counts_avg_10s.hit_revalidated", \
+  "proxy.node.http.transaction_counts_avg_10s.miss_changed", \
+  "proxy.node.http.transaction_counts_avg_10s.miss_client_no_cache", \
+  "proxy.node.http.transaction_counts_avg_10s.miss_cold", \
+  "proxy.node.http.transaction_counts_avg_10s.miss_not_cacheable", \
+  "proxy.node.http.transaction_counts_avg_10s.other.unclassified", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.aborts", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.aborts_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.connect_failed", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.connect_failed_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.early_hangups", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.early_hangups_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.empty_hangups", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.empty_hangups_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.other", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.other_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.possible_aborts", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.possible_aborts_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.pre_accept_hangups", \
+  "proxy.node.http.transaction_frac_avg_10s.errors.pre_accept_hangups_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.hit_fresh", \
+  "proxy.node.http.transaction_frac_avg_10s.hit_fresh_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.hit_revalidated", \
+  "proxy.node.http.transaction_frac_avg_10s.hit_revalidated_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_changed", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_changed_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_client_no_cache", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_client_no_cache_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_cold", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_cold_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_not_cacheable", \
+  "proxy.node.http.transaction_frac_avg_10s.miss_not_cacheable_int_pct", \
+  "proxy.node.http.transaction_frac_avg_10s.other.unclassified", \
+  "proxy.node.http.transaction_frac_avg_10s.other.unclassified_int_pct", \
+  "proxy.node.http.transaction_msec_avg_10s.errors.aborts", \
+  "proxy.node.http.transaction_msec_avg_10s.errors.connect_failed", \
+  "proxy.node.http.transaction_msec_avg_10s.errors.early_hangups", \
+  "proxy.node.http.transaction_msec_avg_10s.errors.empty_hangups", \
+  "proxy.node.http.transaction_msec_avg_10s.errors.other", \
+  "proxy.node.http.transaction_msec_avg_10s.errors.possible_aborts", \
+  "proxy.node.http.transaction_msec_avg_10s.errors.pre_accept_hangups", \
+  "proxy.node.http.transaction_msec_avg_10s.hit_fresh", \
+  "proxy.node.http.transaction_msec_avg_10s.hit_revalidated", \
+  "proxy.node.http.transaction_msec_avg_10s.miss_changed", \
+  "proxy.node.http.transaction_msec_avg_10s.miss_client_no_cache", \
+  "proxy.node.http.transaction_msec_avg_10s.miss_cold", \
+  "proxy.node.http.transaction_msec_avg_10s.miss_not_cacheable", \
+  "proxy.node.http.transaction_msec_avg_10s.other.unclassified", \
+  "proxy.node.http.user_agent_current_connections_count", \
+  "proxy.node.http.user_agent_total_request_bytes", \
+  "proxy.node.http.user_agent_total_response_bytes", \
+  "proxy.node.http.user_agent_xacts_per_second", \
+  "proxy.node.http.user_agents_total_documents_served", \
+  "proxy.node.http.user_agents_total_transactions_count", \
+  "proxy.node.log.bytes_received_from_network_avg_10s", \
+  "proxy.node.log.bytes_sent_to_network_avg_10s", \
+  "proxy.node.num_processes", \
+  "proxy.node.origin_server_total_bytes", \
+  "proxy.node.origin_server_total_bytes_avg_10s", \
+  "proxy.node.proxy_running", \
+  "proxy.node.restarts.manager.start_time", \
+  "proxy.node.restarts.proxy.cache_ready_time", \
+  "proxy.node.restarts.proxy.start_time", \
+  "proxy.node.restarts.proxy.stop_time", \
+  "proxy.node.user_agent_total_bytes", \
+  "proxy.node.user_agent_total_bytes_avg_10s", \
+  "proxy.node.user_agent_xacts_per_second", \
+  "proxy.node.user_agents_total_documents_served", \
+  "proxy.node.version.manager.build_time", \
+  "proxy.process.cache.KB_read_per_sec", \
+  "proxy.process.cache.KB_write_per_sec", \
+  "proxy.process.cache.bytes_total", \
+  "proxy.process.cache.bytes_used", \
+  "proxy.process.cache.direntries.total", \
+  "proxy.process.cache.direntries.used", \
+  "proxy.process.cache.evacuate.active", \
+  "proxy.process.cache.lookup.active", \
+  "proxy.process.cache.percent_full", \
+  "proxy.process.cache.ram_cache.bytes_total", \
+  "proxy.process.cache.ram_cache.bytes_used", \
+  "proxy.process.cache.ram_cache.total_bytes", \
+  "proxy.process.cache.read.active", \
+  "proxy.process.cache.read_per_sec", \
+  "proxy.process.cache.remove.active", \
+  "proxy.process.cache.scan.active", \
+  "proxy.process.cache.update.active", \
+  "proxy.process.cache.write.active", \
+  "proxy.process.cache.write_per_sec", \
+  "proxy.process.cluster.cache_callback_time", \
+  "proxy.process.cluster.cache_outstanding", \
+  "proxy.process.cluster.cluster_ping_time", \
+  "proxy.process.cluster.connections_avg_time", \
+  "proxy.process.cluster.connections_open", \
+  "proxy.process.cluster.control_messages_avg_receive_time", \
+  "proxy.process.cluster.control_messages_avg_send_time", \
+  "proxy.process.cluster.lkrmt_cache_callback_time", \
+  "proxy.process.cluster.local_connection_time", \
+  "proxy.process.cluster.open_delay_time", \
+  "proxy.process.cluster.rdmsg_assemble_time", \
+  "proxy.process.cluster.remote_connection_time", \
+  "proxy.process.cluster.remote_op_reply_timeouts", \
+  "proxy.process.cluster.remote_op_timeouts", \
+  "proxy.process.cluster.rmt_cache_callback_time", \
+  "proxy.process.dns.fail_avg_time", \
+  "proxy.process.dns.in_flight", \
+  "proxy.process.dns.lookup_avg_time", \
+  "proxy.process.dns.success_avg_time", \
+  "proxy.process.dns.total_dns_lookups", \
+  "proxy.process.hostdb.total_entries", \
+  "proxy.process.hostdb.total_hits", \
+  "proxy.process.hostdb.total_lookups", \
+  "proxy.process.http.avg_transactions_per_client_connection", \
+  "proxy.process.http.avg_transactions_per_parent_connection", \
+  "proxy.process.http.avg_transactions_per_server_connection", \
+  "proxy.process.http.background_fill_current_count", \
+  "proxy.process.http.cache_connection_time", \
+  "proxy.process.http.client_connection_time", \
+  "proxy.process.http.client_transaction_time", \
+  "proxy.process.http.client_write_time", \
+  "proxy.process.http.current_active_client_connections", \
+  "proxy.process.http.current_cache_connections", \
+  "proxy.process.http.current_client_connections", \
+  "proxy.process.http.current_client_transactions", \
+  "proxy.process.http.current_icp_raw_transactions", \
+  "proxy.process.http.current_icp_transactions", \
+  "proxy.process.http.current_parent_proxy_connections", \
+  "proxy.process.http.current_parent_proxy_raw_transactions", \
+  "proxy.process.http.current_parent_proxy_transactions", \
+  "proxy.process.http.current_server_connections", \
+  "proxy.process.http.current_server_raw_transactions", \
+  "proxy.process.http.current_server_transactions", \
+  "proxy.process.http.icp_raw_transaction_time", \
+  "proxy.process.http.icp_transaction_time", \
+  "proxy.process.http.origin_server_request_document_total_size", \
+  "proxy.process.http.origin_server_request_header_total_size", \
+  "proxy.process.http.origin_server_response_document_total_size", \
+  "proxy.process.http.origin_server_response_header_total_size", \
+  "proxy.process.http.origin_server_speed_bytes_per_sec_100", \
+  "proxy.process.http.origin_server_speed_bytes_per_sec_100K", \
+  "proxy.process.http.origin_server_speed_bytes_per_sec_100M", \
+  "proxy.process.http.origin_server_speed_bytes_per_sec_10K", \
+  "proxy.process.http.origin_server_speed_bytes_per_sec_10M", \
+  "proxy.process.http.origin_server_speed_bytes_per_sec_1K", \
+  "proxy.process.http.origin_server_speed_bytes_per_sec_1M", \
+  "proxy.process.http.parent_proxy_connection_time", \
+  "proxy.process.http.parent_proxy_raw_transaction_time", \
+  "proxy.process.http.parent_proxy_request_total_bytes", \
+  "proxy.process.http.parent_proxy_response_total_bytes", \
+  "proxy.process.http.parent_proxy_transaction_time", \
+  "proxy.process.http.pushed_document_total_size", \
+  "proxy.process.http.pushed_response_header_total_size", \
+  "proxy.process.http.server_connection_time", \
+  "proxy.process.http.server_raw_transaction_time", \
+  "proxy.process.http.server_read_time", \
+  "proxy.process.http.server_transaction_time", \
+  "proxy.process.http.total_client_connections", \
+  "proxy.process.http.total_client_connections_ipv4", \
+  "proxy.process.http.total_client_connections_ipv6", \
+  "proxy.process.http.total_incoming_connections", \
+  "proxy.process.http.total_parent_proxy_connections", \
+  "proxy.process.http.total_server_connections", \
+  "proxy.process.http.total_transactions_think_time", \
+  "proxy.process.http.total_transactions_time", \
+  "proxy.process.http.total_x_redirect_count", \
+  "proxy.process.http.transaction_totaltime.errors.aborts", \
+  "proxy.process.http.transaction_totaltime.errors.connect_failed", \
+  "proxy.process.http.transaction_totaltime.errors.early_hangups", \
+  "proxy.process.http.transaction_totaltime.errors.empty_hangups", \
+  "proxy.process.http.transaction_totaltime.errors.other", \
+  "proxy.process.http.transaction_totaltime.errors.possible_aborts", \
+  "proxy.process.http.transaction_totaltime.errors.pre_accept_hangups", \
+  "proxy.process.http.transaction_totaltime.hit_fresh", \
+  "proxy.process.http.transaction_totaltime.hit_fresh.process", \
+  "proxy.process.http.transaction_totaltime.hit_revalidated", \
+  "proxy.process.http.transaction_totaltime.miss_changed", \
+  "proxy.process.http.transaction_totaltime.miss_client_no_cache", \
+  "proxy.process.http.transaction_totaltime.miss_cold", \
+  "proxy.process.http.transaction_totaltime.miss_not_cacheable", \
+  "proxy.process.http.transaction_totaltime.other.unclassified", \
+  "proxy.process.http.user_agent_request_document_total_size", \
+  "proxy.process.http.user_agent_request_header_total_size", \
+  "proxy.process.http.user_agent_response_document_total_size", \
+  "proxy.process.http.user_agent_response_header_total_size", \
+  "proxy.process.http.user_agent_speed_bytes_per_sec_100", \
+  "proxy.process.http.user_agent_speed_bytes_per_sec_100K", \
+  "proxy.process.http.user_agent_speed_bytes_per_sec_100M", \
+  "proxy.process.http.user_agent_speed_bytes_per_sec_10K", \
+  "proxy.process.http.user_agent_speed_bytes_per_sec_10M", \
+  "proxy.process.http.user_agent_speed_bytes_per_sec_1K", \
+  "proxy.process.http.user_agent_speed_bytes_per_sec_1M", \
+  "proxy.process.https.total_client_connections", \
+  "proxy.process.log.log_files_open", \
+  "proxy.process.log.log_files_space_used", \
+  "proxy.process.net.accepts_currently_open", \
+  "proxy.process.net.connections_currently_open", \
+  "proxy.process.socks.connections_currently_open", \
+  "proxy.process.ssl.total_handshake_time", \
+  "proxy.process.ssl.total_success_handshake_count", \
+  "proxy.process.ssl.user_agent_session_timeout", \
+  "proxy.process.update.state_machines", \
+  "proxy.process.version.server.build_time", \
+  "proxy.process.websocket.current_active_client_connections"
+
+// XXX SSL stats (TS-2169) are going to land soon; we will need to update this list ...
+
+// NOTE: the status list of gauge metric names does not capture dynamically constructed
+// names like proxy.process.cache.volume_XX.*.active
+
+#if __cplusplus >= 201102L
+
+static const std::set<std::string> epic_gauges = {
+  GAUGE_METRIC_NAMES
+};
+
+#else
+
+static std::set<std::string> init_gauges()
+{
+  static const char *gauges[] = {
+    GAUGE_METRIC_NAMES
+  };
+
+  std::set<std::string> s;
+
+  for (unsigned i = 0; i < countof(gauges); ++i) {
+    s.insert(gauges[i]);
+  }
+
+  return s;
+}
+
+static const std::set<std::string> epic_gauges(init_gauges());
+
+#endif
+
+struct epic_sample_context
+{
+  time_t sample_time;
+  FILE *sample_fp;
+  char sample_host[MAXHOSTNAMELEN];     /* sysconf(_SC_HOST_NAME_MAX) */
+};
+
+static void
+epic_write_stats(
+    TSRecordType /* rtype */,
+    void * edata,
+    int /* registered */,
+    const char * name,
+    TSRecordDataType dtype,
+    TSRecordData * dvalue)
+{
+  epic_sample_context *sample = (epic_sample_context *) edata;
+  const char *etype;
+
+  TSReleaseAssert(sample != NULL);
+  TSReleaseAssert(sample->sample_fp != NULL);
+
+  /*
+   * O:varName:itime:value:node:type:step
+   *
+   * varName: the name of the variable being stored in 'NODE'
+   * node : name space for variables, buckets of data, hostname, node, etc.
+   * itime : the time in unix seconds which the datapoint is to be stored
+   * value : numeric value to be stored in the ITIME time slot. Counter and
+   *          Derive must be integers, not floats.
+   * type: the datasource type:
+   *      GAUGE: for things like temperature, or current number of processes
+   *      COUNTER: for continuous incrementing numbers, inception based stats
+   *              (will do counter-wrap addition at 32bit or 64bit)
+   *      DERIVE: like COUNTER, except no counter-wrap detection (note: use
+   *              this for Epic API data publishing)
+   *      ABSOLUTE: for counters that reset upon reading
+   * step: (optional) default step is 60 seconds, used here if required and
+   * not sending
+   */
+
+  /* Traffic server metrics don't tell is their semantics, only their data
+   * type. Mostly, metrics are counters, though a few are really gauges. This
+   * sucks, but there's no workaround right now ...
+   */
+  etype = (epic_gauges.find(name) != epic_gauges.end())? "GAUGE" : "DERIVE";
+
+  switch (dtype) {
+  case TS_RECORDDATATYPE_INT:
+    fprintf(sample->sample_fp, "O:%s:%lld:%" PRId64 ":%s:%s:%lld\n",
+            name, (long long) sample->sample_time, dvalue->rec_int, sample->sample_host, etype,
+            (long long) epic_period);
+    break;
+  case TS_RECORDDATATYPE_FLOAT:
+    fprintf(sample->sample_fp, "O:%s:%lld:%f:%s:%s:%lld\n",
+            name, (long long) sample->sample_time, dvalue->rec_float, sample->sample_host, etype,
+            (long long) epic_period);
+    break;
+  case TS_RECORDDATATYPE_COUNTER:
+    fprintf(sample->sample_fp, "O:%s:%lld:%" PRId64 ":%s:%s:%lld\n",
+            name, (long long) sample->sample_time, dvalue->rec_counter, sample->sample_host, etype,
+            (long long) epic_period);
+    break;
+  case TS_RECORDDATATYPE_STRING:
+  case TS_RECORDDATATYPE_STAT_CONST:   /* float */
+  case TS_RECORDDATATYPE_STAT_FX:      /* int */
+    /* fallthru */
+  default:
+#if defined(DEBUG)
+    debug("skipping unsupported metric %s (type %d)", name, (int) dtype);
+#endif
+    return;
+  }
+}
+
+static int
+epic_flush_stats(TSCont /* contp */, TSEvent /* event */ , void * /* edata */ )
+{
+  char path[MAXPATHLEN];
+  epic_sample_context sample;
+
+  TSReleaseAssert(epic_prefix != NULL);
+  TSReleaseAssert(*epic_prefix != '\0');
+
+  sample.sample_time = time(NULL);
+  debug("%s/trafficserver.%lld.%llu", epic_prefix, (long long) sample.sample_time, (unsigned long long) getpid());
+  if (gethostname(sample.sample_host, sizeof(sample.sample_host)) == -1) {
+    error("gethostname() failed: %s", strerror(errno));
+    strncpy(sample.sample_host, "unknown", sizeof(sample.sample_host));
+  }
+
+  snprintf(path, sizeof(path), "%s/trafficserver.%lld.%llu",
+           epic_prefix, (long long) sample.sample_time, (unsigned long long) getpid());
+
+  // XXX track the file size and preallocate ...
+
+  sample.sample_fp = fopen(path, "w");
+  if (sample.sample_fp == NULL) {
+    error("failed to create %s: %s", path, strerror(errno));
+    return 0;
+  }
+
+  TSRecordDump(TS_RECORDTYPE_PLUGIN | TS_RECORDTYPE_NODE | TS_RECORDTYPE_PROCESS, epic_write_stats, &sample);
+
+  if (fclose(sample.sample_fp) == -1) {
+    error("fclose() failed: %s", strerror(errno));
+  }
+
+  return 0;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  static const struct option longopts[] = {
+    {"directory", required_argument, NULL, 'd'},
+    {"period", required_argument, NULL, 'p'},
+    {NULL, 0, NULL, 0}
+  };
+
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name = (char *) "epic";
+  info.vendor_name = (char *) "Apache Software Foundation";
+  info.support_email = (char *) "dev@trafficserver.apache.org";
+
+  if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
+    error("plugin registration failed");
+  }
+
+  for (;;) {
+    int opt = getopt_long(argc, (char *const *) argv, "p:d:", longopts, NULL);
+
+    if (opt == -1) {
+      break;                    /* done */
+    }
+
+    switch (opt) {
+    case 'd':
+      epic_prefix = strdup(optarg);
+      break;
+    case 'p':
+      epic_period = atoi(optarg);
+      break;
+    default:
+      error("usage: epic.so [--directory PATH] [--period SECS]");
+    }
+  }
+
+  debug("initialized plugin with directory %s and period %d sec", epic_prefix, (int) epic_period);
+  TSContScheduleEvery(TSContCreate(epic_flush_stats, NULL), epic_period * 1000ll, TS_THREAD_POOL_TASK);
+}