diff options
Diffstat (limited to 'tools/perf/builtin-ftrace.c')
-rw-r--r-- | tools/perf/builtin-ftrace.c | 413 |
1 files changed, 363 insertions, 50 deletions
diff --git a/tools/perf/builtin-ftrace.c b/tools/perf/builtin-ftrace.c index 272d3c70810e..6b6eec65f93f 100644 --- a/tools/perf/builtin-ftrace.c +++ b/tools/perf/builtin-ftrace.c @@ -19,6 +19,7 @@ #include <ctype.h> #include <linux/capability.h> #include <linux/string.h> +#include <sys/stat.h> #include "debug.h" #include <subcmd/pager.h> @@ -43,6 +44,10 @@ static volatile sig_atomic_t workload_exec_errno; static volatile sig_atomic_t done; +static struct stats latency_stats; /* for tracepoints */ + +static char tracing_instance[PATH_MAX]; /* Trace instance directory */ + static void sig_handler(int sig __maybe_unused) { done = true; @@ -98,6 +103,34 @@ static bool is_ftrace_supported(void) return supported; } +/* + * Wrapper to test if a file in directory .../tracing/instances/XXX + * exists. If so return the .../tracing/instances/XXX file for use. + * Otherwise the file exists only in directory .../tracing and + * is applicable to all instances, for example file available_filter_functions. + * Return that file name in this case. + * + * This functions works similar to get_tracing_file() and expects its caller + * to free the returned file name. + * + * The global variable tracing_instance is set in init_tracing_instance() + * called at the beginning to a process specific tracing subdirectory. + */ +static char *get_tracing_instance_file(const char *name) +{ + char *file; + + if (asprintf(&file, "%s/%s", tracing_instance, name) < 0) + return NULL; + + if (!access(file, F_OK)) + return file; + + free(file); + file = get_tracing_file(name); + return file; +} + static int __write_tracing_file(const char *name, const char *val, bool append) { char *file; @@ -107,7 +140,7 @@ static int __write_tracing_file(const char *name, const char *val, bool append) char errbuf[512]; char *val_copy; - file = get_tracing_file(name); + file = get_tracing_instance_file(name); if (!file) { pr_debug("cannot get tracing file: %s\n", name); return -1; @@ -165,7 +198,7 @@ static int read_tracing_file_to_stdout(const char *name) int fd; int ret = -1; - file = get_tracing_file(name); + file = get_tracing_instance_file(name); if (!file) { pr_debug("cannot get tracing file: %s\n", name); return -1; @@ -207,7 +240,7 @@ static int read_tracing_file_by_line(const char *name, char *file; FILE *fp; - file = get_tracing_file(name); + file = get_tracing_instance_file(name); if (!file) { pr_debug("cannot get tracing file: %s\n", name); return -1; @@ -268,6 +301,10 @@ static void reset_tracing_options(struct perf_ftrace *ftrace __maybe_unused) write_tracing_option_file("funcgraph-proc", "0"); write_tracing_option_file("funcgraph-abstime", "0"); write_tracing_option_file("funcgraph-tail", "0"); + write_tracing_option_file("funcgraph-args", "0"); + write_tracing_option_file("funcgraph-retval", "0"); + write_tracing_option_file("funcgraph-retval-hex", "0"); + write_tracing_option_file("funcgraph-retaddr", "0"); write_tracing_option_file("latency-format", "0"); write_tracing_option_file("irq-info", "0"); } @@ -297,6 +334,39 @@ static int reset_tracing_files(struct perf_ftrace *ftrace __maybe_unused) return 0; } +/* Remove .../tracing/instances/XXX subdirectory created with + * init_tracing_instance(). + */ +static void exit_tracing_instance(void) +{ + if (rmdir(tracing_instance)) + pr_err("failed to delete tracing/instances directory\n"); +} + +/* Create subdirectory within .../tracing/instances/XXX to have session + * or process specific setup. To delete this setup, simply remove the + * subdirectory. + */ +static int init_tracing_instance(void) +{ + char dirname[] = "instances/perf-ftrace-XXXXXX"; + char *path; + + path = get_tracing_file(dirname); + if (!path) + goto error; + strncpy(tracing_instance, path, sizeof(tracing_instance) - 1); + put_tracing_file(path); + path = mkdtemp(tracing_instance); + if (!path) + goto error; + return 0; + +error: + pr_err("failed to create tracing/instances directory\n"); + return -1; +} + static int set_tracing_pid(struct perf_ftrace *ftrace) { int i; @@ -476,6 +546,41 @@ static int set_tracing_sleep_time(struct perf_ftrace *ftrace) return 0; } +static int set_tracing_funcgraph_args(struct perf_ftrace *ftrace) +{ + if (ftrace->graph_args) { + if (write_tracing_option_file("funcgraph-args", "1") < 0) + return -1; + } + + return 0; +} + +static int set_tracing_funcgraph_retval(struct perf_ftrace *ftrace) +{ + if (ftrace->graph_retval || ftrace->graph_retval_hex) { + if (write_tracing_option_file("funcgraph-retval", "1") < 0) + return -1; + } + + if (ftrace->graph_retval_hex) { + if (write_tracing_option_file("funcgraph-retval-hex", "1") < 0) + return -1; + } + + return 0; +} + +static int set_tracing_funcgraph_retaddr(struct perf_ftrace *ftrace) +{ + if (ftrace->graph_retaddr) { + if (write_tracing_option_file("funcgraph-retaddr", "1") < 0) + return -1; + } + + return 0; +} + static int set_tracing_funcgraph_irqs(struct perf_ftrace *ftrace) { if (!ftrace->graph_noirqs) @@ -576,6 +681,21 @@ static int set_tracing_options(struct perf_ftrace *ftrace) return -1; } + if (set_tracing_funcgraph_args(ftrace) < 0) { + pr_err("failed to set tracing option funcgraph-args\n"); + return -1; + } + + if (set_tracing_funcgraph_retval(ftrace) < 0) { + pr_err("failed to set tracing option funcgraph-retval\n"); + return -1; + } + + if (set_tracing_funcgraph_retaddr(ftrace) < 0) { + pr_err("failed to set tracing option funcgraph-retaddr\n"); + return -1; + } + if (set_tracing_funcgraph_irqs(ftrace) < 0) { pr_err("failed to set tracing option funcgraph-irqs\n"); return -1; @@ -627,14 +747,17 @@ static int __cmd_ftrace(struct perf_ftrace *ftrace) select_tracer(ftrace); + if (init_tracing_instance() < 0) + goto out; + if (reset_tracing_files(ftrace) < 0) { pr_err("failed to reset ftrace\n"); - goto out; + goto out_reset; } /* reset ftrace buffer */ if (write_tracing_file("trace", "0") < 0) - goto out; + goto out_reset; if (set_tracing_options(ftrace) < 0) goto out_reset; @@ -646,7 +769,7 @@ static int __cmd_ftrace(struct perf_ftrace *ftrace) setup_pager(); - trace_file = get_tracing_file("trace_pipe"); + trace_file = get_tracing_instance_file("trace_pipe"); if (!trace_file) { pr_err("failed to open trace_pipe\n"); goto out_reset; @@ -721,14 +844,17 @@ static int __cmd_ftrace(struct perf_ftrace *ftrace) out_close_fd: close(trace_fd); out_reset: - reset_tracing_files(ftrace); + exit_tracing_instance(); out: return (done && !workload_exec_errno) ? 0 : -1; } -static void make_histogram(int buckets[], char *buf, size_t len, char *linebuf, - bool use_nsec) +static void make_histogram(struct perf_ftrace *ftrace, int buckets[], + char *buf, size_t len, char *linebuf) { + int min_latency = ftrace->min_latency; + int max_latency = ftrace->max_latency; + unsigned int bucket_num = ftrace->bucket_num; char *p, *q; char *unit; double num; @@ -774,16 +900,34 @@ static void make_histogram(int buckets[], char *buf, size_t len, char *linebuf, if (!unit || strncmp(unit, " us", 3)) goto next; - if (use_nsec) + if (ftrace->use_nsec) num *= 1000; - i = log2(num); - if (i < 0) - i = 0; - if (i >= NUM_BUCKET) - i = NUM_BUCKET - 1; + i = 0; + if (num < min_latency) + goto do_inc; + + num -= min_latency; + + if (!ftrace->bucket_range) { + i = log2(num); + if (i < 0) + i = 0; + } else { + // Less than 1 unit (ms or ns), or, in the future, + // than the min latency desired. + if (num > 0) // 1st entry: [ 1 unit .. bucket_range units ] + i = num / ftrace->bucket_range + 1; + if (num >= max_latency - min_latency) + i = bucket_num -1; + } + if ((unsigned)i >= bucket_num) + i = bucket_num - 1; + num += min_latency; +do_inc: buckets[i]++; + update_stats(&latency_stats, num); next: /* empty the line buffer for the next output */ @@ -794,15 +938,18 @@ next: strcat(linebuf, p); } -static void display_histogram(int buckets[], bool use_nsec) +static void display_histogram(struct perf_ftrace *ftrace, int buckets[]) { - int i; + int min_latency = ftrace->min_latency; + bool use_nsec = ftrace->use_nsec; + unsigned int bucket_num = ftrace->bucket_num; + unsigned int i; int total = 0; int bar_total = 46; /* to fit in 80 column */ char bar[] = "###############################################"; int bar_len; - for (i = 0; i < NUM_BUCKET; i++) + for (i = 0; i < bucket_num; i++) total += buckets[i]; if (total == 0) { @@ -814,30 +961,80 @@ static void display_histogram(int buckets[], bool use_nsec) " DURATION ", "COUNT", bar_total, "GRAPH"); bar_len = buckets[0] * bar_total / total; - printf(" %4d - %-4d %s | %10d | %.*s%*s |\n", - 0, 1, use_nsec ? "ns" : "us", buckets[0], bar_len, bar, bar_total - bar_len, ""); - for (i = 1; i < NUM_BUCKET - 1; i++) { - int start = (1 << (i - 1)); - int stop = 1 << i; + if (!ftrace->hide_empty || buckets[0]) + printf(" %4d - %4d %s | %10d | %.*s%*s |\n", + 0, min_latency ?: 1, use_nsec ? "ns" : "us", + buckets[0], bar_len, bar, bar_total - bar_len, ""); + + for (i = 1; i < bucket_num - 1; i++) { + unsigned int start, stop; const char *unit = use_nsec ? "ns" : "us"; - if (start >= 1024) { - start >>= 10; - stop >>= 10; - unit = use_nsec ? "us" : "ms"; + if (ftrace->hide_empty && !buckets[i]) + continue; + if (!ftrace->bucket_range) { + start = (1 << (i - 1)); + stop = 1 << i; + + if (start >= 1024) { + start >>= 10; + stop >>= 10; + unit = use_nsec ? "us" : "ms"; + } + } else { + start = (i - 1) * ftrace->bucket_range + min_latency; + stop = i * ftrace->bucket_range + min_latency; + + if (start >= ftrace->max_latency) + break; + if (stop > ftrace->max_latency) + stop = ftrace->max_latency; + + if (start >= 1000) { + double dstart = start / 1000.0, + dstop = stop / 1000.0; + printf(" %4.2f - %-4.2f", dstart, dstop); + unit = use_nsec ? "us" : "ms"; + goto print_bucket_info; + } } + + printf(" %4d - %4d", start, stop); +print_bucket_info: bar_len = buckets[i] * bar_total / total; - printf(" %4d - %-4d %s | %10d | %.*s%*s |\n", - start, stop, unit, buckets[i], bar_len, bar, + printf(" %s | %10d | %.*s%*s |\n", unit, buckets[i], bar_len, bar, bar_total - bar_len, ""); } - bar_len = buckets[NUM_BUCKET - 1] * bar_total / total; - printf(" %4d - %-4s %s | %10d | %.*s%*s |\n", - 1, "...", use_nsec ? "ms" : " s", buckets[NUM_BUCKET - 1], + bar_len = buckets[bucket_num - 1] * bar_total / total; + if (ftrace->hide_empty && !buckets[bucket_num - 1]) + goto print_stats; + if (!ftrace->bucket_range) { + printf(" %4d - %-4s %s", 1, "...", use_nsec ? "ms" : "s "); + } else { + unsigned int upper_outlier = (bucket_num - 2) * ftrace->bucket_range + min_latency; + if (upper_outlier > ftrace->max_latency) + upper_outlier = ftrace->max_latency; + + if (upper_outlier >= 1000) { + double dstart = upper_outlier / 1000.0; + + printf(" %4.2f - %-4s %s", dstart, "...", use_nsec ? "us" : "ms"); + } else { + printf(" %4d - %4s %s", upper_outlier, "...", use_nsec ? "ns" : "us"); + } + } + printf(" | %10d | %.*s%*s |\n", buckets[bucket_num - 1], bar_len, bar, bar_total - bar_len, ""); +print_stats: + printf("\n# statistics (in %s)\n", ftrace->use_nsec ? "nsec" : "usec"); + printf(" total time: %20.0f\n", latency_stats.mean * latency_stats.n); + printf(" avg time: %20.0f\n", latency_stats.mean); + printf(" max time: %20"PRIu64"\n", latency_stats.max); + printf(" min time: %20"PRIu64"\n", latency_stats.min); + printf(" count: %20.0f\n", latency_stats.n); } static int prepare_func_latency(struct perf_ftrace *ftrace) @@ -848,6 +1045,9 @@ static int prepare_func_latency(struct perf_ftrace *ftrace) if (ftrace->target.use_bpf) return perf_ftrace__latency_prepare_bpf(ftrace); + if (init_tracing_instance() < 0) + return -1; + if (reset_tracing_files(ftrace) < 0) { pr_err("failed to reset ftrace\n"); return -1; @@ -866,7 +1066,7 @@ static int prepare_func_latency(struct perf_ftrace *ftrace) return -1; } - trace_file = get_tracing_file("trace_pipe"); + trace_file = get_tracing_instance_file("trace_pipe"); if (!trace_file) { pr_err("failed to open trace_pipe\n"); return -1; @@ -876,6 +1076,8 @@ static int prepare_func_latency(struct perf_ftrace *ftrace) if (fd < 0) pr_err("failed to open trace_pipe\n"); + init_stats(&latency_stats); + put_tracing_file(trace_file); return fd; } @@ -905,7 +1107,7 @@ static int stop_func_latency(struct perf_ftrace *ftrace) static int read_func_latency(struct perf_ftrace *ftrace, int buckets[]) { if (ftrace->target.use_bpf) - return perf_ftrace__latency_read_bpf(ftrace, buckets); + return perf_ftrace__latency_read_bpf(ftrace, buckets, &latency_stats); return 0; } @@ -915,7 +1117,7 @@ static int cleanup_func_latency(struct perf_ftrace *ftrace) if (ftrace->target.use_bpf) return perf_ftrace__latency_cleanup_bpf(ftrace); - reset_tracing_files(ftrace); + exit_tracing_instance(); return 0; } @@ -927,7 +1129,7 @@ static int __cmd_latency(struct perf_ftrace *ftrace) struct pollfd pollfd = { .events = POLLIN, }; - int buckets[NUM_BUCKET] = { }; + int *buckets; trace_fd = prepare_func_latency(ftrace); if (trace_fd < 0) @@ -941,6 +1143,12 @@ static int __cmd_latency(struct perf_ftrace *ftrace) evlist__start_workload(ftrace->evlist); + buckets = calloc(ftrace->bucket_num, sizeof(*buckets)); + if (buckets == NULL) { + pr_err("failed to allocate memory for the buckets\n"); + goto out; + } + line[0] = '\0'; while (!done) { if (poll(&pollfd, 1, -1) < 0) @@ -951,7 +1159,7 @@ static int __cmd_latency(struct perf_ftrace *ftrace) if (n < 0) break; - make_histogram(buckets, buf, n, line, ftrace->use_nsec); + make_histogram(ftrace, buckets, buf, n, line); } } @@ -960,7 +1168,7 @@ static int __cmd_latency(struct perf_ftrace *ftrace) if (workload_exec_errno) { const char *emsg = str_error_r(workload_exec_errno, buf, sizeof(buf)); pr_err("workload failed: %s\n", emsg); - goto out; + goto out_free_buckets; } /* read remaining buffer contents */ @@ -968,13 +1176,15 @@ static int __cmd_latency(struct perf_ftrace *ftrace) int n = read(trace_fd, buf, sizeof(buf) - 1); if (n <= 0) break; - make_histogram(buckets, buf, n, line, ftrace->use_nsec); + make_histogram(ftrace, buckets, buf, n, line); } read_func_latency(ftrace, buckets); - display_histogram(buckets, ftrace->use_nsec); + display_histogram(ftrace, buckets); +out_free_buckets: + free(buckets); out: close(trace_fd); cleanup_func_latency(ftrace); @@ -996,6 +1206,7 @@ static int prepare_func_profile(struct perf_ftrace *ftrace) { ftrace->tracer = "function_graph"; ftrace->graph_tail = 1; + ftrace->graph_verbose = 0; ftrace->profile_hash = hashmap__new(profile_hash, profile_equal, NULL); if (ftrace->profile_hash == NULL) @@ -1151,8 +1362,9 @@ static int cmp_profile_data(const void *a, const void *b) if (v1 > v2) return -1; - else + if (v1 < v2) return 1; + return 0; } static void print_profile_result(struct perf_ftrace *ftrace) @@ -1216,17 +1428,20 @@ static int __cmd_profile(struct perf_ftrace *ftrace) goto out; } + if (init_tracing_instance() < 0) + goto out; + if (reset_tracing_files(ftrace) < 0) { pr_err("failed to reset ftrace\n"); - goto out; + goto out_reset; } /* reset ftrace buffer */ if (write_tracing_file("trace", "0") < 0) - goto out; + goto out_reset; if (set_tracing_options(ftrace) < 0) - return -1; + goto out_reset; if (write_tracing_file("current_tracer", ftrace->tracer) < 0) { pr_err("failed to set current_tracer to %s\n", ftrace->tracer); @@ -1235,7 +1450,7 @@ static int __cmd_profile(struct perf_ftrace *ftrace) setup_pager(); - trace_file = get_tracing_file("trace_pipe"); + trace_file = get_tracing_instance_file("trace_pipe"); if (!trace_file) { pr_err("failed to open trace_pipe\n"); goto out_reset; @@ -1297,7 +1512,7 @@ out_free_line: out_close_fd: close(trace_fd); out_reset: - reset_tracing_files(ftrace); + exit_tracing_instance(); out: return (done && !workload_exec_errno) ? 0 : -1; } @@ -1388,6 +1603,33 @@ static void delete_filter_func(struct list_head *head) } } +static int parse_filter_event(const struct option *opt, const char *str, + int unset __maybe_unused) +{ + struct list_head *head = opt->value; + struct filter_entry *entry; + char *s, *p; + int ret = -ENOMEM; + + s = strdup(str); + if (s == NULL) + return -ENOMEM; + + while ((p = strsep(&s, ",")) != NULL) { + entry = malloc(sizeof(*entry) + strlen(p) + 1); + if (entry == NULL) + goto out; + + strcpy(entry->name, p); + list_add_tail(&entry->list, head); + } + ret = 0; + +out: + free(s); + return ret; +} + static int parse_buffer_size(const struct option *opt, const char *str, int unset) { @@ -1446,6 +1688,10 @@ static int parse_graph_tracer_opts(const struct option *opt, int ret; struct perf_ftrace *ftrace = (struct perf_ftrace *) opt->value; struct sublevel_option graph_tracer_opts[] = { + { .name = "args", .value_ptr = &ftrace->graph_args }, + { .name = "retval", .value_ptr = &ftrace->graph_retval }, + { .name = "retval-hex", .value_ptr = &ftrace->graph_retval_hex }, + { .name = "retaddr", .value_ptr = &ftrace->graph_retaddr }, { .name = "nosleep-time", .value_ptr = &ftrace->graph_nosleep_time }, { .name = "noirqs", .value_ptr = &ftrace->graph_noirqs }, { .name = "verbose", .value_ptr = &ftrace->graph_verbose }, @@ -1502,7 +1748,6 @@ int cmd_ftrace(int argc, const char **argv) int (*cmd_func)(struct perf_ftrace *) = NULL; struct perf_ftrace ftrace = { .tracer = DEFAULT_TRACER, - .target = { .uid = UINT_MAX, }, }; const struct option common_options[] = { OPT_STRING('p', "pid", &ftrace.target.pid, "pid", @@ -1538,7 +1783,7 @@ int cmd_ftrace(int argc, const char **argv) OPT_CALLBACK('g', "nograph-funcs", &ftrace.nograph_funcs, "func", "Set nograph filter on given functions", parse_filter_func), OPT_CALLBACK(0, "graph-opts", &ftrace, "options", - "Graph tracer options, available options: nosleep-time,noirqs,verbose,thresh=<n>,depth=<n>", + "Graph tracer options, available options: args,retval,retval-hex,retaddr,nosleep-time,noirqs,verbose,thresh=<n>,depth=<n>", parse_graph_tracer_opts), OPT_CALLBACK('m', "buffer-size", &ftrace.percpu_buffer_size, "size", "Size of per cpu buffer, needs to use a B, K, M or G suffix.", parse_buffer_size), @@ -1551,12 +1796,22 @@ int cmd_ftrace(int argc, const char **argv) const struct option latency_options[] = { OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", "Show latency of given function", parse_filter_func), + OPT_CALLBACK('e', "events", &ftrace.event_pair, "event1,event2", + "Show latency between the two events", parse_filter_event), #ifdef HAVE_BPF_SKEL OPT_BOOLEAN('b', "use-bpf", &ftrace.target.use_bpf, "Use BPF to measure function latency"), #endif OPT_BOOLEAN('n', "use-nsec", &ftrace.use_nsec, "Use nano-second histogram"), + OPT_UINTEGER(0, "bucket-range", &ftrace.bucket_range, + "Bucket range in ms or ns (-n/--use-nsec), default is log2() mode"), + OPT_UINTEGER(0, "min-latency", &ftrace.min_latency, + "Minimum latency (1st bucket). Works only with --bucket-range."), + OPT_UINTEGER(0, "max-latency", &ftrace.max_latency, + "Maximum latency (last bucket). Works only with --bucket-range."), + OPT_BOOLEAN(0, "hide-empty", &ftrace.hide_empty, + "Hide empty buckets in the histogram"), OPT_PARENT(common_options), }; const struct option profile_options[] = { @@ -1575,6 +1830,9 @@ int cmd_ftrace(int argc, const char **argv) OPT_CALLBACK('s', "sort", &profile_sort, "key", "Sort result by key: total (default), avg, max, count, name.", parse_sort_key), + OPT_CALLBACK(0, "graph-opts", &ftrace, "options", + "Graph tracer options, available options: nosleep-time,noirqs,thresh=<n>,depth=<n>", + parse_graph_tracer_opts), OPT_PARENT(common_options), }; const struct option *options = ftrace_options; @@ -1592,6 +1850,7 @@ int cmd_ftrace(int argc, const char **argv) INIT_LIST_HEAD(&ftrace.notrace); INIT_LIST_HEAD(&ftrace.graph_funcs); INIT_LIST_HEAD(&ftrace.nograph_funcs); + INIT_LIST_HEAD(&ftrace.event_pair); signal(SIGINT, sig_handler); signal(SIGUSR1, sig_handler); @@ -1646,12 +1905,65 @@ int cmd_ftrace(int argc, const char **argv) cmd_func = __cmd_ftrace; break; case PERF_FTRACE_LATENCY: - if (list_empty(&ftrace.filters)) { - pr_err("Should provide a function to measure\n"); + if (list_empty(&ftrace.filters) && list_empty(&ftrace.event_pair)) { + pr_err("Should provide a function or events to measure\n"); + parse_options_usage(ftrace_usage, options, "T", 1); + parse_options_usage(NULL, options, "e", 1); + ret = -EINVAL; + goto out_delete_filters; + } + if (!list_empty(&ftrace.filters) && !list_empty(&ftrace.event_pair)) { + pr_err("Please specify either of function or events\n"); parse_options_usage(ftrace_usage, options, "T", 1); + parse_options_usage(NULL, options, "e", 1); + ret = -EINVAL; + goto out_delete_filters; + } + if (!list_empty(&ftrace.event_pair) && !ftrace.target.use_bpf) { + pr_err("Event processing needs BPF\n"); + parse_options_usage(ftrace_usage, options, "b", 1); + parse_options_usage(NULL, options, "e", 1); + ret = -EINVAL; + goto out_delete_filters; + } + if (!ftrace.bucket_range && ftrace.min_latency) { + pr_err("--min-latency works only with --bucket-range\n"); + parse_options_usage(ftrace_usage, options, + "min-latency", /*short_opt=*/false); ret = -EINVAL; goto out_delete_filters; } + if (ftrace.bucket_range && !ftrace.min_latency) { + /* default min latency should be the bucket range */ + ftrace.min_latency = ftrace.bucket_range; + } + if (!ftrace.bucket_range && ftrace.max_latency) { + pr_err("--max-latency works only with --bucket-range\n"); + parse_options_usage(ftrace_usage, options, + "max-latency", /*short_opt=*/false); + ret = -EINVAL; + goto out_delete_filters; + } + if (ftrace.bucket_range && ftrace.max_latency && + ftrace.max_latency < ftrace.min_latency + ftrace.bucket_range) { + /* we need at least 1 bucket excluding min and max buckets */ + pr_err("--max-latency must be larger than min-latency + bucket-range\n"); + parse_options_usage(ftrace_usage, options, + "max-latency", /*short_opt=*/false); + ret = -EINVAL; + goto out_delete_filters; + } + /* set default unless max_latency is set and valid */ + ftrace.bucket_num = NUM_BUCKET; + if (ftrace.bucket_range) { + if (ftrace.max_latency) + ftrace.bucket_num = (ftrace.max_latency - ftrace.min_latency) / + ftrace.bucket_range + 2; + else + /* default max latency should depend on bucket range and num_buckets */ + ftrace.max_latency = (NUM_BUCKET - 2) * ftrace.bucket_range + + ftrace.min_latency; + } cmd_func = __cmd_latency; break; case PERF_FTRACE_PROFILE: @@ -1701,6 +2013,7 @@ out_delete_filters: delete_filter_func(&ftrace.notrace); delete_filter_func(&ftrace.graph_funcs); delete_filter_func(&ftrace.nograph_funcs); + delete_filter_func(&ftrace.event_pair); return ret; } |