Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf ftrace: Add 'profile' command

The 'perf ftrace profile' command is to get function execution profiles
using function-graph tracer so that users can see the total, average,
max execution time as well as the number of invocations easily.

The following is a profile for the perf_event_open syscall.

$ sudo perf ftrace profile -G __x64_sys_perf_event_open -- \
perf stat -e cycles -C1 true 2> /dev/null | head
# Total (us) Avg (us) Max (us) Count Function
65.611 65.611 65.611 1 __x64_sys_perf_event_open
30.527 30.527 30.527 1 anon_inode_getfile
30.260 30.260 30.260 1 __anon_inode_getfile
29.700 29.700 29.700 1 alloc_file_pseudo
17.578 17.578 17.578 1 d_alloc_pseudo
17.382 17.382 17.382 1 __d_alloc
16.738 16.738 16.738 1 kmem_cache_alloc_lru
15.686 15.686 15.686 1 perf_event_alloc
14.012 7.006 11.264 2 obj_cgroup_charge
#

Reviewed-by: Ian Rogers <irogers@google.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Changbin Du <changbin.du@gmail.com>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Steven Rostedt (VMware) <rostedt@goodmis.org>
Link: https://lore.kernel.org/lkml/20240729004127.238611-4-namhyung@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Namhyung Kim and committed by
Arnaldo Carvalho de Melo
0f223813 608585f4

