Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/rtdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,8 @@ struct rt_thread
#ifdef RT_USING_CPU_USAGE_TRACER
rt_ubase_t user_time; /**< Ticks on user */
rt_ubase_t system_time; /**< Ticks on system */
rt_ubase_t total_time_prev; /**< Previous total ticks snapshot */
rt_uint8_t cpu_usage; /**< Recent CPU usage in percent */
#endif /* RT_USING_CPU_USAGE_TRACER */

#ifdef RT_USING_MEM_PROTECTION
Expand Down
11 changes: 11 additions & 0 deletions src/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ config RT_USING_CPU_USAGE_TRACER
percentage information through the list thread command.
It will automatically integrate with the scheduler to track thread execution time.

if RT_USING_CPU_USAGE_TRACER
config RT_CPU_USAGE_CALC_INTERVAL_MS
int "CPU usage sampling interval (ms)"
default 200
range 50 5000
Comment on lines +202 to +206
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Best practices/最佳实践]: PR title prefix formatting is slightly off

English: Project convention expects a lowercase prefix like [module][subsystem] Description (or [module] Description) with a space after the prefix. Current title is [kernel]Rewrite ...; suggested: [kernel] Rewrite rt_thread_get_usage to use incremental statistics based on sampling windows.
中文:项目约定的标题前缀格式通常为小写的 [模块][子系统] 描述(或 [模块] 描述),并且前缀后应有空格。当前标题为 [kernel]Rewrite ...;建议改为:[kernel] Rewrite rt_thread_get_usage to use incremental statistics based on sampling windows

Copilot uses AI. Check for mistakes.
help
Sampling window for thread CPU usage display.
A shorter interval updates faster but fluctuates more.
A longer interval is smoother but has higher display latency.
endif

menu "kservice options"
config RT_USING_TINY_FFS
bool "Enable kservice to use tiny finding first bit set method"
Expand Down
179 changes: 151 additions & 28 deletions src/kservice.c
Original file line number Diff line number Diff line change
Expand Up @@ -572,49 +572,172 @@ rt_err_t rt_backtrace_thread(rt_thread_t thread)
}

#ifdef RT_USING_CPU_USAGE_TRACER

#ifndef RT_CPU_USAGE_CALC_INTERVAL_MS
#define RT_CPU_USAGE_CALC_INTERVAL_MS 200U
#endif

#define RT_CPU_USAGE_CALC_INTERVAL_TICK \
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

license 头部加一下修改?

((RT_TICK_PER_SECOND * RT_CPU_USAGE_CALC_INTERVAL_MS + 999U) / 1000U)

static rt_tick_t _cpu_usage_sample_tick;
static rt_bool_t _cpu_usage_inited = RT_FALSE;
static struct rt_cpu_usage_stats _cpu_usage_prev_cpu_stat[RT_CPUS_NR];

Comment on lines +583 to +586
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Concurrency/并发]: _cpu_usage_update() updates shared static state without any synchronization

English: _cpu_usage_sample_tick, _cpu_usage_inited, and _cpu_usage_prev_cpu_stat[] are global statics mutated by _cpu_usage_update() but there is no lock/critical section guarding concurrent callers. If rt_thread_get_usage() is called from multiple threads/CPUs (e.g., shell, procfs, power code) at the same time, snapshots can be partially updated and produce incorrect usage, or tear the sampling window.
中文:_cpu_usage_sample_tick_cpu_usage_inited_cpu_usage_prev_cpu_stat[] 是全局静态变量,但 _cpu_usage_update() 在更新它们时没有任何锁/临界区保护。如果多个线程/CPU 同时调用 rt_thread_get_usage()(例如 shell/procfs/电源模块等),会出现采样快照被部分更新、采样窗口被撕裂,从而导致 CPU 使用率结果不正确。

English: Consider protecting the whole update path with a dedicated spinlock (or at least a short critical section) so only one caller performs snapshot/refresh at a time.
中文:建议为整个更新路径增加专用的自旋锁(或至少短临界区),确保同一时刻只有一个调用者执行 snapshot/refresh。

Copilot uses AI. Check for mistakes.
/*
* Calculate total CPU-time delta for this sampling window and
* refresh per-CPU snapshots.
*
* Each counter delta is computed in rt_ubase_t width first, so wrap-around
* on 32-bit targets is handled naturally by unsigned arithmetic.
*/
static rt_uint64_t _cpu_usage_calc_total_delta(void)
{
rt_uint64_t total_delta = 0;
int i;

for (i = 0; i < RT_CPUS_NR; i++)
{
rt_cpu_t pcpu = rt_cpu_index(i);
rt_ubase_t user_now = pcpu->cpu_stat.user;
rt_ubase_t system_now = pcpu->cpu_stat.system;
rt_ubase_t idle_now = pcpu->cpu_stat.idle;

/* Per-counter delta first to avoid overflow artifacts after sum. */
rt_ubase_t user_delta = (rt_ubase_t)(user_now - _cpu_usage_prev_cpu_stat[i].user);
rt_ubase_t system_delta = (rt_ubase_t)(system_now - _cpu_usage_prev_cpu_stat[i].system);
rt_ubase_t idle_delta = (rt_ubase_t)(idle_now - _cpu_usage_prev_cpu_stat[i].idle);

total_delta += (rt_uint64_t)user_delta;
total_delta += (rt_uint64_t)system_delta;
total_delta += (rt_uint64_t)idle_delta;

_cpu_usage_prev_cpu_stat[i].user = user_now;
_cpu_usage_prev_cpu_stat[i].system = system_now;
_cpu_usage_prev_cpu_stat[i].idle = idle_now;
}

return total_delta;
}

