| 1 |
|
| 2 | #pragma once
|
| 3 |
|
| 4 | #ifndef RTLA_ALLOW_CLI_P_H
|
| 5 | #error "Private header file included outside of cli.c module"
|
| 6 | #endif
|
| 7 |
|
| 8 | #include <errno.h>
|
| 9 | #include <limits.h>
|
| 10 | #include <linux/kernel.h>
|
| 11 | #include <subcmd/parse-options.h>
|
| 12 |
|
| 13 | #include "cli.h"
|
| 14 | #include "osnoise.h"
|
| 15 | #include "timerlat.h"
|
| 16 |
|
| 17 | struct osnoise_cb_data {
|
| 18 | struct osnoise_params *params;
|
| 19 | char *trace_output;
|
| 20 | };
|
| 21 |
|
| 22 | struct timerlat_cb_data {
|
| 23 | struct timerlat_params *params;
|
| 24 | char *trace_output;
|
| 25 | };
|
| 26 |
|
| 27 | |
| 28 | * Non-zero default values for parameters
|
| 29 |
|
| 30 | static const int default_dma_latency = -1;
|
| 31 | static const int default_deepest_idle_state = -2;
|
| 32 | static const int default_output_divisor = 1000;
|
| 33 | static const int default_bucket_size = 1;
|
| 34 | static const int default_entries = 256;
|
| 35 | static const enum stack_format default_stack_format = STACK_FORMAT_TRUNCATE;
|
| 36 |
|
| 37 | |
| 38 | * Range checking for long long and int option callbacks.
|
| 39 | *
|
| 40 | * Pass a pointer to a const struct as opt->data to enable range checking.
|
| 41 | * If opt->data is NULL, no range check is performed.
|
| 42 |
|
| 43 | struct llong_range {
|
| 44 | long long min;
|
| 45 | long long max;
|
| 46 | };
|
| 47 |
|
| 48 | struct int_range {
|
| 49 | int min;
|
| 50 | int max;
|
| 51 | };
|
| 52 |
|
| 53 | #define LLONG_RANGE(lo, hi) \
|
| 54 | (&(const struct llong_range){ .min = (lo), .max = (hi) })
|
| 55 |
|
| 56 | #define INT_RANGE(lo, hi) \
|
| 57 | (&(const struct int_range){ .min = (lo), .max = (hi) })
|
| 58 |
|
| 59 | static int check_llong_range(const struct option *opt, long long value)
|
| 60 | {
|
| 61 | const struct llong_range *range = opt->data;
|
| 62 |
|
| 63 | if (!range)
|
| 64 | return 0;
|
| 65 | if (value < range->min || value > range->max) {
|
| 66 | fprintf(stderr, " Error: --%s value %lld is out of range [%lld, %lld]\n",
|
| 67 | opt->long_name, value, range->min, range->max);
|
| 68 | return -1;
|
| 69 | }
|
| 70 | return 0;
|
| 71 | }
|
| 72 |
|
| 73 | static int check_int_range(const struct option *opt, int value)
|
| 74 | {
|
| 75 | const struct int_range *range = opt->data;
|
| 76 |
|
| 77 | if (!range)
|
| 78 | return 0;
|
| 79 | if (value < range->min || value > range->max) {
|
| 80 | fprintf(stderr, " Error: --%s value %d is out of range [%d, %d]\n",
|
| 81 | opt->long_name, value, range->min, range->max);
|
| 82 | return -1;
|
| 83 | }
|
| 84 | return 0;
|
| 85 | }
|
| 86 |
|
| 87 | |
| 88 | * OPT_CALLBACK variant that populates .data (for range checking).
|
| 89 |
|
| 90 | #define RTLA_OPT_CALLBACK_DATA(s, l, v, a, h, f, d) \
|
| 91 | { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), \
|
| 92 | .value = (v), .argh = (a), .help = (h), .callback = (f), \
|
| 93 | .data = (void *)(d) }
|
| 94 |
|
| 95 | #define RTLA_OPT_CALLBACK_DATA_DEFVAL(s, l, v, a, h, f, d, dv) \
|
| 96 | { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), \
|
| 97 | .value = (v), .argh = (a), .help = (h), .callback = (f), \
|
| 98 | .data = (void *)(d), .defval = (intptr_t)(dv) }
|
| 99 |
|
| 100 | |
| 101 | * Shorthand macros for integer/long long command line options using
|
| 102 | * opt_int_callback/opt_llong_callback, with variants that set defval
|
| 103 | * and/or data (for range checking).
|
| 104 | *
|
| 105 | * Note: defval's type is intptr_t. opt_int_callback interprets it directly as
|
| 106 | * an int, opt_llong_callback interprets it as a pointer to a long long, as
|
| 107 | * long long does not fit into intptr_t on 32-bit architectures.
|
| 108 |
|
| 109 | #define RTLA_OPT_LLONG(s, l, v, a, h) \
|
| 110 | OPT_CALLBACK(s, l, v, a, h, opt_llong_callback)
|
| 111 |
|
| 112 | #define RTLA_OPT_LLONG_DEFVAL(s, l, v, a, h, d) { .type = OPTION_CALLBACK, \
|
| 113 | .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \
|
| 114 | .help = (h), .callback = opt_llong_callback, .defval = (intptr_t)(d) }
|
| 115 |
|
| 116 | #define RTLA_OPT_LLONG_DATA(s, l, v, a, h, d) { .type = OPTION_CALLBACK, \
|
| 117 | .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \
|
| 118 | .help = (h), .callback = opt_llong_callback, .data = (void *)(d) }
|
| 119 |
|
| 120 | #define RTLA_OPT_INT(s, l, v, a, h) \
|
| 121 | OPT_CALLBACK(s, l, v, a, h, opt_int_callback)
|
| 122 |
|
| 123 | #define RTLA_OPT_INT_DEFVAL(s, l, v, a, h, d) { .type = OPTION_CALLBACK, \
|
| 124 | .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \
|
| 125 | .help = (h), .callback = opt_int_callback, .defval = (intptr_t)(d) }
|
| 126 |
|
| 127 | #define RTLA_OPT_INT_DATA_DEFVAL(s, l, v, a, h, d, dv) { .type = OPTION_CALLBACK, \
|
| 128 | .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \
|
| 129 | .help = (h), .callback = opt_int_callback, \
|
| 130 | .data = (void *)(d), .defval = (intptr_t)(dv) }
|
| 131 |
|
| 132 | |
| 133 | * Macros for command line options common to all tools
|
| 134 | *
|
| 135 | * Note: Some of the options are common to both timerlat and osnoise, but
|
| 136 | * have a slightly different meaning. Such options take additional arguments
|
| 137 | * that have to be provided by the *_parse_args() function of the corresponding
|
| 138 | * tool.
|
| 139 | *
|
| 140 | * All macros defined here assume the presence of a params variable of
|
| 141 | * the corresponding tool type (i.e struct timerlat_params or struct osnoise_params)
|
| 142 | * and a cb_data variable of the matching type.
|
| 143 |
|
| 144 |
|
| 145 | #define RTLA_OPT_STOP(short, long, name) OPT_CALLBACK_FLAG(short, long, \
|
| 146 | ¶ms->common.stop_us, \
|
| 147 | "us", \
|
| 148 | "stop trace if " name " is higher than the argument in us", \
|
| 149 | opt_llong_callback, PARSE_OPT_NOAUTONEG)
|
| 150 |
|
| 151 | #define RTLA_OPT_STOP_TOTAL(short, long, name) OPT_CALLBACK_FLAG(short, long, \
|
| 152 | ¶ms->common.stop_total_us, \
|
| 153 | "us", \
|
| 154 | "stop trace if " name " is higher than the argument in us", \
|
| 155 | opt_llong_callback, PARSE_OPT_NOAUTONEG)
|
| 156 |
|
| 157 | #define RTLA_OPT_TRACE_OUTPUT(tracer, cb) OPT_CALLBACK_OPTARG('t', "trace", \
|
| 158 | (const char **)&cb_data.trace_output, \
|
| 159 | tracer "_trace.txt", \
|
| 160 | "[file]", \
|
| 161 | "save the stopped trace to [file|" tracer "_trace.txt]", \
|
| 162 | cb)
|
| 163 |
|
| 164 | #define RTLA_OPT_CPUS OPT_CALLBACK('c', "cpus", ¶ms->common, \
|
| 165 | "cpu-list", \
|
| 166 | "run the tracer only on the given cpus", \
|
| 167 | opt_cpus_cb)
|
| 168 |
|
| 169 | #define RTLA_OPT_CGROUP OPT_CALLBACK_OPTARG('C', "cgroup", ¶ms->common, \
|
| 170 | "[cgroup_name]", NULL, \
|
| 171 | "set cgroup, no argument means rtla's cgroup will be inherited", \
|
| 172 | opt_cgroup_cb)
|
| 173 |
|
| 174 | #define RTLA_OPT_USER_THREADS OPT_CALLBACK_NOOPT('u', "user-threads", params, NULL, \
|
| 175 | "use rtla user-space threads instead of kernel-space timerlat threads", \
|
| 176 | opt_user_threads_cb)
|
| 177 |
|
| 178 | #define RTLA_OPT_KERNEL_THREADS OPT_BOOLEAN('k', "kernel-threads", \
|
| 179 | ¶ms->common.kernel_workload, \
|
| 180 | "use timerlat kernel-space threads instead of rtla user-space threads")
|
| 181 |
|
| 182 | #define RTLA_OPT_USER_LOAD OPT_BOOLEAN('U', "user-load", ¶ms->common.user_data, \
|
| 183 | "enable timerlat for user-defined user-space workload")
|
| 184 |
|
| 185 | #define RTLA_OPT_DURATION OPT_CALLBACK('d', "duration", ¶ms->common, \
|
| 186 | "time[s|m|h|d]", \
|
| 187 | "set the duration of the session", \
|
| 188 | opt_duration_cb)
|
| 189 |
|
| 190 | #define RTLA_OPT_EVENT OPT_CALLBACK('e', "event", ¶ms->common.events, \
|
| 191 | "sys:event", \
|
| 192 | "enable the <sys:event> in the trace instance, multiple -e are allowed", \
|
| 193 | opt_event_cb)
|
| 194 |
|
| 195 | #define RTLA_OPT_HOUSEKEEPING OPT_CALLBACK('H', "house-keeping", ¶ms->common, \
|
| 196 | "cpu-list", \
|
| 197 | "run rtla control threads only on the given cpus", \
|
| 198 | opt_housekeeping_cb)
|
| 199 |
|
| 200 | #define RTLA_OPT_PRIORITY OPT_CALLBACK('P', "priority", ¶ms->common, \
|
| 201 | "o:prio|r:prio|f:prio|d:runtime:period", \
|
| 202 | "set scheduling parameters", \
|
| 203 | opt_priority_cb)
|
| 204 |
|
| 205 | #define RTLA_OPT_TRIGGER OPT_CALLBACK(0, "trigger", ¶ms->common.events, \
|
| 206 | "trigger", \
|
| 207 | "enable a trace event trigger to the previous -e event", \
|
| 208 | opt_trigger_cb)
|
| 209 |
|
| 210 | #define RTLA_OPT_FILTER OPT_CALLBACK(0, "filter", ¶ms->common.events, \
|
| 211 | "filter", \
|
| 212 | "enable a trace event filter to the previous -e event", \
|
| 213 | opt_filter_cb)
|
| 214 |
|
| 215 | #define RTLA_OPT_QUIET OPT_BOOLEAN('q', "quiet", ¶ms->common.quiet, \
|
| 216 | "print only a summary at the end")
|
| 217 |
|
| 218 | #define RTLA_OPT_TRACE_BUFFER_SIZE RTLA_OPT_INT(0, "trace-buffer-size", \
|
| 219 | ¶ms->common.buffer_size, "kB", \
|
| 220 | "set the per-cpu trace buffer size in kB")
|
| 221 |
|
| 222 | #define RTLA_OPT_WARM_UP RTLA_OPT_INT(0, "warm-up", ¶ms->common.warmup, "s", \
|
| 223 | "let the workload run for s seconds before collecting data")
|
| 224 |
|
| 225 | #define RTLA_OPT_AUTO(cb) OPT_CALLBACK('a', "auto", &cb_data, "us", \
|
| 226 | "set automatic trace mode, stopping the session if argument in us sample is hit", \
|
| 227 | cb)
|
| 228 |
|
| 229 | #define RTLA_OPT_ON_THRESHOLD(threshold, cb) OPT_CALLBACK(0, "on-threshold", \
|
| 230 | ¶ms->common.threshold_actions, \
|
| 231 | "action", \
|
| 232 | "define action to be executed at " threshold " threshold, multiple are allowed", \
|
| 233 | cb)
|
| 234 |
|
| 235 | #define RTLA_OPT_ON_END(cb) OPT_CALLBACK(0, "on-end", ¶ms->common.end_actions, \
|
| 236 | "action", \
|
| 237 | "define action to be executed at measurement end, multiple are allowed", \
|
| 238 | cb)
|
| 239 |
|
| 240 | #define RTLA_OPT_DEBUG OPT_BOOLEAN('D', "debug", &config_debug, \
|
| 241 | "print debug info")
|
| 242 |
|
| 243 | |
| 244 | * Helper functions for parsing numeric option arguments.
|
| 245 |
|
| 246 | static void opt_err(const struct option *opt, const char *arg, const char *msg)
|
| 247 | {
|
| 248 | fprintf(stderr, " Error: --%s: '%s' %s\n", opt->long_name, arg, msg);
|
| 249 | }
|
| 250 |
|
| 251 | static int strtoll_safe(const struct option *opt, const char *arg, long long *value)
|
| 252 | {
|
| 253 | long long tmp;
|
| 254 | char *end;
|
| 255 |
|
| 256 | errno = 0;
|
| 257 | tmp = strtoll(arg, &end, 10);
|
| 258 | if (errno || *end || end == arg) {
|
| 259 | opt_err(opt, arg, "is not a valid number");
|
| 260 | return -1;
|
| 261 | }
|
| 262 | *value = tmp;
|
| 263 | return 0;
|
| 264 | }
|
| 265 |
|
| 266 | static int strtoi_safe(const struct option *opt, const char *arg, int *value)
|
| 267 | {
|
| 268 | int tmp;
|
| 269 |
|
| 270 | if (strtoi(arg, &tmp)) {
|
| 271 | opt_err(opt, arg, "is not a valid number");
|
| 272 | return -1;
|
| 273 | }
|
| 274 | *value = tmp;
|
| 275 | return 0;
|
| 276 | }
|
| 277 |
|
| 278 | |
| 279 | * Common callback functions for command line options
|
| 280 |
|
| 281 |
|
| 282 | static int opt_llong_callback(const struct option *opt, const char *arg, int unset)
|
| 283 | {
|
| 284 | long long *value = opt->value;
|
| 285 |
|
| 286 | if (unset) {
|
| 287 | *value = opt->defval ? *(long long *)opt->defval : 0;
|
| 288 | return 0;
|
| 289 | }
|
| 290 |
|
| 291 | if (!arg)
|
| 292 | return -1;
|
| 293 |
|
| 294 | if (strtoll_safe(opt, arg, value))
|
| 295 | return -1;
|
| 296 | if (check_llong_range(opt, *value))
|
| 297 | return -1;
|
| 298 | return 0;
|
| 299 | }
|
| 300 |
|
| 301 | static int opt_int_callback(const struct option *opt, const char *arg, int unset)
|
| 302 | {
|
| 303 | int *value = opt->value;
|
| 304 |
|
| 305 | if (unset) {
|
| 306 | *value = (int)opt->defval;
|
| 307 | return 0;
|
| 308 | }
|
| 309 |
|
| 310 | if (!arg)
|
| 311 | return -1;
|
| 312 |
|
| 313 | if (strtoi_safe(opt, arg, value))
|
| 314 | return -1;
|
| 315 | if (check_int_range(opt, *value))
|
| 316 | return -1;
|
| 317 |
|
| 318 | return 0;
|
| 319 | }
|
| 320 |
|
| 321 | static int opt_cpus_cb(const struct option *opt, const char *arg, int unset)
|
| 322 | {
|
| 323 | struct common_params *params = opt->value;
|
| 324 | int retval;
|
| 325 |
|
| 326 | if (unset) {
|
| 327 | CPU_ZERO(¶ms->monitored_cpus);
|
| 328 | params->cpus = NULL;
|
| 329 | return 0;
|
| 330 | }
|
| 331 |
|
| 332 | if (!arg)
|
| 333 | return -1;
|
| 334 |
|
| 335 | retval = parse_cpu_set((char *)arg, ¶ms->monitored_cpus);
|
| 336 | if (retval) {
|
| 337 | opt_err(opt, arg, "is not a valid cpu set");
|
| 338 | return -1;
|
| 339 | }
|
| 340 | params->cpus = (char *)arg;
|
| 341 |
|
| 342 | return 0;
|
| 343 | }
|
| 344 |
|
| 345 | static int opt_cgroup_cb(const struct option *opt, const char *arg, int unset)
|
| 346 | {
|
| 347 | struct common_params *params = opt->value;
|
| 348 |
|
| 349 | if (unset) {
|
| 350 | params->cgroup = 0;
|
| 351 | params->cgroup_name = NULL;
|
| 352 | return 0;
|
| 353 | }
|
| 354 |
|
| 355 | params->cgroup = 1;
|
| 356 | params->cgroup_name = (char *)arg;
|
| 357 | if (params->cgroup_name && params->cgroup_name[0] == '=')
|
| 358 |
|
| 359 | ++params->cgroup_name;
|
| 360 |
|
| 361 | return 0;
|
| 362 | }
|
| 363 |
|
| 364 | static int opt_duration_cb(const struct option *opt, const char *arg, int unset)
|
| 365 | {
|
| 366 | struct common_params *params = opt->value;
|
| 367 |
|
| 368 | if (unset) {
|
| 369 | params->duration = 0;
|
| 370 | return 0;
|
| 371 | }
|
| 372 |
|
| 373 | if (!arg)
|
| 374 | return -1;
|
| 375 |
|
| 376 | params->duration = parse_seconds_duration((char *)arg);
|
| 377 | if (!params->duration) {
|
| 378 | opt_err(opt, arg, "is not a valid duration");
|
| 379 | return -1;
|
| 380 | }
|
| 381 |
|
| 382 | return 0;
|
| 383 | }
|
| 384 |
|
| 385 | static int opt_event_cb(const struct option *opt, const char *arg, int unset)
|
| 386 | {
|
| 387 | struct trace_events **events = opt->value;
|
| 388 | struct trace_events *tevent;
|
| 389 |
|
| 390 | if (unset || !arg)
|
| 391 | return -1;
|
| 392 |
|
| 393 | tevent = trace_event_alloc((char *)arg);
|
| 394 | if (!tevent)
|
| 395 | fatal("Error alloc trace event");
|
| 396 |
|
| 397 | if (*events)
|
| 398 | tevent->next = *events;
|
| 399 | *events = tevent;
|
| 400 |
|
| 401 | return 0;
|
| 402 | }
|
| 403 |
|
| 404 | static int opt_housekeeping_cb(const struct option *opt, const char *arg, int unset)
|
| 405 | {
|
| 406 | struct common_params *params = opt->value;
|
| 407 | int retval;
|
| 408 |
|
| 409 | if (unset) {
|
| 410 | params->hk_cpus = 0;
|
| 411 | CPU_ZERO(¶ms->hk_cpu_set);
|
| 412 | return 0;
|
| 413 | }
|
| 414 |
|
| 415 | if (!arg)
|
| 416 | return -1;
|
| 417 |
|
| 418 | params->hk_cpus = 1;
|
| 419 | retval = parse_cpu_set((char *)arg, ¶ms->hk_cpu_set);
|
| 420 | if (retval) {
|
| 421 | opt_err(opt, arg, "is not a valid cpu set");
|
| 422 | return -1;
|
| 423 | }
|
| 424 |
|
| 425 | return 0;
|
| 426 | }
|
| 427 |
|
| 428 | static int opt_priority_cb(const struct option *opt, const char *arg, int unset)
|
| 429 | {
|
| 430 | struct common_params *params = opt->value;
|
| 431 | int retval;
|
| 432 |
|
| 433 | if (unset) {
|
| 434 | memset(¶ms->sched_param, 0, sizeof(params->sched_param));
|
| 435 | params->set_sched = 0;
|
| 436 | return 0;
|
| 437 | }
|
| 438 |
|
| 439 | if (!arg)
|
| 440 | return -1;
|
| 441 |
|
| 442 | retval = parse_prio((char *)arg, ¶ms->sched_param);
|
| 443 | if (retval == -1) {
|
| 444 | opt_err(opt, arg, "is not a valid priority");
|
| 445 | return -1;
|
| 446 | }
|
| 447 | params->set_sched = 1;
|
| 448 |
|
| 449 | return 0;
|
| 450 | }
|
| 451 |
|
| 452 | static int opt_trigger_cb(const struct option *opt, const char *arg, int unset)
|
| 453 | {
|
| 454 | struct trace_events **events = opt->value;
|
| 455 |
|
| 456 | if (unset || !arg)
|
| 457 | return -1;
|
| 458 |
|
| 459 | if (!*events) {
|
| 460 | opt_err(opt, arg, "has no previous event to apply to");
|
| 461 | return -1;
|
| 462 | }
|
| 463 |
|
| 464 | trace_event_add_trigger(*events, (char *)arg);
|
| 465 |
|
| 466 | return 0;
|
| 467 | }
|
| 468 |
|
| 469 | static int opt_filter_cb(const struct option *opt, const char *arg, int unset)
|
| 470 | {
|
| 471 | struct trace_events **events = opt->value;
|
| 472 |
|
| 473 | if (unset || !arg)
|
| 474 | return -1;
|
| 475 |
|
| 476 | if (!*events) {
|
| 477 | opt_err(opt, arg, "has no previous event to apply to");
|
| 478 | return -1;
|
| 479 | }
|
| 480 |
|
| 481 | trace_event_add_filter(*events, (char *)arg);
|
| 482 |
|
| 483 | return 0;
|
| 484 | }
|
| 485 |
|
| 486 | |
| 487 | * Macros for command line options specific to osnoise
|
| 488 |
|
| 489 | #define OSNOISE_OPT_PERIOD RTLA_OPT_LLONG_DATA('p', "period", ¶ms->period, "us", \
|
| 490 | "osnoise period in us", \
|
| 491 | LLONG_RANGE(1, 10000000))
|
| 492 |
|
| 493 | #define OSNOISE_OPT_RUNTIME RTLA_OPT_LLONG_DATA('r', "runtime", ¶ms->runtime, "us", \
|
| 494 | "osnoise runtime in us", \
|
| 495 | LLONG_RANGE(100, LLONG_MAX))
|
| 496 |
|
| 497 | #define OSNOISE_OPT_THRESHOLD RTLA_OPT_LLONG('T', "threshold", ¶ms->threshold, "us", \
|
| 498 | "the minimum delta to be considered a noise")
|
| 499 |
|
| 500 | |
| 501 | * Callback functions for command line options for osnoise tools
|
| 502 |
|
| 503 |
|
| 504 | static int opt_osnoise_auto_cb(const struct option *opt, const char *arg, int unset)
|
| 505 | {
|
| 506 | struct osnoise_cb_data *cb_data = opt->value;
|
| 507 | struct osnoise_params *params = cb_data->params;
|
| 508 | long long auto_thresh;
|
| 509 |
|
| 510 | if (unset) {
|
| 511 | params->common.stop_us = 0;
|
| 512 | params->threshold = 0;
|
| 513 | cb_data->trace_output = NULL;
|
| 514 | return 0;
|
| 515 | }
|
| 516 |
|
| 517 | if (!arg)
|
| 518 | return -1;
|
| 519 |
|
| 520 | if (strtoll_safe(opt, arg, &auto_thresh))
|
| 521 | return -1;
|
| 522 | params->common.stop_us = auto_thresh;
|
| 523 | params->threshold = 1;
|
| 524 |
|
| 525 | if (!cb_data->trace_output)
|
| 526 | cb_data->trace_output = "osnoise_trace.txt";
|
| 527 |
|
| 528 | return 0;
|
| 529 | }
|
| 530 |
|
| 531 | static int opt_osnoise_trace_output_cb(const struct option *opt, const char *arg, int unset)
|
| 532 | {
|
| 533 | const char **trace_output = opt->value;
|
| 534 |
|
| 535 | if (unset) {
|
| 536 | *trace_output = NULL;
|
| 537 | return 0;
|
| 538 | }
|
| 539 |
|
| 540 | if (!arg) {
|
| 541 | *trace_output = "osnoise_trace.txt";
|
| 542 | } else {
|
| 543 | *trace_output = (char *)arg;
|
| 544 | if (*trace_output && (*trace_output)[0] == '=')
|
| 545 |
|
| 546 | ++*trace_output;
|
| 547 | }
|
| 548 |
|
| 549 | return 0;
|
| 550 | }
|
| 551 |
|
| 552 | static int opt_osnoise_on_threshold_cb(const struct option *opt, const char *arg, int unset)
|
| 553 | {
|
| 554 | struct actions *actions = opt->value;
|
| 555 | int retval;
|
| 556 |
|
| 557 | if (unset || !arg)
|
| 558 | return -1;
|
| 559 |
|
| 560 | retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
|
| 561 | if (retval) {
|
| 562 | opt_err(opt, arg, "is not a valid action");
|
| 563 | return -1;
|
| 564 | }
|
| 565 |
|
| 566 | return 0;
|
| 567 | }
|
| 568 |
|
| 569 | static int opt_osnoise_on_end_cb(const struct option *opt, const char *arg, int unset)
|
| 570 | {
|
| 571 | struct actions *actions = opt->value;
|
| 572 | int retval;
|
| 573 |
|
| 574 | if (unset || !arg)
|
| 575 | return -1;
|
| 576 |
|
| 577 | retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
|
| 578 | if (retval) {
|
| 579 | opt_err(opt, arg, "is not a valid action");
|
| 580 | return -1;
|
| 581 | }
|
| 582 |
|
| 583 | return 0;
|
| 584 | }
|
| 585 |
|
| 586 | |
| 587 | * Macros for command line options specific to timerlat
|
| 588 |
|
| 589 | #define TIMERLAT_OPT_PERIOD RTLA_OPT_LLONG_DATA('p', "period", ¶ms->timerlat_period_us, "us", \
|
| 590 | "timerlat period in us", \
|
| 591 | LLONG_RANGE(100, 1000000))
|
| 592 |
|
| 593 | #define TIMERLAT_OPT_STACK RTLA_OPT_LLONG('s', "stack", ¶ms->print_stack, "us", \
|
| 594 | "save the stack trace at the IRQ if a thread latency is higher than the argument in us")
|
| 595 |
|
| 596 | #define TIMERLAT_OPT_NANO OPT_CALLBACK_NOOPT('n', "nano", params, NULL, \
|
| 597 | "display data in nanoseconds", \
|
| 598 | opt_nano_cb)
|
| 599 |
|
| 600 | #define TIMERLAT_OPT_DMA_LATENCY RTLA_OPT_INT_DATA_DEFVAL(0, "dma-latency", \
|
| 601 | ¶ms->dma_latency, "us", \
|
| 602 | "set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency", \
|
| 603 | INT_RANGE(0, 10000), default_dma_latency)
|
| 604 |
|
| 605 | #define TIMERLAT_OPT_DEEPEST_IDLE_STATE RTLA_OPT_INT_DATA_DEFVAL(0, "deepest-idle-state", \
|
| 606 | ¶ms->deepest_idle_state, "n", \
|
| 607 | "only go down to idle state n on cpus used by timerlat to reduce exit from idle latency", \
|
| 608 | INT_RANGE(-1, INT_MAX), default_deepest_idle_state)
|
| 609 |
|
| 610 | #define TIMERLAT_OPT_AA_ONLY OPT_CALLBACK(0, "aa-only", params, "us", \
|
| 611 | "stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)", \
|
| 612 | opt_aa_only_cb)
|
| 613 |
|
| 614 | #define TIMERLAT_OPT_NO_AA OPT_BOOLEAN(0, "no-aa", ¶ms->no_aa, \
|
| 615 | "disable auto-analysis, reducing rtla timerlat cpu usage")
|
| 616 |
|
| 617 | #define TIMERLAT_OPT_DUMPS_TASKS OPT_BOOLEAN(0, "dump-tasks", ¶ms->dump_tasks, \
|
| 618 | "prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)")
|
| 619 |
|
| 620 | #define TIMERLAT_OPT_BPF_ACTION OPT_STRING(0, "bpf-action", ¶ms->bpf_action_program, \
|
| 621 | "program", \
|
| 622 | "load and execute BPF program when latency threshold is exceeded")
|
| 623 |
|
| 624 | #define TIMERLAT_OPT_STACK_FORMAT OPT_CALLBACK(0, "stack-format", ¶ms->stack_format, "format", \
|
| 625 | "set the stack format (truncate, skip, full)", \
|
| 626 | opt_stack_format_cb)
|
| 627 |
|
| 628 | #define TIMERLAT_OPT_ALIGNED RTLA_OPT_CALLBACK_DATA('A', "aligned", params, "us", \
|
| 629 | "align thread wakeups to a specific offset", \
|
| 630 | opt_timerlat_align_cb, LLONG_RANGE(0, LLONG_MAX))
|
| 631 |
|
| 632 | |
| 633 | * Callback functions for command line options for timerlat tools
|
| 634 |
|
| 635 |
|
| 636 | static int opt_timerlat_auto_cb(const struct option *opt, const char *arg, int unset)
|
| 637 | {
|
| 638 | struct timerlat_cb_data *cb_data = opt->value;
|
| 639 | struct timerlat_params *params = cb_data->params;
|
| 640 | long long auto_thresh;
|
| 641 |
|
| 642 | if (unset) {
|
| 643 | params->common.stop_total_us = 0;
|
| 644 | params->common.stop_us = 0;
|
| 645 | params->print_stack = 0;
|
| 646 | cb_data->trace_output = NULL;
|
| 647 | return 0;
|
| 648 | }
|
| 649 |
|
| 650 | if (!arg)
|
| 651 | return -1;
|
| 652 |
|
| 653 | if (strtoll_safe(opt, arg, &auto_thresh))
|
| 654 | return -1;
|
| 655 | params->common.stop_total_us = auto_thresh;
|
| 656 | params->common.stop_us = auto_thresh;
|
| 657 | params->print_stack = auto_thresh;
|
| 658 |
|
| 659 | if (!cb_data->trace_output)
|
| 660 | cb_data->trace_output = "timerlat_trace.txt";
|
| 661 |
|
| 662 | return 0;
|
| 663 | }
|
| 664 |
|
| 665 | static int opt_aa_only_cb(const struct option *opt, const char *arg, int unset)
|
| 666 | {
|
| 667 | struct timerlat_params *params = opt->value;
|
| 668 | long long auto_thresh;
|
| 669 |
|
| 670 | if (unset) {
|
| 671 | params->common.stop_total_us = 0;
|
| 672 | params->common.stop_us = 0;
|
| 673 | params->print_stack = 0;
|
| 674 | params->common.aa_only = 0;
|
| 675 | return 0;
|
| 676 | }
|
| 677 |
|
| 678 | if (!arg)
|
| 679 | return -1;
|
| 680 |
|
| 681 | if (strtoll_safe(opt, arg, &auto_thresh))
|
| 682 | return -1;
|
| 683 | params->common.stop_total_us = auto_thresh;
|
| 684 | params->common.stop_us = auto_thresh;
|
| 685 | params->print_stack = auto_thresh;
|
| 686 | params->common.aa_only = 1;
|
| 687 |
|
| 688 | return 0;
|
| 689 | }
|
| 690 |
|
| 691 | static int opt_timerlat_trace_output_cb(const struct option *opt, const char *arg, int unset)
|
| 692 | {
|
| 693 | const char **trace_output = opt->value;
|
| 694 |
|
| 695 | if (unset) {
|
| 696 | *trace_output = NULL;
|
| 697 | return 0;
|
| 698 | }
|
| 699 |
|
| 700 | if (!arg) {
|
| 701 | *trace_output = "timerlat_trace.txt";
|
| 702 | } else {
|
| 703 | *trace_output = (char *)arg;
|
| 704 | if (*trace_output && (*trace_output)[0] == '=')
|
| 705 |
|
| 706 | ++*trace_output;
|
| 707 | }
|
| 708 |
|
| 709 | return 0;
|
| 710 | }
|
| 711 |
|
| 712 | static int opt_timerlat_on_threshold_cb(const struct option *opt, const char *arg, int unset)
|
| 713 | {
|
| 714 | struct actions *actions = opt->value;
|
| 715 | int retval;
|
| 716 |
|
| 717 | if (unset || !arg)
|
| 718 | return -1;
|
| 719 |
|
| 720 | retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
|
| 721 | if (retval) {
|
| 722 | opt_err(opt, arg, "is not a valid action");
|
| 723 | return -1;
|
| 724 | }
|
| 725 |
|
| 726 | return 0;
|
| 727 | }
|
| 728 |
|
| 729 | static int opt_timerlat_on_end_cb(const struct option *opt, const char *arg, int unset)
|
| 730 | {
|
| 731 | struct actions *actions = opt->value;
|
| 732 | int retval;
|
| 733 |
|
| 734 | if (unset || !arg)
|
| 735 | return -1;
|
| 736 |
|
| 737 | retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
|
| 738 | if (retval) {
|
| 739 | opt_err(opt, arg, "is not a valid action");
|
| 740 | return -1;
|
| 741 | }
|
| 742 |
|
| 743 | return 0;
|
| 744 | }
|
| 745 |
|
| 746 | static int opt_user_threads_cb(const struct option *opt, const char *arg, int unset)
|
| 747 | {
|
| 748 | struct timerlat_params *params = opt->value;
|
| 749 |
|
| 750 | if (unset) {
|
| 751 | params->common.user_workload = false;
|
| 752 | params->common.user_data = false;
|
| 753 | return 0;
|
| 754 | }
|
| 755 |
|
| 756 | params->common.user_workload = true;
|
| 757 | params->common.user_data = true;
|
| 758 |
|
| 759 | return 0;
|
| 760 | }
|
| 761 |
|
| 762 | static int opt_nano_cb(const struct option *opt, const char *arg, int unset)
|
| 763 | {
|
| 764 | struct timerlat_params *params = opt->value;
|
| 765 |
|
| 766 | if (unset) {
|
| 767 | params->common.output_divisor = default_output_divisor;
|
| 768 | return 0;
|
| 769 | }
|
| 770 |
|
| 771 | params->common.output_divisor = 1;
|
| 772 |
|
| 773 | return 0;
|
| 774 | }
|
| 775 |
|
| 776 | static int opt_stack_format_cb(const struct option *opt, const char *arg, int unset)
|
| 777 | {
|
| 778 | int *format = opt->value;
|
| 779 |
|
| 780 | if (unset) {
|
| 781 | *format = default_stack_format;
|
| 782 | return 0;
|
| 783 | }
|
| 784 |
|
| 785 | if (!arg)
|
| 786 | return -1;
|
| 787 |
|
| 788 | *format = parse_stack_format((char *)arg);
|
| 789 |
|
| 790 | if (*format == -1) {
|
| 791 | opt_err(opt, arg, "is not a valid stack format");
|
| 792 | return -1;
|
| 793 | }
|
| 794 |
|
| 795 | return 0;
|
| 796 | }
|
| 797 |
|
| 798 | static int opt_timerlat_align_cb(const struct option *opt, const char *arg, int unset)
|
| 799 | {
|
| 800 | struct timerlat_params *params = opt->value;
|
| 801 | long long val;
|
| 802 |
|
| 803 | if (unset) {
|
| 804 | params->timerlat_align = false;
|
| 805 | params->timerlat_align_us = 0;
|
| 806 | return 0;
|
| 807 | }
|
| 808 |
|
| 809 | if (!arg)
|
| 810 | return -1;
|
| 811 |
|
| 812 | if (strtoll_safe(opt, arg, &val))
|
| 813 | return -1;
|
| 814 | if (check_llong_range(opt, val))
|
| 815 | return -1;
|
| 816 |
|
| 817 | params->timerlat_align = true;
|
| 818 | params->timerlat_align_us = val;
|
| 819 |
|
| 820 | return 0;
|
| 821 | }
|
| 822 |
|
| 823 | |
| 824 | * Macros for command line options specific to histogram-based tools
|
| 825 |
|
| 826 |
|
| 827 | #define HIST_OPT_BUCKET_SIZE RTLA_OPT_INT_DATA_DEFVAL('b', "bucket-size", \
|
| 828 | ¶ms->common.hist.bucket_size, "N", \
|
| 829 | "set the histogram bucket size (default 1)", \
|
| 830 | INT_RANGE(1, 999999), default_bucket_size)
|
| 831 |
|
| 832 | #define HIST_OPT_ENTRIES RTLA_OPT_INT_DATA_DEFVAL('E', "entries", \
|
| 833 | ¶ms->common.hist.entries, "N", \
|
| 834 | "set the number of entries of the histogram (default 256)", \
|
| 835 | INT_RANGE(10, 9999999), default_entries)
|
| 836 |
|
| 837 | #define HIST_OPT_NO_IRQ OPT_BOOLEAN_FLAG(0, "no-irq", ¶ms->common.hist.no_irq, \
|
| 838 | "ignore IRQ latencies", PARSE_OPT_NOAUTONEG)
|
| 839 |
|
| 840 | #define HIST_OPT_NO_THREAD OPT_BOOLEAN_FLAG(0, "no-thread", ¶ms->common.hist.no_thread, \
|
| 841 | "ignore thread latencies", PARSE_OPT_NOAUTONEG)
|
| 842 |
|
| 843 | #define HIST_OPT_NO_HEADER OPT_BOOLEAN(0, "no-header", ¶ms->common.hist.no_header, \
|
| 844 | "do not print header")
|
| 845 |
|
| 846 | #define HIST_OPT_NO_SUMMARY OPT_BOOLEAN(0, "no-summary", ¶ms->common.hist.no_summary, \
|
| 847 | "do not print summary")
|
| 848 |
|
| 849 | #define HIST_OPT_NO_INDEX OPT_BOOLEAN(0, "no-index", ¶ms->common.hist.no_index, \
|
| 850 | "do not print index")
|
| 851 |
|
| 852 | #define HIST_OPT_WITH_ZEROS OPT_BOOLEAN(0, "with-zeros", ¶ms->common.hist.with_zeros, \
|
| 853 | "print zero only entries")
|
| 854 |
|