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.
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.
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]
22 * Copyright (c) 2008-2009, Intel Corporation.
23 * All Rights Reserved.
32 #include "latencytop.h"
35 * Structure that holds detail of a cause.
44 * Structure that represents a matched cause.
51 /* All lt_cause_t that are created. */
52 static GHashTable
*cause_lookup
= NULL
;
53 static GPtrArray
*causes_array
= NULL
;
54 static int causes_array_len
= 0;
57 * This hash table maps a symbol to a cause.
58 * key is of type "char *" and value is of type "lt_match_t *".
60 static GHashTable
*symbol_lookup_table
= NULL
;
63 * The dtrace translation rules we get from the script
68 * These structures are only used inside .trans parser.
76 GSequence
*lt_pr_cmd_disable
;
77 GHashTable
*lt_pr_dmacro
;
82 free_cause(lt_cause_t
*cause
, void *user
)
84 g_assert(cause
!= NULL
&& cause
->lt_c_name
!= NULL
);
86 free(cause
->lt_c_name
);
91 free_dmacro(lt_dmacro_t
*d
)
93 g_assert(d
->lt_dm_macro
!= NULL
);
102 new_cause(char *name
, int flags
)
106 g_assert(name
!= NULL
);
108 entry
= (lt_cause_t
*)lt_malloc(sizeof (lt_cause_t
));
109 entry
->lt_c_flags
= flags
;
110 entry
->lt_c_name
= name
;
111 entry
->lt_c_cause_id
= causes_array_len
;
113 g_ptr_array_add(causes_array
, entry
);
120 * Set a cause to "disabled" state.
123 disable_cause(char *cause_str
, GHashTable
*cause_table
)
127 cause
= (lt_cause_t
*)g_hash_table_lookup(cause_table
, cause_str
);
130 cause
->lt_c_flags
|= CAUSE_FLAG_DISABLED
;
135 * Helper functions that reads a line from a character array.
138 read_line_from_mem(const char *mem
, int mem_len
, char *line
, int line_len
,
141 g_assert(mem
!= NULL
&& line
!= NULL
&& index
!= NULL
);
143 if (line_len
<= 0 || mem_len
<= 0) {
147 if (*index
>= mem_len
) {
151 while (line_len
> 1 && *index
< mem_len
) {
152 *line
= mem
[(*index
)++];
156 if (*(line
-1) == '\r' || *(line
-1) == '\n') {
166 * Parse special command from configuration file. Special command
167 * has the following format :
169 * disable_cause <cause name>
172 parse_config_cmd(char *begin
, lt_parser_t
*parser
)
178 * disable_cause FSFlush Daemon
181 if (*begin
== '\0') {
186 *tmp
!= '\0' && !isspace(*tmp
);
192 if (strcmp("disable_cause", begin
) == 0) {
193 if (old_chr
== '\0') {
194 /* Must have an argument */
196 "Invalid command format: %s\n",
202 while (isspace(*begin
)) {
206 g_sequence_append(parser
->lt_pr_cmd_disable
,
211 "Unknown command: %s\n", begin
);
219 * Parse symbol translation from configuration file. Symbol translation
220 * has the following format :
222 * <priority> <symbol name> <cause>
224 * Finally check if that cause has already been mapped.
227 parse_sym_trans(char *begin
)
234 lt_match_t
*match_entry
;
238 * 10 genunix`pread Syscall pread
241 priority
= strtol(begin
, &tmp
, 10);
243 if (tmp
== begin
|| priority
== 0) {
250 * 10 genunix`pread Syscall pread
254 if (!isspace(*begin
)) {
255 /* At least one space char after <priority> */
259 while (isspace(*begin
)) {
268 * 10 genunix`pread Syscall pread
272 *tmp
!= '\0' && !isspace(*tmp
);
283 /* Check if we have mapped this function before. */
284 match_entry
= (lt_match_t
*)
285 g_hash_table_lookup(symbol_lookup_table
, match
);
287 if (match_entry
!= NULL
&&
288 HIGHER_PRIORITY(match_entry
->lt_mt_priority
, priority
)) {
289 /* We already have a higher entry. Ignore this. */
296 * 10 genunix`pread Syscall pread
297 * -------------------------------------^
299 while (isspace(*begin
)) {
309 /* Check if we have mapped this cause before. */
310 cause
= (lt_cause_t
*)
311 g_hash_table_lookup(cause_lookup
, cause_str
);
314 char *cause_dup
= lt_strdup(cause_str
);
315 cause
= new_cause(cause_dup
, 0);
316 g_hash_table_insert(cause_lookup
, cause_dup
, cause
);
319 match_entry
= (lt_match_t
*)lt_malloc(sizeof (lt_match_t
));
320 match_entry
->lt_mt_priority
= priority
;
321 match_entry
->lt_mt_cause_id
= cause
->lt_c_cause_id
;
322 match_dup
= lt_strdup(match
);
324 g_hash_table_insert(symbol_lookup_table
, match_dup
,
331 * Parse D macro. D macros have the following format :
333 * <priority> <entry probe> <return probe> <cause>
335 * Finally check if that cause has already been mapped.
338 parse_dmacro(char *begin
, lt_parser_t
*parser
)
351 * 10 syscall::pread:entry syscall::pread:return Syscall pread
354 priority
= strtol(begin
, &tmp
, 10);
356 if (tmp
== begin
|| priority
== 0) {
363 * 10 syscall::pread:entry syscall::pread:return Syscall pread
366 while (isspace(*begin
)) {
375 * 10 syscall::pread:entry syscall::pread:return Syscall pread
379 *tmp
!= '\0' && !isspace(*tmp
);
391 while (isspace(*begin
)) {
396 * 10 syscall::pread:entry syscall::pread:return Syscall pread
397 * -----------------------------^
400 *tmp
!= '\0' && !isspace(*tmp
);
412 while (isspace(*begin
)) {
417 * 10 syscall::pread:entry syscall::pread:return Syscall pread
418 * -----------------------------------------------------^
428 /* Check if we have mapped this cause before. */
429 cause
= (lt_cause_t
*)
430 g_hash_table_lookup(cause_lookup
, cause_str
);
433 char *cause_dup
= lt_strdup(cause_str
);
434 cause
= new_cause(cause_dup
, 0);
435 g_hash_table_insert(cause_lookup
, cause_dup
, cause
);
438 (void) snprintf(buf
, sizeof (buf
), "\nTRANSLATE(%s, %s, \"%s\", %d)\n",
439 entryprobe
, returnprobe
, cause_str
, priority
);
441 (void) snprintf(probepair
, sizeof (probepair
), "%s %s", entryprobe
,
444 g_assert(cause
!= NULL
);
445 g_assert(parser
->lt_pr_dmacro
!= NULL
);
447 dmacro
= g_hash_table_lookup(parser
->lt_pr_dmacro
, probepair
);
449 if (dmacro
== NULL
) {
450 dmacro
= (lt_dmacro_t
*)lt_malloc(sizeof (lt_dmacro_t
));
451 dmacro
->lt_dm_priority
= priority
;
452 dmacro
->lt_dm_macro
= lt_strdup(buf
);
453 g_hash_table_insert(parser
->lt_pr_dmacro
, lt_strdup(probepair
),
455 } else if (dmacro
->lt_dm_priority
< priority
) {
456 free(dmacro
->lt_dm_macro
);
457 dmacro
->lt_dm_priority
= priority
;
458 dmacro
->lt_dm_macro
= lt_strdup(buf
);
465 * Helper function to collect TRANSLATE() macros.
469 genscript(void *key
, lt_dmacro_t
*dmacro
, GString
*str
)
471 g_string_append(str
, dmacro
->lt_dm_macro
);
475 * Main logic that parses translation rules one line at a time,
476 * and creates a lookup table from it. The syntax for the translation
480 * D <D macro rule> <--- D macro
481 * S <Symbol translation> <--- Symbols
482 * disable_cause <cause> <--- special command
485 parse_config(const char *work
, int work_len
)
496 cause_lookup
= g_hash_table_new(g_str_hash
, g_str_equal
);
497 lt_check_null(cause_lookup
);
499 parser
.lt_pr_cmd_disable
= g_sequence_new((GDestroyNotify
)free
);
500 lt_check_null(parser
.lt_pr_cmd_disable
);
502 parser
.lt_pr_dmacro
= g_hash_table_new_full(g_str_hash
,
503 g_str_equal
, (GDestroyNotify
)free
, (GDestroyNotify
)free_dmacro
);
504 lt_check_null(parser
.lt_pr_dmacro
);
506 while (read_line_from_mem(work
, work_len
, line
, sizeof (line
),
510 if (line
[len
-1] != '\n' && line
[len
-1] != '\r' &&
511 current
< work_len
) {
512 lt_display_error("Configuration line too long.\n");
518 while (isspace(*begin
)) {
522 if (*begin
== '\0') {
523 /* Ignore empty line */
527 /* Delete trailing spaces. */
528 end
= begin
+ strlen(begin
) - 1;
530 while (isspace(*end
)) {
544 ret
= parse_config_cmd(begin
, &parser
);
548 if (!isspace(*begin
)) {
550 "No space after flag char: %s\n", line
);
552 while (isspace(*begin
)) {
555 ret
= parse_dmacro(begin
, &parser
);
559 if (!isspace(*begin
)) {
561 "No space after flag char: %s\n", line
);
563 while (isspace(*begin
)) {
566 ret
= parse_sym_trans(begin
);
575 "Invalid configuration line: %s\n", line
);
580 script
= g_string_new(NULL
);
581 g_hash_table_foreach(parser
.lt_pr_dmacro
, (GHFunc
)genscript
, script
);
582 dtrans
= g_string_free(script
, FALSE
);
584 if (dtrans
!= NULL
&& strlen(dtrans
) == 0) {
589 g_sequence_foreach(parser
.lt_pr_cmd_disable
, (GFunc
)disable_cause
,
591 g_sequence_free(parser
.lt_pr_cmd_disable
);
596 g_sequence_free(parser
.lt_pr_cmd_disable
);
597 g_hash_table_destroy(parser
.lt_pr_dmacro
);
603 * Init function, called when latencytop starts.
604 * It loads translation rules from the configuration file. The configuration
605 * file defines some causes and symbols that match those causes.
610 char *config_loaded
= NULL
;
611 int config_loaded_len
= 0;
612 const char *work
= NULL
;
617 work
= &latencytop_trans_start
;
618 work_len
= (int)(&latencytop_trans_end
- &latencytop_trans_start
);
621 if (g_config
.lt_cfg_config_name
!= NULL
) {
623 fp
= fopen(g_config
.lt_cfg_config_name
, "r");
627 "Unable to open configuration file.\n");
631 (void) fseek(fp
, 0, SEEK_END
);
632 config_loaded_len
= (int)ftell(fp
);
633 config_loaded
= (char *)lt_malloc(config_loaded_len
);
634 (void) fseek(fp
, 0, SEEK_SET
);
636 /* A zero-byte translation is valid */
637 if (config_loaded_len
!= 0 &&
638 fread(config_loaded
, config_loaded_len
, 1, fp
) == 0) {
640 "Unable to read configuration file.\n");
647 (void) printf("Loaded configuration from %s\n",
648 g_config
.lt_cfg_config_name
);
650 work
= config_loaded
;
651 work_len
= config_loaded_len
;
655 causes_array
= g_ptr_array_new();
656 lt_check_null(causes_array
);
658 /* 0 is not used, but it is kept as a place for bugs etc. */
659 cause
= new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED
);
660 g_assert(cause
->lt_c_cause_id
== INVALID_CAUSE
);
662 symbol_lookup_table
= g_hash_table_new_full(
663 g_str_hash
, g_str_equal
,
664 (GDestroyNotify
)free
, (GDestroyNotify
)free
);
665 lt_check_null(symbol_lookup_table
);
667 if (work_len
!= 0 && parse_config(work
, work_len
) != 0) {
671 if (config_loaded
!= NULL
) {
679 * Some causes, such as "lock spinning", do not have stack trace. Names
680 * of such causes are explicitly specified in the D script.
681 * This function resolves such causes and dynamically adds them
682 * to the global tables when they are found first. If auto_create is set
683 * to TRUE, the entry will be created if it is not found.
684 * Return cause_id of the cause.
687 lt_table_cause_from_name(char *name
, int auto_create
, int flags
)
689 lt_cause_t
*cause
= NULL
;
691 if (cause_lookup
== NULL
) {
692 cause_lookup
= g_hash_table_new(g_str_hash
, g_str_equal
);
693 lt_check_null(cause_lookup
);
695 cause
= (lt_cause_t
*)
696 g_hash_table_lookup(cause_lookup
, name
);
699 if (cause
== NULL
&& auto_create
) {
702 if (name
[0] == '#') {
703 flags
|= CAUSE_FLAG_HIDE_IN_SUMMARY
;
706 cause_dup
= lt_strdup(name
);
707 cause
= new_cause(cause_dup
, flags
);
708 g_hash_table_insert(cause_lookup
, cause_dup
, cause
);
711 return (cause
== NULL
? INVALID_CAUSE
: cause
->lt_c_cause_id
);
715 * Try to map a symbol on stack to a known cause.
716 * module_func has the format "module_name`function_name".
717 * cause_id and priority will be set if a cause is found.
718 * If cause is found return 1, otherwise return 0.
721 lt_table_cause_from_stack(const char *module_func
, int *cause_id
, int *priority
)
725 g_assert(module_func
!= NULL
&& cause_id
!= NULL
&& priority
!= NULL
);
727 if (symbol_lookup_table
== NULL
) {
731 match
= (lt_match_t
*)
732 g_hash_table_lookup(symbol_lookup_table
, module_func
);
735 char *func
= strchr(module_func
, '`');
738 match
= (lt_match_t
*)
739 g_hash_table_lookup(symbol_lookup_table
, func
);
746 *cause_id
= match
->lt_mt_cause_id
;
747 *priority
= match
->lt_mt_priority
;
753 * Get the display name of a cause. cause_id must be valid,
754 * it is usually returned from lt_table_cause_from_stack() or
755 * lt_table_cause_from_name().
758 lt_table_get_cause_name(int cause_id
)
762 if (cause_id
< 0 || cause_id
>= causes_array_len
) {
766 cause
= (lt_cause_t
*)g_ptr_array_index(causes_array
, cause_id
);
771 return (cause
->lt_c_name
);
777 * If CAUSE_ALL_FLAGS is passed in, all flags are returned.
780 lt_table_get_cause_flag(int cause_id
, int flag
)
784 if (cause_id
< 0 || cause_id
>= causes_array_len
) {
788 cause
= (lt_cause_t
*)g_ptr_array_index(causes_array
, cause_id
);
793 return (cause
->lt_c_flags
& flag
);
798 * Append macros to D script, if any.
801 lt_table_append_trans(FILE *fp
)
803 if (dtrans
!= NULL
) {
804 if (fwrite(dtrans
, strlen(dtrans
), 1, fp
) != 1) {
814 * Free the resources used for symbol table (symbols, causes etc.).
817 lt_table_deinit(void)
819 if (symbol_lookup_table
!= NULL
) {
820 g_hash_table_destroy(symbol_lookup_table
);
821 symbol_lookup_table
= NULL
;
824 if (cause_lookup
!= NULL
) {
825 g_hash_table_destroy(cause_lookup
);
829 if (causes_array
!= NULL
) {
830 g_ptr_array_foreach(causes_array
, (GFunc
)free_cause
, NULL
);
831 g_ptr_array_free(causes_array
, TRUE
);
833 causes_array_len
= 0;
836 if (dtrans
!= NULL
) {