Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(92)

Side by Side Diff: base/metrics/histogram_persistence.cc

Issue 1425533011: Support "shared" histograms between processes. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@shmem-alloc
Patch Set: moved Create-Result histogram to private space and added to histograms.xml Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « base/metrics/histogram_persistence.h ('k') | base/metrics/histogram_snapshot_manager.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/metrics/histogram_persistence.h"
6
7 #include "base/logging.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/metrics/histogram.h"
10 #include "base/metrics/histogram_base.h"
11 #include "base/metrics/histogram_samples.h"
12 #include "base/metrics/statistics_recorder.h"
13 #include "base/synchronization/lock.h"
14
15 namespace base {
16
17 namespace {
18
19 // Enumerate possible creation results for reporting.
20 enum CreateHistogramResultType {
21 // Everything was fine.
22 CREATE_HISTOGRAM_SUCCESS = 0,
23
24 // Pointer to metadata was not valid.
25 CREATE_HISTOGRAM_INVALID_METADATA_POINTER,
26
27 // Histogram metadata was not valid.
28 CREATE_HISTOGRAM_INVALID_METADATA,
29
30 // Ranges information was not valid.
31 CREATE_HISTOGRAM_INVALID_RANGES_ARRAY,
32
33 // Counts information was not valid.
34 CREATE_HISTOGRAM_INVALID_COUNTS_ARRAY,
35
36 // Could not allocate histogram memory due to corruption.
37 CREATE_HISTOGRAM_ALLOCATOR_CORRUPT,
38
39 // Could not allocate histogram memory due to lack of space.
40 CREATE_HISTOGRAM_ALLOCATOR_FULL,
41
42 // Could not allocate histogram memory due to unknown error.
43 CREATE_HISTOGRAM_ALLOCATOR_ERROR,
44
45 // Always keep this at the end.
46 CREATE_HISTOGRAM_MAX
47 };
48
49 // Type identifiers used when storing in persistent memory so they can be
50 // identified during extraction; the first 4 bytes of the SHA1 of the name
51 // is used as a unique integer. A "version number" is added to the base
52 // so that, if the structure of that object changes, stored older versions
53 // will be safely ignored.
54 enum : uint32_t {
55 kTypeIdHistogram = 0xF1645910 + 1, // SHA1(Histogram) v1
56 kTypeIdRangesArray = 0xBCEA225A + 1, // SHA1(RangesArray) v1
57 kTypeIdCountsArray = 0x53215530 + 1, // SHA1(CountsArray) v1
58 };
59
60 // This data must be held in persistent memory in order for processes to
61 // locate and use histograms created elsewhere.
62 struct PersistentHistogramData {
63 int histogram_type;
64 int flags;
65 int minimum;
66 int maximum;
67 size_t bucket_count;
68 PersistentMemoryAllocator::Reference ranges_ref;
69 uint32_t ranges_checksum;
70 PersistentMemoryAllocator::Reference counts_ref;
71 HistogramSamples::Metadata samples_metadata;
72
73 // Space for the histogram name will be added during the actual allocation
74 // request. This must be the last field of the structure. A zero-size array
75 // or a "flexible" array would be preferred but is not (yet) valid C++.
76 char name[1];
77 };
78
79 // The object held here will obviously not be destructed at process exit
80 // but that's okay since PersistentMemoryAllocator objects are explicitly
81 // forbidden from doing anything essential at exit anyway due to the fact
82 // that they depend on data managed elsewhere and which could be destructed
83 // first.
84 PersistentMemoryAllocator* g_allocator = nullptr;
85
86 // Take an array of range boundaries and create a proper BucketRanges object
87 // which is returned to the caller. A return of nullptr indicates that the
88 // passed boundaries are invalid.
89 BucketRanges* CreateRangesFromData(HistogramBase::Sample* ranges_data,
90 uint32_t ranges_checksum,
91 size_t count) {
92 scoped_ptr<BucketRanges> ranges(new BucketRanges(count));
93 DCHECK_EQ(count, ranges->size());
94 for (size_t i = 0; i < count; ++i) {
95 if (i > 0 && ranges_data[i] <= ranges_data[i - 1])
96 return nullptr;
97 ranges->set_range(i, ranges_data[i]);
98 }
99
100 ranges->ResetChecksum();
101 if (ranges->checksum() != ranges_checksum)
102 return nullptr;
103
104 return ranges.release();
105 }
106
107 } // namespace
108
109 const Feature kPersistentHistogramsFeature{
110 "PersistentHistograms", FEATURE_DISABLED_BY_DEFAULT
111 };
112
113 // Get the histogram in which create results are stored. This is copied almost
114 // exactly from the STATIC_HISTOGRAM_POINTER_BLOCK macro but with added code
115 // to prevent recursion (a likely occurance because the creation of a new
116 // histogram can end up calling this.)
117 HistogramBase* GetCreateHistogramResultHistogram() {
118 static base::subtle::AtomicWord atomic_histogram_pointer = 0;
119 HistogramBase* histogram_pointer(
120 reinterpret_cast<HistogramBase*>(
121 base::subtle::Acquire_Load(&atomic_histogram_pointer)));
122 if (!histogram_pointer) {
123 // It's possible for multiple threads to make it here in parallel but
124 // they'll always return the same result as there is a mutex in the Get.
125 // The purpose of the "initialized" variable is just to ensure that
126 // the same thread doesn't recurse which is also why it doesn't have
127 // to be atomic.
128 static bool initialized = false;
129 if (!initialized) {
130 initialized = true;
131 histogram_pointer = LinearHistogram::FactoryGet(
132 "UMA.CreatePersistentHistogram.Result",
133 1, CREATE_HISTOGRAM_MAX, CREATE_HISTOGRAM_MAX + 1,
134 HistogramBase::kUmaTargetedHistogramFlag);
135 base::subtle::Release_Store(
136 &atomic_histogram_pointer,
137 reinterpret_cast<base::subtle::AtomicWord>(histogram_pointer));
138 }
139 }
140 return histogram_pointer;
141 }
142
143 // Record the result of a histogram creation.
144 void RecordCreateHistogramResult(CreateHistogramResultType result) {
145 HistogramBase* result_histogram = GetCreateHistogramResultHistogram();
146 if (result_histogram)
147 result_histogram->Add(result);
148 }
149
150 void SetPersistentHistogramMemoryAllocator(
151 PersistentMemoryAllocator* allocator) {
152 // Releasing or changing an allocator is extremely dangerous because it
153 // likely has histograms stored within it. If the backing memory is also
154 // also released, future accesses to those histograms will seg-fault.
155 // It's not a fatal CHECK() because tests do this knowing that all
156 // such persistent histograms have already been forgotten.
157 if (g_allocator) {
158 LOG(WARNING) << "Active PersistentMemoryAllocator has been released."
159 << " Some existing histogram pointers may be invalid.";
160 delete g_allocator;
161 }
162 g_allocator = allocator;
163 }
164
165 PersistentMemoryAllocator* GetPersistentHistogramMemoryAllocator() {
166 return g_allocator;
167 }
168
169 PersistentMemoryAllocator* ReleasePersistentHistogramMemoryAllocator() {
170 PersistentMemoryAllocator* allocator = g_allocator;
171 g_allocator = nullptr;
172 return allocator;
173 };
174
175 HistogramBase* CreatePersistentHistogram(
176 PersistentMemoryAllocator* allocator,
177 PersistentHistogramData* histogram_data_ptr) {
178 if (!histogram_data_ptr) {
179 RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_METADATA_POINTER);
180 NOTREACHED();
181 return nullptr;
182 }
183
184 // Copy the histogram_data to local storage because anything in persistent
185 // memory cannot be trusted as it could be changed at any moment by a
186 // malicious actor that shares access. The contents of histogram_data are
187 // validated below; the local copy is to ensure that the contents cannot
188 // be externally changed between validation and use.
189 PersistentHistogramData histogram_data = *histogram_data_ptr;
190
191 HistogramBase::Sample* ranges_data =
192 allocator->GetAsObject<HistogramBase::Sample>(histogram_data.ranges_ref,
193 kTypeIdRangesArray);
194 if (!ranges_data || histogram_data.bucket_count < 2 ||
195 histogram_data.bucket_count + 1 >
196 std::numeric_limits<size_t>::max() / sizeof(HistogramBase::Sample) ||
197 allocator->GetAllocSize(histogram_data.ranges_ref) <
198 (histogram_data.bucket_count + 1) * sizeof(HistogramBase::Sample)) {
199 RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_RANGES_ARRAY);
200 NOTREACHED();
201 return nullptr;
202 }
203 // To avoid racy destruction at shutdown, the following will be leaked.
204 const BucketRanges* ranges = CreateRangesFromData(
205 ranges_data,
206 histogram_data.ranges_checksum,
207 histogram_data.bucket_count + 1);
208 if (!ranges) {
209 RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_RANGES_ARRAY);
210 NOTREACHED();
211 return nullptr;
212 }
213 ranges = StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges);
214
215 HistogramBase::AtomicCount* counts_data =
216 allocator->GetAsObject<HistogramBase::AtomicCount>(
217 histogram_data.counts_ref, kTypeIdCountsArray);
218 if (!counts_data ||
219 allocator->GetAllocSize(histogram_data.counts_ref) <
220 histogram_data.bucket_count * sizeof(HistogramBase::AtomicCount)) {
221 RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_COUNTS_ARRAY);
222 NOTREACHED();
223 return nullptr;
224 }
225
226 std::string name(histogram_data_ptr->name);
227 HistogramBase* histogram = nullptr;
228 switch (histogram_data.histogram_type) {
229 case HISTOGRAM:
230 histogram = Histogram::PersistentGet(
231 name,
232 histogram_data.minimum,
233 histogram_data.maximum,
234 ranges,
235 counts_data,
236 histogram_data.bucket_count,
237 &histogram_data_ptr->samples_metadata);
238 break;
239 case LINEAR_HISTOGRAM:
240 histogram = LinearHistogram::PersistentGet(
241 name,
242 histogram_data.minimum,
243 histogram_data.maximum,
244 ranges,
245 counts_data,
246 histogram_data.bucket_count,
247 &histogram_data_ptr->samples_metadata);
248 break;
249 case BOOLEAN_HISTOGRAM:
250 histogram = BooleanHistogram::PersistentGet(
251 name,
252 ranges,
253 counts_data,
254 &histogram_data_ptr->samples_metadata);
255 break;
256 case CUSTOM_HISTOGRAM:
257 histogram = CustomHistogram::PersistentGet(
258 name,
259 ranges,
260 counts_data,
261 histogram_data.bucket_count,
262 &histogram_data_ptr->samples_metadata);
263 break;
264 }
265
266 if (histogram) {
267 DCHECK_EQ(histogram_data.histogram_type, histogram->GetHistogramType());
268 histogram->SetFlags(histogram_data.flags);
269 }
270
271 RecordCreateHistogramResult(CREATE_HISTOGRAM_SUCCESS);
272 return histogram;
273 }
274
275 HistogramBase* GetPersistentHistogram(
276 PersistentMemoryAllocator* allocator,
277 int32_t ref) {
278 // Unfortunately, the above "pickle" methods cannot be used as part of the
279 // persistance because the deserialization methods always create local
280 // count data (these must referenced the persistent counts) and always add
281 // it to the local list of known histograms (these may be simple references
282 // to histograms in other processes).
283 PersistentHistogramData* histogram_data =
284 allocator->GetAsObject<PersistentHistogramData>(ref, kTypeIdHistogram);
285 size_t length = allocator->GetAllocSize(ref);
286 if (!histogram_data ||
287 reinterpret_cast<char*>(histogram_data)[length - 1] != '\0') {
288 RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_METADATA);
289 NOTREACHED();
290 return nullptr;
291 }
292 return CreatePersistentHistogram(allocator, histogram_data);
293 }
294
295 HistogramBase* GetNextPersistentHistogram(
296 PersistentMemoryAllocator* allocator,
297 PersistentMemoryAllocator::Iterator* iter) {
298 PersistentMemoryAllocator::Reference ref;
299 uint32_t type_id;
300 while ((ref = allocator->GetNextIterable(iter, &type_id)) != 0) {
301 if (type_id == kTypeIdHistogram)
302 return GetPersistentHistogram(allocator, ref);
303 }
304 return nullptr;
305 }
306
307 void FinalizePersistentHistogram(PersistentMemoryAllocator::Reference ref,
308 bool registered) {
309 // If the created persistent histogram was registered then it needs to
310 // be marked as "iterable" in order to be found by other processes.
311 if (registered)
312 GetPersistentHistogramMemoryAllocator()->MakeIterable(ref);
313 // If it wasn't registered then a race condition must have caused
314 // two to be created. The allocator does not support releasing the
315 // acquired memory so just change the type to be empty.
316 else
317 GetPersistentHistogramMemoryAllocator()->SetType(ref, 0);
318 }
319
320 HistogramBase* AllocatePersistentHistogram(
321 PersistentMemoryAllocator* allocator,
322 HistogramType histogram_type,
323 const std::string& name,
324 int minimum,
325 int maximum,
326 const BucketRanges* bucket_ranges,
327 int32_t flags,
328 PersistentMemoryAllocator::Reference* ref_ptr) {
329 if (!allocator)
330 return nullptr;
331
332 size_t bucket_count = bucket_ranges->bucket_count();
333 // An overflow such as this, perhaps as the result of a milicious actor,
334 // could lead to writing beyond the allocation boundary and into other
335 // memory. Just fail the allocation and let the caller deal with it.
336 if (bucket_count > std::numeric_limits<int32_t>::max() /
337 sizeof(HistogramBase::AtomicCount)) {
338 NOTREACHED();
339 return nullptr;
340 }
341 size_t counts_bytes = bucket_count * sizeof(HistogramBase::AtomicCount);
342 size_t ranges_bytes = (bucket_count + 1) * sizeof(HistogramBase::Sample);
343 PersistentMemoryAllocator::Reference ranges_ref =
344 allocator->Allocate(ranges_bytes, kTypeIdRangesArray);
345 PersistentMemoryAllocator::Reference counts_ref =
346 allocator->Allocate(counts_bytes, kTypeIdCountsArray);
347 PersistentMemoryAllocator::Reference histogram_ref =
348 allocator->Allocate(offsetof(PersistentHistogramData, name) +
349 name.length() + 1, kTypeIdHistogram);
350 HistogramBase::Sample* ranges_data =
351 allocator->GetAsObject<HistogramBase::Sample>(ranges_ref,
352 kTypeIdRangesArray);
353 PersistentHistogramData* histogram_data =
354 allocator->GetAsObject<PersistentHistogramData>(histogram_ref,
355 kTypeIdHistogram);
356
357 // Only continue here if all allocations were successful. If they weren't
358 // there is no way to free the space but that's not really a problem since
359 // the allocations only fail because the space is full and so any future
360 // attempts will also fail.
361 if (counts_ref && ranges_data && histogram_data) {
362 strcpy(histogram_data->name, name.c_str());
363 for (size_t i = 0; i < bucket_ranges->size(); ++i)
364 ranges_data[i] = bucket_ranges->range(i);
365
366 histogram_data->histogram_type = histogram_type;
367 histogram_data->flags = flags;
368 histogram_data->minimum = minimum;
369 histogram_data->maximum = maximum;
370 histogram_data->bucket_count = bucket_count;
371 histogram_data->ranges_ref = ranges_ref;
372 histogram_data->ranges_checksum = bucket_ranges->checksum();
373 histogram_data->counts_ref = counts_ref;
374
375 // Create the histogram using resources in persistent memory. This ends up
376 // resolving the "ref" values stored in histogram_data instad of just
377 // using what is already known above but avoids duplicating the switch
378 // statement here and serves as a double-check that everything is
379 // correct before commiting the new histogram to persistent space.
380 HistogramBase* histogram =
381 CreatePersistentHistogram(allocator, histogram_data);
382 DCHECK(histogram);
383 if (ref_ptr != nullptr)
384 *ref_ptr = histogram_ref;
385 return histogram;
386 }
387
388 CreateHistogramResultType result;
389 if (allocator->IsCorrupt()) {
390 result = CREATE_HISTOGRAM_ALLOCATOR_CORRUPT;
391 } else if (allocator->IsFull()) {
392 result = CREATE_HISTOGRAM_ALLOCATOR_FULL;
393 } else {
394 result = CREATE_HISTOGRAM_ALLOCATOR_ERROR;
395 }
396 RecordCreateHistogramResult(result);
397
398 return nullptr;
399 }
400
401 void ImportPersistentHistograms() {
402 // Each call resumes from where it last left off so need persistant iterator.
403 // The lock protects against concurrent access to the iterator and is created
404 // dynamically so as to not require destruction during program exit.
405 static PersistentMemoryAllocator::Iterator iter;
406 static base::Lock* lock = new base::Lock();
407
408 if (g_allocator) {
409 base::AutoLock auto_lock(*lock);
410 if (iter.is_clear())
411 g_allocator->CreateIterator(&iter);
412
413 for (;;) {
414 HistogramBase* histogram = GetNextPersistentHistogram(g_allocator, &iter);
415 if (!histogram)
416 break;
417 StatisticsRecorder::RegisterOrDeleteDuplicate(histogram);
418 }
419 }
420 }
421
422 } // namespace base
OLDNEW
« no previous file with comments | « base/metrics/histogram_persistence.h ('k') | base/metrics/histogram_snapshot_manager.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698