xref: /illumos-gate/usr/src/cmd/cpc/common/strtoset.c (revision cb6207858a9fcc2feaee22e626912fba281ac969)
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 2006 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 	found = 0;
120 
121 	cpc_walk_events_pic(cpc, picnum, event, event_walker);
122 
123 	if (found)
124 		return (1);
125 
126 	/*
127 	 * Before assuming this is an invalid event, see if we have been given
128 	 * a raw event code. An event code of '0' is not recognized, as it
129 	 * already has a corresponding event name in existing backends and it
130 	 * is the only reasonable way to know if strtol() succeeded.
131 	 */
132 	if (strtol(event, NULL, 0) != 0)
133 		/*
134 		 * Success - this is a valid raw code in hex, decimal, or octal.
135 		 */
136 		return (1);
137 
138 	return (0);
139 }
140 
141 /*
142  * An unknown token was encountered; check here if it is an implicit event
143  * name. We allow users to omit the "picn=" portion of the event spec, and
144  * assign such events to available pics in order they are returned from
145  * getsubopt(3C). We start our search for an available pic _after_ the highest
146  * picnum to be assigned. This ensures that the event spec can never be out of
147  * order; i.e. if the event string is "eventa,eventb" we must ensure that the
148  * picnum counting eventa is less than the picnum counting eventb.
149  */
150 static int
151 find_event(char *event)
152 {
153 	int i;
154 
155 	/*
156 	 * Event names cannot have '=' in them. If present here, it means we
157 	 * have encountered an unknown token (foo=bar, for example).
158 	 */
159 	if (strchr(event, '=') != NULL)
160 		return (0);
161 
162 	/*
163 	 * Find the first unavailable pic, after which we must start our search.
164 	 */
165 	for (i = ncounters - 1; i >= 0; i--) {
166 		if (reqs[i].cr_event[0] != '\0')
167 			break;
168 	}
169 	/*
170 	 * If the last counter has been assigned, we cannot place this event.
171 	 */
172 	if (i == ncounters - 1)
173 		return (0);
174 
175 	/*
176 	 * If none of the counters have been assigned yet, i is -1 and we will
177 	 * begin our search at 0. Else we begin our search at the counter after
178 	 * the last one currently assigned.
179 	 */
180 	i++;
181 
182 	for (; i < ncounters; i++) {
183 		if (event_valid(i, event) == 0)
184 			continue;
185 
186 		nreqs++;
187 		(void) strncpy(reqs[i].cr_event, event, CPC_MAX_EVENT_LEN);
188 		return (1);
189 	}
190 
191 	return (0);
192 }
193 
194 static int
195 pic(int tok, char *val)
196 {
197 	int picnum = tok_info[tok].picnum;
198 	/*
199 	 * Make sure the each pic only appears in the spec once.
200 	 */
201 	if (reqs[picnum].cr_event[0] != '\0') {
202 		strtoset_err(gettext("repeated 'pic%d' token\n"), picnum);
203 		return (-1);
204 	}
205 
206 	if (val == NULL || val[0] == '\0') {
207 		strtoset_err(gettext("missing 'pic%d' value\n"), picnum);
208 		return (-1);
209 	}
210 
211 	if (event_valid(picnum, val) == 0) {
212 		strtoset_err(gettext("pic%d cannot measure event '%s' on this "
213 		    "cpu\n"), picnum, val);
214 		return (-1);
215 	}
216 
217 	nreqs++;
218 	(void) strncpy(reqs[picnum].cr_event, val, CPC_MAX_EVENT_LEN);
219 	return (0);
220 }
221 
222 /*
223  * We explicitly ignore any value provided for these tokens, as their
224  * mere presence signals us to turn on or off the relevant flags.
225  */
226 /*ARGSUSED*/
227 static int
228 flag(int tok, char *val)
229 {
230 	int i;
231 	int picnum = tok_info[tok].picnum;
232 
233 	/*
234 	 * If picnum is -1, this flag should be applied to all reqs.
235 	 */
236 	for (i = (picnum == -1) ? 0 : picnum; i < ncounters; i++) {
237 		if (strcmp(tok_info[tok].name, "nouser") == 0)
238 			reqs[i].cr_flags &= ~CPC_COUNT_USER;
239 		else if (strcmp(tok_info[tok].name, "sys") == 0)
240 			reqs[i].cr_flags |= CPC_COUNT_SYSTEM;
241 		else
242 			return (-1);
243 
244 		if (picnum != -1)
245 			break;
246 	}
247 
248 	return (0);
249 }
250 
251 static int
252 doattr(int tok, char *val)
253 {
254 	int		i;
255 	int		picnum = tok_info[tok].picnum;
256 	tmp_attr_t	*tmp;
257 	char		*endptr;
258 
259 	/*
260 	 * If picnum is -1, this attribute should be applied to all reqs.
261 	 */
262 	for (i = (picnum == -1) ? 0 : picnum; i < ncounters; i++) {
263 		tmp = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t));
264 		tmp->name = tok_info[tok].name;
265 		if (val != NULL) {
266 			tmp->val = strtoll(val, &endptr, 0);
267 			if (endptr == val) {
268 				strtoset_err(gettext("invalid value '%s' for "
269 				    "attribute '%s'\n"), val, tmp->name);
270 				free(tmp);
271 				return (-1);
272 			}
273 		} else
274 			/*
275 			 * No value was provided for this attribute,
276 			 * so specify a default value of 1.
277 			 */
278 			tmp->val = 1;
279 
280 		tmp->next = attrs[i];
281 		attrs[i] = tmp;
282 		reqs[i].cr_nattrs++;
283 
284 		if (picnum != -1)
285 			break;
286 	}
287 
288 	return (0);
289 }
290 
291 /*ARGSUSED*/
292 static void
293 attr_count_walker(void *arg, const char *attr)
294 {
295 	/*
296 	 * We don't allow picnum to be specified by the user.
297 	 */
298 	if (strncmp(attr, "picnum", 7) == 0)
299 		return;
300 	(*(int *)arg)++;
301 }
302 
303 static int
304 cpc_count_attrs(cpc_t *cpc)
305 {
306 	int nattrs = 0;
307 
308 	cpc_walk_attrs(cpc, &nattrs, attr_count_walker);
309 
310 	return (nattrs);
311 }
312 
313 static void
314 attr_walker(void *arg, const char *attr)
315 {
316 	int *i = arg;
317 
318 	if (strncmp(attr, "picnum", 7) == 0)
319 		return;
320 
321 	if ((attrlist[(*i)++] = strdup(attr)) == NULL) {
322 		strtoset_err(gettext("no memory available\n"));
323 		exit(0);
324 	}
325 }
326 
327 cpc_set_t *
328 cpc_strtoset(cpc_t *cpcin, const char *spec, int smt)
329 {
330 	cpc_set_t		*set;
331 	cpc_attr_t		*req_attrs;
332 	tmp_attr_t		*tmp;
333 	size_t			toklen;
334 	int			i;
335 	int			j;
336 	int			x;
337 	char			*opts;
338 	char			*val;
339 
340 	cpc = cpcin;
341 	nattrs = 0;
342 
343 	ncounters = cpc_npic(cpc);
344 
345 	reqs = (request_t *)emalloc(ncounters * sizeof (request_t));
346 
347 	attrs = (tmp_attr_t **)emalloc(ncounters * sizeof (tmp_attr_t *));
348 
349 	for (i = 0; i < ncounters; i++) {
350 		reqs[i].cr_event[0] = '\0';
351 		reqs[i].cr_flags = CPC_COUNT_USER;
352 		/*
353 		 * Each pic will have at least one attribute: the physical pic
354 		 * assignment via the "picnum" attribute. Set that up here for
355 		 * each request.
356 		 */
357 		reqs[i].cr_nattrs = 1;
358 		attrs[i] = emalloc(sizeof (tmp_attr_t));
359 		attrs[i]->name = "picnum";
360 		attrs[i]->val = i;
361 		attrs[i]->next = NULL;
362 	}
363 
364 	/*
365 	 * Build up a list of acceptable tokens.
366 	 *
367 	 * Permitted tokens are
368 	 * picn=event
369 	 * nousern
370 	 * sysn
371 	 * attrn=val
372 	 * nouser
373 	 * sys
374 	 * attr=val
375 	 *
376 	 * Where n is a counter number, and attr is any attribute supported by
377 	 * the current processor.
378 	 *
379 	 * If a token appears without a counter number, it applies to all
380 	 * counters in the request set.
381 	 *
382 	 * The number of tokens is:
383 	 *
384 	 * picn: ncounters
385 	 * generic flags: 2 * ncounters (nouser, sys)
386 	 * attrs: nattrs * ncounters
387 	 * attrs with no picnum: nattrs
388 	 * generic flags with no picnum: 2 (nouser, sys)
389 	 * NULL token to signify end of list to getsubopt(3C).
390 	 *
391 	 * Matching each token's index in the token table is a function which
392 	 * process that token; these are in tok_funcs.
393 	 */
394 
395 	/*
396 	 * Count the number of valid attributes.
397 	 * Set up the attrlist array to point to the attributes in attrlistp.
398 	 */
399 	nattrs = cpc_count_attrs(cpc);
400 	attrlist = (char **)emalloc(nattrs * sizeof (char *));
401 
402 	i = 0;
403 	cpc_walk_attrs(cpc, &i, attr_walker);
404 
405 	ntoks = ncounters + (2 * ncounters) + (nattrs * ncounters) + nattrs + 3;
406 	toks = (char **)emalloc(ntoks * sizeof (char *));
407 	tok_info = (tok_info_t *)emalloc(ntoks * sizeof (tok_info_t));
408 
409 	tok_funcs = (int (**)(int, char *))emalloc(ntoks *
410 	    sizeof (int (*)(char *)));
411 
412 	for (i = 0; i < ntoks; i++) {
413 		toks[i] = NULL;
414 		tok_funcs[i] = NULL;
415 	}
416 
417 	x = 0;
418 	for (i = 0; i < ncounters; i++) {
419 		toks[x] = (char *)emalloc(TOK_SIZE);
420 		(void) snprintf(toks[x], TOK_SIZE, "pic%d", i);
421 		tok_info[x].name = "pic";
422 		tok_info[i].picnum = i;
423 		tok_funcs[x] = pic;
424 		x++;
425 	}
426 
427 	for (i = 0; i < ncounters; i++) {
428 		toks[x] = (char *)emalloc(TOK_SIZE);
429 		(void) snprintf(toks[x], TOK_SIZE, "nouser%d", i);
430 		tok_info[x].name = "nouser";
431 		tok_info[x].picnum = i;
432 		tok_funcs[x] = flag;
433 		x++;
434 	}
435 
436 	for (i = 0; i < ncounters; i++) {
437 		toks[x] = (char *)emalloc(TOK_SIZE);
438 		(void) snprintf(toks[x], TOK_SIZE, "sys%d", i);
439 		tok_info[x].name = "sys";
440 		tok_info[x].picnum = i;
441 		tok_funcs[x] = flag;
442 		x++;
443 	}
444 	for (j = 0; j < nattrs; j++) {
445 		toklen = strlen(attrlist[j]) + 3;
446 		for (i = 0; i < ncounters; i++) {
447 			toks[x] = (char *)emalloc(toklen);
448 			(void) snprintf(toks[x], toklen, "%s%d", attrlist[j],
449 			    i);
450 			tok_info[x].name = attrlist[j];
451 			tok_info[x].picnum = i;
452 			tok_funcs[x] = doattr;
453 			x++;
454 		}
455 
456 		/*
457 		 * Now create a token for this attribute with no picnum; if used
458 		 * it will be applied to all reqs.
459 		 */
460 		toks[x] = (char *)emalloc(toklen);
461 		(void) snprintf(toks[x], toklen, "%s", attrlist[j]);
462 		tok_info[x].name = attrlist[j];
463 		tok_info[x].picnum = -1;
464 		tok_funcs[x] = doattr;
465 		x++;
466 	}
467 
468 	toks[x] = "nouser";
469 	tok_info[x].name = "nouser";
470 	tok_info[x].picnum = -1;
471 	tok_funcs[x] = flag;
472 	x++;
473 
474 	toks[x] = "sys";
475 	tok_info[x].name = "sys";
476 	tok_info[x].picnum = -1;
477 	tok_funcs[x] = flag;
478 	x++;
479 
480 	toks[x] = NULL;
481 
482 	opts = strcpy(alloca(strlen(spec) + 1), spec);
483 	while (*opts != '\0') {
484 		int idx = getsubopt(&opts, toks, &val);
485 
486 		if (idx == -1) {
487 			if (find_event(val) == 0) {
488 				strtoset_err(gettext("bad token '%s'\n"), val);
489 				goto inval;
490 			} else
491 				continue;
492 		}
493 
494 		if (tok_funcs[idx](idx, val) == -1)
495 			goto inval;
496 	}
497 
498 	/*
499 	 * The string has been processed. Now count how many PICs were used,
500 	 * create a request set, and specify each request properly.
501 	 */
502 
503 	if ((set = cpc_set_create(cpc)) == NULL) {
504 		strtoset_err(gettext("no memory available\n"));
505 		exit(0);
506 	}
507 
508 	for (i = 0; i < ncounters; i++) {
509 		if (reqs[i].cr_event[0] == '\0')
510 			continue;
511 
512 		/*
513 		 * If the caller wishes to measure events on the physical CPU,
514 		 * we need to add SMT attributes to each request.
515 		 */
516 		if (smt)
517 			smt_special(i);
518 
519 		req_attrs = (cpc_attr_t *)emalloc(reqs[i].cr_nattrs *
520 		    sizeof (cpc_attr_t));
521 
522 		j = 0;
523 		for (tmp = attrs[i]; tmp != NULL; tmp = tmp->next) {
524 			req_attrs[j].ca_name = tmp->name;
525 			req_attrs[j].ca_val = tmp->val;
526 			j++;
527 		}
528 
529 		if (cpc_set_add_request(cpc, set, reqs[i].cr_event, 0,
530 		    reqs[i].cr_flags, reqs[i].cr_nattrs, req_attrs) == -1) {
531 			free(req_attrs);
532 			(void) cpc_set_destroy(cpc, set);
533 			strtoset_err(
534 				gettext("cpc_set_add_request() failed: %s\n"),
535 				    strerror(errno));
536 			goto inval;
537 		}
538 
539 		free(req_attrs);
540 	}
541 
542 	strtoset_cleanup();
543 
544 	return (set);
545 
546 inval:
547 	strtoset_cleanup();
548 	errno = EINVAL;
549 	return (NULL);
550 }
551 
552 static void
553 strtoset_cleanup(void)
554 {
555 	int		i;
556 	tmp_attr_t	*tmp, *p;
557 
558 	for (i = 0; i < nattrs; i++)
559 		free(attrlist[i]);
560 	free(attrlist);
561 
562 	for (i = 0; i < ncounters; i++) {
563 		for (tmp = attrs[i]; tmp != NULL; tmp = p) {
564 			p = tmp->next;
565 			free(tmp);
566 		}
567 	}
568 	free(attrs);
569 
570 	for (i = 0; i < ntoks - 3; i++)
571 		/*
572 		 * We free all but the last three tokens: "nouser", "sys", NULL
573 		 */
574 		free(toks[i]);
575 	free(toks);
576 	free(reqs);
577 	free(tok_info);
578 	free(tok_funcs);
579 }
580 
581 /*
582  * The following is called to modify requests so that they count events on
583  * behalf of a physical processor, instead of a logical processor. It duplicates
584  * the request flags for the sibling processor (i.e. if the request counts user
585  * events, add an attribute to count user events on the sibling processor also).
586  */
587 static void
588 smt_special(int picnum)
589 {
590 	tmp_attr_t *attr;
591 
592 	if (reqs[picnum].cr_flags & CPC_COUNT_USER) {
593 		attr = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t));
594 		attr->name = "count_sibling_usr";
595 		attr->val = 1;
596 		attr->next = attrs[picnum];
597 		attrs[picnum] = attr;
598 		reqs[picnum].cr_nattrs++;
599 	}
600 
601 	if (reqs[picnum].cr_flags & CPC_COUNT_SYSTEM) {
602 		attr = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t));
603 		attr->name = "count_sibling_sys";
604 		attr->val = 1;
605 		attr->next = attrs[picnum];
606 		attrs[picnum] = attr;
607 		reqs[picnum].cr_nattrs++;
608 	}
609 }
610 
611 /*
612  * If we ever fail to get memory, we print an error message and exit.
613  */
614 static void *
615 emalloc(size_t n)
616 {
617 	void *p = malloc(n);
618 
619 	if (p == NULL) {
620 		strtoset_err(gettext("no memory available\n"));
621 		exit(0);
622 	}
623 
624 	return (p);
625 }
626