Skip to content

Commit c7d7aac

Browse files
nordic-krchfabiobaltieri
authored andcommitted
debug: Add cpu_load module
Add module which can measure CPU idle time. Idle time is measured in the pre/post CPU idle hooks but they are not provided by this module. Signed-off-by: Krzysztof Chruściński <krzysztof.chruscinski@nordicsemi.no>
1 parent c4ebf71 commit c7d7aac

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

include/zephyr/debug/cpu_load.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2024, Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_DEBUG_CPU_LOAD_H_
8+
#define ZEPHYR_INCLUDE_DEBUG_CPU_LOAD_H_
9+
10+
#include <stdbool.h>
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
/** @defgroup cpu_load CPU load monitor
17+
* @ingroup os_services
18+
* @brief Module for monitoring CPU Load
19+
*
20+
* This module allow monitoring of the CPU load.
21+
* @{
22+
*/
23+
24+
/** @brief Hook called by the application specific hook on entering CPU idle. */
25+
void cpu_load_on_enter_idle(void);
26+
27+
/** @brief Hook called by the application specific hook on exiting CPU idle. */
28+
void cpu_load_on_exit_idle(void);
29+
30+
/** @brief Get CPU load.
31+
*
32+
* CPU load is measured using a timer which tracks amount of time spent in the
33+
* CPU idle. Since it is a software tracking there is some small overhead.
34+
* Precision depends on the frequency of the timer in relation to the CPU frequency.
35+
*
36+
* @param reset Reset the measurement after reading.
37+
*
38+
* @retval Positive number - CPU load in per mille.
39+
* @retval Negative number - error code.
40+
*/
41+
int cpu_load_get(bool reset);
42+
43+
/** @brief Control periodic CPU statistics report.
44+
*
45+
* Report logging is by default enabled.
46+
*
47+
* @param enable true to enable report logging and false to disable.
48+
*/
49+
void cpu_load_log_control(bool enable);
50+
51+
/**
52+
* @}
53+
*/
54+
55+
#ifdef __cplusplus
56+
}
57+
#endif
58+
59+
60+
#endif /* ZEPHYR_INCLUDE_DEBUG_CPU_LOAD_H_ */

subsys/debug/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ zephyr_sources_ifdef(
3939
CONFIG_CS_TRACE_DEFMT
4040
coresight/cs_trace_defmt.c
4141
)
42+
43+
zephyr_sources_ifdef(
44+
CONFIG_CPU_LOAD
45+
cpu_load.c
46+
)

subsys/debug/Kconfig

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,27 @@ config CS_TRACE_DEFMT
431431
help
432432
Module is decoding data which is formatted using Coresight
433433
Trace Formatter, e.g. when data is put into ETR (Embedded Trace Router).
434+
435+
config CPU_LOAD
436+
select TRACING
437+
# Currently hooks are added only to Cortex M architecture.
438+
depends on CPU_CORTEX_M
439+
bool "CPU load measurement"
440+
441+
# Workaround for not being able to have commas in macro arguments
442+
DT_CHOSEN_Z_CPU_LOAD_COUNTER := zephyr,cpu-load-counter
443+
444+
config CPU_LOAD_USE_COUNTER
445+
bool "Use counter"
446+
depends on $(dt_chosen_enabled,DT_CHOSEN_Z_CPU_LOAD_COUNTER)
447+
default y
448+
select COUNTER
449+
help
450+
Use counter for tracking CPU idle time.
451+
452+
config CPU_LOAD_LOG_PERIODICALLY
453+
int "Report period (in milliseconds)"
454+
depends on LOG
455+
default 0
456+
help
457+
Specifies how often CPU load shall be logged. 0 means that there is no logging.

