blob: 1aaaff02778644e5b7037f1fb41f5a14ad20e0d8 [file] [log] [blame]
#ifndef AOS_UTIL_TOP_H_
#define AOS_UTIL_TOP_H_
#include <stdint.h>
#include <sys/types.h>
#include <chrono>
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include "flatbuffers/buffer.h"
#include "flatbuffers/flatbuffer_builder.h"
#include "aos/containers/ring_buffer.h"
#include "aos/events/event_loop.h"
#include "aos/time/time.h"
#include "aos/util/process_info_generated.h"
namespace aos::util {
// ProcStat is a struct to hold all the fields available in /proc/[pid]/stat.
// Currently we only use a small subset of the fields. See man 5 proc for
// details on what the fields are--these are in the same order as they appear in
// the stat file.
//
// Things are signed or unsigned based on whether they are listed
// as signed/unsigned in man 5 proc. We just make everything 64 bits wide
// because otherwise we have to write out way too many casts everywhere.
struct ProcStat {
int pid;
std::string name;
char state;
int64_t parent_pid;
int64_t group_id;
int64_t session_id;
int64_t tty;
int64_t tpgid;
uint64_t kernel_flags;
uint64_t minor_faults;
uint64_t children_minor_faults;
uint64_t major_faults;
uint64_t children_major_faults;
uint64_t user_mode_ticks;
uint64_t kernel_mode_ticks;
int64_t children_user_mode_ticks;
int64_t children_kernel_mode_ticks;
int64_t priority;
int64_t nice;
int64_t num_threads;
int64_t itrealvalue; // always zero.
uint64_t start_time_ticks;
uint64_t virtual_memory_size;
// Number of pages in real memory.
int64_t resident_set_size;
uint64_t rss_soft_limit;
uint64_t start_code_address;
uint64_t end_code_address;
uint64_t start_stack_address;
uint64_t stack_pointer;
uint64_t instruction_pointer;
uint64_t signal_bitmask;
uint64_t blocked_signals;
uint64_t ignored_signals;
uint64_t caught_signals;
uint64_t wchan;
// swap_pages fields are not maintained.
uint64_t swap_pages;
uint64_t children_swap_pages;
int64_t exit_signal;
// CPU number last exitted on.
int64_t processor;
// Zero for non-realtime processes.
uint64_t rt_priority;
uint64_t scheduling_policy;
// Aggregated block I/O delay.
uint64_t block_io_delay_ticks;
uint64_t guest_ticks;
uint64_t children_guest_ticks;
uint64_t start_data_address;
uint64_t end_data_address;
uint64_t start_brk_address;
uint64_t start_arg_address;
uint64_t end_arg_address;
uint64_t start_env_address;
uint64_t end_env_address;
int64_t exit_code;
};
// Retrieves the statistics for a particular process or thread. If only a pid is
// provided, it reads the process's stat file at /proc/[pid]/stat. If both pid
// and tid are provided, it reads the thread's stat file at
// /proc/[pid]/task/[tid]/stat. Returns nullopt if unable to read or parse the
// file.
std::optional<ProcStat> ReadProcStat(pid_t pid,
std::optional<pid_t> tid = std::nullopt);
// This class provides a basic utility for retrieving general performance
// information on running processes (named after the top utility). It can either
// be used to directly get information on individual processes (via
// set_track_pids()) or used to track a list of the top N processes with the
// highest CPU usage.
// Note that this currently relies on sampling processes in /proc every second
// and using the differences between the two readings to calculate CPU usage.
// For crash-looping processees or other situations with highly variable or
// extremely short-lived loads, this may do a poor job of capturing information.
class Top {
public:
// Set the ring buffer size to 2 so we can keep track of a current reading and
// previous reading.
static constexpr int kRingBufferSize = 2;
// A snapshot of the resource usage of a process.
struct Reading {
aos::monotonic_clock::time_point reading_time;
std::chrono::nanoseconds total_run_time;
// Memory usage in bytes.
uint64_t memory_usage;
};
struct ThreadReading {
aos::monotonic_clock::time_point reading_time;
std::chrono::nanoseconds total_run_time;
};
struct ThreadReadings {
aos::RingBuffer<ThreadReading, kRingBufferSize> readings;
double cpu_percent;
std::string name; // Name of the thread
aos::monotonic_clock::time_point start_time;
ThreadState state;
};
// All the information we have about a process.
struct ProcessReadings {
std::string name;
aos::monotonic_clock::time_point start_time;
// CPU usage is based on the past two readings.
double cpu_percent;
// True if this is a kernel thread, false if this is a userspace thread.
bool kthread;
// Last 2 readings
aos::RingBuffer<Reading, kRingBufferSize> readings;
std::map<pid_t, ThreadReadings> thread_readings;
};
// An enum for track_threads with enabled and disabled
enum class TrackThreadsMode {
kDisabled,
kEnabled // Track the thread ids for each process.
};
// An enum for track_per_thread_info with enabled and disabled
enum class TrackPerThreadInfoMode {
kDisabled,
kEnabled // Track statistics for each thread.
};
// Constructs a new Top object.
// event_loop: The event loop object to be used.
// track_threads: Set to true to track the thread IDs for each process.
// track_per_thread_info: Set to true to track statistics for each thread.
Top(aos::EventLoop *event_loop, TrackThreadsMode track_threads,
TrackPerThreadInfoMode track_per_thread_info_mode);
// Set whether to track all the top processes (this will result in us having
// to track every single process on the system, so that we can sort them).
void set_track_top_processes(bool track_all) { track_all_ = track_all; }
void set_on_reading_update(std::function<void()> fn) {
on_reading_update_ = std::move(fn);
}
// Specify a set of individual processes to track statistics for.
// This can be changed at run-time, although it may take up to kSamplePeriod
// to have full statistics on all the relevant processes, since we need at
// least two samples to estimate CPU usage.
void set_track_pids(const std::set<pid_t> &pids) { pids_to_track_ = pids; }
// Retrieve statistics for the specified process. Will return the null offset
// of no such pid is being tracked.
flatbuffers::Offset<ProcessInfo> InfoForProcess(
flatbuffers::FlatBufferBuilder *fbb, pid_t pid);
// Returns information on up to n processes, sorted by CPU usage.
flatbuffers::Offset<TopProcessesFbs> TopProcesses(
flatbuffers::FlatBufferBuilder *fbb, int n);
const std::map<pid_t, ProcessReadings> &readings() const { return readings_; }
private:
// Rate at which to sample /proc/[pid]/stat.
static constexpr std::chrono::seconds kSamplePeriod{1};
std::chrono::nanoseconds TotalProcessTime(const ProcStat &proc_stat);
aos::monotonic_clock::time_point ProcessStartTime(const ProcStat &proc_stat);
uint64_t RealMemoryUsage(const ProcStat &proc_stat);
void UpdateReadings();
void UpdateThreadReadings(pid_t pid, ProcessReadings &process);
// Adds thread ids for the given pid to the pids set,
// if we are tracking threads.
void MaybeAddThreadIds(pid_t pid, std::set<pid_t> *pids);
aos::EventLoop *event_loop_;
// Length of a clock tick (used to convert from raw numbers in /proc to actual
// times).
const std::chrono::nanoseconds clock_tick_;
// Page size, in bytes, on the current system.
const long page_size_;
std::set<pid_t> pids_to_track_;
bool track_all_ = false;
TrackThreadsMode track_threads_;
// Whether to include per-thread information in the top processes.
TrackPerThreadInfoMode track_per_thread_info_;
std::map<pid_t, ProcessReadings> readings_;
std::function<void()> on_reading_update_;
};
} // namespace aos::util
#endif // AOS_UTIL_TOP_H_