feat add profiling

This commit is contained in:
tqcq
2024-03-16 22:56:10 +08:00
parent e05c1f4894
commit 719fecd4bc
30 changed files with 4673 additions and 1 deletions

View File

@@ -0,0 +1,114 @@
PROJECT(cppuprofile DESCRIPTION "CppProfiling library")
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
SET( CMAKE_USE_RELATIVE_PATHS ON)
IF(CMAKE_COMPILER_IS_GNUCXX)
ADD_DEFINITIONS( -std=c++0x )
ADD_DEFINITIONS( -D_GLIBCXX_USE_NANOSLEEP )
ENDIF()
IF(WIN32)
add_compile_options(/W4)
ELSE()
add_compile_options(-Wall -Werror)
ENDIF()
IF(BUILD_SHARED_LIBS)
IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0)
ADD_COMPILE_DEFINITIONS(_UPROFILE_BUILD_SHARED)
ELSE()
ADD_DEFINITIONS(-D_UPROFILE_BUILD_SHARED)
ENDIF()
ENDIF()
IF(PROFILE_ENABLED)
IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0)
ADD_COMPILE_DEFINITIONS(PROFILE_ON)
ADD_DEFINITIONS( -DPROFILE_ON )
ELSE()
ADD_DEFINITIONS(-DPROFILE_ON)
ENDIF()
ENDIF()
SET(UProfile_PUBLIC_HEADERS
api.h
uprofile.h
timestampunit.h
igpumonitor.h
)
SET(UProfile_IMPL
uprofile.cpp
uprofileimpl.cpp
util/timer.cpp
util/cpumonitor.cpp
)
IF(GPU_MONITOR_NVIDIA)
LIST(APPEND UProfile_PUBLIC_HEADERS monitors/nvidiamonitor.h)
LIST(APPEND UProfile_IMPL monitors/nvidiamonitor.cpp)
ENDIF()
SET(UProfile_HEADERS
${UProfile_PUBLIC_HEADERS}
uprofileimpl.h
util/timer.h
util/cpumonitor.h
)
SET(UProfile_SRCS
${UProfile_HEADERS}
${UProfile_IMPL}
)
SET(LIBRARY_NAME ${PROJECT_NAME})
ADD_LIBRARY(${LIBRARY_NAME} ${UProfile_SRCS}) # STATIC or SHARED are determined through BUILD_SHARED_LIBS flag
IF(BUILD_SHARED_LIBS AND UNIX)
# Hide symbols not explicitly tagged for export from the shared library
TARGET_COMPILE_OPTIONS(${LIBRARY_NAME} PRIVATE "-fvisibility=hidden")
ENDIF()
# Specify the include directories exported by this library
TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
IF(UNIX)
TARGET_LINK_LIBRARIES(${LIBRARY_NAME} pthread)
ENDIF()
# Set specific pkg-config variables
SET(PKG_CONFIG_LIBDIR
"\${prefix}/lib"
)
SET(PKG_CONFIG_INCLUDEDIR
"\${prefix}/include/${LIBRARY_NAME}"
)
SET(PKG_CONFIG_LIBS
"-L\${libdir} -l${LIBRARY_NAME}"
)
SET(PKG_CONFIG_CFLAGS
"-I\${includedir}"
)
# Generate the pkg-config file
CONFIGURE_FILE(
"${CMAKE_CURRENT_SOURCE_DIR}/pkg-config.pc.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
)
# Install library project files
INSTALL(FILES ${UProfile_PUBLIC_HEADERS}
DESTINATION include/${LIBRARY_NAME}
)
INSTALL(TARGETS ${LIBRARY_NAME}
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
DESTINATION lib/pkgconfig
)

View File

@@ -0,0 +1,31 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2023 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#pragma once
// UPROFAPI is used to export public API functions from the DLL / shared library.
#if defined(_UPROFILE_BUILD_SHARED)
#if defined(_WIN32)
/* Build as a Win32 DLL */
#define UPROFAPI __declspec(dllexport)
#elif defined(__linux__)
/* Build as a shared library */
#define UPROFAPI __attribute__((visibility("default")))
#endif // if defined(_UPROFILE_BUILD_SHARED)
#elif defined(UPROFILE_DLL)
#if defined(_WIN32)
/* Call uprofile as a Win32 DLL */
#define UPROFAPI __declspec(dllimport)
#endif // if defined(_WIN32)
#endif // if defined(UPROFILE_DLL)
#if !defined(UPROFAPI)
#define UPROFAPI
#endif

