Commit 91c8be95 authored by Peter Kvitek's avatar Peter Kvitek Committed by Commit Bot

[DevTools] Implemented DevTools protocol API to retrieve V8 RunTime Call Stats.

The new APIs are:
enableRuntimeCallStats
disableRuntimeCallStats
getRuntimeCallStats

The RunTime Call Stats are collected per isolate.

Change-Id: I7e520e2c866288aa9f9dc74f12572abedf0d3ac8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1881601
Commit-Queue: Peter Kvitek <kvitekp@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64784}
parent cb51a138
......@@ -783,6 +783,14 @@ domain Profiler
# Type profile entries for parameters and return values of the functions in the script.
array of TypeProfileEntry entries
# Collected counter information.
experimental type CounterInfo extends object
properties
# Counter name.
string name
# Counter value.
integer value
command disable
command enable
......@@ -840,6 +848,18 @@ domain Profiler
# Type profile for all scripts since startTypeProfile() was turned on.
array of ScriptTypeProfile result
# Enable run time call stats collection.
experimental command enableRuntimeCallStats
# Disable run time call stats collection.
experimental command disableRuntimeCallStats
# Retrieve run time call stats.
experimental command getRuntimeCallStats
returns
# Collected counter information.
array of CounterInfo result
event consoleProfileFinished
parameters
string id
......
......@@ -9,6 +9,7 @@
#include <cctype>
#include <memory>
#include <unordered_map>
#include "v8.h" // NOLINT(build/include)
......@@ -299,6 +300,24 @@ class V8_EXPORT V8Inspector {
virtual std::unique_ptr<V8StackTrace> createStackTrace(
v8::Local<v8::StackTrace>) = 0;
virtual std::unique_ptr<V8StackTrace> captureStackTrace(bool fullStack) = 0;
// Performance counters.
class V8_EXPORT Counters : public std::enable_shared_from_this<Counters> {
public:
explicit Counters(v8::Isolate* isolate);
~Counters();
const std::unordered_map<std::string, int>& getCountersMap() const {
return m_countersMap;
}
private:
static int* getCounterPtr(const char* name);
v8::Isolate* m_isolate;
std::unordered_map<std::string, int> m_countersMap;
};
virtual std::shared_ptr<Counters> enableCounters() = 0;
};
} // namespace v8_inspector
......
......@@ -331,6 +331,39 @@ void V8InspectorImpl::allAsyncTasksCanceled() {
m_debugger->allAsyncTasksCanceled();
}
V8Inspector::Counters::Counters(v8::Isolate* isolate) : m_isolate(isolate) {
CHECK(m_isolate);
V8InspectorImpl* inspector =
static_cast<V8InspectorImpl*>(v8::debug::GetInspector(m_isolate));
CHECK(inspector);
CHECK(!inspector->m_counters);
inspector->m_counters = this;
m_isolate->SetCounterFunction(&Counters::getCounterPtr);
}
V8Inspector::Counters::~Counters() {
V8InspectorImpl* inspector =
static_cast<V8InspectorImpl*>(v8::debug::GetInspector(m_isolate));
CHECK(inspector);
inspector->m_counters = nullptr;
m_isolate->SetCounterFunction(nullptr);
}
int* V8Inspector::Counters::getCounterPtr(const char* name) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
DCHECK(isolate);
V8Inspector* inspector = v8::debug::GetInspector(isolate);
DCHECK(inspector);
auto* instance = static_cast<V8InspectorImpl*>(inspector)->m_counters;
DCHECK(instance);
return &(instance->m_countersMap[name]);
}
std::shared_ptr<V8Inspector::Counters> V8InspectorImpl::enableCounters() {
if (m_counters) return m_counters->shared_from_this();
return std::make_shared<Counters>(m_isolate);
}
v8::Local<v8::Context> V8InspectorImpl::regexContext() {
if (m_regexContext.IsEmpty())
m_regexContext.Reset(m_isolate, v8::Context::New(m_isolate));
......
......@@ -107,6 +107,8 @@ class V8InspectorImpl : public V8Inspector {
void externalAsyncTaskStarted(const V8StackTraceId& parent) override;
void externalAsyncTaskFinished(const V8StackTraceId& parent) override;
std::shared_ptr<Counters> enableCounters() override;
unsigned nextExceptionId() { return ++m_lastExceptionId; }
void enableStackCapturingIfNeeded();
void disableStackCapturingIfNeeded();
......@@ -144,6 +146,8 @@ class V8InspectorImpl : public V8Inspector {
};
private:
friend class Counters;
v8::Isolate* m_isolate;
V8InspectorClient* m_client;
std::unique_ptr<V8Debugger> m_debugger;
......@@ -174,6 +178,8 @@ class V8InspectorImpl : public V8Inspector {
std::unique_ptr<V8Console> m_console;
Counters* m_counters = nullptr;
DISALLOW_COPY_AND_ASSIGN(V8InspectorImpl);
};
......
......@@ -488,6 +488,44 @@ Response V8ProfilerAgentImpl::takeTypeProfile(
return Response::OK();
}
Response V8ProfilerAgentImpl::enableRuntimeCallStats() {
if (m_counters)
return Response::Error("RuntimeCallStats collection already enabled.");
if (V8Inspector* inspector = v8::debug::GetInspector(m_isolate))
m_counters = inspector->enableCounters();
else
return Response::Error("No inspector found.");
return Response::OK();
}
Response V8ProfilerAgentImpl::disableRuntimeCallStats() {
if (m_counters) m_counters.reset();
return Response::OK();
}
Response V8ProfilerAgentImpl::getRuntimeCallStats(
std::unique_ptr<protocol::Array<protocol::Profiler::CounterInfo>>*
out_result) {
if (!m_counters)
return Response::Error("RuntimeCallStats collection is not enabled.");
*out_result =
std::make_unique<protocol::Array<protocol::Profiler::CounterInfo>>();
for (const auto& counter : m_counters->getCountersMap()) {
(*out_result)
->emplace_back(
protocol::Profiler::CounterInfo::create()
.setName(String16(counter.first.data(), counter.first.length()))
.setValue(counter.second)
.build());
}
return Response::OK();
}
String16 V8ProfilerAgentImpl::nextProfileId() {
return String16::fromInteger(
v8::base::Relaxed_AtomicIncrement(&s_lastProfileId, 1));
......
......@@ -55,6 +55,12 @@ class V8ProfilerAgentImpl : public protocol::Profiler::Backend {
std::unique_ptr<protocol::Array<protocol::Profiler::ScriptTypeProfile>>*
out_result) override;
Response enableRuntimeCallStats() override;
Response disableRuntimeCallStats() override;
Response getRuntimeCallStats(
std::unique_ptr<protocol::Array<protocol::Profiler::CounterInfo>>*
out_result) override;
void consoleProfile(const String16& title);
void consoleProfileEnd(const String16& title);
......@@ -76,6 +82,7 @@ class V8ProfilerAgentImpl : public protocol::Profiler::Backend {
std::vector<ProfileDescriptor> m_startedProfiles;
String16 m_frontendInitiatedProfileId;
int m_startedProfilesCount = 0;
std::shared_ptr<V8Inspector::Counters> m_counters;
DISALLOW_COPY_AND_ASSIGN(V8ProfilerAgentImpl);
};
......
......@@ -42,6 +42,7 @@ v8_executable("inspector-test") {
"json-parse.js",
"protocol-test.js",
"runtime/",
"runtime-call-stats/",
"sessions/",
"testcfg.py",
"type-profiler/",
......
Test RunTimeCallStats collection using Profiler.getRuntimeCallStats.
PASSED
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
let {session, contextGroup, Protocol} = InspectorTest.start(
'Test RunTimeCallStats collection using Profiler.getRuntimeCallStats.');
var source =
`
function fib(x) {
if (x < 2) return 1;
return fib(x-1) + fib(x-2);
}
fib(5);
`;
function buildCounterMap(result) {
let counterMap = new Map();
let counters = result.result.result;
for (const {name, value} of counters) {
counterMap.set(name, value);
}
return counterMap;
}
function compareCounterMaps(counterMap, counterMap2) {
// Check for counters that are present in the first map but are not found
// in the the second map
for (let counter of counterMap.keys()) {
if (!counterMap2.has(counter)) {
InspectorTest.log(`Counter ${counter} is missing`);
return false;
}
}
// Check for the counter value changes
let counterValueIncreased = false;
for (let [counter, value2] of counterMap2) {
let value = counterMap.get(counter);
if (value !== undefined) {
if (value2 < value) {
InspectorTest.log(`Counter ${counter} value decreased: ${value} -> ${value2}`);
return false;
}
if (value2 > value) {
counterValueIncreased = true;
}
}
}
if (!counterValueIncreased && counterMap.size === counterMap2.size) {
InspectorTest.log(`No counter values has increased or added`);
return false;
}
return true;
}
(async function test() {
await Protocol.Runtime.ensable();
await Protocol.Profiler.enableRuntimeCallStats();
let counterMap = buildCounterMap(await Protocol.Profiler.getRuntimeCallStats());
await Protocol.Runtime.evaluate({ expression: source, sourceURL: arguments.callee.name, persistScript: true });
let counterMap2 = buildCounterMap(await Protocol.Profiler.getRuntimeCallStats());
const check1 = compareCounterMaps(counterMap, counterMap2);
await Protocol.Runtime.evaluate({ expression: source, sourceURL: arguments.callee.name, persistScript: true });
let counterMap3 = buildCounterMap(await Protocol.Profiler.getRuntimeCallStats());
const check2 = compareCounterMaps(counterMap2, counterMap3);
await Protocol.Profiler.disableRuntimeCallStats();
await Protocol.Runtime.disable();
InspectorTest.log(check1 && check2 ? 'PASSED' : 'FAILED');
InspectorTest.completeTest();
})().catch(e => InspectorTest.log('caught: ' + e));
Test RunTimeCallStats collection enabling and disabling.
Expected error: "RuntimeCallStats collection is not enabled."
Expected error: "RuntimeCallStats collection already enabled."
Some counters reported
Expected error: "RuntimeCallStats collection is not enabled."
Less counters reported
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
let {session, contextGroup, Protocol} = InspectorTest.start(
'Test RunTimeCallStats collection enabling and disabling.');
var source =
`
function fib(x) {
if (x < 2) return 1;
return fib(x-1) + fib(x-2);
}
fib(5);
`;
function logErrorMessage(result) {
InspectorTest.log('Expected error: "' + result.error.message + '"');
}
(async function test() {
await Protocol.Runtime.ensable();
// This should fail with "not enabled" error.
logErrorMessage(await Protocol.Profiler.getRuntimeCallStats());
// This should fail with "already enabled" error.
await Protocol.Profiler.enableRuntimeCallStats();
logErrorMessage(await Protocol.Profiler.enableRuntimeCallStats());
// The result should not be empty.
await Protocol.Runtime.evaluate({ expression: source, sourceURL: arguments.callee.name, persistScript: true });
const counters = (await Protocol.Profiler.getRuntimeCallStats()).result.result;
if (counters.length > 0)
InspectorTest.log('Some counters reported');
await Protocol.Profiler.disableRuntimeCallStats();
// This should fail with "not enabled" error too.
logErrorMessage(await Protocol.Profiler.getRuntimeCallStats());
// The result should not be empty and have smaller amount of counters than
// the first result.
await Protocol.Profiler.enableRuntimeCallStats();
const counters2 = (await Protocol.Profiler.getRuntimeCallStats()).result.result;
if (counters2.length > 0 && counters2.length < counters.length)
InspectorTest.log('Less counters reported');
await Protocol.Profiler.disableRuntimeCallStats();
await Protocol.Runtime.disable();
InspectorTest.completeTest();
})().catch(e => InspectorTest.log('caught: ' + e));
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