From 6d431b703af830a1561f622e0fa0f9c457fc8681 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Wed, 10 Oct 2007 20:34:37 +0200 Subject: [PATCH] ipvs: Added plugin to collect IPVS connection statistics. This plugin collects detailed statistics for each IPVS service and it's destinations. It is based on ipvsadm and libipvs by Wensong Zhang and Peter Kese. The plugin requires a Linux (>= 2.6) kernel. See http://www.linuxvirtualserver.org/software/index.html for more details about IPVS. A new DS type "connections" (value:COUNTER:0:U) has been added to types.db. Signed-off-by: Sebastian Harl Signed-off-by: Florian Forster --- configure.in | 4 + src/Makefile.am | 8 ++ src/ipvs.c | 335 ++++++++++++++++++++++++++++++++++++++++++++++++ src/types.db | 1 + 4 files changed, 348 insertions(+) create mode 100644 src/ipvs.c diff --git a/configure.in b/configure.in index b0968737..75e9131a 100644 --- a/configure.in +++ b/configure.in @@ -1674,6 +1674,7 @@ plugin_df="no" plugin_disk="no" plugin_entropy="no" plugin_interface="no" +plugin_ipvs="no" plugin_irq="no" plugin_load="no" plugin_memory="no" @@ -1697,6 +1698,7 @@ then plugin_disk="yes" plugin_entropy="yes" plugin_interface="yes" + plugin_ipvs="yes" plugin_irq="yes" plugin_load="yes" plugin_memory="yes" @@ -1816,6 +1818,7 @@ AC_PLUGIN([exec], [yes], [Execution of external programs]) AC_PLUGIN([hddtemp], [yes], [Query hddtempd]) AC_PLUGIN([interface], [$plugin_interface], [Interface traffic statistics]) AC_PLUGIN([iptables], [$with_libiptc], [IPTables rule counters]) +AC_PLUGIN([ipvs], [$plugin_ipvs], [IPVS connection statistics]) AC_PLUGIN([irq], [$plugin_irq], [IRQ statistics]) AC_PLUGIN([load], [$plugin_load], [System load]) AC_PLUGIN([logfile], [yes], [File logging plugin]) @@ -1933,6 +1936,7 @@ Configuration: hddtemp . . . . . . $enable_hddtemp interface . . . . . $enable_interface iptables . . . . . $enable_iptables + ipvs . . . . . . . $enable_ipvs irq . . . . . . . . $enable_irq load . . . . . . . $enable_load logfile . . . . . . $enable_logfile diff --git a/src/Makefile.am b/src/Makefile.am index fe69e8c6..15e17086 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -260,6 +260,14 @@ collectd_LDADD += "-dlopen" iptables.la collectd_DEPENDENCIES += iptables.la endif +if BUILD_PLUGIN_IPVS +pkglib_LTLIBRARIES += ipvs.la +ipvs_la_SOURCES = ipvs.c +ipvs_la_LDFLAGS = -module -avoid-version +collectd_LDADD += "-dlopen" ipvs.la +collectd_DEPENDENCIES += ipvs.la +endif + if BUILD_PLUGIN_IRQ pkglib_LTLIBRARIES += irq.la irq_la_SOURCES = irq.c diff --git a/src/ipvs.c b/src/ipvs.c new file mode 100644 index 00000000..5755e06b --- /dev/null +++ b/src/ipvs.c @@ -0,0 +1,335 @@ +/** + * collectd - src/ipvs.c (based on ipvsadm and libipvs) + * Copyright (C) 1997 Steven Clarke + * Copyright (C) 1998-2004 Wensong Zhang + * Copyright (C) 2003-2004 Peter Kese + * Copyright (C) 2007 Sebastian Harl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Sebastian Harl + **/ + +/* + * This plugin collects statistics about IPVS connections. It requires Linux + * kernels >= 2.6. + * + * See http://www.linuxvirtualserver.org/software/index.html for more + * information about IPVS. + */ + +#include "collectd.h" +#include "plugin.h" +#include "common.h" + +#include +#include +#include + +/* this can probably only be found in the kernel sources */ +#include + +#define log_err(...) ERROR ("ipvs: " __VA_ARGS__) + + +/* + * private variables + */ + +static int sockfd = -1; +static void *ipvs_func = NULL; + +static struct ip_vs_getinfo ipvs_info; + + +/* + * libipvs API + */ + +static struct ip_vs_get_services *ipvs_get_services (void); +static struct ip_vs_get_dests *ipvs_get_dests (struct ip_vs_service_entry *); + +static const char *ipvs_strerror (int err) +{ + unsigned int i; + + struct { + void *func; + int err; + const char *message; + } table [] = { + { 0, EPERM, "Permission denied (you must be root)" }, + { 0, EINVAL, "Module is wrong version" }, + { 0, ENOPROTOOPT, "Protocol not available" }, + { 0, ENOMEM, "Memory allocation problem" }, + { ipvs_get_services, ESRCH, "No such service" }, + { ipvs_get_dests, ESRCH, "No such service" }, + }; + + for (i = 0; i < sizeof (table) / sizeof (table[0]); i++) { + if (((NULL == table[i].func) || (table[i].func == ipvs_func)) + && (table[i].err == err)) + return table[i].message; + } + return strerror (err); +} /* ipvs_strerror */ + +static struct ip_vs_get_services *ipvs_get_services (void) +{ + struct ip_vs_get_services *ret; + socklen_t len; + + len = sizeof (*ret) + + sizeof (struct ip_vs_service_entry) * ipvs_info.num_services; + + if (NULL == (ret = malloc (len))) { + log_err ("ipvs_get_services: Out of memory."); + exit (3); + } + + ipvs_func = ipvs_get_services; + + ret->num_services = ipvs_info.num_services; + + if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_SERVICES, + (void *)ret, &len)) { + log_err ("ipvs_get_services: getsockopt failed: %s", + ipvs_strerror (errno)); + + free(ret); + return NULL; + } + return ret; +} /* ipvs_get_services */ + +static struct ip_vs_get_dests *ipvs_get_dests (struct ip_vs_service_entry *se) +{ + struct ip_vs_get_dests *ret; + socklen_t len; + + len = sizeof (*ret) + sizeof (struct ip_vs_dest_entry) * se->num_dests; + + if (NULL == (ret = malloc (len))) { + log_err ("ipvs_get_dests: Out of memory."); + exit (3); + } + + ipvs_func = ipvs_get_dests; + + ret->fwmark = se->fwmark; + ret->protocol = se->protocol; + ret->addr = se->addr; + ret->port = se->port; + ret->num_dests = se->num_dests; + + if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_DESTS, + (void *)ret, &len)) { + log_err ("ipvs_get_dests: getsockopt() failed: %s", + ipvs_strerror (errno)); + free (ret); + return NULL; + } + return ret; +} /* ip_vs_get_dests */ + + +/* + * collectd plugin API and helper functions + */ + +static int cipvs_init (void) +{ + socklen_t len; + + len = sizeof (ipvs_info); + + if (-1 == (sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW))) { + log_err ("cipvs_init: socket() failed: %s", ipvs_strerror (errno)); + return -1; + } + + if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO, + (void *)&ipvs_info, &len)) { + log_err ("cipvs_init: getsockopt() failed: %s", ipvs_strerror (errno)); + return -1; + } + return 0; +} /* cipvs_init */ + +/* + * ipvs-_{UDP,TCP}/-total + * ipvs-_{UDP,TCP}/-_ + */ + +/* plugin instance */ +static int get_pi (struct ip_vs_service_entry *se, char *pi, size_t size) +{ + struct in_addr addr; + int len = 0; + + if ((NULL == se) || (NULL == pi)) + return 0; + + addr.s_addr = se->addr; + + /* inet_ntoa() returns a pointer to a statically allocated buffer + * I hope non-glibc systems behave the same */ + len = snprintf (pi, size, "%s_%s%u", inet_ntoa (addr), + (se->protocol == IPPROTO_TCP) ? "TCP" : "UDP", + ntohs (se->port)); + + if ((0 > len) || (size <= len)) { + log_err ("plugin instance truncated: %s", pi); + return -1; + } + return 0; +} /* get_pi */ + +/* type instance */ +static int get_ti (struct ip_vs_dest_entry *de, char *ti, size_t size) +{ + struct in_addr addr; + int len = 0; + + if ((NULL == de) || (NULL == ti)) + return 0; + + addr.s_addr = de->addr; + + /* inet_ntoa() returns a pointer to a statically allocated buffer + * I hope non-glibc systems behave the same */ + len = snprintf (ti, size, "%s_%u", inet_ntoa (addr), + ntohs (de->port)); + + if ((0 > len) || (size <= len)) { + log_err ("type instance truncated: %s", ti); + return -1; + } + return 0; +} /* get_ti */ + +static void cipvs_submit_connections (char *pi, char *ti, counter_t value) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + values[0].counter = value; + + vl.values = values; + vl.values_len = 1; + + vl.time = time (NULL); + vl.interval = interval_g; + + strcpy (vl.host, hostname_g); + strcpy (vl.plugin, "ipvs"); + strcpy (vl.plugin_instance, pi); + strcpy (vl.type_instance, (NULL != ti) ? ti : "total"); + + plugin_dispatch_values ("connections", &vl); + return; +} /* cipvs_submit_connections */ + +static void cipvs_submit_if (char *pi, char *t, char *ti, + counter_t rx, counter_t tx) +{ + value_t values[2]; + value_list_t vl = VALUE_LIST_INIT; + + values[0].counter = rx; + values[1].counter = tx; + + vl.values = values; + vl.values_len = 2; + + vl.time = time (NULL); + vl.interval = interval_g; + + strcpy (vl.host, hostname_g); + strcpy (vl.plugin, "ipvs"); + strcpy (vl.plugin_instance, pi); + strcpy (vl.type_instance, (NULL != ti) ? ti : "total"); + + plugin_dispatch_values (t, &vl); + return; +} /* cipvs_submit_if */ + +static void cipvs_submit_dest (char *pi, struct ip_vs_dest_entry *de) { + struct ip_vs_stats_user stats = de->stats; + + char ti[DATA_MAX_NAME_LEN]; + + if (0 != get_ti (de, ti, DATA_MAX_NAME_LEN)) + return; + + cipvs_submit_connections (pi, ti, stats.conns); + cipvs_submit_if (pi, "if_packets", ti, stats.inpkts, stats.outpkts); + cipvs_submit_if (pi, "if_octets", ti, stats.inbytes, stats.outbytes); + return; +} /* cipvs_submit_dest */ + +static void cipvs_submit_service (struct ip_vs_service_entry *se) +{ + struct ip_vs_stats_user stats = se->stats; + struct ip_vs_get_dests *dests = ipvs_get_dests (se); + + char pi[DATA_MAX_NAME_LEN]; + + int i = 0; + + if (0 != get_pi (se, pi, DATA_MAX_NAME_LEN)) + return; + + cipvs_submit_connections (pi, NULL, stats.conns); + cipvs_submit_if (pi, "if_packets", NULL, stats.inpkts, stats.outpkts); + cipvs_submit_if (pi, "if_octets", NULL, stats.inbytes, stats.outbytes); + + for (i = 0; i < dests->num_dests; ++i) + cipvs_submit_dest (pi, &dests->entrytable[i]); + return; +} /* cipvs_submit_service */ + +static int cipvs_read (void) +{ + struct ip_vs_get_services *services = NULL; + + int i = 0; + + if (NULL == (services = ipvs_get_services ())) + return -1; + + for (i = 0; i < services->num_services; ++i) + cipvs_submit_service (&services->entrytable[i]); + + free (services); + return 0; +} /* cipvs_read */ + +static int cipvs_shutdown (void) +{ + close (sockfd); + return 0; +} /* cipvs_shutdown */ + +void module_register (void) +{ + plugin_register_init ("ipvs", cipvs_init); + plugin_register_read ("ipvs", cipvs_read); + plugin_register_shutdown ("ipvs", cipvs_shutdown); + return; +} /* module_register */ + +/* vim: set sw=4 ts=4 tw=78 noexpandtab : */ + diff --git a/src/types.db b/src/types.db index 7973cd44..e53f92ff 100644 --- a/src/types.db +++ b/src/types.db @@ -4,6 +4,7 @@ apache_requests count:COUNTER:0:134217728 apache_scoreboard count:GAUGE:0:65535 bitrate value:GAUGE:0:4294967295 charge value:GAUGE:0:U +connections value:COUNTER:0:U counter value:COUNTER:U:U cpu value:COUNTER:0:4294967295 cpufreq value:GAUGE:0:U -- 2.30.2