Skip to content
Open
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
137 changes: 122 additions & 15 deletions dynolog/src/CPUTimeMonitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "dynolog/src/CPUTimeMonitor.h"
#include <dynolog/src/metric_frame/MetricFrameTsUnit.h>

#include <algorithm>
#include <utility>

enum { IDX_MIN = 0, IDX_SEC = 1, IDX_HUNDRED_MS = 2 };
Expand All @@ -19,14 +20,16 @@ static const std::string kBreakdownIdle = ".idle";
static const std::string kBreakdownSoftirq = ".softirq";
static const std::string kBreakdownIowait = ".iowait";
static const std::string kBreakdownHardirq = ".hardirq";
static const std::string kBreakdownNice = ".nice";

// All breakdown suffixes in a single list to avoid iterating in multiple
// places.
static const std::vector<std::string> kBreakdownSuffixes = {
kBreakdownIdle,
kBreakdownSoftirq,
kBreakdownIowait,
kBreakdownHardirq};
kBreakdownHardirq,
kBreakdownNice};

// Helper to add breakdown series for a target to a MetricFrameMap.
static void addBreakdownSeries(
Expand All @@ -51,13 +54,27 @@ static const std::string& breakdownSuffix(
return kBreakdownIowait;
case CPUTimeMonitor::CpuBreakdown::HARDIRQ:
return kBreakdownHardirq;
case CPUTimeMonitor::CpuBreakdown::NICE:
return kBreakdownNice;
}
return kBreakdownIdle; // unreachable
}

constexpr int kRingbufferSizeMinutes = 6;
static const std::string kHostCgroupPath = "/sys/fs/cgroup";

static std::optional<int> granularityToLevel(CPUTimeMonitor::Granularity gran) {
switch (gran) {
case CPUTimeMonitor::Granularity::MINUTE:
return IDX_MIN;
case CPUTimeMonitor::Granularity::SECOND:
return IDX_SEC;
case CPUTimeMonitor::Granularity::HUNDRED_MS:
return IDX_HUNDRED_MS;
}
return std::nullopt;
}

