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