Commit 871f5628 authored by wittman's avatar wittman Committed by Commit bot

Second reland: Introduce mojo interface for collecting StackSamplingProfiler data

Adds interfaces, typemaps, and tests for collecting
StackSamplingProfiler result types. The collection interface will be
used to receive stack profiles from non-browser processes.

This change was previously reverted in
https://codereview.chromium.org/2264613002/ for introducing a skia
dependency that broke cronet. The dependency has been removed and the
new code has been split into independent targets.

BUG=517958

Committed: https://crrev.com/a6e65bb0cea385fc9fa82a5a1af4a5c3616e43e4
Review-Url: https://codereview.chromium.org/2253013008
Cr-Original-Commit-Position: refs/heads/master@{#413285}
Cr-Commit-Position: refs/heads/master@{#413471}
parent ab2d1cd2
...@@ -101,7 +101,9 @@ StackSamplingProfiler::Frame::Frame(uintptr_t instruction_pointer, ...@@ -101,7 +101,9 @@ StackSamplingProfiler::Frame::Frame(uintptr_t instruction_pointer,
StackSamplingProfiler::Frame::~Frame() {} StackSamplingProfiler::Frame::~Frame() {}
StackSamplingProfiler::Frame::Frame() {} StackSamplingProfiler::Frame::Frame()
: instruction_pointer(0), module_index(kUnknownModuleIndex) {
}
// StackSamplingProfiler::CallStackProfile ------------------------------------ // StackSamplingProfiler::CallStackProfile ------------------------------------
...@@ -275,6 +277,12 @@ void StackSamplingProfiler::Stop() { ...@@ -275,6 +277,12 @@ void StackSamplingProfiler::Stop() {
// StackSamplingProfiler::Frame global functions ------------------------------ // StackSamplingProfiler::Frame global functions ------------------------------
bool operator==(const StackSamplingProfiler::Module& a,
const StackSamplingProfiler::Module& b) {
return a.base_address == b.base_address && a.id == b.id &&
a.filename == b.filename;
}
bool operator==(const StackSamplingProfiler::Frame &a, bool operator==(const StackSamplingProfiler::Frame &a,
const StackSamplingProfiler::Frame &b) { const StackSamplingProfiler::Frame &b) {
return a.instruction_pointer == b.instruction_pointer && return a.instruction_pointer == b.instruction_pointer &&
......
...@@ -247,8 +247,10 @@ class BASE_EXPORT StackSamplingProfiler { ...@@ -247,8 +247,10 @@ class BASE_EXPORT StackSamplingProfiler {
DISALLOW_COPY_AND_ASSIGN(StackSamplingProfiler); DISALLOW_COPY_AND_ASSIGN(StackSamplingProfiler);
}; };
// The metrics provider code wants to put Samples in a map and compare them, // These operators permit types to be compared and used in a map of Samples, as
// which requires us to define a few operators. // done in tests and by the metrics provider code.
BASE_EXPORT bool operator==(const StackSamplingProfiler::Module& a,
const StackSamplingProfiler::Module& b);
BASE_EXPORT bool operator==(const StackSamplingProfiler::Frame& a, BASE_EXPORT bool operator==(const StackSamplingProfiler::Frame& a,
const StackSamplingProfiler::Frame& b); const StackSamplingProfiler::Frame& b);
BASE_EXPORT bool operator<(const StackSamplingProfiler::Frame& a, BASE_EXPORT bool operator<(const StackSamplingProfiler::Frame& a,
......
...@@ -240,6 +240,12 @@ if (!is_ios) { ...@@ -240,6 +240,12 @@ if (!is_ios) {
} }
} }
source_set("call_stacks") {
deps = [
"//components/metrics/public/interfaces:call_stack_mojo_bindings",
]
}
# GYP version: components/metrics.gypi:metrics_test_support # GYP version: components/metrics.gypi:metrics_test_support
static_library("test_support") { static_library("test_support") {
testonly = true testonly = true
...@@ -306,6 +312,7 @@ source_set("unit_tests") { ...@@ -306,6 +312,7 @@ source_set("unit_tests") {
":test_support", ":test_support",
":ui", ":ui",
"//base/test:test_support", "//base/test:test_support",
"//components/metrics/public/cpp:call_stack_unit_tests",
"//components/prefs:test_support", "//components/prefs:test_support",
"//components/variations", "//components/variations",
"//net:test_support", "//net:test_support",
...@@ -322,4 +329,10 @@ source_set("unit_tests") { ...@@ -322,4 +329,10 @@ source_set("unit_tests") {
if (is_chromeos) { if (is_chromeos) {
deps += [ "leak_detector:unit_tests" ] deps += [ "leak_detector:unit_tests" ]
} }
# iOS is not supported by the profiler and the ios-simulator bot chokes on
# this test.
if (is_ios) {
deps -= [ "//components/metrics/public/cpp:call_stack_unit_tests" ]
}
} }
...@@ -8,6 +8,7 @@ include_rules = [ ...@@ -8,6 +8,7 @@ include_rules = [
"+components/variations", "+components/variations",
"+components/version_info", "+components/version_info",
"+content/public/test", "+content/public/test",
"+mojo/public/cpp",
"+third_party/zlib/google", "+third_party/zlib/google",
"-net", "-net",
] ]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//mojo/public/tools/bindings/mojom.gni")
source_set("call_stack_unit_tests") {
testonly = true
sources = [
"call_stack_profile_struct_traits_unittest.cc",
]
deps = [
"//base",
"//components/metrics/public/interfaces:call_stack_mojo_test_bindings",
"//mojo/public/cpp/bindings",
"//testing/gtest",
]
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
per-file *_struct_traits*.*=set noparent
per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
mojom =
"//components/metrics/public/interfaces/call_stack_profile_collector.mojom"
public_headers = [ "//base/profiler/stack_sampling_profiler.h" ]
traits_headers =
[ "//components/metrics/public/cpp/call_stack_profile_struct_traits.h" ]
deps = [
"//base",
]
type_mappings = [
"metrics.mojom.CallStackModule=base::StackSamplingProfiler::Module",
"metrics.mojom.CallStackFrame=base::StackSamplingProfiler::Frame",
"metrics.mojom.CallStackProfile=base::StackSamplingProfiler::CallStackProfile",
]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Defines StructTraits specializations for translating between mojo types and
// base::StackSamplingProfiler types, with data validity checks.
#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_STRUCT_TRAITS_H_
#define COMPONENTS_METRICS_CALL_STACK_PROFILE_STRUCT_TRAITS_H_
#include <vector>
#include "base/files/file_path.h"
#include "base/profiler/stack_sampling_profiler.h"
#include "components/metrics/public/interfaces/call_stack_profile_collector.mojom.h"
namespace mojo {
template <>
struct StructTraits<metrics::mojom::CallStackModule,
base::StackSamplingProfiler::Module> {
static uint64_t base_address(
const base::StackSamplingProfiler::Module& module) {
return module.base_address;
}
static const std::string& id(
const base::StackSamplingProfiler::Module& module) {
return module.id;
}
static const base::FilePath& filename(
const base::StackSamplingProfiler::Module& module) {
return module.filename;
}
static bool Read(metrics::mojom::CallStackModuleDataView data,
base::StackSamplingProfiler::Module* out) {
// Linux has the longest build id at 40 bytes.
static const size_t kMaxIDSize = 40;
std::string id;
base::FilePath filename;
if (!data.ReadId(&id) || id.size() > kMaxIDSize ||
!data.ReadFilename(&filename))
return false;
*out =
base::StackSamplingProfiler::Module(data.base_address(), id, filename);
return true;
}
};
template <>
struct StructTraits<metrics::mojom::CallStackFrame,
base::StackSamplingProfiler::Frame> {
static uint64_t instruction_pointer(
const base::StackSamplingProfiler::Frame& frame) {
return frame.instruction_pointer;
}
static uint64_t module_index(
const base::StackSamplingProfiler::Frame& frame) {
return frame.module_index ==
base::StackSamplingProfiler::Frame::kUnknownModuleIndex ?
static_cast<uint64_t>(-1) :
frame.module_index;
}
static bool Read(metrics::mojom::CallStackFrameDataView data,
base::StackSamplingProfiler::Frame* out) {
size_t module_index = data.module_index() == static_cast<uint64_t>(-1) ?
base::StackSamplingProfiler::Frame::kUnknownModuleIndex :
data.module_index();
// We can't know whether the module_index field is valid at this point since
// we don't have access to the number of modules here. This will be checked
// in CallStackProfile's Read function below.
*out = base::StackSamplingProfiler::Frame(data.instruction_pointer(),
module_index);
return true;
}
};
template <>
struct StructTraits<metrics::mojom::CallStackProfile,
base::StackSamplingProfiler::CallStackProfile> {
static const std::vector<base::StackSamplingProfiler::Module>& modules(
const base::StackSamplingProfiler::CallStackProfile& profile) {
return profile.modules;
}
static const std::vector<base::StackSamplingProfiler::Sample>& samples(
const base::StackSamplingProfiler::CallStackProfile& profile) {
return profile.samples;
}
static const base::TimeDelta profile_duration(
const base::StackSamplingProfiler::CallStackProfile& profile) {
return profile.profile_duration;
}
static const base::TimeDelta sampling_period(
const base::StackSamplingProfiler::CallStackProfile& profile) {
return profile.sampling_period;
}
static bool ValidateSamples(
std::vector<base::StackSamplingProfiler::Sample> samples,
size_t module_count) {
for (const base::StackSamplingProfiler::Sample& sample : samples) {
for (const base::StackSamplingProfiler::Frame& frame : sample) {
if (frame.module_index >= module_count &&
frame.module_index !=
base::StackSamplingProfiler::Frame::kUnknownModuleIndex)
return false;
}
}
return true;
}
static bool Read(metrics::mojom::CallStackProfileDataView data,
base::StackSamplingProfiler::CallStackProfile* out) {
std::vector<base::StackSamplingProfiler::Module> modules;
std::vector<base::StackSamplingProfiler::Sample> samples;
base::TimeDelta profile_duration, sampling_period;
if (!data.ReadModules(&modules) || !data.ReadSamples(&samples) ||
!data.ReadProfileDuration(&profile_duration) ||
!data.ReadSamplingPeriod(&sampling_period) ||
!ValidateSamples(samples, modules.size()))
return false;
*out = base::StackSamplingProfiler::CallStackProfile();
out->modules = std::move(modules);
out->samples = std::move(samples);
out->profile_duration = profile_duration;
out->sampling_period = sampling_period;
return true;
}
};
} // mojo
#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_STRUCT_TRAITS_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include "base/logging.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "components/metrics/public/interfaces/call_stack_profile_collector_test.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace metrics {
namespace {
base::StackSamplingProfiler::CallStackProfile CreateProfile(
const std::vector<base::StackSamplingProfiler::Module>& modules,
const std::vector<base::StackSamplingProfiler::Sample>& samples,
base::TimeDelta profile_duration,
base::TimeDelta sampling_period) {
base::StackSamplingProfiler::CallStackProfile profile;
profile.modules = modules;
profile.samples = samples;
profile.profile_duration = profile_duration;
profile.sampling_period = sampling_period;
return profile;
}
}
class CallStackProfileCollectorTestImpl
: public mojom::CallStackProfileCollectorTest {
public:
explicit CallStackProfileCollectorTestImpl(
mojo::InterfaceRequest<mojom::CallStackProfileCollectorTest> request)
: binding_(this, std::move(request)) {
}
// CallStackProfileCollectorTest:
void BounceFrame(const base::StackSamplingProfiler::Frame& in,
const BounceFrameCallback& callback) override {
callback.Run(in);
}
void BounceModule(const base::StackSamplingProfiler::Module& in,
const BounceModuleCallback& callback) override {
callback.Run(in);
}
void BounceProfile(const base::StackSamplingProfiler::CallStackProfile& in,
const BounceProfileCallback& callback) override {
callback.Run(in);
}
private:
mojo::Binding<CallStackProfileCollectorTest> binding_;
DISALLOW_COPY_AND_ASSIGN(CallStackProfileCollectorTestImpl);
};
class CallStackProfileStructTraitsTest : public testing::Test {
public:
CallStackProfileStructTraitsTest() : impl_(GetProxy(&proxy_)) {}
protected:
base::MessageLoop message_loop_;
mojom::CallStackProfileCollectorTestPtr proxy_;
CallStackProfileCollectorTestImpl impl_;
DISALLOW_COPY_AND_ASSIGN(CallStackProfileStructTraitsTest);
};
// Checks serialization/deserialization of Module fields.
TEST_F(CallStackProfileStructTraitsTest, Module) {
using Module = base::StackSamplingProfiler::Module;
struct SerializeCase {
Module module;
bool expect_success;
};
const SerializeCase serialize_cases[] = {
// Null base address.
{
Module(0x0, "abcd", base::FilePath(base::FilePath::kCurrentDirectory)),
true
},
// Non-null base address.
{
Module(0x10, "abcd", base::FilePath(base::FilePath::kCurrentDirectory)),
true
},
// Base address with a bit set beyond 32 bits, when built for x64.
{
Module(1ULL << (sizeof(uintptr_t) * 8) * 3 / 4, "abcd",
base::FilePath(base::FilePath::kCurrentDirectory)),
true
},
// Empty module id.
{
Module(0x10, "", base::FilePath(base::FilePath::kCurrentDirectory)),
true
},
// Module id at the length limit.
{
Module(0x10, std::string(40, ' '),
base::FilePath(base::FilePath::kCurrentDirectory)),
true
},
// Module id beyond the length limit.
{
Module(0x10, std::string(41, ' '),
base::FilePath(base::FilePath::kCurrentDirectory)),
false
},
};
for (const SerializeCase& input : serialize_cases) {
Module output;
EXPECT_EQ(input.expect_success,
proxy_->BounceModule(input.module, &output));
if (!input.expect_success)
continue;
EXPECT_EQ(input.module.base_address, output.base_address);
EXPECT_EQ(input.module.id, output.id);
EXPECT_EQ(input.module.filename, output.filename);
}
}
// Checks serialization/deserialization of Frame fields.
TEST_F(CallStackProfileStructTraitsTest, Frame) {
using Frame = base::StackSamplingProfiler::Frame;
const Frame serialize_cases[] = {
// Null instruction pointer.
Frame(0x0, 10),
// Non-null instruction pointer.
Frame(0x10, 10),
// Instruction pointer with a bit set beyond 32 bits, when built for x64.
Frame(1ULL << (sizeof(uintptr_t) * 8) * 3 / 4, 10),
// Zero module index.
Frame(0xabcd, 0),
// Non-zero module index.
Frame(0xabcd, 1),
// Non-zero module index.
Frame(0xabcd, 10),
// Unknown module index.
Frame(0xabcd, Frame::kUnknownModuleIndex),
};
for (const Frame& input : serialize_cases) {
Frame output;
EXPECT_TRUE(proxy_->BounceFrame(input, &output));
EXPECT_EQ(input.instruction_pointer, output.instruction_pointer);
EXPECT_EQ(input.module_index, output.module_index);
}
}
// Checks serialization/deserialization of Profile fields, including validation
// of the Frame module_index field.
TEST_F(CallStackProfileStructTraitsTest, Profile) {
using Module = base::StackSamplingProfiler::Module;
using Frame = base::StackSamplingProfiler::Frame;
using Sample = base::StackSamplingProfiler::Sample;
using Profile = base::StackSamplingProfiler::CallStackProfile;
struct SerializeCase {
Profile profile;
bool expect_success;
};
const SerializeCase serialize_cases[] = {
// Empty modules and samples.
{
CreateProfile(std::vector<Module>(), std::vector<Sample>(),
base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromSeconds(2)),
true
},
// Non-empty modules and empty samples.
{
CreateProfile({ Module(0x4000, "a", base::FilePath()) },
std::vector<Sample>(),
base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromSeconds(2)),
true
},
// Valid values for modules and samples.
{
CreateProfile({
Module(0x4000, "a", base::FilePath()),
Module(0x4100, "b", base::FilePath()),
},
{
{
Frame(0x4010, 0),
Frame(0x4110, 1),
Frame(0x4110, Frame::kUnknownModuleIndex),
}
},
base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromSeconds(2)),
true
},
// Valid values for modules, but an out of range module index in the second
// sample.
{
CreateProfile({
Module(0x4000, "a", base::FilePath()),
Module(0x4100, "b", base::FilePath()),
},
{
{
Frame(0x4010, 0),
Frame(0x4110, 1),
Frame(0x4110, Frame::kUnknownModuleIndex),
},
{
Frame(0x4010, 0),
Frame(0x4110, 2),
},
},
base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromSeconds(2)),
false
},
};
for (const SerializeCase& input : serialize_cases) {
SCOPED_TRACE(&input - &serialize_cases[0]);
Profile output;
EXPECT_EQ(input.expect_success,
proxy_->BounceProfile(input.profile, &output));
if (!input.expect_success)
continue;
EXPECT_EQ(input.profile.modules, output.modules);
EXPECT_EQ(input.profile.samples, output.samples);
EXPECT_EQ(input.profile.profile_duration, output.profile_duration);
EXPECT_EQ(input.profile.sampling_period, output.sampling_period);
}
}
} // namespace metrics
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
typemaps = [ "//components/metrics/public/cpp/call_stack_profile.typemap" ]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//mojo/public/tools/bindings/mojom.gni")
mojom("call_stack_mojo_bindings") {
sources = [
"call_stack_profile_collector.mojom",
]
deps = [
"//mojo/common:common_custom_types",
]
}
mojom("call_stack_mojo_test_bindings") {
sources = [
"call_stack_profile_collector_test.mojom",
]
deps = [
":call_stack_mojo_bindings",
"//mojo/common:common_custom_types",
]
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
per-file *_struct_traits*.*=set noparent
per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module metrics.mojom;
import "mojo/common/common_custom_types.mojom";
// These structs mirror the corresponding types in base::StackSamplingProfiler.
struct CallStackModule {
uint64 base_address;
string id;
mojo.common.mojom.FilePath filename;
};
struct CallStackFrame {
uint64 instruction_pointer;
uint64 module_index;
};
struct CallStackProfile {
array<CallStackModule> modules;
array<array<CallStackFrame>> samples;
mojo.common.mojom.TimeDelta profile_duration;
mojo.common.mojom.TimeDelta sampling_period;
};
enum Trigger {
UNKNOWN,
PROCESS_STARTUP,
JANKY_TASK,
THREAD_HUNG,
};
interface CallStackProfileCollector {
Collect(Trigger trigger, bool preserve_sample_ordering,
mojo.common.mojom.TimeTicks start_timestamp,
array<CallStackProfile> profiles);
};
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module metrics.mojom;
import "components/metrics/public/interfaces/call_stack_profile_collector.mojom";
interface CallStackProfileCollectorTest {
[Sync]
BounceFrame(CallStackFrame in) => (CallStackFrame out);
[Sync]
BounceModule(CallStackModule in) => (CallStackModule out);
[Sync]
BounceProfile(CallStackProfile in) => (CallStackProfile out);
};
...@@ -6,6 +6,7 @@ _typemap_imports = [ ...@@ -6,6 +6,7 @@ _typemap_imports = [
"//cc/ipc/typemaps.gni", "//cc/ipc/typemaps.gni",
"//chrome/browser/media/router/mojo/typemaps.gni", "//chrome/browser/media/router/mojo/typemaps.gni",
"//components/arc/common/typemaps.gni", "//components/arc/common/typemaps.gni",
"//components/metrics/public/cpp/typemaps.gni",
"//components/typemaps.gni", "//components/typemaps.gni",
"//content/common/bluetooth/typemaps.gni", "//content/common/bluetooth/typemaps.gni",
"//content/common/typemaps.gni", "//content/common/typemaps.gni",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment