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
|
// Copyright 2023 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_stacks/stack_sampling_recorder.h"
#include <sys/file.h>
#include <string>
#include <utility>
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "components/metrics/call_stacks/call_stack_profile_metrics_provider.h"
#include "third_party/cros_system_api/proto/stack_sampled_metrics_status/stack_sampled_metrics_status.pb.h"
namespace metrics {
namespace {
// The path to write to. Deliberately not using base::GetTempDir() here;
// the tast test needs to known the complete, exact path.
constexpr char kDefaultFilePath[] = "/tmp/stack-sampling-data";
// Time between writes. This is very short because we are only using this as
// part of an integration test and the test doesn't want to have a large
// timeout.
constexpr base::TimeDelta kTimeBetweenWrites = base::Seconds(1);
} // namespace
StackSamplingRecorder::StackSamplingRecorder()
: file_path_(base::FilePath(kDefaultFilePath)) {}
StackSamplingRecorder::StackSamplingRecorder(base::FilePath file_path)
: file_path_(std::move(file_path)) {}
void StackSamplingRecorder::Start() {
base::ThreadPool::PostDelayedTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce(&StackSamplingRecorder::WriteFile, this),
kTimeBetweenWrites);
}
StackSamplingRecorder::~StackSamplingRecorder() = default;
metrics::CallStackProfileMetricsProvider::ProcessThreadCount
StackSamplingRecorder::GetSuccessfullyCollectedCounts() const {
return metrics::CallStackProfileMetricsProvider::
GetSuccessfullyCollectedCounts();
}
void StackSamplingRecorder::WriteFile() {
// Do most work in a helper function so that we can return early if there's
// an error, but we still always post a retry task.
WriteFileHelper();
base::ThreadPool::PostDelayedTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce(&StackSamplingRecorder::WriteFile, this),
kTimeBetweenWrites);
}
void StackSamplingRecorder::WriteFileHelper() {
// Build up the protobuf we want to write before locking the file, to
// minimize the time spent holding the lock.
auto process_thread_count_map = GetSuccessfullyCollectedCounts();
stack_sampled_metrics_status::StackSampledMetricsStatus status;
for (const auto& [process, thread_count_map] : process_thread_count_map) {
auto& thread_count_map_proto =
(*status.mutable_process_type_to_thread_count_map())[process];
thread_count_map_proto.mutable_thread_type_to_success_count()->insert(
thread_count_map.begin(), thread_count_map.end());
}
// Don't use the open flags that truncate the file; we can't truncate until
// the lock succeeds.
base::File file(file_path_,
base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
if (!file.IsValid()) {
LOG(ERROR) << "Failed to open " << file_path_ << ": "
<< base::File::ErrorToString(file.error_details());
return;
}
// Use flock instead of File::Lock; File::Lock doesn't allow
// block-until-locked.
if (HANDLE_EINTR(flock(file.GetPlatformFile(), LOCK_EX)) != 0) {
PLOG(ERROR) << "Unable to lock " << file_path_ << ": ";
return;
}
// We don't need to unlock the file explicitly; it will always unlock when
// we destruct |file|.
if (!file.SetLength(0)) {
LOG(ERROR) << "Unable to truncate " << file_path_;
return;
}
if (!status.SerializeToFileDescriptor(file.GetPlatformFile())) {
LOG(ERROR) << "Unable to write to " << file_path_;
return;
}
}
} // namespace metrics
|