Skip to content

Commit 91c8be9

Browse files
Peter KvitekCommit Bot
authored andcommitted
[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: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/master@{#64784}
1 parent cb51a13 commit 91c8be9

11 files changed

Lines changed: 266 additions & 0 deletions

include/js_protocol.pdl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,14 @@ domain Profiler
783783
# Type profile entries for parameters and return values of the functions in the script.
784784
array of TypeProfileEntry entries
785785

786+
# Collected counter information.
787+
experimental type CounterInfo extends object
788+
properties
789+
# Counter name.
790+
string name
791+
# Counter value.
792+
integer value
793+
786794
command disable
787795

788796
command enable
@@ -840,6 +848,18 @@ domain Profiler
840848
# Type profile for all scripts since startTypeProfile() was turned on.
841849
array of ScriptTypeProfile result
842850

851+
# Enable run time call stats collection.
852+
experimental command enableRuntimeCallStats
853+
854+
# Disable run time call stats collection.
855+
experimental command disableRuntimeCallStats
856+
857+
# Retrieve run time call stats.
858+
experimental command getRuntimeCallStats
859+
returns
860+
# Collected counter information.
861+
array of CounterInfo result
862+
843863
event consoleProfileFinished
844864
parameters
845865
string id

include/v8-inspector.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <cctype>
1010

1111
#include <memory>
12+
#include <unordered_map>
1213

1314
#include "v8.h" // NOLINT(build/include)
1415

@@ -299,6 +300,24 @@ class V8_EXPORT V8Inspector {
299300
virtual std::unique_ptr<V8StackTrace> createStackTrace(
300301
v8::Local<v8::StackTrace>) = 0;
301302
virtual std::unique_ptr<V8StackTrace> captureStackTrace(bool fullStack) = 0;
303+
304+
// Performance counters.
305+
class V8_EXPORT Counters : public std::enable_shared_from_this<Counters> {
306+
public:
307+
explicit Counters(v8::Isolate* isolate);
308+
~Counters();
309+
const std::unordered_map<std::string, int>& getCountersMap() const {
310+
return m_countersMap;
311+
}
312+
313+
private:
314+
static int* getCounterPtr(const char* name);
315+
316+
v8::Isolate* m_isolate;
317+
std::unordered_map<std::string, int> m_countersMap;
318+
};
319+
320+
virtual std::shared_ptr<Counters> enableCounters() = 0;
302321
};
303322

304323
} // namespace v8_inspector

src/inspector/v8-inspector-impl.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,39 @@ void V8InspectorImpl::allAsyncTasksCanceled() {
331331
m_debugger->allAsyncTasksCanceled();
332332
}
333333

334+
V8Inspector::Counters::Counters(v8::Isolate* isolate) : m_isolate(isolate) {
335+
CHECK(m_isolate);
336+
V8InspectorImpl* inspector =
337+
static_cast<V8InspectorImpl*>(v8::debug::GetInspector(m_isolate));
338+
CHECK(inspector);
339+
CHECK(!inspector->m_counters);
340+
inspector->m_counters = this;
341+
m_isolate->SetCounterFunction(&Counters::getCounterPtr);
342+
}
343+
344+
V8Inspector::Counters::~Counters() {
345+
V8InspectorImpl* inspector =
346+
static_cast<V8InspectorImpl*>(v8::debug::GetInspector(m_isolate));
347+
CHECK(inspector);
348+
inspector->m_counters = nullptr;
349+
m_isolate->SetCounterFunction(nullptr);
350+
}
351+
352+
int* V8Inspector::Counters::getCounterPtr(const char* name) {
353+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
354+
DCHECK(isolate);
355+
V8Inspector* inspector = v8::debug::GetInspector(isolate);
356+
DCHECK(inspector);
357+
auto* instance = static_cast<V8InspectorImpl*>(inspector)->m_counters;
358+
DCHECK(instance);
359+
return &(instance->m_countersMap[name]);
360+
}
361+
362+
std::shared_ptr<V8Inspector::Counters> V8InspectorImpl::enableCounters() {
363+
if (m_counters) return m_counters->shared_from_this();
364+
return std::make_shared<Counters>(m_isolate);
365+
}
366+
334367
v8::Local<v8::Context> V8InspectorImpl::regexContext() {
335368
if (m_regexContext.IsEmpty())
336369
m_regexContext.Reset(m_isolate, v8::Context::New(m_isolate));

src/inspector/v8-inspector-impl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class V8InspectorImpl : public V8Inspector {
107107
void externalAsyncTaskStarted(const V8StackTraceId& parent) override;
108108
void externalAsyncTaskFinished(const V8StackTraceId& parent) override;
109109

110+
std::shared_ptr<Counters> enableCounters() override;
111+
110112
unsigned nextExceptionId() { return ++m_lastExceptionId; }
111113
void enableStackCapturingIfNeeded();
112114
void disableStackCapturingIfNeeded();
@@ -144,6 +146,8 @@ class V8InspectorImpl : public V8Inspector {
144146
};
145147

146148
private:
149+
friend class Counters;
150+
147151
v8::Isolate* m_isolate;
148152
V8InspectorClient* m_client;
149153
std::unique_ptr<V8Debugger> m_debugger;
@@ -174,6 +178,8 @@ class V8InspectorImpl : public V8Inspector {
174178

175179
std::unique_ptr<V8Console> m_console;
176180

181+
Counters* m_counters = nullptr;
182+
177183
DISALLOW_COPY_AND_ASSIGN(V8InspectorImpl);
178184
};
179185

src/inspector/v8-profiler-agent-impl.cc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,44 @@ Response V8ProfilerAgentImpl::takeTypeProfile(
488488
return Response::OK();
489489
}
490490

491+
Response V8ProfilerAgentImpl::enableRuntimeCallStats() {
492+
if (m_counters)
493+
return Response::Error("RuntimeCallStats collection already enabled.");
494+
495+
if (V8Inspector* inspector = v8::debug::GetInspector(m_isolate))
496+
m_counters = inspector->enableCounters();
497+
else
498+
return Response::Error("No inspector found.");
499+
500+
return Response::OK();
501+
}
502+
503+
Response V8ProfilerAgentImpl::disableRuntimeCallStats() {
504+
if (m_counters) m_counters.reset();
505+
return Response::OK();
506+
}
507+
508+
Response V8ProfilerAgentImpl::getRuntimeCallStats(
509+
std::unique_ptr<protocol::Array<protocol::Profiler::CounterInfo>>*
510+
out_result) {
511+
if (!m_counters)
512+
return Response::Error("RuntimeCallStats collection is not enabled.");
513+
514+
*out_result =
515+
std::make_unique<protocol::Array<protocol::Profiler::CounterInfo>>();
516+
517+
for (const auto& counter : m_counters->getCountersMap()) {
518+
(*out_result)
519+
->emplace_back(
520+
protocol::Profiler::CounterInfo::create()
521+
.setName(String16(counter.first.data(), counter.first.length()))
522+
.setValue(counter.second)
523+
.build());
524+
}
525+
526+
return Response::OK();
527+
}
528+
491529
String16 V8ProfilerAgentImpl::nextProfileId() {
492530
return String16::fromInteger(
493531
v8::base::Relaxed_AtomicIncrement(&s_lastProfileId, 1));

src/inspector/v8-profiler-agent-impl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ class V8ProfilerAgentImpl : public protocol::Profiler::Backend {
5555
std::unique_ptr<protocol::Array<protocol::Profiler::ScriptTypeProfile>>*
5656
out_result) override;
5757

58+
Response enableRuntimeCallStats() override;
59+
Response disableRuntimeCallStats() override;
60+
Response getRuntimeCallStats(
61+
std::unique_ptr<protocol::Array<protocol::Profiler::CounterInfo>>*
62+
out_result) override;
63+
5864
void consoleProfile(const String16& title);
5965
void consoleProfileEnd(const String16& title);
6066

@@ -76,6 +82,7 @@ class V8ProfilerAgentImpl : public protocol::Profiler::Backend {
7682
std::vector<ProfileDescriptor> m_startedProfiles;
7783
String16 m_frontendInitiatedProfileId;
7884
int m_startedProfilesCount = 0;
85+
std::shared_ptr<V8Inspector::Counters> m_counters;
7986

8087
DISALLOW_COPY_AND_ASSIGN(V8ProfilerAgentImpl);
8188
};

test/inspector/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ v8_executable("inspector-test") {
4242
"json-parse.js",
4343
"protocol-test.js",
4444
"runtime/",
45+
"runtime-call-stats/",
4546
"sessions/",
4647
"testcfg.py",
4748
"type-profiler/",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Test RunTimeCallStats collection using Profiler.getRuntimeCallStats.
2+
PASSED
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2019 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
let {session, contextGroup, Protocol} = InspectorTest.start(
6+
'Test RunTimeCallStats collection using Profiler.getRuntimeCallStats.');
7+
8+
var source =
9+
`
10+
function fib(x) {
11+
if (x < 2) return 1;
12+
return fib(x-1) + fib(x-2);
13+
}
14+
fib(5);
15+
`;
16+
17+
function buildCounterMap(result) {
18+
let counterMap = new Map();
19+
20+
let counters = result.result.result;
21+
for (const {name, value} of counters) {
22+
counterMap.set(name, value);
23+
}
24+
25+
return counterMap;
26+
}
27+
28+
function compareCounterMaps(counterMap, counterMap2) {
29+
// Check for counters that are present in the first map but are not found
30+
// in the the second map
31+
for (let counter of counterMap.keys()) {
32+
if (!counterMap2.has(counter)) {
33+
InspectorTest.log(`Counter ${counter} is missing`);
34+
return false;
35+
}
36+
}
37+
38+
// Check for the counter value changes
39+
let counterValueIncreased = false;
40+
for (let [counter, value2] of counterMap2) {
41+
let value = counterMap.get(counter);
42+
if (value !== undefined) {
43+
if (value2 < value) {
44+
InspectorTest.log(`Counter ${counter} value decreased: ${value} -> ${value2}`);
45+
return false;
46+
}
47+
if (value2 > value) {
48+
counterValueIncreased = true;
49+
}
50+
}
51+
}
52+
53+
if (!counterValueIncreased && counterMap.size === counterMap2.size) {
54+
InspectorTest.log(`No counter values has increased or added`);
55+
return false;
56+
}
57+
58+
return true;
59+
}
60+
61+
(async function test() {
62+
await Protocol.Runtime.ensable();
63+
await Protocol.Profiler.enableRuntimeCallStats();
64+
65+
let counterMap = buildCounterMap(await Protocol.Profiler.getRuntimeCallStats());
66+
67+
await Protocol.Runtime.evaluate({ expression: source, sourceURL: arguments.callee.name, persistScript: true });
68+
69+
let counterMap2 = buildCounterMap(await Protocol.Profiler.getRuntimeCallStats());
70+
const check1 = compareCounterMaps(counterMap, counterMap2);
71+
72+
await Protocol.Runtime.evaluate({ expression: source, sourceURL: arguments.callee.name, persistScript: true });
73+
74+
let counterMap3 = buildCounterMap(await Protocol.Profiler.getRuntimeCallStats());
75+
const check2 = compareCounterMaps(counterMap2, counterMap3);
76+
77+
await Protocol.Profiler.disableRuntimeCallStats();
78+
await Protocol.Runtime.disable();
79+
80+
InspectorTest.log(check1 && check2 ? 'PASSED' : 'FAILED');
81+
82+
InspectorTest.completeTest();
83+
})().catch(e => InspectorTest.log('caught: ' + e));
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Test RunTimeCallStats collection enabling and disabling.
2+
Expected error: "RuntimeCallStats collection is not enabled."
3+
Expected error: "RuntimeCallStats collection already enabled."
4+
Some counters reported
5+
Expected error: "RuntimeCallStats collection is not enabled."
6+
Less counters reported

0 commit comments

Comments
 (0)