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