1 /*
2 * Copyright (c) 2023 KNS Group LLC (YADRO)
3 * Copyright (c) 2020 Yonatan Goldschmidt <yon.goldschmidt@gmail.com>
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/kernel.h>
9 #include <zephyr/init.h>
10 #include <zephyr/arch/cpu.h>
11 #include <zephyr/shell/shell.h>
12 #include <zephyr/shell/shell_uart.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15
16 size_t arch_perf_current_stack_trace(uintptr_t *buf, size_t size);
17
18 struct perf_data_t {
19 struct k_timer timer;
20
21 const struct shell *sh;
22
23 struct k_work_delayable dwork;
24
25 size_t idx;
26 uintptr_t buf[CONFIG_PROFILING_PERF_BUFFER_SIZE];
27 bool buf_full;
28 };
29
30 static void perf_tracer(struct k_timer *timer);
31 static void perf_dwork_handler(struct k_work *work);
32 static struct perf_data_t perf_data = {
33 .timer = Z_TIMER_INITIALIZER(perf_data.timer, perf_tracer, NULL),
34 .dwork = Z_WORK_DELAYABLE_INITIALIZER(perf_dwork_handler),
35 };
36
perf_tracer(struct k_timer * timer)37 static void perf_tracer(struct k_timer *timer)
38 {
39 struct perf_data_t *perf_data_ptr =
40 (struct perf_data_t *)k_timer_user_data_get(timer);
41
42 size_t trace_length = 0;
43
44 if (++perf_data_ptr->idx < CONFIG_PROFILING_PERF_BUFFER_SIZE) {
45 trace_length = arch_perf_current_stack_trace(
46 perf_data_ptr->buf + perf_data_ptr->idx,
47 CONFIG_PROFILING_PERF_BUFFER_SIZE - perf_data_ptr->idx);
48 }
49
50 if (trace_length != 0) {
51 perf_data_ptr->buf[perf_data_ptr->idx - 1] = trace_length;
52 perf_data_ptr->idx += trace_length;
53 } else {
54 --perf_data_ptr->idx;
55 perf_data_ptr->buf_full = true;
56 k_work_reschedule(&perf_data_ptr->dwork, K_NO_WAIT);
57 }
58 }
59
perf_dwork_handler(struct k_work * work)60 static void perf_dwork_handler(struct k_work *work)
61 {
62 struct k_work_delayable *dwork = k_work_delayable_from_work(work);
63 struct perf_data_t *perf_data_ptr = CONTAINER_OF(dwork, struct perf_data_t, dwork);
64
65 k_timer_stop(&perf_data_ptr->timer);
66 if (perf_data_ptr->buf_full) {
67 shell_error(perf_data_ptr->sh, "Perf buf overflow!");
68 } else {
69 shell_print(perf_data_ptr->sh, "Perf done!");
70 }
71 }
72
cmd_perf_record(const struct shell * sh,size_t argc,char ** argv)73 static int cmd_perf_record(const struct shell *sh, size_t argc, char **argv)
74 {
75 if (k_work_delayable_is_pending(&perf_data.dwork)) {
76 shell_warn(sh, "Perf is running");
77 return -EINPROGRESS;
78 }
79
80 if (perf_data.buf_full) {
81 shell_warn(sh, "Perf buffer is full");
82 return -ENOBUFS;
83 }
84
85 k_timeout_t duration = K_MSEC(strtoll(argv[1], NULL, 10));
86 k_timeout_t period = K_NSEC(1000000000 / strtoll(argv[2], NULL, 10));
87
88 perf_data.sh = sh;
89
90 k_timer_user_data_set(&perf_data.timer, &perf_data);
91 k_timer_start(&perf_data.timer, K_NO_WAIT, period);
92
93 k_work_schedule(&perf_data.dwork, duration);
94
95 shell_print(sh, "Enabled perf");
96
97 return 0;
98 }
99
cmd_perf_clear(const struct shell * sh,size_t argc,char ** argv)100 static int cmd_perf_clear(const struct shell *sh, size_t argc, char **argv)
101 {
102 if (sh != NULL) {
103 if (k_work_delayable_is_pending(&perf_data.dwork)) {
104 shell_warn(sh, "Perf is running");
105 return -EINPROGRESS;
106 }
107 shell_print(sh, "Perf buffer cleared");
108 }
109
110 perf_data.idx = 0;
111 perf_data.buf_full = false;
112
113 return 0;
114 }
115
cmd_perf_info(const struct shell * sh,size_t argc,char ** argv)116 static int cmd_perf_info(const struct shell *sh, size_t argc, char **argv)
117 {
118 if (k_work_delayable_is_pending(&perf_data.dwork)) {
119 shell_print(sh, "Perf is running");
120 }
121
122 shell_print(sh, "Perf buf: %zu/%d %s", perf_data.idx, CONFIG_PROFILING_PERF_BUFFER_SIZE,
123 perf_data.buf_full ? "(full)" : "");
124
125 return 0;
126 }
127
cmd_perf_print(const struct shell * sh,size_t argc,char ** argv)128 static int cmd_perf_print(const struct shell *sh, size_t argc, char **argv)
129 {
130 if (k_work_delayable_is_pending(&perf_data.dwork)) {
131 shell_warn(sh, "Perf is running");
132 return -EINPROGRESS;
133 }
134
135 shell_print(sh, "Perf buf length %zu", perf_data.idx);
136 for (size_t i = 0; i < perf_data.idx; i++) {
137 shell_print(sh, "%016lx", perf_data.buf[i]);
138 }
139
140 cmd_perf_clear(NULL, 0, NULL);
141
142 return 0;
143 }
144
145 #define CMD_HELP_RECORD \
146 "Start recording for <duration> ms on <frequency> Hz\n" \
147 "Usage: record <duration> <frequency>"
148
149 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_perf,
150 SHELL_CMD_ARG(record, NULL, CMD_HELP_RECORD, cmd_perf_record, 3, 0),
151 SHELL_CMD_ARG(printbuf, NULL, "Print the perf buffer", cmd_perf_print, 0, 0),
152 SHELL_CMD_ARG(clear, NULL, "Clear the perf buffer", cmd_perf_clear, 0, 0),
153 SHELL_CMD_ARG(info, NULL, "Print the perf info", cmd_perf_info, 0, 0),
154 SHELL_SUBCMD_SET_END
155 );
156 SHELL_CMD_ARG_REGISTER(perf, &m_sub_perf, "Lightweight profiler", NULL, 0, 0);
157