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