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