1 /* libxenstat: statistics-collection library for Xen
2 * Copyright (C) International Business Machines Corp., 2005
3 * Authors: Josh Triplett <josh@kernel.org>
4 * Judy Fischbach <jfisch@cs.pdx.edu>
5 * David Hendricks <cro_marmot@comcast.net>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 */
17
18 /*
19 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
20 * Use is subject to license terms.
21 */
22
23 #define _GNU_SOURCE
24 #include <fcntl.h>
25 #include <dirent.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <regex.h>
33 #include <xen-tools/common-macros.h>
34
35 #include "xenstat_priv.h"
36
37 #define SYSFS_VBD_PATH "/sys/bus/xen-backend/devices"
38 #define XENSTAT_VBD_TYPE_VBD3 3
39
40 struct priv_data {
41 FILE *procnetdev;
42 DIR *sysfsvbd;
43 };
44
45 static struct priv_data *
get_priv_data(xenstat_handle * handle)46 get_priv_data(xenstat_handle *handle)
47 {
48 if (handle->priv != NULL)
49 return handle->priv;
50
51 handle->priv = malloc(sizeof(struct priv_data));
52 if (handle->priv == NULL)
53 return (NULL);
54
55 ((struct priv_data *)handle->priv)->procnetdev = NULL;
56 ((struct priv_data *)handle->priv)->sysfsvbd = NULL;
57
58 return handle->priv;
59 }
60
61 /* Expected format of /proc/net/dev */
62 static const char PROCNETDEV_HEADER[] =
63 "Inter-| Receive |"
64 " Transmit\n"
65 " face |bytes packets errs drop fifo frame compressed multicast|"
66 "bytes packets errs drop fifo colls carrier compressed\n";
67
68 /* We need to get the name of the bridge interface for use with bonding interfaces */
69 /* Use excludeName parameter to avoid adding bridges we don't care about, eg. virbr0 */
getBridge(const char * excludeName,char * result,size_t resultLen)70 static void getBridge(const char *excludeName, char *result, size_t resultLen)
71 {
72 struct dirent *de;
73 DIR *d;
74
75 char tmp[512] = { 0 };
76
77 d = opendir("/sys/class/net");
78 while ((de = readdir(d)) != NULL) {
79 if ((strlen(de->d_name) > 0) && (de->d_name[0] != '.')
80 && (strstr(de->d_name, excludeName) == NULL)) {
81 sprintf(tmp, "/sys/class/net/%s/bridge", de->d_name);
82
83 if (access(tmp, F_OK) == 0) {
84 /*
85 * Do not use strncpy to prevent compiler warning with
86 * gcc >= 10.0
87 * If de->d_name is longer then resultLen we truncate it
88 */
89 memset(result, 0, resultLen);
90 memcpy(result, de->d_name, MIN(strnlen(de->d_name,
91 NAME_MAX),resultLen - 1));
92 }
93 }
94 }
95
96 closedir(d);
97 }
98
99 /* parseNetLine provides regular expression based parsing for lines from /proc/net/dev, all the */
100 /* information are parsed but not all are used in our case, ie. for xenstat */
parseNetDevLine(char * line,char * iface,unsigned long long * rxBytes,unsigned long long * rxPackets,unsigned long long * rxErrs,unsigned long long * rxDrops,unsigned long long * rxFifo,unsigned long long * rxFrames,unsigned long long * rxComp,unsigned long long * rxMcast,unsigned long long * txBytes,unsigned long long * txPackets,unsigned long long * txErrs,unsigned long long * txDrops,unsigned long long * txFifo,unsigned long long * txColls,unsigned long long * txCarrier,unsigned long long * txComp)101 static int parseNetDevLine(char *line, char *iface, unsigned long long *rxBytes, unsigned long long *rxPackets,
102 unsigned long long *rxErrs, unsigned long long *rxDrops, unsigned long long *rxFifo,
103 unsigned long long *rxFrames, unsigned long long *rxComp, unsigned long long *rxMcast,
104 unsigned long long *txBytes, unsigned long long *txPackets, unsigned long long *txErrs,
105 unsigned long long *txDrops, unsigned long long *txFifo, unsigned long long *txColls,
106 unsigned long long *txCarrier, unsigned long long *txComp)
107 {
108 /* Temporary/helper variables */
109 int ret;
110 char *tmp;
111 int i = 0, x = 0, col = 0;
112 regex_t r;
113 regmatch_t matches[19];
114 int num = 19;
115
116 /* Regular exception to parse all the information from /proc/net/dev line */
117 const char *regex = "([^:]*):([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)"
118 "[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*"
119 "([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)[ ]*([^ ]*)";
120
121 /* Initialize all variables called has passed as non-NULL to zeros */
122 if (iface != NULL)
123 memset(iface, 0, sizeof(*iface));
124 if (rxBytes != NULL)
125 *rxBytes = 0;
126 if (rxPackets != NULL)
127 *rxPackets = 0;
128 if (rxErrs != NULL)
129 *rxErrs = 0;
130 if (rxDrops != NULL)
131 *rxDrops = 0;
132 if (rxFifo != NULL)
133 *rxFifo = 0;
134 if (rxFrames != NULL)
135 *rxFrames = 0;
136 if (rxPackets != NULL)
137 *rxPackets = 0;
138 if (rxComp != NULL)
139 *rxComp = 0;
140 if (txBytes != NULL)
141 *txBytes = 0;
142 if (txPackets != NULL)
143 *txPackets = 0;
144 if (txErrs != NULL)
145 *txErrs = 0;
146 if (txDrops != NULL)
147 *txDrops = 0;
148 if (txFifo != NULL)
149 *txFifo = 0;
150 if (txColls != NULL)
151 *txColls = 0;
152 if (txCarrier != NULL)
153 *txCarrier = 0;
154 if (txComp != NULL)
155 *txComp = 0;
156
157 if ((ret = regcomp(&r, regex, REG_EXTENDED))) {
158 regfree(&r);
159 return ret;
160 }
161
162 tmp = (char *)malloc( sizeof(char) );
163 if (regexec (&r, line, num, matches, REG_EXTENDED) == 0){
164 for (i = 1; i < num; i++) {
165 /* The expression matches are empty sometimes so we need to check it first */
166 if (matches[i].rm_eo - matches[i].rm_so > 0) {
167 /* Col variable contains current id of non-empty match */
168 col++;
169 tmp = (char *)realloc(tmp, (matches[i].rm_eo -
170 matches[i].rm_so + 1) * sizeof(char));
171 for (x = matches[i].rm_so; x < matches[i].rm_eo; x++)
172 tmp[x - matches[i].rm_so] = line[x];
173 tmp[x - matches[i].rm_so] = 0;
174
175 /* We populate all the fields from /proc/net/dev line */
176 if (i > 1) {
177 unsigned long long ullTmp = strtoull(tmp, NULL, 10);
178
179 switch (col) {
180 case 2: if (rxBytes != NULL)
181 *rxBytes = ullTmp;
182 break;
183 case 3: if (rxPackets != NULL)
184 *rxPackets = ullTmp;
185 break;
186 case 4: if (rxErrs != NULL)
187 *rxErrs = ullTmp;
188 break;
189 case 5: if (rxDrops != NULL)
190 *rxDrops = ullTmp;
191 break;
192 case 6: if (rxFifo != NULL)
193 *rxFifo = ullTmp;
194 break;
195 case 7: if (rxFrames != NULL)
196 *rxFrames = ullTmp;
197 break;
198 case 8: if (rxComp != NULL)
199 *rxComp = ullTmp;
200 break;
201 case 9: if (rxMcast != NULL)
202 *rxMcast = ullTmp;
203 break;
204 case 10: if (txBytes != NULL)
205 *txBytes = ullTmp;
206 break;
207 case 11: if (txPackets != NULL)
208 *txPackets = ullTmp;
209 break;
210 case 12: if (txErrs != NULL)
211 *txErrs = ullTmp;
212 break;
213 case 13: if (txDrops != NULL)
214 *txDrops = ullTmp;
215 break;
216 case 14: if (txFifo != NULL)
217 *txFifo = ullTmp;
218 break;
219 case 15: if (txColls != NULL)
220 *txColls = ullTmp;
221 break;
222 case 16: if (txCarrier != NULL)
223 *txCarrier = ullTmp;
224 break;
225 case 17: if (txComp != NULL)
226 *txComp = ullTmp;
227 break;
228 }
229 }
230 else if (iface != NULL) {
231 char *tmp2 = strpbrk(tmp, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
232 if (tmp2 != NULL)
233 strcpy(iface, tmp2);
234 }
235 }
236 }
237 }
238
239 free(tmp);
240 regfree(&r);
241
242 return 0;
243 }
244
245 /* Find out the domid and network number given an interface name.
246 * Return 0 if the iface cannot be recognized as a Xen VIF. */
get_iface_domid_network(const char * iface,unsigned int * domid_p,unsigned int * netid_p)247 static int get_iface_domid_network(const char *iface, unsigned int *domid_p, unsigned int *netid_p)
248 {
249 char nodename_path[48];
250 FILE * nodename_file;
251 int ret;
252
253 snprintf(nodename_path, 48, "/sys/class/net/%s/device/nodename", iface);
254 nodename_file = fopen(nodename_path, "r");
255 if (nodename_file != NULL) {
256 ret = fscanf(nodename_file, "backend/vif/%u/%u", domid_p, netid_p);
257 fclose(nodename_file);
258 if (ret == 2)
259 return 1;
260 }
261
262 if (sscanf(iface, "vif%u.%u", domid_p, netid_p) == 2)
263 return 1;
264
265 return 0;
266 }
267
268 /* Collect information about networks */
xenstat_collect_networks(xenstat_node * node)269 int xenstat_collect_networks(xenstat_node * node)
270 {
271 /* Helper variables for parseNetDevLine() function defined above */
272 int i;
273 char line[512] = { 0 }, iface[16] = { 0 }, devBridge[16] = { 0 }, devNoBridge[17] = { 0 };
274 unsigned long long rxBytes, rxPackets, rxErrs, rxDrops, txBytes, txPackets, txErrs, txDrops;
275
276 struct priv_data *priv = get_priv_data(node->handle);
277
278 if (priv == NULL) {
279 perror("Allocation error");
280 return 0;
281 }
282
283 /* Open and validate /proc/net/dev if we haven't already */
284 if (priv->procnetdev == NULL) {
285 char header[sizeof(PROCNETDEV_HEADER)];
286 priv->procnetdev = fopen("/proc/net/dev", "r");
287 if (priv->procnetdev == NULL) {
288 perror("Error opening /proc/net/dev");
289 return 0;
290 }
291
292 /* Validate the format of /proc/net/dev */
293 if (fread(header, sizeof(PROCNETDEV_HEADER) - 1, 1,
294 priv->procnetdev) != 1) {
295 perror("Error reading /proc/net/dev header");
296 return 0;
297 }
298 header[sizeof(PROCNETDEV_HEADER) - 1] = '\0';
299 if (strcmp(header, PROCNETDEV_HEADER) != 0) {
300 fprintf(stderr,
301 "Unexpected /proc/net/dev format\n");
302 return 0;
303 }
304 }
305
306 /* Fill in networks */
307 /* FIXME: optimize this */
308 fseek(priv->procnetdev, sizeof(PROCNETDEV_HEADER) - 1,
309 SEEK_SET);
310
311 /* We get the bridge devices for use with bonding interface to get bonding interface stats */
312 getBridge("vir", devBridge, sizeof(devBridge));
313 snprintf(devNoBridge, sizeof(devNoBridge), "p%s", devBridge);
314
315 while (fgets(line, 512, priv->procnetdev)) {
316 xenstat_domain *domain;
317 xenstat_network net;
318 unsigned int domid;
319
320 parseNetDevLine(line, iface, &rxBytes, &rxPackets, &rxErrs, &rxDrops, NULL, NULL, NULL,
321 NULL, &txBytes, &txPackets, &txErrs, &txDrops, NULL, NULL, NULL, NULL);
322
323 /* If the device parsed is network bridge and both tx & rx packets are zero, we are most */
324 /* likely using bonding so we alter the configuration for dom0 to have bridge stats */
325 if ((strstr(iface, devBridge) != NULL) &&
326 (strstr(iface, devNoBridge) == NULL) &&
327 ((domain = xenstat_node_domain(node, 0)) != NULL)) {
328 for (i = 0; i < domain->num_networks; i++) {
329 if ((domain->networks[i].id != 0) ||
330 (domain->networks[i].tbytes != 0) ||
331 (domain->networks[i].rbytes != 0))
332 continue;
333 domain->networks[i].tbytes = txBytes;
334 domain->networks[i].tpackets = txPackets;
335 domain->networks[i].terrs = txErrs;
336 domain->networks[i].tdrop = txDrops;
337 domain->networks[i].rbytes = rxBytes;
338 domain->networks[i].rpackets = rxPackets;
339 domain->networks[i].rerrs = rxErrs;
340 domain->networks[i].rdrop = rxDrops;
341 }
342 }
343 else /* Otherwise we need to preserve old behaviour */
344 if (get_iface_domid_network(iface, &domid, &net.id)) {
345
346 net.tbytes = txBytes;
347 net.tpackets = txPackets;
348 net.terrs = txErrs;
349 net.tdrop = txDrops;
350 net.rbytes = rxBytes;
351 net.rpackets = rxPackets;
352 net.rerrs = rxErrs;
353 net.rdrop = rxDrops;
354
355 /* FIXME: this does a search for the domid */
356 domain = xenstat_node_domain(node, domid);
357 if (domain == NULL) {
358 fprintf(stderr,
359 "Found interface vif%u.%u but domain %u"
360 " does not exist.\n", domid, net.id,
361 domid);
362 continue;
363 }
364 if (domain->networks == NULL) {
365 domain->num_networks = 1;
366 domain->networks = malloc(sizeof(xenstat_network));
367 } else {
368 struct xenstat_network *tmp;
369 domain->num_networks++;
370 tmp = realloc(domain->networks,
371 domain->num_networks *
372 sizeof(xenstat_network));
373 if (tmp == NULL)
374 free(domain->networks);
375 domain->networks = tmp;
376 }
377 if (domain->networks == NULL)
378 return 0;
379 domain->networks[domain->num_networks - 1] = net;
380 }
381 }
382
383 return 1;
384 }
385
386 /* Free network information in handle */
xenstat_uninit_networks(xenstat_handle * handle)387 void xenstat_uninit_networks(xenstat_handle * handle)
388 {
389 struct priv_data *priv = get_priv_data(handle);
390 if (priv != NULL && priv->procnetdev != NULL)
391 fclose(priv->procnetdev);
392 }
393
read_attributes_vbd3(const char * vbd3_path,xenstat_vbd * vbd)394 static int read_attributes_vbd3(const char *vbd3_path, xenstat_vbd *vbd)
395 {
396 FILE *fp;
397 struct vbd3_stats vbd3_stats;
398
399 fp = fopen(vbd3_path, "rb");
400
401 if (fp == NULL) {
402 return -1;
403 }
404
405 if (fread(&vbd3_stats, sizeof(struct vbd3_stats), 1, fp) != 1) {
406 fclose(fp);
407 return -1;
408 }
409
410 if (vbd3_stats.version != 1) {
411 fclose(fp);
412 return -1;
413 }
414
415 vbd->oo_reqs = vbd3_stats.oo_reqs;
416 vbd->rd_reqs = vbd3_stats.read_reqs_submitted;
417 vbd->rd_sects = vbd3_stats.read_sectors;
418 vbd->wr_reqs = vbd3_stats.write_reqs_submitted;
419 vbd->wr_sects = vbd3_stats.write_sectors;
420
421 fclose(fp);
422
423 return 0;
424 }
425
read_attributes_vbd(const char * vbd_directory,const char * what,char * ret,int cap)426 static int read_attributes_vbd(const char *vbd_directory, const char *what, char *ret, int cap)
427 {
428 static char file_name[80];
429 int fd, num_read;
430
431 snprintf(file_name, sizeof(file_name), "%s/%s/%s",
432 SYSFS_VBD_PATH, vbd_directory, what);
433 fd = open(file_name, O_RDONLY, 0);
434 if (fd==-1) return -1;
435 num_read = read(fd, ret, cap - 1);
436 close(fd);
437 if (num_read<=0) return -1;
438 ret[num_read] = '\0';
439 return num_read;
440 }
441
442 /* Collect information about VBDs */
xenstat_collect_vbds(xenstat_node * node)443 int xenstat_collect_vbds(xenstat_node * node)
444 {
445 struct dirent *dp;
446 struct priv_data *priv = get_priv_data(node->handle);
447
448 if (priv == NULL) {
449 perror("Allocation error");
450 return 0;
451 }
452
453 if (priv->sysfsvbd == NULL) {
454 priv->sysfsvbd = opendir(SYSFS_VBD_PATH);
455 if (priv->sysfsvbd == NULL) {
456 perror("Error opening " SYSFS_VBD_PATH);
457 return 0;
458 }
459 }
460
461 /* Get qdisk statistics */
462 read_attributes_qdisk(node);
463
464 rewinddir(priv->sysfsvbd);
465
466 for(dp = readdir(priv->sysfsvbd); dp != NULL ;
467 dp = readdir(priv->sysfsvbd)) {
468 xenstat_domain *domain;
469 xenstat_vbd vbd;
470 unsigned int domid;
471 int ret;
472 char buf[256];
473
474 ret = sscanf(dp->d_name, "%255[^-]-%u-%u", buf, &domid, &vbd.dev);
475 if (ret != 3)
476 continue;
477 if (!(strstr(buf, "vbd")) && !(strstr(buf, "tap")))
478 continue;
479
480 if (strcmp(buf,"vbd") == 0)
481 vbd.back_type = 1;
482 else if (strcmp(buf,"tap") == 0)
483 vbd.back_type = 2;
484 else if (strcmp(buf,"vbd3") == 0)
485 vbd.back_type = XENSTAT_VBD_TYPE_VBD3;
486 else
487 vbd.back_type = 0;
488
489 domain = xenstat_node_domain(node, domid);
490 if (domain == NULL) {
491 fprintf(stderr,
492 "Found interface %s-%u-%u but domain %u"
493 " does not exist.\n",
494 buf, domid, vbd.dev, domid);
495 continue;
496 }
497
498 if (vbd.back_type == 1 || vbd.back_type == 2)
499 {
500
501 vbd.error = 0;
502
503 if ((read_attributes_vbd(dp->d_name, "statistics/oo_req", buf, 256)<=0) ||
504 ((ret = sscanf(buf, "%llu", &vbd.oo_reqs)) != 1) ||
505 (read_attributes_vbd(dp->d_name, "statistics/rd_req", buf, 256)<=0) ||
506 ((ret = sscanf(buf, "%llu", &vbd.rd_reqs)) != 1) ||
507 (read_attributes_vbd(dp->d_name, "statistics/wr_req", buf, 256)<=0) ||
508 ((ret = sscanf(buf, "%llu", &vbd.wr_reqs)) != 1) ||
509 (read_attributes_vbd(dp->d_name, "statistics/rd_sect", buf, 256)<=0) ||
510 ((ret = sscanf(buf, "%llu", &vbd.rd_sects)) != 1) ||
511 (read_attributes_vbd(dp->d_name, "statistics/wr_sect", buf, 256)<=0) ||
512 ((ret = sscanf(buf, "%llu", &vbd.wr_sects)) != 1))
513 {
514 vbd.error = 1;
515 }
516 }
517 else if (vbd.back_type == XENSTAT_VBD_TYPE_VBD3)
518 {
519 char *td3_pid;
520 char *path;
521
522 vbd.error = 0;
523
524 if (asprintf(&path, "/local/domain/0/backend/vbd3/%u/%u/kthread-pid", domid, vbd.dev) < 0)
525 continue;
526
527 td3_pid = xs_read(node->handle->xshandle, XBT_NULL, path, NULL);
528
529 free(path);
530
531 if (td3_pid == NULL)
532 continue;
533
534 if (asprintf(&path, "/dev/shm/td3-%s/vbd-%u-%u", td3_pid, domid, vbd.dev) < 0) {
535 free(td3_pid);
536 continue;
537 }
538
539 if (read_attributes_vbd3(path, &vbd) < 0)
540 vbd.error = 1;
541
542 free(td3_pid);
543 free(path);
544 }
545 else
546 {
547 vbd.error = 1;
548 }
549 if ((xenstat_save_vbd(domain, &vbd)) == NULL) {
550 perror("Allocation error");
551 return 0;
552 }
553 }
554
555 return 1;
556 }
557
558 /* Free VBD information in handle */
xenstat_uninit_vbds(xenstat_handle * handle)559 void xenstat_uninit_vbds(xenstat_handle * handle)
560 {
561 struct priv_data *priv = get_priv_data(handle);
562 if (priv != NULL && priv->sysfsvbd != NULL)
563 closedir(priv->sysfsvbd);
564 }
565