1 // Copyright 2018 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 #include "base/metrics/persistent_histogram_storage.h"
6
7 #include "base/files/file_util.h"
8 #include "base/files/important_file_writer.h"
9 #include "base/logging.h"
10 #include "base/metrics/persistent_histogram_allocator.h"
11 #include "base/metrics/persistent_memory_allocator.h"
12 #include "base/process/memory.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/time/time.h"
16 #include "build/build_config.h"
17
18 #if BUILDFLAG(IS_WIN)
19 #include <windows.h>
20 // Must be after <windows.h>
21 #include <memoryapi.h>
22 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
23 #include <sys/mman.h>
24 #endif
25
26 namespace {
27
28 constexpr size_t kAllocSize = 1 << 20; // 1 MiB
29
AllocateLocalMemory(size_t size)30 void* AllocateLocalMemory(size_t size) {
31 void* address;
32
33 #if BUILDFLAG(IS_WIN)
34 address =
35 ::VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
36 if (address)
37 return address;
38 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
39 // MAP_ANON is deprecated on Linux but MAP_ANONYMOUS is not universal on Mac.
40 // MAP_SHARED is not available on Linux <2.4 but required on Mac.
41 address = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
42 -1, 0);
43 if (address != MAP_FAILED)
44 return address;
45 #else
46 #error This architecture is not (yet) supported.
47 #endif
48
49 // As a last resort, just allocate the memory from the heap. This will
50 // achieve the same basic result but the acquired memory has to be
51 // explicitly zeroed and thus realized immediately (i.e. all pages are
52 // added to the process now instead of only when first accessed).
53 if (!base::UncheckedMalloc(size, &address))
54 return nullptr;
55 DCHECK(address);
56 memset(address, 0, size);
57 return address;
58 }
59
60 } // namespace
61
62 namespace base {
63
PersistentHistogramStorage(StringPiece allocator_name,StorageDirManagement storage_dir_management)64 PersistentHistogramStorage::PersistentHistogramStorage(
65 StringPiece allocator_name,
66 StorageDirManagement storage_dir_management)
67 : storage_dir_management_(storage_dir_management) {
68 DCHECK(!allocator_name.empty());
69 DCHECK(IsStringASCII(allocator_name));
70
71 // This code may be executed before crash handling and/or OOM handling has
72 // been initialized for the process. Silently ignore a failed allocation
73 // (no metric persistence) rather that generating a crash that won't be
74 // caught/reported.
75 void* memory = AllocateLocalMemory(kAllocSize);
76 if (!memory)
77 return;
78
79 GlobalHistogramAllocator::CreateWithPersistentMemory(memory, kAllocSize, 0,
80 0, // No identifier.
81 allocator_name);
82 GlobalHistogramAllocator::Get()->CreateTrackingHistograms(allocator_name);
83 }
84
~PersistentHistogramStorage()85 PersistentHistogramStorage::~PersistentHistogramStorage() {
86 PersistentHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
87 if (!allocator)
88 return;
89
90 allocator->UpdateTrackingHistograms();
91
92 if (disabled_)
93 return;
94
95 // Stop if the storage base directory has not been properly set.
96 if (storage_base_dir_.empty()) {
97 LOG(ERROR)
98 << "Could not write \"" << allocator->Name()
99 << "\" persistent histograms to file as the storage base directory "
100 "is not properly set.";
101 return;
102 }
103
104 FilePath storage_dir = storage_base_dir_.AppendASCII(allocator->Name());
105
106 switch (storage_dir_management_) {
107 case StorageDirManagement::kCreate:
108 if (!CreateDirectory(storage_dir)) {
109 LOG(ERROR)
110 << "Could not write \"" << allocator->Name()
111 << "\" persistent histograms to file as the storage directory "
112 "cannot be created.";
113 return;
114 }
115 break;
116 case StorageDirManagement::kUseExisting:
117 if (!DirectoryExists(storage_dir)) {
118 // When the consumer of this class decides to use an existing storage
119 // directory, it should ensure the directory's existence if it's
120 // essential.
121 LOG(ERROR)
122 << "Could not write \"" << allocator->Name()
123 << "\" persistent histograms to file as the storage directory "
124 "does not exist.";
125 return;
126 }
127 break;
128 }
129
130 // Save data using the current time as the filename. The actual filename
131 // doesn't matter (so long as it ends with the correct extension) but this
132 // works as well as anything.
133 //
134 // NOTE: Cannot use `UnlocalizedTimeFormatWithPattern()` here since `//base`
135 // cannot depend on `//base:i18n`.
136 Time::Exploded exploded;
137 Time::Now().LocalExplode(&exploded);
138 const FilePath file_path =
139 storage_dir
140 .AppendASCII(StringPrintf("%04d%02d%02d%02d%02d%02d", exploded.year,
141 exploded.month, exploded.day_of_month,
142 exploded.hour, exploded.minute,
143 exploded.second))
144 .AddExtension(PersistentMemoryAllocator::kFileExtension);
145
146 StringPiece contents(static_cast<const char*>(allocator->data()),
147 allocator->used());
148 if (!ImportantFileWriter::WriteFileAtomically(file_path, contents)) {
149 LOG(ERROR) << "Persistent histograms fail to write to file: "
150 << file_path.value();
151 }
152 }
153
154 } // namespace base
155