xref: /illumos-gate/usr/src/cmd/cpc/common/strtoset.c (revision 051aabe6136ff13e81542a427e9693ffe1503525)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <alloca.h>
31 #include <errno.h>
32 #include <libintl.h>
33 
34 #include "libcpc.h"
35 
36 /*
37  * Takes a string and converts it to a cpc_set_t.
38  *
39  * While processing the string using getsubopt(), we will use an array of
40  * requests to hold the data, and a proprietary representation of attributes
41  * which allow us to avoid a realloc()/bcopy() dance every time we come across
42  * a new attribute.
43  *
44  * Not until after the string has been processed in its entirety do we
45  * allocate and specify a request set properly.
46  */
47 
48 /*
49  * Leave enough room in token strings for picn, nousern, or sysn where n is
50  * picnum.
51  */
52 #define	TOK_SIZE	10
53 
54 typedef struct __tmp_attr {
55 	char			*name;
56 	uint64_t		val;
57 	struct __tmp_attr	*next;
58 } tmp_attr_t;
59 
60 typedef struct __tok_info {
61 	char			*name;
62 	int			picnum;
63 } tok_info_t;
64 
65 typedef struct __request_t {
66 	char			cr_event[CPC_MAX_EVENT_LEN];
67 	uint_t			cr_flags;
68 	uint_t			cr_nattrs;	/* # CPU-specific attrs */
69 } request_t;
70 
71 static void strtoset_cleanup(void);
72 static void smt_special(int picnum);
73 static void *emalloc(size_t n);
74 
75 /*
76  * Clients of cpc_strtoset may set this to specify an error handler during
77  * string parsing.
78  */
79 cpc_errhndlr_t		*strtoset_errfn = NULL;
80 
81 static request_t	*reqs;
82 static int		nreqs;
83 static int		ncounters;
84 
85 static tmp_attr_t	**attrs;
86 static int		ntoks;
87 static char		**toks;
88 static tok_info_t	*tok_info;
89 static int		(*(*tok_funcs))(int, char *);
90 static char		**attrlist;	/* array of ptrs to toks in attrlistp */
91 static int		nattrs;
92 static cpc_t		*cpc;
93 static int		found;
94 
95 static void
96 strtoset_err(const char *fmt, ...)
97 {
98 	va_list ap;
99 
100 	if (strtoset_errfn == NULL)
101 		return;
102 
103 	va_start(ap, fmt);
104 	(*strtoset_errfn)("cpc_strtoset", -1, fmt, ap);
105 	va_end(ap);
106 }
107 
108 /*ARGSUSED*/
109 static void
110 event_walker(void *arg, uint_t picno, const char *event)
111 {
112 	if (strncmp(arg, event, CPC_MAX_EVENT_LEN) == 0)
113 		found = 1;
114 }
115 
116 static int
117 event_valid(int picnum, char *event)
118 {
119 	char *end_event;
120 	found = 0;
121 
122 
123 	cpc_walk_events_pic(cpc, picnum, event, event_walker);
124 
125 	if (found)
126 		return (1);
127 
128 	/*
129 	 * Before assuming this is an invalid event, see if we have been given
130 	 * a raw event code. An event code of '0' is not recognized, as it
131 	 * already has a corresponding event name in existing backends and it
132 	 * is the only reasonable way to know if strtol() succeeded.
133 	 * Check the second argument of strtol() to ensure invalid events
134 	 * beginning with number do not go through.
135 	 */
136 	if ((strtol(event, &end_event, 0) != 0) && (*end_event == '\0'))
137 		/*
138 		 * Success - this is a valid raw code in hex, decimal, or octal.
139 		 */
140 		return (1);
141 
142 	return (0);
143 }
144 
145 /*
146  * An unknown token was encountered; check here if it is an implicit event
147  * name. We allow users to omit the "picn=" portion of the event spec, and
148  * assign such events to available pics in order they are returned from
149  * getsubopt(3C). We start our search for an available pic _after_ the highest
150  * picnum to be assigned. This ensures that the event spec can never be out of
151  * order; i.e. if the event string is "eventa,eventb" we must ensure that the
152  * picnum counting eventa is less than the picnum counting eventb.
153  */
154 static int
155 find_event(char *event)
156 {
157 	int i;
158 
159 	/*
160 	 * Event names cannot have '=' in them. If present here, it means we
161 	 * have encountered an unknown token (foo=bar, for example).
162 	 */
163 	if (strchr(event, '=') != NULL)
164 		return (0);
165 
166 	/*
167 	 * Find the first unavailable pic, after which we must start our search.
168 	 */
169 	for (i = ncounters - 1; i >= 0; i--) {
170 		if (reqs[i].cr_event[0] != '\0')
171 			break;
172 	}
173 	/*
174 	 * If the last counter has been assigned, we cannot place this event.
175 	 */
176 	if (i == ncounters - 1)
177 		return (0);
178 
179 	/*
180 	 * If none of the counters have been assigned yet, i is -1 and we will
181 	 * begin our search at 0. Else we begin our search at the counter after
182 	 * the last one currently assigned.
183 	 */
184 	i++;
185 
186 	for (; i < ncounters; i++) {
187 		if (event_valid(i, event) == 0)
188 			continue;
189 
190 		nreqs++;
191 		(void) strncpy(reqs[i].cr_event, event, CPC_MAX_EVENT_LEN);
192 		return (1);
193 	}
194 
195 	return (0);
196 }
197 
198 static int
199 pic(int tok, char *val)
200 {
201 	int picnum = tok_info[tok].picnum;
202 	/*
203 	 * Make sure the each pic only appears in the spec once.
204 	 */
205 	if (reqs[picnum].cr_event[0] != '\0') {
206 		strtoset_err(gettext("repeated 'pic%d' token\n"), picnum);
207 		return (-1);
208 	}
209 
210 	if (val == NULL || val[0] == '\0') {
211 		strtoset_err(gettext("missing 'pic%d' value\n"), picnum);
212 		return (-1);
213 	}
214 
215 	if (event_valid(picnum, val) == 0) {
216 		strtoset_err(gettext("pic%d cannot measure event '%s' on this "
217 		    "cpu\n"), picnum, val);
218 		return (-1);
219 	}
220 
221 	nreqs++;
222 	(void) strncpy(reqs[picnum].cr_event, val, CPC_MAX_EVENT_LEN);
223 	return (0);
224 }
225 
226 /*
227  * We explicitly ignore any value provided for these tokens, as their
228  * mere presence signals us to turn on or off the relevant flags.
229  */
230 /*ARGSUSED*/
231 static int
232 flag(int tok, char *val)
233 {
234 	int i;
235 	int picnum = tok_info[tok].picnum;
236 
237 	/*
238 	 * If picnum is -1, this flag should be applied to all reqs.
239 	 */
240 	for (i = (picnum == -1) ? 0 : picnum; i < ncounters; i++) {
241 		if (strcmp(tok_info[tok].name, "nouser") == 0)
242 			reqs[i].cr_flags &= ~CPC_COUNT_USER;
243 		else if (strcmp(tok_info[tok].name, "sys") == 0)
244 			reqs[i].cr_flags |= CPC_COUNT_SYSTEM;
245 		else
246 			return (-1);
247 
248 		if (picnum != -1)
249 			break;
250 	}
251 
252 	return (0);
253 }
254 
255 static int
256 doattr(int tok, char *val)
257 {
258 	int		i;
259 	int		picnum = tok_info[tok].picnum;
260 	tmp_attr_t	*tmp;
261 	char		*endptr;
262 
263 	/*
264 	 * If picnum is -1, this attribute should be applied to all reqs.
265 	 */
266 	for (i = (picnum == -1) ? 0 : picnum; i < ncounters; i++) {
267 		tmp = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t));
268 		tmp->name = tok_info[tok].name;
269 		if (val != NULL) {
270 			tmp->val = strtoll(val, &endptr, 0);
271 			if (endptr == val) {
272 				strtoset_err(gettext("invalid value '%s' for "
273 				    "attribute '%s'\n"), val, tmp->name);
274 				free(tmp);
275 				return (-1);
276 			}
277 		} else
278 			/*
279 			 * No value was provided for this attribute,
280 			 * so specify a default value of 1.
281 			 */
282 			tmp->val = 1;
283 
284 		tmp->next = attrs[i];
285 		attrs[i] = tmp;
286 		reqs[i].cr_nattrs++;
287 
288 		if (picnum != -1)
289 			break;
290 	}
291 
292 	return (0);
293 }
294 
295 /*ARGSUSED*/
296 static void
297 attr_count_walker(void *arg, const char *attr)
298 {
299 	/*
300 	 * We don't allow picnum to be specified by the user.
301 	 */
302 	if (strncmp(attr, "picnum", 7) == 0)
303 		return;
304 	(*(int *)arg)++;
305 }
306 
307 static int
308 cpc_count_attrs(cpc_t *cpc)
309 {
310 	int nattrs = 0;
311 
312 	cpc_walk_attrs(cpc, &nattrs, attr_count_walker);
313 
314 	return (nattrs);
315 }
316 
317 static void
318 attr_walker(void *arg, const char *attr)
319 {
320 	int *i = arg;
321 
322 	if (strncmp(attr, "picnum", 7) == 0)
323 		return;
324 
325 	if ((attrlist[(*i)++] = strdup(attr)) == NULL) {
326 		strtoset_err(gettext("no memory available\n"));
327 		exit(0);
328 	}
329 }
330 
331 cpc_set_t *
332 cpc_strtoset(cpc_t *cpcin, const char *spec, int smt)
333 {
334 	cpc_set_t		*set;
335 	cpc_attr_t		*req_attrs;
336 	tmp_attr_t		*tmp;
337 	size_t			toklen;
338 	int			i;
339 	int			j;
340 	int			x;
341 	char			*opts;
342 	char			*val;
343 
344 	cpc = cpcin;
345 	nattrs = 0;
346 
347 	ncounters = cpc_npic(cpc);
348 
349 	reqs = (request_t *)emalloc(ncounters * sizeof (request_t));
350 
351 	attrs = (tmp_attr_t **)emalloc(ncounters * sizeof (tmp_attr_t *));
352 
353 	for (i = 0; i < ncounters; i++) {
354 		reqs[i].cr_event[0] = '\0';
355 		reqs[i].cr_flags = CPC_COUNT_USER;
356 		/*
357 		 * Each pic will have at least one attribute: the physical pic
358 		 * assignment via the "picnum" attribute. Set that up here for
359 		 * each request.
360 		 */
361 		reqs[i].cr_nattrs = 1;
362 		attrs[i] = emalloc(sizeof (tmp_attr_t));
363 		attrs[i]->name = "picnum";
364 		attrs[i]->val = i;
365 		attrs[i]->next = NULL;
366 	}
367 
368 	/*
369 	 * Build up a list of acceptable tokens.
370 	 *
371 	 * Permitted tokens are
372 	 * picn=event
373 	 * nousern
374 	 * sysn
375 	 * attrn=val
376 	 * nouser
377 	 * sys
378 	 * attr=val
379 	 *
380 	 * Where n is a counter number, and attr is any attribute supported by
381 	 * the current processor.
382 	 *
383 	 * If a token appears without a counter number, it applies to all
384 	 * counters in the request set.
385 	 *
386 	 * The number of tokens is:
387 	 *
388 	 * picn: ncounters
389 	 * generic flags: 2 * ncounters (nouser, sys)
390 	 * attrs: nattrs * ncounters
391 	 * attrs with no picnum: nattrs
392 	 * generic flags with no picnum: 2 (nouser, sys)
393 	 * NULL token to signify end of list to getsubopt(3C).
394 	 *
395 	 * Matching each token's index in the token table is a function which
396 	 * process that token; these are in tok_funcs.
397 	 */
398 
399 	/*
400 	 * Count the number of valid attributes.
401 	 * Set up the attrlist array to point to the attributes in attrlistp.
402 	 */
403 	nattrs = cpc_count_attrs(cpc);
404 	attrlist = (char **)emalloc(nattrs * sizeof (char *));
405 
406 	i = 0;
407 	cpc_walk_attrs(cpc, &i, attr_walker);
408 
409 	ntoks = ncounters + (2 * ncounters) + (nattrs * ncounters) + nattrs + 3;
410 	toks = (char **)emalloc(ntoks * sizeof (char *));
411 	tok_info = (tok_info_t *)emalloc(ntoks * sizeof (tok_info_t));
412 
413 	tok_funcs = (int (**)(int, char *))emalloc(ntoks *
414 	    sizeof (int (*)(char *)));
415 
416 	for (i = 0; i < ntoks; i++) {
417 		toks[i] = NULL;
418 		tok_funcs[i] = NULL;
419 	}
420 
421 	x = 0;
422 	for (i = 0; i < ncounters; i++) {
423 		toks[x] = (char *)emalloc(TOK_SIZE);
424 		(void) snprintf(toks[x], TOK_SIZE, "pic%d", i);
425 		tok_info[x].name = "pic";
426 		tok_info[i].picnum = i;
427 		tok_funcs[x] = pic;
428 		x++;
429 	}
430 
431 	for (i = 0; i < ncounters; i++) {
432 		toks[x] = (char *)emalloc(TOK_SIZE);
433 		(void) snprintf(toks[x], TOK_SIZE, "nouser%d", i);
434 		tok_info[x].name = "nouser";
435 		tok_info[x].picnum = i;
436 		tok_funcs[x] = flag;
437 		x++;
438 	}
439 
440 	for (i = 0; i < ncounters; i++) {
441 		toks[x] = (char *)emalloc(TOK_SIZE);
442 		(void) snprintf(toks[x], TOK_SIZE, "sys%d", i);
443 		tok_info[x].name = "sys";
444 		tok_info[x].picnum = i;
445 		tok_funcs[x] = flag;
446 		x++;
447 	}
448 	for (j = 0; j < nattrs; j++) {
449 		toklen = strlen(attrlist[j]) + 3;
450 		for (i = 0; i < ncounters; i++) {
451 			toks[x] = (char *)emalloc(toklen);
452 			(void) snprintf(toks[x], toklen, "%s%d", attrlist[j],
453 			    i);
454 			tok_info[x].name = attrlist[j];
455 			tok_info[x].picnum = i;
456 			tok_funcs[x] = doattr;
457 			x++;
458 		}
459 
460 		/*
461 		 * Now create a token for this attribute with no picnum; if used
462 		 * it will be applied to all reqs.
463 		 */
464 		toks[x] = (char *)emalloc(toklen);
465 		(void) snprintf(toks[x], toklen, "%s", attrlist[j]);
466 		tok_info[x].name = attrlist[j];
467 		tok_info[x].picnum = -1;
468 		tok_funcs[x] = doattr;
469 		x++;
470 	}
471 
472 	toks[x] = "nouser";
473 	tok_info[x].name = "nouser";
474 	tok_info[x].picnum = -1;
475 	tok_funcs[x] = flag;
476 	x++;
477 
478 	toks[x] = "sys";
479 	tok_info[x].name = "sys";
480 	tok_info[x].picnum = -1;
481 	tok_funcs[x] = flag;
482 	x++;
483 
484 	toks[x] = NULL;
485 
486 	opts = strcpy(alloca(strlen(spec) + 1), spec);
487 	while (*opts != '\0') {
488 		int idx = getsubopt(&opts, toks, &val);
489 
490 		if (idx == -1) {
491 			if (find_event(val) == 0) {
492 				strtoset_err(gettext("bad token '%s'\n"), val);
493 				goto inval;
494 			} else
495 				continue;
496 		}
497 
498 		if (tok_funcs[idx](idx, val) == -1)
499 			goto inval;
500 	}
501 
502 	/*
503 	 * The string has been processed. Now count how many PICs were used,
504 	 * create a request set, and specify each request properly.
505 	 */
506 
507 	if ((set = cpc_set_create(cpc)) == NULL) {
508 		strtoset_err(gettext("no memory available\n"));
509 		exit(0);
510 	}
511 
512 	for (i = 0; i < ncounters; i++) {
513 		if (reqs[i].cr_event[0] == '\0')
514 			continue;
515 
516 		/*
517 		 * If the caller wishes to measure events on the physical CPU,
518 		 * we need to add SMT attributes to each request.
519 		 */
520 		if (smt)
521 			smt_special(i);
522 
523 		req_attrs = (cpc_attr_t *)emalloc(reqs[i].cr_nattrs *
524 		    sizeof (cpc_attr_t));
525 
526 		j = 0;
527 		for (tmp = attrs[i]; tmp != NULL; tmp = tmp->next) {
528 			req_attrs[j].ca_name = tmp->name;
529 			req_attrs[j].ca_val = tmp->val;
530 			j++;
531 		}
532 
533 		if (cpc_set_add_request(cpc, set, reqs[i].cr_event, 0,
534 		    reqs[i].cr_flags, reqs[i].cr_nattrs, req_attrs) == -1) {
535 			free(req_attrs);
536 			(void) cpc_set_destroy(cpc, set);
537 			strtoset_err(
538 			    gettext("cpc_set_add_request() failed: %s\n"),
539 			    strerror(errno));
540 			goto inval;
541 		}
542 
543 		free(req_attrs);
544 	}
545 
546 	strtoset_cleanup();
547 
548 	return (set);
549 
550 inval:
551 	strtoset_cleanup();
552 	errno = EINVAL;
553 	return (NULL);
554 }
555 
556 static void
557 strtoset_cleanup(void)
558 {
559 	int		i;
560 	tmp_attr_t	*tmp, *p;
561 
562 	for (i = 0; i < nattrs; i++)
563 		free(attrlist[i]);
564 	free(attrlist);
565 
566 	for (i = 0; i < ncounters; i++) {
567 		for (tmp = attrs[i]; tmp != NULL; tmp = p) {
568 			p = tmp->next;
569 			free(tmp);
570 		}
571 	}
572 	free(attrs);
573 
574 	for (i = 0; i < ntoks - 3; i++)
575 		/*
576 		 * We free all but the last three tokens: "nouser", "sys", NULL
577 		 */
578 		free(toks[i]);
579 	free(toks);
580 	free(reqs);
581 	free(tok_info);
582 	free(tok_funcs);
583 }
584 
585 /*
586  * The following is called to modify requests so that they count events on
587  * behalf of a physical processor, instead of a logical processor. It duplicates
588  * the request flags for the sibling processor (i.e. if the request counts user
589  * events, add an attribute to count user events on the sibling processor also).
590  */
591 static void
592 smt_special(int picnum)
593 {
594 	tmp_attr_t *attr;
595 
596 	if (reqs[picnum].cr_flags & CPC_COUNT_USER) {
597 		attr = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t));
598 		attr->name = "count_sibling_usr";
599 		attr->val = 1;
600 		attr->next = attrs[picnum];
601 		attrs[picnum] = attr;
602 		reqs[picnum].cr_nattrs++;
603 	}
604 
605 	if (reqs[picnum].cr_flags & CPC_COUNT_SYSTEM) {
606 		attr = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t));
607 		attr->name = "count_sibling_sys";
608 		attr->val = 1;
609 		attr->next = attrs[picnum];
610 		attrs[picnum] = attr;
611 		reqs[picnum].cr_nattrs++;
612 	}
613 }
614 
615 /*
616  * If we ever fail to get memory, we print an error message and exit.
617  */
618 static void *
619 emalloc(size_t n)
620 {
621 	void *p = malloc(n);
622 
623 	if (p == NULL) {
624 		strtoset_err(gettext("no memory available\n"));
625 		exit(0);
626 	}
627 
628 	return (p);
629 }
630