static void _cpu_usage_snapshot_init(void)
{
struct rt_object_information *info;
rt_list_t *list;
rt_list_t *node;
rt_base_t level;
int i;

info = rt_object_get_information(RT_Object_Class_Thread);
list = &info->object_list;

level = rt_spin_lock_irqsave(&info->spinlock);
for (node = list->next; node != list; node = node->next)
{
struct rt_object *obj = rt_list_entry(node, struct rt_object, list);
struct rt_thread *t = (struct rt_thread *)obj;

t->total_time_prev = 0U;
t->cpu_usage = 0U;
}
rt_spin_unlock_irqrestore(&info->spinlock, level);

for (i = 0; i < RT_CPUS_NR; i++)
{
_cpu_usage_prev_cpu_stat[i].user = 0U;
_cpu_usage_prev_cpu_stat[i].system = 0U;
_cpu_usage_prev_cpu_stat[i].idle = 0U;
}

_cpu_usage_sample_tick = rt_tick_get();
_cpu_usage_inited = RT_TRUE;
}
Comment on lines +640 to +654
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug/缺陷]: First sampling window is initialized to 0, so the first computed “window” is actually since-boot (and doc says initial is 0)

English: _cpu_usage_snapshot_init() sets all per-CPU previous counters and each thread’s total_time_prev to 0, then _cpu_usage_update() immediately computes deltas against 0 on the first call (because bypass_interval_check is true). This makes the first returned usage reflect cumulative time since boot/thread start, not the “recent sampling window”, and it contradicts the doc note that the initial cached value is 0.
中文:_cpu_usage_snapshot_init() 将每个 CPU 的 prev 计数器以及每个线程的 total_time_prev 都置为 0,然后 _cpu_usage_update() 在第一次调用时(bypass_interval_check 为真)会立刻用 0 作为基准计算 delta。这样第一次返回的 usage 实际是“自启动/自线程创建以来”的累计占比,而不是“最近采样窗口”,同时也与注释中“初始值为 0”的描述不一致。

English: A more consistent approach is to initialize snapshots to the current counters (per-CPU and per-thread) and return without computing usage until the next interval elapses.
中文:更一致的做法是在初始化时将快照设置为“当前值”(CPU/线程),并在首次初始化后不计算 usage,等待下一个采样窗口到期再更新。

Copilot uses AI. Check for mistakes.

static void _cpu_usage_refresh_threads(rt_uint64_t total_delta)
{
struct rt_object_information *info;
rt_list_t *list;
rt_list_t *node;
rt_base_t level;

info = rt_object_get_information(RT_Object_Class_Thread);
list = &info->object_list;

level = rt_spin_lock_irqsave(&info->spinlock);
for (node = list->next; node != list; node = node->next)
{
struct rt_object *obj = rt_list_entry(node, struct rt_object, list);
struct rt_thread *t = (struct rt_thread *)obj;
rt_ubase_t total_now = (rt_ubase_t)(t->user_time + t->system_time);
rt_ubase_t total_delta_now = (rt_ubase_t)(total_now - t->total_time_prev);
rt_uint64_t thread_delta = (rt_uint64_t)total_delta_now;

if (total_delta > 0U)
{
rt_uint64_t usage = (thread_delta * 100U) / total_delta;
t->cpu_usage = (rt_uint8_t)(usage > 100U ? 100U : usage);
}
else
{
t->cpu_usage = 0U;
}

t->total_time_prev = total_now;
}
rt_spin_unlock_irqrestore(&info->spinlock, level);
Comment on lines +666 to +687
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Performance/实时性能]: Holding thread object spinlock (IRQ-save) while doing per-thread 64-bit division over the whole thread list

English: _cpu_usage_refresh_threads() holds info->spinlock with rt_spin_lock_irqsave() while iterating every thread and performing 64-bit division for each entry. On 32-bit MCUs this division is relatively expensive, and keeping IRQs disabled for O(nthreads) work can hurt real-time latency.
中文:_cpu_usage_refresh_threads()rt_spin_lock_irqsave() 持有 info->spinlock 的同时遍历所有线程,并且对每个线程执行 64 位除法。对 32 位 MCU 来说除法开销较大,在中断关闭状态下做 O(线程数) 的工作会明显影响实时性/中断响应。

English: Consider shortening the critical section (e.g., collect thread pointers/snapshots in batches under the lock like list_thread() does, then do math outside the lock and only briefly lock to write back fields).
中文:建议缩短临界区(例如参考 list_thread() 的做法,在锁内分批收集线程指针/快照,锁外完成计算,只在必要时短暂加锁回写字段)。

