1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef PARTITION_ALLOC_STARSCAN_STATS_COLLECTOR_H_
6 #define PARTITION_ALLOC_STARSCAN_STATS_COLLECTOR_H_
7 
8 #include <array>
9 #include <atomic>
10 #include <functional>
11 #include <mutex>
12 #include <string>
13 #include <type_traits>
14 #include <unordered_map>
15 #include <utility>
16 
17 #include "partition_alloc/internal_allocator_forward.h"
18 #include "partition_alloc/partition_alloc_base/threading/platform_thread.h"
19 #include "partition_alloc/partition_alloc_base/time/time.h"
20 #include "partition_alloc/partition_alloc_check.h"
21 #include "partition_alloc/starscan/starscan_fwd.h"
22 
23 namespace partition_alloc {
24 
25 class StatsReporter;
26 
27 namespace internal {
28 
29 #define FOR_ALL_PCSCAN_SCANNER_SCOPES(V) \
30   V(Clear)                               \
31   V(Scan)                                \
32   V(Sweep)                               \
33   V(Overall)
34 
35 #define FOR_ALL_PCSCAN_MUTATOR_SCOPES(V) \
36   V(Clear)                               \
37   V(ScanStack)                           \
38   V(Scan)                                \
39   V(Overall)
40 
41 class StatsCollector final {
42  public:
43   enum class ScannerId {
44 #define DECLARE_ENUM(name) k##name,
45     FOR_ALL_PCSCAN_SCANNER_SCOPES(DECLARE_ENUM)
46 #undef DECLARE_ENUM
47         kNumIds,
48   };
49 
50   enum class MutatorId {
51 #define DECLARE_ENUM(name) k##name,
52     FOR_ALL_PCSCAN_MUTATOR_SCOPES(DECLARE_ENUM)
53 #undef DECLARE_ENUM
54         kNumIds,
55   };
56 
57   template <Context context>
58   using IdType =
59       std::conditional_t<context == Context::kMutator, MutatorId, ScannerId>;
60 
61   // We don't immediately trace events, but instead defer it until scanning is
62   // done. This is needed to avoid unpredictable work that can be done by traces
63   // (e.g. recursive mutex lock).
64   struct DeferredTraceEvent {
65     base::TimeTicks start_time;
66     base::TimeTicks end_time;
67   };
68 
69   // Thread-safe hash-map that maps thread id to scanner events. Doesn't
70   // accumulate events, i.e. every event can only be registered once.
71   template <Context context>
72   class DeferredTraceEventMap final {
73    public:
74     using IdType = StatsCollector::IdType<context>;
75     using PerThreadEvents =
76         std::array<DeferredTraceEvent, static_cast<size_t>(IdType::kNumIds)>;
77     using UnderlyingMap =
78         std::unordered_map<internal::base::PlatformThreadId,
79                            PerThreadEvents,
80                            std::hash<internal::base::PlatformThreadId>,
81                            std::equal_to<>,
82                            internal::InternalAllocator<
83                                std::pair<const internal::base::PlatformThreadId,
84                                          PerThreadEvents>>>;
85 
86     inline void RegisterBeginEventFromCurrentThread(IdType id);
87     inline void RegisterEndEventFromCurrentThread(IdType id);
88 
get_underlying_map_unsafe()89     const UnderlyingMap& get_underlying_map_unsafe() const { return events_; }
90 
91    private:
92     std::mutex mutex_;
93     UnderlyingMap events_;
94   };
95 
96   template <Context context>
97   class Scope final {
98    public:
Scope(StatsCollector & stats,IdType<context> type)99     Scope(StatsCollector& stats, IdType<context> type)
100         : stats_(stats), type_(type) {
101       stats_.RegisterBeginEventFromCurrentThread(type);
102     }
103 
104     Scope(const Scope&) = delete;
105     Scope& operator=(const Scope&) = delete;
106 
~Scope()107     ~Scope() { stats_.RegisterEndEventFromCurrentThread(type_); }
108 
109    private:
110     StatsCollector& stats_;
111     IdType<context> type_;
112   };
113 
114   using ScannerScope = Scope<Context::kScanner>;
115   using MutatorScope = Scope<Context::kMutator>;
116 
117   StatsCollector(const char* process_name, size_t quarantine_last_size);
118 
119   StatsCollector(const StatsCollector&) = delete;
120   StatsCollector& operator=(const StatsCollector&) = delete;
121 
122   ~StatsCollector();
123 
IncreaseSurvivedQuarantineSize(size_t size)124   void IncreaseSurvivedQuarantineSize(size_t size) {
125     survived_quarantine_size_.fetch_add(size, std::memory_order_relaxed);
126   }
survived_quarantine_size()127   size_t survived_quarantine_size() const {
128     return survived_quarantine_size_.load(std::memory_order_relaxed);
129   }
130 
IncreaseSweptSize(size_t size)131   void IncreaseSweptSize(size_t size) { swept_size_ += size; }
swept_size()132   size_t swept_size() const { return swept_size_; }
133 
IncreaseDiscardedQuarantineSize(size_t size)134   void IncreaseDiscardedQuarantineSize(size_t size) {
135     discarded_quarantine_size_ += size;
136   }
137 
138   base::TimeDelta GetOverallTime() const;
139   void ReportTracesAndHists(partition_alloc::StatsReporter& reporter) const;
140 
141  private:
142   using MetadataString = std::basic_string<char,
143                                            std::char_traits<char>,
144                                            internal::InternalAllocator<char>>;
145 
146   MetadataString ToUMAString(ScannerId id) const;
147   MetadataString ToUMAString(MutatorId id) const;
148 
RegisterBeginEventFromCurrentThread(MutatorId id)149   void RegisterBeginEventFromCurrentThread(MutatorId id) {
150     mutator_trace_events_.RegisterBeginEventFromCurrentThread(id);
151   }
RegisterEndEventFromCurrentThread(MutatorId id)152   void RegisterEndEventFromCurrentThread(MutatorId id) {
153     mutator_trace_events_.RegisterEndEventFromCurrentThread(id);
154   }
RegisterBeginEventFromCurrentThread(ScannerId id)155   void RegisterBeginEventFromCurrentThread(ScannerId id) {
156     scanner_trace_events_.RegisterBeginEventFromCurrentThread(id);
157   }
RegisterEndEventFromCurrentThread(ScannerId id)158   void RegisterEndEventFromCurrentThread(ScannerId id) {
159     scanner_trace_events_.RegisterEndEventFromCurrentThread(id);
160   }
161 
162   template <Context context>
163   base::TimeDelta GetTimeImpl(const DeferredTraceEventMap<context>& event_map,
164                               IdType<context> id) const;
165 
166   template <Context context>
167   void ReportTracesAndHistsImpl(
168       partition_alloc::StatsReporter& reporter,
169       const DeferredTraceEventMap<context>& event_map) const;
170 
171   void ReportSurvivalRate(partition_alloc::StatsReporter& reporter) const;
172 
173   DeferredTraceEventMap<Context::kMutator> mutator_trace_events_;
174   DeferredTraceEventMap<Context::kScanner> scanner_trace_events_;
175 
176   std::atomic<size_t> survived_quarantine_size_{0u};
177   size_t swept_size_ = 0u;
178   size_t discarded_quarantine_size_ = 0u;
179   const char* process_name_ = nullptr;
180   const size_t quarantine_last_size_ = 0u;
181 };
182 
183 template <Context context>
184 inline void StatsCollector::DeferredTraceEventMap<
RegisterBeginEventFromCurrentThread(IdType id)185     context>::RegisterBeginEventFromCurrentThread(IdType id) {
186   std::lock_guard<std::mutex> lock(mutex_);
187   const auto tid = base::PlatformThread::CurrentId();
188   const auto now = base::TimeTicks::Now();
189   auto& event_array = events_[tid];
190   auto& event = event_array[static_cast<size_t>(id)];
191   PA_DCHECK(event.start_time.is_null());
192   PA_DCHECK(event.end_time.is_null());
193   event.start_time = now;
194 }
195 
196 template <Context context>
197 inline void StatsCollector::DeferredTraceEventMap<
RegisterEndEventFromCurrentThread(IdType id)198     context>::RegisterEndEventFromCurrentThread(IdType id) {
199   std::lock_guard<std::mutex> lock(mutex_);
200   const auto tid = base::PlatformThread::CurrentId();
201   const auto now = base::TimeTicks::Now();
202   auto& event_array = events_[tid];
203   auto& event = event_array[static_cast<size_t>(id)];
204   PA_DCHECK(!event.start_time.is_null());
205   PA_DCHECK(event.end_time.is_null());
206   event.end_time = now;
207 }
208 
ToUMAString(ScannerId id)209 inline StatsCollector::MetadataString StatsCollector::ToUMAString(
210     ScannerId id) const {
211   PA_DCHECK(process_name_);
212   const MetadataString process_name = process_name_;
213   switch (id) {
214     case ScannerId::kClear:
215       return "PA.PCScan." + process_name + ".Scanner.Clear";
216     case ScannerId::kScan:
217       return "PA.PCScan." + process_name + ".Scanner.Scan";
218     case ScannerId::kSweep:
219       return "PA.PCScan." + process_name + ".Scanner.Sweep";
220     case ScannerId::kOverall:
221       return "PA.PCScan." + process_name + ".Scanner";
222     case ScannerId::kNumIds:
223       __builtin_unreachable();
224   }
225 }
226 
ToUMAString(MutatorId id)227 inline StatsCollector::MetadataString StatsCollector::ToUMAString(
228     MutatorId id) const {
229   PA_DCHECK(process_name_);
230   const MetadataString process_name = process_name_;
231   switch (id) {
232     case MutatorId::kClear:
233       return "PA.PCScan." + process_name + ".Mutator.Clear";
234     case MutatorId::kScanStack:
235       return "PA.PCScan." + process_name + ".Mutator.ScanStack";
236     case MutatorId::kScan:
237       return "PA.PCScan." + process_name + ".Mutator.Scan";
238     case MutatorId::kOverall:
239       return "PA.PCScan." + process_name + ".Mutator";
240     case MutatorId::kNumIds:
241       __builtin_unreachable();
242   }
243 }
244 
245 #undef FOR_ALL_PCSCAN_MUTATOR_SCOPES
246 #undef FOR_ALL_PCSCAN_SCANNER_SCOPES
247 
248 }  // namespace internal
249 }  // namespace partition_alloc
250 
251 #endif  // PARTITION_ALLOC_STARSCAN_STATS_COLLECTOR_H_
252