statistics.hpp
1 
5 #pragma once
6 #include <atomic>
7 #include <concepts>
8 #include <cstddef>
9 #include <filesystem>
10 #include <initializer_list>
11 #include <limits>
12 #include <map>
13 #include <memory>
14 #include <mutex>
15 #include <optional>
16 #include <ostream>
17 #include <span>
18 #include <string>
19 #include <string_view>
20 #include <utility>
21 #include <vector>
22 
23 #include <rapidsmpf/config.hpp>
24 #include <rapidsmpf/error.hpp>
25 #include <rapidsmpf/memory/memory_type.hpp>
26 #include <rapidsmpf/memory/pinned_memory_resource.hpp>
27 #include <rapidsmpf/memory/resource_types.hpp>
28 #include <rapidsmpf/rmm_resource_adaptor.hpp>
29 #include <rapidsmpf/utils/misc.hpp>
30 
31 namespace rapidsmpf {
32 
33 class StreamOrderedTiming;
34 
74 class Statistics : public std::enable_shared_from_this<Statistics> {
75  public:
102  enum class Formatter : std::uint8_t {
103  Default = 0,
104  Bytes,
105  Duration,
106  HitRate,
107  MemoryThroughput,
108  _Count,
109  };
110 
115  enum class Mode : std::uint8_t {
116  Enabled,
117  Disabled,
118  };
119 
127  static std::shared_ptr<Statistics> create(Mode mode = Mode::Enabled);
128 
134  static std::shared_ptr<Statistics> disabled() {
135  return create(Mode::Disabled);
136  }
137 
145  static std::shared_ptr<Statistics> from_options(config::Options options);
146 
147  ~Statistics() noexcept;
148 
149  // `Statistics` is owned exclusively through `std::shared_ptr`. Use
150  // `create()` or `from_options()` to construct instances.
151  Statistics(Statistics const&) = delete;
152  Statistics& operator=(Statistics const&) = delete;
153  Statistics(Statistics&&) = delete;
154  Statistics& operator=(Statistics&&) = delete;
155 
161  bool enabled() const noexcept {
162  return enabled_.load(std::memory_order_acquire);
163  }
164 
168  void enable() noexcept {
169  enabled_.store(true, std::memory_order_release);
170  }
171 
175  void disable() noexcept {
176  enabled_.store(false, std::memory_order_release);
177  }
178 
188  struct ReportArgs {
192  std::optional<any_device_resource> mr = std::nullopt;
195  std::optional<any_host_device_resource> pinned_mr = std::nullopt;
197  std::string_view header = "Statistics:";
198  };
199 
218  std::string report(ReportArgs report_args) const;
219 
224  std::string report() const {
225  return report(ReportArgs{});
226  }
227 
240  void write_json(std::ostream& os) const;
241 
248  void write_json(std::filesystem::path const& filepath) const;
249 
257  [[nodiscard]] std::shared_ptr<Statistics> copy() const;
258 
266  [[nodiscard]] std::vector<std::uint8_t> serialize() const;
267 
277  [[nodiscard]] static std::shared_ptr<Statistics> deserialize(
278  std::span<std::uint8_t const> data
279  );
280 
302  [[nodiscard]] static std::shared_ptr<Statistics> merge(
303  std::span<std::shared_ptr<Statistics> const> stats
304  );
305 
312  class Stat {
313  public:
317  Stat() = default;
318 
326  Stat(std::size_t count, double value, double max);
327 
335  auto operator<=>(Stat const&) const noexcept = default;
336 
342  void add(double value);
343 
349  [[nodiscard]] std::size_t count() const noexcept;
350 
356  [[nodiscard]] double value() const noexcept;
357 
364  [[nodiscard]] double max() const noexcept;
365 
374  [[nodiscard]] static constexpr std::size_t serialized_size() noexcept {
375  return sizeof(std::uint64_t) + sizeof(double) + sizeof(double);
376  }
377 
385  std::uint8_t* serialize(std::uint8_t* out) const;
386 
396  [[nodiscard]] static std::pair<Stat, std::span<std::uint8_t const>> deserialize(
397  std::span<std::uint8_t const> data
398  );
399 
408  [[nodiscard]] Stat merge(Stat const& other) const;
409 
410  private:
411  std::size_t count_{0};
412  double value_{0};
413  double max_{-std::numeric_limits<double>::infinity()};
414  };
415 
422  Stat get_stat(std::string const& name) const;
423 
434  void add_stat(std::string const& name, double value);
435 
453  std::string const& report_entry_name,
454  std::initializer_list<std::string_view> stat_names,
455  Formatter formatter
456  );
457 
458  // clang-format off
464  // clang-format on
466  std::string const& report_entry_name,
467  std::vector<std::string> stat_names,
468  Formatter formatter
469  );
470 
481  void add_bytes_stat(std::string const& name, std::size_t nbytes);
482 
493  void add_duration_stat(std::string const& name, Duration seconds);
494 
516  MemoryType src, MemoryType dst, std::size_t nbytes, StreamOrderedTiming&& timing
517  );
518 
537  MemoryType mem_type, std::size_t nbytes, StreamOrderedTiming&& timing
538  );
539 
545  std::vector<std::string> list_stat_names() const;
546 
552  void clear();
553 
554  // TODO: move MemoryRecord and MemoryRecorder to RmmResourceAdaptor?
555 
559  struct MemoryRecord {
561  std::int64_t global_peak{0};
562  std::uint64_t num_calls{0};
563  };
564 
571  public:
573  MemoryRecorder() = default;
574 
585  std::shared_ptr<Statistics> stats, RmmResourceAdaptor mr, std::string name
586  );
587 
588  ~MemoryRecorder();
589 
590  MemoryRecorder(MemoryRecorder const&) = delete;
591  MemoryRecorder& operator=(MemoryRecorder const&) = delete;
592  MemoryRecorder(MemoryRecorder&&) = delete;
593  MemoryRecorder& operator=(MemoryRecorder&&) = delete;
594 
595  private:
597  std::optional<RmmResourceAdaptor> mr_{std::nullopt};
599  std::shared_ptr<Statistics> stats_{nullptr};
600  std::string name_{};
601  };
602 
613 
619  std::unordered_map<std::string, MemoryRecord> const& get_memory_records() const;
620 
621  private:
625  struct ReportEntry {
626  std::vector<std::string> stat_names;
627  Formatter formatter;
628  };
629 
630  explicit Statistics(bool enabled);
631 
632  mutable std::mutex mutex_;
633  std::atomic<bool> enabled_;
634  std::map<std::string, Stat> stats_;
635  std::map<std::string, ReportEntry> report_entries_;
636  std::unordered_map<std::string, MemoryRecord> memory_records_;
637 };
638 
652 template <typename T>
653 concept StatisticsProvider = requires(T const& t) {
654  {
655  t.statistics()
656  } noexcept -> std::same_as<std::shared_ptr<Statistics>>;
657 };
658 
688 // clang-format off
689 // Picks between _2 (stats, mr) and _3 (stats, mr, funcname) forms.
690 #define RAPIDSMPF_MEMORY_PROFILE_PICK_(_1, _2, _3, NAME, ...) NAME
691 #define RAPIDSMPF_MEMORY_PROFILE(...) \
692  RAPIDSMPF_MEMORY_PROFILE_PICK_( \
693  __VA_ARGS__, RAPIDSMPF_MEMORY_PROFILE_3, RAPIDSMPF_MEMORY_PROFILE_2, ~ \
694  )(__VA_ARGS__)
695 // clang-format on
696 
697 // Version with default function name (__func__)
698 #define RAPIDSMPF_MEMORY_PROFILE_2(stats, mr) \
699  RAPIDSMPF_MEMORY_PROFILE_3(stats, mr, __func__)
700 
701 // Version with custom function name
702 #define RAPIDSMPF_MEMORY_PROFILE_3(stats, mr, funcname) \
703  auto const& RAPIDSMPF_CONCAT(_rapidsmpf_memory_profile_stats_, __LINE__) = (stats); \
704  RAPIDSMPF_EXPECTS( \
705  RAPIDSMPF_CONCAT(_rapidsmpf_memory_profile_stats_, __LINE__) != nullptr, \
706  "RAPIDSMPF_MEMORY_PROFILE: stats must not be null" \
707  ); \
708  auto const RAPIDSMPF_CONCAT(_rapidsmpf_memory_recorder_, __LINE__) = \
709  RAPIDSMPF_CONCAT(_rapidsmpf_memory_profile_stats_, __LINE__) \
710  -> create_memory_recorder( \
711  (mr), \
712  std::string(__FILE__) + ":" + RAPIDSMPF_STRINGIFY(__LINE__) + "(" \
713  + std::string(funcname) + ")" \
714  )
715 
716 } // namespace rapidsmpf
A RMM memory resource adaptor tailored to RapidsMPF.
RAII-style object for scoped memory usage tracking.
Definition: statistics.hpp:570
MemoryRecorder(std::shared_ptr< Statistics > stats, RmmResourceAdaptor mr, std::string name)
Constructs an active MemoryRecorder. Pushes a scoped record at construction; the destructor pops it a...
MemoryRecorder()=default
Constructs a no-op MemoryRecorder.
Represents a single tracked statistic.
Definition: statistics.hpp:312
void add(double value)
Adds a value to this statistic.
std::size_t count() const noexcept
Returns the number of updates applied to this statistic.
double max() const noexcept
Returns the maximum value seen across all add() calls.
std::uint8_t * serialize(std::uint8_t *out) const
Serializes this Stat to a byte buffer.
Stat merge(Stat const &other) const
Merges another Stat into this one, returning the combined result.
Stat(std::size_t count, double value, double max)
Constructs a Stat with explicit field values.
double value() const noexcept
Returns the total accumulated value.
auto operator<=>(Stat const &) const noexcept=default
Three-way comparison operator.
static std::pair< Stat, std::span< std::uint8_t const > > deserialize(std::span< std::uint8_t const > data)
Deserializes a Stat from a byte buffer.
Stat()=default
Default-constructs a Stat.
static constexpr std::size_t serialized_size() noexcept
Returns the serialized size of this Stat in bytes.
Definition: statistics.hpp:374
Tracks statistics across rapidsmpf operations.
Definition: statistics.hpp:74
static std::shared_ptr< Statistics > from_options(config::Options options)
Construct from configuration options.
MemoryRecorder create_memory_recorder(any_device_resource mr, std::string name)
Creates a scoped memory recorder for the given name.
Formatter
Identifies a predefined formatter used by report().
Definition: statistics.hpp:102
@ _Count
Sentinel; must remain last.
Stat get_stat(std::string const &name) const
Retrieves a statistic by name.
void clear()
Clears all statistics.
static std::shared_ptr< Statistics > disabled()
Returns a disabled Statistics instance which can be enabled later.
Definition: statistics.hpp:134
void write_json(std::ostream &os) const
Writes a JSON representation of all collected statistics to a stream.
std::shared_ptr< Statistics > copy() const
Creates a deep copy of this Statistics object.
void add_bytes_stat(std::string const &name, std::size_t nbytes)
Adds a byte count to the named statistic.
std::string report() const
Overload with all-default options. Equivalent to report(ReportArgs{}).
Definition: statistics.hpp:224
void enable() noexcept
Enable statistics tracking for this instance.
Definition: statistics.hpp:168
void record_alloc(MemoryType mem_type, std::size_t nbytes, StreamOrderedTiming &&timing)
Record size and wall-clock duration for a buffer allocation.
void add_duration_stat(std::string const &name, Duration seconds)
Adds a duration to the named statistic.
void add_report_entry(std::string const &report_entry_name, std::vector< std::string > stat_names, Formatter formatter)
Associate a formatter with one or more named statistics for report rendering.
std::vector< std::string > list_stat_names() const
Get the names of all statistics.
static std::shared_ptr< Statistics > create(Mode mode=Mode::Enabled)
Creates a Statistics instance.
void add_stat(std::string const &name, double value)
Adds a numeric value to the named statistic.
static std::shared_ptr< Statistics > merge(std::span< std::shared_ptr< Statistics > const > stats)
Merge a set of Statistics into a new instance.
Mode
Selects whether a newly constructed Statistics instance tracks data or is a no-op.
Definition: statistics.hpp:115
@ Enabled
Statistics tracking is active.
@ Disabled
All operations are no-ops; can be toggled on via enable().
std::vector< std::uint8_t > serialize() const
Serializes the stats and report entries to a binary byte vector.
void record_copy(MemoryType src, MemoryType dst, std::size_t nbytes, StreamOrderedTiming &&timing)
Record byte count and wall-clock duration for a memory copy operation.
bool enabled() const noexcept
Checks if statistics tracking is enabled.
Definition: statistics.hpp:161
std::string report(ReportArgs report_args) const
Generates a formatted report of all collected statistics.
void write_json(std::filesystem::path const &filepath) const
Writes a JSON report of all collected statistics to a file.
static std::shared_ptr< Statistics > deserialize(std::span< std::uint8_t const > data)
Deserializes a Statistics object from a binary byte vector.
void disable() noexcept
Disable statistics tracking for this instance.
Definition: statistics.hpp:175
void add_report_entry(std::string const &report_entry_name, std::initializer_list< std::string_view > stat_names, Formatter formatter)
Associate a formatter with one or more named statistics for report rendering.
std::unordered_map< std::string, MemoryRecord > const & get_memory_records() const
Retrieves all memory profiling records stored by this instance.
Stream-ordered wall-clock timer that records its result into Statistics.
Manages configuration options for RapidsMPF operations.
Definition: config.hpp:144
RAPIDS Multi-Processor interfaces.
Definition: backend.hpp:14
concept StatisticsProvider
Satisfied by any type that exposes a statistics() method returning std::shared_ptr<Statistics> by val...
Definition: statistics.hpp:653
std::chrono::duration< double > Duration
Alias for a duration type representing time in seconds as a double.
Definition: misc.hpp:35
MemoryType
Enum representing the type of memory sorted in decreasing order of preference.
Definition: memory_type.hpp:16
cuda::mr::any_resource< cuda::mr::device_accessible > any_device_resource
Owning type-erased device memory resource.
Memory statistics for a specific scope.
Holds memory profiling information for a named scope.
Definition: statistics.hpp:559
std::int64_t global_peak
Peak global memory usage during the scope.
Definition: statistics.hpp:561
ScopedMemoryRecord scoped
Scoped memory stats.
Definition: statistics.hpp:560
std::uint64_t num_calls
Number of times the scope was invoked.
Definition: statistics.hpp:562
Named-argument struct for report().
Definition: statistics.hpp:188
std::optional< any_device_resource > mr
Definition: statistics.hpp:192
std::string_view header
Header line prepended to the report.
Definition: statistics.hpp:197
std::optional< any_host_device_resource > pinned_mr
Definition: statistics.hpp:195