std::array<MetricFrameMap, 3> CPUTimeMonitor::createMetricFrameArray() {
return {
MetricFrameMap(
Expand Down Expand Up @@ -194,20 +211,11 @@ std::optional<double> CPUTimeMonitor::getCpuBreakdownAvg(
TimePoint now = std::chrono::steady_clock::now();
std::shared_lock lock(dataLock_);

int level = 0;
switch (gran) {
case Granularity::MINUTE:
level = IDX_MIN;
break;
case Granularity::SECOND:
level = IDX_SEC;
break;
case Granularity::HUNDRED_MS:
level = IDX_HUNDRED_MS;
break;
default:
return std::nullopt;
auto levelOpt = granularityToLevel(gran);
if (!levelOpt.has_value()) {
return std::nullopt;
}
int level = *levelOpt;

auto& frame = procUsageMetricFrames_[level];
auto slice = frame.slice(now - std::chrono::seconds(seconds_ago), now);
Expand Down Expand Up @@ -248,6 +256,100 @@ const MetricFrameMap* CPUTimeMonitor::getMetricFrame(
: &procUsageMetricFrames_[level];
}

// Combined min+max in a single lock+slice+copy pass. Breakdown values are
// already stored as per-second rates (cores) by processProcUsage(), so we
// pick min/max directly from the raw samples using std::minmax_element.
std::optional<std::pair<double, double>> CPUTimeMonitor::getCpuBreakdownMinMax(
Granularity gran,
uint64_t seconds_ago,
CpuBreakdown breakdown,
const std::optional<std::string>& targetId) {
TimePoint now = std::chrono::steady_clock::now();
std::shared_lock lock(dataLock_);

auto levelOpt = granularityToLevel(gran);
if (!levelOpt.has_value()) {
return std::nullopt;
}
int level = *levelOpt;

auto& frame = procUsageMetricFrames_[level];
auto slice = frame.slice(now - std::chrono::seconds(seconds_ago), now);
if (slice == std::nullopt) {
return std::nullopt;
}
std::string key = targetId.value_or("host") + breakdownSuffix(breakdown);
auto series = slice->series<double>(key);
if (series == std::nullopt || series->size() == 0) {
return std::nullopt;
}
auto data = series->raw();
auto [minIt, maxIt] = std::minmax_element(data.begin(), data.end());
return std::make_pair(*minIt, *maxIt);
}

std::optional<double> CPUTimeMonitor::getCpuBreakdownMin(
Granularity gran,
uint64_t seconds_ago,
CpuBreakdown breakdown,
const std::optional<std::string>& targetId) {
auto result = getCpuBreakdownMinMax(gran, seconds_ago, breakdown, targetId);
return result ? std::optional(result->first) : std::nullopt;
}

std::optional<double> CPUTimeMonitor::getCpuBreakdownMax(
Granularity gran,
uint64_t seconds_ago,
CpuBreakdown breakdown,
const std::optional<std::string>& targetId) {
auto result = getCpuBreakdownMinMax(gran, seconds_ago, breakdown, targetId);
return result ? std::optional(result->second) : std::nullopt;
}

std::optional<std::pair<double, double>> CPUTimeMonitor::getMinMaxCPUCoresUsage(
Granularity gran,
uint64_t seconds_ago,
const std::optional<std::string>& targetId,
DataSource dataSource) {
TimePoint now = std::chrono::steady_clock::now();
std::shared_lock lock(dataLock_);

const auto* frame = getMetricFrame(gran, dataSource);
if (frame == nullptr) {
return std::nullopt;
}
auto slice = frame->slice(now - std::chrono::seconds(seconds_ago), now);
if (slice == std::nullopt) {
return std::nullopt;
}
std::string key = targetId.value_or("host");
auto series = slice->series<double>(key);
if (series == std::nullopt || series->size() == 0) {
return std::nullopt;
}
auto data = series->raw();
auto [minIt, maxIt] = std::minmax_element(data.begin(), data.end());
return std::make_pair(*minIt, *maxIt);
}

std::optional<double> CPUTimeMonitor::getMinCPUCoresUsage(
Granularity gran,
uint64_t seconds_ago,
const std::optional<std::string>& targetId,
DataSource dataSource) {
auto result = getMinMaxCPUCoresUsage(gran, seconds_ago, targetId, dataSource);
return result ? std::optional(result->first) : std::nullopt;
}

std::optional<double> CPUTimeMonitor::getMaxCPUCoresUsage(
Granularity gran,
uint64_t seconds_ago,
const std::optional<std::string>& targetId,
DataSource dataSource) {
auto result = getMinMaxCPUCoresUsage(gran, seconds_ago, targetId, dataSource);
return result ? std::optional(result->second) : std::nullopt;
}

void CPUTimeMonitor::tick(TMask mask) {
TimePoint tickTime = std::chrono::steady_clock::now();
bool readPerCore = false;
Expand Down Expand Up @@ -589,8 +691,10 @@ void CPUTimeMonitor::processProcUsage(
auto softirqDeltaOpt = safeDelta(newCt.y, lastCt.y);
auto iowaitDeltaOpt = safeDelta(newCt.w, lastCt.w);
auto hardirqDeltaOpt = safeDelta(newCt.x, lastCt.x);
auto niceDeltaOpt = safeDelta(newCt.n, lastCt.n);

if (idleDeltaOpt && softirqDeltaOpt && iowaitDeltaOpt && hardirqDeltaOpt) {
if (idleDeltaOpt && softirqDeltaOpt && iowaitDeltaOpt && hardirqDeltaOpt &&
niceDeltaOpt) {
double idleCores =
static_cast<double>(*idleDeltaOpt) * msPerJiffy / wallDelta;
double softirqCores =
Expand All @@ -599,11 +703,14 @@ void CPUTimeMonitor::processProcUsage(
static_cast<double>(*iowaitDeltaOpt) * msPerJiffy / wallDelta;
double hardirqCores =
static_cast<double>(*hardirqDeltaOpt) * msPerJiffy / wallDelta;
double niceCores =
static_cast<double>(*niceDeltaOpt) * msPerJiffy / wallDelta;

line.emplace_back(targetId + kBreakdownIdle, idleCores);
line.emplace_back(targetId + kBreakdownSoftirq, softirqCores);
line.emplace_back(targetId + kBreakdownIowait, iowaitCores);
line.emplace_back(targetId + kBreakdownHardirq, hardirqCores);
line.emplace_back(targetId + kBreakdownNice, niceCores);
}
}
if (!line.empty()) {
Expand Down
44 changes: 39 additions & 5 deletions dynolog/src/CPUTimeMonitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class CPUTimeMonitor : MonitorBase<Ticker<60000, 1000, 10, 3>> {

enum class Granularity { MINUTE, SECOND, HUNDRED_MS };
enum class DataSource { PROC_STAT, CGROUP_STAT };
enum class CpuBreakdown { IDLE, SOFTIRQ, IOWAIT, HARDIRQ };
enum class CpuBreakdown { IDLE, SOFTIRQ, IOWAIT, HARDIRQ, NICE };

explicit CPUTimeMonitor(
std::shared_ptr<TTicker> ticker,
Expand Down Expand Up @@ -97,18 +97,52 @@ class CPUTimeMonitor : MonitorBase<Ticker<60000, 1000, 10, 3>> {
const std::optional<std::string>& targetId = std::nullopt,
DataSource dataSource = DataSource::PROC_STAT);

// Get the average CPU breakdown value (idle, softirq, iowait, hardirq)
// Get the average CPU breakdown value (idle, softirq, iowait, hardirq, nice)
// for a target over the specified time window. Returns raw cores used.
std::optional<double> getCpuBreakdownAvg(
Granularity gran,
uint64_t seconds_ago,
CpuBreakdown breakdown,
const std::optional<std::string>& targetId = std::nullopt);

std::optional<double> getCpuBreakdownMin(
Granularity gran,
uint64_t seconds_ago,
CpuBreakdown breakdown,
const std::optional<std::string>& targetId = std::nullopt);

std::optional<double> getCpuBreakdownMax(
Granularity gran,
uint64_t seconds_ago,
CpuBreakdown breakdown,
const std::optional<std::string>& targetId = std::nullopt);

// Combined min+max in a single lock+slice+copy pass.
std::optional<std::pair<double, double>> getCpuBreakdownMinMax(
Granularity gran,
uint64_t seconds_ago,
CpuBreakdown breakdown,
const std::optional<std::string>& targetId = std::nullopt);

std::optional<double> getMinCPUCoresUsage(
Granularity gran,
uint64_t seconds_ago,
const std::optional<std::string>& targetId = std::nullopt,
DataSource dataSource = DataSource::PROC_STAT);

std::optional<double> getMaxCPUCoresUsage(
Granularity gran,
uint64_t seconds_ago,
const std::optional<std::string>& targetId = std::nullopt,
DataSource dataSource = DataSource::PROC_STAT);

std::optional<std::pair<double, double>> getMinMaxCPUCoresUsage(
Granularity gran,
uint64_t seconds_ago,
const std::optional<std::string>& targetId = std::nullopt,
DataSource dataSource = DataSource::PROC_STAT);

private:
// Reads CPU time data from /proc/stat
// If read_per_core is true, reads per-core data in addition to the
// all-core data (first element)
enum class Statistic { AVG, QUANTILE };

std::vector<::dynolog::CpuTime> readProcStat(bool read_per_core = false);
Expand Down
Loading
Loading