xref: /aosp_15_r20/external/cronet/base/metrics/histogram_samples.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 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 BASE_METRICS_HISTOGRAM_SAMPLES_H_
6 #define BASE_METRICS_HISTOGRAM_SAMPLES_H_
7 
8 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <limits>
12 #include <memory>
13 #include <string>
14 #include <string_view>
15 
16 #include "base/atomicops.h"
17 #include "base/base_export.h"
18 #include "base/gtest_prod_util.h"
19 #include "base/memory/raw_ptr.h"
20 #include "base/metrics/histogram_base.h"
21 
22 namespace base {
23 
24 class Pickle;
25 class PickleIterator;
26 class SampleCountIterator;
27 
28 // HistogramSamples is a container storing all samples of a histogram. All
29 // elements must be of a fixed width to ensure 32/64-bit interoperability.
30 // If this structure changes, bump the version number for kTypeIdHistogram
31 // in persistent_histogram_allocator.cc.
32 //
33 // Note that though these samples are individually consistent (through the use
34 // of atomic operations on the counts), there is only "eventual consistency"
35 // overall when multiple threads are accessing this data. That means that the
36 // sum, redundant-count, etc. could be momentarily out-of-sync with the stored
37 // counts but will settle to a consistent "steady state" once all threads have
38 // exited this code.
39 class BASE_EXPORT HistogramSamples {
40  public:
41   // A single bucket and count. To fit within a single atomic on 32-bit build
42   // architectures, both |bucket| and |count| are limited in size to 16 bits.
43   // This limits the functionality somewhat but if an entry can't fit then
44   // the full array of samples can be allocated and used.
45   struct SingleSample {
46     uint16_t bucket;
47     uint16_t count;
48   };
49 
50   // A structure for managing an atomic single sample. Because this is generally
51   // used in association with other atomic values, the defined methods use
52   // acquire/release operations to guarantee ordering with outside values.
53   union BASE_EXPORT AtomicSingleSample {
AtomicSingleSample()54     AtomicSingleSample() : as_atomic(0) {}
AtomicSingleSample(subtle::Atomic32 rhs)55     explicit AtomicSingleSample(subtle::Atomic32 rhs) : as_atomic(rhs) {}
56 
57     // Returns the single sample in an atomic manner. This in an "acquire"
58     // load. The returned sample isn't shared and thus its fields can be safely
59     // accessed. If this object is disabled, this will return an empty sample
60     // (bucket count set to 0).
61     SingleSample Load() const;
62 
63     // Extracts and returns the single sample and changes it to |new_value| in
64     // an atomic manner. If this object is disabled, this will return an empty
65     // sample (bucket count set to 0).
66     SingleSample Extract(AtomicSingleSample new_value = AtomicSingleSample(0));
67 
68     // Like Extract() above, but also disables this object so that it will
69     // never accumulate another value. If this object is already disabled, this
70     // will return an empty sample (bucket count set to 0).
71     SingleSample ExtractAndDisable();
72 
73     // Adds a given count to the held bucket. If not possible, it returns false
74     // and leaves the parts unchanged. Once extracted/disabled, this always
75     // returns false. This in an "acquire/release" operation.
76     bool Accumulate(size_t bucket, HistogramBase::Count count);
77 
78     // Returns if the sample has been "disabled" (via Extract) and thus not
79     // allowed to accept further accumulation.
80     bool IsDisabled() const;
81 
82    private:
83     // union field: The actual sample bucket and count.
84     SingleSample as_parts;
85 
86     // union field: The sample as an atomic value. Atomic64 would provide
87     // more flexibility but isn't available on all builds. This can hold a
88     // special, internal "disabled" value indicating that it must not accept
89     // further accumulation.
90     subtle::Atomic32 as_atomic;
91   };
92 
93   // A structure of information about the data, common to all sample containers.
94   // Because of how this is used in persistent memory, it must be a POD object
95   // that makes sense when initialized to all zeros.
96   struct Metadata {
97     // Expected size for 32/64-bit check.
98     static constexpr size_t kExpectedInstanceSize = 24;
99 
100     // Initialized when the sample-set is first created with a value provided
101     // by the caller. It is generally used to identify the sample-set across
102     // threads and processes, though not necessarily uniquely as it is possible
103     // to have multiple sample-sets representing subsets of the data.
104     uint64_t id;
105 
106     // The sum of all the entries, effectivly the sum(sample * count) for
107     // all samples. Despite being atomic, no guarantees are made on the
108     // accuracy of this value; there may be races during histogram
109     // accumulation and snapshotting that we choose to accept. It should
110     // be treated as approximate.
111 #ifdef ARCH_CPU_64_BITS
112     subtle::Atomic64 sum;
113 #else
114     // 32-bit systems don't have atomic 64-bit operations. Use a basic type
115     // and don't worry about "shearing".
116     int64_t sum;
117 #endif
118 
119     // A "redundant" count helps identify memory corruption. It redundantly
120     // stores the total number of samples accumulated in the histogram. We
121     // can compare this count to the sum of the counts (TotalCount() function),
122     // and detect problems. Note, depending on the implementation of different
123     // histogram types, there might be races during histogram accumulation
124     // and snapshotting that we choose to accept. In this case, the tallies
125     // might mismatch even when no memory corruption has happened.
126     HistogramBase::AtomicCount redundant_count{0};
127 
128     // A single histogram value and associated count. This allows histograms
129     // that typically report only a single value to not require full storage
130     // to be allocated.
131     AtomicSingleSample single_sample;  // 32 bits
132   };
133 
134   // Because structures held in persistent memory must be POD, there can be no
135   // default constructor to clear the fields. This derived class exists just
136   // to clear them when being allocated on the heap.
137   struct BASE_EXPORT LocalMetadata : Metadata {
138     LocalMetadata();
139   };
140 
141   HistogramSamples(const HistogramSamples&) = delete;
142   HistogramSamples& operator=(const HistogramSamples&) = delete;
143   virtual ~HistogramSamples();
144 
145   virtual void Accumulate(HistogramBase::Sample value,
146                           HistogramBase::Count count) = 0;
147   virtual HistogramBase::Count GetCount(HistogramBase::Sample value) const = 0;
148   virtual HistogramBase::Count TotalCount() const = 0;
149 
150   void Add(const HistogramSamples& other);
151 
152   // Add from serialized samples.
153   bool AddFromPickle(PickleIterator* iter);
154 
155   void Subtract(const HistogramSamples& other);
156 
157   // Adds the samples from |other| while also resetting |other|'s sample counts
158   // to 0.
159   void Extract(HistogramSamples& other);
160 
161   // Returns an iterator to read the sample counts.
162   virtual std::unique_ptr<SampleCountIterator> Iterator() const = 0;
163 
164   // Returns a special kind of iterator that resets the underlying sample count
165   // to 0 when Get() is called. The returned iterator must be consumed
166   // completely before being destroyed, otherwise samples may be lost (this is
167   // enforced by a DCHECK in the destructor).
168   virtual std::unique_ptr<SampleCountIterator> ExtractingIterator() = 0;
169 
170   // Returns true if |this| is empty (has no samples, has a |sum| of zero, and
171   // has a |redundant_count| of zero), which is indicative that the caller does
172   // not need to process |this|.
173   // - Note 1: This should only be called when |this| is only manipulated on one
174   // thread at a time (e.g., the underlying data does not change on another
175   // thread). If this is not the case, then the returned value cannot be trusted
176   // at all.
177   // - Note 2: For performance reasons, this is not guaranteed to return the
178   // correct value. If false is returned, |this| may or may not be empty.
179   // However, if true is returned, then |this| is guaranteed to be empty (no
180   // false positives). Of course, this assumes that "Note 1" is respected.
181   //  - Note 3: The base implementation of this method checks for |sum| and
182   // |redundant_count|, but the child implementations should also check for
183   // samples.
184   virtual bool IsDefinitelyEmpty() const;
185 
186   void Serialize(Pickle* pickle) const;
187 
188   // Returns ASCII representation of histograms data for histogram samples.
189   // The dictionary returned will be of the form
190   // {"name":<string>, "header":<string>, "body": <string>}
191   base::Value::Dict ToGraphDict(std::string_view histogram_name,
192                                 int32_t flags) const;
193 
194   // Accessor functions.
id()195   uint64_t id() const { return meta_->id; }
sum()196   int64_t sum() const {
197 #ifdef ARCH_CPU_64_BITS
198     return subtle::NoBarrier_Load(&meta_->sum);
199 #else
200     return meta_->sum;
201 #endif
202   }
redundant_count()203   HistogramBase::Count redundant_count() const {
204     return subtle::NoBarrier_Load(&meta_->redundant_count);
205   }
206 
207  protected:
208   enum NegativeSampleReason {
209     SAMPLES_HAVE_LOGGED_BUT_NOT_SAMPLE,
210     SAMPLES_SAMPLE_LESS_THAN_LOGGED,
211     SAMPLES_ADDED_NEGATIVE_COUNT,
212     SAMPLES_ADD_WENT_NEGATIVE,
213     SAMPLES_ADD_OVERFLOW,
214     SAMPLES_ACCUMULATE_NEGATIVE_COUNT,
215     SAMPLES_ACCUMULATE_WENT_NEGATIVE,
216     DEPRECATED_SAMPLES_ACCUMULATE_OVERFLOW,
217     SAMPLES_ACCUMULATE_OVERFLOW,
218     MAX_NEGATIVE_SAMPLE_REASONS
219   };
220 
221   HistogramSamples(uint64_t id, Metadata* meta);
222   HistogramSamples(uint64_t id, std::unique_ptr<Metadata> meta);
223 
224   // Based on |op| type, add or subtract sample counts data from the iterator.
225   enum Operator { ADD, SUBTRACT };
226   virtual bool AddSubtractImpl(SampleCountIterator* iter, Operator op) = 0;
227 
228   // Accumulates to the embedded single-sample field if possible. Returns true
229   // on success, false otherwise. Sum and redundant-count are also updated in
230   // the success case.
231   bool AccumulateSingleSample(HistogramBase::Sample value,
232                               HistogramBase::Count count,
233                               size_t bucket);
234 
235   // Atomically adjust the sum and redundant-count.
236   void IncreaseSumAndCount(int64_t sum, HistogramBase::Count count);
237 
238   // Record a negative-sample observation and the reason why.
239   void RecordNegativeSample(NegativeSampleReason reason,
240                             HistogramBase::Count increment);
241 
single_sample()242   AtomicSingleSample& single_sample() { return meta_->single_sample; }
single_sample()243   const AtomicSingleSample& single_sample() const {
244     return meta_->single_sample;
245   }
246 
247   // Produces an actual graph (set of blank vs non blank char's) for a bucket.
248   static void WriteAsciiBucketGraph(double x_count,
249                                     int line_length,
250                                     std::string* output);
251 
252   // Writes textual description of the bucket contents (relative to histogram).
253   // Output is the count in the buckets, as well as the percentage.
254   void WriteAsciiBucketValue(HistogramBase::Count current,
255                              double scaled_sum,
256                              std::string* output) const;
257 
258   // Gets a body for this histogram samples.
259   virtual std::string GetAsciiBody() const;
260 
261   // Gets a header message describing this histogram samples.
262   virtual std::string GetAsciiHeader(std::string_view histogram_name,
263                                      int32_t flags) const;
264 
265   // Returns a string description of what goes in a given bucket.
266   const std::string GetSimpleAsciiBucketRange(
267       HistogramBase::Sample sample) const;
268 
meta()269   Metadata* meta() { return meta_; }
270 
271  private:
272   FRIEND_TEST_ALL_PREFIXES(HistogramSamplesTest, WriteAsciiBucketGraph);
273 
274   // Depending on derived class `meta_` can come from:
275   // - Local storage: Then `meta_owned_` is set and meta_ points to it.
276   // - External storage: Then `meta_owned_` is null, and `meta_` point toward an
277   //   external object. The callers guarantees the value will outlive this
278   //   instance.
279   std::unique_ptr<Metadata> meta_owned_;
280   raw_ptr<Metadata> meta_;
281 };
282 
283 class BASE_EXPORT SampleCountIterator {
284  public:
285   virtual ~SampleCountIterator();
286 
287   virtual bool Done() const = 0;
288   virtual void Next() = 0;
289 
290   // Get the sample and count at current position.
291   // Note: |max| is int64_t because histograms support logged values in the
292   // full int32_t range and bucket max is exclusive, so it needs to support
293   // values up to MAXINT32+1.
294   // Requires: !Done();
295   virtual void Get(HistogramBase::Sample* min,
296                    int64_t* max,
297                    HistogramBase::Count* count) = 0;
298   static_assert(std::numeric_limits<HistogramBase::Sample>::max() <
299                     std::numeric_limits<int64_t>::max(),
300                 "Get() |max| must be able to hold Histogram::Sample max + 1");
301 
302   // Get the index of current histogram bucket.
303   // For histograms that don't use predefined buckets, it returns false.
304   // Requires: !Done();
305   virtual bool GetBucketIndex(size_t* index) const;
306 };
307 
308 class BASE_EXPORT SingleSampleIterator : public SampleCountIterator {
309  public:
310   SingleSampleIterator(HistogramBase::Sample min,
311                        int64_t max,
312                        HistogramBase::Count count,
313                        size_t bucket_index,
314                        bool value_was_extracted);
315   ~SingleSampleIterator() override;
316 
317   // SampleCountIterator:
318   bool Done() const override;
319   void Next() override;
320   void Get(HistogramBase::Sample* min,
321            int64_t* max,
322            HistogramBase::Count* count) override;
323 
324   // SampleVector uses predefined buckets so iterator can return bucket index.
325   bool GetBucketIndex(size_t* index) const override;
326 
327  private:
328   // Information about the single value to return.
329   const HistogramBase::Sample min_;
330   const int64_t max_;
331   const size_t bucket_index_;
332   HistogramBase::Count count_;
333 
334   // Whether the value that this iterator holds was extracted from the
335   // underlying data (i.e., reset to 0).
336   const bool value_was_extracted_;
337 };
338 
339 }  // namespace base
340 
341 #endif  // BASE_METRICS_HISTOGRAM_SAMPLES_H_
342