+359 -3
+41 -1
tools/perf/Documentation/perf-ftrace.txt
··· 9 9 SYNOPSIS 10 10 -------- 11 11 [verse] 12 - 'perf ftrace' {trace|latency} <command> 12 + 'perf ftrace' {trace|latency|profile} <command> 13 13 14 14 DESCRIPTION 15 15 ----------- ··· 22 22 23 23 'perf ftrace latency' calculates execution latency of a given function 24 24 (optionally with BPF) and display it as a histogram. 25 + 26 + 'perf ftrace profile' show a execution profile for each function including 27 + total, average, max time and the number of calls. 25 28 26 29 The following options apply to perf ftrace. 27 30 ··· 147 144 -n:: 148 145 --use-nsec:: 149 146 Use nano-second instead of micro-second as a base unit of the histogram. 147 + 148 + 149 + OPTIONS for 'perf ftrace profile' 150 + --------------------------------- 151 + 152 + -T:: 153 + --trace-funcs=:: 154 + Set function filter on the given function (or a glob pattern). 155 + Multiple functions can be given by using this option more than once. 156 + The function argument also can be a glob pattern. It will be passed 157 + to 'set_ftrace_filter' in tracefs. 158 + 159 + -N:: 160 + --notrace-funcs=:: 161 + Do not trace functions given by the argument. Like -T option, this 162 + can be used more than once to specify multiple functions (or glob 163 + patterns). It will be passed to 'set_ftrace_notrace' in tracefs. 164 + 165 + -G:: 166 + --graph-funcs=:: 167 + Set graph filter on the given function (or a glob pattern). This is 168 + useful to trace for functions executed from the given function. This 169 + can be used more than once to specify multiple functions. It will be 170 + passed to 'set_graph_function' in tracefs. 171 + 172 + -g:: 173 + --nograph-funcs=:: 174 + Set graph notrace filter on the given function (or a glob pattern). 175 + Like -G option, this is useful for the function_graph tracer only and 176 + disables tracing for function executed from the given function. This 177 + can be used more than once to specify multiple functions. It will be 178 + passed to 'set_graph_notrace' in tracefs. 179 + 180 + -m:: 181 + --buffer-size:: 182 + Set the size of per-cpu tracing buffer, <size> is expected to 183 + be a number with appended unit character - B/K/M/G. 150 184 151 185 152 186 SEE ALSO
+316 -2
tools/perf/builtin-ftrace.c
··· 13 13 #include <signal.h> 14 14 #include <stdlib.h> 15 15 #include <fcntl.h> 16 + #include <inttypes.h> 16 17 #include <math.h> 17 18 #include <poll.h> 18 19 #include <ctype.h> ··· 23 22 #include "debug.h" 24 23 #include <subcmd/pager.h> 25 24 #include <subcmd/parse-options.h> 25 + #include <api/io.h> 26 26 #include <api/fs/tracing_path.h> 27 27 #include "evlist.h" 28 28 #include "target.h" 29 29 #include "cpumap.h" 30 + #include "hashmap.h" 30 31 #include "thread_map.h" 31 32 #include "strfilter.h" 32 33 #include "util/cap.h" 33 34 #include "util/config.h" 34 35 #include "util/ftrace.h" 36 + #include "util/stat.h" 35 37 #include "util/units.h" 36 38 #include "util/parse-sublevel-options.h" 37 39 ··· 963 959 return (done && !workload_exec_errno) ? 0 : -1; 964 960 } 965 961 962 + static size_t profile_hash(long func, void *ctx __maybe_unused) 963 + { 964 + return str_hash((char *)func); 965 + } 966 + 967 + static bool profile_equal(long func1, long func2, void *ctx __maybe_unused) 968 + { 969 + return !strcmp((char *)func1, (char *)func2); 970 + } 971 + 972 + static int prepare_func_profile(struct perf_ftrace *ftrace) 973 + { 974 + ftrace->tracer = "function_graph"; 975 + ftrace->graph_tail = 1; 976 + 977 + ftrace->profile_hash = hashmap__new(profile_hash, profile_equal, NULL); 978 + if (ftrace->profile_hash == NULL) 979 + return -ENOMEM; 980 + 981 + return 0; 982 + } 983 + 984 + /* This is saved in a hashmap keyed by the function name */ 985 + struct ftrace_profile_data { 986 + struct stats st; 987 + }; 988 + 989 + static int add_func_duration(struct perf_ftrace *ftrace, char *func, double time_ns) 990 + { 991 + struct ftrace_profile_data *prof = NULL; 992 + 993 + if (!hashmap__find(ftrace->profile_hash, func, &prof)) { 994 + char *key = strdup(func); 995 + 996 + if (key == NULL) 997 + return -ENOMEM; 998 + 999 + prof = zalloc(sizeof(*prof)); 1000 + if (prof == NULL) { 1001 + free(key); 1002 + return -ENOMEM; 1003 + } 1004 + 1005 + init_stats(&prof->st); 1006 + hashmap__add(ftrace->profile_hash, key, prof); 1007 + } 1008 + 1009 + update_stats(&prof->st, time_ns); 1010 + return 0; 1011 + } 1012 + 1013 + /* 1014 + * The ftrace function_graph text output normally looks like below: 1015 + * 1016 + * CPU DURATION FUNCTION 1017 + * 1018 + * 0) | syscall_trace_enter.isra.0() { 1019 + * 0) | __audit_syscall_entry() { 1020 + * 0) | auditd_test_task() { 1021 + * 0) 0.271 us | __rcu_read_lock(); 1022 + * 0) 0.275 us | __rcu_read_unlock(); 1023 + * 0) 1.254 us | } /\* auditd_test_task *\/ 1024 + * 0) 0.279 us | ktime_get_coarse_real_ts64(); 1025 + * 0) 2.227 us | } /\* __audit_syscall_entry *\/ 1026 + * 0) 2.713 us | } /\* syscall_trace_enter.isra.0 *\/ 1027 + * 1028 + * Parse the line and get the duration and function name. 1029 + */ 1030 + static int parse_func_duration(struct perf_ftrace *ftrace, char *line, size_t len) 1031 + { 1032 + char *p; 1033 + char *func; 1034 + double duration; 1035 + 1036 + /* skip CPU */ 1037 + p = strchr(line, ')'); 1038 + if (p == NULL) 1039 + return 0; 1040 + 1041 + /* get duration */ 1042 + p = skip_spaces(p + 1); 1043 + 1044 + /* no duration? */ 1045 + if (p == NULL || *p == '|') 1046 + return 0; 1047 + 1048 + /* skip markers like '*' or '!' for longer than ms */ 1049 + if (!isdigit(*p)) 1050 + p++; 1051 + 1052 + duration = strtod(p, &p); 1053 + 1054 + if (strncmp(p, " us", 3)) { 1055 + pr_debug("non-usec time found.. ignoring\n"); 1056 + return 0; 1057 + } 1058 + 1059 + /* 1060 + * profile stat keeps the max and min values as integer, 1061 + * convert to nsec time so that we can have accurate max. 1062 + */ 1063 + duration *= 1000; 1064 + 1065 + /* skip to the pipe */ 1066 + while (p < line + len && *p != '|') 1067 + p++; 1068 + 1069 + if (*p++ != '|') 1070 + return -EINVAL; 1071 + 1072 + /* get function name */ 1073 + func = skip_spaces(p); 1074 + 1075 + /* skip the closing bracket and the start of comment */ 1076 + if (*func == '}') 1077 + func += 5; 1078 + 1079 + /* remove semi-colon or end of comment at the end */ 1080 + p = line + len - 1; 1081 + while (!isalnum(*p) && *p != ']') { 1082 + *p = '\0'; 1083 + --p; 1084 + } 1085 + 1086 + return add_func_duration(ftrace, func, duration); 1087 + } 1088 + 1089 + static int cmp_profile_data(const void *a, const void *b) 1090 + { 1091 + const struct hashmap_entry *e1 = *(const struct hashmap_entry **)a; 1092 + const struct hashmap_entry *e2 = *(const struct hashmap_entry **)b; 1093 + struct ftrace_profile_data *p1 = e1->pvalue; 1094 + struct ftrace_profile_data *p2 = e2->pvalue; 1095 + 1096 + /* compare by total time */ 1097 + if ((p1->st.n * p1->st.mean) > (p2->st.n * p2->st.mean)) 1098 + return -1; 1099 + else 1100 + return 1; 1101 + } 1102 + 1103 + static void print_profile_result(struct perf_ftrace *ftrace) 1104 + { 1105 + struct hashmap_entry *entry, **profile; 1106 + size_t i, nr, bkt; 1107 + 1108 + nr = hashmap__size(ftrace->profile_hash); 1109 + if (nr == 0) 1110 + return; 1111 + 1112 + profile = calloc(nr, sizeof(*profile)); 1113 + if (profile == NULL) { 1114 + pr_err("failed to allocate memory for the result\n"); 1115 + return; 1116 + } 1117 + 1118 + i = 0; 1119 + hashmap__for_each_entry(ftrace->profile_hash, entry, bkt) 1120 + profile[i++] = entry; 1121 + 1122 + assert(i == nr); 1123 + 1124 + //cmp_profile_data(profile[0], profile[1]); 1125 + qsort(profile, nr, sizeof(*profile), cmp_profile_data); 1126 + 1127 + printf("# %10s %10s %10s %10s %s\n", 1128 + "Total (us)", "Avg (us)", "Max (us)", "Count", "Function"); 1129 + 1130 + for (i = 0; i < nr; i++) { 1131 + const char *name = profile[i]->pkey; 1132 + struct ftrace_profile_data *p = profile[i]->pvalue; 1133 + 1134 + printf("%12.3f %10.3f %6"PRIu64".%03"PRIu64" %10.0f %s\n", 1135 + p->st.n * p->st.mean / 1000, p->st.mean / 1000, 1136 + p->st.max / 1000, p->st.max % 1000, p->st.n, name); 1137 + } 1138 + 1139 + free(profile); 1140 + 1141 + hashmap__for_each_entry(ftrace->profile_hash, entry, bkt) { 1142 + free((char *)entry->pkey); 1143 + free(entry->pvalue); 1144 + } 1145 + 1146 + hashmap__free(ftrace->profile_hash); 1147 + ftrace->profile_hash = NULL; 1148 + } 1149 + 1150 + static int __cmd_profile(struct perf_ftrace *ftrace) 1151 + { 1152 + char *trace_file; 1153 + int trace_fd; 1154 + char buf[4096]; 1155 + struct io io; 1156 + char *line = NULL; 1157 + size_t line_len = 0; 1158 + 1159 + if (prepare_func_profile(ftrace) < 0) { 1160 + pr_err("failed to prepare func profiler\n"); 1161 + goto out; 1162 + } 1163 + 1164 + if (reset_tracing_files(ftrace) < 0) { 1165 + pr_err("failed to reset ftrace\n"); 1166 + goto out; 1167 + } 1168 + 1169 + /* reset ftrace buffer */ 1170 + if (write_tracing_file("trace", "0") < 0) 1171 + goto out; 1172 + 1173 + if (set_tracing_options(ftrace) < 0) 1174 + return -1; 1175 + 1176 + if (write_tracing_file("current_tracer", ftrace->tracer) < 0) { 1177 + pr_err("failed to set current_tracer to %s\n", ftrace->tracer); 1178 + goto out_reset; 1179 + } 1180 + 1181 + setup_pager(); 1182 + 1183 + trace_file = get_tracing_file("trace_pipe"); 1184 + if (!trace_file) { 1185 + pr_err("failed to open trace_pipe\n"); 1186 + goto out_reset; 1187 + } 1188 + 1189 + trace_fd = open(trace_file, O_RDONLY); 1190 + 1191 + put_tracing_file(trace_file); 1192 + 1193 + if (trace_fd < 0) { 1194 + pr_err("failed to open trace_pipe\n"); 1195 + goto out_reset; 1196 + } 1197 + 1198 + fcntl(trace_fd, F_SETFL, O_NONBLOCK); 1199 + 1200 + if (write_tracing_file("tracing_on", "1") < 0) { 1201 + pr_err("can't enable tracing\n"); 1202 + goto out_close_fd; 1203 + } 1204 + 1205 + evlist__start_workload(ftrace->evlist); 1206 + 1207 + io__init(&io, trace_fd, buf, sizeof(buf)); 1208 + io.timeout_ms = -1; 1209 + 1210 + while (!done && !io.eof) { 1211 + if (io__getline(&io, &line, &line_len) < 0) 1212 + break; 1213 + 1214 + if (parse_func_duration(ftrace, line, line_len) < 0) 1215 + break; 1216 + } 1217 + 1218 + write_tracing_file("tracing_on", "0"); 1219 + 1220 + if (workload_exec_errno) { 1221 + const char *emsg = str_error_r(workload_exec_errno, buf, sizeof(buf)); 1222 + /* flush stdout first so below error msg appears at the end. */ 1223 + fflush(stdout); 1224 + pr_err("workload failed: %s\n", emsg); 1225 + goto out_free_line; 1226 + } 1227 + 1228 + /* read remaining buffer contents */ 1229 + io.timeout_ms = 0; 1230 + while (!io.eof) { 1231 + if (io__getline(&io, &line, &line_len) < 0) 1232 + break; 1233 + 1234 + if (parse_func_duration(ftrace, line, line_len) < 0) 1235 + break; 1236 + } 1237 + 1238 + print_profile_result(ftrace); 1239 + 1240 + out_free_line: 1241 + free(line); 1242 + out_close_fd: 1243 + close(trace_fd); 1244 + out_reset: 1245 + reset_tracing_files(ftrace); 1246 + out: 1247 + return (done && !workload_exec_errno) ? 0 : -1; 1248 + } 1249 + 966 1250 static int perf_ftrace_config(const char *var, const char *value, void *cb) 967 1251 { 968 1252 struct perf_ftrace *ftrace = cb; ··· 1418 1126 PERF_FTRACE_NONE, 1419 1127 PERF_FTRACE_TRACE, 1420 1128 PERF_FTRACE_LATENCY, 1129 + PERF_FTRACE_PROFILE, 1421 1130 }; 1422 1131 1423 1132 int cmd_ftrace(int argc, const char **argv) ··· 1484 1191 "Use nano-second histogram"), 1485 1192 OPT_PARENT(common_options), 1486 1193 }; 1194 + const struct option profile_options[] = { 1195 + OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", 1196 + "Trace given functions using function tracer", 1197 + parse_filter_func), 1198 + OPT_CALLBACK('N', "notrace-funcs", &ftrace.notrace, "func", 1199 + "Do not trace given functions", parse_filter_func), 1200 + OPT_CALLBACK('G', "graph-funcs", &ftrace.graph_funcs, "func", 1201 + "Trace given functions using function_graph tracer", 1202 + parse_filter_func), 1203 + OPT_CALLBACK('g', "nograph-funcs", &ftrace.nograph_funcs, "func", 1204 + "Set nograph filter on given functions", parse_filter_func), 1205 + OPT_CALLBACK('m', "buffer-size", &ftrace.percpu_buffer_size, "size", 1206 + "Size of per cpu buffer, needs to use a B, K, M or G suffix.", parse_buffer_size), 1207 + OPT_PARENT(common_options), 1208 + }; 1487 1209 const struct option *options = ftrace_options; 1488 1210 1489 1211 const char * const ftrace_usage[] = { 1490 1212 "perf ftrace [<options>] [<command>]", 1491 1213 "perf ftrace [<options>] -- [<command>] [<options>]", 1492 - "perf ftrace {trace|latency} [<options>] [<command>]", 1493 - "perf ftrace {trace|latency} [<options>] -- [<command>] [<options>]", 1214 + "perf ftrace {trace|latency|profile} [<options>] [<command>]", 1215 + "perf ftrace {trace|latency|profile} [<options>] -- [<command>] [<options>]", 1494 1216 NULL 1495 1217 }; 1496 1218 enum perf_ftrace_subcommand subcmd = PERF_FTRACE_NONE; ··· 1534 1226 } else if (!strcmp(argv[1], "latency")) { 1535 1227 subcmd = PERF_FTRACE_LATENCY; 1536 1228 options = latency_options; 1229 + } else if (!strcmp(argv[1], "profile")) { 1230 + subcmd = PERF_FTRACE_PROFILE; 1231 + options = profile_options; 1537 1232 } 1538 1233 1539 1234 if (subcmd != PERF_FTRACE_NONE) { ··· 1571 1260 goto out_delete_filters; 1572 1261 } 1573 1262 cmd_func = __cmd_latency; 1263 + break; 1264 + case PERF_FTRACE_PROFILE: 1265 + cmd_func = __cmd_profile; 1574 1266 break; 1575 1267 case PERF_FTRACE_NONE: 1576 1268 default:
+2
tools/perf/util/ftrace.h
··· 6 6 #include "target.h" 7 7 8 8 struct evlist; 9 + struct hashamp; 9 10 10 11 struct perf_ftrace { 11 12 struct evlist *evlist; ··· 16 15 struct list_head notrace; 17 16 struct list_head graph_funcs; 18 17 struct list_head nograph_funcs; 18 + struct hashmap *profile_hash; 19 19 unsigned long percpu_buffer_size; 20 20 bool inherit; 21 21 bool use_nsec;