1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 4 #include <pthread.h> 5 #include <signal.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <getopt.h> 9 #include <sys/sysinfo.h> 10 11 #include "common.h" 12 13 struct trace_instance *trace_inst; 14 volatile int stop_tracing; 15 int nr_cpus; 16 17 static void stop_trace(int sig) 18 { 19 if (stop_tracing) { 20 /* 21 * Stop requested twice in a row; abort event processing and 22 * exit immediately 23 */ 24 tracefs_iterate_stop(trace_inst->inst); 25 return; 26 } 27 stop_tracing = 1; 28 if (trace_inst) 29 trace_instance_stop(trace_inst); 30 } 31 32 /* 33 * set_signals - handles the signal to stop the tool 34 */ 35 static void set_signals(struct common_params *params) 36 { 37 signal(SIGINT, stop_trace); 38 if (params->duration) { 39 signal(SIGALRM, stop_trace); 40 alarm(params->duration); 41 } 42 } 43 44 /* 45 * unset_signals - unsets the signals to stop the tool 46 */ 47 static void unset_signals(struct common_params *params) 48 { 49 signal(SIGINT, SIG_DFL); 50 if (params->duration) { 51 alarm(0); 52 signal(SIGALRM, SIG_DFL); 53 } 54 } 55 56 /* 57 * getopt_auto - auto-generates optstring from long_options 58 */ 59 int getopt_auto(int argc, char **argv, const struct option *long_opts) 60 { 61 char opts[256]; 62 int n = 0; 63 64 for (int i = 0; long_opts[i].name; i++) { 65 if (long_opts[i].val < 32 || long_opts[i].val > 127) 66 continue; 67 68 if (n + 4 >= sizeof(opts)) 69 fatal("optstring buffer overflow"); 70 71 opts[n++] = long_opts[i].val; 72 73 if (long_opts[i].has_arg == required_argument) 74 opts[n++] = ':'; 75 else if (long_opts[i].has_arg == optional_argument) { 76 opts[n++] = ':'; 77 opts[n++] = ':'; 78 } 79 } 80 81 opts[n] = '\0'; 82 83 return getopt_long(argc, argv, opts, long_opts, NULL); 84 } 85 86 /* 87 * common_parse_options - parse common command line options 88 * 89 * @argc: argument count 90 * @argv: argument vector 91 * @common: common parameters structure 92 * 93 * Parse command line options that are common to all rtla tools. 94 * 95 * Returns: non zero if a common option was parsed, or 0 96 * if the option should be handled by tool-specific parsing. 97 */ 98 int common_parse_options(int argc, char **argv, struct common_params *common) 99 { 100 struct trace_events *tevent; 101 int saved_state = optind; 102 int c; 103 104 static struct option long_options[] = { 105 {"cpus", required_argument, 0, 'c'}, 106 {"cgroup", optional_argument, 0, 'C'}, 107 {"debug", no_argument, 0, 'D'}, 108 {"duration", required_argument, 0, 'd'}, 109 {"event", required_argument, 0, 'e'}, 110 {"house-keeping", required_argument, 0, 'H'}, 111 {"priority", required_argument, 0, 'P'}, 112 {0, 0, 0, 0} 113 }; 114 115 opterr = 0; 116 c = getopt_auto(argc, argv, long_options); 117 opterr = 1; 118 119 switch (c) { 120 case 'c': 121 if (parse_cpu_set(optarg, &common->monitored_cpus)) 122 fatal("Invalid -c cpu list"); 123 common->cpus = optarg; 124 break; 125 case 'C': 126 common->cgroup = 1; 127 common->cgroup_name = parse_optional_arg(argc, argv); 128 break; 129 case 'D': 130 config_debug = 1; 131 break; 132 case 'd': 133 common->duration = parse_seconds_duration(optarg); 134 if (!common->duration) 135 fatal("Invalid -d duration"); 136 break; 137 case 'e': 138 tevent = trace_event_alloc(optarg); 139 if (!tevent) 140 fatal("Error alloc trace event"); 141 142 if (common->events) 143 tevent->next = common->events; 144 common->events = tevent; 145 break; 146 case 'H': 147 common->hk_cpus = 1; 148 if (parse_cpu_set(optarg, &common->hk_cpu_set)) 149 fatal("Error parsing house keeping CPUs"); 150 break; 151 case 'P': 152 if (parse_prio(optarg, &common->sched_param) == -1) 153 fatal("Invalid -P priority"); 154 common->set_sched = 1; 155 break; 156 default: 157 optind = saved_state; 158 return 0; 159 } 160 161 return c; 162 } 163 164 /* 165 * common_apply_config - apply common configs to the initialized tool 166 */ 167 int 168 common_apply_config(struct osnoise_tool *tool, struct common_params *params) 169 { 170 int retval, i; 171 172 if (!params->sleep_time) 173 params->sleep_time = 1; 174 175 retval = osnoise_set_cpus(tool->context, params->cpus ? params->cpus : "all"); 176 if (retval) { 177 err_msg("Failed to apply CPUs config\n"); 178 goto out_err; 179 } 180 181 if (!params->cpus) { 182 for (i = 0; i < nr_cpus; i++) 183 CPU_SET(i, ¶ms->monitored_cpus); 184 } 185 186 if (params->hk_cpus) { 187 retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set), 188 ¶ms->hk_cpu_set); 189 if (retval == -1) { 190 err_msg("Failed to set rtla to the house keeping CPUs\n"); 191 goto out_err; 192 } 193 } else if (params->cpus) { 194 /* 195 * Even if the user do not set a house-keeping CPU, try to 196 * move rtla to a CPU set different to the one where the user 197 * set the workload to run. 198 * 199 * No need to check results as this is an automatic attempt. 200 */ 201 auto_house_keeping(¶ms->monitored_cpus); 202 } 203 204 /* 205 * Set workload according to type of thread if the kernel supports it. 206 * On kernels without support, user threads will have already failed 207 * on missing fd, and kernel threads do not need it. 208 */ 209 retval = osnoise_set_workload(tool->context, params->kernel_workload); 210 if (retval < -1) { 211 err_msg("Failed to set OSNOISE_WORKLOAD option\n"); 212 goto out_err; 213 } 214 215 return 0; 216 217 out_err: 218 return -1; 219 } 220 221 222 /** 223 * common_threshold_handler - handle latency threshold overflow 224 * @tool: pointer to the osnoise_tool instance containing trace contexts 225 * 226 * Executes the configured threshold actions (e.g., saving trace, printing, 227 * sending signals). If the continue flag is set (--on-threshold continue), 228 * restarts the auxiliary trace instances to continue monitoring. 229 * 230 * Return: 0 for success, -1 for error. 231 */ 232 int 233 common_threshold_handler(const struct osnoise_tool *tool) 234 { 235 actions_perform(&tool->params->threshold_actions); 236 237 if (!should_continue_tracing(tool->params)) 238 /* continue flag not set, break */ 239 return 0; 240 241 /* continue action reached, re-enable tracing */ 242 if (tool->record && trace_instance_start(&tool->record->trace)) 243 goto err; 244 if (tool->aa && trace_instance_start(&tool->aa->trace)) 245 goto err; 246 247 return 0; 248 249 err: 250 err_msg("Error restarting trace\n"); 251 return -1; 252 } 253 254 int run_tool(struct tool_ops *ops, int argc, char *argv[]) 255 { 256 struct common_params *params; 257 enum result return_value = ERROR; 258 struct osnoise_tool *tool; 259 bool stopped; 260 int retval; 261 262 nr_cpus = get_nprocs_conf(); 263 params = ops->parse_args(argc, argv); 264 if (!params) 265 exit(1); 266 267 tool = ops->init_tool(params); 268 if (!tool) { 269 err_msg("Could not init osnoise tool\n"); 270 goto out_exit; 271 } 272 tool->ops = ops; 273 tool->params = params; 274 275 /* 276 * Save trace instance into global variable so that SIGINT can stop 277 * the timerlat tracer. 278 * Otherwise, rtla could loop indefinitely when overloaded. 279 */ 280 trace_inst = &tool->trace; 281 282 retval = ops->apply_config(tool); 283 if (retval) { 284 err_msg("Could not apply config\n"); 285 goto out_free; 286 } 287 288 retval = enable_tracer_by_name(trace_inst->inst, ops->tracer); 289 if (retval) { 290 err_msg("Failed to enable %s tracer\n", ops->tracer); 291 goto out_free; 292 } 293 294 if (params->set_sched) { 295 retval = set_comm_sched_attr(ops->comm_prefix, ¶ms->sched_param); 296 if (retval) { 297 err_msg("Failed to set sched parameters\n"); 298 goto out_free; 299 } 300 } 301 302 if (params->cgroup && !params->user_data) { 303 retval = set_comm_cgroup(ops->comm_prefix, params->cgroup_name); 304 if (!retval) { 305 err_msg("Failed to move threads to cgroup\n"); 306 goto out_free; 307 } 308 } 309 310 311 if (params->threshold_actions.present[ACTION_TRACE_OUTPUT] || 312 params->end_actions.present[ACTION_TRACE_OUTPUT]) { 313 tool->record = osnoise_init_trace_tool(ops->tracer); 314 if (!tool->record) { 315 err_msg("Failed to enable the trace instance\n"); 316 goto out_free; 317 } 318 params->threshold_actions.trace_output_inst = tool->record->trace.inst; 319 params->end_actions.trace_output_inst = tool->record->trace.inst; 320 321 if (params->events) { 322 retval = trace_events_enable(&tool->record->trace, params->events); 323 if (retval) 324 goto out_trace; 325 } 326 327 if (params->buffer_size > 0) { 328 retval = trace_set_buffer_size(&tool->record->trace, params->buffer_size); 329 if (retval) 330 goto out_trace; 331 } 332 } 333 334 if (params->user_workload) { 335 pthread_t user_thread; 336 337 /* rtla asked to stop */ 338 params->user.should_run = 1; 339 /* all threads left */ 340 params->user.stopped_running = 0; 341 342 params->user.set = ¶ms->monitored_cpus; 343 if (params->set_sched) 344 params->user.sched_param = ¶ms->sched_param; 345 else 346 params->user.sched_param = NULL; 347 348 params->user.cgroup_name = params->cgroup_name; 349 350 retval = pthread_create(&user_thread, NULL, timerlat_u_dispatcher, ¶ms->user); 351 if (retval) { 352 err_msg("Error creating timerlat user-space threads\n"); 353 goto out_trace; 354 } 355 } 356 357 retval = ops->enable(tool); 358 if (retval) 359 goto out_trace; 360 361 tool->start_time = time(NULL); 362 set_signals(params); 363 364 retval = ops->main(tool); 365 if (retval) 366 goto out_signals; 367 368 if (params->user_workload && !params->user.stopped_running) { 369 params->user.should_run = 0; 370 sleep(1); 371 } 372 373 ops->print_stats(tool); 374 375 actions_perform(¶ms->end_actions); 376 377 return_value = PASSED; 378 379 stopped = osnoise_trace_is_off(tool, tool->record) && !stop_tracing; 380 if (stopped) { 381 printf("%s hit stop tracing\n", ops->tracer); 382 return_value = FAILED; 383 } 384 385 if (ops->analyze) 386 ops->analyze(tool, stopped); 387 388 out_signals: 389 unset_signals(params); 390 out_trace: 391 trace_events_destroy(&tool->record->trace, params->events); 392 params->events = NULL; 393 out_free: 394 ops->free(tool); 395 osnoise_destroy_tool(tool->record); 396 osnoise_destroy_tool(tool); 397 actions_destroy(¶ms->threshold_actions); 398 actions_destroy(¶ms->end_actions); 399 free(params); 400 out_exit: 401 exit(return_value); 402 } 403 404 int top_main_loop(struct osnoise_tool *tool) 405 { 406 struct common_params *params = tool->params; 407 struct trace_instance *trace = &tool->trace; 408 struct osnoise_tool *record = tool->record; 409 int retval; 410 411 while (!stop_tracing) { 412 sleep(params->sleep_time); 413 414 if (params->aa_only && !osnoise_trace_is_off(tool, record)) 415 continue; 416 417 retval = tracefs_iterate_raw_events(trace->tep, 418 trace->inst, 419 NULL, 420 0, 421 collect_registered_events, 422 trace); 423 if (retval < 0) { 424 err_msg("Error iterating on events\n"); 425 return retval; 426 } 427 428 if (!params->quiet) 429 tool->ops->print_stats(tool); 430 431 if (osnoise_trace_is_off(tool, record)) { 432 if (stop_tracing) 433 /* stop tracing requested, do not perform actions */ 434 return 0; 435 436 retval = common_threshold_handler(tool); 437 if (retval) 438 return retval; 439 440 441 if (!should_continue_tracing(params)) 442 return 0; 443 444 trace_instance_start(trace); 445 } 446 447 /* is there still any user-threads ? */ 448 if (params->user_workload) { 449 if (params->user.stopped_running) { 450 debug_msg("timerlat user space threads stopped!\n"); 451 break; 452 } 453 } 454 } 455 456 return 0; 457 } 458 459 int hist_main_loop(struct osnoise_tool *tool) 460 { 461 struct common_params *params = tool->params; 462 struct trace_instance *trace = &tool->trace; 463 int retval = 0; 464 465 while (!stop_tracing) { 466 sleep(params->sleep_time); 467 468 retval = tracefs_iterate_raw_events(trace->tep, 469 trace->inst, 470 NULL, 471 0, 472 collect_registered_events, 473 trace); 474 if (retval < 0) { 475 err_msg("Error iterating on events\n"); 476 break; 477 } 478 479 if (osnoise_trace_is_off(tool, tool->record)) { 480 if (stop_tracing) 481 /* stop tracing requested, do not perform actions */ 482 break; 483 484 retval = common_threshold_handler(tool); 485 if (retval) 486 return retval; 487 488 if (!should_continue_tracing(params)) 489 return 0; 490 491 trace_instance_start(trace); 492 } 493 494 /* is there still any user-threads ? */ 495 if (params->user_workload) { 496 if (params->user.stopped_running) { 497 debug_msg("user-space threads stopped!\n"); 498 break; 499 } 500 } 501 } 502 503 return retval; 504 } 505 506 int osn_set_stop(struct osnoise_tool *tool) 507 { 508 struct common_params *params = tool->params; 509 int retval; 510 511 retval = osnoise_set_stop_us(tool->context, params->stop_us); 512 if (retval) { 513 err_msg("Failed to set stop us\n"); 514 return retval; 515 } 516 517 retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us); 518 if (retval) { 519 err_msg("Failed to set stop total us\n"); 520 return retval; 521 } 522 523 return 0; 524 } 525 526 static void print_msg_array(const char * const *msgs) 527 { 528 if (!msgs) 529 return; 530 531 for (int i = 0; msgs[i]; i++) 532 fprintf(stderr, "%s\n", msgs[i]); 533 } 534 535 /* 536 * common_usage - print complete usage information 537 */ 538 void common_usage(const char *tool, const char *mode, 539 const char *desc, const char * const *start_msgs, const char * const *opt_msgs) 540 { 541 static const char * const common_options[] = { 542 " -h/--help: print this menu", 543 NULL 544 }; 545 fprintf(stderr, "rtla %s", tool); 546 if (strcmp(mode, "")) 547 fprintf(stderr, " %s", mode); 548 fprintf(stderr, ": %s (version %s)\n\n", desc, VERSION); 549 fprintf(stderr, " usage: [rtla] %s ", tool); 550 551 if (strcmp(mode, "top") == 0) 552 fprintf(stderr, "[top] [-h] "); 553 else 554 fprintf(stderr, "%s [-h] ", mode); 555 556 print_msg_array(start_msgs); 557 fprintf(stderr, "\n"); 558 print_msg_array(common_options); 559 print_msg_array(opt_msgs); 560 561 exit(EXIT_SUCCESS); 562 } 563