Commit 1541f422 authored by jgruber's avatar jgruber Committed by Commit Bot

[coverage] Expose block coverage through inspector

This piggy-backs on top of existing precise and best-effort coverage to expose
block coverage through the inspector protocol.

Coverage collection now implicitly reports block-granularity coverage when
available.  A new 'isBlockCoverage' property on Inspector's FunctionCoverage
type specifies the granularity of reported coverage.

For now, only count-based block coverage is supported, but binary block
coverage should follow soon.

Support is still gated behind the --block-coverage flag.

Bug: v8:6000
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
Change-Id: I9c4d64e1d2a098e66178b3a68dcee800de0081af
Reviewed-on: https://chromium-review.googlesource.com/532975
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarAleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: 's avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46157}
parent 11636325
......@@ -9908,6 +9908,10 @@ size_t debug::Coverage::FunctionData::BlockCount() const {
return function_->blocks.size();
}
bool debug::Coverage::FunctionData::HasBlockCoverage() const {
return function_->has_block_coverage;
}
debug::Coverage::BlockData debug::Coverage::FunctionData::GetBlockData(
size_t i) const {
return BlockData(&function_->blocks.at(i));
......
......@@ -131,6 +131,18 @@ std::vector<CoverageBlock> GetSortedBlockData(Isolate* isolate,
return result;
}
void ResetAllBlockCounts(SharedFunctionInfo* shared) {
DCHECK(FLAG_block_coverage);
DCHECK(shared->HasCoverageInfo());
CoverageInfo* coverage_info =
CoverageInfo::cast(shared->GetDebugInfo()->coverage_info());
for (int i = 0; i < coverage_info->SlotCount(); i++) {
coverage_info->ResetBlockCount(i);
}
}
// Rewrite position singletons (produced by unconditional control flow
// like return statements, and by continuation counters) into source
// ranges that end at the next sibling range or the end of the parent
......@@ -191,11 +203,12 @@ Coverage* Coverage::Collect(Isolate* isolate,
v8::debug::Coverage::Mode collectionMode) {
SharedToCounterMap counter_map;
const bool reset_count = collectionMode != v8::debug::Coverage::kBestEffort;
switch (isolate->code_coverage_mode()) {
case v8::debug::Coverage::kBlockCount:
case v8::debug::Coverage::kPreciseBinary:
case v8::debug::Coverage::kPreciseCount: {
bool reset_count = collectionMode != v8::debug::Coverage::kBestEffort;
// Feedback vectors are already listed to prevent losing them to GC.
DCHECK(isolate->factory()->code_coverage_list()->IsArrayList());
Handle<ArrayList> list =
......@@ -285,8 +298,12 @@ Coverage* Coverage::Collect(Isolate* isolate,
if (FLAG_block_coverage && info->HasCoverageInfo()) {
CoverageFunction* function = &functions->back();
function->has_block_coverage = true;
function->blocks = GetSortedBlockData(isolate, info);
RewritePositionSingletonsToRanges(function);
// TODO(jgruber): Filter empty block ranges with empty parent ranges.
// We should probably unify handling of function & block ranges.
if (reset_count) ResetAllBlockCounts(info);
}
}
}
......
......@@ -26,13 +26,14 @@ struct CoverageBlock {
struct CoverageFunction {
CoverageFunction(int s, int e, uint32_t c, Handle<String> n)
: start(s), end(e), count(c), name(n) {}
: start(s), end(e), count(c), name(n), has_block_coverage(false) {}
int start;
int end;
uint32_t count;
Handle<String> name;
// Blocks are sorted by start position, from outer to inner blocks.
std::vector<CoverageBlock> blocks;
bool has_block_coverage;
};
struct CoverageScript {
......
......@@ -276,6 +276,7 @@ class V8_EXPORT_PRIVATE Coverage {
uint32_t Count() const;
MaybeLocal<String> Name() const;
size_t BlockCount() const;
bool HasBlockCoverage() const;
BlockData GetBlockData(size_t i) const;
private:
......
......@@ -6,6 +6,7 @@ include_rules = [
"+src/base/logging.h",
"+src/base/platform/platform.h",
"+src/conversions.h",
"+src/flags.h",
"+src/unicode-cache.h",
"+src/inspector",
"+src/tracing",
......
......@@ -863,7 +863,8 @@
"description": "Coverage data for a JavaScript function.",
"properties": [
{ "name": "functionName", "type": "string", "description": "JavaScript function name." },
{ "name": "ranges", "type": "array", "items": { "$ref": "CoverageRange" }, "description": "Source ranges inside the function with coverage data." }
{ "name": "ranges", "type": "array", "items": { "$ref": "CoverageRange" }, "description": "Source ranges inside the function with coverage data." },
{ "name": "isBlockCoverage", "type": "boolean", "description": "Whether coverage data for this function has block granularity." }
],
"experimental": true
},
......
......@@ -7,6 +7,7 @@
#include <vector>
#include "src/base/atomicops.h"
#include "src/flags.h" // TODO(jgruber): Remove include and DEPS entry.
#include "src/inspector/protocol/Protocol.h"
#include "src/inspector/string-util.h"
#include "src/inspector/v8-debugger.h"
......@@ -279,9 +280,17 @@ Response V8ProfilerAgentImpl::startPreciseCoverage(Maybe<bool> callCount) {
m_state->setBoolean(ProfilerAgentState::preciseCoverageStarted, true);
m_state->setBoolean(ProfilerAgentState::preciseCoverageCallCount,
callCountValue);
v8::debug::Coverage::SelectMode(
m_isolate, callCountValue ? v8::debug::Coverage::kPreciseCount
: v8::debug::Coverage::kPreciseBinary);
// BlockCount is a superset of PreciseCount. It includes block-granularity
// coverage data if it exists (at the time of writing, that's the case for
// each function recompiled after the BlockCount mode has been set); and
// function-granularity coverage data otherwise.
// TODO(jgruber): Implement block binary coverage.
v8::debug::Coverage::Mode count_mode =
v8::internal::FLAG_block_coverage ? v8::debug::Coverage::kBlockCount
: v8::debug::Coverage::kPreciseCount;
v8::debug::Coverage::Mode binary_mode = v8::debug::Coverage::kPreciseBinary;
v8::debug::Coverage::SelectMode(m_isolate,
callCountValue ? count_mode : binary_mode);
return Response::OK();
}
......@@ -294,6 +303,15 @@ Response V8ProfilerAgentImpl::stopPreciseCoverage() {
}
namespace {
std::unique_ptr<protocol::Profiler::CoverageRange> createCoverageRange(
int start, int end, int count) {
return protocol::Profiler::CoverageRange::create()
.setStartOffset(start)
.setEndOffset(end)
.setCount(count)
.build();
}
Response coverageToProtocol(
v8::Isolate* isolate, const v8::debug::Coverage& coverage,
std::unique_ptr<protocol::Array<protocol::Profiler::ScriptCoverage>>*
......@@ -311,18 +329,27 @@ Response coverageToProtocol(
script_data.GetFunctionData(j);
std::unique_ptr<protocol::Array<protocol::Profiler::CoverageRange>>
ranges = protocol::Array<protocol::Profiler::CoverageRange>::create();
// At this point we only have per-function coverage data, so there is
// only one range per function.
ranges->addItem(protocol::Profiler::CoverageRange::create()
.setStartOffset(function_data.StartOffset())
.setEndOffset(function_data.EndOffset())
.setCount(function_data.Count())
.build());
// Add function range.
ranges->addItem(createCoverageRange(function_data.StartOffset(),
function_data.EndOffset(),
function_data.Count()));
// Process inner blocks.
for (size_t k = 0; k < function_data.BlockCount(); k++) {
v8::debug::Coverage::BlockData block_data =
function_data.GetBlockData(k);
ranges->addItem(createCoverageRange(block_data.StartOffset(),
block_data.EndOffset(),
block_data.Count()));
}
functions->addItem(
protocol::Profiler::FunctionCoverage::create()
.setFunctionName(toProtocolString(
function_data.Name().FromMaybe(v8::Local<v8::String>())))
.setRanges(std::move(ranges))
.setIsBlockCoverage(function_data.HasBlockCoverage())
.build());
}
String16 url;
......
......@@ -326,5 +326,12 @@ void CoverageInfo::IncrementBlockCount(int slot_index) {
set(slot_start + kSlotBlockCountIndex, Smi::FromInt(old_count + 1));
}
void CoverageInfo::ResetBlockCount(int slot_index) {
DCHECK(FLAG_block_coverage);
DCHECK_LT(slot_index, SlotCount());
const int slot_start = CoverageInfo::FirstIndexForSlot(slot_index);
set(slot_start + kSlotBlockCountIndex, Smi::kZero);
}
} // namespace internal
} // namespace v8
......@@ -154,6 +154,7 @@ class CoverageInfo : public FixedArray {
void InitializeSlot(int slot_index, int start_pos, int end_pos);
void IncrementBlockCount(int slot_index);
void ResetBlockCount(int slot_index);
static int FixedArrayLengthForSlotCount(int slot_count) {
return slot_count * kSlotIndexCount + kFirstSlotIndex;
......
This diff is collapsed.
// Copyright 2017 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.
// Flags: --allow-natives-syntax --no-always-opt --opt --block-coverage
var source =
`
function fib(x) {
if (x < 2) return 1;
return fib(x-1) + fib(x-2);
}
function is_optimized(f) {
return (%GetOptimizationStatus(f) & 16) ? "optimized" : "unoptimized";
}
(function iife() {
return 1;
})();
fib(5);
`;
var break_source =
`
function g() {
debugger;
}
function f(x) {
if (x == 0) g();
else f(x - 1);
}
function h() {
g();
}
f(3);
`;
var nested =
`
var f = (function outer() {
function nested_0() {
return function nested_1() {
return function nested_2() {
return function nested_3() {}
}
}
}
function nested_4() {}
return nested_0();
})();
f()()();
`;
let {session, contextGroup, Protocol} = InspectorTest.start("Test collecting code coverage data with Profiler.collectCoverage.");
function ClearAndGC() {
return Protocol.Runtime.evaluate({ expression: "fib = g = f = h = is_optimized = null;" })
.then(GC);
}
function GC() {
return Protocol.HeapProfiler.collectGarbage();
}
function LogSorted(message) {
message.result.result.sort((a, b) => parseInt(a.scriptId) - parseInt(b.scriptId));
return InspectorTest.logMessage(message);
}
InspectorTest.runTestSuite([
function testPreciseCountBaseline(next)
{
Protocol.Runtime.enable()
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(GC)
.then(Protocol.Profiler.enable)
.then(() => Protocol.Profiler.startPreciseCoverage({callCount: true}))
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
function testPreciseCountCoverage(next)
{
Protocol.Runtime.enable()
.then(Protocol.Profiler.enable)
.then(() => Protocol.Profiler.startPreciseCoverage({callCount: true}))
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
function testPreciseCoverageFail(next)
{
Protocol.Runtime.enable()
.then(Protocol.Profiler.enable)
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Profiler.takePreciseCoverage)
.then(InspectorTest.logMessage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
function testBestEffortCoverage(next)
{
Protocol.Runtime.enable()
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Profiler.getBestEffortCoverage)
.then(LogSorted)
.then(Protocol.Profiler.getBestEffortCoverage)
.then(LogSorted)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
function testBestEffortCoverageWithPreciseBinaryEnabled(next)
{
Protocol.Runtime.enable()
.then(Protocol.Profiler.enable)
.then(Protocol.Profiler.startPreciseCoverage)
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Profiler.getBestEffortCoverage)
.then(LogSorted)
.then(Protocol.Profiler.getBestEffortCoverage)
.then(LogSorted)
.then(ClearAndGC)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
function testBestEffortCoverageWithPreciseCountEnabled(next)
{
Protocol.Runtime.enable()
.then(Protocol.Profiler.enable)
.then(() => Protocol.Profiler.startPreciseCoverage({callCount: true}))
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Profiler.getBestEffortCoverage)
.then(LogSorted)
.then(Protocol.Profiler.getBestEffortCoverage)
.then(LogSorted)
.then(ClearAndGC)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
function testEnablePreciseCountCoverageAtPause(next)
{
function handleDebuggerPause() {
Protocol.Profiler.enable()
.then(() => Protocol.Profiler.startPreciseCoverage({callCount: true}))
.then(Protocol.Debugger.resume)
}
Protocol.Debugger.enable();
Protocol.Debugger.oncePaused().then(handleDebuggerPause);
Protocol.Runtime.enable()
.then(() => Protocol.Runtime.compileScript({ expression: break_source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(ClearAndGC)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(Protocol.Debugger.disable)
.then(ClearAndGC)
.then(next);
},
function testPreciseBinaryCoverage(next)
{
Protocol.Runtime.enable()
.then(Protocol.Profiler.enable)
.then(Protocol.Profiler.startPreciseCoverage)
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(() => Protocol.Runtime.evaluate({ expression: "is_optimized(fib)" }))
.then(message => InspectorTest.logMessage(message))
.then(() => Protocol.Runtime.evaluate({ expression: "fib(20)" }))
.then(message => InspectorTest.logMessage(message))
.then(() => Protocol.Runtime.evaluate({ expression: "is_optimized(fib)" }))
.then(message => InspectorTest.logMessage(message))
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
function testPreciseEmptyScriptCoverageEntries(next)
{
// Enabling the debugger holds onto script objects even though its
// functions can be garbage collected. We would get empty ScriptCoverage
// entires unless we remove them.
Protocol.Debugger.enable()
.then(Protocol.Runtime.enable)
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(ClearAndGC)
.then(Protocol.Profiler.enable)
.then(Protocol.Profiler.startPreciseCoverage)
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(Protocol.Debugger.disable)
.then(ClearAndGC)
.then(next);
},
function testPreciseCountCoveragePartial(next)
{
Protocol.Runtime.enable()
.then(Protocol.Profiler.enable)
.then(() => Protocol.Profiler.startPreciseCoverage({callCount: true}))
.then(() => Protocol.Runtime.compileScript({ expression: nested, sourceURL: arguments.callee.name, persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(() => Protocol.Runtime.evaluate({ expression: "f()" }))
.then(Protocol.Profiler.takePreciseCoverage)
.then(LogSorted)
.then(Protocol.Profiler.stopPreciseCoverage)
.then(Protocol.Profiler.disable)
.then(Protocol.Runtime.disable)
.then(ClearAndGC)
.then(next);
},
]);
......@@ -58,9 +58,7 @@ function ClearAndGC() {
}
function GC() {
return Protocol.HeapProfiler.enable()
.then(() => Protocol.HeapProfiler.collectGarbage())
.then(() => Protocol.HeapProfiler.disable());
return Protocol.HeapProfiler.collectGarbage();
}
function LogSorted(message) {
......
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