using namespace std;
ostream &
-BeesHeatmap::print(ostream &os) const
+BeesHeatmap::print_cells(ostream &os, const string &name,
+ const uint64_t cells[X_BUCKETS][T_BUCKETS])
{
- // Snapshot the populated bounds.
+ // Find the populated bounds.
int x_lo = X_BUCKETS, x_hi = -1, t_lo = T_BUCKETS, t_hi = -1;
- uint64_t cell[X_BUCKETS][T_BUCKETS];
for (int xi = 0; xi < X_BUCKETS; ++xi) {
for (int ti = 0; ti < T_BUCKETS; ++ti) {
- const uint64_t c =
- m_cells[xi][ti].load(memory_order_relaxed);
- cell[xi][ti] = c;
- if (c) {
+ if (cells[xi][ti]) {
x_lo = min(x_lo, xi);
x_hi = max(x_hi, xi);
t_lo = min(t_lo, ti);
}
}
}
- os << "heatmap " << m_name;
+ os << "heatmap " << name;
if (x_hi < 0) {
os << ": (empty)\n";
return os;
row.push_back(Table::Text(x_label(xi)));
uint64_t row_total = 0;
for (int ti = t_lo; ti <= t_hi; ++ti) {
- const uint64_t c = cell[xi][ti];
+ const uint64_t c = cells[xi][ti];
row_total += c;
row.push_back(c ? Table::Number(c) : Table::Text("."));
}
for (int ti = t_lo; ti <= t_hi; ++ti) {
uint64_t col_total = 0;
for (int xi = x_lo; xi <= x_hi; ++xi) {
- col_total += cell[xi][ti];
+ col_total += cells[xi][ti];
}
grand += col_total;
footer.push_back(Table::Number(col_total));
os << table;
return os;
}
+
+BeesHeatmap::Snapshot
+BeesHeatmap::snapshot() const
+{
+ Snapshot snap;
+ snap.m_name = m_name;
+ for (int xi = 0; xi < X_BUCKETS; ++xi) {
+ for (int ti = 0; ti < T_BUCKETS; ++ti) {
+ snap.m_cells[xi][ti] =
+ m_cells[xi][ti].load(memory_order_relaxed);
+ }
+ }
+ return snap;
+}
+
+ostream &
+BeesHeatmap::print(ostream &os, const Snapshot &snap)
+{
+ return print_cells(os, snap.m_name, snap.m_cells);
+}
+
+uint64_t
+BeesHeatmap::Snapshot::count() const
+{
+ uint64_t total = 0;
+ for (int xi = 0; xi < X_BUCKETS; ++xi) {
+ for (int ti = 0; ti < T_BUCKETS; ++ti) {
+ total += m_cells[xi][ti];
+ }
+ }
+ return total;
+}
+
+BeesHeatmap::Snapshot
+BeesHeatmap::Snapshot::operator-(const Snapshot &older) const
+{
+ Snapshot diff;
+ diff.m_name = m_name;
+ for (int xi = 0; xi < X_BUCKETS; ++xi) {
+ for (int ti = 0; ti < T_BUCKETS; ++ti) {
+ diff.m_cells[xi][ti] = m_cells[xi][ti] - older.m_cells[xi][ti];
+ }
+ }
+ return diff;
+}
/// input-region count, both against the same elapsed time. Only print()
/// reaches into libcrucible (Table), and only for layout.
class BeesHeatmap {
+ // Bucket geometry. Declared first because Snapshot sizes its grid from
+ // these constants.
+ //
+ // Magnitude exponents 2^0 .. 2^X_EXP_MAX, plus a dedicated zero bucket.
+ // 2^32 is far more than we expect to count; the spare rows are a few KB
+ // and print() hides the unpopulated ones anyway.
+ static constexpr int X_EXP_MAX = 32; // up to ~4 billion
+ static constexpr int X_BUCKETS = X_EXP_MAX + 2; // [0] = zero, [1..] = 2^(k-1)
+ // Time exponents 2^T_EXP_MIN .. 2^T_EXP_MAX seconds (~1ms .. ~12 days).
+ // The top end covers the longest Task lifetimes (which include queueing,
+ // restarts, and loadavg throttling, so a 20-hour wall time is normal);
+ // the bottom bucket absorbs every sub-millisecond plan.
+ static constexpr int T_EXP_MIN = -10;
+ static constexpr int T_EXP_MAX = 20;
+ static constexpr int T_BUCKETS = T_EXP_MAX - T_EXP_MIN + 1;
+
public:
+ /// A detached, copiable value-copy of a heatmap's cells at one instant.
+ /// BeesHeatmap itself holds atomic counters and cannot be copied;
+ /// snapshot() reads them into one of these so a report can diff two
+ /// instants (and print the difference) without resetting the live
+ /// counters that the planner threads keep writing.
+ class Snapshot {
+ friend class BeesHeatmap;
+ std::string m_name;
+ uint64_t m_cells[X_BUCKETS][T_BUCKETS] {};
+ public:
+ /// Total number of events in this snapshot.
+ uint64_t count() const;
+ /// Cellwise difference (this - @p older). Only ever applied to two
+ /// snapshots of the same monotonic counter, so cells never underflow.
+ Snapshot operator-(const Snapshot &older) const;
+ };
+
explicit BeesHeatmap(std::string name) :
m_name(std::move(name)) {}
.fetch_add(1, std::memory_order_relaxed);
}
- /// Zero all buckets. Safe to call concurrently with add()/print();
- /// individual cells reset atomically, so a concurrent add() is either
- /// counted or not, never corrupted.
- void reset()
- {
- for (int xi = 0; xi < X_BUCKETS; ++xi) {
- for (int ti = 0; ti < T_BUCKETS; ++ti) {
- m_cells[xi][ti].store(0, std::memory_order_relaxed);
- }
- }
- }
-
/// Total number of events recorded.
uint64_t count() const
{
const std::string &name() const { return m_name; }
- /// Render the populated sub-grid (magnitude rows x time columns) with
- /// row and column totals. An empty heatmap prints a single line.
- std::ostream &print(std::ostream &os) const;
+ /// Copy the live atomic cells into a detached snapshot.
+ Snapshot snapshot() const;
-private:
- // Magnitude exponents 2^0 .. 2^X_EXP_MAX, plus a dedicated zero bucket.
- // 2^32 is far more than we expect to count; the spare rows are a few KB
- // and print() hides the unpopulated ones anyway.
- static constexpr int X_EXP_MAX = 32; // up to ~4 billion
- static constexpr int X_BUCKETS = X_EXP_MAX + 2; // [0] = zero, [1..] = 2^(k-1)
- // Time exponents 2^T_EXP_MIN .. 2^T_EXP_MAX seconds (~1ms .. ~12 days).
- // The top end covers the longest Task lifetimes (which include queueing,
- // restarts, and loadavg throttling, so a 20-hour wall time is normal);
- // the bottom bucket absorbs every sub-millisecond plan.
- static constexpr int T_EXP_MIN = -10;
- static constexpr int T_EXP_MAX = 20;
- static constexpr int T_BUCKETS = T_EXP_MAX - T_EXP_MIN + 1;
+ /// Render @p snap as a populated sub-grid (magnitude rows x time columns)
+ /// with row and column totals. An empty snapshot prints a single line.
+ static std::ostream &print(std::ostream &os, const Snapshot &snap);
+
+ /// Convenience: snapshot the live heatmap and render it.
+ std::ostream &print(std::ostream &os) const { return print(os, snapshot()); }
+private:
static int x_bucket(uint64_t x)
{
if (x == 0) {
return buf;
}
+ // Shared grid formatter used by both print() entry points.
+ static std::ostream &print_cells(std::ostream &os, const std::string &name,
+ const uint64_t cells[X_BUCKETS][T_BUCKETS]);
+
std::string m_name;
std::atomic<uint64_t> m_cells[X_BUCKETS][T_BUCKETS] {};
};