View File

@@ -0,0 +1,44 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2023 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#ifndef IGPUMONITOR_H_
#define IGPUMONITOR_H_
namespace uprofile
{
/**
* Interface to implement for monitoring GPU usage and memory
*
* No generic abstraction of GPU metrics exists
* on Linux nor Windows. So specific IGPUMonitor class should
* be defined to retrieve metrics from GPU vendor (Nvidia, AMD, Broadcom
* for RPI...)
*/
class IGPUMonitor
{
public:
virtual ~IGPUMonitor() {}
// Start monitoring
virtual void start(int period) = 0;
// Stop monitoring
virtual void stop() = 0;
// Return if monitor is currently watching data
virtual bool watching() const = 0;
// Usage should be in percentage
virtual float getUsage() const = 0;
// usedMem and totalMem should be returned as KiB
virtual void getMemory(int& usedMem, int& totalMem) const = 0;
};
}
#endif /* IGPUMONITOR_H_ */

View File

@@ -0,0 +1,171 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2023 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#include "nvidiamonitor.h"
#include <algorithm>
#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <sstream>
#include <string.h>
#if defined(__linux__)
#include <sys/wait.h>
#include <unistd.h>
#endif
const string errorMsg = "Failed to monitor nvidia-smi process";
#if defined(__linux__)
int read_nvidia_smi_stdout(int fd, string& gpuUsage, string& usedMem, string& totalMem)
{
string line;
while (line.find('\n') == string::npos) { // full line read
char buffer[4096];
ssize_t count = read(fd, buffer, sizeof(buffer)); // if child process crashes, we gonna be blocked here forever
if (count == -1) {
return errno;
} else if (count > 0) { // there is something to read
line += string(buffer, count);
}
}
// Remove colon to have only spaces and use istringstream
auto noSpaceEnd = remove(line.begin(), line.end(), ',');
if (noSpaceEnd == line.end()) { // output trace does not have comma so something went wrong with the command
return ENODATA;
}
line.erase(noSpaceEnd, line.end());
std::istringstream ss(line);
ss >> gpuUsage >> usedMem >> totalMem;
return 0;
}
#endif
uprofile::NvidiaMonitor::NvidiaMonitor()
{
}
uprofile::NvidiaMonitor::~NvidiaMonitor()
{
stop();
}
void uprofile::NvidiaMonitor::start(int period)
{
watchGPU(period);
}
void uprofile::NvidiaMonitor::stop()
{
abortWatchGPU();
}
float uprofile::NvidiaMonitor::getUsage() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_gpuUsage;
}
void uprofile::NvidiaMonitor::getMemory(int& usedMem, int& totalMem) const
{
std::lock_guard<std::mutex> lock(m_mutex);
usedMem = m_usedMem;
totalMem = m_totalMem;
}
void uprofile::NvidiaMonitor::watchGPU(int period)
{
if (m_watching) {
return;
}
#if defined(__linux__)
char* args[5];
args[0] = (char*)"/usr/bin/nvidia-smi";
string period_arg = "-lms=" + to_string(period); // lms stands for continuous watching
args[1] = (char*)period_arg.c_str();
args[2] = (char*)"--query-gpu=utilization.gpu,memory.used,memory.total";
args[3] = (char*)"--format=csv,noheader,nounits";
args[4] = NULL;
string output;
int pipes[2];
// Create the pipe
if (pipe(pipes) == -1) {
cerr << errorMsg << ": pipe creation failed" << endl;
return;
}
// Create a child process for calling nvidia-smi
pid_t pid = fork();
switch (pid) {
case -1: /* Error */
cerr << errorMsg << ": process fork failed" << endl;
return;
case 0: /* We are in the child process */
while ((dup2(pipes[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {
}
close(pipes[1]);
close(pipes[0]);
execv(args[0], args);
cerr << "Failed to execute '" << args[0] << "': " << strerror(errno) << endl; /* execl doesn't return unless there's an error */
exit(1);
default: /* We are in the parent process */
int stdout_fd = pipes[0];
// Start a thread to retrieve the child process stdout
m_watching = true;
m_watcherThread = unique_ptr<std::thread>(new thread([stdout_fd, pid, this]() {
while (watching()) {
string gpuUsage, usedMem, totalMem;
// if the child process crashes, an error is raised here and threads ends up
int err = read_nvidia_smi_stdout(stdout_fd, gpuUsage, usedMem, totalMem);
if (err != 0) {
cerr << errorMsg << ": read_error = " << strerror(err) << endl;
m_mutex.lock();
m_watching = false;
m_mutex.unlock();
break;
}
m_mutex.lock();
m_gpuUsage = !gpuUsage.empty() ? stof(gpuUsage) : 0.f;
m_usedMem = !usedMem.empty() ? stoi(usedMem) * 1024 : 0; // MiB to KiB
m_totalMem = !totalMem.empty() ? stoi(totalMem) * 1024 : 0; // MiB to KiB
m_mutex.unlock();
}
}));
}
#else
cerr << errorMsg << endl;
#endif
}
void uprofile::NvidiaMonitor::abortWatchGPU()
{
#if defined(__linux__)
if (m_watcherThread) {
m_mutex.lock();
m_watching = false;
m_mutex.unlock();
m_watcherThread->join();
m_watcherThread.reset();
}
#endif
}
bool uprofile::NvidiaMonitor::watching() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_watching;
}

View File

@@ -0,0 +1,49 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2023 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#ifndef NVIDIAMONITOR_H_
#define NVIDIAMONITOR_H_
#include "api.h"
#include "igpumonitor.h"
#include <mutex>
#include <string>
#include <thread>
#include <vector>
using namespace std;
namespace uprofile
{
class NvidiaMonitor : public IGPUMonitor
{
public:
UPROFAPI explicit NvidiaMonitor();
UPROFAPI virtual ~NvidiaMonitor();
UPROFAPI void start(int period) override;
UPROFAPI void stop() override;
UPROFAPI bool watching() const override;
UPROFAPI float getUsage() const override;
UPROFAPI void getMemory(int& usedMem, int& totalMem) const override;
private:
void watchGPU(int period);
void abortWatchGPU();
mutable std::mutex m_mutex;
std::unique_ptr<std::thread> m_watcherThread;
bool m_watching = false;
int m_totalMem = 0;
int m_usedMem = 0;
float m_gpuUsage = 0.f;
};
}
#endif /* NVIDIAMONITOR_H_ */

View File

@@ -0,0 +1,8 @@
Name: ${PROJECT_NAME}
Description: ${PROJECT_DESCRIPTION}
Version: ${PROJECT_VERSION}
prefix=${CMAKE_INSTALL_PREFIX}
includedir=${PKG_CONFIG_INCLUDEDIR}
libdir=${PKG_CONFIG_LIBDIR}
Libs: ${PKG_CONFIG_LIBS}
Cflags: ${PKG_CONFIG_CFLAGS}

View File

@@ -0,0 +1,23 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#ifndef TIMESTAMP_UNIT_H_
#define TIMESTAMP_UNIT_H_
namespace uprofile
{
enum class TimestampUnit {
EPOCH_TIME, // Time since epoch
UPTIME // Time since boot
};
}
#endif /* TIMESTAMP_UNIT_H_ */

View File

@@ -0,0 +1,113 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#include <chrono>
#include <fstream>
#include <iostream>
#include <sstream>
#include "uprofile.h"
#include "uprofileimpl.h"
using namespace std::chrono;
#ifdef PROFILE_ON
#define UPROFILE_INSTANCE_CALL(func, ...) \
UProfileImpl::getInstance()->func(__VA_ARGS__);
#define UPROFILE_INSTANCE_CALL_RETURN(func, ...) \
return UProfileImpl::getInstance()->func(__VA_ARGS__);
#define UPROFILE_DESTROY_INSTANCE() \
UProfileImpl::destroyInstance();
#else
#define UPROFILE_INSTANCE_CALL(func, ...) (void)0;
#define UPROFILE_INSTANCE_CALL_RETURN(func, ...) \
return {}
#define UPROFILE_DESTROY_INSTANCE() (void)0;
#endif
namespace uprofile
{
void start(const char* file)
{
UPROFILE_INSTANCE_CALL(start, file);
}
void stop()
{
UPROFILE_INSTANCE_CALL(stop);
UPROFILE_DESTROY_INSTANCE();
}
void addGPUMonitor(IGPUMonitor* monitor)
{
UPROFILE_INSTANCE_CALL(addGPUMonitor, monitor);
}
void removeGPUMonitor()
{
UPROFILE_INSTANCE_CALL(removeGPUMonitor);
}
void setTimestampUnit(TimestampUnit tsUnit)
{
UPROFILE_INSTANCE_CALL(setTimestampUnit, tsUnit);
}
void timeBegin(const std::string& step)
{
UPROFILE_INSTANCE_CALL(timeBegin, step);
}
void timeEnd(const std::string& step)
{
UPROFILE_INSTANCE_CALL(timeEnd, step);
}
void startProcessMemoryMonitoring(int period)
{
UPROFILE_INSTANCE_CALL(startProcessMemoryMonitoring, period);
}
void startSystemMemoryMonitoring(int period)
{
UPROFILE_INSTANCE_CALL(startSystemMemoryMonitoring, period);
}
void startCPUUsageMonitoring(int period)
{
UPROFILE_INSTANCE_CALL(startCPUUsageMonitoring, period);
}
void startGPUUsageMonitoring(int period)
{
UPROFILE_INSTANCE_CALL(startGPUUsageMonitoring, period);
}
void startGPUMemoryMonitoring(int period)
{
UPROFILE_INSTANCE_CALL(startGPUMemoryMonitoring, period);
}
void getProcessMemory(int& rss, int& shared)
{
UPROFILE_INSTANCE_CALL(getProcessMemory, rss, shared);
}
void getSystemMemory(int& totalMem, int& availableMem, int& freeMem)
{
UPROFILE_INSTANCE_CALL(getSystemMemory, totalMem, availableMem, freeMem);
}
std::vector<float> getInstantCpuUsage()
{
UPROFILE_INSTANCE_CALL_RETURN(getInstantCpuUsage);
}
}

View File

@@ -0,0 +1,142 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#ifndef UPROFILE_H_
#define UPROFILE_H_
#include <fstream>
#include <list>
#include <map>
#include <string>
#include <vector>
#include "api.h"
#include "igpumonitor.h"
#include "timestampunit.h"
/**
* @defgroup uprofile Functions for monitoring system metrics
* @{
*/
namespace uprofile
{
/**
* @ingroup uprofile
* @brief Start profiling to periodically record monitored events to a file
* @param file: file path where events will be saved
*/
UPROFAPI void start(const char* file);
/**
* @ingroup uprofile
* @brief Stop all monitorings
*
* Note: stopping profiler without prior call to start() has no effect
*/
UPROFAPI void stop();
/**
* @ingroup uprofile
* @brief Inject a GPUMonitor object that will be responsible for monitoring GPU metrics (usage and memory)
* @param monitor: custom GPUMonitor object
*
* Note: uprofile takes ownership of the passed object.
*/
UPROFAPI void addGPUMonitor(IGPUMonitor* monitor);
/**
* @ingroup uprofile
* @brief Destroy injected GPUMonitor object
*/
UPROFAPI void removeGPUMonitor();
/**
* @ingroup uprofile
* @brief Change the timestamp unit to record profiling metrics
*
* It should be called before calling start() method.
*
* Note: default value is EPOCH_TIME
*/
UPROFAPI void setTimestampUnit(TimestampUnit tsUnit);
/**
* @ingroup uprofile
* @brief Start monitoring the execution time of the given event
* @param title: event key
*/
UPROFAPI void timeBegin(const std::string& title);
/**
* @ingroup uprofile
* @brief Stop monitoring the execution time of the given event
*
* The library computes the duration for the given event and saves it into the report file.
*
* If no timeBegin() has been called with the given title, the call is ignored.
*/
UPROFAPI void timeEnd(const std::string& title);
/**
* @ingroup uprofile
* @brief Start monitoring of the memory used by the process
* @param period: period between two memory dump (in ms)
*/
UPROFAPI void startProcessMemoryMonitoring(int period);
/**
* @ingroup uprofile
* @brief Start monitoring of the global memory used on the system
* @param period: period between two memory dump (in ms)
*/
UPROFAPI void startSystemMemoryMonitoring(int period);
/**
* @ingroup uprofile
* @brief Start monitoring of the usage percentage of each CPU
* @param period: period between two cpu usage dump (in ms)
*/
UPROFAPI void startCPUUsageMonitoring(int period);
/**
* @ingroup uprofile
* @brief Start monitoring of the usage of the GPU
* @param period: period between two gpu usage dump (in ms)
*/
UPROFAPI void startGPUUsageMonitoring(int period);
/**
* @ingroup uprofile
* @brief Start monitoring of the usage of the GPU memory
* @param period: period between two gpu usage dump (in ms)
*/
UPROFAPI void startGPUMemoryMonitoring(int period);
/**
* @ingroup uprofile
* @brief memory used by the current process
*/
UPROFAPI void getProcessMemory(int& rss, int& shared);
/**
* @ingroup uprofile
* @brief dump global system memory
*/
UPROFAPI void getSystemMemory(int& totalMem, int& availableMem, int& freeMem);
/**
* @ingroup uprofile
* @brief get usage of all cpu cores
* @return vector holding the usage percentage of each CPU core
*/
UPROFAPI std::vector<float> getInstantCpuUsage();
}
/** @} */ // end of uprofile group
#endif /* UPROFILE_H_ */

View File

@@ -0,0 +1,353 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#include <chrono>
#include <fstream>
#include <iostream>
#include <sstream>
#if defined(__linux__)
#include <sys/sysinfo.h>
#include <unistd.h>
#elif defined(_WIN32)
#include <windows.h>
#elif defined(__APPLE__)
#include <sys/resource.h>
#include <sys/time.h>
#endif
#include "uprofileimpl.h"
using namespace std::chrono;
namespace uprofile {
UProfileImpl *UProfileImpl::m_uprofiler = NULL;
UProfileImpl::UProfileImpl() : m_tsUnit(TimestampUnit::EPOCH_TIME), m_gpuMonitor(NULL) {}
UProfileImpl *
UProfileImpl::getInstance()
{
if (!m_uprofiler) { m_uprofiler = new UProfileImpl; }
return m_uprofiler;
}
void
UProfileImpl::destroyInstance()
{
delete m_uprofiler;
m_uprofiler = NULL;
}
UProfileImpl::~UProfileImpl() { removeGPUMonitor(); }
void
UProfileImpl::start(const char *file)
{
m_file.open(file, std::ios::out);
if (!m_file.is_open()) { std::cerr << "Failed to open file: " << file << std::endl; }
}
void
UProfileImpl::addGPUMonitor(IGPUMonitor *monitor)
{
if (!monitor) {
std::cerr << "Invalid GPU Monitor" << std::endl;
return;
}
if (!m_gpuMonitor) { removeGPUMonitor(); }
m_gpuMonitor = monitor;
}
void
UProfileImpl::removeGPUMonitor()
{
delete m_gpuMonitor;
m_gpuMonitor = NULL;
}
void
UProfileImpl::timeBegin(const std::string &title)
{
std::lock_guard<std::mutex> guard(m_stepsMutex);
m_steps.insert(make_pair(title, getTimestamp()));
}
void
UProfileImpl::timeEnd(const std::string &title)
{
unsigned long long beginTimestamp = 0;
// Find step in the map
m_stepsMutex.lock();
auto it = m_steps.find(title);
bool found = it != m_steps.end();
if (found) {
beginTimestamp = (*it).second;
m_steps.erase(it);
}
m_stepsMutex.unlock();
if (found) {
write(ProfilingType::TIME_EXEC, {std::to_string(beginTimestamp), title});
} else {
write(ProfilingType::TIME_EVENT, {title});
}
}
void
UProfileImpl::startProcessMemoryMonitoring(int period)
{
m_processMemoryMonitorTimer.setInterval(period);
m_processMemoryMonitorTimer.setTimeout([=]() { dumpProcessMemory(); });
m_processMemoryMonitorTimer.start();
}
void
UProfileImpl::startSystemMemoryMonitoring(int period)
{
m_systemMemoryMonitorTimer.setInterval(period);
m_systemMemoryMonitorTimer.setTimeout([=]() { dumpSystemMemory(); });
m_systemMemoryMonitorTimer.start();
}
void
UProfileImpl::startCPUUsageMonitoring(int period)
{
m_cpuMonitorTimer.setInterval(period);
m_cpuMonitorTimer.setTimeout([=]() { dumpCpuUsage(); });
m_cpuMonitorTimer.start();
}
void
UProfileImpl::startGPUUsageMonitoring(int period)
{
if (!m_gpuMonitor) {
std::cerr << "Cannot monitor GPU usage: no GPUMonitor set!" << std::endl;
return;
}
m_gpuMonitor->start(period);
m_gpuUsageMonitorTimer.setInterval(period);
m_gpuUsageMonitorTimer.setTimeout([=]() { dumpGpuUsage(); });
m_gpuUsageMonitorTimer.start();
}
void
UProfileImpl::startGPUMemoryMonitoring(int period)
{
if (!m_gpuMonitor) {
std::cerr << "Cannot monitor GPU memory: no GPUMonitor set!" << std::endl;
return;
}
m_gpuMonitor->start(period);
m_gpuMemoryMonitorTimer.setInterval(period);
m_gpuMemoryMonitorTimer.setTimeout([=]() { dumpGpuMemory(); });
m_gpuMemoryMonitorTimer.start();
}
void
UProfileImpl::dumpProcessMemory()
{
int rss = 0, shared = 0;
getProcessMemory(rss, shared);
write(ProfilingType::PROCESS_MEMORY, {std::to_string(rss), std::to_string(shared)});
}
void
UProfileImpl::dumpSystemMemory()
{
int total = 0, available = 0, free = 0;
getSystemMemory(total, available, free);
write(ProfilingType::SYSTEM_MEMORY, {std::to_string(total), std::to_string(available), std::to_string(free)});
}
void
UProfileImpl::dumpCpuUsage()
{
vector<float> cpuLoads = m_cpuMonitor.getUsage();
for (size_t index = 0; index < cpuLoads.size(); ++index) {
write(ProfilingType::CPU, {std::to_string(index), std::to_string(cpuLoads.at(index))});
}
}
void
UProfileImpl::dumpGpuUsage()
{
if (!m_gpuMonitor || !m_gpuMonitor->watching()) { return; }
float usage = m_gpuMonitor->getUsage();
write(ProfilingType::GPU_USAGE, {std::to_string(usage)});
}
void
UProfileImpl::dumpGpuMemory()
{
if (!m_gpuMonitor || !m_gpuMonitor->watching()) { return; }
int usedMem, totalMem;
m_gpuMonitor->getMemory(usedMem, totalMem);
write(ProfilingType::GPU_MEMORY, {std::to_string(usedMem), std::to_string(totalMem)});
}
vector<float>
UProfileImpl::getInstantCpuUsage()
{
// To get instaneous CPU usage, we should wait at least one unit between two polling (aka: 100 ms)
m_cpuMonitor.getUsage();
this_thread::sleep_for(std::chrono::milliseconds(100));
return m_cpuMonitor.getUsage();
}
void
UProfileImpl::stop()
{
m_processMemoryMonitorTimer.stop();
m_systemMemoryMonitorTimer.stop();
m_cpuMonitorTimer.stop();
m_gpuUsageMonitorTimer.stop();
m_gpuMemoryMonitorTimer.stop();
if (m_gpuMonitor) { m_gpuMonitor->stop(); }
m_file.close();
}
void
UProfileImpl::setTimestampUnit(TimestampUnit tsUnit)
{
m_tsUnit = tsUnit;
}
void
UProfileImpl::write(ProfilingType type, const std::list<std::string> &data)
{
std::lock_guard<std::mutex> guard(m_fileMutex);
if (m_file.is_open()) {
const char csvSeparator = ';';
std::string strType;
switch (type) {
case ProfilingType::TIME_EXEC:
strType = "time_exec";
break;
case ProfilingType::TIME_EVENT:
strType = "time_event";
break;
case ProfilingType::PROCESS_MEMORY:
strType = "proc_mem";
break;
case ProfilingType::SYSTEM_MEMORY:
strType = "sys_mem";
break;
case ProfilingType::CPU:
strType = "cpu";
break;
case ProfilingType::GPU_USAGE:
strType = "gpu";
break;
case ProfilingType::GPU_MEMORY:
strType = "gpu_mem";
break;
default:
strType = "undefined";
break;
}
m_file << strType.c_str() << csvSeparator << getTimestamp();
for (auto it = data.cbegin(); it != data.cend(); ++it) { m_file << csvSeparator << *it; }
m_file << "\n";
m_file.flush();
}
}
unsigned long long
UProfileImpl::getTimestamp() const
{
return (m_tsUnit == TimestampUnit::EPOCH_TIME ? getEpochTime() : getTimeSinceBoot());
}
unsigned long long
UProfileImpl::getEpochTime()
{
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}
unsigned long long
UProfileImpl::getTimeSinceBoot()
{
#if defined(__linux__)
double uptime_seconds;
if (std::ifstream("/proc/uptime", std::ios::in) >> uptime_seconds) {
return static_cast<unsigned long long>(uptime_seconds * 1000.0);
}
return 0;
#elif defined(_WIN32)
return GetTickCount64();
#else
return 0;
#endif
}
void
UProfileImpl::getSystemMemory(int &totalMem, int &availableMem, int &freeMem)
{
#if defined(__linux__)
// /proc/meminfo returns the dump here:
// MemTotal: 515164 kB
// MemFree: 7348 kB
// MemAvailable: 7348 kB
ifstream meminfo("/proc/meminfo");
string line;
while (std::getline(meminfo, line)) {
if (line.find("MemTotal") != std::string::npos) {
stringstream ls(line);
ls.ignore(256, ' ');
ls >> totalMem;
} else if (line.find("MemFree") != std::string::npos) {
stringstream ls(line);
ls.ignore(256, ' ');
ls >> freeMem;
} else if (line.find("MemAvailable") != std::string::npos) {
{
stringstream ls(line);
ls.ignore(256, ' ');
ls >> availableMem;
break;
}
}
}
#endif
}
void
UProfileImpl::getProcessMemory(int &rss, int &shared)
{
#if defined(__linux__)
int tSize = 0, resident = 0, share = 0;
ifstream buffer("/proc/self/statm");
buffer >> tSize >> resident >> share;
buffer.close();
long page_size_kb = getpagesize() / 1024;
rss = resident * page_size_kb;
shared = share * page_size_kb;
#elif defined(__APPLE__)
struct rusage usage;
if (0 == getrusage(RUSAGE_SELF, &usage)) {
rss = usage.ru_maxrss;
shared = usage.ru_ixrss;
}
#endif
}
}// namespace uprofile

View File

@@ -0,0 +1,91 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#ifndef UPROFILEIMPL_H_
#define UPROFILEIMPL_H_
#include "igpumonitor.h"
#include "timestampunit.h"
#include "util/cpumonitor.h"
#include "util/timer.h"
#include <fstream>
#include <list>
#include <map>
#include <mutex>
#include <string>
namespace uprofile
{
class UProfileImpl
{
public:
enum class ProfilingType {
TIME_EXEC,
TIME_EVENT,
PROCESS_MEMORY,
SYSTEM_MEMORY,
CPU,
GPU_USAGE,
GPU_MEMORY
};
static UProfileImpl* getInstance();
static void destroyInstance();
virtual ~UProfileImpl();
// Implementation
void start(const char* file);
void stop();
void addGPUMonitor(IGPUMonitor* monitor);
void removeGPUMonitor();
void setTimestampUnit(TimestampUnit tsUnit);
void timeBegin(const std::string& title);
void timeEnd(const std::string& title);
void startProcessMemoryMonitoring(int period);
void startSystemMemoryMonitoring(int period);
void startCPUUsageMonitoring(int period);
void startGPUUsageMonitoring(int period);
void startGPUMemoryMonitoring(int period);
void getProcessMemory(int& rss, int& shared);
void getSystemMemory(int& totalMem, int& availableMem, int& freeMem);
vector<float> getInstantCpuUsage();
private:
static UProfileImpl* m_uprofiler;
UProfileImpl();
void write(ProfilingType type, const std::list<std::string>& data);
unsigned long long getTimestamp() const;
static unsigned long long getEpochTime();
static unsigned long long getTimeSinceBoot();
void dumpCpuUsage();
void dumpProcessMemory();
void dumpSystemMemory();
void dumpGpuUsage();
void dumpGpuMemory();
TimestampUnit m_tsUnit;
std::map<std::string, unsigned long long> m_steps; // Store steps (title, start time)
std::ofstream m_file;
Timer m_processMemoryMonitorTimer;
Timer m_systemMemoryMonitorTimer;
Timer m_cpuMonitorTimer;
Timer m_gpuUsageMonitorTimer;
Timer m_gpuMemoryMonitorTimer;
CpuMonitor m_cpuMonitor;
IGPUMonitor* m_gpuMonitor;
std::mutex m_fileMutex;
std::mutex m_stepsMutex;
};
}
#endif /* UPROFILEIMPL_H_ */

View File

@@ -0,0 +1,100 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#include "cpumonitor.h"
#include <fstream>
#include <sstream>
using namespace std;
uprofile::CpuMonitor::CpuMonitor() :
m_nbCpus(getNumberOfCPUCores())
{
m_lastIdleTimes.resize(m_nbCpus, 0);
m_lastTotalTimes.resize(m_nbCpus, 0);
}
uprofile::CpuMonitor::~CpuMonitor()
{
}
size_t uprofile::CpuMonitor::getNumberOfCPUCores()
{
size_t nbCores = 0;
#if defined(__linux__)
ifstream meminfo("/proc/cpuinfo");
string str;
while (getline(meminfo, str)) {
if (str.rfind("processor", 0) == 0) {
nbCores++;
}
}
#endif
return nbCores;
}
void uprofile::CpuMonitor::extractCpuTimes(const string& cpuInfo, size_t& idleTime, size_t& totalTime)
{
// Remove 'cpu<index' word and
// extract the idle time and sum all other times
size_t spacePos = cpuInfo.find(' ');
if (spacePos == string::npos) {
return;
}
stringstream times(cpuInfo.substr(spacePos + 1));
int index = 0;
for (size_t time; times >> time; ++index) {
if (index == 3) { // idle time i s the 4th param
idleTime = time;
}
totalTime += time;
}
}
vector<float> uprofile::CpuMonitor::getUsage()
{
vector<float> usages;
#if defined(__linux__)
ifstream procStat("/proc/stat");
// /proc/stat dumps the following info:
// user nice system idle iowait irq softirq
// cpu 2255 34 2290 22625563 6290 127 456
// cpu0 1132 34 1441 11311718 3675 127 438
// cpu1 1123 0 849 11313845 2614 0 18
// ...
// Each numbers represents the amount of time the CPU has spent performing
// different kind of work
for (size_t cpuIndex = 0; cpuIndex < m_nbCpus; ++cpuIndex) {
string cpuName("cpu");
cpuName += to_string(cpuIndex);
// Look in /proc/stack the CPU info
string line;
while (getline(procStat, line)) {
if (line.find(cpuName) != std::string::npos) {
size_t idleTime = 0, totalTime = 0;
extractCpuTimes(line, idleTime, totalTime);
// To compute CPU load, we compute the time the CPU has been idle since the last read.
float cpuLoad = 100.0 * (1.0 - (float)(idleTime - m_lastIdleTimes[cpuIndex]) / (totalTime - m_lastTotalTimes[cpuIndex]));
usages.push_back(cpuLoad);
// Save the times value for the next read
m_lastIdleTimes[cpuIndex] = idleTime;
m_lastTotalTimes[cpuIndex] = totalTime;
break;
}
}
}
#endif
return usages;
}

View File

@@ -0,0 +1,41 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#ifndef CPUMONITOR_H_
#define CPUMONITOR_H_
#include <string>
#include <vector>
using namespace std;
namespace uprofile
{
class CpuMonitor
{
public:
explicit CpuMonitor();
virtual ~CpuMonitor();
vector<float> getUsage();
private:
static size_t getNumberOfCPUCores();
static void extractCpuTimes(const string& cpuInfo, size_t& idleTime, size_t& totalTime);
// Store the last idle and total time for each CPU
vector<size_t> m_lastIdleTimes;
vector<size_t> m_lastTotalTimes;
size_t m_nbCpus;
};
}
#endif /* CPUMONITOR_H_ */

View File

@@ -0,0 +1,64 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#include "timer.h"
Timer::Timer(int interval) :
m_th(NULL),
m_interval(interval)
{
}
Timer::~Timer()
{
stop();
}
void Timer::setInterval(int interval)
{
m_interval = interval;
}
void Timer::setTimeout(const std::function<void(void)>& timeout)
{
m_timeout = timeout;
}
bool Timer::isRunning()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_running;
}
void Timer::start()
{
if (m_interval > 0 && m_th == NULL) {
m_running = true;
m_th = new thread([=]() {
while (isRunning()) {
this_thread::sleep_for(chrono::milliseconds(m_interval));
if (m_timeout) {
m_timeout();
}
}
});
}
}
void Timer::stop()
{
m_mutex.lock();
m_running = false;
m_mutex.unlock();
if (m_th) {
m_th->join();
delete m_th;
m_th = NULL;
}
}

View File

@@ -0,0 +1,41 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2022 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
#ifndef TIMER_H_
#define TIMER_H_
#include <chrono>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
class Timer
{
public:
explicit Timer(int interval /* ms */ = 0);
virtual ~Timer();
void setTimeout(const function<void(void)>& timeout);
void setInterval(int interval);
void start();
void stop();
bool isRunning();
private:
thread* m_th;
bool m_running;
int m_interval;
std::mutex m_mutex;
std::function<void(void)> m_timeout;
};
#endif // TIMER_H_