| Index: runtime/vm/profiler_test.cc
|
| diff --git a/runtime/vm/profiler_test.cc b/runtime/vm/profiler_test.cc
|
| index f1518a4766eebf047a36bfebf35048dc1bce4d0f..6f4e0ad40ae67f890501f3988106dd8b13d96e5c 100644
|
| --- a/runtime/vm/profiler_test.cc
|
| +++ b/runtime/vm/profiler_test.cc
|
| @@ -9,6 +9,7 @@
|
| #include "vm/globals.h"
|
| #include "vm/profiler.h"
|
| #include "vm/profiler_service.h"
|
| +#include "vm/source_report.h"
|
| #include "vm/unit_test.h"
|
|
|
| namespace dart {
|
| @@ -2299,6 +2300,204 @@ TEST_CASE(Profiler_BinaryOperatorSourcePositionOptimized) {
|
| }
|
| }
|
|
|
| +
|
| +static void InsertFakeSample(SampleBuffer* sample_buffer,
|
| + uword* pc_offsets) {
|
| + ASSERT(sample_buffer != NULL);
|
| + Isolate* isolate = Isolate::Current();
|
| + Sample* sample = sample_buffer->ReserveSample();
|
| + ASSERT(sample != NULL);
|
| + sample->Init(isolate,
|
| + OS::GetCurrentMonotonicMicros(),
|
| + OSThread::Current()->trace_id());
|
| +
|
| + intptr_t i = 0;
|
| + while (pc_offsets[i] != 0) {
|
| + // When we collect a real stack trace, all PCs collected aside from the
|
| + // executing one (i == 0) are actually return addresses. Return addresses
|
| + // are one byte beyond the call instruction that is executing. The profiler
|
| + // accounts for this and subtracts one from these addresses when querying
|
| + // inline and token position ranges. To be consistent with real stack
|
| + // traces, we add one byte to all PCs except the executing one.
|
| + // See OffsetForPC in profiler_service.cc for more context.
|
| + const intptr_t return_address_offset = i > 0 ? 1 : 0;
|
| + sample->SetAt(i, pc_offsets[i] + return_address_offset);
|
| + i++;
|
| + }
|
| + sample->SetAt(i, NULL);
|
| +}
|
| +
|
| +
|
| +static uword FindPCForTokenPosition(const Code& code,
|
| + const CodeSourceMap& code_source_map,
|
| + TokenPosition tp) {
|
| + CodeSourceMap::Iterator it(code_source_map);
|
| +
|
| + while (it.MoveNext()) {
|
| + if (it.TokenPos() == tp) {
|
| + return it.PcOffset() + code.EntryPoint();
|
| + }
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +
|
| +TEST_CASE(Profiler_GetSourceReport) {
|
| + const char* kScript =
|
| + "doWork(i) => i * i;\n"
|
| + "main() {\n"
|
| + " var sum = 0;\n"
|
| + " for (var i = 0; i < 100; i++) {\n"
|
| + " sum += doWork(i);\n"
|
| + " }\n"
|
| + " return sum;\n"
|
| + "}\n";
|
| +
|
| + // Token position of * in `i * i`.
|
| + const TokenPosition squarePosition = TokenPosition(6);
|
| +
|
| + // Token position of the call to `doWork`.
|
| + const TokenPosition callPosition = TokenPosition(39);
|
| +
|
| + DisableNativeProfileScope dnps;
|
| + // Disable profiling for this thread.
|
| + DisableThreadInterruptsScope dtis(Thread::Current());
|
| +
|
| + SampleBuffer* sample_buffer = Profiler::sample_buffer();
|
| + EXPECT(sample_buffer != NULL);
|
| +
|
| + Dart_Handle lib = TestCase::LoadTestScript(kScript, NULL);
|
| + EXPECT_VALID(lib);
|
| + Library& root_library = Library::Handle();
|
| + root_library ^= Api::UnwrapHandle(lib);
|
| +
|
| + // Invoke main so that it gets compiled.
|
| + Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
| + EXPECT_VALID(result);
|
| +
|
| + {
|
| + // Clear the profile for this isolate.
|
| + ClearProfileVisitor cpv(Isolate::Current());
|
| + sample_buffer->VisitSamples(&cpv);
|
| + }
|
| +
|
| + // Query the code object for main and determine the PC at some token
|
| + // positions.
|
| + const Function& main = Function::Handle(GetFunction(root_library, "main"));
|
| + EXPECT(!main.IsNull());
|
| +
|
| + const Function& do_work =
|
| + Function::Handle(GetFunction(root_library, "doWork"));
|
| + EXPECT(!do_work.IsNull());
|
| +
|
| + const Script& script = Script::Handle(main.script());
|
| + EXPECT(!script.IsNull());
|
| +
|
| + const Code& main_code = Code::Handle(main.CurrentCode());
|
| + EXPECT(!main_code.IsNull());
|
| +
|
| + const Code& do_work_code = Code::Handle(do_work.CurrentCode());
|
| + EXPECT(!do_work_code.IsNull());
|
| +
|
| + const CodeSourceMap& main_code_source_map =
|
| + CodeSourceMap::Handle(main_code.code_source_map());
|
| + EXPECT(!main_code_source_map.IsNull());
|
| +
|
| + const CodeSourceMap& do_work_code_source_map =
|
| + CodeSourceMap::Handle(do_work_code.code_source_map());
|
| + EXPECT(!do_work_code_source_map.IsNull());
|
| +
|
| + // Dump code source map.
|
| + CodeSourceMap::Dump(do_work_code_source_map, do_work_code, main);
|
| + CodeSourceMap::Dump(main_code_source_map, main_code, main);
|
| +
|
| + // Look up some source token position's pc.
|
| + uword squarePositionPc =
|
| + FindPCForTokenPosition(do_work_code,
|
| + do_work_code_source_map,
|
| + squarePosition);
|
| + EXPECT(squarePositionPc != 0);
|
| +
|
| + uword callPositionPc =
|
| + FindPCForTokenPosition(main_code, main_code_source_map, callPosition);
|
| + EXPECT(callPositionPc != 0);
|
| +
|
| + // Look up some classifying token position's pc.
|
| + uword controlFlowPc =
|
| + FindPCForTokenPosition(do_work_code,
|
| + do_work_code_source_map,
|
| + TokenPosition::kControlFlow);
|
| + EXPECT(controlFlowPc != 0);
|
| +
|
| + uword tempMovePc =
|
| + FindPCForTokenPosition(main_code,
|
| + main_code_source_map,
|
| + TokenPosition::kTempMove);
|
| + EXPECT(tempMovePc != 0);
|
| +
|
| + // Insert fake samples.
|
| +
|
| + // Sample 1:
|
| + // squarePositionPc exclusive.
|
| + // callPositionPc inclusive.
|
| + uword sample1[] = {
|
| + squarePositionPc, // doWork.
|
| + callPositionPc, // main.
|
| + 0
|
| + };
|
| +
|
| + // Sample 2:
|
| + // squarePositionPc exclusive.
|
| + uword sample2[] = {
|
| + squarePositionPc, // doWork.
|
| + 0,
|
| + };
|
| +
|
| + // Sample 3:
|
| + // controlFlowPc exclusive.
|
| + // callPositionPc inclusive.
|
| + uword sample3[] = {
|
| + controlFlowPc, // doWork.
|
| + callPositionPc, // main.
|
| + 0
|
| + };
|
| +
|
| + // Sample 4:
|
| + // tempMovePc exclusive.
|
| + uword sample4[] = {
|
| + tempMovePc, // main.
|
| + 0
|
| + };
|
| +
|
| + InsertFakeSample(sample_buffer, &sample1[0]);
|
| + InsertFakeSample(sample_buffer, &sample2[0]);
|
| + InsertFakeSample(sample_buffer, &sample3[0]);
|
| + InsertFakeSample(sample_buffer, &sample4[0]);
|
| +
|
| + // Generate source report for main.
|
| + SourceReport sourceReport(SourceReport::kProfile);
|
| + JSONStream js;
|
| + sourceReport.PrintJSON(&js,
|
| + script,
|
| + do_work.token_pos(),
|
| + main.end_token_pos());
|
| +
|
| + // Verify positions in do_work.
|
| + EXPECT_SUBSTRING("\"positions\":[\"ControlFlow\",6]", js.ToCString());
|
| + // Verify exclusive ticks in do_work.
|
| + EXPECT_SUBSTRING("\"exclusiveTicks\":[1,2]", js.ToCString());
|
| + // Verify inclusive ticks in do_work.
|
| + EXPECT_SUBSTRING("\"inclusiveTicks\":[1,2]", js.ToCString());
|
| +
|
| + // Verify positions in main.
|
| + EXPECT_SUBSTRING("\"positions\":[\"TempMove\",39]", js.ToCString());
|
| + // Verify exclusive ticks in main.
|
| + EXPECT_SUBSTRING("\"exclusiveTicks\":[1,0]", js.ToCString());
|
| + // Verify inclusive ticks in main.
|
| + EXPECT_SUBSTRING("\"inclusiveTicks\":[1,2]", js.ToCString());
|
| +}
|
| +
|
| #endif // !PRODUCT
|
|
|
| } // namespace dart
|
|
|