1 /**
2  * collectd - src/utils_taskstats.c
3  * Copyright (C) 2017       Florian octo Forster
4  *
5  * ISC License (ISC)
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  *
19  * Authors:
20  *   Florian octo Forster <octo at collectd.org>
21  */
22 
23 #include "collectd.h"
24 #include "utils/taskstats/taskstats.h"
25 
26 #include "plugin.h"
27 #include "utils/common/common.h"
28 #include "utils_time.h"
29 
30 #include <libmnl/libmnl.h>
31 #include <linux/genetlink.h>
32 #include <linux/taskstats.h>
33 
34 struct ts_s {
35   struct mnl_socket *nl;
36   pid_t pid;
37   uint32_t seq;
38   uint16_t genl_id_taskstats;
39   unsigned int port_id;
40 };
41 
42 /* nlmsg_errno returns the errno encoded in nlh or zero if not an error. */
nlmsg_errno(struct nlmsghdr * nlh,size_t sz)43 static int nlmsg_errno(struct nlmsghdr *nlh, size_t sz) {
44   if (!mnl_nlmsg_ok(nlh, (int)sz)) {
45     ERROR("utils_taskstats: mnl_nlmsg_ok failed.");
46     return EPROTO;
47   }
48 
49   if (nlh->nlmsg_type != NLMSG_ERROR) {
50     return 0;
51   }
52 
53   struct nlmsgerr *nlerr = mnl_nlmsg_get_payload(nlh);
54   /* (struct nlmsgerr).error holds a negative errno. */
55   return nlerr->error * (-1);
56 }
57 
get_taskstats_attr_cb(const struct nlattr * attr,void * data)58 static int get_taskstats_attr_cb(const struct nlattr *attr, void *data) {
59   struct taskstats *ret_taskstats = data;
60 
61   uint16_t type = mnl_attr_get_type(attr);
62   switch (type) {
63   case TASKSTATS_TYPE_STATS:
64     if (mnl_attr_get_payload_len(attr) != sizeof(*ret_taskstats)) {
65       ERROR("utils_taskstats: mnl_attr_get_payload_len(attr) = %" PRIu32
66             ", want %zu",
67             mnl_attr_get_payload_len(attr), sizeof(*ret_taskstats));
68       return MNL_CB_ERROR;
69     }
70     struct taskstats *ts = mnl_attr_get_payload(attr);
71     memmove(ret_taskstats, ts, sizeof(*ret_taskstats));
72     return MNL_CB_OK;
73 
74   case TASKSTATS_TYPE_AGGR_PID: /* fall through */
75   case TASKSTATS_TYPE_AGGR_TGID:
76     return mnl_attr_parse_nested(attr, get_taskstats_attr_cb, ret_taskstats);
77 
78   case TASKSTATS_TYPE_PID: /* fall through */
79   case TASKSTATS_TYPE_TGID:
80     /* ignore */
81     return MNL_CB_OK;
82 
83   default:
84     DEBUG("utils_taskstats: unknown attribute %" PRIu16
85           ", want one of TASKSTATS_TYPE_AGGR_PID/TGID, TASKSTATS_TYPE_STATS",
86           type);
87   }
88   return MNL_CB_OK;
89 }
90 
get_taskstats_msg_cb(const struct nlmsghdr * nlh,void * data)91 static int get_taskstats_msg_cb(const struct nlmsghdr *nlh, void *data) {
92   return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_taskstats_attr_cb,
93                         data);
94 }
95 
get_taskstats(ts_t * ts,uint32_t tgid,struct taskstats * ret_taskstats)96 static int get_taskstats(ts_t *ts, uint32_t tgid,
97                          struct taskstats *ret_taskstats) {
98   char buffer[MNL_SOCKET_BUFFER_SIZE];
99   uint32_t seq = ts->seq++;
100 
101   struct nlmsghdr *nlh = mnl_nlmsg_put_header(buffer);
102   *nlh = (struct nlmsghdr){
103       .nlmsg_len = nlh->nlmsg_len,
104       .nlmsg_type = ts->genl_id_taskstats,
105       .nlmsg_flags = NLM_F_REQUEST,
106       .nlmsg_seq = seq,
107       .nlmsg_pid = ts->pid,
108   };
109 
110   struct genlmsghdr *genh = mnl_nlmsg_put_extra_header(nlh, sizeof(*genh));
111   *genh = (struct genlmsghdr){
112       .cmd = TASKSTATS_CMD_GET,
113       .version = TASKSTATS_GENL_VERSION, // or TASKSTATS_VERSION?
114   };
115 
116   // mnl_attr_put_u32(nlh, TASKSTATS_CMD_ATTR_PID, tgid);
117   mnl_attr_put_u32(nlh, TASKSTATS_CMD_ATTR_TGID, tgid);
118 
119   if (mnl_socket_sendto(ts->nl, nlh, nlh->nlmsg_len) < 0) {
120     int status = errno;
121     ERROR("utils_taskstats: mnl_socket_sendto() = %s", STRERROR(status));
122     return status;
123   }
124 
125   int status = mnl_socket_recvfrom(ts->nl, buffer, sizeof(buffer));
126   if (status < 0) {
127     status = errno;
128     ERROR("utils_taskstats: mnl_socket_recvfrom() = %s", STRERROR(status));
129     return status;
130   } else if (status == 0) {
131     ERROR("utils_taskstats: mnl_socket_recvfrom() = 0");
132     return ECONNABORTED;
133   }
134   size_t buffer_size = (size_t)status;
135 
136   if ((status = nlmsg_errno((void *)buffer, buffer_size)) != 0) {
137     ERROR("utils_taskstats: TASKSTATS_CMD_GET(TASKSTATS_CMD_ATTR_TGID = "
138           "%" PRIu32 ") = %s",
139           (uint32_t)tgid, STRERROR(status));
140     return status;
141   }
142 
143   status = mnl_cb_run(buffer, buffer_size, seq, ts->port_id,
144                       get_taskstats_msg_cb, ret_taskstats);
145   if (status < MNL_CB_STOP) {
146     ERROR("utils_taskstats: Parsing message failed.");
147     return EPROTO;
148   }
149 
150   return 0;
151 }
152 
get_family_id_attr_cb(const struct nlattr * attr,void * data)153 static int get_family_id_attr_cb(const struct nlattr *attr, void *data) {
154   uint16_t type = mnl_attr_get_type(attr);
155   if (type != CTRL_ATTR_FAMILY_ID) {
156     return MNL_CB_OK;
157   }
158 
159   if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) {
160     ERROR("mnl_attr_validate() = %s", STRERRNO);
161     return MNL_CB_ERROR;
162   }
163 
164   uint16_t *ret_family_id = data;
165   *ret_family_id = mnl_attr_get_u16(attr);
166   return MNL_CB_STOP;
167 }
168 
get_family_id_msg_cb(const struct nlmsghdr * nlh,void * data)169 static int get_family_id_msg_cb(const struct nlmsghdr *nlh, void *data) {
170   return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb,
171                         data);
172 }
173 
174 /* get_family_id initializes ts->genl_id_taskstats. Returns 0 on success and
175  * an error code otherwise. */
get_family_id(ts_t * ts)176 static int get_family_id(ts_t *ts) {
177   char buffer[MNL_SOCKET_BUFFER_SIZE];
178   uint32_t seq = ts->seq++;
179 
180   struct nlmsghdr *nlh = mnl_nlmsg_put_header(buffer);
181   *nlh = (struct nlmsghdr){
182       .nlmsg_len = nlh->nlmsg_len,
183       .nlmsg_type = GENL_ID_CTRL,
184       .nlmsg_flags = NLM_F_REQUEST,
185       .nlmsg_seq = seq,
186       .nlmsg_pid = ts->pid,
187   };
188 
189   struct genlmsghdr *genh = mnl_nlmsg_put_extra_header(nlh, sizeof(*genh));
190   *genh = (struct genlmsghdr){
191       .cmd = CTRL_CMD_GETFAMILY,
192       .version = 0x01,
193   };
194 
195   mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, TASKSTATS_GENL_NAME);
196 
197   assert(genh->cmd == CTRL_CMD_GETFAMILY);
198   assert(genh->version == TASKSTATS_GENL_VERSION);
199 
200   if (mnl_socket_sendto(ts->nl, nlh, nlh->nlmsg_len) < 0) {
201     int status = errno;
202     ERROR("utils_taskstats: mnl_socket_sendto() = %s", STRERROR(status));
203     return status;
204   }
205 
206   ts->genl_id_taskstats = 0;
207   while (42) {
208     int status = mnl_socket_recvfrom(ts->nl, buffer, sizeof(buffer));
209     if (status < 0) {
210       status = errno;
211       ERROR("utils_taskstats: mnl_socket_recvfrom() = %s", STRERROR(status));
212       return status;
213     } else if (status == 0) {
214       break;
215     }
216     size_t buffer_size = (size_t)status;
217 
218     if ((status = nlmsg_errno((void *)buffer, buffer_size)) != 0) {
219       ERROR("utils_taskstats: CTRL_CMD_GETFAMILY(\"%s\"): %s",
220             TASKSTATS_GENL_NAME, STRERROR(status));
221       return status;
222     }
223 
224     status = mnl_cb_run(buffer, buffer_size, seq, ts->port_id,
225                         get_family_id_msg_cb, &ts->genl_id_taskstats);
226     if (status < MNL_CB_STOP) {
227       ERROR("utils_taskstats: Parsing message failed.");
228       return EPROTO;
229     } else if (status == MNL_CB_STOP) {
230       break;
231     }
232   }
233 
234   if (ts->genl_id_taskstats == 0) {
235     ERROR("utils_taskstats: Netlink communication succeeded, but "
236           "genl_id_taskstats is still zero.");
237     return ENOENT;
238   }
239 
240   return 0;
241 }
242 
ts_destroy(ts_t * ts)243 void ts_destroy(ts_t *ts) {
244   if (ts == NULL) {
245     return;
246   }
247 
248   if (ts->nl != NULL) {
249     mnl_socket_close(ts->nl);
250     ts->nl = NULL;
251   }
252 
253   sfree(ts);
254 }
255 
ts_create(void)256 ts_t *ts_create(void) {
257   ts_t *ts = calloc(1, sizeof(*ts));
258   if (ts == NULL) {
259     ERROR("utils_taskstats: calloc failed: %s", STRERRNO);
260     return NULL;
261   }
262 
263   if ((ts->nl = mnl_socket_open(NETLINK_GENERIC)) == NULL) {
264     ERROR("utils_taskstats: mnl_socket_open(NETLINK_GENERIC) = %s", STRERRNO);
265     ts_destroy(ts);
266     return NULL;
267   }
268 
269   if (mnl_socket_bind(ts->nl, 0, MNL_SOCKET_AUTOPID) != 0) {
270     ERROR("utils_taskstats: mnl_socket_bind() = %s", STRERRNO);
271     ts_destroy(ts);
272     return NULL;
273   }
274 
275   ts->pid = getpid();
276   ts->port_id = mnl_socket_get_portid(ts->nl);
277 
278   int status = get_family_id(ts);
279   if (status != 0) {
280     ERROR("utils_taskstats: get_family_id() = %s", STRERROR(status));
281     ts_destroy(ts);
282     return NULL;
283   }
284 
285   return ts;
286 }
287 
ts_delay_by_tgid(ts_t * ts,uint32_t tgid,ts_delay_t * out)288 int ts_delay_by_tgid(ts_t *ts, uint32_t tgid, ts_delay_t *out) {
289   if ((ts == NULL) || (out == NULL)) {
290     return EINVAL;
291   }
292 
293   struct taskstats raw = {0};
294 
295   int status = get_taskstats(ts, tgid, &raw);
296   if (status != 0) {
297     return status;
298   }
299 
300   *out = (ts_delay_t){
301       .cpu_ns = raw.cpu_delay_total,
302       .blkio_ns = raw.blkio_delay_total,
303       .swapin_ns = raw.swapin_delay_total,
304       .freepages_ns = raw.freepages_delay_total,
305   };
306   return 0;
307 }
308