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