From c7c01bfff595b6e61a3722f163cb4c0312df8d7a Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Wed, 6 Dec 2017 22:03:52 +0100 Subject: [PATCH] src/utils_taskstats.[ch]: Add library for Linux Delay Accounting. --- Makefile.am | 9 ++ src/utils_taskstats.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils_taskstats.h | 47 ++++++++ 3 files changed, 362 insertions(+) create mode 100644 src/utils_taskstats.c create mode 100644 src/utils_taskstats.h diff --git a/Makefile.am b/Makefile.am index ae027a36..def61ed6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1421,6 +1421,15 @@ python_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBPYTHON_CPPFLAGS) python_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBPYTHON_LDFLAGS) endif +if HAVE_LIBMNL +noinst_LTLIBRARIES += libtaskstats.la +libtaskstats_la_SOURCES = \ + src/utils_taskstats.c \ + src/utils_taskstats.h +libtaskstats_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBMNL_CFLAGS) +libtaskstats_la_LIBADD = $(BUILD_WITH_LIBMNL_LIBS) +endif + if BUILD_PLUGIN_PROCESSES pkglib_LTLIBRARIES += processes.la processes_la_SOURCES = src/processes.c diff --git a/src/utils_taskstats.c b/src/utils_taskstats.c new file mode 100644 index 00000000..f0d73334 --- /dev/null +++ b/src/utils_taskstats.c @@ -0,0 +1,306 @@ +/** + * collectd - src/utils_taskstats.c + * Copyright (C) 2017 Florian octo Forster + * + * ISC License (ISC) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Florian octo Forster + */ + +#include "collectd.h" +#include "utils_taskstats.h" + +#include "common.h" +#include "plugin.h" +#include "utils_time.h" + +#include +#include +#include + +struct ts_s { + struct mnl_socket *nl; + pid_t pid; + uint32_t seq; + uint16_t genl_id_taskstats; + unsigned int port_id; +}; + +/* nlmsg_errno returns the errno encoded in nlh or zero if not an error. */ +static int nlmsg_errno(struct nlmsghdr *nlh, size_t sz) { + if (!mnl_nlmsg_ok(nlh, (int)sz)) { + ERROR("utils_taskstats: mnl_nlmsg_ok failed."); + return EPROTO; + } + + if (nlh->nlmsg_type != NLMSG_ERROR) { + return 0; + } + + struct nlmsgerr *nlerr = mnl_nlmsg_get_payload(nlh); + /* (struct nlmsgerr).error holds a negative errno. */ + return nlerr->error * (-1); +} + +static int get_taskstats_attr_cb(const struct nlattr *attr, void *data) { + struct taskstats *ret_taskstats = data; + + uint16_t type = mnl_attr_get_type(attr); + switch (type) { + case TASKSTATS_TYPE_STATS: + if (mnl_attr_get_payload_len(attr) != sizeof(*ret_taskstats)) { + ERROR("utils_taskstats: mnl_attr_get_payload_len(attr) = %" PRIu32 + ", want %zu", + mnl_attr_get_payload_len(attr), sizeof(*ret_taskstats)); + return MNL_CB_ERROR; + } + struct taskstats *ts = mnl_attr_get_payload(attr); + memmove(ret_taskstats, ts, sizeof(*ret_taskstats)); + return MNL_CB_OK; + + case TASKSTATS_TYPE_AGGR_PID: /* fall through */ + case TASKSTATS_TYPE_AGGR_TGID: + return mnl_attr_parse_nested(attr, get_taskstats_attr_cb, ret_taskstats); + + case TASKSTATS_TYPE_PID: /* fall through */ + case TASKSTATS_TYPE_TGID: + /* ignore */ + return MNL_CB_OK; + + default: + DEBUG("utils_taskstats: unknown attribute %" PRIu16 + ", want one of TASKSTATS_TYPE_AGGR_PID/TGID, TASKSTATS_TYPE_STATS", + type); + } + return MNL_CB_OK; +} + +static int get_taskstats_msg_cb(const struct nlmsghdr *nlh, void *data) { + return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_taskstats_attr_cb, + data); +} + +static int get_taskstats(ts_t *ts, uint32_t tgid, + struct taskstats *ret_taskstats) { + char buffer[MNL_SOCKET_BUFFER_SIZE]; + uint32_t seq = ts->seq++; + + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buffer); + *nlh = (struct nlmsghdr){ + .nlmsg_len = nlh->nlmsg_len, + .nlmsg_type = ts->genl_id_taskstats, + .nlmsg_flags = NLM_F_REQUEST, + .nlmsg_seq = seq, + .nlmsg_pid = ts->pid, + }; + + struct genlmsghdr *genh = mnl_nlmsg_put_extra_header(nlh, sizeof(*genh)); + *genh = (struct genlmsghdr){ + .cmd = TASKSTATS_CMD_GET, + .version = TASKSTATS_GENL_VERSION, // or TASKSTATS_VERSION? + }; + + // mnl_attr_put_u32(nlh, TASKSTATS_CMD_ATTR_PID, tgid); + mnl_attr_put_u32(nlh, TASKSTATS_CMD_ATTR_TGID, tgid); + + if (mnl_socket_sendto(ts->nl, nlh, nlh->nlmsg_len) < 0) { + int status = errno; + ERROR("utils_taskstats: mnl_socket_sendto() = %s", STRERROR(status)); + return status; + } + + int status = mnl_socket_recvfrom(ts->nl, buffer, sizeof(buffer)); + if (status < 0) { + status = errno; + ERROR("utils_taskstats: mnl_socket_recvfrom() = %s", STRERROR(status)); + return status; + } else if (status == 0) { + ERROR("utils_taskstats: mnl_socket_recvfrom() = 0"); + return ECONNABORTED; + } + size_t buffer_size = (size_t)status; + + if ((status = nlmsg_errno((void *)buffer, buffer_size)) != 0) { + ERROR("utils_taskstats: TASKSTATS_CMD_GET(TASKSTATS_CMD_ATTR_TGID = " + "%" PRIu32 ") = %s", + (uint32_t)tgid, STRERROR(status)); + return status; + } + + status = mnl_cb_run(buffer, buffer_size, seq, ts->port_id, + get_taskstats_msg_cb, ret_taskstats); + if (status < MNL_CB_STOP) { + ERROR("utils_taskstats: Parsing message failed."); + return EPROTO; + } + + return 0; +} + +static int get_family_id_attr_cb(const struct nlattr *attr, void *data) { + uint16_t type = mnl_attr_get_type(attr); + if (type != CTRL_ATTR_FAMILY_ID) { + return MNL_CB_OK; + } + + if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) { + ERROR("mnl_attr_validate() = %s", STRERRNO); + return MNL_CB_ERROR; + } + + uint16_t *ret_family_id = data; + *ret_family_id = mnl_attr_get_u16(attr); + return MNL_CB_STOP; +} + +static int get_family_id_msg_cb(const struct nlmsghdr *nlh, void *data) { + return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, + data); +} + +/* get_family_id initializes ts->genl_id_taskstats. Returns 0 on success and + * an error code otherwise. */ +static int get_family_id(ts_t *ts) { + char buffer[MNL_SOCKET_BUFFER_SIZE]; + uint32_t seq = ts->seq++; + + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buffer); + *nlh = (struct nlmsghdr){ + .nlmsg_len = nlh->nlmsg_len, + .nlmsg_type = GENL_ID_CTRL, + .nlmsg_flags = NLM_F_REQUEST, + .nlmsg_seq = seq, + .nlmsg_pid = ts->pid, + }; + + struct genlmsghdr *genh = mnl_nlmsg_put_extra_header(nlh, sizeof(*genh)); + *genh = (struct genlmsghdr){ + .cmd = CTRL_CMD_GETFAMILY, .version = 0x01, + }; + + mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, TASKSTATS_GENL_NAME); + + assert(genh->cmd == CTRL_CMD_GETFAMILY); + assert(genh->version == TASKSTATS_GENL_VERSION); + + if (mnl_socket_sendto(ts->nl, nlh, nlh->nlmsg_len) < 0) { + int status = errno; + ERROR("utils_taskstats: mnl_socket_sendto() = %s", STRERROR(status)); + return status; + } + + ts->genl_id_taskstats = 0; + while (42) { + int status = mnl_socket_recvfrom(ts->nl, buffer, sizeof(buffer)); + if (status < 0) { + status = errno; + ERROR("utils_taskstats: mnl_socket_recvfrom() = %s", STRERROR(status)); + return status; + } else if (status == 0) { + break; + } + size_t buffer_size = (size_t)status; + + if ((status = nlmsg_errno((void *)buffer, buffer_size)) != 0) { + ERROR("utils_taskstats: CTRL_CMD_GETFAMILY(\"%s\"): %s", + TASKSTATS_GENL_NAME, STRERROR(status)); + return status; + } + + status = mnl_cb_run(buffer, buffer_size, seq, ts->port_id, + get_family_id_msg_cb, &ts->genl_id_taskstats); + if (status < MNL_CB_STOP) { + ERROR("utils_taskstats: Parsing message failed."); + return EPROTO; + } else if (status == MNL_CB_STOP) { + break; + } + } + + if (ts->genl_id_taskstats == 0) { + ERROR("utils_taskstats: Netlink communication succeeded, but " + "genl_id_taskstats is still zero."); + return ENOENT; + } + + return 0; +} + +void ts_destroy(ts_t *ts) { + if (ts == NULL) { + return; + } + + if (ts->nl != NULL) { + mnl_socket_close(ts->nl); + ts->nl = NULL; + } + + sfree(ts); +} + +ts_t *ts_create(void) { + ts_t *ts = calloc(1, sizeof(*ts)); + if (ts == NULL) { + ERROR("utils_taskstats: calloc failed: %s", STRERRNO); + return NULL; + } + + if ((ts->nl = mnl_socket_open(NETLINK_GENERIC)) == NULL) { + ERROR("utils_taskstats: mnl_socket_open(NETLINK_GENERIC) = %s", STRERRNO); + ts_destroy(ts); + return NULL; + } + + if (mnl_socket_bind(ts->nl, 0, MNL_SOCKET_AUTOPID) != 0) { + ERROR("utils_taskstats: mnl_socket_bind() = %s", STRERRNO); + ts_destroy(ts); + return NULL; + } + + ts->pid = getpid(); + ts->port_id = mnl_socket_get_portid(ts->nl); + + int status = get_family_id(ts); + if (status != 0) { + ERROR("utils_taskstats: get_family_id() = %s", STRERROR(status)); + ts_destroy(ts); + return NULL; + } + + return ts; +} + +int ts_delay_by_tgid(ts_t *ts, uint32_t tgid, ts_delay_t *out) { + if ((ts == NULL) || (out == NULL)) { + return EINVAL; + } + + struct taskstats raw = {0}; + + int status = get_taskstats(ts, tgid, &raw); + if (status != 0) { + return status; + } + + *out = (ts_delay_t){ + .cpu_ns = raw.cpu_delay_total, + .blkio_ns = raw.blkio_delay_total, + .swapin_ns = raw.swapin_delay_total, + .freepages_ns = raw.freepages_delay_total, + }; + return 0; +} diff --git a/src/utils_taskstats.h b/src/utils_taskstats.h new file mode 100644 index 00000000..de07427c --- /dev/null +++ b/src/utils_taskstats.h @@ -0,0 +1,47 @@ +/** + * collectd - src/utils_taskstats.h + * Copyright (C) 2017 Florian octo Forster + * + * ISC License (ISC) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Florian octo Forster + */ + +#ifndef UTILS_TASKSTATS_H +#define UTILS_TASKSTATS_H 1 + +#include "collectd.h" + +#include "utils_time.h" + +struct ts_s; +typedef struct ts_s ts_t; + +typedef struct { + uint64_t cpu_ns; + uint64_t blkio_ns; + uint64_t swapin_ns; + uint64_t freepages_ns; +} ts_delay_t; + +ts_t *ts_create(void); +void ts_destroy(ts_t *); + +/* ts_delay_by_tgid returns Linux delay accounting information for the task + * identified by tgid. Returns zero on success and an errno otherwise. */ +int ts_delay_by_tgid(ts_t *ts, uint32_t tgid, ts_delay_t *out); + +#endif /* UTILS_TASKSTATS_H */ -- 2.11.0