MLECO-2970: Moving Profiler out as a CMake target.

Profiler is a stand alone static lib that will depend on
log and hal targets.

Change-Id: Ibbff289c6760982f54ae278d95a054e73db018c8
diff --git a/source/profiler/CMakeLists.txt b/source/profiler/CMakeLists.txt
new file mode 100644
index 0000000..f70e86d
--- /dev/null
+++ b/source/profiler/CMakeLists.txt
@@ -0,0 +1,40 @@
+#----------------------------------------------------------------------------
+#  Copyright (c) 2022 Arm Limited. All rights reserved.
+#  SPDX-License-Identifier: Apache-2.0
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#----------------------------------------------------------------------------
+
+#######################################################
+# Profiling library                                   #
+#######################################################
+
+project(profiler
+        DESCRIPTION "Profiling API for applications."
+        LANGUAGES C CXX)
+
+add_library(profiler STATIC)
+
+target_sources(profiler
+        PRIVATE
+        Profiler.cc)
+
+target_include_directories(profiler PUBLIC include)
+
+# Profiling API depends on the logging interface and the HAL library.
+target_link_libraries(profiler PRIVATE log hal)
+
+message(STATUS "*******************************************************")
+message(STATUS "Library                                : " profiler)
+message(STATUS "CMAKE_SYSTEM_PROCESSOR                 : " ${CMAKE_SYSTEM_PROCESSOR})
+message(STATUS "*******************************************************")
diff --git a/source/profiler/Profiler.cc b/source/profiler/Profiler.cc
new file mode 100644
index 0000000..efbc64d
--- /dev/null
+++ b/source/profiler/Profiler.cc
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2022 Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Profiler.hpp"
+#include "log_macros.h"
+
+#include <cstring>
+
+namespace arm {
+namespace app {
+    Profiler::Profiler(hal_platform* platform, const char* name = "Unknown")
+    : m_name(name)
+    {
+        if (platform && platform->inited) {
+            this->m_pPlatform = platform;
+            this->Reset();
+        } else {
+            printf_err("Profiler %s initialised with invalid platform\n",
+                this->m_name.c_str());
+        }
+    }
+
+    bool Profiler::StartProfiling(const char* name)
+    {
+        if (name) {
+            this->SetName(name);
+        }
+        if (this->m_pPlatform && !this->m_started) {
+            this->m_pPlatform->timer->reset();
+            this->m_tstampSt = this->m_pPlatform->timer->start_profiling();
+            this->m_started = true;
+            return true;
+        }
+        printf_err("Failed to start profiler %s\n", this->m_name.c_str());
+        return false;
+    }
+
+    bool Profiler::StopProfiling()
+    {
+        if (this->m_pPlatform && this->m_started) {
+            this->m_tstampEnd = this->m_pPlatform->timer->stop_profiling();
+            this->m_started = false;
+
+            this->AddProfilingUnit(this->m_tstampSt, this->m_tstampEnd, this->m_name);
+
+            return true;
+        }
+        printf_err("Failed to stop profiler %s\n", this->m_name.c_str());
+        return false;
+    }
+
+    bool Profiler::StopProfilingAndReset()
+    {
+        if (this->StopProfiling()) {
+            this->Reset();
+            return true;
+        }
+        printf_err("Failed to stop profiler %s\n", this->m_name.c_str());
+        return false;
+    }
+
+    void Profiler::Reset()
+    {
+        this->m_started = false;
+        this->m_series.clear();
+        memset(&this->m_tstampSt, 0, sizeof(this->m_tstampSt));
+        memset(&this->m_tstampEnd, 0, sizeof(this->m_tstampEnd));
+    }
+
+    void calcProfilingStat(uint64_t currentValue,
+                           Statistics& data,
+                           uint32_t samples)
+    {
+        data.total += currentValue;
+        data.min = std::min(data.min, currentValue);
+        data.max = std::max(data.max, currentValue);
+        data.avrg = ((double)data.total / samples);
+    }
+
+    void Profiler::GetAllResultsAndReset(std::vector<ProfileResult>& results)
+    {
+        for (const auto& item: this->m_series) {
+            auto name = item.first;
+            ProfilingSeries series = item.second;
+            ProfileResult result{};
+            result.name = item.first;
+            result.samplesNum = series.size();
+
+            Statistics AXI0_RD {
+                .name = "NPU AXI0_RD_DATA_BEAT_RECEIVED",
+                .unit = "beats",
+                .total = 0,
+                .avrg = 0.0,
+                .min = series[0].axi0writes,
+                .max = 0
+            };
+            Statistics AXI0_WR {
+                    .name = "NPU AXI0_WR_DATA_BEAT_WRITTEN",
+                    .unit = "beats",
+                    .total = 0,
+                    .avrg = 0.0,
+                    .min = series[0].axi0reads,
+                    .max = 0
+            };
+            Statistics AXI1_RD {
+                    .name = "NPU AXI1_RD_DATA_BEAT_RECEIVED",
+                    .unit = "beats",
+                    .total = 0,
+                    .avrg = 0.0,
+                    .min = series[0].axi1reads,
+                    .max = 0
+            };
+            Statistics NPU_ACTIVE {
+                    .name = "NPU ACTIVE",
+                    .unit = "cycles",
+                    .total = 0,
+                    .avrg = 0.0,
+                    .min = series[0].activeNpuCycles,
+                    .max = 0
+            };
+            Statistics NPU_IDLE {
+                    .name = "NPU IDLE",
+                    .unit = "cycles",
+                    .total = 0,
+                    .avrg = 0.0,
+                    .min = series[0].idleNpuCycles,
+                    .max = 0
+            };
+            Statistics NPU_Total {
+                    .name = "NPU TOTAL",
+                    .unit = "cycles",
+                    .total = 0,
+                    .avrg = 0.0,
+                    .min = series[0].npuCycles,
+                    .max = 0,
+            };
+#if defined(CPU_PROFILE_ENABLED)
+            Statistics CPU_ACTIVE {
+                    .name = "CPU ACTIVE",
+                    .unit = "cycles (approx)",
+                    .total = 0,
+                    .avrg = 0.0,
+                    .min = series[0].cpuCycles - NPU_ACTIVE.min,
+                    .max = 0
+            };
+            Statistics TIME {
+                    .name = "Time",
+                    .unit = "ms",
+                    .total = 0,
+                    .avrg = 0.0,
+                    .min = static_cast<uint64_t>(series[0].time),
+                    .max = 0
+            };
+#endif
+            for(ProfilingUnit& unit: series){
+
+                calcProfilingStat(unit.npuCycles,
+                                  NPU_Total, result.samplesNum);
+
+                calcProfilingStat(unit.activeNpuCycles,
+                                  NPU_ACTIVE, result.samplesNum);
+
+                calcProfilingStat(unit.idleNpuCycles,
+                                  NPU_IDLE, result.samplesNum);
+
+                calcProfilingStat(unit.axi0writes,
+                                  AXI0_WR, result.samplesNum);
+
+                calcProfilingStat(unit.axi0reads,
+                                  AXI0_RD, result.samplesNum);
+
+                calcProfilingStat(unit.axi1reads,
+                                  AXI1_RD, result.samplesNum);
+#if defined(CPU_PROFILE_ENABLED)
+                calcProfilingStat(static_cast<uint64_t>(unit.time),
+                                  TIME, result.samplesNum);
+
+                calcProfilingStat(unit.cpuCycles - unit.activeNpuCycles,
+                                  CPU_ACTIVE, result.samplesNum);
+#endif
+            }
+            result.data.emplace_back(AXI0_RD);
+            result.data.emplace_back(AXI0_WR);
+            result.data.emplace_back(AXI1_RD);
+            result.data.emplace_back(NPU_ACTIVE);
+            result.data.emplace_back(NPU_IDLE);
+            result.data.emplace_back(NPU_Total);
+#if defined(CPU_PROFILE_ENABLED)
+            result.data.emplace_back(CPU_ACTIVE);
+            result.data.emplace_back(TIME);
+#endif
+        results.emplace_back(result);
+        }
+        this->Reset();
+    }
+
+    void printStatisticsHeader(uint32_t samplesNum) {
+        info("Number of samples: %" PRIu32 "\n", samplesNum);
+        info("%s\n", "Total / Avg./ Min / Max");
+    }
+
+    void Profiler::PrintProfilingResult(bool printFullStat) {
+        std::vector<ProfileResult> results{};
+        GetAllResultsAndReset(results);
+        for(ProfileResult& result: results) {
+            info("Profile for %s:\n", result.name.c_str());
+
+            if (printFullStat) {
+                printStatisticsHeader(result.samplesNum);
+            }
+
+            for (Statistics &stat: result.data) {
+                if (printFullStat) {
+                    info("%s %s: %" PRIu64 "/ %.0f / %" PRIu64 " / %" PRIu64 " \n",
+                         stat.name.c_str(), stat.unit.c_str(),
+                         stat.total, stat.avrg, stat.min, stat.max);
+                } else {
+                    info("%s %s: %.0f\n", stat.name.c_str(), stat.unit.c_str(), stat.avrg);
+                }
+            }
+        }
+    }
+
+    void Profiler::SetName(const char* str)
+    {
+        this->m_name = std::string(str);
+    }
+
+    void Profiler::AddProfilingUnit(time_counter start, time_counter end,
+                                    const std::string& name)
+    {
+        if (!this->m_pPlatform) {
+            printf_err("Invalid platform\n");
+            return;
+        }
+
+        platform_timer * timer = this->m_pPlatform->timer;
+
+        struct ProfilingUnit unit;
+
+        if (timer->cap.npu_cycles && timer->get_npu_cycles_diff)
+        {
+            const size_t size = 6;
+            uint64_t pmuCounters[size] = {0};
+            /* 6 values: total cc, active cc, idle cc, axi0 read, axi0 write, axi1 read*/
+            if (0 == timer->get_npu_cycles_diff(&start, &end, pmuCounters, size)) {
+                unit.npuCycles = pmuCounters[0];
+                unit.activeNpuCycles = pmuCounters[1];
+                unit.idleNpuCycles = pmuCounters[2];
+                unit.axi0reads = pmuCounters[3];
+                unit.axi0writes = pmuCounters[4];
+                unit.axi1reads = pmuCounters[5];
+            }
+        }
+
+        if (timer->cap.cpu_cycles && timer->get_cpu_cycle_diff) {
+            unit.cpuCycles = timer->get_cpu_cycle_diff(&start, &end);
+        }
+
+        if (timer->cap.duration_ms && timer->get_duration_ms) {
+            unit.time = timer->get_duration_ms(&start, &end);
+        }
+
+        this->m_series[name].emplace_back(unit);
+    }
+
+} /* namespace app */
+} /* namespace arm */
diff --git a/source/profiler/include/Profiler.hpp b/source/profiler/include/Profiler.hpp
new file mode 100644
index 0000000..503d805
--- /dev/null
+++ b/source/profiler/include/Profiler.hpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2022 Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef APP_PROFILER_HPP
+#define APP_PROFILER_HPP
+
+#include "hal.h"
+
+#include <string>
+#include <map>
+#include <vector>
+
+namespace arm {
+namespace app {
+
+    /** Statistics for a profiling metric. */
+    struct Statistics {
+        std::string name;
+        std::string unit;
+        std::uint64_t total;
+        double avrg;
+        std::uint64_t min;
+        std::uint64_t max;
+    };
+
+    /** Profiling results with calculated statistics. */
+    struct ProfileResult {
+        std::string name;
+        std::uint32_t samplesNum;
+        std::vector<Statistics> data;
+    };
+
+    /** A single profiling unit definition. */
+    struct ProfilingUnit {
+        uint64_t npuCycles = 0;
+        uint64_t activeNpuCycles = 0;
+        uint64_t idleNpuCycles = 0;
+        uint64_t axi0writes = 0;
+        uint64_t axi0reads = 0;
+        uint64_t axi1reads = 0;
+        uint64_t cpuCycles = 0;
+        time_t time = 0;
+    };
+
+    /* A collection of profiling units. */
+    using ProfilingSeries = std::vector<arm::app::ProfilingUnit>;
+
+    /* A map for string identifiable profiling series. */
+    using ProfilingMap = std::map<std::string, ProfilingSeries>;
+
+    /**
+     * @brief   A very simple profiler example using the platform timer
+     *          implementation.
+     */
+    class Profiler {
+    public:
+        /**
+         * @brief       Constructor for profiler.
+         * @param[in]   platform   Pointer to a valid, initialised hal platform.
+         * @param[in]   name       A friendly name for this profiler.
+         **/
+        Profiler(hal_platform* platform, const char* name);
+
+        /** Block the default constructor. */
+        Profiler() = delete;
+
+        /** Default destructor. */
+        ~Profiler() = default;
+
+        /** @brief  Start profiling => get starting time-stamp. */
+        bool StartProfiling(const char* name = nullptr);
+
+        /** @brief  Stop profiling => get the ending time-stamp. */
+        bool StopProfiling();
+
+        /** @brief  Stops the profiling and internally resets the
+         *          platform timers. */
+        bool StopProfilingAndReset();
+
+        /** @brief  Reset the platform timers. */
+        void Reset();
+
+        /**
+         * @brief   Collects profiling results statistics and resets the profiler.
+         **/
+        void GetAllResultsAndReset(std::vector<ProfileResult>& results);
+
+        /**
+         * @brief   Prints collected profiling results and resets the profiler.
+         **/
+        void PrintProfilingResult(bool printFullStat = false);
+
+        /** @brief Set the profiler name. */
+        void SetName(const char* str);
+
+    private:
+        ProfilingMap    m_series;                /* Profiling series map. */
+        time_counter    m_tstampSt{};            /* Container for a current starting timestamp. */
+        time_counter    m_tstampEnd{};           /* Container for a current ending timestamp. */
+        hal_platform *  m_pPlatform = nullptr;   /* Platform pointer - to get the timer. */
+
+        bool            m_started = false;       /* Indicates profiler has been started. */
+
+        std::string     m_name;                  /* Name given to this profiler. */
+
+        /**
+         * @brief       Appends the profiling unit computed by the "start" and
+         *              "end" timestamps to the profiling series identified by
+         *              the name provided.
+         * @param[in]   start   Starting time-stamp.
+         * @param[in]   end     Ending time-stamp.
+         * @param[in]   name    Name for the profiling unit series to be
+         *                      appended to.
+         **/
+        void AddProfilingUnit(time_counter start, time_counter end,
+                              const std::string& name);
+    };
+
+} /* namespace app */
+} /* namespace arm */
+
+#endif /* APP_PROFILER_HPP */