subsys/debug/cpu_load.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright (c) 2024, Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/debug/cpu_load.h>
8+
#include <zephyr/kernel.h>
9+
#include <zephyr/drivers/counter.h>
10+
#include <zephyr/logging/log.h>
11+
LOG_MODULE_REGISTER(cpu_load);
12+
13+
BUILD_ASSERT(!IS_ENABLED(CONFIG_CPU_LOAD_USE_COUNTER) || DT_HAS_CHOSEN(zephyr_cpu_load_counter));
14+
15+
#ifndef CONFIG_CPU_LOAD_LOG_PERIODICALLY
16+
#define CONFIG_CPU_LOAD_LOG_PERIODICALLY 0
17+
#endif
18+
19+
const struct device *counter = COND_CODE_1(CONFIG_CPU_LOAD_USE_COUNTER,
20+
(DEVICE_DT_GET(DT_CHOSEN(zephyr_cpu_load_counter))), (NULL));
21+
static uint32_t enter_ts;
22+
static uint32_t cyc_start;
23+
static uint32_t ticks_idle;
24+
25+
static struct k_work_delayable cpu_load_log;
26+
27+
void cpu_load_log_control(bool enable)
28+
{
29+
if (CONFIG_CPU_LOAD_LOG_PERIODICALLY == 0) {
30+
return;
31+
}
32+
33+
if (enable) {
34+
(void)cpu_load_get(true);
35+
k_work_schedule(&cpu_load_log, K_MSEC(CONFIG_CPU_LOAD_LOG_PERIODICALLY));
36+
} else {
37+
k_work_cancel_delayable(&cpu_load_log);
38+
}
39+
}
40+
41+
#if CONFIG_CPU_LOAD_USE_COUNTER || CONFIG_CPU_LOAD_LOG_PERIODICALLY
42+
static void cpu_load_log_fn(struct k_work *work)
43+
{
44+
int load = cpu_load_get(true);
45+
uint32_t percent = load / 10;
46+
uint32_t fraction = load % 10;
47+
48+
LOG_INF("Load:%d.%03d%%", percent, fraction);
49+
cpu_load_log_control(true);
50+
}
51+
52+
static int cpu_load_init(void)
53+
{
54+
if (IS_ENABLED(CONFIG_CPU_LOAD_USE_COUNTER)) {
55+
int err = counter_start(counter);
56+
57+
(void)err;
58+
__ASSERT_NO_MSG(err == 0);
59+
}
60+
61+
if (CONFIG_CPU_LOAD_LOG_PERIODICALLY > 0) {
62+
k_work_init_delayable(&cpu_load_log, cpu_load_log_fn);
63+
return k_work_schedule(&cpu_load_log, K_MSEC(CONFIG_CPU_LOAD_LOG_PERIODICALLY));
64+
}
65+
66+
return 0;
67+
}
68+
69+
SYS_INIT(cpu_load_init, POST_KERNEL, 0);
70+
#endif
71+
72+
void cpu_load_on_enter_idle(void)
73+
{
74+
if (IS_ENABLED(CONFIG_CPU_LOAD_USE_COUNTER)) {
75+
counter_get_value(counter, &enter_ts);
76+
return;
77+
}
78+
79+
enter_ts = k_cycle_get_32();
80+
}
81+
82+
void cpu_load_on_exit_idle(void)
83+
{
84+
uint32_t now;
85+
86+
if (IS_ENABLED(CONFIG_CPU_LOAD_USE_COUNTER)) {
87+
counter_get_value(counter, &now);
88+
} else {
89+
now = k_cycle_get_32();
90+
}
91+
92+
ticks_idle += now - enter_ts;
93+
}
94+
95+
int cpu_load_get(bool reset)
96+
{
97+
uint32_t idle_us;
98+
uint32_t total = k_cycle_get_32() - cyc_start;
99+
uint32_t total_us = (uint32_t)k_cyc_to_us_floor32(total);
100+
uint32_t res;
101+
uint32_t active_us;
102+
103+
if (IS_ENABLED(CONFIG_CPU_LOAD_USE_COUNTER)) {
104+
idle_us = counter_ticks_to_us(counter, ticks_idle);
105+
} else {
106+
idle_us = k_cyc_to_us_floor32(ticks_idle);
107+
}
108+
109+
idle_us = MIN(idle_us, total_us);
110+
active_us = total_us - idle_us;
111+
112+
res = ((1000 * active_us) / total_us);
113+
114+
if (reset) {
115+
cyc_start = k_cycle_get_32();
116+
ticks_idle = 0;
117+
}
118+
119+
return res;
120+
}

0 commit comments

Comments
 (0)