Skip to content

Commit

Permalink
Could swap to a single CPU clock?
Browse files Browse the repository at this point in the history
  • Loading branch information
TrentHouliston committed Nov 28, 2023
1 parent 3d1bb53 commit b826cd7
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 111 deletions.
4 changes: 1 addition & 3 deletions src/message/ReactionStatistics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ namespace message {
/// @brief The time that execution finished on this reaction
clock::time_point finished{};
/// @brief The amount of CPU time that this reaction took to execute
util::user_cpu_clock::duration user_cpu_time{};
/// @brief The amount of kernel time that this reaction took to execute
util::kernel_cpu_clock::duration kernel_cpu_time{};
util::cpu_clock::duration cpu_time{};
/// @brief An exception pointer that can be rethrown (if the reaction threw an exception)
std::exception_ptr exception{nullptr};
};
Expand Down
8 changes: 3 additions & 5 deletions src/util/CallbackGenerator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ namespace util {

// Start times
task.stats->started = clock::now();
auto user_start = util::user_cpu_clock::now();
auto kernel_start = util::kernel_cpu_clock::now();
auto cpu_start = util::cpu_clock::now();

// We have to catch any exceptions
try {
Expand All @@ -125,9 +124,8 @@ namespace util {
}

// Finish times in same order
task.stats->finished = clock::now();
task.stats->user_cpu_time = util::user_cpu_clock::now() - user_start;
task.stats->kernel_cpu_time = util::kernel_cpu_clock::now() - kernel_start;
task.stats->finished = clock::now();
task.stats->cpu_time = util::cpu_clock::now() - cpu_start;

// Run our postconditions
DSL::postcondition(task);
Expand Down
94 changes: 18 additions & 76 deletions src/util/usage_clock.cpp
Original file line number Diff line number Diff line change
@@ -1,87 +1,14 @@
#include "usage_clock.hpp"


// Linux
#if defined(__linux__)
#include <sys/resource.h>

namespace NUClear {
namespace util {

user_cpu_clock::time_point user_cpu_clock::now() noexcept {
rusage usage{};
if (::getrusage(RUSAGE_THREAD, &usage) == 0) {
return time_point(std::chrono::seconds(usage.ru_utime.tv_sec)
+ std::chrono::microseconds(usage.ru_utime.tv_usec));
}
return {};
}

kernel_cpu_clock::time_point kernel_cpu_clock::now() noexcept {
rusage usage{};
if (::getrusage(RUSAGE_THREAD, &usage) == 0) {
return time_point(std::chrono::seconds(usage.ru_stime.tv_sec)
+ std::chrono::microseconds(usage.ru_stime.tv_usec));
}
return {};
}

} // namespace util
} // namespace NUClear

// Mac OS X
#elif defined(__MACH__) && defined(__APPLE__)

#include <errno.h>
#include <mach/mach.h>
#include <sys/resource.h>

namespace NUClear {
namespace util {

user_cpu_clock::time_point user_cpu_clock::now() noexcept {
thread_basic_info_data_t info{};
mach_msg_type_number_t info_count = THREAD_BASIC_INFO_COUNT;
kern_return_t kern_err;

mach_port_t port = mach_thread_self();
kern_err = thread_info(port, THREAD_BASIC_INFO, reinterpret_cast<thread_info_t>(&info), &info_count);
mach_port_deallocate(mach_task_self(), port);

if (kern_err == KERN_SUCCESS) {
return time_point(std::chrono::seconds(info.user_time.seconds)
+ std::chrono::microseconds(info.user_time.microseconds));
}
return time_point();
}

kernel_cpu_clock::time_point kernel_cpu_clock::now() noexcept {
thread_basic_info_data_t info{};
mach_msg_type_number_t info_count = THREAD_BASIC_INFO_COUNT;
kern_return_t kern_err;

mach_port_t port = mach_thread_self();
kern_err = thread_info(port, THREAD_BASIC_INFO, reinterpret_cast<thread_info_t>(&info), &info_count);
mach_port_deallocate(mach_task_self(), port);

if (kern_err == KERN_SUCCESS) {
return time_point(std::chrono::seconds(info.system_time.seconds)
+ std::chrono::microseconds(info.system_time.microseconds));
}
return time_point();
}

} // namespace util
} // namespace NUClear

// Windows
#elif defined(_WIN32)
#if defined(_WIN32)
#include "platform.hpp"

namespace NUClear {
namespace util {

user_cpu_clock::time_point user_cpu_clock::now() noexcept {
cpu_clock::time_point cpu_clock::now() noexcept {
FILETIME creation_time;
FILETIME exit_time;
FILETIME kernel_time;
Expand Down Expand Up @@ -110,4 +37,19 @@ namespace util {
} // namespace util
} // namespace NUClear

#endif // OS
#else
#include <time.h>

namespace NUClear {
namespace util {

cpu_clock::time_point cpu_clock::now() noexcept {
timespec ts{};
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
return time_point(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec));
}

} // namespace util
} // namespace NUClear

#endif // _WIN32
14 changes: 2 additions & 12 deletions src/util/usage_clock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,11 @@
namespace NUClear {
namespace util {

struct user_cpu_clock {
struct cpu_clock {
using duration = std::chrono::nanoseconds;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<user_cpu_clock>;
static const bool is_steady = true;

static time_point now() noexcept;
};

struct kernel_cpu_clock {
using duration = std::chrono::nanoseconds;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<kernel_cpu_clock>;
using time_point = std::chrono::time_point<cpu_clock>;
static const bool is_steady = true;

static time_point now() noexcept;
Expand Down
24 changes: 9 additions & 15 deletions tests/api/ReactionStatisticsTiming.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,15 @@
namespace {

/// @brief Events that occur during the test and the time they occur
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
/// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::vector<std::pair<std::string, NUClear::clock::time_point>> events;

struct Usage {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::map<std::string, NUClear::clock::duration> real;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::map<std::string, NUClear::util::user_cpu_clock::duration> user;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::map<std::string, NUClear::util::kernel_cpu_clock::duration> kernel;
std::map<std::string, NUClear::util::cpu_clock::duration> cpu;
};

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Usage usage;

struct DoTest {};
Expand Down Expand Up @@ -96,7 +94,6 @@ class TestReactor : public test_util::TestBase<TestReactor> {
events.emplace_back("Code: Finished " + light_name, NUClear::clock::now());
});


on<Trigger<ReactionStatistics>>().then([this](const ReactionStatistics& stats) {
if (stats.identifiers.reactor == reactor_name
&& (stats.identifiers.name == initial_name || stats.identifiers.name == heavy_name
Expand All @@ -105,9 +102,8 @@ class TestReactor : public test_util::TestBase<TestReactor> {
events.emplace_back("Stat: Started " + stats.identifiers.name, stats.started);
events.emplace_back("Stat: Finished " + stats.identifiers.name, stats.finished);

usage.real[stats.identifiers.name] = stats.finished - stats.started;
usage.user[stats.identifiers.name] = stats.user_cpu_time;
usage.kernel[stats.identifiers.name] = stats.kernel_cpu_time;
usage.real[stats.identifiers.name] = stats.finished - stats.started;
usage.cpu[stats.identifiers.name] = stats.cpu_time;
}
});

Expand Down Expand Up @@ -153,14 +149,12 @@ TEST_CASE("Testing reaction statistics timing", "[api][reactionstatistics][timin
// Check the events fired in order and only those events
REQUIRE(delta_events == expected);

// Check that the amount of CPU time spent is at least reasonable for each of the reactions

// Most of initial real time should be spent sleeping
REQUIRE(usage.user[initial_name] + usage.kernel[initial_name] < usage.real[initial_name] / 2);
REQUIRE(usage.cpu[initial_name] < usage.real[initial_name] / 2);

// Most of heavy real time should be cpu time
REQUIRE(usage.user[heavy_name] + usage.kernel[heavy_name] > usage.real[heavy_name] / 2);
REQUIRE(usage.cpu[heavy_name] > usage.real[heavy_name] / 2);

// Most of light real time should be sleeping
REQUIRE(usage.user[light_name] + usage.kernel[light_name] < usage.real[light_name] / 2);
REQUIRE(usage.cpu[light_name] < usage.real[light_name] / 2);
}

0 comments on commit b826cd7

Please sign in to comment.