1 /* libxenstat: statistics-collection library for Xen
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2.1 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  */
13 
14 #include <fcntl.h>
15 #include <sys/types.h>
16 #include <sys/socket.h>
17 #include <poll.h>
18 #include <sys/un.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include <xenctrl.h>
24 
25 #include "xenstat_priv.h"
26 #include "_paths.h"
27 
28 #ifdef HAVE_YAJL_YAJL_VERSION_H
29 #  include <yajl/yajl_version.h>
30 #endif
31 
32 /* YAJL version check */
33 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
34 #  define HAVE_YAJL_V2 1
35 #endif
36 
37 #ifdef HAVE_YAJL_V2
38 
39 #include <yajl/yajl_tree.h>
40 
41 static unsigned char *qmp_query(int, char *);
42 
43 enum query_blockstats {
44     QMP_STATS_RETURN  = 0,
45     QMP_STATS_DEVICE  = 1,
46     QMP_STATS         = 2,
47     QMP_RD_BYTES      = 3,
48     QMP_WR_BYTES      = 4,
49     QMP_RD_OPERATIONS = 5,
50     QMP_WR_OPERATIONS = 6,
51 };
52 
53 enum query_block {
54     QMP_BLOCK_RETURN  = 0,
55     QMP_BLOCK_DEVICE  = 1,
56     QMP_INSERTED      = 2,
57     QMP_FILE          = 3,
58 };
59 
60 
61 /* Given the qmp device name, get the image filename associated with it
62    QMP Syntax for querying block information:
63      In: { "execute": "query-block" }
64      Out: {"return": [{
65             "device": 'str, "locked": 'bool', "removable": bool,
66             "inserted": {
67               "iops_rd": 'int',
68               "image": {
69                 "virtual-size": 'int', "filename": 'str', "cluster-size": 'int',
70                 "format": 'str', "actual-size": 'int', "dirty-flag": 'bool'
71               },
72               "iops_wr": 'int', "ro": 'bool', "backing_file_depth": 'int',
73               "drv": 'str', "iops": 'int', "bps_wr": 'int', "encrypted": 'bool',
74               "bps": 'int', "bps_rd": 'int',
75               "file": 'str', "encryption_key_missing": 'bool'
76             },
77             "type": 'str'
78           }]}
79 */
qmp_get_block_image(xenstat_node * node,char * qmp_devname,int qfd)80 static char *qmp_get_block_image(xenstat_node *node, char *qmp_devname, int qfd)
81 {
82 	char *tmp, *file = NULL;
83 	char *query_block_cmd = "{ \"execute\": \"query-block\" }";
84 	static const char *const qblock[] = {
85 		[ QMP_BLOCK_RETURN  ] = "return",
86 		[ QMP_BLOCK_DEVICE  ] = "device",
87 		[ QMP_INSERTED      ] = "inserted",
88 		[ QMP_FILE          ] = "file",
89 	};
90 	const char *ptr[] = {0, 0};
91 	unsigned char *qmp_stats;
92 	yajl_val info, ret_obj, dev_obj, n;
93 	int i;
94 
95 	if ((qmp_stats = qmp_query(qfd, query_block_cmd)) == NULL)
96 		return NULL;
97 
98 	/* Use libyajl version 2.0.3 or newer for the tree parser feature with bug fixes */
99 	info = yajl_tree_parse((char *)qmp_stats, NULL, 0);
100 	free(qmp_stats);
101 	if (info == NULL)
102 		return NULL;
103 
104 	ptr[0] = qblock[QMP_BLOCK_RETURN]; /* "return" */
105 	if ((ret_obj = yajl_tree_get(info, ptr, yajl_t_array)) == NULL)
106 		goto done;
107 
108 	for (i=0; i<YAJL_GET_ARRAY(ret_obj)->len; i++) {
109 		n = YAJL_GET_ARRAY(ret_obj)->values[i];
110 
111 		ptr[0] = qblock[QMP_BLOCK_DEVICE]; /* "device" */
112 		if ((dev_obj = yajl_tree_get(n, ptr, yajl_t_any)) != NULL) {
113 			tmp = YAJL_GET_STRING(dev_obj);
114 			if (!tmp || strcmp(qmp_devname, tmp))
115 				continue;
116 		}
117 		else
118 			continue;
119 
120 		ptr[0] = qblock[QMP_INSERTED]; /* "inserted" */
121 		n = yajl_tree_get(n, ptr, yajl_t_any);
122 		if (n) {
123 			ptr[0] = qblock[QMP_FILE]; /* "file" */
124 			n = yajl_tree_get(n, ptr, yajl_t_any);
125 			if (n && YAJL_IS_STRING(n)) {
126 				tmp = YAJL_GET_STRING(n);
127 				file = malloc(strlen(tmp)+1);
128 				if (file != NULL)
129 					strcpy(file, tmp);
130 				goto done;
131 			}
132 		}
133 	}
134 done:
135 	yajl_tree_free(info);
136 	return file;
137 }
138 
139 
140 /* Given a QMP device name, lookup the associated xenstore qdisk device id */
lookup_xenstore_devid(xenstat_node * node,unsigned int domid,char * qmp_devname,int qfd,unsigned int * dev,unsigned int * sector_size)141 static void lookup_xenstore_devid(xenstat_node * node, unsigned int domid, char *qmp_devname,
142 	int qfd, unsigned int *dev, unsigned int *sector_size)
143 {
144 	char **dev_ids, *tmp, *ptr, *image, path[80];
145 	unsigned int num_dev_ids;
146 	int i, devid;
147 
148 	/* Get all the qdisk dev IDs associated with the this VM */
149 	snprintf(path, sizeof(path),"/local/domain/0/backend/qdisk/%i", domid);
150 	dev_ids = xs_directory(node->handle->xshandle, XBT_NULL, path, &num_dev_ids);
151 	if (dev_ids == NULL) {
152 		return;
153 	}
154 
155 	/* Get the filename of the image associated with this QMP device */
156 	image = qmp_get_block_image(node, qmp_devname, qfd);
157 	if (image == NULL) {
158 		free(dev_ids);
159 		return;
160 	}
161 
162 	/* Look for a matching image in xenstore */
163 	for (i=0; i<num_dev_ids; i++) {
164 		devid = atoi(dev_ids[i]);
165 		/* Get the xenstore name of the image */
166 		snprintf(path, sizeof(path),"/local/domain/0/backend/qdisk/%i/%i/params", domid, devid);
167 		if ((ptr = xs_read(node->handle->xshandle, XBT_NULL, path, NULL)) == NULL)
168 			continue;
169 
170 		/* Get to actual path in string */
171 		if ((tmp = strchr(ptr, '/')) == NULL)
172 			tmp = ptr;
173 		if (!strcmp(tmp,image)) {
174 			*dev = devid;
175 			free(ptr);
176 
177 			/* Get the xenstore sector size of the image while we're here */
178 			snprintf(path, sizeof(path),"/local/domain/0/backend/qdisk/%i/%i/sector-size", domid, devid);
179 			if ((ptr = xs_read(node->handle->xshandle, XBT_NULL, path, NULL)) != NULL) {
180 				*sector_size = atoi((char *)ptr);
181 				free(ptr);
182 			}
183 			break;
184 		}
185 		free(ptr);
186 	}
187 
188 	free(image);
189 	free(dev_ids);
190 }
191 
192 /* Parse the stats buffer which contains I/O data for all the disks belonging to domid */
qmp_parse_stats(xenstat_node * node,unsigned int domid,unsigned char * stats_buf,int qfd)193 static void qmp_parse_stats(xenstat_node *node, unsigned int domid, unsigned char *stats_buf, int qfd)
194 {
195 	char *qmp_devname;
196 	static const char *const qstats[] = {
197 		[ QMP_STATS_RETURN  ] = "return",
198 		[ QMP_STATS_DEVICE  ] = "device",
199 		[ QMP_STATS         ] = "stats",
200 		[ QMP_RD_BYTES      ] = "rd_bytes",
201 		[ QMP_WR_BYTES      ] = "wr_bytes",
202 		[ QMP_RD_OPERATIONS ] = "rd_operations",
203 		[ QMP_WR_OPERATIONS ] = "wr_operations",
204 	};
205 	const char *ptr[] = {0, 0};
206 	yajl_val info, ret_obj, stats_obj, n;
207 	xenstat_vbd vbd;
208 	xenstat_domain *domain;
209 	unsigned int sector_size = 512;
210 	int i, j;
211 
212 	/* Use libyajl version 2.0.3 or newer for the tree parser feature */
213 	if ((info = yajl_tree_parse((char *)stats_buf, NULL, 0)) == NULL)
214 		return;
215 
216 	ptr[0] = qstats[QMP_STATS_RETURN]; /* "return" */
217 	if ((ret_obj = yajl_tree_get(info, ptr, yajl_t_array)) == NULL)
218 		goto done;
219 
220 	/* Array of devices */
221 	for (i=0; i<YAJL_GET_ARRAY(ret_obj)->len; i++) {
222 		memset(&vbd, 0, sizeof(xenstat_vbd));
223 		qmp_devname = NULL;
224 		stats_obj = YAJL_GET_ARRAY(ret_obj)->values[i];
225 
226 		ptr[0] = qstats[QMP_STATS_DEVICE]; /* "device" */
227 		if ((n = yajl_tree_get(stats_obj, ptr, yajl_t_any)) != NULL)
228 			qmp_devname = YAJL_GET_STRING(n);
229 
230 		ptr[0] = qstats[QMP_STATS]; /* "stats" */
231 		stats_obj = yajl_tree_get(stats_obj, ptr, yajl_t_object);
232 		if (stats_obj && YAJL_IS_OBJECT(stats_obj)) {
233 			for (j=3; j<7; j++) {
234 				ptr[0] = qstats[j];
235 				n = yajl_tree_get(stats_obj, ptr, yajl_t_number);
236 				if (n && YAJL_IS_NUMBER(n)) {
237 					switch(j) {
238 					case QMP_RD_BYTES: /* "rd_bytes" */
239 						vbd.rd_sects = YAJL_GET_INTEGER(n) / sector_size;
240 						break;
241 					case QMP_WR_BYTES: /* "wr_bytes" */
242 						vbd.wr_sects = YAJL_GET_INTEGER(n) / sector_size;
243 						break;
244 					case QMP_RD_OPERATIONS: /* "rd_operations" */
245 						vbd.rd_reqs = YAJL_GET_INTEGER(n);
246 						break;
247 					case QMP_WR_OPERATIONS: /* "wr_operations" */
248 						vbd.wr_reqs = YAJL_GET_INTEGER(n);
249 						break;
250 					}
251 				}
252 			}
253 			/* With the QMP device name, lookup the xenstore qdisk device ID and set vdb.dev */
254 			if (qmp_devname)
255 				lookup_xenstore_devid(node, domid, qmp_devname, qfd, &vbd.dev, &sector_size);
256 			if ((domain = xenstat_node_domain(node, domid)) == NULL)
257 				continue;
258 			if ((xenstat_save_vbd(domain, &vbd)) == NULL)
259 				goto done;
260 		}
261 	}
262 done:
263 	yajl_tree_free(info);
264 }
265 
266 /* Write a command via the QMP. Returns number of bytes written */
qmp_write(int qfd,char * cmd,size_t cmd_len)267 static size_t qmp_write(int qfd, char *cmd, size_t cmd_len)
268 {
269 	size_t pos = 0;
270 	ssize_t res;
271 
272 	while (cmd_len > pos) {
273 		res = write(qfd, cmd + pos, cmd_len - pos);
274 		switch (res) {
275 		case -1:
276 			if (errno == EINTR || errno == EAGAIN)
277 				continue;
278 			return 0;
279 		case 0:
280 			errno = EPIPE;
281 			return pos;
282 		default:
283 			pos += (size_t)res;
284 		}
285 	}
286 	return pos;
287 }
288 
289 /* Read the data sent in response to a QMP execute query. Returns 1 for success */
qmp_read(int qfd,unsigned char ** qstats)290 static int qmp_read(int qfd, unsigned char **qstats)
291 {
292 	unsigned char buf[1024], *ptr;
293 	struct pollfd pfd[1];
294 	int n, qsize = 0;
295 
296 	*qstats = NULL;
297 	pfd[0].fd = qfd;
298 	pfd[0].events = POLLIN;
299 	while ((n = poll(pfd, 1, 10)) > 0) {
300 		if (pfd[0].revents & POLLIN) {
301 			if ((n = read(qfd, buf, sizeof(buf))) < 0) {
302 				free(*qstats);
303 				return 0;
304 			}
305 			ptr = realloc(*qstats, qsize+n+1);
306 			if (ptr == NULL) {
307 				free(*qstats);
308 				return 0;
309 			}
310 			memcpy(&ptr[qsize], buf, n);
311 			qsize += n;
312 			ptr[qsize] = 0;
313 			*qstats = ptr;
314 		}
315 	}
316 	return 1;
317 }
318 
319 /* With the given cmd, query QMP for requested data. Returns allocated buffer containing data or NULL */
qmp_query(int qfd,char * cmd)320 static unsigned char *qmp_query(int qfd, char *cmd)
321 {
322 	unsigned char *qstats = NULL;
323 	int n;
324 
325 	n = strlen(cmd);
326 	if (qmp_write(qfd, cmd, n) != n)
327 		return NULL;
328 	if (!qmp_read(qfd, &qstats))
329 		return NULL;
330 	return qstats;
331 }
332 
333 /* Returns a socket connected to the QMP socket. Returns -1 on failure. */
qmp_connect(char * path)334 static int qmp_connect(char *path)
335 {
336 	struct sockaddr_un sun;
337 	int s;
338 
339 	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
340 		return -1;
341 	(void)fcntl(s, F_SETFD, 1);
342 
343 	memset(&sun, 0, sizeof(struct sockaddr_un));
344 	sun.sun_family = AF_UNIX;
345 
346 	if (strlen(path) >= sizeof(sun.sun_path)) {
347 		close(s);
348 		return -1;
349 	}
350 
351 	strcpy(sun.sun_path, path);
352 	if (connect(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) < 0) {
353 		close(s);
354 		return -1;
355 	}
356 
357 	return s;
358 }
359 
360 /* Gather the qdisk statistics by querying QMP
361    Resources: http://wiki.qemu.org/QMP and qmp-commands.hx from the qemu code
362    QMP Syntax for entering command mode. This command must be issued before
363    issuing any other command:
364      In: {"execute": "qmp_capabilities"}
365      Out: {"return": {}}
366    QMP Syntax for querying block statistics:
367      In: { "execute": "query-blockstats" }
368      Out: {"return": [{
369             "device": 'str',
370             "parent": {
371               "stats": {
372                 "flush_total_time_ns": 'int', "wr_highest_offset": 'int',
373                 "wr_total_time_ns": 'int', "wr_bytes": 'int',
374                 "rd_total_time_ns": 'int', "flush_operations": 'int',
375                 "wr_operations": 'int', "rd_bytes": 'int', "rd_operations": 'int'
376               }
377             },
378             "stats": {
379               "flush_total_time_ns": 'int', "wr_highest_offset": 'int',
380               "wr_total_time_ns": 'int', "wr_bytes": 'int',
381               "rd_total_time_ns": 'int', "flush_operations": 'int',
382               "wr_operations": 'int', "rd_bytes": 'int', "rd_operations": 'int'
383             }
384           }]}
385 */
read_attributes_qdisk_dom(xenstat_node * node,domid_t domain)386 static void read_attributes_qdisk_dom(xenstat_node *node, domid_t domain)
387 {
388 	char *cmd_mode = "{ \"execute\": \"qmp_capabilities\" }";
389 	char *query_blockstats_cmd = "{ \"execute\": \"query-blockstats\" }";
390 	unsigned char *qmp_stats, *val;
391 	char path[80];
392 	int qfd;
393 
394 	/* Verify that qdisk disks are used with this VM */
395 	snprintf(path, sizeof(path),"/local/domain/0/backend/qdisk/%i", domain);
396 	val = xs_read(node->handle->xshandle, XBT_NULL, path, NULL);
397 	if (val == NULL)
398 		return;
399 	free(val);
400 
401 	/* Connect to this VMs QMP socket */
402 	snprintf(path, sizeof(path), XEN_RUN_DIR "/qmp-libxenstat-%i", domain);
403 	if ((qfd = qmp_connect(path)) < 0)
404 		return;
405 
406 	/* First enable QMP capabilities so that we can query for data */
407 	if ((qmp_stats = qmp_query(qfd, cmd_mode)) != NULL) {
408 		free(qmp_stats);
409 		/* Query QMP for this VMs blockstats */
410 		qmp_stats = qmp_query(qfd, query_blockstats_cmd);
411 		if (qmp_stats != NULL) {
412 			qmp_parse_stats(node, domain, qmp_stats, qfd);
413 			free(qmp_stats);
414 		}
415 	}
416 	close(qfd);
417 }
418 
read_attributes_qdisk(xenstat_node * node)419 void read_attributes_qdisk(xenstat_node * node)
420 {
421 	xc_domaininfo_t dominfo[1024];
422 	int i, num_doms;
423 	domid_t next_domid = 0;
424 
425 	for (;;) {
426 		num_doms = xc_domain_getinfolist(node->handle->xc_handle,
427 						 next_domid, 1024, dominfo);
428 		if (num_doms <= 0)
429 			return;
430 
431 		for (i = 0; i < num_doms; i++)
432 			if (dominfo[i].domain > 0)
433 				read_attributes_qdisk_dom(node, dominfo[i].domain);
434 
435 		next_domid = dominfo[num_doms - 1].domain + 1;
436 	}
437 }
438 
439 #else /* !HAVE_YAJL_V2 */
440 
441 /* Statistics gathering for qdisks requires at least yajl v2 */
read_attributes_qdisk(xenstat_node * node)442 void read_attributes_qdisk(xenstat_node * node)
443 {
444 }
445 
446 #endif /* !HAVE_YAJL_V2 */
447