TS-2714: promote the tcp_info plugin to stable as 'tcpinfo'
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/86453b99 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/86453b99 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/86453b99 Branch: refs/heads/5.0.x Commit: 86453b9930827d69e6647770b4073a74a71312ed Parents: d461c33 Author: James Peach <[email protected]> Authored: Mon Apr 14 11:56:05 2014 -0700 Committer: James Peach <[email protected]> Committed: Wed Apr 16 10:29:11 2014 -0700 ---------------------------------------------------------------------- CHANGES | 2 + configure.ac | 2 +- doc/reference/plugins/index.en.rst | 1 + doc/reference/plugins/tcpinfo.en.rst | 122 ++++++ plugins/Makefile.am | 1 + plugins/experimental/Makefile.am | 1 - plugins/experimental/tcp_info/Makefile.am | 22 -- plugins/experimental/tcp_info/README | 76 ---- plugins/experimental/tcp_info/tcp_info.cc | 424 --------------------- plugins/experimental/tcp_info/tcp_info.config | 3 - plugins/tcpinfo/Makefile.am | 22 ++ plugins/tcpinfo/tcpinfo.cc | 424 +++++++++++++++++++++ 12 files changed, 573 insertions(+), 527 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/CHANGES ---------------------------------------------------------------------- diff --git a/CHANGES b/CHANGES index 932f614..b952ea0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ -*- coding: utf-8 -*- Changes with Apache Traffic Server 5.0.0 + *) [TS-2714] Promote the tcp_info plugin to stable as 'tcpinfo'. + *) [TS-2708] Refactor and modernize the tcp_info plugin. *) [TS-2555] Adding global plugin support to ts_lua plugin. http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index 2a19d01..bd3ddcf 100644 --- a/configure.ac +++ b/configure.ac @@ -1927,6 +1927,7 @@ AC_CONFIG_FILES([ plugins/libloader/Makefile plugins/regex_remap/Makefile plugins/stats_over_http/Makefile + plugins/tcpinfo/Makefile plugins/experimental/Makefile plugins/experimental/authproxy/Makefile plugins/experimental/background_fetch/Makefile @@ -1944,7 +1945,6 @@ AC_CONFIG_FILES([ plugins/experimental/rfc5861/Makefile plugins/experimental/s3_auth/Makefile plugins/experimental/spdy/Makefile - plugins/experimental/tcp_info/Makefile plugins/experimental/ts_lua/Makefile plugins/experimental/xdebug/Makefile proxy/Makefile http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/doc/reference/plugins/index.en.rst ---------------------------------------------------------------------- diff --git a/doc/reference/plugins/index.en.rst b/doc/reference/plugins/index.en.rst index 669b8d2..eff4c5b 100644 --- a/doc/reference/plugins/index.en.rst +++ b/doc/reference/plugins/index.en.rst @@ -46,6 +46,7 @@ Apache Traffic Server releases. header_rewrite.en regex_remap.en stats_over_http.en + tcpinfo.en Experimental plugins ==================== http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/doc/reference/plugins/tcpinfo.en.rst ---------------------------------------------------------------------- diff --git a/doc/reference/plugins/tcpinfo.en.rst b/doc/reference/plugins/tcpinfo.en.rst new file mode 100644 index 0000000..688fe45 --- /dev/null +++ b/doc/reference/plugins/tcpinfo.en.rst @@ -0,0 +1,122 @@ +.. _tcpinfo-plugin: + +TCPInfo 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. + +This global plugin logs TCP metrics at various points in the HTTP +processing pipeline. The TCP information is retrieved by the +:manpage:`getsockopt(2)` function using the ``TCP_INFO`` option. +This is only supported on systems that support the ``TCP_INFO`` +option, currently Linux and BSD. + +Log rolling is enabled automatically, based on the +:ts:cv:`proxy.config.log.rolling_size_mb` configuration variable. + +Plugin Options +-------------- + +The following options may be specified in :file:`plugin.config`: + +--hooks=LIST + This option specifies when TCP information should be logged. The + argument is a comma-separated list of the event names listed + below. TCP information will be sampled and logged each time the + specified set of events occurs. + + ============== =============================================== + Event Name Triggered when + ============== =============================================== + send_resp_hdr The server begins sending an HTTP response. + ssn_close The TCP connection closes. + ssn_start A new TCP connection is accepted. + txn_close A HTTP transaction is completed. + txn_start A HTTP transaction is initiated. + ============== =============================================== + +--log-file=NAME + This specifies the base name of the file where TCP information + should be logged. If this option is not specified, the name + ``tcpinfo`` is used. Traffic Server will automatically append + the ``.log`` suffix. + +--log-level=LEVEL + The log level can be either ``1`` to log only the round trip + time estimate, or ``2`` to log the complete set of TCP information. + + The following fields are logged when the log level is ``1``: + + ========== ================================================== + Field Name Description + ========== ================================================== + timestamp Event timestamp + event Event name (one of the names listed above) + client Client IP address + server Server IP address + rtt Estimated round trip time + ========== ================================================== + + The following fields are logged when the log level is ``2``: + + ============== ================================================== + Field Name Description + ============== ================================================== + timestamp Event timestamp + event Event name (one of the names listed above) + client Client IP address + server Server IP address + rtt Estimated round trip time + rttvar + last_sent + last_recv + snd_ssthresh + rcv_ssthresh + unacked + sacked + lost + retrans + fackets + ============== ================================================== + +--sample-rate=COUNT + + This is the number of times per 1000 requests that the data will + be logged. A pseudo-random number generator is used to determine if a + request will be logged. The default value is 1000 and this option is + not required to be in the configuration file. To achieve a log rate + of 1% you would set this value to 10. + +Examples: +--------- + +This example logs the simple TCP information to ``jpeach.log`` +at the start of a TCP connection and once for each HTTP +transaction thereafter:: + + tcp_info.so --log-file=jpeach --log-level=1 --hooks=ssn_start,txn_start + +The file ``jpeach.log`` will contain the following log format:: + + timestamp event client server rtt + 20140414.17h40m14s ssn_start 127.0.0.1 127.0.0.1 4000 + 20140414.17h40m14s txn_start 127.0.0.1 127.0.0.1 4000 + 20140414.17h40m16s ssn_start 127.0.0.1 127.0.0.1 4000 + 20140414.17h40m16s txn_start 127.0.0.1 127.0.0.1 4000 + 20140414.17h40m16s ssn_start 127.0.0.1 127.0.0.1 4000 + 20140414.17h40m16s txn_start 127.0.0.1 127.0.0.1 4000 http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 6e00ec5..a810f8a 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -24,4 +24,5 @@ SUBDIRS = \ libloader \ regex_remap \ stats_over_http \ + tcpinfo \ experimental http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/experimental/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am index 9aac6ad..4b811c1 100644 --- a/plugins/experimental/Makefile.am +++ b/plugins/experimental/Makefile.am @@ -33,7 +33,6 @@ SUBDIRS = \ rfc5861 \ s3_auth \ spdy \ - tcp_info \ ts_lua \ xdebug http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/experimental/tcp_info/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/tcp_info/Makefile.am b/plugins/experimental/tcp_info/Makefile.am deleted file mode 100644 index daabefd..0000000 --- a/plugins/experimental/tcp_info/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# 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 = tcp_info.la -tcp_info_la_SOURCES = tcp_info.cc -tcp_info_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) - http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/experimental/tcp_info/README ---------------------------------------------------------------------- diff --git a/plugins/experimental/tcp_info/README b/plugins/experimental/tcp_info/README deleted file mode 100644 index 56c911f..0000000 --- a/plugins/experimental/tcp_info/README +++ /dev/null @@ -1,76 +0,0 @@ -Apache Traffic Server TCP info plugin - -This plugin was written to log client round trip times. This -information is retrieved from the getsockopt() function using the -TCP_INFO option. Other information can also be gathered from TCP_INFO -and there is an option to log most of the data structure. - - -Configuration: -The configuration files default location is in the etc or -configuration directory under the default install directory. For -example if your installation directory is "/usr/local", then the -plugin will first look in "/usr/local/etc" and then in -"/usr/local/conf" for the configuration file. The configuration -filename is "tcp_info.config". - - --------------------------------------------------------------------------------- -Configuration Options: - -"sample" is the number of times per 1000 requests that the data will -be logged. A pseudo-random number generator is used to determine if a -request will be logged. The default value is 1000 and this option is -not required to be in the configuration file. To achieve a log rate -of 1% you would set this value to 10. -example: -sample=1000 - -"log_file" is the full path to the log file. There is no default -value and this option is required to be set in the config file. If -the plugin can't open the log file the plugin will assert. -example: -log_file=/usr/local/var/log/trafficserver/tcp_info.log - -"log_level" determines how much information you want in the log file. -The default is 1 and this option is not required to be in the config -file. Log level of 1 will only log the rtt value from the TCP_INFO -data structure. To log most of the TCP_INFO data structure use the -value of 2. -example: -log_level=1 - - -*** "hook" DOESN'T WORK RIGHT NOW - ATS NEEDS ANOTHER HOOK BEFORE CLOSING THE SOCKET *** -"hook" tells the plugin when to log the information. The default is 1 -and this will log the TCP_INFO data when the client makes a TCP -connection. A value of 2 will log the information at the end of the -TCP connection. If you are looking for information about retransmit -or lost, then you most likely would want to set this to 2. This -configuration option is not required to be in the config file. -example: -hook=1 - --------------------------------------------------------------------------------- -Log Format: - -log_level=1 -[unix seconds] [client ip] [server ip] [rtt] -example: -1344483780 192.168.1.12 192.168.1.1 1875 - -log_level=2 -[unix seconds] [microseconds] [client ip] [server ip] [last_data_sent] -[last_data_recv] [snd_cwnd] [snd_ssthresh] [rcv_ssthresh] [rtt] -[rttvar] [unacked] [sacked] [lost] [retrans] [fackets] - - -example: -1344483817 281068 192.168.1.12 192.168.1.1 2 2 10 2147483647 32768 3875 4500 0 0 0 0 0 - --------------------------------------------------------------------------------- -Version 1.0.1 (8/9/2012, bcall) - * Documentation and code cleanup - -Version 1.0.0 (8/8/2012, bcall) - * Initial Version http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/experimental/tcp_info/tcp_info.cc ---------------------------------------------------------------------- diff --git a/plugins/experimental/tcp_info/tcp_info.cc b/plugins/experimental/tcp_info/tcp_info.cc deleted file mode 100644 index e828ded..0000000 --- a/plugins/experimental/tcp_info/tcp_info.cc +++ /dev/null @@ -1,424 +0,0 @@ -/** @file - - tcp_info: A plugin to log TCP session information. - - @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. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <ts/ts.h> -#include <unistd.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <getopt.h> -#include <fcntl.h> -#include <limits.h> -#include <string.h> -#include <errno.h> -#include <sys/time.h> -#include <arpa/inet.h> - -#include "ink_defs.h" - -#if defined(TCP_INFO) && defined(HAVE_STRUCT_TCP_INFO) -#define TCPI_PLUGIN_SUPPORTED 1 -#endif - -#define TCPI_HOOK_SSN_START 0x01u -#define TCPI_HOOK_TXN_START 0x02u -#define TCPI_HOOK_SEND_RESPONSE 0x04u -#define TCPI_HOOK_SSN_CLOSE 0x08u -#define TCPI_HOOK_TXN_CLOSE 0x10u - -// Log format headers. These are emitted once at the start of a log file. Note that we -// carefully order the fields so the field ordering is compatible. This lets you change -// the verbosity without breaking a perser that is moderately robust. -static const char * tcpi_headers[] = { - "timestamp event client server rtt", - "timestamp event client server rtt rttvar last_sent last_recv " - "snd_ssthresh rcv_ssthresh unacked sacked lost retrans fackets", -}; - -struct Config { - int sample; - unsigned log_level; - TSTextLogObject log; - - Config() : sample(1000), log_level(1), log(NULL) { - } - - ~Config() { - if (log) { - TSTextLogObjectDestroy(log); - } - } -}; - -union const_sockaddr_ptr { - const struct sockaddr * sa; - const struct sockaddr_in * in; - const struct sockaddr_in6 * in6; - - const void * addr() const { - switch (sa->sa_family) { - case AF_INET: return &(in->sin_addr); - case AF_INET6: return &(in6->sin6_addr); - default: return NULL; - } - } - -}; - -#if TCPI_PLUGIN_SUPPORTED - -static void -log_tcp_info(Config * config, const char * event_name, TSHttpSsn ssnp) -{ - char client_str[INET6_ADDRSTRLEN]; - char server_str[INET6_ADDRSTRLEN]; - const_sockaddr_ptr client_addr; - const_sockaddr_ptr server_addr; - - struct tcp_info info; - socklen_t tcp_info_len = sizeof(info); - int fd; - - TSReleaseAssert(config->log != NULL); - - if (TSHttpSsnClientFdGet(ssnp, &fd) != TS_SUCCESS) { - TSDebug("tcp_info", "error getting the client socket fd"); - return; - } - - if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &tcp_info_len) != 0) { - TSDebug("tcp_info", "getsockopt(%d, TCP_INFO) failed: %s", fd, strerror(errno)); - return; - } - - client_addr.sa = TSHttpSsnClientAddrGet(ssnp); - server_addr.sa = TSHttpSsnIncomingAddrGet(ssnp); - - if (client_addr.sa == NULL || server_addr.sa == NULL) { - return; - } - - // convert ip to string - inet_ntop(client_addr.sa->sa_family, client_addr.addr(), client_str, sizeof(client_str)); - inet_ntop(server_addr.sa->sa_family, server_addr.addr(), server_str, sizeof(server_str)); - - TSReturnCode ret; - - if (config->log_level == 2) { -#if !defined(freebsd) || defined(__GLIBC__) - ret = TSTextLogObjectWrite(config->log, "%s %s %s %u %u %u %u %u %u %u %u %u %u %u %u", - event_name, - client_str, - server_str, - info.tcpi_rtt, - info.tcpi_rttvar, - info.tcpi_last_data_sent, - info.tcpi_last_data_recv, - info.tcpi_snd_cwnd, - info.tcpi_snd_ssthresh, - info.tcpi_rcv_ssthresh, - info.tcpi_unacked, - info.tcpi_sacked, - info.tcpi_lost, - info.tcpi_retrans, - info.tcpi_fackets); -#else - ret = TSTextLogObjectWrite(config->log, "%s %s %s %u %u %u %u %u %u %u %u %u %u %u %u", - event_name, - client_str, - server_str, - info.tcpi_rtt, - info.tcpi_rttvar, - info.__tcpi_last_data_sent, - info.tcpi_last_data_recv, - info.tcpi_snd_cwnd, - info.tcpi_snd_ssthresh, - info.__tcpi_rcv_ssthresh, - info.__tcpi_unacked, - info.__tcpi_sacked, - info.__tcpi_lost, - info.__tcpi_retrans, - info.__tcpi_fackets); -#endif - } else { - ret = TSTextLogObjectWrite(config->log, "%s %s %s %u", - event_name, - client_str, - server_str, - info.tcpi_rtt); - } - - // It's really not clear how we should handle logging failures. It a failure transient - // or persistent? Should we try to re-open the logs? How frequently should we do that? - if (ret != TS_SUCCESS) { - TSError("[tcp_info] log write failed, disabling logging"); - TSTextLogObjectDestroy(config->log); - config->log = NULL; - } -} - -#else /* TCPI_PLUGIN_SUPPORTED */ - -static void -log_tcp_info(Config * /* config */, const char * /* event_name */, TSHttpSsn /* ssnp */) -{ - return; // TCP metrics not supported. -} - -#endif /* TCPI_PLUGIN_SUPPORTED */ - -static int -tcp_info_hook(TSCont contp, TSEvent event, void *edata) -{ - TSHttpSsn ssnp = NULL; - TSHttpTxn txnp = NULL; - int random = 0; - Config * config = (Config *)TSContDataGet(contp); - - const char *event_name; - switch (event) { - case TS_EVENT_HTTP_SSN_START: - ssnp = (TSHttpSsn)edata; - event_name = "ssn_start"; - break; - case TS_EVENT_HTTP_TXN_START: - txnp = (TSHttpTxn)edata; - ssnp = TSHttpTxnSsnGet(txnp); - event_name = "txn_start"; - break; - case TS_EVENT_HTTP_SEND_RESPONSE_HDR: - txnp = (TSHttpTxn)edata; - ssnp = TSHttpTxnSsnGet(txnp); - event_name = "send_resp_hdr"; - break; - case TS_EVENT_HTTP_SSN_CLOSE: - ssnp = (TSHttpSsn)edata; - event_name = "ssn_close"; - default: - return 0; - } - - TSDebug("tcp_info", "logging hook called for %s (%s) with log object %p", - TSHttpEventNameLookup(event), event_name, config->log); - - if (config->log == NULL) { - goto done; - } - - // no need to run rand if we are always going log (100%) - if (config->sample < 1000) { - random = rand() % 1000; - TSDebug("tcp_info", "random: %d, config->sample: %d", random, config->sample); - } - - if (random < config->sample) { - TSDebug("tcp_info", "sampling TCP metrics for %s event", event_name); - log_tcp_info(config, event_name, ssnp); - } - -done: - if (txnp != NULL) { - TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); - } else if (ssnp != NULL) { - TSHttpSsnReenable(ssnp, TS_EVENT_HTTP_CONTINUE); - } - - return TS_EVENT_NONE; -} - -static bool -parse_unsigned(const char * str, unsigned long& lval) -{ - char * end = NULL; - - if (*str == '\0') { - return false; - } - - lval = strtoul(str, &end, 0 /* base */); - if (end == str) { - // No valid characters. - return false; - } - - if (end && *end != '\0') { - // Not all charaters consumed. - return false; - } - - return true; -} - -// Parse a comma-separated list of hook names into a hook bitmask. -static unsigned -parse_hook_list(const char * hook_list) -{ - unsigned mask = 0; - char * tok; - char * str; - char * last; - - const struct hookmask { const char * name; unsigned mask; } hooks[] = { - { "ssn_start", TCPI_HOOK_SSN_START }, - { "txn_start", TCPI_HOOK_TXN_START }, - { "send_resp_hdr", TCPI_HOOK_SEND_RESPONSE }, - { "ssn_close", TCPI_HOOK_SSN_CLOSE }, - { "txn_close", TCPI_HOOK_TXN_CLOSE }, - { NULL, 0u } - }; - - str = TSstrdup(hook_list); - - for (tok = strtok_r(str, ",", &last); tok; tok = strtok_r(NULL, ",", &last)) { - bool match = false; - - for (const struct hookmask * m = hooks; m->name != NULL; ++m) { - if (strcmp(m->name, tok) == 0) { - mask |= m->mask; - match = true; - break; - } - } - - if (!match) { - TSError("[tcp_info] invalid hook name '%s'", tok); - } - } - - TSfree(str); - return mask; -} - -void -TSPluginInit(int argc, const char * argv[]) -{ - static const char usage[] = "tcp_info.so [--log-file=PATH] [--log-level=LEVEL] [--hooks=LIST] [--sample-rate=COUNT]"; - static const struct option longopts[] = { - { const_cast<char *>("sample-rate"), required_argument, NULL, 'r' }, - { const_cast<char *>("log-file"), required_argument, NULL, 'f' }, - { const_cast<char *>("log-level"), required_argument, NULL, 'l' }, - { const_cast<char *>("hooks"), required_argument, NULL, 'h' }, - { NULL, 0, NULL, 0 } - }; - - TSPluginRegistrationInfo info; - Config * config = new Config(); - const char * filename = "tcp_info"; - TSCont cont; - unsigned hooks = 0; - - info.plugin_name = (char*)"tcp_info"; - info.vendor_name = (char*)"Apache Software Foundation"; - info.support_email = (char*)"[email protected]"; - - if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) { - TSError("tcp_info: plugin registration failed"); - } - - optind = 0; - for (;;) { - unsigned long lval; - - switch (getopt_long(argc, (char * const *)argv, "r:f:l:h:", longopts, NULL)) { - case 'r': - if (parse_unsigned(optarg, lval)) { - config->sample = atoi(optarg); - } else { - TSError("[tcp_info] invalid sample rate '%s'", optarg); - } - break; - case 'f': - filename = optarg; - break; - case 'l': - if (parse_unsigned(optarg, lval) && (lval <= countof(tcpi_headers))) { - config->log_level = lval; - } else { - TSError("[tcp_info] invalid log level '%s'", optarg); - } - break; - case 'h': - hooks = parse_hook_list(optarg); - break; - case -1: - goto init; - default: - TSError("[tcp_info] usage: %s", usage); - } - } - -init: - -#if !TCPI_PLUGIN_SUPPORTED - TSError("[tcp_info] TCP metrics are not supported on this platform"); -#endif - - TSDebug("tcp_info", "sample: %d", config->sample); - TSDebug("tcp_info", "log filename: %s", filename); - TSDebug("tcp_info", "log_level: %u", config->log_level); - TSDebug("tcp_info", "hook mask: 0x%x", hooks); - - if (TSTextLogObjectCreate(filename, TS_LOG_MODE_ADD_TIMESTAMP, &config->log) != TS_SUCCESS) { - TSError("[tcp_info] failed to create log file '%s'", filename); - delete config; - return; - } - - TSTextLogObjectHeaderSet(config->log, tcpi_headers[config->log_level - 1]); - - // Enable log rolling. Unless we specify an explicit rolling period, the log will - // be rolled based on the size specified in proxy.config.log.rolling_size_mb. - TSTextLogObjectRollingEnabledSet(config->log, 1 /* rolling_enabled */); - - cont = TSContCreate(tcp_info_hook, NULL); - TSContDataSet(cont, config); - - if (hooks & TCPI_HOOK_SSN_START) { - TSHttpHookAdd(TS_HTTP_SSN_START_HOOK, cont); - TSDebug("tcp_info", "added hook to the start of the TCP connection"); - } - - if (hooks & TCPI_HOOK_TXN_START) { - TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, cont); - TSDebug("tcp_info", "added hook to the close of the transaction"); - } - - if (hooks & TCPI_HOOK_SEND_RESPONSE) { - TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, cont); - TSDebug("tcp_info", "added hook to the sending of the headers"); - } - - if (hooks & TCPI_HOOK_SSN_CLOSE) { - TSHttpHookAdd(TS_HTTP_SSN_CLOSE_HOOK, cont); - TSDebug("tcp_info", "added hook to the close of the TCP connection"); - } - - if (hooks & TCPI_HOOK_TXN_CLOSE) { - TSHttpHookAdd(TS_HTTP_TXN_CLOSE_HOOK, cont); - TSDebug("tcp_info", "added hook to the close of the transaction"); - } - -} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/experimental/tcp_info/tcp_info.config ---------------------------------------------------------------------- diff --git a/plugins/experimental/tcp_info/tcp_info.config b/plugins/experimental/tcp_info/tcp_info.config deleted file mode 100644 index 52321de..0000000 --- a/plugins/experimental/tcp_info/tcp_info.config +++ /dev/null @@ -1,3 +0,0 @@ -log_file=/usr/local/var/log/trafficserver/tcp_info.log -sample=1000 -log_level=1 http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/tcpinfo/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/tcpinfo/Makefile.am b/plugins/tcpinfo/Makefile.am new file mode 100644 index 0000000..ae1078b --- /dev/null +++ b/plugins/tcpinfo/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 = tcpinfo.la +tcpinfo_la_SOURCES = tcpinfo.cc +tcpinfo_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/86453b99/plugins/tcpinfo/tcpinfo.cc ---------------------------------------------------------------------- diff --git a/plugins/tcpinfo/tcpinfo.cc b/plugins/tcpinfo/tcpinfo.cc new file mode 100644 index 0000000..cad921e --- /dev/null +++ b/plugins/tcpinfo/tcpinfo.cc @@ -0,0 +1,424 @@ +/** @file + + tcpinfo: A plugin to log TCP session information. + + @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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ts/ts.h> +#include <unistd.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <getopt.h> +#include <fcntl.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <sys/time.h> +#include <arpa/inet.h> + +#include "ink_defs.h" + +#if defined(TCP_INFO) && defined(HAVE_STRUCT_TCP_INFO) +#define TCPI_PLUGIN_SUPPORTED 1 +#endif + +#define TCPI_HOOK_SSN_START 0x01u +#define TCPI_HOOK_TXN_START 0x02u +#define TCPI_HOOK_SEND_RESPONSE 0x04u +#define TCPI_HOOK_SSN_CLOSE 0x08u +#define TCPI_HOOK_TXN_CLOSE 0x10u + +// Log format headers. These are emitted once at the start of a log file. Note that we +// carefully order the fields so the field ordering is compatible. This lets you change +// the verbosity without breaking a perser that is moderately robust. +static const char * tcpi_headers[] = { + "timestamp event client server rtt", + "timestamp event client server rtt rttvar last_sent last_recv " + "snd_ssthresh rcv_ssthresh unacked sacked lost retrans fackets", +}; + +struct Config { + int sample; + unsigned log_level; + TSTextLogObject log; + + Config() : sample(1000), log_level(1), log(NULL) { + } + + ~Config() { + if (log) { + TSTextLogObjectDestroy(log); + } + } +}; + +union const_sockaddr_ptr { + const struct sockaddr * sa; + const struct sockaddr_in * in; + const struct sockaddr_in6 * in6; + + const void * addr() const { + switch (sa->sa_family) { + case AF_INET: return &(in->sin_addr); + case AF_INET6: return &(in6->sin6_addr); + default: return NULL; + } + } + +}; + +#if TCPI_PLUGIN_SUPPORTED + +static void +log_tcp_info(Config * config, const char * event_name, TSHttpSsn ssnp) +{ + char client_str[INET6_ADDRSTRLEN]; + char server_str[INET6_ADDRSTRLEN]; + const_sockaddr_ptr client_addr; + const_sockaddr_ptr server_addr; + + struct tcp_info info; + socklen_t tcp_info_len = sizeof(info); + int fd; + + TSReleaseAssert(config->log != NULL); + + if (TSHttpSsnClientFdGet(ssnp, &fd) != TS_SUCCESS) { + TSDebug("tcpinfo", "error getting the client socket fd"); + return; + } + + if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &tcp_info_len) != 0) { + TSDebug("tcpinfo", "getsockopt(%d, TCP_INFO) failed: %s", fd, strerror(errno)); + return; + } + + client_addr.sa = TSHttpSsnClientAddrGet(ssnp); + server_addr.sa = TSHttpSsnIncomingAddrGet(ssnp); + + if (client_addr.sa == NULL || server_addr.sa == NULL) { + return; + } + + // convert ip to string + inet_ntop(client_addr.sa->sa_family, client_addr.addr(), client_str, sizeof(client_str)); + inet_ntop(server_addr.sa->sa_family, server_addr.addr(), server_str, sizeof(server_str)); + + TSReturnCode ret; + + if (config->log_level == 2) { +#if !defined(freebsd) || defined(__GLIBC__) + ret = TSTextLogObjectWrite(config->log, "%s %s %s %u %u %u %u %u %u %u %u %u %u %u %u", + event_name, + client_str, + server_str, + info.tcpi_rtt, + info.tcpi_rttvar, + info.tcpi_last_data_sent, + info.tcpi_last_data_recv, + info.tcpi_snd_cwnd, + info.tcpi_snd_ssthresh, + info.tcpi_rcv_ssthresh, + info.tcpi_unacked, + info.tcpi_sacked, + info.tcpi_lost, + info.tcpi_retrans, + info.tcpi_fackets); +#else + ret = TSTextLogObjectWrite(config->log, "%s %s %s %u %u %u %u %u %u %u %u %u %u %u %u", + event_name, + client_str, + server_str, + info.tcpi_rtt, + info.tcpi_rttvar, + info.__tcpi_last_data_sent, + info.tcpi_last_data_recv, + info.tcpi_snd_cwnd, + info.tcpi_snd_ssthresh, + info.__tcpi_rcv_ssthresh, + info.__tcpi_unacked, + info.__tcpi_sacked, + info.__tcpi_lost, + info.__tcpi_retrans, + info.__tcpi_fackets); +#endif + } else { + ret = TSTextLogObjectWrite(config->log, "%s %s %s %u", + event_name, + client_str, + server_str, + info.tcpi_rtt); + } + + // It's really not clear how we should handle logging failures. It a failure transient + // or persistent? Should we try to re-open the logs? How frequently should we do that? + if (ret != TS_SUCCESS) { + TSError("[tcpinfo] log write failed, disabling logging"); + TSTextLogObjectDestroy(config->log); + config->log = NULL; + } +} + +#else /* TCPI_PLUGIN_SUPPORTED */ + +static void +log_tcp_info(Config * /* config */, const char * /* event_name */, TSHttpSsn /* ssnp */) +{ + return; // TCP metrics not supported. +} + +#endif /* TCPI_PLUGIN_SUPPORTED */ + +static int +tcp_info_hook(TSCont contp, TSEvent event, void *edata) +{ + TSHttpSsn ssnp = NULL; + TSHttpTxn txnp = NULL; + int random = 0; + Config * config = (Config *)TSContDataGet(contp); + + const char *event_name; + switch (event) { + case TS_EVENT_HTTP_SSN_START: + ssnp = (TSHttpSsn)edata; + event_name = "ssn_start"; + break; + case TS_EVENT_HTTP_TXN_START: + txnp = (TSHttpTxn)edata; + ssnp = TSHttpTxnSsnGet(txnp); + event_name = "txn_start"; + break; + case TS_EVENT_HTTP_SEND_RESPONSE_HDR: + txnp = (TSHttpTxn)edata; + ssnp = TSHttpTxnSsnGet(txnp); + event_name = "send_resp_hdr"; + break; + case TS_EVENT_HTTP_SSN_CLOSE: + ssnp = (TSHttpSsn)edata; + event_name = "ssn_close"; + default: + return 0; + } + + TSDebug("tcpinfo", "logging hook called for %s (%s) with log object %p", + TSHttpEventNameLookup(event), event_name, config->log); + + if (config->log == NULL) { + goto done; + } + + // no need to run rand if we are always going log (100%) + if (config->sample < 1000) { + random = rand() % 1000; + TSDebug("tcpinfo", "random: %d, config->sample: %d", random, config->sample); + } + + if (random < config->sample) { + TSDebug("tcpinfo", "sampling TCP metrics for %s event", event_name); + log_tcp_info(config, event_name, ssnp); + } + +done: + if (txnp != NULL) { + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + } else if (ssnp != NULL) { + TSHttpSsnReenable(ssnp, TS_EVENT_HTTP_CONTINUE); + } + + return TS_EVENT_NONE; +} + +static bool +parse_unsigned(const char * str, unsigned long& lval) +{ + char * end = NULL; + + if (*str == '\0') { + return false; + } + + lval = strtoul(str, &end, 0 /* base */); + if (end == str) { + // No valid characters. + return false; + } + + if (end && *end != '\0') { + // Not all charaters consumed. + return false; + } + + return true; +} + +// Parse a comma-separated list of hook names into a hook bitmask. +static unsigned +parse_hook_list(const char * hook_list) +{ + unsigned mask = 0; + char * tok; + char * str; + char * last; + + const struct hookmask { const char * name; unsigned mask; } hooks[] = { + { "ssn_start", TCPI_HOOK_SSN_START }, + { "txn_start", TCPI_HOOK_TXN_START }, + { "send_resp_hdr", TCPI_HOOK_SEND_RESPONSE }, + { "ssn_close", TCPI_HOOK_SSN_CLOSE }, + { "txn_close", TCPI_HOOK_TXN_CLOSE }, + { NULL, 0u } + }; + + str = TSstrdup(hook_list); + + for (tok = strtok_r(str, ",", &last); tok; tok = strtok_r(NULL, ",", &last)) { + bool match = false; + + for (const struct hookmask * m = hooks; m->name != NULL; ++m) { + if (strcmp(m->name, tok) == 0) { + mask |= m->mask; + match = true; + break; + } + } + + if (!match) { + TSError("[tcpinfo] invalid hook name '%s'", tok); + } + } + + TSfree(str); + return mask; +} + +void +TSPluginInit(int argc, const char * argv[]) +{ + static const char usage[] = "tcpinfo.so [--log-file=PATH] [--log-level=LEVEL] [--hooks=LIST] [--sample-rate=COUNT]"; + static const struct option longopts[] = { + { const_cast<char *>("sample-rate"), required_argument, NULL, 'r' }, + { const_cast<char *>("log-file"), required_argument, NULL, 'f' }, + { const_cast<char *>("log-level"), required_argument, NULL, 'l' }, + { const_cast<char *>("hooks"), required_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + TSPluginRegistrationInfo info; + Config * config = new Config(); + const char * filename = "tcpinfo"; + TSCont cont; + unsigned hooks = 0; + + info.plugin_name = (char*)"tcpinfo"; + info.vendor_name = (char*)"Apache Software Foundation"; + info.support_email = (char*)"[email protected]"; + + if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) { + TSError("[tcpinfo] plugin registration failed"); + } + + optind = 0; + for (;;) { + unsigned long lval; + + switch (getopt_long(argc, (char * const *)argv, "r:f:l:h:", longopts, NULL)) { + case 'r': + if (parse_unsigned(optarg, lval)) { + config->sample = atoi(optarg); + } else { + TSError("[tcpinfo] invalid sample rate '%s'", optarg); + } + break; + case 'f': + filename = optarg; + break; + case 'l': + if (parse_unsigned(optarg, lval) && (lval <= countof(tcpi_headers))) { + config->log_level = lval; + } else { + TSError("[tcpinfo] invalid log level '%s'", optarg); + } + break; + case 'h': + hooks = parse_hook_list(optarg); + break; + case -1: + goto init; + default: + TSError("[tcpinfo] usage: %s", usage); + } + } + +init: + +#if !TCPI_PLUGIN_SUPPORTED + TSError("[tcpinfo] TCP metrics are not supported on this platform"); +#endif + + TSDebug("tcpinfo", "sample: %d", config->sample); + TSDebug("tcpinfo", "log filename: %s", filename); + TSDebug("tcpinfo", "log_level: %u", config->log_level); + TSDebug("tcpinfo", "hook mask: 0x%x", hooks); + + if (TSTextLogObjectCreate(filename, TS_LOG_MODE_ADD_TIMESTAMP, &config->log) != TS_SUCCESS) { + TSError("[tcpinfo] failed to create log file '%s'", filename); + delete config; + return; + } + + TSTextLogObjectHeaderSet(config->log, tcpi_headers[config->log_level - 1]); + + // Enable log rolling. Unless we specify an explicit rolling period, the log will + // be rolled based on the size specified in proxy.config.log.rolling_size_mb. + TSTextLogObjectRollingEnabledSet(config->log, 1 /* rolling_enabled */); + + cont = TSContCreate(tcp_info_hook, NULL); + TSContDataSet(cont, config); + + if (hooks & TCPI_HOOK_SSN_START) { + TSHttpHookAdd(TS_HTTP_SSN_START_HOOK, cont); + TSDebug("tcpinfo", "added hook to the start of the TCP connection"); + } + + if (hooks & TCPI_HOOK_TXN_START) { + TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, cont); + TSDebug("tcpinfo", "added hook to the close of the transaction"); + } + + if (hooks & TCPI_HOOK_SEND_RESPONSE) { + TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, cont); + TSDebug("tcpinfo", "added hook to the sending of the headers"); + } + + if (hooks & TCPI_HOOK_SSN_CLOSE) { + TSHttpHookAdd(TS_HTTP_SSN_CLOSE_HOOK, cont); + TSDebug("tcpinfo", "added hook to the close of the TCP connection"); + } + + if (hooks & TCPI_HOOK_TXN_CLOSE) { + TSHttpHookAdd(TS_HTTP_TXN_CLOSE_HOOK, cont); + TSDebug("tcpinfo", "added hook to the close of the transaction"); + } + +}
