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