blob: c094b840d0358e04e154b7702f323c8708d41789 [file] [log] [blame]
// Copyright 2022 The Centipede Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO(ussuri): Upgrade to optionally measure the metrics of a given thread,
// not the entire process (available via /proc/self/tasks/<tid>/<file>).
// Utility classes to capture and log system resource usage of the current
// process.
#ifndef THIRD_PARTY_CENTIPEDE_RUSAGE_STATS_H_
#define THIRD_PARTY_CENTIPEDE_RUSAGE_STATS_H_
#include <sys/resource.h>
#include <array>
#include <cstdint>
#include <iosfwd>
#include <ostream>
#include <string>
#include "absl/status/status.h"
#include "absl/time/time.h"
namespace centipede::perf {
// Memory size in bytes.
using MemSize = int64_t;
// How many CPU hyperthreaded cores the process has been using on average.
// 1 corresponds to 1 hypercore. The max is the number of hyperthreaded cores
// on the system.
using CpuHyperCores = double;
// What percentage of the allotted system scheduling time the process has
// actually utilized for CPU processing, as opposed to idling (e.g. waiting for
// I/O etc.). The theoretical max is 1.0, which corresponds to 100% utilization,
// however the value can go slightly higher due to rounding errors in the system
// scheduler's accounting logic.
using CpuUtilization = long double;
//------------------------------------------------------------------------------
// RUsageScope
//
// Specifies the scope of resource usage measurements: a process or a thread.
//------------------------------------------------------------------------------
class RUsageScope {
public:
enum ProcFile : size_t { kSched = 0, kStatm = 1, kStatus = 2, kNum = 3 };
// Static ctors for supported use cases. If the same scope is used repeatedly,
// callers should prefer caching it, as construction may involve syscalls.
static RUsageScope ThisProcess();
static RUsageScope Process(pid_t pid);
static RUsageScope ThisThread();
static RUsageScope ThisProcessThread(pid_t tid);
static RUsageScope Thread(pid_t pid, pid_t tid);
// Copyable and movable.
RUsageScope(const RUsageScope&) = default;
RUsageScope& operator=(const RUsageScope&) = default;
RUsageScope(RUsageScope&&) = default;
RUsageScope& operator=(RUsageScope&&) = default;
// Returns a path to the /proc/<pid>/<file> or /proc/<pid>/task/<tid>/<file>.
[[nodiscard]] const std::string& GetProcFilePath(ProcFile file) const;
template <typename OStream>
friend OStream& operator<<(OStream& os, const RUsageScope& s) {
return os << s.description_;
}
private:
explicit RUsageScope(pid_t pid);
RUsageScope(pid_t pid, pid_t tid);
std::string description_;
std::array<std::string, ProcFile::kNum> proc_file_paths_;
};
//------------------------------------------------------------------------------
// ProcessTimer
//
// Measures the system, user, and wall times of the process. Can be a global
// variable because the implementation depends on nothing but syscalls.
// The parameterless RUsageTiming::Snapshot() uses the default global timer that
// starts with the process; clients also have an option to define and pass a
// custom timer to count from some other point in time.
//------------------------------------------------------------------------------
class ProcessTimer {
public:
ProcessTimer();
void Get(double& user, double& sys, double& wall) const;
private:
absl::Time start_time_;
struct rusage start_rusage_;
};
//------------------------------------------------------------------------------
// RUsageTiming
//
// An interface to measure, store, manipulate, and log the system timing stats
// of a process or a thread.
//------------------------------------------------------------------------------
struct RUsageTiming {
//----------------------------------------------------------------------------
// Static factory ctors and friend operators
static RUsageTiming Zero();
static RUsageTiming Min();
static RUsageTiming Max();
// Returns the system timing stats for the specified rusage scope.
// NOTE: Clients must cache the r-value returned by `RUsageScope` static ctors
// to be able to call this (this is on purpose).
static RUsageTiming Snapshot(const RUsageScope& scope);
// Same as above, but using a custom timer. The caller is responsible for
// setting up and passing the same timer object to all Snapshot() calls to get
// consistent results.
static RUsageTiming Snapshot( //
const RUsageScope& scope, const ProcessTimer& timer);
// Comparisons. NOTE: `is_delta` is always ignored.
friend bool operator==(const RUsageTiming& t1, const RUsageTiming& t2);
friend bool operator!=(const RUsageTiming& t1, const RUsageTiming& t2);
friend bool operator<(const RUsageTiming& t1, const RUsageTiming& t2);
friend bool operator<=(const RUsageTiming& t1, const RUsageTiming& t2);
friend bool operator>(const RUsageTiming& t1, const RUsageTiming& t2);
friend bool operator>=(const RUsageTiming& t1, const RUsageTiming& t2);
// Returns the low-water resource usage between the two args.
static RUsageTiming LowWater(const RUsageTiming& t1, const RUsageTiming& t2);
// Returns the high-water value between the two args.
static RUsageTiming HighWater(const RUsageTiming& t1, const RUsageTiming& t2);
// Returns the value with `is_delta` set to true. Useful for signed logging.
friend RUsageTiming operator+(const RUsageTiming& t);
// Returns the negated value with `is_delta` set to true.
friend RUsageTiming operator-(const RUsageTiming& t);
// Returns the signed delta between two stats, with `is_delta` set to true.
friend RUsageTiming operator-(const RUsageTiming& t1, const RUsageTiming& t2);
// Returns the sum of two stats, with `is_delta` set to true iff `t1` or `t2`
// or both are deltas.
friend RUsageTiming operator+(const RUsageTiming& t1, const RUsageTiming& t2);
// Returns a RUsageTiming where every field is divided by `div`. `is_delta` is
// carried over from `t`.
friend RUsageTiming operator/(const RUsageTiming& t, int64_t div);
// Streams `t.ShortStr()`.
friend std::ostream& operator<<(std::ostream& os, const RUsageTiming& t);
//----------------------------------------------------------------------------
// Non-static methods
// Returns the metrics as short string. If `is_delta` is true, positive values
// will be prefixed with a '+'.
std::string ShortStr() const;
// Returns a formatted representation of the metrics. The format is fixed, so
// if multiple objects get printed with newline separators, they will form a
// table. If `is_delta` is true, positive values will be prefixed with a '+'.
std::string FormattedStr() const;
//----------------------------------------------------------------------------
// Public data
absl::Duration wall_time = absl::ZeroDuration();
absl::Duration user_time = absl::ZeroDuration();
absl::Duration sys_time = absl::ZeroDuration();
CpuUtilization cpu_utilization = 0.;
CpuHyperCores cpu_hyper_cores = 0.;
// If true, positive values will be printed with a '+'.
bool is_delta = false;
};
//------------------------------------------------------------------------------
// RUsageMemory
//
// An interface to measure, store, manipulate, and log the system memory usage
// of a process or a thread.
//------------------------------------------------------------------------------
struct RUsageMemory {
//----------------------------------------------------------------------------
// Static factory ctors and friend operators
static RUsageMemory Zero();
static RUsageMemory Min();
static RUsageMemory Max();
// Returns the system memory stats for the specified rusage scope.
// NOTE: Clients must cache the r-value returned by `RUsageScope` static ctors
// to be able to call this (this is on purpose).
static RUsageMemory Snapshot(const RUsageScope& scope);
// Comparisons. NOTE: `is_delta` is always ignored.
friend bool operator==(const RUsageMemory& m1, const RUsageMemory& m2);
friend bool operator!=(const RUsageMemory& m1, const RUsageMemory& m2);
friend bool operator<(const RUsageMemory& m1, const RUsageMemory& m2);
friend bool operator<=(const RUsageMemory& m1, const RUsageMemory& m2);
friend bool operator>(const RUsageMemory& m1, const RUsageMemory& m2);
friend bool operator>=(const RUsageMemory& m1, const RUsageMemory& m2);
// Returns the low-water value between the two args.
static RUsageMemory LowWater(const RUsageMemory& m1, const RUsageMemory& m2);
// Returns the high-water value usage between the two args.
static RUsageMemory HighWater(const RUsageMemory& m1, const RUsageMemory& m2);
// Returns the value with `is_delta` set to true. Useful for signed logging.
friend RUsageMemory operator+(const RUsageMemory& m);
// Returns the negated value with `is_delta` set to true.
friend RUsageMemory operator-(const RUsageMemory& m);
// Returns the signed delta of two stats, with `is_delta` set to true.
friend RUsageMemory operator-(const RUsageMemory& m1, const RUsageMemory& m2);
// Returns the sum of two stats, with `is_delta` set to true iff `m1` or `m2`
// or both are deltas.
friend RUsageMemory operator+(const RUsageMemory& m1, const RUsageMemory& m2);
// Returns a value with every metric divided by `div`. `is_delta` is
// carried over from `m`.
friend RUsageMemory operator/(const RUsageMemory& m, int64_t div);
// Streams `m.ShortStr()`.
friend std::ostream& operator<<(std::ostream& os, const RUsageMemory& m);
//----------------------------------------------------------------------------
// Non-static methods
// Returns the metrics as short string. If `is_delta` is true, positive values
// will be prefixed with a '+'.
std::string ShortStr() const;
// Returns a formatted representation of the metrics. The format is fixed, so
// if multiple objects get printed with newline separators, they will form a
// table. If `is_delta` is true, positive values will be prefixed with a '+'.
std::string FormattedStr() const;
//----------------------------------------------------------------------------
// Data
// Memory sizes are all in bytes. For the meaning of these, cf. `man proc` or
// https://man7.org/linux/man-pages/man5/proc.5.html, sections
// /proc/[pid]/{stat,statm,status}.
MemSize mem_vsize = 0;
MemSize mem_vpeak = 0;
MemSize mem_rss = 0;
MemSize mem_data = 0;
MemSize mem_shared = 0;
// If true, positive values will be printed with a '+'.
bool is_delta = false;
};
//------------------------------------------------------------------------------
// Pretty-printing of the stats
//------------------------------------------------------------------------------
// Formats `duration` as the most compact human-readable string. Differences
// from absl::FormatDuration():
// - Durations up to 1s are rounded up to whole numbers of ns/us/ms.
// - Durations longer than 1s are rounded up to 2 decimals and are never
// converted to hours/minutes/seconds.
// - Positive durations can be prefixed with a '+' (useful to indicate that the
// value is a positive delta).
std::string FormatInOptimalUnits(absl::Duration duration, bool always_signed);
// Formats `bytes` as the most compact human-readable string in SI units.
// `always_signed` prints '+' before positive numbers (useful to indicate
// positive deltas).
std::string FormatInOptimalUnits(MemSize bytes, bool always_signed);
// Formats CPU utilization as a percentage.
// `always_signed` prints '+' before positive numbers (useful to indicate
// positive deltas).
std::string FormatInOptimalUnits(CpuUtilization util, bool always_signed);
// Formats CPU hypercores with decimal precision.
// `always_signed` prints '+' before positive numbers (useful to indicate
// positive deltas).
std::string FormatInOptimalUnits(CpuHyperCores cores, bool always_signed);
} // namespace centipede::perf
#endif // THIRD_PARTY_CENTIPEDE_RUSAGE_STATS_H_