Copilot uses AI. Check for mistakes.
}

static void _cpu_usage_update(void)
{
rt_tick_t tick_now;
rt_tick_t delta_tick;
rt_uint64_t total_delta;
rt_bool_t bypass_interval_check = RT_FALSE;

if (!_cpu_usage_inited)
{
_cpu_usage_snapshot_init();
bypass_interval_check = RT_TRUE;
}

tick_now = rt_tick_get();
delta_tick = rt_tick_get_delta(_cpu_usage_sample_tick);
if (!bypass_interval_check && delta_tick < RT_CPU_USAGE_CALC_INTERVAL_TICK)
{
return;
}

total_delta = _cpu_usage_calc_total_delta();
_cpu_usage_refresh_threads(total_delta);
_cpu_usage_sample_tick = tick_now;
}

/**
* @brief Get thread usage percentage relative to total system CPU time
* @brief Get thread CPU usage percentage in the recent sampling window
*
* This function calculates the CPU usage percentage of a specific thread
* relative to the total CPU time consumed by all threads in the system.
* This function returns per-thread CPU usage based on delta runtime in the
* latest sampling window, rather than cumulative runtime since boot.
*
* @param thread Pointer to the thread object. Must not be NULL.
*
* @return The CPU usage percentage as an integer value (0-100).
* Returns 0 if total system time is 0 or if CPU usage tracing is not enabled.
* If sampling interval has not elapsed yet, the previous cached value
* is returned (initial value is 0).
*
* @note This function requires RT_USING_CPU_USAGE_TRACER to be enabled.
* @note The percentage is calculated as: (thread_time * 100) / total_system_time
* @note Due to integer arithmetic, the result is truncated and may not sum
* to exactly 100% across all threads due to rounding.
* @note The percentage is calculated as
* (thread_time_delta * 100) / total_time_delta,
* where total_time_delta is the sum of user/system/idle deltas of all CPUs.
* @note Sampling interval can be tuned with RT_CPU_USAGE_CALC_INTERVAL_MS.
* @note If thread is NULL, an assertion will be triggered in debug builds.
*/
rt_uint8_t rt_thread_get_usage(rt_thread_t thread)
{
rt_ubase_t thread_time;
rt_ubase_t total_time = 0U;
int i;
rt_cpu_t pcpu;

RT_ASSERT(thread != RT_NULL);

thread_time = thread->user_time + thread->system_time;

/* Calculate total system time by summing all CPUs' time */
for (i = 0; i < RT_CPUS_NR; i++)
{
pcpu = rt_cpu_index(i);
total_time += pcpu->cpu_stat.user + pcpu->cpu_stat.system + pcpu->cpu_stat.idle;
}

if (total_time > 0U)
{
/* Calculate thread usage percentage: (thread_time * 100) / total_time */
rt_ubase_t usage = (thread_time * 100U) / total_time;
return (rt_uint8_t)(usage > 100U ? 100U : usage);
}
_cpu_usage_update();

return 0U;
return thread->cpu_usage;
}
#endif /* RT_USING_CPU_USAGE_TRACER */

Expand Down
7 changes: 7 additions & 0 deletions src/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ static rt_err_t _thread_init(struct rt_thread *thread,
thread->system_time = 0;
#endif

#ifdef RT_USING_CPU_USAGE_TRACER
thread->user_time = 0;
thread->system_time = 0;
Comment on lines +280 to +282
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Maintainability/可维护性]: Duplicate initialization of user_time/system_time when RT_USING_SMART is enabled

English: thread->user_time and thread->system_time are already initialized earlier inside the RT_USING_SMART block in this function. Initializing them again under RT_USING_CPU_USAGE_TRACER is redundant for configurations where both are enabled, and increases the chance of future divergence.
中文:在该函数中,thread->user_timethread->system_time 已在前面的 RT_USING_SMART 代码块里初始化过;当同时启用 RT_USING_SMARTRT_USING_CPU_USAGE_TRACER 时,这里再次初始化属于冗余,后续维护时也更容易出现两处逻辑不一致。

English: Consider consolidating these initializations so each field is set in only one place for a given build configuration.
中文:建议合并/统一这些初始化逻辑,确保同一构建配置下每个字段只在一个位置赋值。

Suggested change
#ifdef RT_USING_CPU_USAGE_TRACER
thread->user_time = 0;
thread->system_time = 0;
#ifdef RT_USING_CPU_USAGE_TRACER
#ifndef RT_USING_SMART
thread->user_time = 0;
thread->system_time = 0;
#endif /* !RT_USING_SMART */

Copilot uses AI. Check for mistakes.
thread->total_time_prev = 0;
thread->cpu_usage = 0;
#endif /* RT_USING_CPU_USAGE_TRACER */

#ifdef RT_USING_PTHREADS
thread->pthread_data = RT_NULL;
#endif /* RT_USING_PTHREADS */
Expand Down
Loading