From cf4a362ab5466c6b8475b0e337dd278db92e210f Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sun, 23 Feb 2014 10:55:01 -0600 Subject: [PATCH] CPU load calculations now available for all threads. Available in /proc/pid/loadavg --- ChangeLog | 4 ++ fs/procfs/fs_procfscpuload.c | 40 +++++------ fs/procfs/fs_procfsproc.c | 83 +++++++++++++++++++++- include/nuttx/clock.h | 45 ++++++++---- sched/Makefile | 4 ++ sched/os_internal.h | 29 ++++++-- sched/sched_cpuload.c | 134 +++++++++++++++++++++++++++++++++++ sched/sched_processtimer.c | 37 ++++++---- sched/sched_releasetcb.c | 9 ++- sched/task_setup.c | 10 ++- 10 files changed, 331 insertions(+), 64 deletions(-) create mode 100644 sched/sched_cpuload.c diff --git a/ChangeLog b/ChangeLog index 990d0932d1..3651571ee3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6657,3 +6657,7 @@ * sched/sched_processtimer.c and fs/procfs/fs_procfscpuload.c: Add logic to measure and calculate the CPU load percentage. From David Alessio (2014-2-22). + * sched/sched_processtimer.c, sched_cpuload.c, fs/procfs/fs_procfscpuload.c, + and fs_procfscpuload.c: CPU load logic extended to keep counts on each + thread. The per-thread CPU is now reported in the procfs under + /loadavg (2014-2-23). diff --git a/fs/procfs/fs_procfscpuload.c b/fs/procfs/fs_procfscpuload.c index a1f8179bf8..75572dee3c 100644 --- a/fs/procfs/fs_procfscpuload.c +++ b/fs/procfs/fs_procfscpuload.c @@ -207,9 +207,6 @@ static ssize_t cpuload_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct cpuload_file_s *attr; - uint32_t cpuload; - uint32_t cpuload_int; - uint32_t cpuload_frac; size_t linesize; off_t offset; ssize_t ret; @@ -230,40 +227,37 @@ static ssize_t cpuload_read(FAR struct file *filep, FAR char *buffer, if (filep->f_pos == 0) { - uint32_t idle; - uint32_t busycnt; - uint32_t verify; + struct cpuload_s cpuload; + uint32_t intpart; + uint32_t fracpart; - /* Sample the counts, repeatedly if necessary to assure that they are - * in sync. + /* Sample the counts for the IDLE thread. clock_cpuload should only + * fail if the PID is not valid. This, however, should never happen + * for the IDLE thread. */ - do - { - busycnt = g_cpuload.cnt; - idle = g_cpuload.idle; - verify = g_cpuload.cnt; - } - while (busycnt != verify); + DEBUGVERIFY(clock_cpuload(0, &cpuload)); - /* On the simulator, you may hit busycnt == 0, but probably never on + /* On the simulator, you may hit cpuload.total == 0, but probably never on * real hardware. */ - if (busycnt > 0) + if (cpuload.total > 0) { - cpuload = 1000 - (1000 * idle) / busycnt; - cpuload_int = cpuload / 10; - cpuload_frac = cpuload - 10 * cpuload_int; + uint32_t tmp; + + tmp = 1000 - (1000 * cpuload.active) / cpuload.total; + intpart = tmp / 10; + fracpart = tmp - 10 * intpart; } else { - cpuload_int = 0; - cpuload_frac = 0; + intpart = 0; + fracpart = 0; } linesize = snprintf(attr->line, CPULOAD_LINELEN, "%3d.%01d%%\n", - cpuload_int, cpuload_frac); + intpart, fracpart); /* Save the linesize in case we are re-entered with f_pos > 0 */ diff --git a/fs/procfs/fs_procfsproc.c b/fs/procfs/fs_procfsproc.c index 1fc90b0969..a1ec1b898a 100644 --- a/fs/procfs/fs_procfsproc.c +++ b/fs/procfs/fs_procfsproc.c @@ -60,6 +60,10 @@ #include #include +#ifdef CONFIG_SCHED_CPULOAD +# include +#endif + #include #if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS) @@ -86,6 +90,9 @@ enum proc_node_e PROC_LEVEL0 = 0, /* The top-level directory */ PROC_STATUS, /* Task/thread status */ PROC_CMDLINE, /* Task command line */ +#ifdef CONFIG_SCHED_CPULOAD + PROC_LOADAVG, /* Average CPU utilization */ +#endif PROC_STACK, /* Task stack info */ PROC_GROUP, /* Group directory */ PROC_GROUP_STATUS, /* Task group status */ @@ -136,6 +143,11 @@ static ssize_t proc_status(FAR struct proc_file_s *procfile, static ssize_t proc_cmdline(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset); +#ifdef CONFIG_SCHED_CPULOAD +static ssize_t proc_loadavg(FAR struct proc_file_s *procfile, + FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, + off_t offset); +#endif static ssize_t proc_stack(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset); @@ -211,6 +223,13 @@ static const struct proc_node_s g_cmdline = "cmdline", "cmdline", (uint8_t)PROC_CMDLINE, DTYPE_FILE /* Task command line */ }; +#ifdef CONFIG_SCHED_CPULOAD +static const struct proc_node_s g_loadavg = +{ + "loadavg", "loadavg", (uint8_t)PROC_LOADAVG, DTYPE_FILE /* Average CPU utilization */ +}; +#endif + static const struct proc_node_s g_stack = { "stack", "stack", (uint8_t)PROC_STACK, DTYPE_FILE /* Task stack info */ @@ -237,6 +256,9 @@ static FAR const struct proc_node_s * const g_nodeinfo[] = { &g_status, /* Task/thread status */ &g_cmdline, /* Task command line */ +#ifdef CONFIG_SCHED_CPULOAD + &g_loadavg, /* Average CPU utilization */ +#endif &g_stack, /* Task stack info */ &g_group, /* Group directory */ &g_groupstatus, /* Task group status */ @@ -250,6 +272,9 @@ static const struct proc_node_s * const g_level0info[] = { &g_status, /* Task/thread status */ &g_cmdline, /* Task command line */ +#ifdef CONFIG_SCHED_CPULOAD + &g_loadavg, /* Average CPU utilization */ +#endif &g_stack, /* Task stack info */ &g_group, /* Group directory */ }; @@ -517,13 +542,62 @@ static ssize_t proc_cmdline(FAR struct proc_file_s *procfile, return totalsize; } +/**************************************************************************** + * Name: proc_loadavg + ****************************************************************************/ + +#ifdef CONFIG_SCHED_CPULOAD +static ssize_t proc_loadavg(FAR struct proc_file_s *procfile, + FAR struct tcb_s *tcb, FAR char *buffer, + size_t buflen, off_t offset) +{ + struct cpuload_s cpuload; + uint32_t intpart; + uint32_t fracpart; + size_t linesize; + size_t copysize; + ssize_t ret; + + /* Sample the counts for the thread. clock_cpuload should only fail if + * the PID is not valid. This could happen if the thread exited sometime + * after the procfs entry was opened. + */ + + ret = (ssize_t)clock_cpuload(procfile->pid, &cpuload); + + /* On the simulator, you may hit cpuload.total == 0, but probably never on + * real hardware. + */ + + if (cpuload.total > 0) + { + uint32_t tmp; + + tmp = 1000 - (1000 * cpuload.active) / cpuload.total; + intpart = tmp / 10; + fracpart = tmp - 10 * intpart; + } + else + { + intpart = 0; + fracpart = 0; + } + + linesize = snprintf(procfile->line, STATUS_LINELEN, "%3d.%01d%%\n", + intpart, fracpart); + copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen, &offset); + + return copysize; +} +#endif + /**************************************************************************** * Name: proc_stack ****************************************************************************/ static ssize_t proc_stack(FAR struct proc_file_s *procfile, - FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, - off_t offset) + FAR struct tcb_s *tcb, FAR char *buffer, + size_t buflen, off_t offset) { size_t remaining; size_t linesize; @@ -986,6 +1060,11 @@ static ssize_t proc_read(FAR struct file *filep, FAR char *buffer, ret = proc_cmdline(procfile, tcb, buffer, buflen, filep->f_pos); break; +#ifdef CONFIG_SCHED_CPULOAD + case PROC_LOADAVG: /* Average CPU utilization */ + ret = proc_loadavg(procfile, tcb, buffer, buflen, filep->f_pos); + break; +#endif case PROC_STACK: /* Task stack info */ ret = proc_stack(procfile, tcb, buffer, buflen, filep->f_pos); break; diff --git a/include/nuttx/clock.h b/include/nuttx/clock.h index d0fd9220f6..d1315fbbab 100644 --- a/include/nuttx/clock.h +++ b/include/nuttx/clock.h @@ -124,13 +124,13 @@ /**************************************************************************** * Public Types ****************************************************************************/ -/* This structure is used when CONFIG_SCHED_CPULOAD to sample CPU usage */ +/* This structure is used to report CPU usage for a particular thread */ #ifdef CONFIG_SCHED_CPULOAD struct cpuload_s { - volatile uint32_t cnt; /* Total number of clock ticks */ - volatile uint32_t idle; /* Total number of clocks ticks with CPU IDLE */ + volatile uint32_t total; /* Total number of clock ticks */ + volatile uint32_t active; /* Number of ticks while this thread was active */ }; #endif @@ -162,20 +162,14 @@ extern volatile uint32_t g_system_timer; # endif #endif -/* CPU Load Measurements ***************************************************/ -/* This structure is used when CONFIG_SCHED_CPULOAD to sample CPU usage */ - -#ifdef CONFIG_SCHED_CPULOAD -extern volatile struct cpuload_s g_cpuload; -#endif - /**************************************************************************** * Public Function Prototypes ****************************************************************************/ #ifdef __cplusplus #define EXTERN extern "C" -extern "C" { +extern "C" +{ #else #define EXTERN extern #endif @@ -209,7 +203,7 @@ extern "C" { ****************************************************************************/ #ifdef CONFIG_RTC -EXTERN void clock_synchronize(void); +void clock_synchronize(void); #endif /**************************************************************************** @@ -235,7 +229,7 @@ EXTERN void clock_synchronize(void); # ifdef CONFIG_SYSTEM_TIME64 # define clock_systimer() (uint32_t)(clock_systimer64() & 0x00000000ffffffff) # else -EXTERN uint32_t clock_systimer(void); +uint32_t clock_systimer(void); # endif #endif @@ -259,7 +253,30 @@ EXTERN uint32_t clock_systimer(void); ****************************************************************************/ #if !__HAVE_KERNEL_GLOBALS && defined(CONFIG_SYSTEM_TIME64) -EXTERN uint64_t clock_systimer64(void); +uint64_t clock_systimer64(void); +#endif + +/**************************************************************************** + * Function: clock_cpuload + * + * Description: + * Return load measurement data for the select PID. + * + * Parameters: + * pid - The task ID of the thread of interest. pid == 0 is the IDLE thread. + * cpuload - The location to return the CPU load + * + * Return Value: + * OK (0) on success; a negated errno value on failure. The only reason + * that this function can fail is if 'pid' no longer refers to a valid + * thread. + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_SCHED_CPULOAD +int clock_cpuload(int pid, FAR struct cpuload_s *cpuload); #endif #undef EXTERN diff --git a/sched/Makefile b/sched/Makefile index a9ee0dc3e4..67da182ab6 100644 --- a/sched/Makefile +++ b/sched/Makefile @@ -93,6 +93,10 @@ SCHED_SRCS += sched_waitid.c sched_wait.c endif endif +ifeq ($(CONFIG_SCHED_CPULOAD),y) +SCHED_SRCS += sched_cpuload.c +endif + GRP_SRCS = group_create.c group_join.c group_leave.c group_find.c GRP_SRCS += group_setupstreams.c group_setupidlefiles.c group_setuptaskfiles.c GRP_SRCS += task_getgroup.c group_foreachchild.c group_killchildren.c diff --git a/sched/os_internal.h b/sched/os_internal.h index 6073bb39f0..999e7c3df6 100644 --- a/sched/os_internal.h +++ b/sched/os_internal.h @@ -1,7 +1,7 @@ /**************************************************************************** * sched/os_internal.h * - * Copyright (C) 2007-2013 Gregory Nutt. All rights reserved. + * Copyright (C) 2007-2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -81,15 +81,24 @@ * Public Type Definitions ****************************************************************************/ -/* This structure defines the format of the hash table that - * is used to (1) determine if a task ID is unique, and (2) - * to map a process ID to its corresponding TCB. +/* This structure defines the format of the hash table that is used to (1) + * determine if a task ID is unique, and (2) to map a process ID to its + * corresponding TCB. + * + * NOTE also that CPU load measurement data is retained in his table vs. in + * the TCB which would seem to be the more logic place. It is place in the + * hash table, instead, to facilitate CPU load adjustments on all threads + * during timer interrupt handling. sched_foreach() could do this too, but + * this would require a little more overhead. */ struct pidhash_s { - FAR struct tcb_s *tcb; - pid_t pid; + FAR struct tcb_s *tcb; /* TCB assigned to this PID */ + pid_t pid; /* The full PID value */ +#ifdef CONFIG_SCHED_CPULOAD + uint32_t ticks; /* Number of ticks on this thread */ +#endif }; typedef struct pidhash_s pidhash_t; @@ -210,6 +219,14 @@ extern pidhash_t g_pidhash[CONFIG_MAX_TASKS]; extern const tasklist_t g_tasklisttable[NUM_TASK_STATES]; +#ifdef CONFIG_SCHED_CPULOAD +/* This is the total number of clock tick counts. Essentially the + * 'denominator' for all CPU load calculations. + */ + +extern volatile uint32_t g_cpuload_total; +#endif + /**************************************************************************** * Public Function Prototypes ****************************************************************************/ diff --git a/sched/sched_cpuload.c b/sched/sched_cpuload.c new file mode 100644 index 0000000000..03f7656c12 --- /dev/null +++ b/sched/sched_cpuload.c @@ -0,0 +1,134 @@ +/************************************************************************ + * sched/sched_cpuload.c + * + * Copyright (C) 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * 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. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + ************************************************************************/ + +/************************************************************************ + * Included Files + ************************************************************************/ + +#include + +#include +#include + +#include +#include + +#include "os_internal.h" + +#ifdef CONFIG_SCHED_CPULOAD + +/************************************************************************ + * Pre-processor Definitions + ************************************************************************/ + +/************************************************************************ + * Private Type Declarations + ************************************************************************/ + +/************************************************************************ + * Public Variables + ************************************************************************/ + +/************************************************************************ + * Private Variables + ************************************************************************/ + +/************************************************************************ + * Private Functions + ************************************************************************/ + +/************************************************************************ + * Public Functions + ************************************************************************/ + +/**************************************************************************** + * Function: clock_cpuload + * + * Description: + * Return load measurement data for the select PID. + * + * Parameters: + * pid - The task ID of the thread of interest. pid == 0 is the IDLE thread. + * cpuload - The location to return the CPU load + * + * Return Value: + * OK (0) on success; a negated errno value on failure. The only reason + * that this function can fail is if 'pid' no longer refers to a valid + * thread. + * + * Assumptions: + * + ****************************************************************************/ + +int clock_cpuload(int pid, FAR struct cpuload_s *cpuload) +{ + irqstate_t flags; + int hash_index = PIDHASH(pid); + int ret = -ESRCH; + + DEBUGASSERT(cpuload); + + /* Momentarily disable interrupts. We need (1) the task to stay valid + * while we are doing these operations and (2) the tick counts to be + * synchronized when read. + */ + + flags = irqsave(); + + /* Make sure that the entry is valid (TCB field is not NULL) and matches + * the requested PID. The first check is needed if the thread has exited. + * The second check is needed for the case where the task associated with + * the requested PID has exited and the slot has been taken by another + * thread with a different PID. + * + * NOTE also that CPU load measurement data is retained in the g_pidhash + * table vs. in the TCB which would seem to be the more logic place. It + * is place in the hash table, instead, to facilitate CPU load adjustments + * on all threads during timer interrupt handling. sched_foreach() could + * do this too, but this would require a little more overhead. + */ + + if (g_pidhash[hash_index].tcb && g_pidhash[hash_index].pid == pid) + { + cpuload->total = g_cpuload_total; + cpuload->active = g_pidhash[hash_index].ticks; + ret = OK; + } + + irqrestore(flags); + return ret; +} + +#endif /* CONFIG_SCHED_CPULOAD */ diff --git a/sched/sched_processtimer.c b/sched/sched_processtimer.c index b07994cb49..c03077c217 100644 --- a/sched/sched_processtimer.c +++ b/sched/sched_processtimer.c @@ -63,13 +63,15 @@ ************************************************************************/ /************************************************************************ - * Global Variables + * Public Variables ************************************************************************/ -/* This structure is used when CONFIG_SCHED_CPULOAD to sample CPU usage */ - #ifdef CONFIG_SCHED_CPULOAD -volatile struct cpuload_s g_cpuload; +/* This is the total number of clock tick counts. Essentially the + * 'denominator' for all CPU load calculations. + */ + +volatile uint32_t g_cpuload_total; #endif /************************************************************************ @@ -170,27 +172,34 @@ static inline void sched_process_timeslice(void) * ************************************************************************/ -#if defined(CONFIG_SCHED_CPULOAD) +#ifdef CONFIG_SCHED_CPULOAD static inline void sched_process_cpuload(void) { FAR struct tcb_s *rtcb = (FAR struct tcb_s*)g_readytorun.head; + int hash_index; + int i; - /* Gather stats for cpuload. cpuload is percent of time cpu is not idle. */ - /* Is the idle task running */ + /* Increment the count on the currently executing thread */ - if (rtcb->pid == 0) - { - ++g_cpuload.idle; - } + hash_index = PIDHASH(rtcb->pid); + g_pidhash[hash_index].ticks++; /* Increment tick count. If the accumulated tick value exceed a time * constant, then shift the accumulators. */ - if (++g_cpuload.cnt > (CONFIG_SCHED_CPULOAD_TIMECONSTANT * CLOCKS_PER_SEC)) + if (++g_cpuload_total > (CONFIG_SCHED_CPULOAD_TIMECONSTANT * CLOCKS_PER_SEC)) { - g_cpuload.cnt >>= 1; - g_cpuload.idle >>= 1; + /* Divide the tick count for every task by two */ + + for (i = 0; i < CONFIG_MAX_TASKS; i++) + { + g_pidhash[i].ticks >>= 1; + } + + /* Divide the total tick count by two */ + + g_cpuload_total >>= 1; } } #else diff --git a/sched/sched_releasetcb.c b/sched/sched_releasetcb.c index 26476ec987..2b184fbda9 100644 --- a/sched/sched_releasetcb.c +++ b/sched/sched_releasetcb.c @@ -1,7 +1,7 @@ /************************************************************************ * sched/sched_releasetcb.c * - * Copyright (C) 2007, 2009, 2012-2013 Gregory Nutt. All rights reserved. + * Copyright (C) 2007, 2009, 2012-2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -68,8 +68,11 @@ static void sched_releasepid(pid_t pid) * following action is atomic */ - g_pidhash[hash_ndx].tcb = NULL; - g_pidhash[hash_ndx].pid = INVALID_PROCESS_ID; + g_pidhash[hash_ndx].tcb = NULL; + g_pidhash[hash_ndx].pid = INVALID_PROCESS_ID; +#ifdef CONFIG_SCHED_CPULOAD + g_pidhash[hash_ndx].ticks = 0; +#endif } /************************************************************************ diff --git a/sched/task_setup.c b/sched/task_setup.c index 582b64a4a0..ed1578da90 100644 --- a/sched/task_setup.c +++ b/sched/task_setup.c @@ -132,9 +132,15 @@ static int task_assignpid(FAR struct tcb_s *tcb) if (!g_pidhash[hash_ndx].tcb) { - g_pidhash[hash_ndx].tcb = tcb; - g_pidhash[hash_ndx].pid = next_pid; + /* Assign this PID to the task */ + + g_pidhash[hash_ndx].tcb = tcb; + g_pidhash[hash_ndx].pid = next_pid; +#ifdef CONFIG_SCHED_CPULOAD + g_pidhash[hash_ndx].ticks = 0; +#endif tcb->pid = next_pid; + (void)sched_unlock(); return OK; }