From 18dc49d04588570b37eb4a47c0265a92bec3349b Mon Sep 17 00:00:00 2001 From: Daniele Cattaneo <daniele3.cattaneo@mail.polimi.it> Date: Sun, 5 Mar 2023 21:41:30 +0100 Subject: [PATCH] Add support for keeping track of cumulative CPU time for each thread. Signed-off-by: Terraneo Federico <fede.tft@miosix.org> --- miosix/Makefile | 1 + miosix/_doc/doxygen/Doxyfile | 1 + miosix/config/miosix_settings.h | 6 + miosix/kernel/cpu_time_counter.cpp | 59 ++++++ miosix/kernel/cpu_time_counter.h | 221 +++++++++++++++++++++++ miosix/kernel/cpu_time_counter_private.h | 56 ++++++ miosix/kernel/cpu_time_counter_types.h | 57 ++++++ miosix/kernel/kernel.h | 8 + miosix/kernel/scheduler/scheduler.h | 23 ++- miosix/miosix.h | 1 + 10 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 miosix/kernel/cpu_time_counter.cpp create mode 100644 miosix/kernel/cpu_time_counter.h create mode 100644 miosix/kernel/cpu_time_counter_private.h create mode 100644 miosix/kernel/cpu_time_counter_types.h diff --git a/miosix/Makefile b/miosix/Makefile index 1fb69bf3..07a6266b 100644 --- a/miosix/Makefile +++ b/miosix/Makefile @@ -20,6 +20,7 @@ kernel/process.cpp \ kernel/process_pool.cpp \ kernel/timeconversion.cpp \ kernel/SystemMap.cpp \ +kernel/cpu_time_counter.cpp \ kernel/scheduler/priority/priority_scheduler.cpp \ kernel/scheduler/control/control_scheduler.cpp \ kernel/scheduler/edf/edf_scheduler.cpp \ diff --git a/miosix/_doc/doxygen/Doxyfile b/miosix/_doc/doxygen/Doxyfile index 3d07497c..4272f67c 100644 --- a/miosix/_doc/doxygen/Doxyfile +++ b/miosix/_doc/doxygen/Doxyfile @@ -2024,6 +2024,7 @@ INCLUDE_FILE_PATTERNS = # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. PREDEFINED = WITH_FILESYSTEM \ + WITH_CPU_TIME_COUNTER \ _MIOSIX \ MIOSIX_LITTLE_ENDIAN \ __DOXYGEN__ diff --git a/miosix/config/miosix_settings.h b/miosix/config/miosix_settings.h index a5a3d675..a1c33bb3 100644 --- a/miosix/config/miosix_settings.h +++ b/miosix/config/miosix_settings.h @@ -76,6 +76,12 @@ namespace miosix { //#define SCHED_TYPE_CONTROL_BASED //#define SCHED_TYPE_EDF +/// \def WITH_CPU_TIME_COUNTER +/// Allows to enable/disable CPUTimeCounter to save code size and remove its +/// overhead from the scheduling process. By default it is defined +/// (CPUTimeCounter is enabled). +#define WITH_CPU_TIME_COUNTER + // // Filesystem options // diff --git a/miosix/kernel/cpu_time_counter.cpp b/miosix/kernel/cpu_time_counter.cpp new file mode 100644 index 00000000..ea57ed48 --- /dev/null +++ b/miosix/kernel/cpu_time_counter.cpp @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2023 by Daniele Cattaneo * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#include "cpu_time_counter.h" +#include "kernel/kernel.h" + +#ifdef WITH_CPU_TIME_COUNTER + +using namespace miosix; + +Thread *CPUTimeCounter::head = nullptr; +Thread *CPUTimeCounter::tail = nullptr; +volatile unsigned int CPUTimeCounter::nThreads = 0; + +void CPUTimeCounter::PKremoveDeadThreads() +{ + Thread *prev = nullptr; + Thread *cur = head; + while (cur) { + if (cur->flags.isDeleted()) { + if (prev) { + prev->timeCounterData.next = cur->timeCounterData.next; + } else { + head = cur->timeCounterData.next; + } + nThreads--; + } else { + prev = cur; + } + cur = cur->timeCounterData.next; + } + tail = prev; +} + +#endif // WITH_CPU_TIME_COUNTER diff --git a/miosix/kernel/cpu_time_counter.h b/miosix/kernel/cpu_time_counter.h new file mode 100644 index 00000000..d9e0f998 --- /dev/null +++ b/miosix/kernel/cpu_time_counter.h @@ -0,0 +1,221 @@ +/*************************************************************************** + * Copyright (C) 2023 by Daniele Cattaneo * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#ifndef CPU_TIME_COUNTER_H +#define CPU_TIME_COUNTER_H + +#include "kernel.h" +#include "cpu_time_counter_types.h" + +#ifdef WITH_CPU_TIME_COUNTER + +namespace miosix { + +/** + * \addtogroup Kernel + * \{ + */ + +/** + * CPUTimeCounter provides a low-level method to retrieve information about how + * much CPU time was used up to now by each thread in the system. + * It is intended for debugging and evaluation purposes and is enabled only if + * the symbol `WITH_CPU_TIME_COUNTER` has been defined in + * config/miosix_settings.h. + * + * The implementation of this class collects this data by intercepting context + * switch events. Due to the measurement method, some caveats apply to the data + * returned: + * - There is no distinction between time spent in thread code or in the + * kernel. + * - Time spent in an interrupt is accounted towards the thread that has been + * interrupted. + * + * Retrieving the time accounting data for all threads is performed through the + * iterator returned by PKbegin(). To prevent the thread list from changing + * because of a context switch, keep the kernel paused while you traverse the + * iterator. + * + * To simplify post-processing, the list of thread data information accessible + * through the iterator always satisfies the following properties: + * - There is at least one item in the list. + * - The first item corresponds to the idle thread. + * - The threads are listed in creation order. + * - The relative order in which the items are iterated is deterministic and + * does not change even after a context switch. + * + * These properties allow to compute the difference between two thread data + * lists collected at different times in O(max(n,m)) complexity. + * + * \note This is a very low-level interface. For actual use, a more practical + * alternative is miosix::CPUProfiler, which provides a top-like display of the + * amount of CPU used by each thread in a given time interval. + */ +class CPUTimeCounter +{ +public: + /** + * Struct used to return the time counter data for a specific thread. + */ + struct Data + { + /// The thread the data belongs to + Thread *thread; + /// Cumulative amount of CPU time scheduled to the thread in ns + long long usedCpuTime = 0; + }; + + /** + * CPUTimeCounter thread data iterator type + */ + class iterator + { + public: + inline iterator operator++() + { + cur = cur->timeCounterData.next; + return *this; + } + inline iterator operator++(int) + { + iterator result = *this; + cur = cur->timeCounterData.next; + return result; + } + inline Data operator*() + { + Data res; + res.thread = cur; + res.usedCpuTime = cur->timeCounterData.usedCpuTime; + return res; + } + inline bool operator==(const iterator& rhs) { return cur==rhs.cur; } + inline bool operator!=(const iterator& rhs) { return cur!=rhs.cur; } + private: + friend class CPUTimeCounter; + Thread *cur; + iterator(Thread *cur) : cur(cur) {} + }; + + /** + * \returns the number of threads currently alive in the system. + * \warning This method is only provided for the purpose of reserving enough + * memory for collecting the time data for all threads. The value it + * returns may change at any time. + */ + static inline unsigned int getThreadCount() + { + return nThreads; + } + + /** + * \returns the begin iterator for the thread data. + */ + static iterator PKbegin() + { + return iterator(head); + } + + /** + * \returns the end iterator for the thread data. + */ + static iterator PKend() + { + return iterator(nullptr); + } + +private: + // The following methods are called from basic_scheduler to notify + // CPUTimeCounter of various events. + template<typename> friend class basic_scheduler; + + // CPUTimeCounter cannot be constructed + CPUTimeCounter() = delete; + + /** + * \internal + * Add the idle thread to the list of threads tracked by CPUTimeCounter. + * \param thread The idle thread. + */ + static inline void PKaddIdleThread(Thread *thread) + { + thread->timeCounterData.next = head; + head = thread; + if (!tail) + tail = thread; + nThreads++; + } + + /** + * \internal + * Add an item to the list of threads tracked by CPUTimeCounter. + * \param thread The thread to be added. + */ + static inline void PKaddThread(Thread *thread) + { + tail->timeCounterData.next = thread; + tail = thread; + if (!head) + head = thread; + nThreads++; + } + + /** + * \internal + * Update the list of threads tracked by CPUTimeCounter to remove dead + * threads. + */ + static void PKremoveDeadThreads(); + + /** + * \internal + * Notify that a context switch is about to happen. + * \returns The current time. + */ + static inline long long PKwillSwitchContext(); + + /** + * \internal + * Notify that a context switch has just happened. + * \param t The time of the context switch. + */ + static inline void PKdidSwitchContext(long long t); + + static Thread *head; ///< Head of the thread list + static Thread *tail; ///< Tail of the thread list + static volatile unsigned int nThreads; ///< Number of threads in the list +}; + +/** + * \} + */ + +} + +#endif // WITH_CPU_TIME_COUNTER + +#endif diff --git a/miosix/kernel/cpu_time_counter_private.h b/miosix/kernel/cpu_time_counter_private.h new file mode 100644 index 00000000..734308e7 --- /dev/null +++ b/miosix/kernel/cpu_time_counter_private.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2023 by Daniele Cattaneo * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#ifndef CPU_TIME_COUNTER_PRIVATE_H +#define CPU_TIME_COUNTER_PRIVATE_H + +#include "cpu_time_counter.h" + +#ifdef WITH_CPU_TIME_COUNTER + +namespace miosix { + +// Declared in kernel.cpp +extern volatile Thread *cur; + +long long CPUTimeCounter::PKwillSwitchContext() +{ + long long t = IRQgetTime(); + cur->timeCounterData.usedCpuTime += t - cur->timeCounterData.lastActivation; + return t; +} + +void CPUTimeCounter::PKdidSwitchContext(long long t) +{ + cur->timeCounterData.lastActivation = t; +} + +} + +#endif // WITH_CPU_TIME_COUNTER + +#endif diff --git a/miosix/kernel/cpu_time_counter_types.h b/miosix/kernel/cpu_time_counter_types.h new file mode 100644 index 00000000..e897667b --- /dev/null +++ b/miosix/kernel/cpu_time_counter_types.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * Copyright (C) 2023 by Daniele Cattaneo * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#ifndef CPU_TIME_COUNTER_TYPES_H +#define CPU_TIME_COUNTER_TYPES_H + +#include "config/miosix_settings.h" + +#ifdef WITH_CPU_TIME_COUNTER + +namespace miosix { + +class Thread; + +/** + * \internal + * Thread-local data structure used by the implementation of CPUTimeCounter + */ +struct CPUTimeCounterPrivateThreadData +{ + /// Timestamp of the last context change to this thread + long long lastActivation = 0; + /// Cumulative amount of CPU time used by this thread + long long usedCpuTime = 0; + /// Next thread in the thread list used by CPUTimeCounter + Thread *next = nullptr; +}; + +} + +#endif // WITH_CPU_TIME_COUNTER + +#endif diff --git a/miosix/kernel/kernel.h b/miosix/kernel/kernel.h index 9c80f856..58ece03c 100755 --- a/miosix/kernel/kernel.h +++ b/miosix/kernel/kernel.h @@ -32,6 +32,7 @@ #include "kernel/scheduler/sched_types.h" #include "stdlib_integration/libstdcpp_integration.h" #include "intrusive.h" +#include "cpu_time_counter_types.h" #include <cstdlib> #include <new> #include <functional> @@ -1025,6 +1026,9 @@ private: ///pointer is null unsigned int *userCtxsave; #endif //WITH_PROCESSES + #ifdef WITH_CPU_TIME_COUNTER + CPUTimeCounterPrivateThreadData timeCounterData; + #endif //WITH_CPU_TIME_COUNTER //friend functions //Needs access to watermark, ctxsave @@ -1062,6 +1066,10 @@ private: //Needs PKcreateUserspace(), setupUserspaceContext(), switchToUserspace() friend class Process; #endif //WITH_PROCESSES + #ifdef WITH_CPU_TIME_COUNTER + //Needs access to timeCounterData + friend class CPUTimeCounter; + #endif //WITH_CPU_TIME_COUNTER }; /** diff --git a/miosix/kernel/scheduler/scheduler.h b/miosix/kernel/scheduler/scheduler.h index cb4043ed..ed67a16c 100755 --- a/miosix/kernel/scheduler/scheduler.h +++ b/miosix/kernel/scheduler/scheduler.h @@ -32,6 +32,7 @@ #include "kernel/scheduler/priority/priority_scheduler.h" #include "kernel/scheduler/control/control_scheduler.h" #include "kernel/scheduler/edf/edf_scheduler.h" +#include "kernel/cpu_time_counter_private.h" namespace miosix { @@ -67,7 +68,12 @@ public: */ static bool PKaddThread(Thread *thread, Priority priority) { - return T::PKaddThread(thread,priority); + bool res = T::PKaddThread(thread,priority); + #ifdef WITH_CPU_TIME_COUNTER + if (res) + CPUTimeCounter::PKaddThread(thread); + #endif + return res; } /** @@ -91,6 +97,9 @@ public: */ static void PKremoveDeadThreads() { + #ifdef WITH_CPU_TIME_COUNTER + CPUTimeCounter::PKremoveDeadThreads(); + #endif T::PKremoveDeadThreads(); } @@ -138,6 +147,9 @@ public: */ static void IRQsetIdleThread(Thread *idleThread) { + #ifdef WITH_CPU_TIME_COUNTER + CPUTimeCounter::PKaddIdleThread(idleThread); + #endif return T::IRQsetIdleThread(idleThread); } @@ -167,7 +179,14 @@ public: */ static unsigned int IRQfindNextThread() { - return T::IRQfindNextThread(); + #ifdef WITH_CPU_TIME_COUNTER + long long t = CPUTimeCounter::PKwillSwitchContext(); + #endif + unsigned int res = T::IRQfindNextThread(); + #ifdef WITH_CPU_TIME_COUNTER + CPUTimeCounter::PKdidSwitchContext(t); + #endif + return res; } /** diff --git a/miosix/miosix.h b/miosix/miosix.h index 8fc8e39a..0a7de362 100644 --- a/miosix/miosix.h +++ b/miosix/miosix.h @@ -38,6 +38,7 @@ #include <kernel/kernel.h> #include <kernel/sync.h> #include <kernel/queue.h> +#include <kernel/cpu_time_counter.h> /* Utilities */ #include <util/util.h> /* Settings */ -- GitLab