xref: /titanic_51/usr/src/cmd/latencytop/common/table.c (revision de3d2ce46fc25c7b67ccbae4afe5f15e5357568f)
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 (c) 2008-2009, Intel Corporation.
23  * All Rights Reserved.
24  */
25 
26 #include <stdlib.h>
27 #include <string.h>
28 #include <memory.h>
29 #include <stdio.h>
30 #include <ctype.h>
31 
32 #include "latencytop.h"
33 
34 /*
35  * Structure that holds detail of a cause.
36  */
37 typedef struct {
38 	int cause_id;
39 	int flags;
40 	char *name;
41 } lt_cause_t;
42 
43 /*
44  * Structure that represents a matched cause.
45  */
46 typedef struct  {
47 	int priority;
48 	int cause_id;
49 } lt_match_t;
50 
51 /* All lt_cause_t that are created. */
52 static GPtrArray *causes_array = NULL;
53 static int causes_array_len = 0;
54 /*
55  * This hash table maps a symbol to a cause entry.
56  * key type is "char *" and value type is "lt_match_t *".
57  */
58 static GHashTable *symbol_lookup_table = NULL;
59 /*
60  * This hash table maps a cause name to an cause id.
61  * Note only cause names that are found in D script is put in this table.
62  * key type is "char *" and value type is "int" (which is cause_id).
63  */
64 static GHashTable *named_causes = NULL;
65 
66 /*
67  * Help function to free one lt_cause_t structure.
68  */
69 /* ARGSUSED */
70 static void
71 free_cause(lt_cause_t *cause, void *user)
72 {
73 	g_assert(cause != NULL && cause->name != NULL);
74 
75 	free(cause->name);
76 	free(cause);
77 }
78 
79 /*
80  * Add a cause.
81  * Note this function takes ownership of char *name.
82  */
83 static lt_cause_t *
84 new_cause(char *name, int flags)
85 {
86 	lt_cause_t *entry;
87 
88 	g_assert(name != NULL);
89 
90 	entry = (lt_cause_t *)lt_malloc(sizeof (lt_cause_t));
91 	entry->flags = flags;
92 	entry->name = name;
93 	entry->cause_id = causes_array_len;
94 
95 	g_ptr_array_add(causes_array, entry);
96 	++causes_array_len;
97 
98 	return (entry);
99 }
100 
101 /*
102  * Set a cause to "disabled" state.
103  */
104 static void
105 disable_cause(char *cause_str, GHashTable *cause_table)
106 {
107 	lt_cause_t *cause;
108 
109 	cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str);
110 	if (cause != NULL) {
111 		cause->flags |= CAUSE_FLAG_DISABLED;
112 	}
113 }
114 
115 /*
116  * Helper functions that reads a line from a char * array.
117  */
118 static int
119 read_line_from_mem(const char *mem, int mem_len, char *line, int line_len,
120     int *index)
121 {
122 	g_assert(mem != NULL && line != NULL && index != NULL);
123 
124 	if (line_len <= 0 || mem_len <= 0) {
125 		return (0);
126 	}
127 	if (*index >= mem_len) {
128 		return (0);
129 	}
130 
131 	while (line_len > 1 && *index < mem_len) {
132 		*line = mem[(*index)++];
133 		--line_len;
134 		++line;
135 		if (*(line-1) == '\r' || *(line-1) == '\n') {
136 			break;
137 		}
138 	}
139 	*line = 0;
140 
141 	return (1);
142 }
143 
144 /*
145  * The main loop that parses the translation rules one line at a time,
146  * and construct latencytop lookup data structure from it.
147  */
148 static int
149 parse_config(const char *work, int work_len)
150 {
151 	char line[256];
152 	int len;
153 	char *begin, *end, *tmp;
154 	int priority = 0;
155 	char *match;
156 	char *match_dup;
157 	char *cause_str;
158 	lt_cause_t *cause;
159 	lt_match_t *match_entry;
160 	int current = 0;
161 	GHashTable *cause_lookup;
162 	GSequence *cmd_disable;
163 
164 	cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
165 	lt_check_null(cause_lookup);
166 
167 	cmd_disable = g_sequence_new((GDestroyNotify)free);
168 	lt_check_null(cmd_disable);
169 
170 	while (read_line_from_mem(work, work_len, line, sizeof (line),
171 	    &current)) {
172 		len = strlen(line);
173 		if (line[len-1] != '\n' && line[len-1] != '\r' &&
174 		    current < work_len) {
175 			lt_display_error("Configuration line too long.\n");
176 			goto err;
177 		}
178 
179 		begin = line;
180 		while (isspace(*begin)) {
181 			++begin;
182 		}
183 		if (*begin == '\0') {
184 			/* empty line, ignore */
185 			continue;
186 		}
187 
188 		/* Delete trailing spaces. */
189 		end = begin + strlen(begin) - 1;
190 		while (isspace(*end)) {
191 			--end;
192 		}
193 		end[1] = 0;
194 
195 		if (*begin == '#') {
196 			continue;
197 		} else if (*begin == ';') {
198 			char old_chr = 0;
199 			/* special command */
200 			/* ; disable_cause  FSFlush Daemon  */
201 			/* ^ */
202 			++begin;
203 
204 			while (isspace(*begin)) {
205 				++begin;
206 			}
207 			/* ; disable_cause  FSFlush Daemon  */
208 			/*   ^ */
209 			if (*begin == '\0') {
210 				continue;
211 			}
212 
213 			for (tmp = begin;
214 			    *tmp != '\0' && !isspace(*tmp);
215 			    ++tmp) {
216 			}
217 			old_chr = *tmp;
218 			*tmp = 0;
219 
220 			if (strcmp("disable_cause", begin) == 0) {
221 				if (old_chr == '\0') {
222 					/* Must have an argument */
223 					lt_display_error(
224 					    "Invalid command format: %s\n",
225 					    begin);
226 					goto err;
227 				}
228 
229 				begin = tmp+1;
230 				while (isspace(*begin)) {
231 					++begin;
232 				}
233 
234 				g_sequence_append(cmd_disable,
235 				    lt_strdup(begin));
236 			} else   {
237 				*tmp = old_chr;
238 				lt_display_error(
239 				    "Unknown command: %s\n", begin);
240 				goto err;
241 			}
242 			continue;
243 		}
244 
245 		g_assert(*begin != '#' && *begin != ';');
246 
247 		/* 10	genunix`indir			Syscall indir */
248 		/* ^ */
249 		priority = strtol(begin, &tmp, 10);
250 		if (tmp == begin || priority == 0) {
251 			lt_display_error(
252 			    "Invalid configuration line: %s\n", line);
253 			goto err;
254 		}
255 		begin = tmp;
256 
257 		/* 10	genunix`indir			Syscall indir */
258 		/*   ^ */
259 		while (isspace(*begin)) {
260 			++begin;
261 		}
262 		if (*begin == 0) {
263 			lt_display_error(
264 			    "Invalid configuration line: %s\n", line);
265 			goto err;
266 		}
267 
268 		/* 10	genunix`indir			Syscall indir */
269 		/* -----^ */
270 		for (tmp = begin;
271 		    *tmp != '\0' && !isspace(*tmp);
272 		    ++tmp) {
273 		}
274 		if (*tmp == '\0') {
275 			lt_display_error(
276 			    "Invalid configuration line: %s\n", line);
277 			goto err;
278 		}
279 		*tmp = 0;
280 		match = begin;
281 
282 		/* Check if we have mapped this function before. */
283 		match_entry = (lt_match_t *)
284 		    g_hash_table_lookup(symbol_lookup_table, match);
285 		if (match_entry != NULL &&
286 		    HIGHER_PRIORITY(match_entry->priority, priority)) {
287 			/* We already have a higher entry. Ignore this. */
288 			continue;
289 		}
290 
291 		begin = tmp+1;
292 
293 		/* 10	genunix`indir			Syscall indir */
294 		/* -------------------------------------^ */
295 		while (isspace(*begin)) {
296 			++begin;
297 		}
298 		if (*begin == 0) {
299 			lt_display_error(
300 			    "Invalid configuration line: %s\n", line);
301 			goto err;
302 		}
303 		cause_str = begin;
304 
305 		/* Check if we have mapped this cause before. */
306 		cause = (lt_cause_t *)
307 		    g_hash_table_lookup(cause_lookup, cause_str);
308 		if (cause == NULL) {
309 			char *cause_dup = lt_strdup(cause_str);
310 			cause = new_cause(cause_dup, 0);
311 			g_hash_table_insert(cause_lookup, cause_dup, cause);
312 		}
313 
314 		match_entry = (lt_match_t *)lt_malloc(sizeof (lt_match_t));
315 		g_assert(NULL != match_entry);
316 		match_entry->priority = priority;
317 		match_entry->cause_id = cause->cause_id;
318 		match_dup = lt_strdup(match);
319 
320 		g_hash_table_insert(symbol_lookup_table, match_dup,
321 		    match_entry);
322 	}
323 
324 	g_sequence_foreach(cmd_disable, (GFunc)disable_cause, cause_lookup);
325 	g_sequence_free(cmd_disable);
326 	g_hash_table_destroy(cause_lookup);
327 
328 	return (0);
329 
330 err:
331 	g_sequence_free(cmd_disable);
332 	g_hash_table_destroy(cause_lookup);
333 
334 	return (-1);
335 }
336 
337 /*
338  * Init function, called when latencytop starts.
339  * It loads the translation rules from a file.
340  * A configuration file defines some causes and symbols matching these causes.
341  */
342 int
343 lt_table_init(void)
344 {
345 	char *config_loaded = NULL;
346 	int config_loaded_len = 0;
347 	const char *work = NULL;
348 	int work_len = 0;
349 	lt_cause_t *cause;
350 
351 #ifdef EMBED_CONFIGS
352 	work = (char *)latencytop_trans;
353 	work_len = latencytop_trans_len;
354 #endif
355 
356 	if (g_config.config_name != NULL) {
357 		FILE *fp;
358 
359 		fp = fopen(g_config.config_name, "r");
360 		if (NULL == fp) {
361 			lt_display_error(
362 			    "Unable to open configuration file.\n");
363 			return (-1);
364 		}
365 
366 		(void) fseek(fp, 0, SEEK_END);
367 		config_loaded_len = (int)ftell(fp);
368 		config_loaded = (char *)lt_malloc(config_loaded_len);
369 		(void) fseek(fp, 0, SEEK_SET);
370 
371 		if (fread(config_loaded, config_loaded_len, 1, fp) == 0) {
372 			lt_display_error(
373 			    "Unable to read configuration file.\n");
374 			(void) fclose(fp);
375 			free(config_loaded);
376 			return (-1);
377 		}
378 
379 		(void) fclose(fp);
380 		(void) printf("Loaded configuration from %s\n",
381 		    g_config.config_name);
382 
383 		work = config_loaded;
384 		work_len = config_loaded_len;
385 	}
386 
387 	g_assert(work != NULL && work_len != 0);
388 
389 	lt_table_deinit();
390 	causes_array = g_ptr_array_new();
391 	lt_check_null(causes_array);
392 
393 	/* 0 is not used, to keep a place for bugs etc. */
394 	cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED);
395 	g_assert(cause->cause_id == INVALID_CAUSE);
396 
397 	symbol_lookup_table = g_hash_table_new_full(
398 	    g_str_hash, g_str_equal,
399 	    (GDestroyNotify)free, (GDestroyNotify)free);
400 	lt_check_null(symbol_lookup_table);
401 
402 	if (parse_config(work, work_len) != 0) {
403 		return (-1);
404 	}
405 
406 	if (config_loaded != NULL) {
407 		free(config_loaded);
408 	}
409 
410 	return (0);
411 }
412 
413 /*
414  * Some causes, such as "lock spinning", does not have stack trace.
415  * Instead, their names are explicitly specified in DTrace script.
416  * This function will resolve such causes, and dynamically add them
417  * to the global tables when first met (lazy initialization).
418  * auto_create: set to TRUE will create the entry if it is not found.
419  * Returns cause_id of the cause.
420  */
421 int
422 lt_table_lookup_named_cause(char *name, int auto_create)
423 {
424 	int cause_id = INVALID_CAUSE;
425 
426 	if (named_causes == NULL) {
427 		named_causes = g_hash_table_new_full(
428 		    g_str_hash, g_str_equal, (GDestroyNotify)free, NULL);
429 		lt_check_null(named_causes);
430 	} else   {
431 		cause_id = LT_POINTER_TO_INT(g_hash_table_lookup(
432 		    named_causes, name));
433 	}
434 
435 	if (cause_id == INVALID_CAUSE && auto_create) {
436 		int flags = CAUSE_FLAG_SPECIAL;
437 		lt_cause_t *cause;
438 
439 		if (name[0] == '#') {
440 			flags |= CAUSE_FLAG_HIDE_IN_SUMMARY;
441 		}
442 
443 		cause = new_cause(lt_strdup(name), flags);
444 		if (cause == NULL) {
445 			return (INVALID_CAUSE);
446 		}
447 		cause_id = cause->cause_id;
448 
449 		g_hash_table_insert(named_causes, lt_strdup(name),
450 		    LT_INT_TO_POINTER(cause_id));
451 	}
452 
453 	return (cause_id);
454 }
455 
456 /*
457  * Try to map a symbol on stack to a known cause.
458  * module_func has the format "module_name`function_name".
459  * cause_id and priority will be set if a cause is found.
460  * Returns 1 if found, 0 if not found.
461  */
462 int
463 lt_table_lookup_cause(const char *module_func, int *cause_id, int *priority)
464 {
465 	lt_match_t *match;
466 
467 	g_assert(module_func != NULL && cause_id != NULL && priority != NULL);
468 
469 	if (symbol_lookup_table == NULL) {
470 		return (0);
471 	}
472 
473 	match = (lt_match_t *)
474 	    g_hash_table_lookup(symbol_lookup_table, module_func);
475 	if (match == NULL) {
476 		char *func = strchr(module_func, '`');
477 
478 		if (func != NULL) {
479 			match = (lt_match_t *)
480 			    g_hash_table_lookup(symbol_lookup_table, func);
481 		}
482 	}
483 
484 	if (match == NULL) {
485 		return (0);
486 	} else   {
487 		*cause_id = match->cause_id;
488 		*priority = match->priority;
489 		return (1);
490 	}
491 }
492 
493 /*
494  * Get the display name of a cause. Cause_id must be valid,
495  * which is usually return from lt_table_lookup_cause() or
496  * lt_table_lookup_named_cause().
497  */
498 const char *
499 lt_table_get_cause_name(int cause_id)
500 {
501 	lt_cause_t *cause;
502 
503 	if (cause_id < 0 || cause_id >= causes_array_len) {
504 		return (NULL);
505 	}
506 
507 	cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
508 	if (cause == NULL) {
509 		return (NULL);
510 	} else {
511 		return (cause->name);
512 	}
513 }
514 
515 /*
516  * Check a cause's flag, e.g. if it has CAUSE_FLAG_DISABLED.
517  * Use CAUSE_ALL_FLAGS to get all flags at once.
518  */
519 int
520 lt_table_get_cause_flag(int cause_id, int flag)
521 {
522 	lt_cause_t *cause;
523 
524 	if (cause_id < 0 || cause_id >= causes_array_len) {
525 		return (0);
526 	}
527 	cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
528 
529 	if (cause == NULL) {
530 		return (0);
531 	} else {
532 		return (cause->flags & flag);
533 	}
534 }
535 
536 /*
537  * Clean up function.
538  * Free the resource used for symbol table. E.g. symbols, causes.
539  */
540 void
541 lt_table_deinit(void)
542 {
543 	if (symbol_lookup_table != NULL) {
544 		g_hash_table_destroy(symbol_lookup_table);
545 		symbol_lookup_table = NULL;
546 	}
547 
548 	if (named_causes != NULL) {
549 		g_hash_table_destroy(named_causes);
550 		named_causes = NULL;
551 	}
552 
553 	if (causes_array != NULL) {
554 		g_ptr_array_foreach(causes_array, (GFunc)free_cause, NULL);
555 		g_ptr_array_free(causes_array, TRUE);
556 		causes_array = NULL;
557 	}
558 
559 	causes_array_len = 0;
560 }
561