Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions src/green/utils/timing.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
namespace green::utils {

struct event_t {
event_t() : start(0), duration(0), active(false) {}
event_t(double start_, double duration_) : start(start_), duration(duration_), active(false){};
event_t() : start(0), duration(0), active(false), accumulate(true) {}
event_t(double start_, double duration_) : start(start_), duration(duration_), active(false), accumulate(true){};
event_t(double start_, double duration_, bool accumulate_) : start(start_), duration(duration_), active(false), accumulate(accumulate_){};
double start;
double duration;
bool active;
bool accumulate; // accumulate time for subsequent measurements
event_t* parent = nullptr;
std::unordered_map<std::string, std::unique_ptr<event_t>> children;
};
Expand Down Expand Up @@ -129,6 +131,20 @@ namespace green::utils {
timing(timing const&) = delete;
void operator=(timing const&) = delete;

/**
* @brief Register a root-level timing event by name without starting it.
*
* Creates the event entry in the internal root events map if it does not exist yet.
* This call is idempotent and has no effect if the event is already present.
*
* Notes:
* - This does not start timing. Use start(name) to begin measuring and end() to stop.
* - Added events appear in print()/print(MPI_Comm) output even if never started (duration = 0).
* - The event is registered at the root level; child events are created implicitly when
* start(name) is invoked while another event is active.
*
* @param name Unique identifier of the event to pre-register at the root level.
*/
void add(const std::string& name) {
if (_root_events.find(name) == _root_events.end()) {
_root_events[name] = std::make_unique<event_t>(0.0, 0.0);
Expand All @@ -141,8 +157,9 @@ namespace green::utils {
* `_current_event` will be set to a newly started event.
*
* @param name - event name to start time measurement
* @param accumulate - whether to accumulate time for this event
*/
void start(const std::string& name) {
void start(const std::string& name, bool accumulate=true) {
#ifndef NDEBUG
if (_root_events.find(name) != _root_events.end() && _root_events[name]->active) {
throw wrong_event_state("Event is already active");
Expand All @@ -153,10 +170,12 @@ namespace green::utils {
if (_current_event->children[name] == nullptr) _current_event->children[name] = std::make_unique<event_t>(0, 0);
_current_event->children[name]->parent = _current_event;
_current_event = _current_event->children[name].get();
_current_event->accumulate = accumulate;
} else {
// start root event
if (_root_events[name] == nullptr) _root_events[name] = std::make_unique<event_t>(0, 0);
_current_event = _root_events[name].get();
_current_event->accumulate = accumulate;
}
_current_event->active = true;
_current_event->start = time();
Expand All @@ -173,12 +192,30 @@ namespace green::utils {
return;
}
double time1 = time();
_current_event->duration += time1 - _current_event->start;
if (_current_event->accumulate) {
_current_event->duration += time1 - _current_event->start;
} else {
_current_event->duration = time1 - _current_event->start;
}
_current_event->active = false;
_current_event = _current_event->parent;

}


/**
* @brief reset the `duration` attribute of all child events of the current event
* If there is no current event (i.e., at the root level when `_current_event` is nullptr),
* this method returns silently and does nothing.
*/
void reset() {
if (!_current_event) return;
for (auto& kv : _current_event->children) {
kv.second->duration = 0.0;
kv.second->active = false;
}
}

/**
* Print statistics for all observed events
*/
Expand Down
52 changes: 52 additions & 0 deletions test/utils_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,56 @@ TEST_CASE("Timing") {
REQUIRE(statistic.event("UNKNOWN2").parent == &statistic.event("TEST"));
statistic.end();
}

SECTION("Test Accumulate") {
// Test case when Accumulate is ON
green::utils::timing statistic;
double s1 = MPI_Wtime();
statistic.start("ACCUMULATE ON", true);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
statistic.end();
double e1 = MPI_Wtime();
double duration1 = statistic.event("ACCUMULATE ON").duration;
REQUIRE(std::abs(duration1 - (e1 - s1)) < 1e-2);

double s2 = MPI_Wtime();
statistic.start("ACCUMULATE ON", true);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
statistic.end();
double e2 = MPI_Wtime();
double duration2 = statistic.event("ACCUMULATE ON").duration;
REQUIRE(std::abs(duration2 - ((e2 - s2) + (e1 - s1))) < 1e-2);

// Test case when Accumulate is OFF
statistic.start("ACCUMULATE OFF", false);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
statistic.end();
double duration3 = statistic.event("ACCUMULATE OFF").duration;

statistic.start("ACCUMULATE OFF", false);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
statistic.end();
double duration4 = statistic.event("ACCUMULATE OFF").duration;
REQUIRE(std::abs(duration4 - duration3) < 1e-2);
}

SECTION("Test Reset") {
green::utils::timing statistic;
statistic.start("ROOT");
statistic.start("CHILD1");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
statistic.end();
statistic.start("CHILD2");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
statistic.end();
statistic.reset();
statistic.end();

double duration_root = statistic.event("ROOT").duration;
double duration_child1 = statistic.event("ROOT").children["CHILD1"]->duration;
double duration_child2 = statistic.event("ROOT").children["CHILD2"]->duration;

REQUIRE(duration_child1 == 0.0);
REQUIRE(duration_child2 == 0.0);
}
}