/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup libmisc_cpuuse CPU Usage
*
* @brief CPU Usage Top
*/
/*
* COPYRIGHT (c) 2015, 2019. Chris Johns <chrisj@rtems.org>
*
* COPYRIGHT (c) 2014.
* On-Line Applications Research Corporation (OAR).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Based on the old capture engine ct-load.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <inttypes.h>
#include <rtems/cpuuse.h>
#include <rtems/printer.h>
#include <rtems/malloc.h>
#include <rtems/score/objectimpl.h>
#include <rtems/score/protectedheap.h>
#include <rtems/score/schedulerimpl.h>
#include <rtems/score/threadimpl.h>
#include <rtems/score/todimpl.h>
#include <rtems/score/watchdogimpl.h>
#include <rtems/score/wkspace.h>
#include <rtems/rtems/tasksimpl.h>
#include "cpuuseimpl.h"
/*
* Use a struct for all data to allow more than one top and to support the
* thread iterator.
*/
typedef struct
{
volatile bool thread_run;
volatile bool thread_active;
volatile bool single_page;
volatile uint32_t sort_order;
volatile uint32_t poll_rate_usecs;
volatile uint32_t show;
const rtems_printer* printer;
Timestamp_Control zero;
Timestamp_Control uptime;
Timestamp_Control last_uptime;
Timestamp_Control period;
int task_count; /* Number of tasks. */
int last_task_count; /* Number of tasks in the previous sample. */
int task_size; /* The size of the arrays */
Thread_Control** tasks; /* List of tasks in this sample. */
Thread_Control** last_tasks; /* List of tasks in the last sample. */
Timestamp_Control* usage; /* Usage of task's in this sample. */
Timestamp_Control* last_usage; /* Usage of task's in the last sample. */
Timestamp_Control* current_usage; /* Current usage for this sample. */
Timestamp_Control total; /* Total run run, should equal the uptime. */
Timestamp_Control idle; /* Time spent in idle. */
Timestamp_Control current; /* Current time run in this period. */
Timestamp_Control current_idle; /* Current time in idle this period. */
uint32_t stack_size; /* Size of stack allocated. */
} rtems_cpu_usage_data;
/*
* Sort orders.
*/
#define RTEMS_TOP_SORT_ID (0)
#define RTEMS_TOP_SORT_REAL_PRI (1)
#define RTEMS_TOP_SORT_CURRENT_PRI (2)
#define RTEMS_TOP_SORT_TOTAL (3)
#define RTEMS_TOP_SORT_CURRENT (4)
#define RTEMS_TOP_SORT_MAX (4)
static inline bool equal_to_uint32_t( uint32_t * lhs, uint32_t * rhs )
{
if ( *lhs == *rhs )
return true;
else
return false;
}
static inline bool less_than_uint32_t( uint32_t * lhs, uint32_t * rhs )
{
if ( *lhs < *rhs )
return true;
else
return false;
}
#define CPU_usage_Equal_to( _lhs, _rhs ) _Timestamp_Equal_to( _lhs, _rhs )
#define CPU_usage_Set_to_zero( _time ) _Timestamp_Set_to_zero( _time )
#define CPU_usage_Less_than( _lhs, _rhs ) _Timestamp_Less_than( _lhs, _rhs )
static void
print_memsize(rtems_cpu_usage_data* data, const uintptr_t size, const char* label)
{
if (size > (1024 * 1024))
rtems_printf(data->printer, "%4" PRIuPTR "M %s", size / (1024 * 1024), label);
else if (size > 1024)
rtems_printf(data->printer, "%4" PRIuPTR "K %s", size / 1024, label);
else
rtems_printf(data->printer, "%4" PRIuPTR " %s", size, label);
}
static int
print_time(rtems_cpu_usage_data* data,
const Timestamp_Control* time,
const int length)
{
uint32_t secs = _Timestamp_Get_seconds( time );
uint32_t usecs = _Timestamp_Get_nanoseconds( time ) / TOD_NANOSECONDS_PER_MICROSECOND;
int len = 0;
if (secs > 60)
{
uint32_t mins = secs / 60;
if (mins > 60)
{
uint32_t hours = mins / 60;
if (hours > 24)
{
len += rtems_printf(data->printer, "%" PRIu32 "d", hours / 24);
hours %= 24;
}
len += rtems_printf(data->printer, "%" PRIu32 "hr", hours);
mins %= 60;
}
len += rtems_printf(data->printer, "%" PRIu32 "m", mins);
secs %= 60;
}
len += rtems_printf(data->printer, "%" PRIu32 ".%06" PRIu32, secs, usecs);
if (len < length)
rtems_printf(data->printer, "%*c", length - len, ' ');
return len;
}
/*
* Count the number of tasks.
*/
static bool
task_counter(Thread_Control *thrad, void* arg)
{
rtems_cpu_usage_data* data = (rtems_cpu_usage_data*) arg;
++data->task_count;
return false;
}
/*
* Create the sorted table with the current and total usage.
*/
static bool
task_usage(Thread_Control* thread, void* arg)
{
rtems_cpu_usage_data* data = (rtems_cpu_usage_data*) arg;
Timestamp_Control usage;
Timestamp_Control current = data->zero;
int j;
data->stack_size += thread->Start.Initial_stack.size;
usage = _Thread_Get_CPU_time_used_after_last_reset(thread);
for (j = 0; j < data->last_task_count; j++)
{
if (thread == data->last_tasks[j])
{
_Timestamp_Subtract(&data->last_usage[j], &usage, ¤t);
break;
}
}
/*
* When not using nanosecond CPU usage resolution, we have to count the
* number of "ticks" we gave credit for to give the user a rough guideline as
* to what each number means proportionally.
*/
_Timestamp_Add_to(&data->total, &usage);
_Timestamp_Add_to(&data->current, ¤t);
if (thread->is_idle)
{
_Timestamp_Add_to(&data->idle, &usage);
_Timestamp_Add_to(&data->current_idle, ¤t);
}
/*
* Create the tasks to display sorting as we create.
*/
for (j = 0; j < data->task_count; j++)
{
if (data->tasks[j])
{
int k;
/*
* Sort on the current load.
*/
switch (data->sort_order)
{
default:
data->sort_order = RTEMS_TOP_SORT_CURRENT;
/* drop through */
case RTEMS_TOP_SORT_CURRENT:
if (CPU_usage_Equal_to(¤t, &data->zero) ||
CPU_usage_Less_than(¤t, &data->current_usage[j]))
continue;
case RTEMS_TOP_SORT_TOTAL:
if (CPU_usage_Equal_to(&usage, &data->zero) ||
CPU_usage_Less_than(&usage, &data->usage[j]))
continue;
/* Fall through */
case RTEMS_TOP_SORT_REAL_PRI:
if (thread->Real_priority.priority > data->tasks[j]->Real_priority.priority)
continue;
/* Fall through */
case RTEMS_TOP_SORT_CURRENT_PRI:
if (
_Thread_Get_priority( thread )
> _Thread_Get_priority( data->tasks[j] )
) {
continue;
}
/* Fall through */
case RTEMS_TOP_SORT_ID:
if (thread->Object.id < data->tasks[j]->Object.id)
continue;
}
for (k = (data->task_count - 1); k >= j; k--)
{
data->tasks[k + 1] = data->tasks[k];
data->usage[k + 1] = data->usage[k];
data->current_usage[k + 1] = data->current_usage[k];
}
}
data->tasks[j] = thread;
data->usage[j] = usage;
data->current_usage[j] = current;
break;
}
return false;
}
/*
* rtems_cpuusage_top_thread
*
* This function displays the load of the tasks on an ANSI terminal.
*/
static void
rtems_cpuusage_top_thread (rtems_task_argument arg)
{
rtems_cpu_usage_data* data = (rtems_cpu_usage_data*) arg;
char name[13];
int i;
Heap_Information_block wksp;
uint32_t ival, fval;
int task_count;
rtems_event_set out;
rtems_status_code sc;
bool first_time = true;
data->thread_active = true;
_TOD_Get_uptime(&data->last_uptime);
CPU_usage_Set_to_zero(&data->zero);
while (data->thread_run)
{
Timestamp_Control uptime_at_last_reset = CPU_usage_Uptime_at_last_reset;
size_t tasks_size;
size_t usage_size;
Timestamp_Control load;
data->task_count = 0;
_Thread_Iterate(task_counter, data);
tasks_size = sizeof(Thread_Control*) * (data->task_count + 1);
usage_size = sizeof(Timestamp_Control) * (data->task_count + 1);
if (data->task_count > data->task_size)
{
data->tasks = realloc(data->tasks, tasks_size);
data->usage = realloc(data->usage, usage_size);
data->current_usage = realloc(data->current_usage, usage_size);
if ((data->tasks == NULL) || (data->usage == NULL) || (data->current_usage == NULL))
{
rtems_printf(data->printer, "top worker: error: no memory\n");
data->thread_run = false;
break;
}
}
memset(data->tasks, 0, tasks_size);
memset(data->usage, 0, usage_size);
memset(data->current_usage, 0, usage_size);
_Timestamp_Set_to_zero(&data->total);
_Timestamp_Set_to_zero(&data->current);
_Timestamp_Set_to_zero(&data->idle);
_Timestamp_Set_to_zero(&data->current_idle);
data->stack_size = 0;
_TOD_Get_uptime(&data->uptime);
_Timestamp_Subtract(&uptime_at_last_reset, &data->uptime, &data->uptime);
_Timestamp_Subtract(&data->last_uptime, &data->uptime, &data->period);
data->last_uptime = data->uptime;
_Thread_Iterate(task_usage, data);
if (data->task_count > data->task_size)
{
data->last_tasks = realloc(data->last_tasks, tasks_size);
data->last_usage = realloc(data->last_usage, usage_size);
if ((data->last_tasks == NULL) || (data->last_usage == NULL))
{
rtems_printf(data->printer, "top worker: error: no memory\n");
data->thread_run = false;
break;
}
data->task_size = data->task_count;
}
memcpy(data->last_tasks, data->tasks, tasks_size);
memcpy(data->last_usage, data->usage, usage_size);
data->last_task_count = data->task_count;
/*
* We need to loop again to get suitable current usage values as we need a
* last sample to work.
*/
if (first_time)
{
rtems_task_wake_after(RTEMS_MILLISECONDS_TO_TICKS(500));
first_time = false;
continue;
}
_Protected_heap_Get_information(&_Workspace_Area, &wksp);
if (data->single_page)
rtems_printf(data->printer,
"\x1b[H\x1b[J"
" ENTER:Exit SPACE:Refresh"
" S:Scroll A:All <>:Order +/-:Lines\n");
rtems_printf(data->printer, "\n");
/*
* Uptime and period of this sample.
*/
rtems_printf(data->printer, "Uptime: ");
print_time(data, &data->uptime, 20);
rtems_printf(data->printer, " Period: ");
print_time(data, &data->period, 20);
/*
* Task count, load and idle levels.
*/
rtems_printf(data->printer, "\nTasks: %4i ", data->task_count);
_Timestamp_Subtract(&data->idle, &data->total, &load);
_Timestamp_Divide(&load, &data->uptime, &ival, &fval);
rtems_printf(data->printer,
"Load Average: %4" PRIu32 ".%03" PRIu32 "%%", ival, fval);
_Timestamp_Subtract(&data->current_idle, &data->current, &load);
_Timestamp_Divide(&load, &data->period, &ival, &fval);
rtems_printf(data->printer,
" Load: %4" PRIu32 ".%03" PRIu32 "%%", ival, fval);
_Timestamp_Divide(&data->current_idle, &data->period, &ival, &fval);
rtems_printf(data->printer,
" Idle: %4" PRIu32 ".%03" PRIu32 "%%", ival, fval);
/*
* Memory usage.
*/
if (rtems_configuration_get_unified_work_area())
{
rtems_printf(data->printer, "\nMem: ");
print_memsize(data, wksp.Free.total, "free");
print_memsize(data, wksp.Used.total, "used");
}
else
{
Heap_Information_block libc_heap;
malloc_info(&libc_heap);
rtems_printf(data->printer, "\nMem: Wksp: ");
print_memsize(data, wksp.Free.total, "free");
print_memsize(data, wksp.Used.total, "used Heap: ");
print_memsize(data, libc_heap.Free.total, "free");
print_memsize(data, libc_heap.Used.total, "used");
}
print_memsize(data, data->stack_size, "stack\n");
rtems_printf(data->printer,
"\n"
" ID | NAME | RPRI | CPRI | TIME | TOTAL | CURRENT\n"
"-%s---------+---------------------+-%s-----%s-----+---------------------+-%s------+--%s----\n",
data->sort_order == RTEMS_TOP_SORT_ID ? "^^" : "--",
data->sort_order == RTEMS_TOP_SORT_REAL_PRI ? "^^" : "--",
data->sort_order == RTEMS_TOP_SORT_CURRENT_PRI ? "^^" : "--",
data->sort_order == RTEMS_TOP_SORT_TOTAL ? "^^" : "--",
data->sort_order == RTEMS_TOP_SORT_CURRENT ? "^^" : "--"
);
task_count = 0;
for (i = 0; i < data->task_count; i++)
{
Thread_Control* thread = data->tasks[i];
Timestamp_Control usage;
Timestamp_Control current_usage;
Thread_queue_Context queue_context;
const Scheduler_Control *scheduler;
Priority_Control real_priority;
Priority_Control priority;
if (thread == NULL)
break;
if (data->single_page && (data->show != 0) && (i >= data->show))
break;
/*
* We need to count the number displayed to clear the remainder of the
* the display.
*/
++task_count;
/*
* If the API os POSIX print the entry point.
*/
rtems_object_get_name(thread->Object.id, sizeof(name), name);
if (name[0] == '\0')
snprintf(name, sizeof(name) - 1, "(%p)", thread->Start.Entry.Kinds.Numeric.entry);
_Thread_queue_Context_initialize(&queue_context);
_Thread_Wait_acquire(thread, &queue_context);
scheduler = _Thread_Scheduler_get_home(thread);
real_priority = thread->Real_priority.priority;
priority = _Thread_Get_priority(thread);
_Thread_Wait_release(thread, &queue_context);
rtems_printf(data->printer,
" 0x%08" PRIx32 " | %-19s | %3" PRId32 " | %3" PRId32 " | ",
thread->Object.id,
name,
_RTEMS_Priority_From_core(scheduler, real_priority),
_RTEMS_Priority_From_core(scheduler, priority));
usage = data->usage[i];
current_usage = data->current_usage[i];
/*
* Print the information
*/
print_time(data, &usage, 19);
_Timestamp_Divide(&usage, &data->total, &ival, &fval);
rtems_printf(data->printer,
" |%4" PRIu32 ".%03" PRIu32, ival, fval);
_Timestamp_Divide(¤t_usage, &data->period, &ival, &fval);
rtems_printf(data->printer,
" |%4" PRIu32 ".%03" PRIu32 "\n", ival, fval);
}
if (data->single_page && (data->show != 0) && (task_count < data->show))
{
i = data->show - task_count;
while (i > 0)
{
rtems_printf(data->printer, "\x1b[K\n");
i--;
}
}
sc = rtems_event_receive(RTEMS_EVENT_1,
RTEMS_EVENT_ANY,
RTEMS_MILLISECONDS_TO_TICKS (data->poll_rate_usecs),
&out);
if ((sc != RTEMS_SUCCESSFUL) && (sc != RTEMS_TIMEOUT))
{
rtems_printf(data->printer,
"error: event receive: %s\n", rtems_status_text(sc));
break;
}
}
free(data->tasks);
free(data->last_tasks);
free(data->last_usage);
free(data->current_usage);
data->thread_active = false;
rtems_task_exit();
}
void rtems_cpu_usage_top_with_plugin(
const rtems_printer *printer
)
{
rtems_status_code sc;
rtems_task_priority priority;
rtems_name name;
rtems_id id;
rtems_cpu_usage_data data;
int show_lines = 25;
memset(&data, 0, sizeof(data));
data.thread_run = true;
data.single_page = true;
data.sort_order = RTEMS_TOP_SORT_CURRENT;
data.poll_rate_usecs = 3000;
data.show = show_lines;
data.printer = printer;
sc = rtems_task_set_priority (RTEMS_SELF, RTEMS_CURRENT_PRIORITY, &priority);
if (sc != RTEMS_SUCCESSFUL)
{
rtems_printf (printer,
"error: cannot obtain the current priority: %s\n", rtems_status_text (sc));
return;
}
name = rtems_build_name('C', 'P', 'l', 't');
sc = rtems_task_create (name, priority, 4 * 1024,
RTEMS_PREEMPT | RTEMS_TIMESLICE | RTEMS_NO_ASR,
RTEMS_FLOATING_POINT | RTEMS_LOCAL,
&id);
if (sc != RTEMS_SUCCESSFUL)
{
rtems_printf (printer,
"error: cannot create helper thread: %s\n", rtems_status_text (sc));
return;
}
sc = rtems_task_start (id, rtems_cpuusage_top_thread, (rtems_task_argument) &data);
if (sc != RTEMS_SUCCESSFUL)
{
rtems_printf (printer,
"error: cannot start helper thread: %s\n", rtems_status_text (sc));
rtems_task_delete (id);
return;
}
while (true)
{
int c = getchar ();
if ((c == '\r') || (c == '\n') || (c == 'q') || (c == 'Q'))
{
int loops = 50;
data.thread_run = false;
rtems_event_send(id, RTEMS_EVENT_1);
while (loops && data.thread_active)
rtems_task_wake_after (RTEMS_MICROSECONDS_TO_TICKS (100000));
rtems_printf (printer, "load monitoring stopped.\n");
return;
}
else if (c == '<')
{
if (data.sort_order == 0)
data.sort_order = RTEMS_TOP_SORT_MAX;
else
--data.sort_order;
rtems_event_send(id, RTEMS_EVENT_1);
}
else if (c == '>')
{
if (data.sort_order >= RTEMS_TOP_SORT_MAX)
data.sort_order = 0;
else
++data.sort_order;
rtems_event_send(id, RTEMS_EVENT_1);
}
else if ((c == 's') || (c == 'S'))
{
data.single_page = !data.single_page;
rtems_event_send(id, RTEMS_EVENT_1);
}
else if ((c == 'a') || (c == 'A'))
{
if (data.show == 0)
data.show = show_lines;
else
data.show = 0;
rtems_event_send(id, RTEMS_EVENT_1);
}
else if (c == '+')
{
++show_lines;
if (data.show != 0)
data.show = show_lines;
}
else if (c == '-')
{
if (show_lines > 5)
--show_lines;
if (data.show != 0)
data.show = show_lines;
}
else if (c == ' ')
{
rtems_event_send(id, RTEMS_EVENT_1);
}
}
}
void rtems_cpu_usage_top (void)
{
rtems_printer printer;
rtems_print_printer_printk (&printer);
rtems_cpu_usage_top_with_plugin (&printer);
}