diff --git a/psh/Makefile b/psh/Makefile index c826542e..0522d53e 100644 --- a/psh/Makefile +++ b/psh/Makefile @@ -26,12 +26,13 @@ PSH_COMMANDS ?= $(PSH_ALLCOMMANDS) PSH_INTERNAL_APPLETS := pshapp help $(filter $(PSH_ALLCOMMANDS), $(PSH_COMMANDS)) SRCS := $(foreach app, $(PSH_INTERNAL_APPLETS), $(wildcard $(LOCAL_PATH)$(app)/*.c)) +LIBS := libtrace # Header for project-specific applets HEADERS:= $(LOCAL_PATH)/psh.h # Project-specific applets .a files and their dependencies -LIBS := $(PSH_PROJECT_DEPS) +LIBS += $(PSH_PROJECT_DEPS) # Applets to create links: PSH_LINK_APPLETS := $(PSH_COMMANDS) pshlogin diff --git a/psh/perf/perf.c b/psh/perf/perf.c index 583f7c0f..3878f1f3 100644 --- a/psh/perf/perf.c +++ b/psh/perf/perf.c @@ -3,8 +3,8 @@ * * perf - track kernel performance events * - * Copyright 2017, 2018, 2020, 2021 Phoenix Systems - * Author: Pawel Pisarczyk, Jan Sikorski, Maciej Purski, Mateusz Niewiadomski + * Copyright 2017, 2018, 2020, 2021, 2025 Phoenix Systems + * Author: Pawel Pisarczyk, Jan Sikorski, Maciej Purski, Mateusz Niewiadomski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -13,105 +13,215 @@ #include #include +#include +#include #include +#include #include +#include +#include + +#include #include "../psh.h" +#include + + +#ifndef PSH_PERF_BUFSZ +#define PSH_PERF_BUFSZ (512 << 10) +#endif + +#ifndef PERF_RTT_ENABLED +#define PERF_RTT_ENABLED 0 +#endif + +#define PSH_PERF_DEFAULT_TIMEOUT_MS (3 * 1000) +#define PSH_PERF_DEFAULT_SLEEPTIME_MS (100) +#define PSH_PERF_BUFSZ_EXP (18) + +#define LOG_TAG "perf: " + +/* clang-format off */ +#define log_info(fmt, ...) do { fprintf(stderr, LOG_TAG fmt "\n", ##__VA_ARGS__); } while (0) +#define log_warning(fmt, ...) do { log_info("warning: " fmt, ##__VA_ARGS__); } while (0) +#define log_error(fmt, ...) do { log_info("error: " fmt, ##__VA_ARGS__); } while (0) +/* clang-format on */ -void psh_perfinfo(void) + +static char *modeStrs[] = { + [perf_mode_trace] = "trace", +}; + +_Static_assert(sizeof(modeStrs) / sizeof(modeStrs[0]) == perf_mode_count, "modeStrs must handle all perf modes"); + + +static void psh_perfinfo(void) { printf("track kernel performance events"); return; } -int psh_perf(int argc, char **argv) +static void perfHelp(void) { - time_t timeout = 0, elapsed = 0, sleeptime = 200 * 1000; - threadinfo_t *info, *rinfo; - const size_t bufsz = 4 << 20; - int bcount, tcnt, n = 32; - perf_event_t *buffer; - char *end; - - if (argc > 1) { - timeout = strtoul(argv[1], &end, 10); - - if (*end != '\0' || !timeout) { - fprintf(stderr, "perf: timeout argument must be integer greater than 0\n"); - return -EINVAL; - } - timeout *= 1000 * 1000; - } + printf("Usage: perf -m MODE" +#if !PERF_RTT_ENABLED + " -o [stream output dir]" +#endif + " [options]\n" + "Modes:\n" + " trace - kernel tracing\n" + "Options:\n" + " -t [timeout] (default: %d ms)\n" + " -b [bufsize exp] (default: %d -> (2 << 18) B)\n" + " -s [sleeptime] (default: %d ms)]\n" + " -j [start | stop] - just start/stop perf and exit\n" + " -p [prio]\n", + PSH_PERF_DEFAULT_TIMEOUT_MS, + PSH_PERF_BUFSZ_EXP, + PSH_PERF_DEFAULT_SLEEPTIME_MS); +} - if ((info = malloc(n * sizeof(threadinfo_t))) == NULL) { - fprintf(stderr, "perf: out of memory\n"); - return -ENOMEM; - } - while ((tcnt = threadsinfo(n, info)) >= n) { - n *= 2; - if ((rinfo = realloc(info, n * sizeof(threadinfo_t))) == NULL) { - fprintf(stderr, "perf: out of memory\n"); - free(info); - return -ENOMEM; - } - info = rinfo; - } +static int psh_perf(int argc, char **argv) +{ + char *end, *modeStr = NULL, *destDir = NULL; - if (fwrite(&tcnt, sizeof(tcnt), 1, stdout) != 1) { - fprintf(stderr, "perf: failed or partial write\n"); - free(info); - return -EIO; - } + time_t timeoutMs = PSH_PERF_DEFAULT_TIMEOUT_MS; + time_t sleeptimeMs = PSH_PERF_DEFAULT_SLEEPTIME_MS; - if (fwrite(info, sizeof(threadinfo_t), tcnt, stdout) != tcnt) { - fprintf(stderr, "perf: failed or partial write\n"); - free(info); - return -EIO; - } + perf_mode_t mode = perf_mode_trace; - free(info); + size_t bufSize = 2 << PSH_PERF_BUFSZ_EXP; - if (argc == 1) - return EOK; + /* clang-format off */ + enum { perf_none, perf_just_start, perf_just_stop } restrictTo = perf_none; + /* clang-format on */ - if ((buffer = malloc(bufsz)) == NULL) { - fprintf(stderr, "perf: out of memory\n"); - return -ENOMEM; + int opt; + for (;;) { + opt = getopt(argc, argv, "o:m:t:b:s:p:j:h"); + if (opt == -1) { + break; + } + switch (opt) { + case 'o': + destDir = optarg; + break; + case 'm': + modeStr = optarg; + mode = -1; + for (size_t i = 0; i < sizeof(modeStrs) / sizeof(modeStrs[0]); i++) { + if (strcmp(modeStr, modeStrs[i]) == 0) { + mode = i; + break; + } + } + if (mode == -1) { + log_error("invalid mode: %s", modeStr); + return -EINVAL; + } + break; + case 't': + timeoutMs = strtoul(optarg, &end, 10); + if (*end != '\0' || timeoutMs == 0) { + log_error("timeout argument must be integer greater than 0"); + return -EINVAL; + } + break; + case 'b': { + size_t exp = strtoul(optarg, &end, 10); + if (*end != '\0' || exp == 0) { + log_error("bufsize argument must be integer greater than 0"); + return -EINVAL; + } + bufSize = 2 << exp; + } break; + case 's': + sleeptimeMs = strtoul(optarg, &end, 10); + if (*end != '\0' || sleeptimeMs == 0) { + log_error("sleeptime argument must be integer greater than 0"); + return -EINVAL; + } + break; + case 'p': { + int prio = strtoul(optarg, &end, 10); + if (*end != '\0') { + log_error("bad prio: %s", optarg); + return -EINVAL; + } + priority(prio); + } break; + case 'j': + if (strcmp(optarg, "start") == 0) { + restrictTo = perf_just_start; + } + else if (strcmp(optarg, "stop") == 0) { + restrictTo = perf_just_stop; + } + else { + log_error("bad arg: -n %s", optarg); + return -EINVAL; + } + break; + default: + case 'h': + perfHelp(); + return EOK; + } } - if (perf_start(-1) < 0) { - fprintf(stderr, "perf: could not start\n"); - free(buffer); - return -1; + if (modeStr == NULL || (PERF_RTT_ENABLED == 0 && destDir == NULL)) { + perfHelp(); + return -EINVAL; } - while (elapsed < timeout) { - bcount = perf_read(buffer, bufsz); - - if (fwrite(buffer, 1, bcount, stdout) < bcount) { - fprintf(stderr, "perf: failed or partial write\n"); + switch (mode) { + case perf_mode_trace: { + trace_ctx_t ctx; + + int res = trace_init(&ctx, false); + if (res < 0) { + log_error("trace_init failed: %d", res); + return -EIO; + } + + if (restrictTo == perf_just_start) { + res = trace_start(&ctx); + if (res < 0) { + log_error("trace_start failed: %d", res); + return -EIO; + } + } + else if (restrictTo == perf_just_stop) { + res = trace_stopAndGather(&ctx, bufSize, destDir); + if (res < 0) { + log_error("trace_stopAndGather failed: %d", res); + return -EIO; + } + } + else { + res = trace_record(&ctx, sleeptimeMs, timeoutMs, bufSize, destDir); + if (res < 0) { + log_error("trace_record failed: %d", res); + return -EIO; + } + } break; } - - fprintf(stderr, "perf: wrote %d/%zd bytes\n", bcount, bufsz); - - usleep(sleeptime); - elapsed += sleeptime; + default: + log_error("unsupported mode"); + break; } - perf_finish(); - free(buffer); - return EOK; } void __attribute__((constructor)) perf_registerapp(void) { - static psh_appentry_t app = {.name = "perf", .run = psh_perf, .info = psh_perfinfo}; + static psh_appentry_t app = { .name = "perf", .run = psh_perf, .info = psh_perfinfo }; psh_registerapp(&app); }