From e2da7ee5e9732ec0d1962b7d74737b8ef5463a9e Mon Sep 17 00:00:00 2001 From: Kshitij Sisodia Date: Mon, 14 Feb 2022 11:22:58 +0000 Subject: 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 --- source/profiler/CMakeLists.txt | 40 +++++ source/profiler/Profiler.cc | 281 +++++++++++++++++++++++++++++++++++ source/profiler/include/Profiler.hpp | 135 +++++++++++++++++ 3 files changed, 456 insertions(+) create mode 100644 source/profiler/CMakeLists.txt create mode 100644 source/profiler/Profiler.cc create mode 100644 source/profiler/include/Profiler.hpp (limited to 'source/profiler') 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 + +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& 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(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(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 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 +#include +#include + +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 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; + + /* A map for string identifiable profiling series. */ + using ProfilingMap = std::map; + + /** + * @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& 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 */ -- cgit v1.2.1