1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/metrics/call_stack_profile_builder.h"
#include <stdint.h>
#include <algorithm>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/metrics/metrics_hashes.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/metrics/call_stack_profile_encoding.h"
namespace metrics {
namespace {
// Only used by child processes. This returns a unique_ptr so that it can be
// reset during tests.
std::unique_ptr<ChildCallStackProfileCollector>&
GetChildCallStackProfileCollector() {
static base::NoDestructor<std::unique_ptr<ChildCallStackProfileCollector>>
instance(std::make_unique<ChildCallStackProfileCollector>());
return *instance;
}
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
GetBrowserProcessReceiverCallbackInstance() {
static base::NoDestructor<
base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
instance;
return *instance;
}
// Convert |filename| to its MD5 hash.
uint64_t HashModuleFilename(const base::FilePath& filename) {
const base::FilePath::StringType basename = filename.BaseName().value();
// Copy the bytes in basename into a string buffer.
size_t basename_length_in_bytes =
basename.size() * sizeof(base::FilePath::CharType);
std::string name_bytes(basename_length_in_bytes, '\0');
memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
return base::HashMetricName(name_bytes);
}
} // namespace
CallStackProfileBuilder::CallStackProfileBuilder(
const CallStackProfileParams& profile_params,
const WorkIdRecorder* work_id_recorder,
base::OnceClosure completed_callback)
: work_id_recorder_(work_id_recorder) {
completed_callback_ = std::move(completed_callback);
sampled_profile_.set_process(
ToExecutionContextProcess(profile_params.process));
sampled_profile_.set_thread(ToExecutionContextThread(profile_params.thread));
sampled_profile_.set_trigger_event(
ToSampledProfileTriggerEvent(profile_params.trigger));
if (!profile_params.time_offset.is_zero()) {
DCHECK(profile_params.time_offset.is_positive());
CallStackProfile* call_stack_profile =
sampled_profile_.mutable_call_stack_profile();
call_stack_profile->set_profile_time_offset_ms(
profile_params.time_offset.InMilliseconds());
}
}
CallStackProfileBuilder::~CallStackProfileBuilder() = default;
base::ModuleCache* CallStackProfileBuilder::GetModuleCache() {
return &module_cache_;
}
// This function is invoked on the profiler thread while the target thread is
// suspended so must not take any locks, including indirectly through use of
// heap allocation, LOG, CHECK, or DCHECK.
void CallStackProfileBuilder::RecordMetadata(
const base::MetadataRecorder::MetadataProvider& metadata_provider) {
if (work_id_recorder_) {
unsigned int work_id = work_id_recorder_->RecordWorkId();
// A work id of 0 indicates that the message loop has not yet started.
if (work_id != 0) {
is_continued_work_ = (last_work_id_ == work_id);
last_work_id_ = work_id;
}
}
metadata_.RecordMetadata(metadata_provider);
}
void CallStackProfileBuilder::ApplyMetadataRetrospectively(
base::TimeTicks period_start,
base::TimeTicks period_end,
const base::MetadataRecorder::Item& item) {
CHECK_LE(period_start, period_end);
CHECK_LE(period_end, base::TimeTicks::Now());
// We don't set metadata if the period extends before the start of the
// sampling, to avoid biasing against the unobserved execution. This will
// introduce bias due to dropping periods longer than the sampling time, but
// that bias is easier to reason about and account for.
if (period_start < profile_start_time_)
return;
CallStackProfile* call_stack_profile =
sampled_profile_.mutable_call_stack_profile();
google::protobuf::RepeatedPtrField<CallStackProfile::StackSample>* samples =
call_stack_profile->mutable_stack_sample();
CHECK_EQ(sample_timestamps_.size(), static_cast<size_t>(samples->size()));
const ptrdiff_t start_offset =
std::lower_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
period_start) -
sample_timestamps_.begin();
const ptrdiff_t end_offset =
std::upper_bound(sample_timestamps_.begin(), sample_timestamps_.end(),
period_end) -
sample_timestamps_.begin();
metadata_.ApplyMetadata(item, samples->begin() + start_offset,
samples->begin() + end_offset, samples,
call_stack_profile->mutable_metadata_name_hash());
}
void CallStackProfileBuilder::AddProfileMetadata(
const base::MetadataRecorder::Item& item) {
CallStackProfile* call_stack_profile =
sampled_profile_.mutable_call_stack_profile();
metadata_.SetMetadata(item,
call_stack_profile->mutable_profile_metadata()->Add(),
call_stack_profile->mutable_metadata_name_hash());
}
void CallStackProfileBuilder::OnSampleCompleted(
std::vector<base::Frame> frames,
base::TimeTicks sample_timestamp) {
OnSampleCompleted(std::move(frames), sample_timestamp, 1, 1);
}
void CallStackProfileBuilder::OnSampleCompleted(
std::vector<base::Frame> frames,
base::TimeTicks sample_timestamp,
size_t weight,
size_t count) {
// Write CallStackProfile::Stack protobuf message.
CallStackProfile::Stack stack;
for (const auto& frame : frames) {
// The function name should never be provided in UMA profiler usage.
DCHECK(frame.function_name.empty());
// keep the frame information even if its module is invalid so we have
// visibility into how often this issue is happening on the server.
CallStackProfile::Location* location = stack.add_frame();
if (!frame.module)
continue;
// Dedup modules.
auto module_loc = module_index_.find(frame.module);
if (module_loc == module_index_.end()) {
modules_.push_back(frame.module);
size_t index = modules_.size() - 1;
module_loc = module_index_.emplace(frame.module, index).first;
}
// Write CallStackProfile::Location protobuf message.
uintptr_t instruction_pointer = frame.instruction_pointer;
#if BUILDFLAG(IS_IOS)
#if !TARGET_IPHONE_SIMULATOR
// Some iOS devices enable pointer authentication, which uses the
// higher-order bits of pointers to store a signature. Strip that signature
// off before computing the module_offset.
// TODO(crbug.com/1084272): Use the ptrauth_strip() macro once it is
// available.
instruction_pointer &= 0xFFFFFFFFF;
#endif // !TARGET_IPHONE_SIMULATOR
#endif // BUILDFLAG(IS_IOS)
ptrdiff_t module_offset =
reinterpret_cast<const char*>(instruction_pointer) -
reinterpret_cast<const char*>(frame.module->GetBaseAddress());
DCHECK_GE(module_offset, 0);
location->set_address(static_cast<uint64_t>(module_offset));
location->set_module_id_index(module_loc->second);
}
CallStackProfile* call_stack_profile =
sampled_profile_.mutable_call_stack_profile();
// Dedup Stacks.
auto stack_loc = stack_index_.find(&stack);
if (stack_loc == stack_index_.end()) {
*call_stack_profile->add_stack() = std::move(stack);
int stack_index = call_stack_profile->stack_size() - 1;
// It is safe to store the Stack pointer because the repeated message
// representation ensures pointer stability.
stack_loc = stack_index_
.emplace(call_stack_profile->mutable_stack(stack_index),
stack_index)
.first;
}
// Write CallStackProfile::StackSample protobuf message.
CallStackProfile::StackSample* stack_sample_proto =
call_stack_profile->add_stack_sample();
stack_sample_proto->set_stack_index(stack_loc->second);
if (weight != 1)
stack_sample_proto->set_weight(weight);
if (count != 1)
stack_sample_proto->set_count(count);
if (is_continued_work_)
stack_sample_proto->set_continued_work(is_continued_work_);
*stack_sample_proto->mutable_metadata() = metadata_.CreateSampleMetadata(
call_stack_profile->mutable_metadata_name_hash());
if (profile_start_time_.is_null())
profile_start_time_ = sample_timestamp;
// Write timestamps to protobuf message. Currently the timestamps are only
// used for browser process to apply LCP tags. The browser process will clear
// the timestamps information once it is done with LCP tagging. Timestamps
// will not be included in the final profile sent to UMA.
const int64_t offset =
(sample_timestamp - profile_start_time_).InMilliseconds();
stack_sample_proto->set_sample_time_offset_ms(
base::saturated_cast<int32_t>(offset));
sample_timestamps_.push_back(sample_timestamp);
}
void CallStackProfileBuilder::OnProfileCompleted(
base::TimeDelta profile_duration,
base::TimeDelta sampling_period) {
// Build the SampledProfile protobuf message.
CallStackProfile* call_stack_profile =
sampled_profile_.mutable_call_stack_profile();
call_stack_profile->set_profile_duration_ms(
profile_duration.InMilliseconds());
call_stack_profile->set_sampling_period_ms(sampling_period.InMilliseconds());
// Heap profiler sets `profile_time_offset_ms` through constructor.
// For CPU profiles, `profile_time_offset_ms` is the time of the first
// sample.
if (!call_stack_profile->has_profile_time_offset_ms()) {
call_stack_profile->set_profile_time_offset_ms(
profile_start_time_.since_origin().InMilliseconds());
}
// Write CallStackProfile::ModuleIdentifier protobuf message.
for (const auto* module : modules_) {
CallStackProfile::ModuleIdentifier* module_id =
call_stack_profile->add_module_id();
module_id->set_build_id(module->GetId());
module_id->set_name_md5_prefix(
HashModuleFilename(module->GetDebugBasename()));
}
// sampled_profile_ cannot be reused after it is cleared by this function.
// Check we still have the information from the constructor.
CHECK(sampled_profile_.has_process());
CHECK(sampled_profile_.has_thread());
CHECK(sampled_profile_.has_trigger_event());
PassProfilesToMetricsProvider(profile_start_time_,
std::move(sampled_profile_));
// Protobuffers are in an uncertain state after moving from; clear to get
// back to known state.
sampled_profile_.Clear();
// Run the completed callback if there is one.
if (!completed_callback_.is_null())
std::move(completed_callback_).Run();
// Clear the caches.
stack_index_.clear();
module_index_.clear();
modules_.clear();
sample_timestamps_.clear();
}
// static
void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
callback) {
GetBrowserProcessReceiverCallbackInstance() = callback;
}
// static
void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
browser_interface) {
GetChildCallStackProfileCollector()->SetParentProfileCollector(
std::move(browser_interface));
}
// static
void CallStackProfileBuilder::ResetChildCallStackProfileCollectorForTesting() {
GetChildCallStackProfileCollector() =
std::make_unique<ChildCallStackProfileCollector>();
}
void CallStackProfileBuilder::PassProfilesToMetricsProvider(
base::TimeTicks profile_start_time,
SampledProfile sampled_profile) {
if (sampled_profile.process() == BROWSER_PROCESS) {
GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time,
std::move(sampled_profile));
} else {
GetChildCallStackProfileCollector()->Collect(profile_start_time,
std::move(sampled_profile));
}
}
bool CallStackProfileBuilder::StackComparer::operator()(
const CallStackProfile::Stack* stack1,
const CallStackProfile::Stack* stack2) const {
return std::lexicographical_compare(
stack1->frame().begin(), stack1->frame().end(), stack2->frame().begin(),
stack2->frame().end(),
[](const CallStackProfile::Location& loc1,
const CallStackProfile::Location& loc2) {
return std::make_pair(loc1.address(), loc1.module_id_index()) <
std::make_pair(loc2.address(), loc2.module_id_index());
});
}
} // namespace metrics
|