diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e5ce4ab55ce84e8bef1219142651bffada6c26d..48f2c0c6012a432f1d0def948593732344adb579 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,8 +50,9 @@ test-das6: - module load cuda/12.2.1 - module list script: - - cmake -S . -B build ${CMAKE_OPTIONS} -DPMT_BUILD_NVML=ON -DPMT_BUILD_BINARY=ON + - cmake -S . -B build ${CMAKE_OPTIONS} -DPMT_BUILD_NVML=ON -DPMT_BUILD_BINARY=ON -DPMT_BUILD_TESTING=ON - make -C build + - CTEST_OUTPUT_ON_FAILURE=1 make -C build test - $(pwd)/build/bin/PMT --name rapl -- sleep 3 - $(pwd)/build/bin/PMT --name nvml -- sleep 3 - PYTHONPATH=$PYTHONPATH:$(pwd)/build/python/site-packages python3 ${CI_PROJECT_DIR}/python/demo.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 426b10fc9356c24cd3dedfe31954c5c66271ae10..6da8654f1ba001a1ced2b739b04a04d308575422 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,14 @@ find_package(Threads REQUIRED) # Enable compile commands (needed for iwyu) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# Option to enable testing +option(PMT_BUILD_TESTING "" OFF) + +if(${PMT_BUILD_TESTING}) + enable_testing() + add_subdirectory(test) +endif() + # Options to disable implementations # that require external dependencies option(PMT_BUILD_CRAY "" OFF) @@ -124,11 +132,19 @@ set_target_properties( # Replicate header file structure in binary directory file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/pmt") file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/pmt/common") -file(CREATE_LINK "${CMAKE_CURRENT_SOURCE_DIR}/common/PMT.h" - "${PROJECT_BINARY_DIR}/pmt/common/PMT.h" SYMBOLIC) +list(APPEND PMT_HEADER_FILES "common/PMT.h") +list(APPEND PMT_HEADER_FILES "common/State.h") +list(APPEND PMT_HEADER_FILES "common/Timestamp.h") foreach(PMT_HEADER_FILE ${PMT_HEADER_FILES}) set(LINK_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${PMT_HEADER_FILE}") - get_filename_component(LINK_DST ${PMT_HEADER_FILE} NAME) + # Strip the subdirectory from the header file path, + # except for header files from the 'common' subdirectory. + string(FIND "${PMT_HEADER_FILE}" "common" PREFIX_POSITION) + if(PREFIX_POSITION EQUAL 0) + set(LINK_DST ${PMT_HEADER_FILE}) + else() + get_filename_component(LINK_DST ${PMT_HEADER_FILE} NAME) + endif() file(CREATE_LINK ${LINK_SRC} "${PROJECT_BINARY_DIR}/pmt/${LINK_DST}" SYMBOLIC) endforeach() diff --git a/amdsmi/AMDSMIImpl.cpp b/amdsmi/AMDSMIImpl.cpp index 5910fed3a8f2c4df1b4777ca78ccaf1e2f558bb1..231561b0fd1b2122b6869b3fc3393d5ea7086e1c 100644 --- a/amdsmi/AMDSMIImpl.cpp +++ b/amdsmi/AMDSMIImpl.cpp @@ -2,18 +2,19 @@ #include <vector> #include "AMDSMIImpl.h" +#include "common/Exception.h" namespace { -class AmdsmiError : public std::exception { +class AmdsmiError : public pmt::Exception { public: explicit AmdsmiError(amdsmi_status_t result, const char *file, int line) : result_(result), file_(file), line_(line) {} const char *what() const noexcept override { - std::ostringstream oss; - oss << "AMDSMI call failed with status: " << result_ << " in " << file_ - << " at line " << line_; - message_ = oss.str(); + std::ostringstream message; + message << "AMDSMI call failed with status: " << result_ << " in " << file_ + << " at line " << line_; + message_ = message.str(); return message_.c_str(); } @@ -78,34 +79,16 @@ namespace pmt::amdsmi { AMDSMIImpl::AMDSMIImpl(const unsigned device_number) { processor_ = Initialize(device_number); - - state_previous_ = GetAMDSMIState(); - state_previous_.joules_ = 0; } AMDSMIImpl::~AMDSMIImpl() { checkAmdsmiCall(amdsmi_shut_down()); } -AMDSMIState::operator State() { +State AMDSMIImpl::GetState() { State state; - state.timestamp_ = timestamp_; - state.name_[0] = "device"; - state.joules_[0] = joules_; - state.watt_[0] = watt_; - return state; -} - -AMDSMIState AMDSMIImpl::GetAMDSMIState() { - AMDSMIState state; state.timestamp_ = GetTime(); - state.watt_ = GetPower(processor_); - state.joules_ = state_previous_.joules_; - const float watt = (state.watt_ + state_previous_.watt_) / 2; - const double duration = seconds(state_previous_.timestamp_, state.timestamp_); - state.joules_ += watt * duration; - state_previous_ = state; + state.name_[0] = "device"; + state.watt_[0] = GetPower(processor_); return state; } -State AMDSMIImpl::GetState() { return GetAMDSMIState(); } - } // end namespace pmt::amdsmi \ No newline at end of file diff --git a/amdsmi/AMDSMIImpl.h b/amdsmi/AMDSMIImpl.h index a8622e52421335703fdba495a54d15350979db71..3a9dbebd285fbd0a9730d1e37e9e82f22b5fa6a2 100644 --- a/amdsmi/AMDSMIImpl.h +++ b/amdsmi/AMDSMIImpl.h @@ -4,14 +4,6 @@ namespace pmt::amdsmi { -class AMDSMIState { - public: - operator State(); - Timestamp timestamp_; - double watt_ = 0; - double joules_ = 0; -}; - class AMDSMIImpl : public AMDSMI { public: AMDSMIImpl(const unsigned device_number); @@ -25,9 +17,6 @@ class AMDSMIImpl : public AMDSMI { } amdsmi_processor_handle processor_; - - AMDSMIState state_previous_; - AMDSMIState GetAMDSMIState(); }; } // end namespace pmt::amdsmi \ No newline at end of file diff --git a/bin/main.cpp b/bin/main.cpp index 87cd22cd6336a868c64716327788271704773f0b..b139881caec527067bd614d451136c8d3fd6c3a8 100644 --- a/bin/main.cpp +++ b/bin/main.cpp @@ -64,7 +64,7 @@ void run(pmt::PMT& sensor, const std::vector<std::string>& command) { std::cout << std::endl; } } else { - std::stringstream command_stream; + std::ostringstream command_stream; for (int i = 1; i < command.size(); i++) { if (i > 1) { command_stream << " "; diff --git a/cmake/modules/AddSensor.cmake b/cmake/modules/AddSensor.cmake index d86fb0054e4b737083c6116d937e25bcb2070428..d450ad73e6dfca5f3c32b489b01b005e9c5dbaa6 100644 --- a/cmake/modules/AddSensor.cmake +++ b/cmake/modules/AddSensor.cmake @@ -26,6 +26,8 @@ macro(add_sensor) target_include_directories(${LIBRARY_NAME} PRIVATE ${ADD_SENSOR_INCLUDE_DIRECTORIES}) endif() + get_filename_component(PMT_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) + target_include_directories(${LIBRARY_NAME} PRIVATE ${PMT_INCLUDE_DIR}) # Link to the global scope install(FILES ${ADD_SENSOR_HEADER} DESTINATION include/pmt) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 0041079a7934a5ecefb0583a0affa1909695e9ea..1a1f44989c0b8099010d2f2f59c5f991979cd910 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,6 +1,6 @@ project(pmt-common) -add_library(${PROJECT_NAME} OBJECT PMT.cpp) +add_library(${PROJECT_NAME} OBJECT PMT.cpp State.cpp) target_include_directories(${PROJECT_NAME} PRIVATE ${PMT_INCLUDE_DIR}) install(FILES PMT.h DESTINATION include/pmt/common) diff --git a/common/Exception.h b/common/Exception.h new file mode 100644 index 0000000000000000000000000000000000000000..3bfcba8e1bff0672c25dd109fd5c9f6acc9e8495 --- /dev/null +++ b/common/Exception.h @@ -0,0 +1,18 @@ +#ifndef PMT_EXCEPTION_H_ +#define PMT_EXCEPTION_H_ + +#include <exception> + +namespace pmt { +class Exception : public std::exception { + public: + explicit Exception(const char *message = "") : message_(message) {} + + const char *what() const noexcept override { return message_; } + + private: + const char *message_; +}; +} // namespace pmt + +#endif // PMT_EXCEPTION_H_ \ No newline at end of file diff --git a/common/PMT.cpp b/common/PMT.cpp index a209e25e70516076a2e5bc48695ad44d927761d2..1448aa58845a230b6e57a3887290d38830d8de6a 100644 --- a/common/PMT.cpp +++ b/common/PMT.cpp @@ -5,10 +5,13 @@ #include <cstdlib> #include <cstring> #include <iomanip> +#include <iostream> #include <pmt.h> +#include "common/Exception.h" #include "common/PMT.h" + namespace { template <typename T> bool isEqual(T x, T y) { @@ -36,9 +39,9 @@ double PMT::seconds(const Timestamp ×tamp) { } double PMT::seconds(const Timestamp &first, const Timestamp &second) { - return std::chrono::duration_cast<std::chrono::microseconds>((second - first)) + return std::chrono::duration_cast<std::chrono::microseconds>(second - first) .count() / - 1.e6; + 1e6; } double PMT::seconds(const State &first, const State &second) { @@ -64,42 +67,61 @@ unsigned int PMT::GetDumpInterval() { } } -std::string State::name(int i) { - assert(i < nr_measurements_); - return name_[i]; -} - -float State::joules(int i) { - assert(i < nr_measurements_); - return joules_[i]; -} - -float State::watts(int i) { - assert(i < nr_measurements_); - return watt_[i]; -} - void PMT::StartThread() { - SetMeasurementInterval(); + thread_mutex_.lock(); thread_ = std::thread([&] { - State state_previous = GetState(); - assert(state_previous.nr_measurements_ > 0); - state_latest_ = state_previous; + try { + state_latest_ = GetState(); + } catch (const pmt::Exception &e) { +#if defined(DEBUG) + std::cerr << "GetState(): " << e.what() << std::endl; +#endif + throw pmt::Exception("Could not read initial state."); + } if (dump_file_) { - DumpHeader(state_previous); + DumpHeader(state_latest_); } while (!thread_stop_) { std::this_thread::sleep_for( - std::chrono::milliseconds(GetMeasurementInterval())); - state_latest_ = GetState(); + std::chrono::milliseconds(measurement_interval_)); + + State state; + try { + state = GetState(); + } catch (const pmt::Exception &e) { +#if defined(DEBUG) + std::cerr << "GetState(): " << e.what() << std::endl; +#endif + continue; + } if (dump_file_ && - (1e3 * seconds(state_previous, state_latest_)) > GetDumpInterval()) { - Dump(state_latest_); - state_previous = state_latest_; + (1e3 * seconds(state_latest_, state)) > GetDumpInterval()) { + Dump(state); + } + + for (int i = 0; i < state.NrMeasurements(); i++) { + const double seconds_diff = seconds(state_latest_, state); + if (state.watt_[i] > 0) { + const double watt_average = + (state_latest_.watt_[i] + state.watt_[i]) / 2.f; + state.joules_[i] = + state_latest_.joules_[i] + watt_average * seconds_diff; + } else if (state.joules_[i] > 0) { + const double joules_diff = + state.joules_[i] - state_latest_.joules_[i]; + state.watt_[i] = joules_diff / seconds_diff; + } + } + + state_latest_ = state; + + if (!thread_started_) { + thread_mutex_.unlock(); + thread_started_ = true; } } }); @@ -159,39 +181,38 @@ void PMT::Mark(const State &state, const std::string &message) const { } } -void PMT::SetMeasurementInterval(unsigned int milliseconds) { - if (milliseconds > 0) { - measurement_interval_ = milliseconds; - } else { - unsigned int measurement_interval = 1; - State state_first; - State state_second; - do { - state_first = GetState(); - std::this_thread::sleep_for( - std::chrono::milliseconds(measurement_interval)); - state_second = GetState(); - if (!isEqual(state_first.watt_[0], state_second.watt_[0])) { - measurement_interval_ = measurement_interval; - return; - } else { - measurement_interval *= 2; - } - } while (measurement_interval < 1000); - } -} - Timestamp PMT::GetTime() { return std::chrono::system_clock::now(); } State PMT::Read() { const int measurement_interval = GetMeasurementInterval(); if (!thread_started_) { StartThread(); - thread_started_ = true; - std::this_thread::sleep_for( - std::chrono::milliseconds(measurement_interval)); + + unsigned int retries = 0; + + do { + std::this_thread::sleep_for( + std::chrono::milliseconds(measurement_interval)); + if (retries++ > kNrRetriesInitialization) { + throw pmt::Exception("Measurement thread failed to start"); + } + } while (!thread_mutex_.try_lock()); + + state_returned_ = state_latest_; + return state_latest_; } - return state_latest_; + + State state = state_latest_; + if (seconds(state_returned_, state) <= 0) { + state = state_returned_; + state.timestamp_ = GetTime(); + const double duration = seconds(state_returned_, state); + for (int i = 0; i < state.NrMeasurements(); i++) { + state.joules_[i] += state.watt_[i] * duration; + } + } + + return state_returned_ = state; } std::unique_ptr<PMT> Create(const std::string &name, @@ -285,9 +306,9 @@ std::unique_ptr<PMT> Create(const std::string &name, } #if defined(DEBUG) - std::stringstream error; - error << "Invalid or unavailable platform specified: " << name << std::endl; - throw std::runtime_error(error.str()); + std::ostringstream message; + message << "Invalid or unavailable platform specified: " << name << std::endl; + throw pmt::Exception(message.str().c_str()); #endif return Dummy::Create(); diff --git a/common/PMT.h b/common/PMT.h index 61e79f51eac96f0418898908cb3435a074495361..92003f28febfa000da57cc96751785c3e27ac849 100644 --- a/common/PMT.h +++ b/common/PMT.h @@ -1,7 +1,6 @@ -#ifndef PMT_COMMON_H_ -#define PMT_COMMON_H_ +#ifndef PMT_PMT_H_ +#define PMT_PMT_H_ -#include <chrono> #include <fstream> #include <memory> #include <mutex> @@ -9,44 +8,14 @@ #include <thread> #include <vector> +#include <pmt/common/State.h> +#include <pmt/common/Timestamp.h> + namespace pmt { const std::string kDumpFilenameVariable = "PMT_DUMP_FILE"; const std::string kDumpIntervalVariable = "PMT_DUMP_INTERVAL"; -using Timestamp = std::chrono::high_resolution_clock::time_point; - -class State { - public: - State &operator=(const State &state) { - timestamp_ = state.timestamp_; - nr_measurements_ = state.nr_measurements_; - name_ = state.name_; - joules_ = state.joules_; - watt_ = state.watt_; - return *this; - } - - State(int nr_measurements = 1) : nr_measurements_(nr_measurements) { - name_.resize(nr_measurements); - joules_.resize(nr_measurements); - watt_.resize(nr_measurements); - } - - int NrMeasurements() { return nr_measurements_; } - - Timestamp timestamp() { return timestamp_; } - std::string name(int i); - float joules(int i); - float watts(int i); - - Timestamp timestamp_; - int nr_measurements_; - std::vector<std::string> name_; - std::vector<float> joules_; - std::vector<float> watt_; -}; - class PMT { public: virtual ~PMT(); @@ -66,7 +35,9 @@ class PMT { virtual void Mark(const State &state, const std::string &message) const; - void SetMeasurementInterval(unsigned int milliseconds = 0); + void SetMeasurementInterval(unsigned int milliseconds) { + measurement_interval_ = milliseconds; + }; unsigned int GetMeasurementInterval() const { return measurement_interval_; }; // in milliseconds @@ -83,24 +54,31 @@ class PMT { Timestamp GetTime(); - private: - unsigned int measurement_interval_ = 100; // milliseconds + unsigned int measurement_interval_ = 1; // milliseconds + + // The last State returned by Read() + State state_returned_; - // The last state set by the thread + // The latest State returned by GetState() State state_latest_; - // This thread continuously call GetState to update state_latest_. It is + private: + // Wait up to this number of measurement intervals for the backend to + // initialize, which it signals by unlocking the thread_mutex_. + static constexpr int kNrRetriesInitialization = 10; + + // This thread continuously calls GetState to update state_latest_. It is // started automatically upon the first Read() call. std::thread thread_; volatile bool thread_started_ = false; volatile bool thread_stop_ = false; + mutable std::mutex thread_mutex_; void StartThread(); void StopThread(); void DumpHeader(const State &state); - protected: std::unique_ptr<std::ofstream> dump_file_ = nullptr; mutable std::mutex dump_file_mutex_; }; @@ -112,4 +90,4 @@ std::unique_ptr<PMT> Create(const std::string &name, std::ostream &operator<<(std::ostream &os, const pmt::Timestamp ×tamp); -#endif +#endif // PMT_PMT_H_ diff --git a/common/State.cpp b/common/State.cpp new file mode 100644 index 0000000000000000000000000000000000000000..59f248ff60c0df50823dc5862c1ab41a3e334dfb --- /dev/null +++ b/common/State.cpp @@ -0,0 +1,18 @@ +#include "State.h" + +namespace pmt { +std::string State::name(int i) const { + assert(i < nr_measurements_); + return name_[i]; +} + +float State::joules(int i) const { + assert(i < nr_measurements_); + return joules_[i]; +} + +float State::watts(int i) const { + assert(i < nr_measurements_); + return watt_[i]; +} +} // end namespace pmt \ No newline at end of file diff --git a/common/State.h b/common/State.h new file mode 100644 index 0000000000000000000000000000000000000000..60e2426ed387f3f4ed2aa188230ac0fb970b59c6 --- /dev/null +++ b/common/State.h @@ -0,0 +1,49 @@ +#ifndef PMT_STATE_H_ +#define PMT_STATE_H_ + +#include <cassert> +#include <string> +#include <vector> + +#include <pmt/common/Timestamp.h> + +namespace pmt { + +class State { + public: + State &operator=(const State &state) { + timestamp_ = state.timestamp_; + nr_measurements_ = state.nr_measurements_; + name_ = state.name_; + joules_ = state.joules_; + watt_ = state.watt_; + return *this; + } + + State(int nr_measurements = 1) : nr_measurements_(nr_measurements) { + name_.resize(nr_measurements); + joules_.resize(nr_measurements); + watt_.resize(nr_measurements); + for (int i = 0; i < nr_measurements; i++) { + name_[i] = ""; + joules_[i] = 0; + watt_[i] = 0; + } + } + + Timestamp timestamp() const { return timestamp_; } + int NrMeasurements() const { return nr_measurements_; } + std::string name(int i) const; + float joules(int i) const; + float watts(int i) const; + + Timestamp timestamp_; + int nr_measurements_; + std::vector<std::string> name_; + std::vector<float> joules_; + std::vector<float> watt_; +}; + +} // namespace pmt + +#endif // PMT_STATE_H_ \ No newline at end of file diff --git a/common/Timestamp.h b/common/Timestamp.h new file mode 100644 index 0000000000000000000000000000000000000000..90e3c93492d4283b034d078394cf6a2b0788b316 --- /dev/null +++ b/common/Timestamp.h @@ -0,0 +1,12 @@ +#ifndef PMT_TIMESTAMP_H_ +#define PMT_TIMESTAMP_H_ + +#include <chrono> + +namespace pmt { + +using Timestamp = std::chrono::high_resolution_clock::time_point; + +} // end namespace pmt + +#endif // PMT_TIMESTAMP_H_ diff --git a/common/cpu.h b/common/cpu.h index 470188d60f87b8c635ae923646ba15078ef524af..709cf7cc6142eae5695746f8d26857d20c990e12 100644 --- a/common/cpu.h +++ b/common/cpu.h @@ -10,6 +10,8 @@ #include <unistd.h> +#include "common/Exception.h" + namespace pmt::common { std::vector<int> get_active_cpus() { @@ -18,8 +20,7 @@ std::vector<int> get_active_cpus() { const int result = sched_getaffinity(0, sizeof(cpu_set), &cpu_set); if (result == -1) { - throw std::system_error(errno, std::generic_category(), - "sched_getaffinity"); + throw pmt::Exception("sched_getaffinity"); } const int n_cpus = sysconf(_SC_NPROCESSORS_ONLN); @@ -44,7 +45,9 @@ std::set<int> get_active_packages(const std::vector<int>& active_cpus) { std::ifstream file(path); if (!file.is_open()) { - throw std::runtime_error("Failed to open file: " + path); + std::ostringstream message; + message << "Failed to open file: " << path; + throw pmt::Exception(message.str().c_str()); } int socket_id; diff --git a/cray/Cray.cpp b/cray/Cray.cpp index 5456a8c311f6e08f9356bd8950e3a3012e5c071c..d691d77ac28c012a8b0dd4da997fd90aceb8f139 100644 --- a/cray/Cray.cpp +++ b/cray/Cray.cpp @@ -7,6 +7,7 @@ #include <ext/alloc_traits.h> +#include "common/Exception.h" #include "Cray.h" #include "FilenamesHelper.h" @@ -14,19 +15,19 @@ namespace { double GetPower(const std::string& filePath) { std::ifstream powerFile(filePath); if (!powerFile.is_open()) { - throw std::runtime_error("Failed to open power file"); + throw pmt::Exception("Failed to open power file"); } std::string line; if (!std::getline(powerFile, line)) { - throw std::runtime_error("Failed to read power value"); + throw pmt::Exception("Failed to read power value"); } double power; try { power = std::stod(line); } catch (const std::exception& e) { - throw std::runtime_error("Failed to parse power value"); + throw pmt::Exception("Failed to parse power value"); } powerFile.close(); @@ -46,9 +47,6 @@ class CrayImpl : public Cray { std::vector<std::string> filenames; std::string cray_pm_counters_path = "/sys/cray/pm_counters"; - Timestamp previous_timestamp_; - std::vector<CrayMeasurement> previous_measurements_; - // Mutex used to guard GetMeasurements() std::mutex mutex_; @@ -67,9 +65,6 @@ CrayImpl::CrayImpl() { #if defined(DEBUG) filenames_helper::PrintFilenames(filenames); #endif - - previous_timestamp_ = GetTime(); - previous_measurements_ = GetMeasurements(); } std::vector<CrayMeasurement> CrayImpl::GetMeasurements() { @@ -95,14 +90,11 @@ State CrayImpl::GetState() { State state(measurements.size()); state.timestamp_ = GetTime(); - const double duration = seconds(previous_timestamp_, state.timestamp_); + const double duration = seconds(state_latest_, state); for (size_t i = 0; i < measurements.size(); i++) { state.name_[i] = measurements[i].name; state.watt_[i] = measurements[i].watt; - const double watt = - (measurements[i].watt + previous_measurements_[i].watt) / 2; - state.joules_[i] += watt * duration; } return state; diff --git a/dummy/Dummy.cpp b/dummy/Dummy.cpp index 9a0544944ce8767c0d5c31c9505ead952cf45b05..bfbfabcd7f43899fbcd7a35756d3e267cf3bbefe 100644 --- a/dummy/Dummy.cpp +++ b/dummy/Dummy.cpp @@ -1,4 +1,3 @@ -#include <ext/alloc_traits.h> #include <vector> #include "Dummy.h" @@ -14,12 +13,48 @@ class DummyImpl : public Dummy { std::unique_ptr<Dummy> Dummy::Create() { return std::make_unique<DummyImpl>(); } +// Returns a State object containing: +// - timestamp: current system time +// - name[0]: "total" for accumulated values +// - name[1..n]: "sensor0".."sensorN" for individual sensors +// +// Operates in three modes: +// 1. Default: report zero power and energy (wattt_.size() == 0) +// +// 2. Fixed joules (fixed_joules_ == true): +// - Accumulates energy based on fixed power values +// - Skips accumulation on first measurement +// - joules[0]: sum of all sensors +// - joules[1..n]: individual sensor values +// +// 3. Fixed watts (fixed_joules_ == false): +// - Reports constant power values +// - watt[0]: sum of all sensors +// - watt[1..n]: individual sensor values State DummyImpl::GetState() { - State state; + State state(1 + watt_.size()); state.timestamp_ = PMT::GetTime(); - state.name_[0] = "none"; - state.joules_[0] = 0; - state.watt_[0] = 0; + state.name_[0] = "total"; + if (fixed_joules_) { + // Accumulate joules as if the device consumed watt_ watts over the previous + // measurement interval, skipping the first measurement where the previous + // timestamp is zero. + if (state_latest_.timestamp_.time_since_epoch().count()) { + for (size_t i = 0; i < watt_.size(); i++) { + state.name_[i] = "sensor" + std::to_string(i); + state.joules_[i + 1] = state_latest_.joules_[i + 1] + + watt_[i] * PMT::seconds(state_latest_, state); + state.joules_[0] += state.joules_[i + 1]; + } + } + } else { + // Use a fixed wattage + for (size_t i = 0; i < watt_.size(); i++) { + state.name_[i] = "sensor" + std::to_string(i); + state.watt_[0] += watt_[i]; + state.watt_[i + 1] = watt_[i]; + } + } return state; } diff --git a/dummy/Dummy.h b/dummy/Dummy.h index 4941ac5f911a10fd2b2e223452474f4a7851e176..9718c5c6baf0ba44297887107c444f3070b3770e 100644 --- a/dummy/Dummy.h +++ b/dummy/Dummy.h @@ -9,6 +9,15 @@ namespace pmt { class Dummy : public PMT { public: static std::unique_ptr<Dummy> Create(); + void UseFixedWatts(const std::vector<float>& watt) { watt_ = watt; } + void UseFixedJoules(const std::vector<float>& watt) { + fixed_joules_ = true; + watt_ = watt; + } + + protected: + bool fixed_joules_ = false; + std::vector<float> watt_; }; } // end namespace pmt diff --git a/likwid/LikwidImpl.cpp b/likwid/LikwidImpl.cpp index 43e233723d9285ca7ef30187b3357a2446f33c80..de317948ac2b5b8c61e38907a1e16ade38de7f7e 100644 --- a/likwid/LikwidImpl.cpp +++ b/likwid/LikwidImpl.cpp @@ -106,8 +106,6 @@ LikwidImpl::LikwidImpl(std::string event_group_name) } } } - - previous_time = GetTime(); } // end constructor LikwidImpl::~LikwidImpl() { @@ -118,22 +116,17 @@ LikwidImpl::~LikwidImpl() { State LikwidImpl::GetState() { State state(nr_groups + 1); + state.name_[0] = "total"; state.timestamp_ = GetTime(); - const double duration = seconds(state.timestamp_, state.timestamp_); - double total_watts = 0; + const double duration = seconds(state_latest_, state); std::vector<double> current_measurements = GetMeasurements(); for (int i = 0; i < nr_groups; i++) { - joulesTotal += current_measurements[i]; - state.joules_[i + 1] = current_measurements[i]; + state.name_[i] = "socket_" + std::to_string(i); state.watt_[i + 1] = current_measurements[i] / duration; - total_watts += state.watt_[i + 1]; + state.watt_[0] += state.watt_[i + 1]; } - state.joules_[0] = static_cast<float>(joulesTotal); - state.watt_[0] = total_watts; - previous_time = state.timestamp_; - return state; } diff --git a/likwid/LikwidImpl.h b/likwid/LikwidImpl.h index c82966e78604c1e50ecd4e825b7b0ae31358203a..c31c307024441ba8694745cd7cc3dd09a143c305 100644 --- a/likwid/LikwidImpl.h +++ b/likwid/LikwidImpl.h @@ -24,7 +24,6 @@ class LikwidImpl : public Likwid { std::string event_group; std::vector<double> GetMeasurements(); - Timestamp previous_time; int nr_groups, nr_events; int nr_threads_group; std::vector<int> relevant_event_ids; diff --git a/nvidia/NVIDIA.cpp b/nvidia/NVIDIA.cpp index 9479d8858fd2ee000383479f34624abe2f472491..f99a83e0ed8790bbf72a520491cc04464b15424f 100644 --- a/nvidia/NVIDIA.cpp +++ b/nvidia/NVIDIA.cpp @@ -16,13 +16,13 @@ inline void __checkCudaCall(cudaError_t result, const char *const func, const char *const file, int const line) { if (result != cudaSuccess) { - std::stringstream error; - error << "CUDA Error at " << file; - error << ":" << line; - error << " in function " << func; - error << ": " << cudaGetErrorString(result); - error << std::endl; - throw std::runtime_error(error.str()); + std::ostringstream message; + message << "CUDA Error at " << file; + message << ":" << line; + message << " in function " << func; + message << ": " << cudaGetErrorString(result); + message << std::endl; + throw std::runtime_error(message.str()); } } #endif diff --git a/nvml/NVMLImpl.cpp b/nvml/NVMLImpl.cpp index 1bfd60d8b4effdc4a97108c0dbae197022b706c6..5cac5a69eeb7a42566016742c465bb2a3cb3ec54 100644 --- a/nvml/NVMLImpl.cpp +++ b/nvml/NVMLImpl.cpp @@ -9,20 +9,10 @@ #include <ext/alloc_traits.h> #include "NVMLImpl.h" +#include "common/Exception.h" namespace pmt::nvml { -NVMLState::operator State() { - State state(measurements_.size()); - state.timestamp_ = timestamp_; - state.joules_[0] = joules_; - for (size_t i = 0; i < measurements_.size(); i++) { - state.name_[i] = measurements_[i].name_; - state.watt_[i] = measurements_[i].milliwatt_ / 1.e3; - } - return state; -} - NVMLImpl::NVMLImpl(int device_number) { const char *pmt_device = getenv("PMT_DEVICE"); device_number = pmt_device ? atoi(pmt_device) : device_number; @@ -54,13 +44,8 @@ void NVMLImpl::Initialize() { device_->getFieldValues(1, values); nr_scopes_ = 1 + (values[0].nvmlReturn == NVML_SUCCESS); #endif - - // Initialize the first timestamp - state_previous_.timestamp_ = GetTime(); } -NVMLImpl::~NVMLImpl() { stopped_ = true; } - #if defined(PMT_NVML_LEGACY_MODE) std::vector<NVMLMeasurement> NVMLImpl::GetMeasurements() { return {{.name_ = "gpu_average", @@ -103,44 +88,33 @@ std::vector<NVMLMeasurement> NVMLImpl::GetMeasurements() { } #endif -NVMLState NVMLImpl::GetNVMLState() { - if (stopped_) { - return state_previous_; +State NVMLImpl::GetState() { + std::vector<NVMLMeasurement> measurements; + try { + measurements = GetMeasurements(); + } catch (const ::nvml::Error &e) { + throw pmt::Exception(e.what()); } - NVMLState state; - try { - state.measurements_ = GetMeasurements(); + State state(measurements.size()); - // Default: use the instantaneous GPU power - // Grace Hopper: use the instantaneous module power -#if defined(PMT_NVML_LEGACY_MODE) - state.milliwatt_ = state.measurements_[0].milliwatt_; - state.timestamp_ = state.measurements_[0].timestamp_; + for (size_t i = 0; i < measurements.size(); i++) { + state.name_[i] = measurements[i].name_; + state.joules_[i] = 0; + state.watt_[i] = measurements[i].milliwatt_ / 1.e3; + } + +#if !defined(PMT_NVML_LEGACY_MODE) + // Default: use the instantaneous GPU power + // Grace Hopper: use the instantaneous module power + const unsigned int measurement_id = nr_scopes_ == 1 ? 0 : 2; + std::swap(state.name_[0], state.name_[measurement_id]); + std::swap(state.watt_[0], state.watt_[measurement_id]); + state.timestamp_ = measurements[measurement_id].timestamp_; #else - const unsigned int measurement_id = nr_scopes_ == 1 ? 0 : 2; - state.milliwatt_ = state.measurements_[measurement_id].milliwatt_; - state.timestamp_ = state.measurements_[measurement_id].timestamp_; + state.timestamp_ = measurements[0].timestamp_; #endif - // Set derived fields of state - const double duration = - seconds(state_previous_.timestamp_, state.timestamp_); - - if (state_previous_.joules_ > 0) { - const double watt = - (state.milliwatt_ + state_previous_.milliwatt_) / 2.e3; - state.joules_ = state_previous_.joules_ + watt * duration; - } else { - const double watt = state.milliwatt_ / 1.e3; - state.joules_ = watt * duration; - } - - state_previous_ = state; - } catch (std::runtime_error &e) { - return state_previous_; - } - return state; } } // end namespace pmt::nvml diff --git a/nvml/NVMLImpl.h b/nvml/NVMLImpl.h index 74b2ffd81b12959ca6ba37da3178408f7918fde2..46db4d1b3b6f4676dd2c2cf5b813d2580bd1a6db 100644 --- a/nvml/NVMLImpl.h +++ b/nvml/NVMLImpl.h @@ -18,10 +18,7 @@ struct NVMLMeasurement { class NVMLState { public: operator State(); - Timestamp timestamp_; std::vector<NVMLMeasurement> measurements_; - unsigned int milliwatt_ = 0; - unsigned int joules_ = 0; }; class NVMLImpl : public NVML { @@ -30,16 +27,13 @@ class NVMLImpl : public NVML { #if defined(PMT_NVML_CUDAWRAPPERS_API) NVMLImpl(cu::Device& device); #endif - ~NVMLImpl(); private: void Initialize(); - State GetState() override { return GetNVMLState(); } + State GetState() override; virtual const char* GetDumpFilename() override { return "/tmp/pmt_nvml.out"; } - NVMLState state_previous_; - NVMLState GetNVMLState(); std::vector<NVMLMeasurement> GetMeasurements(); #if not defined(PMT_NVML_LEGACY_MODE) @@ -47,7 +41,6 @@ class NVMLImpl : public NVML { const unsigned int kFieldIdPowerAverage = NVML_FI_DEV_POWER_AVERAGE; unsigned int nr_scopes_; #endif - bool stopped_ = false; std::unique_ptr<::nvml::Context> context_; std::unique_ptr<::nvml::Device> device_; diff --git a/powersensor3/PowerSensor3Impl.cpp b/powersensor3/PowerSensor3Impl.cpp index ff37be00df2bc30408e2f59dc7d8a6a2302d98a9..6d5f59affa9f7a96971fdb09db8a11c323102fe6 100644 --- a/powersensor3/PowerSensor3Impl.cpp +++ b/powersensor3/PowerSensor3Impl.cpp @@ -2,19 +2,6 @@ namespace pmt::powersensor3 { -PowerSensor3State::operator State() { - State state(::PowerSensor3::MAX_PAIRS); - state.timestamp_ = state_.timeAtRead; - - for (size_t i = 0; i < state.nr_measurements_; i++) { - state.name_[i] = pair_names_[i]; - state.watt_[i] = state_.voltage[i] * state_.current[i]; - state.joules_[i] = state_.consumedEnergy[i]; - } - - return state; -} - PowerSensor3Impl::PowerSensor3Impl(const char *device) : powersensor_(std::make_unique<::PowerSensor3::PowerSensor>(device)) { for (unsigned pair_id = 0; pair_id < ::PowerSensor3::MAX_PAIRS; pair_id++) { @@ -22,19 +9,18 @@ PowerSensor3Impl::PowerSensor3Impl(const char *device) } } -PowerSensor3Impl::~PowerSensor3Impl() { stopped_ = true; } +State PowerSensor3Impl::GetState() { + const ::PowerSensor3::State measurement = powersensor_->read(); -PowerSensor3State PowerSensor3Impl::GetPowerSensor3State() { - if (stopped_) { - return state_previous_; - } + State state(::PowerSensor3::MAX_PAIRS); + state.name_ = pair_names_; + state.timestamp_ = measurement.timeAtRead; - try { - const PowerSensor3State state{powersensor_->read(), pair_names_}; - state_previous_ = state; - return state; - } catch (std::runtime_error &e) { - return state_previous_; + for (size_t i = 0; i < state.nr_measurements_; i++) { + state.watt_[i] = measurement.voltage[i] * measurement.current[i]; + state.joules_[i] = measurement.consumedEnergy[i]; } + + return state; } } // end namespace pmt::powersensor3 diff --git a/powersensor3/PowerSensor3Impl.h b/powersensor3/PowerSensor3Impl.h index 731714a6bb249a2704be74329ab61b84f604db6f..b07813cb8b8604604e2e603d3b5ae1fc0b23ac61 100644 --- a/powersensor3/PowerSensor3Impl.h +++ b/powersensor3/PowerSensor3Impl.h @@ -8,30 +8,17 @@ namespace pmt::powersensor3 { -class PowerSensor3State { - public: - operator State(); - ::PowerSensor3::State state_; - std::vector<std::string> pair_names_; -}; - class PowerSensor3Impl : public PowerSensor3 { public: PowerSensor3Impl(const char *device); - ~PowerSensor3Impl(); private: - State GetState() override { return GetPowerSensor3State(); } + State GetState() override; virtual const char *GetDumpFilename() override { return "/tmp/pmt_powersensor3.out"; } - PowerSensor3State state_previous_; - PowerSensor3State GetPowerSensor3State(); - - bool stopped_ = false; - std::unique_ptr<::PowerSensor3::PowerSensor> powersensor_; std::vector<std::string> pair_names_; }; diff --git a/rapl/RaplImpl.cpp b/rapl/RaplImpl.cpp index 58ecba3cbeadcceda5373104a742d1d19dcf4ce3..05889f057de2712f85254f44c90c8997becfb340 100644 --- a/rapl/RaplImpl.cpp +++ b/rapl/RaplImpl.cpp @@ -40,16 +40,12 @@ namespace fs = std::filesystem; namespace pmt::rapl { -RaplImpl::RaplImpl() { - Init(); - previous_timestamp_ = GetTime(); - previous_measurements_ = GetMeasurements(); -} +RaplImpl::RaplImpl() { Init(); } RaplImpl::~RaplImpl() { std::lock_guard<std::mutex> lock(mutex_); } std::string GetBasename(int package_id) { - std::stringstream basename; + std::ostringstream basename; basename << "/sys/class/powercap/intel-rapl:" << package_id; return basename.str(); } @@ -91,7 +87,7 @@ void RaplImpl::Init() { try { rapl_counters_.emplace_back(rapl_dir); } catch (std::system_error& e) { - std::stringstream message; + std::ostringstream message; message << "OS error: " << e.what(); std::cerr << message.str() << std::endl; if (e.code().value() == EACCES) { @@ -118,29 +114,16 @@ State RaplImpl::GetState() { State state(1 + measurements.size()); state.timestamp_ = GetTime(); state.name_[0] = "total"; - state.joules_[0] = 0; - state.watt_[0] = 0; for (std::size_t i = 0; i < measurements.size(); i++) { - const std::string name = measurements[i].name; - const std::size_t ujoules_now = measurements[i].ujoules; - const std::size_t ujoules_previous = previous_measurements_[i].ujoules; - const double duration = seconds(previous_timestamp_, state.timestamp_); - const float joules_diff = (ujoules_now - ujoules_previous) * 1e-6; - const float watt = joules_diff / duration; - state.name_[i + 1] = name; - state.joules_[i + 1] = ujoules_now * 1e-6; - state.watt_[i + 1] = watt; - - if (name.find("package") != std::string::npos) { - state.joules_[0] += ujoules_now * 1e-6; - state.watt_[0] += watt; + state.name_[i + 1] = measurements[i].name; + state.joules_[i + 1] = measurements[i].ujoules * 1e-6; + + if (measurements[i].name.find("package") != std::string::npos) { + state.joules_[0] += measurements[i].ujoules * 1e-6; } } - previous_timestamp_ = state.timestamp_; - previous_measurements_ = measurements; - return state; } diff --git a/rapl/RaplImpl.h b/rapl/RaplImpl.h index b5c1d679a2bced0c930e188307945bc626fde9a6..49ee3cd517871a0ebd6cdc5201da1989909c9e5f 100644 --- a/rapl/RaplImpl.h +++ b/rapl/RaplImpl.h @@ -32,8 +32,6 @@ class RaplImpl : public Rapl { std::vector<RaplMeasurement> GetMeasurements(); - Timestamp previous_timestamp_; - std::vector<RaplMeasurement> previous_measurements_; std::vector<RaplCounter> rapl_counters_; // Mutex used to guard GetMeasurements() diff --git a/rocm/ROCMImpl.cpp b/rocm/ROCMImpl.cpp index 0601d1bb73025e0c92f270fc7e27774070b5647c..384040415eff6bdc05732b29c22ba54e9bffa4b0 100644 --- a/rocm/ROCMImpl.cpp +++ b/rocm/ROCMImpl.cpp @@ -12,11 +12,10 @@ class RocmError : public std::exception { : result_(result), file_(file), line_(line) {} const char *what() const noexcept override { - std::ostringstream oss; - oss << "RSMI call failed with status: " << result_ << " in " << file_ - << " at line " << line_; - message_ = oss.str(); - return message_.c_str(); + std::ostringstream message; + message << "RSMI call failed with status: " << result_ << " in " << file_ + << " at line " << line_; + return message.str().c_str(); } operator rsmi_status_t() const { return result_; } @@ -25,7 +24,6 @@ class RocmError : public std::exception { rsmi_status_t result_; const char *file_; int line_; - mutable std::string message_; }; inline void checkRsmiCall(rsmi_status_t result, const char *file, int line) { @@ -56,34 +54,16 @@ ROCMImpl::ROCMImpl(const unsigned device_number) { checkRsmiCall(rsmi_init(0)); device_number_ = device_number; - - state_previous_ = GetROCMState(); - state_previous_.joules_ = 0; } ROCMImpl::~ROCMImpl() { checkRsmiCall(rsmi_shut_down()); } -ROCMState::operator State() { +State ROCMImpl::GetState() { State state; - state.timestamp_ = timestamp_; - state.name_[0] = "device"; - state.joules_[0] = joules_; - state.watt_[0] = watt_; - return state; -} - -ROCMState ROCMImpl::GetROCMState() { - ROCMState state; state.timestamp_ = GetTime(); - state.watt_ = GetPower(device_number_); - state.joules_ = state_previous_.joules_; - const float watt = (state.watt_ + state_previous_.watt_) / 2; - const double duration = seconds(state_previous_.timestamp_, state.timestamp_); - state.joules_ += watt * duration; - state_previous_ = state; + state.name_[0] = "device"; + state.watt_[0] = GetPower(device_number_); return state; } -State ROCMImpl::GetState() { return GetROCMState(); } - } // end namespace pmt::rocm diff --git a/rocm/ROCMImpl.h b/rocm/ROCMImpl.h index 9ce4069a7d7a45a47df66be383681cb9d8aeffe9..a22bc0a564ff557ec0bc8dc942621ba01eb4d885 100644 --- a/rocm/ROCMImpl.h +++ b/rocm/ROCMImpl.h @@ -3,14 +3,6 @@ #include "ROCM.h" namespace pmt::rocm { -class ROCMState { - public: - operator State(); - Timestamp timestamp_; - double watt_ = 0; - double joules_ = 0; -}; - class ROCMImpl : public ROCM { public: ROCMImpl(const unsigned device_number); @@ -22,8 +14,5 @@ class ROCMImpl : public ROCM { virtual const char *GetDumpFilename() override { return "/tmp/pmt_rocm.out"; } unsigned int device_number_; - - ROCMState state_previous_; - ROCMState GetROCMState(); }; } // end namespace pmt::rocm \ No newline at end of file diff --git a/tegra/TegraImpl.cpp b/tegra/TegraImpl.cpp index c6d61e45b5b1b5bbb08e3835a67940a06e45e7fa..a67fbb67bcb5b1fe5ad6fb64c8993df4eda3d7e7 100644 --- a/tegra/TegraImpl.cpp +++ b/tegra/TegraImpl.cpp @@ -13,9 +13,10 @@ #include <ext/alloc_traits.h> #include <signal.h> +#include "common/Exception.h" #include "TegraImpl.h" -namespace detail { +namespace { bool FileExists(const std::string& name) { if (FILE* file = fopen(name.c_str(), "r")) { fclose(file); @@ -31,7 +32,7 @@ std::string Execute(const std::string& commandline) { std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(commandline.c_str(), "r"), pclose); if (!pipe) { - throw std::runtime_error("popen() failed!"); + throw pmt::Exception("popen() failed!"); } while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); @@ -78,15 +79,17 @@ std::string FindLogfile() { std::string StartTegraStats(int interval) { char filename[32] = "/tmp/tegrastats-XXXXXX"; if (mkstemp(filename) == -1) { - throw std::runtime_error("Could not create temporary file"); + throw pmt::Exception("Could not create temporary file"); } const char* binary = "/usr/bin/tegrastats"; - std::stringstream commandline; + std::ostringstream commandline; commandline << binary << " --start --interval " << interval << " --logfile " << filename; std::string output = Execute(commandline.str()); if (output.compare("") != 0) { - throw std::runtime_error("Error starting tegrastats: " + output); + std::ostringstream message; + message << "Error starting tegrastats: " << output; + throw pmt::Exception(message.str().c_str()); } return filename; } @@ -94,7 +97,7 @@ std::string StartTegraStats(int interval) { void StopTegraStats(const std::string& logfile) { if (logfile.compare("") != 0) { const char* binary = "/usr/bin/tegrastats"; - std::stringstream commandline; + std::ostringstream commandline; commandline << binary << " --stop"; Execute(commandline.str()); std::remove(logfile.c_str()); @@ -145,8 +148,9 @@ std::vector<pmt::tegra::TegraMeasurement> ReadPowerMeasurements( return measurements; } -bool CheckSensors(const std::vector<std::string>& sensors, - std::vector<pmt::tegra::TegraMeasurement>& measurements) { +bool CheckSensors( + const std::vector<std::string>& sensors, + const std::vector<pmt::tegra::TegraMeasurement>& measurements) { if (sensors.size() != measurements.size()) { return false; } @@ -158,47 +162,44 @@ bool CheckSensors(const std::vector<std::string>& sensors, } return true; } -} // end namespace detail +} // end namespace namespace pmt::tegra { void SignalCallbackHandler(int num) { - const std::string logfile = detail::FindLogfile(); - detail::StopTegraStats(logfile); + const std::string logfile = ::FindLogfile(); + ::StopTegraStats(logfile); signal(SIGINT, SIG_DFL); raise(num); } TegraImpl::TegraImpl() { - filename_ = detail::FindLogfile(); - if (!detail::FileExists(filename_)) { - detail::StopTegraStats(filename_); - filename_ = detail::StartTegraStats(measurement_interval_); + filename_ = ::FindLogfile(); + if (!::FileExists(filename_)) { + ::StopTegraStats(filename_); + filename_ = ::StartTegraStats(measurement_interval_); started_tegrastats_ = true; signal(SIGINT, SignalCallbackHandler); } - - previous_state_ = GetTegraState(); - previous_state_.joules_ = 0; } TegraImpl::~TegraImpl() { if (started_tegrastats_) { - detail::StopTegraStats(filename_); + ::StopTegraStats(filename_); started_tegrastats_ = false; } } std::vector<TegraMeasurement> TegraImpl::GetMeasurements() { - std::string line = detail::ReadLastLine(filename_); + std::string line = ::ReadLastLine(filename_); while (line.compare("") == 0) { std::this_thread::sleep_for( std::chrono::milliseconds(measurement_interval_)); - line = detail::ReadLastLine(filename_); + line = ::ReadLastLine(filename_); } - return detail::ReadPowerMeasurements(line); + return ::ReadPowerMeasurements(line); }; const std::vector<std::string> sensors_agx_xavier{"GPU", "CPU", "SOC", @@ -210,55 +211,41 @@ const std::vector<std::string> sensors_jetson_nano{"POM_5V_IN", "POM_5V_GPU", const std::vector<std::string> sensors_jetson_orin_nano{ "VDD_IN", "VDD_CPU_GPU_CV", "VDD_SOC"}; -TegraState::operator State() { +State TegraImpl::GetState() { + const std::vector<TegraMeasurement> measurements = GetMeasurements(); State state(1 + measurements.size()); - state.timestamp_ = timestamp_; - state.name_[0] = "total"; - state.joules_[0] = joules_ * 1e-3; - state.watt_[0] = watt_ * 1e-3; - - for (size_t i = 0; i < measurements.size(); i++) { - const std::string name = measurements[i].first; - const double watt = double(measurements[i].second) / 1000; - state.name_[i + 1] = name; - state.watt_[i + 1] = watt; - } - - return state; -} - -TegraState TegraImpl::GetTegraState() { - TegraState state; state.timestamp_ = GetTime(); - state.measurements = GetMeasurements(); + state.name_[0] = "total"; // Compute total power consumption as sum of individual measurements. // Which individual measurements to use differs per platform. - state.watt_ = 0; - if (detail::CheckSensors(sensors_agx_xavier, state.measurements)) { + state.watt_[0] = 0; + if (::CheckSensors(sensors_agx_xavier, measurements)) { // Jetson AGX Xavier: sum all sensors - for (auto& measurement : state.measurements) { - state.watt_ += measurement.second; + for (auto& measurement : measurements) { + state.watt_[0] += measurement.second; } - } else if (detail::CheckSensors(sensors_agx_orin, state.measurements)) { + } else if (::CheckSensors(sensors_agx_orin, measurements)) { // Jetson AGX Orin: sum all sensors - for (auto& measurement : state.measurements) { - state.watt_ += measurement.second; + for (auto& measurement : measurements) { + state.watt_[0] += measurement.second; } - } else if (detail::CheckSensors(sensors_jetson_nano, state.measurements)) { + } else if (::CheckSensors(sensors_jetson_nano, measurements)) { // Jetson Nano: POM_5V_IN only - state.watt_ += state.measurements[0].second; - } else if (detail::CheckSensors(sensors_jetson_orin_nano, - state.measurements)) { + state.watt_[0] += measurements[0].second; + } else if (::CheckSensors(sensors_jetson_orin_nano, measurements)) { // Jetson Nano: VDD_IN only - state.watt_ += state.measurements[0].second; + state.watt_[0] += measurements[0].second; + } else { + throw pmt::Exception("Unknown Jetson device."); } - - const float watt = (state.watt_ + previous_state_.watt_) / 2; - const double duration = seconds(previous_state_.timestamp_, state.timestamp_); - state.joules_ = previous_state_.joules_ + (watt * duration); - - previous_state_ = state; + for (size_t i = 0; i < measurements.size(); i++) { + const std::string name = measurements[i].first; + const double watt = measurements[i].second / 1.e3; + state.name_[i + 1] = name; + state.watt_[i + 1] = watt; + } + state.watt_[0] /= 1e3; return state; } diff --git a/tegra/TegraImpl.h b/tegra/TegraImpl.h index a9132ac5cd05d541755985e9bb72720c85222c46..11e83bc86578fc9ac2b12cc359b10490f60b2911 100644 --- a/tegra/TegraImpl.h +++ b/tegra/TegraImpl.h @@ -1,6 +1,4 @@ -#include <algorithm> #include <string> -#include <utility> #include <vector> #include "Tegra.h" @@ -10,34 +8,23 @@ namespace pmt::tegra { using TegraMeasurement = std::pair<std::string, int>; -class TegraState { - public: - operator State(); - Timestamp timestamp_; - std::vector<TegraMeasurement> measurements; - unsigned int watt_ = 0; - unsigned int joules_ = 0; -}; - class TegraImpl : public Tegra { public: TegraImpl(); virtual ~TegraImpl(); - State GetState() override { return GetTegraState(); } + State GetState() override; virtual const char *GetDumpFilename() override { return "/tmp/pmt_tegra.out"; } private: - TegraState GetTegraState(); std::vector<TegraMeasurement> GetMeasurements(); std::string filename_ = ""; bool started_tegrastats_ = false; const int measurement_interval_ = 10; // milliseconds - TegraState previous_state_; }; } // end namespace pmt::tegra \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..fe3345d22d77c1d3efd6c0f496a83f7698234031 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,12 @@ +include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.8.0) + +FetchContent_MakeAvailable(Catch2) +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib) +include(Catch) + +add_subdirectory(dummy) diff --git a/test/dummy/CMakeLists.txt b/test/dummy/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..782a0b81c2b5f0df0bf675657b0db5d8a31bba8f --- /dev/null +++ b/test/dummy/CMakeLists.txt @@ -0,0 +1,7 @@ +project(test-dummy) + +add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) +target_link_libraries(${PROJECT_NAME} pmt Catch2::Catch2WithMain) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}) +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/test/dummy/test-dummy.cpp b/test/dummy/test-dummy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3d94e61f1bddb8f10651e8daec07a172fb9a9b1d --- /dev/null +++ b/test/dummy/test-dummy.cpp @@ -0,0 +1,148 @@ +#include <numeric> +#include <vector> + +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generators.hpp> +#include <catch2/matchers/catch_matchers_floating_point.hpp> + +#include <pmt.h> + +#include "dummy/Dummy.h" + +/** + * Verify default behavior of the Dummy sensor + * + * Test validates that: + * 1. Default sensor returns measurements with zero values + * 2. Time advances between measurements + * 3. Power and energy calculations return zero + */ +TEST_CASE("Default") { + const size_t kSleepMs = GENERATE(1, 1e2, 1e3); + + // Initialize + auto sensor = pmt::Dummy::Create(); + + // Take two measurements + pmt::State state1 = sensor->Read(); + std::this_thread::sleep_for(std::chrono::milliseconds(kSleepMs)); + pmt::State state2 = sensor->Read(); + + // Verify each state has expected default values + for (const pmt::State& state : {state1, state2}) { + REQUIRE(state.NrMeasurements() == 1); // Single measurement point + REQUIRE(state.watts(0) == 0); // Zero power consumption + REQUIRE(state.joules(0) == 0); // Zero energy consumption + } + + // Verify time-based calculations + CHECK(pmt::PMT::seconds(state1, state2) > 0); // Time advances + CHECK_THAT(pmt::PMT::watts(state1, state2), // Power remains zero + Catch::Matchers::WithinULP(0.f, 0)); + CHECK_THAT(pmt::PMT::joules(state1, state2), // Energy remains zero + Catch::Matchers::WithinULP(0.f, 0)); +} + +/** + * Verify the Dummy sensor reporting power + * - The Dummy sensor is configured to report a fixed power value + * + * Test validates that: + * 1. Sensor correctly reports configured power value + * 2. Power calculation from energy matches configured value + */ +TEST_CASE("Set watt") { + const float kWatt = 42; + const size_t kSleepMs = GENERATE(1, 1e2, 1e3); + + // Initialize + auto sensor = pmt::Dummy::Create(); + sensor->UseFixedWatts({kWatt}); + + // Take two measurements + pmt::State state1 = sensor->Read(); + std::this_thread::sleep_for(std::chrono::milliseconds(kSleepMs)); + pmt::State state2 = sensor->Read(); + + // Verify returned power + const float watt = pmt::PMT::watts(state1, state2); + CHECK_THAT(watt, Catch::Matchers::WithinAbs(kWatt, 1.f)); + + // Verify power calculation from energy + const float joules = pmt::PMT::joules(state1, state2); + const float seconds = pmt::PMT::seconds(state1, state2); + REQUIRE(seconds > 0); + CHECK_THAT(joules / seconds, Catch::Matchers::WithinAbs(kWatt, 1.f)); +} + +/** + * Verify the Dummy sensor reporting energy + * - The Dummy sensor is configured to report a fixed energy value + * + * Test validates that: + * 1. Sensor correctly reports configured power value + * 2. Power calculation from energy matches configured value + */ +TEST_CASE("Set joules") { + const float kWatt = 42; + const size_t kSleepMs = GENERATE(1, 1e2, 1e3); + + // Initialize + auto sensor = pmt::Dummy::Create(); + sensor->UseFixedJoules({kWatt}); + + // Take two measurements + pmt::State state1 = sensor->Read(); + std::this_thread::sleep_for(std::chrono::milliseconds(kSleepMs)); + pmt::State state2 = sensor->Read(); + + // Verify returned power + const float watt = pmt::PMT::watts(state1, state2); + CHECK_THAT(watt, Catch::Matchers::WithinAbs(kWatt, 1.f)); + + // Verify power calculation from energy + const float joules = pmt::PMT::joules(state1, state2); + const float seconds = pmt::PMT::seconds(state1, state2); + REQUIRE(seconds > 0); + CHECK_THAT(joules / seconds, Catch::Matchers::WithinAbs(kWatt, 1.f)); +} + +/** + * Verify the Dummy sensor reporting power + * - The Dummy sensor is configured to report a fixed power value + * + * Test validates that: + * 1. Sensor correctly reports sum of configured power values + * 2. Power calculation from energy matches sum of configured values + * 3. Individual sensor values are correctly reported + */ +TEST_CASE("Set watt for 2 sensors") { + const std::vector<float> kWatt{{42, 420}}; + const float kWattSum = std::accumulate(kWatt.begin(), kWatt.end(), 0.f); + const size_t kSleepMs = GENERATE(1, 1e2, 1e3); + + // Initialize + auto sensor = pmt::Dummy::Create(); + sensor->UseFixedWatts(kWatt); + + // Take two measurements + pmt::State state1 = sensor->Read(); + std::this_thread::sleep_for(std::chrono::milliseconds(kSleepMs)); + pmt::State state2 = sensor->Read(); + + // Verify returned power + const float watt = pmt::PMT::watts(state1, state2); + CHECK_THAT(watt, Catch::Matchers::WithinAbs(kWattSum, 1.f)); + + // Verify power calculation from energy + const float joules = pmt::PMT::joules(state1, state2); + const float seconds = pmt::PMT::seconds(state1, state2); + REQUIRE(seconds > 0); + CHECK_THAT(joules / seconds, Catch::Matchers::WithinAbs(kWattSum, 1.f)); + + // Verify individual sensor values + for (size_t i = 0; i < kWatt.size(); ++i) { + CHECK_THAT(state1.watts(i + 1), Catch::Matchers::WithinAbs(kWatt[i], 1.f)); + CHECK_THAT(state2.watts(i + 1), Catch::Matchers::WithinAbs(kWatt[i], 1.f)); + } +} \ No newline at end of file diff --git a/xilinx/Xilinx.cpp b/xilinx/Xilinx.cpp index 56426006a3e7e6973215b2fb312f0a2cbca71b29..508c01654139d26bda43208ff59aa9b865fad848 100644 --- a/xilinx/Xilinx.cpp +++ b/xilinx/Xilinx.cpp @@ -1,6 +1,7 @@ #include "Xilinx.h" #include <istream> +#include <sstream> #include <stdexcept> #include <vector> @@ -8,13 +9,17 @@ #include <ext/alloc_traits.h> #include <stdlib.h> -namespace anonymous { +#include "common/Exception.h" + +namespace { float GetPower(std::string &filename) { // Open power file, e.g. // /sys/devices/pci0000:a0/0000:a0:03.1/0000:a1:00.0/hwmon/hwmon3/power1_input std::ifstream file(filename, std::ios::in | std::ios::binary); if (errno != 0) { - throw std::runtime_error("Could not open: " + filename); + std::ostringstream message; + message << "Could not open: " << filename; + throw pmt::Exception(message.str().c_str()); } // This file has one line with instantenous power consumption in uW @@ -23,43 +28,23 @@ float GetPower(std::string &filename) { return power; } -} // namespace anonymous +} // namespace namespace pmt::xilinx { -class XilinxState { - public: - operator State(); - Timestamp timestamp_; - double watt_ = 0; - double joules_ = 0; -}; - class XilinxImpl : public Xilinx { public: XilinxImpl(const char *device); private: - State GetState() override { return GetXilinxState(); } + State GetState() override; virtual const char *GetDumpFilename() override { return "/tmp/pmt_xilinx.out"; } std::string filename_; - - XilinxState previous_state_; - XilinxState GetXilinxState(); }; -XilinxState::operator State() { - State state; - state.timestamp_ = timestamp_; - state.name_[0] = "device"; - state.joules_[0] = joules_; - state.watt_[0] = watt_; - return state; -} - std::unique_ptr<Xilinx> Xilinx::Create(const char *device) { return std::make_unique<XilinxImpl>(device); } @@ -67,20 +52,13 @@ std::unique_ptr<Xilinx> Xilinx::Create(const char *device) { XilinxImpl::XilinxImpl(const char *device) { char *pmt_device = getenv("PMT_DEVICE"); filename_ = pmt_device ? pmt_device : device; - - previous_state_ = GetXilinxState(); - previous_state_.joules_ = 0; } -XilinxState XilinxImpl::GetXilinxState() { - XilinxState state; +State XilinxImpl::GetState() { + State state; state.timestamp_ = GetTime(); - state.watt_ = anonymous::GetPower(filename_) * 1e-6; - state.joules_ = previous_state_.joules_; - const float watts = (state.watt_ + previous_state_.watt_) / 2; - const double duration = seconds(previous_state_.timestamp_, state.timestamp_); - state.joules_ += watts * duration; - previous_state_ = state; + state.name_[0] = "device"; + state.watt_[0] = ::GetPower(filename_) * 1e-6; return state; }