Web Inspector: Hook the sampling profiler into the Timelines UI
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Jan 2016 21:51:00 +0000 (21:51 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Jan 2016 21:51:00 +0000 (21:51 +0000)
https://bugs.webkit.org/show_bug.cgi?id=152766
<rdar://problem/24066360>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

This patch adds some necessary functions to SamplingProfiler::StackFrame
to allow it to give data to the Inspector for the timelines UI. i.e, the
sourceID of the executable of a stack frame.

This patch also swaps in the SamplingProfiler in place of the
LegacyProfiler inside InspectorScriptProfilerAgent. It adds
the necessary protocol data to allow the SamplingProfiler's
data to hook into the timelines UI.

* debugger/Debugger.cpp:
(JSC::Debugger::setProfilingClient):
(JSC::Debugger::willEvaluateScript):
(JSC::Debugger::didEvaluateScript):
(JSC::Debugger::toggleBreakpoint):
* debugger/Debugger.h:
* debugger/ScriptProfilingScope.h:
(JSC::ScriptProfilingScope::ScriptProfilingScope):
(JSC::ScriptProfilingScope::~ScriptProfilingScope):
* inspector/agents/InspectorScriptProfilerAgent.cpp:
(Inspector::InspectorScriptProfilerAgent::willDestroyFrontendAndBackend):
(Inspector::InspectorScriptProfilerAgent::startTracking):
(Inspector::InspectorScriptProfilerAgent::stopTracking):
(Inspector::InspectorScriptProfilerAgent::isAlreadyProfiling):
(Inspector::InspectorScriptProfilerAgent::willEvaluateScript):
(Inspector::InspectorScriptProfilerAgent::didEvaluateScript):
(Inspector::InspectorScriptProfilerAgent::addEvent):
(Inspector::buildSamples):
(Inspector::InspectorScriptProfilerAgent::trackingComplete):
(Inspector::buildAggregateCallInfoInspectorObject): Deleted.
(Inspector::buildInspectorObject): Deleted.
(Inspector::buildProfileInspectorObject): Deleted.
* inspector/agents/InspectorScriptProfilerAgent.h:
* inspector/protocol/ScriptProfiler.json:
* jsc.cpp:
(functionSamplingProfilerStackTraces):
* runtime/SamplingProfiler.cpp:
(JSC::SamplingProfiler::start):
(JSC::SamplingProfiler::stop):
(JSC::SamplingProfiler::clearData):
(JSC::SamplingProfiler::StackFrame::displayName):
(JSC::SamplingProfiler::StackFrame::displayNameForJSONTests):
(JSC::SamplingProfiler::StackFrame::startLine):
(JSC::SamplingProfiler::StackFrame::startColumn):
(JSC::SamplingProfiler::StackFrame::sourceID):
(JSC::SamplingProfiler::StackFrame::url):
(JSC::SamplingProfiler::stackTraces):
(JSC::SamplingProfiler::stackTracesAsJSON):
(JSC::displayName): Deleted.
(JSC::SamplingProfiler::stacktracesAsJSON): Deleted.
* runtime/SamplingProfiler.h:
(JSC::SamplingProfiler::StackFrame::StackFrame):
(JSC::SamplingProfiler::getLock):
(JSC::SamplingProfiler::setTimingInterval):
(JSC::SamplingProfiler::totalTime):
(JSC::SamplingProfiler::setStopWatch):
(JSC::SamplingProfiler::stackTraces): Deleted.
* tests/stress/sampling-profiler-anonymous-function.js:
(platformSupportsSamplingProfiler.baz):
(platformSupportsSamplingProfiler):
* tests/stress/sampling-profiler-basic.js:
(platformSupportsSamplingProfiler.nothing):
(platformSupportsSamplingProfiler.top):
* tests/stress/sampling-profiler/samplingProfiler.js:
(doesTreeHaveStackTrace):

Source/WebInspectorUI:

The main change in this patch is to swap in the SamplingProfiler
in place of the LegacyProfiler. To do this, we've created a data
structure called CallingContextTree which aggregates the SamplingProfiler's
data into an easy to manage tree. To see how the data structure works,
consider the following program:
```
function bar() { // run code here for a long time. }
function baz() { // run code here for a long time. }
function foo() { bar(); baz(); }
foo();
```
From this program, we will create a tree like this:
                (program)
                    |
                    |
                   foo
                   | |
                  /   \
                 /     \
                bar     baz

From this type of tree, we can easily create a CPUProfile payload
object. Because the Timelines UI knows how to interact with the
CPUProfile object and display it, we currently map the tree to this object
to make it trivially easy to display the SamplingProfiler's data. In the future,
we may want to find ways to work directly with the CallingContextTree instead
of mapping it into another object.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Controllers/TimelineManager.js:
* UserInterface/Main.html:
* UserInterface/Models/CallingContextTree.js: Added.
* UserInterface/Models/ScriptInstrument.js:
* UserInterface/Protocol/ScriptProfilerObserver.js:
* UserInterface/TestStub.html:
* UserInterface/Views/ScriptTimelineView.js:

LayoutTests:

* inspector/sampling-profiler: Added.
* inspector/sampling-profiler/basic-expected.txt: Added.
* inspector/sampling-profiler/basic.html: Added.
* inspector/sampling-profiler/call-frame-with-dom-functions-expected.txt: Added.
* inspector/sampling-profiler/call-frame-with-dom-functions.html: Added.
* inspector/sampling-profiler/eval-source-url-expected.txt: Added.
* inspector/sampling-profiler/eval-source-url.html: Added.
* inspector/sampling-profiler/many-call-frames-expected.txt: Added.
* inspector/sampling-profiler/many-call-frames.html: Added.
* inspector/sampling-profiler/named-function-expression-expected.txt: Added.
* inspector/sampling-profiler/named-function-expression.html: Added.
* inspector/script-profiler/event-type-API-expected.txt:
* inspector/script-profiler/event-type-API.html:
* inspector/script-profiler/event-type-Microtask-expected.txt:
* inspector/script-profiler/event-type-Microtask.html:
* inspector/script-profiler/event-type-Other-expected.txt:
* inspector/script-profiler/event-type-Other.html:
* inspector/script-profiler/tracking-expected.txt:
* inspector/script-profiler/tracking.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@195376 268f45cc-cd09-0410-ab3c-d52691b4dbfc

41 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/sampling-profiler/basic-expected.txt [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/basic.html [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/call-frame-with-dom-functions-expected.txt [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/call-frame-with-dom-functions.html [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/eval-source-url-expected.txt [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/eval-source-url.html [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/many-call-frames-expected.txt [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/many-call-frames.html [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/named-function-expression-expected.txt [new file with mode: 0644]
LayoutTests/inspector/sampling-profiler/named-function-expression.html [new file with mode: 0644]
LayoutTests/inspector/script-profiler/event-type-API-expected.txt
LayoutTests/inspector/script-profiler/event-type-API.html
LayoutTests/inspector/script-profiler/event-type-Microtask-expected.txt
LayoutTests/inspector/script-profiler/event-type-Microtask.html
LayoutTests/inspector/script-profiler/event-type-Other-expected.txt
LayoutTests/inspector/script-profiler/event-type-Other.html
LayoutTests/inspector/script-profiler/tracking-expected.txt
LayoutTests/inspector/script-profiler/tracking.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/debugger/Debugger.cpp
Source/JavaScriptCore/debugger/Debugger.h
Source/JavaScriptCore/debugger/ScriptProfilingScope.h
Source/JavaScriptCore/inspector/agents/InspectorScriptProfilerAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorScriptProfilerAgent.h
Source/JavaScriptCore/inspector/protocol/ScriptProfiler.json
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/runtime/SamplingProfiler.cpp
Source/JavaScriptCore/runtime/SamplingProfiler.h
Source/JavaScriptCore/tests/stress/sampling-profiler-anonymous-function.js
Source/JavaScriptCore/tests/stress/sampling-profiler-basic.js
Source/JavaScriptCore/tests/stress/sampling-profiler/samplingProfiler.js
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/CallingContextTree.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/ScriptInstrument.js
Source/WebInspectorUI/UserInterface/Protocol/ScriptProfilerObserver.js
Source/WebInspectorUI/UserInterface/TestStub.html
Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.js

index 36cd311..1d6cbed 100644 (file)
@@ -1,3 +1,31 @@
+2016-01-20  Saam barati  <sbarati@apple.com>
+
+        Web Inspector: Hook the sampling profiler into the Timelines UI
+        https://bugs.webkit.org/show_bug.cgi?id=152766
+        <rdar://problem/24066360>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/sampling-profiler: Added.
+        * inspector/sampling-profiler/basic-expected.txt: Added.
+        * inspector/sampling-profiler/basic.html: Added.
+        * inspector/sampling-profiler/call-frame-with-dom-functions-expected.txt: Added.
+        * inspector/sampling-profiler/call-frame-with-dom-functions.html: Added.
+        * inspector/sampling-profiler/eval-source-url-expected.txt: Added.
+        * inspector/sampling-profiler/eval-source-url.html: Added.
+        * inspector/sampling-profiler/many-call-frames-expected.txt: Added.
+        * inspector/sampling-profiler/many-call-frames.html: Added.
+        * inspector/sampling-profiler/named-function-expression-expected.txt: Added.
+        * inspector/sampling-profiler/named-function-expression.html: Added.
+        * inspector/script-profiler/event-type-API-expected.txt:
+        * inspector/script-profiler/event-type-API.html:
+        * inspector/script-profiler/event-type-Microtask-expected.txt:
+        * inspector/script-profiler/event-type-Microtask.html:
+        * inspector/script-profiler/event-type-Other-expected.txt:
+        * inspector/script-profiler/event-type-Other.html:
+        * inspector/script-profiler/tracking-expected.txt:
+        * inspector/script-profiler/tracking.html:
+
 2016-01-20  Daniel Bates  <dabates@apple.com>
 
         CSP: Add tests to ensure that alternative text of an image is rendered when CSP blocks its load
diff --git a/LayoutTests/inspector/sampling-profiler/basic-expected.txt b/LayoutTests/inspector/sampling-profiler/basic-expected.txt
new file mode 100644 (file)
index 0000000..74b671d
--- /dev/null
@@ -0,0 +1,13 @@
+
+== Running test suite: ScriptProfiler.Samples.Basic
+-- Running test case: Sampling Profiler basic
+PASS: Should have seen stacktrace:
+[
+  {
+    "name": "foo"
+  },
+  {
+    "name": "runFor"
+  }
+]
+
diff --git a/LayoutTests/inspector/sampling-profiler/basic.html b/LayoutTests/inspector/sampling-profiler/basic.html
new file mode 100644 (file)
index 0000000..477995d
--- /dev/null
@@ -0,0 +1,53 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script>
+
+function runFor(func, millis) {
+    let start = Date.now();
+    do {
+        func();
+    } while (Date.now() - start < millis);
+}
+
+function foo() {
+    for (let i = 0; i < 10000; i++) {
+        i++;
+        i--;
+    }
+}
+noInline(foo);
+
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("ScriptProfiler.Samples.Basic");
+
+    suite.addTestCase({
+        name: "Sampling Profiler basic",
+        description: "Sample some basic code.",
+        test: (resolve, reject) => {
+            InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
+                let tree = WebInspector.CallingContextTree.__test_makeTreeFromProtocolMessageObject(messageObject);
+                let trace = [
+                    {name: "foo"},
+                    {name: "runFor"}
+                ];
+                ProtocolTest.expectThat(tree.__test_matchesStackTrace(trace), "Should have seen stacktrace:\n" + JSON.stringify(trace, undefined, 2));
+                resolve();
+            });
+
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {includeSamples: true});
+            ProtocolTest.evaluateInPage("runFor(foo, 100)");
+            InspectorProtocol.sendCommand('ScriptProfiler.stopTracking', {});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+</body>
+</html>
diff --git a/LayoutTests/inspector/sampling-profiler/call-frame-with-dom-functions-expected.txt b/LayoutTests/inspector/sampling-profiler/call-frame-with-dom-functions-expected.txt
new file mode 100644 (file)
index 0000000..e1fabae
--- /dev/null
@@ -0,0 +1,28 @@
+
+== Running test suite: SciptProfiler.Samples.DOM
+-- Running test case: Sampling Profiler sourceURL directive.
+PASS: Should have seen stacktrace:
+[
+  {
+    "name": "createElement"
+  },
+  {
+    "name": "foo"
+  },
+  {
+    "name": "runFor"
+  }
+]
+PASS: Should have seen stacktrace:
+[
+  {
+    "name": "appendChild"
+  },
+  {
+    "name": "foo"
+  },
+  {
+    "name": "runFor"
+  }
+]
+
diff --git a/LayoutTests/inspector/sampling-profiler/call-frame-with-dom-functions.html b/LayoutTests/inspector/sampling-profiler/call-frame-with-dom-functions.html
new file mode 100644 (file)
index 0000000..afb113d
--- /dev/null
@@ -0,0 +1,64 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script>
+
+function runFor(func, millis) {
+    let start = Date.now();
+    do {
+        func();
+    } while (Date.now() - start < millis);
+}
+
+function foo() {
+    let body = document.body;
+    for (let i = 0; i < 10000; i++) {
+        let p = document.createElement("p");
+        body.appendChild(p);
+    }
+}
+noInline(foo);
+
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("SciptProfiler.Samples.DOM");
+
+    suite.addTestCase({
+        name: "Sampling Profiler sourceURL directive.",
+        description: "Sample some basic code.",
+        test: (resolve, reject) => {
+            InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
+                let tree = WebInspector.CallingContextTree.__test_makeTreeFromProtocolMessageObject(messageObject);
+
+                let trace = [
+                    {name: "createElement"},
+                    {name: "foo"},
+                    {name: "runFor"}
+                ];
+                ProtocolTest.expectThat(tree.__test_matchesStackTrace(trace), "Should have seen stacktrace:\n" + JSON.stringify(trace, undefined, 2));
+
+                trace = [
+                    {name: "appendChild"},
+                    {name: "foo"},
+                    {name: "runFor"}
+                ];
+                ProtocolTest.expectThat(tree.__test_matchesStackTrace(trace), "Should have seen stacktrace:\n" + JSON.stringify(trace, undefined, 2));
+
+                resolve();
+            });
+
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {includeSamples: true});
+            ProtocolTest.evaluateInPage("runFor(foo, 100)");
+            InspectorProtocol.sendCommand('ScriptProfiler.stopTracking', {});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+</body>
+</html>
diff --git a/LayoutTests/inspector/sampling-profiler/eval-source-url-expected.txt b/LayoutTests/inspector/sampling-profiler/eval-source-url-expected.txt
new file mode 100644 (file)
index 0000000..440c10d
--- /dev/null
@@ -0,0 +1,23 @@
+
+== Running test suite: ScriptProfiler.Samples.SourceURL
+-- Running test case: Sampling Profiler sourceURL directive.
+PASS: Should have seen stacktrace:
+[
+  {
+    "name": "foo"
+  },
+  {
+    "name": "(program)",
+    "url": "eval.js"
+  },
+  {
+    "name": "eval"
+  },
+  {
+    "name": "bar"
+  },
+  {
+    "name": "runFor"
+  }
+]
+
diff --git a/LayoutTests/inspector/sampling-profiler/eval-source-url.html b/LayoutTests/inspector/sampling-profiler/eval-source-url.html
new file mode 100644 (file)
index 0000000..e753dfd
--- /dev/null
@@ -0,0 +1,63 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script>
+
+function runFor(func, millis) {
+    let start = Date.now();
+    do {
+        func();
+    } while (Date.now() - start < millis);
+}
+
+function foo() {
+    for (let i = 0; i < 10000; i++) {
+        i++;
+        i--;
+    }
+}
+noInline(foo);
+
+function bar() {
+    for (let i = 0; i < 2; i++)
+        eval("//# sourceURL=eval.js\nfoo();");
+}
+noInline(bar);
+
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("ScriptProfiler.Samples.SourceURL");
+
+    suite.addTestCase({
+        name: "Sampling Profiler sourceURL directive.",
+        description: "Sample some basic code.",
+        test: (resolve, reject) => {
+            InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
+                let tree = WebInspector.CallingContextTree.__test_makeTreeFromProtocolMessageObject(messageObject);
+
+                let trace = [
+                    {name: "foo"},
+                    {name: "(program)", url: "eval.js"},
+                    {name: "eval"},
+                    {name: "bar"},
+                    {name: "runFor"}
+                ];
+                ProtocolTest.expectThat(tree.__test_matchesStackTrace(trace), "Should have seen stacktrace:\n" + JSON.stringify(trace, undefined, 2));
+                resolve();
+            });
+
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {includeSamples: true});
+            ProtocolTest.evaluateInPage("runFor(bar, 100)");
+            InspectorProtocol.sendCommand('ScriptProfiler.stopTracking', {});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+</body>
+</html>
diff --git a/LayoutTests/inspector/sampling-profiler/many-call-frames-expected.txt b/LayoutTests/inspector/sampling-profiler/many-call-frames-expected.txt
new file mode 100644 (file)
index 0000000..a64eaac
--- /dev/null
@@ -0,0 +1,34 @@
+
+== Running test suite: ScriptProfiler.Samples.ManyCallFrames
+-- Running test case: Sampling Profiler sourceURL directive.
+PASS: Should have seen stacktrace:
+[
+  {
+    "name": "top"
+  },
+  {
+    "name": "g"
+  },
+  {
+    "name": "f"
+  },
+  {
+    "name": "e"
+  },
+  {
+    "name": "d"
+  },
+  {
+    "name": "c"
+  },
+  {
+    "name": "b"
+  },
+  {
+    "name": "a"
+  },
+  {
+    "name": "runFor"
+  }
+]
+
diff --git a/LayoutTests/inspector/sampling-profiler/many-call-frames.html b/LayoutTests/inspector/sampling-profiler/many-call-frames.html
new file mode 100644 (file)
index 0000000..6e21848
--- /dev/null
@@ -0,0 +1,68 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script>
+
+function runFor(func, millis) {
+    let start = Date.now();
+    do {
+        func();
+    } while (Date.now() - start < millis);
+}
+function a() { b(); }
+function b() { c(); }
+function c() { d(); }
+function d() { e(); }
+function e() { f(); }
+function f() { g(); }
+function g() { top(); }
+
+function top() {
+    for (let i = 0; i < 10000; i++) {
+        i++;
+        i--;
+    }
+}
+noInline(top);
+
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("ScriptProfiler.Samples.ManyCallFrames");
+
+    suite.addTestCase({
+        name: "Sampling Profiler sourceURL directive.",
+        description: "Sample some basic code.",
+        test: (resolve, reject) => {
+            InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
+                let tree = WebInspector.CallingContextTree.__test_makeTreeFromProtocolMessageObject(messageObject);
+
+                let trace = [
+                    {name: "top"},
+                    {name: "g"},
+                    {name: "f"},
+                    {name: "e"},
+                    {name: "d"},
+                    {name: "c"},
+                    {name: "b"},
+                    {name: "a"},
+                    {name: "runFor"}
+                ];
+                ProtocolTest.expectThat(tree.__test_matchesStackTrace(trace), "Should have seen stacktrace:\n" + JSON.stringify(trace, undefined, 2));
+                resolve();
+            });
+
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {includeSamples: true});
+            ProtocolTest.evaluateInPage("runFor(a, 100)");
+            InspectorProtocol.sendCommand('ScriptProfiler.stopTracking', {});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+</body>
+</html>
diff --git a/LayoutTests/inspector/sampling-profiler/named-function-expression-expected.txt b/LayoutTests/inspector/sampling-profiler/named-function-expression-expected.txt
new file mode 100644 (file)
index 0000000..b197462
--- /dev/null
@@ -0,0 +1,16 @@
+
+== Running test suite: ScriptProfiler.Samples.NamedFunctionExpression
+-- Running test case: Sampling Profiler basic
+PASS: Should have seen stacktrace:
+[
+  {
+    "name": "bar"
+  },
+  {
+    "name": "foo"
+  },
+  {
+    "name": "runFor"
+  }
+]
+
diff --git a/LayoutTests/inspector/sampling-profiler/named-function-expression.html b/LayoutTests/inspector/sampling-profiler/named-function-expression.html
new file mode 100644 (file)
index 0000000..ed5a876
--- /dev/null
@@ -0,0 +1,58 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script>
+
+function runFor(func, millis) {
+    let start = Date.now();
+    do {
+        func();
+    } while (Date.now() - start < millis);
+}
+
+function foo() {
+    let bar = function() {
+        for (let i = 0; i < 10000; i++) {
+            i++;
+            i--;
+        }
+    };
+    noInline(bar);
+    bar();
+}
+noInline(foo);
+
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("ScriptProfiler.Samples.NamedFunctionExpression");
+
+    suite.addTestCase({
+        name: "Sampling Profiler basic",
+        description: "Sample some basic code.",
+        test: (resolve, reject) => {
+            InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
+                let tree = WebInspector.CallingContextTree.__test_makeTreeFromProtocolMessageObject(messageObject);
+                let trace = [
+                    {name: "bar"},
+                    {name: "foo"},
+                    {name: "runFor"}
+                ];
+                ProtocolTest.expectThat(tree.__test_matchesStackTrace(trace), "Should have seen stacktrace:\n" + JSON.stringify(trace, undefined, 2));
+                resolve();
+            });
+
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {includeSamples: true});
+            ProtocolTest.evaluateInPage("runFor(foo, 100)");
+            InspectorProtocol.sendCommand('ScriptProfiler.stopTracking', {});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+</body>
+</html>
index b1ddfd5..011ded7 100644 (file)
@@ -8,6 +8,4 @@ ScriptProfiler.trackingStart
 ScriptProfiler.trackingUpdate
 PASS: Event type should be API.
 ScriptProfiler.trackingComplete
-PASS: Profiles should exist when complete.
-PASS: Should be 1 profile for this session.
 
index a01f0c1..03163b1 100644 (file)
@@ -27,12 +27,10 @@ function test()
 
             InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
                 ProtocolTest.log("ScriptProfiler.trackingComplete");
-                ProtocolTest.expectThat(Array.isArray(messageObject.params.profiles), "Profiles should exist when complete.");
-                ProtocolTest.expectThat(messageObject.params.profiles.length === 1, "Should be 1 profile for this session.");
                 resolve();
             });
 
-            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {profile: true});
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {});
             ProtocolTest.evaluateInPage("triggerAPIScript();"); // This ultimately uses the JSEvaluateScript API on the Page's context.
             InspectorProtocol.sendCommand("ScriptProfiler.stopTracking", {});
         }
index fcfabf7..fde77bd 100644 (file)
@@ -8,6 +8,4 @@ ScriptProfiler.trackingStart
 ScriptProfiler.trackingUpdate
 PASS: Event type should be Microtask.
 ScriptProfiler.trackingComplete
-PASS: Profiles should exist when complete.
-PASS: Should be 1 profile for this session.
 
index 3d60b84..fbbd8fe 100644 (file)
@@ -28,12 +28,10 @@ function test()
 
             InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
                 ProtocolTest.log("ScriptProfiler.trackingComplete");
-                ProtocolTest.expectThat(Array.isArray(messageObject.params.profiles), "Profiles should exist when complete.");
-                ProtocolTest.expectThat(messageObject.params.profiles.length === 1, "Should be 1 profile for this session.");
                 resolve();
             });
 
-            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {profile: true});
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {});
             ProtocolTest.evaluateInPage("triggerMicrotask()");
             InspectorProtocol.sendCommand("ScriptProfiler.stopTracking", {});
         }
index 1958e58..febc6a5 100644 (file)
@@ -14,6 +14,4 @@ PASS: Event type should be Other.
 ScriptProfiler.trackingUpdate
 PASS: Event type should be Other.
 ScriptProfiler.trackingComplete
-PASS: Profiles should exist when complete.
-PASS: Should be 3 profiles for this session.
 
index e30e9cb..ed38ef9 100644 (file)
@@ -41,12 +41,10 @@ function test()
 
             InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
                 ProtocolTest.log("ScriptProfiler.trackingComplete");
-                ProtocolTest.expectThat(Array.isArray(messageObject.params.profiles), "Profiles should exist when complete.");
-                ProtocolTest.expectThat(messageObject.params.profiles.length === 3, "Should be 3 profiles for this session.");
                 resolve();
             });
 
-            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {profile: true});
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {});
             ProtocolTest.evaluateInPage("triggerScriptEvaluation()");
             ProtocolTest.evaluateInPage("triggerEventDispatchEvaluation()");
             ProtocolTest.evaluateInPage("triggerTimerEvaluation()");
index 9cbdf06..3634ea8 100644 (file)
@@ -6,6 +6,4 @@ Tests that ScriptProfiler.startTracking and ScriptProfiler.stopTracking trigger
 ScriptProfiler.trackingStart
 PASS: Should have a timestamp when starting.
 ScriptProfiler.trackingComplete
-PASS: Profiles should exist when complete.
-PASS: Should be no profiles for this session.
 
index 585bd4c..2703f24 100644 (file)
@@ -18,12 +18,10 @@ function test()
 
             InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingComplete"}).then((messageObject) => {
                 ProtocolTest.log("ScriptProfiler.trackingComplete");
-                ProtocolTest.expectThat(Array.isArray(messageObject.params.profiles), "Profiles should exist when complete.");
-                ProtocolTest.expectThat(!messageObject.params.profiles.length, "Should be no profiles for this session.");
                 resolve();
             });
 
-            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {profile: true});
+            InspectorProtocol.sendCommand("ScriptProfiler.startTracking", {});
             InspectorProtocol.sendCommand("ScriptProfiler.stopTracking", {});
         }
     });
index 29c92d9..bd7be44 100644 (file)
@@ -1,3 +1,76 @@
+2016-01-20  Saam barati  <sbarati@apple.com>
+
+        Web Inspector: Hook the sampling profiler into the Timelines UI
+        https://bugs.webkit.org/show_bug.cgi?id=152766
+        <rdar://problem/24066360>
+
+        Reviewed by Joseph Pecoraro.
+
+        This patch adds some necessary functions to SamplingProfiler::StackFrame
+        to allow it to give data to the Inspector for the timelines UI. i.e, the
+        sourceID of the executable of a stack frame.
+
+        This patch also swaps in the SamplingProfiler in place of the
+        LegacyProfiler inside InspectorScriptProfilerAgent. It adds
+        the necessary protocol data to allow the SamplingProfiler's
+        data to hook into the timelines UI.
+
+        * debugger/Debugger.cpp:
+        (JSC::Debugger::setProfilingClient):
+        (JSC::Debugger::willEvaluateScript):
+        (JSC::Debugger::didEvaluateScript):
+        (JSC::Debugger::toggleBreakpoint):
+        * debugger/Debugger.h:
+        * debugger/ScriptProfilingScope.h:
+        (JSC::ScriptProfilingScope::ScriptProfilingScope):
+        (JSC::ScriptProfilingScope::~ScriptProfilingScope):
+        * inspector/agents/InspectorScriptProfilerAgent.cpp:
+        (Inspector::InspectorScriptProfilerAgent::willDestroyFrontendAndBackend):
+        (Inspector::InspectorScriptProfilerAgent::startTracking):
+        (Inspector::InspectorScriptProfilerAgent::stopTracking):
+        (Inspector::InspectorScriptProfilerAgent::isAlreadyProfiling):
+        (Inspector::InspectorScriptProfilerAgent::willEvaluateScript):
+        (Inspector::InspectorScriptProfilerAgent::didEvaluateScript):
+        (Inspector::InspectorScriptProfilerAgent::addEvent):
+        (Inspector::buildSamples):
+        (Inspector::InspectorScriptProfilerAgent::trackingComplete):
+        (Inspector::buildAggregateCallInfoInspectorObject): Deleted.
+        (Inspector::buildInspectorObject): Deleted.
+        (Inspector::buildProfileInspectorObject): Deleted.
+        * inspector/agents/InspectorScriptProfilerAgent.h:
+        * inspector/protocol/ScriptProfiler.json:
+        * jsc.cpp:
+        (functionSamplingProfilerStackTraces):
+        * runtime/SamplingProfiler.cpp:
+        (JSC::SamplingProfiler::start):
+        (JSC::SamplingProfiler::stop):
+        (JSC::SamplingProfiler::clearData):
+        (JSC::SamplingProfiler::StackFrame::displayName):
+        (JSC::SamplingProfiler::StackFrame::displayNameForJSONTests):
+        (JSC::SamplingProfiler::StackFrame::startLine):
+        (JSC::SamplingProfiler::StackFrame::startColumn):
+        (JSC::SamplingProfiler::StackFrame::sourceID):
+        (JSC::SamplingProfiler::StackFrame::url):
+        (JSC::SamplingProfiler::stackTraces):
+        (JSC::SamplingProfiler::stackTracesAsJSON):
+        (JSC::displayName): Deleted.
+        (JSC::SamplingProfiler::stacktracesAsJSON): Deleted.
+        * runtime/SamplingProfiler.h:
+        (JSC::SamplingProfiler::StackFrame::StackFrame):
+        (JSC::SamplingProfiler::getLock):
+        (JSC::SamplingProfiler::setTimingInterval):
+        (JSC::SamplingProfiler::totalTime):
+        (JSC::SamplingProfiler::setStopWatch):
+        (JSC::SamplingProfiler::stackTraces): Deleted.
+        * tests/stress/sampling-profiler-anonymous-function.js:
+        (platformSupportsSamplingProfiler.baz):
+        (platformSupportsSamplingProfiler):
+        * tests/stress/sampling-profiler-basic.js:
+        (platformSupportsSamplingProfiler.nothing):
+        (platformSupportsSamplingProfiler.top):
+        * tests/stress/sampling-profiler/samplingProfiler.js:
+        (doesTreeHaveStackTrace):
+
 2016-01-20  Keith Miller  <keith_miller@apple.com>
 
         TypedArray's .buffer does not return the JSArrayBuffer that was passed to it on creation.
index ee07005..8155ac0 100644 (file)
@@ -234,14 +234,14 @@ void Debugger::setProfilingClient(ProfilingClient* client)
     recompileAllJSFunctions();
 }
 
-double Debugger::willEvaluateScript(JSGlobalObject& globalObject)
+double Debugger::willEvaluateScript()
 {
-    return m_profilingClient->willEvaluateScript(globalObject);
+    return m_profilingClient->willEvaluateScript();
 }
 
-void Debugger::didEvaluateScript(JSGlobalObject& globalObject, double startTime, ProfilingReason reason)
+void Debugger::didEvaluateScript(double startTime, ProfilingReason reason)
 {
-    m_profilingClient->didEvaluateScript(globalObject, startTime, reason);
+    m_profilingClient->didEvaluateScript(startTime, reason);
 }
 
 void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot)
index bd3d643..2e91aaf 100644 (file)
@@ -132,15 +132,15 @@ public:
     public:
         virtual ~ProfilingClient() { }
         virtual bool isAlreadyProfiling() const = 0;
-        virtual double willEvaluateScript(JSGlobalObject&) = 0;
-        virtual void didEvaluateScript(JSGlobalObject&, double startTime, ProfilingReason) = 0;
+        virtual double willEvaluateScript() = 0;
+        virtual void didEvaluateScript(double startTime, ProfilingReason) = 0;
     };
 
     void setProfilingClient(ProfilingClient*);
     bool hasProfilingClient() const { return m_profilingClient != nullptr; }
     bool isAlreadyProfiling() const { return m_profilingClient && m_profilingClient->isAlreadyProfiling(); }
-    double willEvaluateScript(JSGlobalObject&);
-    void didEvaluateScript(JSGlobalObject&, double startTime, ProfilingReason);
+    double willEvaluateScript();
+    void didEvaluateScript(double startTime, ProfilingReason);
 
 protected:
     virtual bool needPauseHandling(JSGlobalObject*) { return false; }
index e788354..ba6bdfc 100644 (file)
@@ -39,13 +39,13 @@ public:
         , m_reason(reason)
     {
         if (shouldStartProfile())
-            m_startTime = m_globalObject->debugger()->willEvaluateScript(*m_globalObject);
+            m_startTime = m_globalObject->debugger()->willEvaluateScript();
     }
 
     ~ScriptProfilingScope()
     {
         if (shouldEndProfile())
-            m_globalObject->debugger()->didEvaluateScript(*m_globalObject, m_startTime.value(), m_reason);
+            m_globalObject->debugger()->didEvaluateScript(m_startTime.value(), m_reason);
     }
 
 private:
index 798e7ce..fe50de7 100644 (file)
@@ -27,7 +27,7 @@
 #include "InspectorScriptProfilerAgent.h"
 
 #include "InspectorEnvironment.h"
-#include "LegacyProfiler.h"
+#include "SamplingProfiler.h"
 #include <wtf/RunLoop.h>
 #include <wtf/Stopwatch.h>
 
@@ -57,15 +57,29 @@ void InspectorScriptProfilerAgent::willDestroyFrontendAndBackend(DisconnectReaso
     stopTracking(ignored);
 }
 
-void InspectorScriptProfilerAgent::startTracking(ErrorString&, const bool* profile)
+void InspectorScriptProfilerAgent::startTracking(ErrorString&, const bool* includeSamples)
 {
     if (m_tracking)
         return;
 
     m_tracking = true;
 
-    if (profile && *profile)
-        m_enableLegacyProfiler = true;
+#if ENABLE(SAMPLING_PROFILER)
+    if (includeSamples && *includeSamples) {
+        VM& vm = m_environment.scriptDebugServer().vm();
+        vm.ensureSamplingProfiler(m_environment.executionStopwatch());
+
+        SamplingProfiler& samplingProfiler = *vm.samplingProfiler();
+        LockHolder locker(samplingProfiler.getLock());
+
+        samplingProfiler.setStopWatch(locker, m_environment.executionStopwatch());
+        samplingProfiler.noticeCurrentThreadAsJSCExecutionThread(locker);
+        samplingProfiler.start(locker);
+        m_enabledSamplingProfiler = true;
+    }
+#else
+    UNUSED_PARAM(includeSamples);
+#endif // ENABLE(SAMPLING_PROFILER)
 
     m_environment.scriptDebugServer().setProfilingClient(this);
 
@@ -78,7 +92,6 @@ void InspectorScriptProfilerAgent::stopTracking(ErrorString&)
         return;
 
     m_tracking = false;
-    m_enableLegacyProfiler = false;
     m_activeEvaluateScript = false;
 
     m_environment.scriptDebugServer().setProfilingClient(nullptr);
@@ -91,23 +104,25 @@ bool InspectorScriptProfilerAgent::isAlreadyProfiling() const
     return m_activeEvaluateScript;
 }
 
-double InspectorScriptProfilerAgent::willEvaluateScript(JSGlobalObject& globalObject)
+double InspectorScriptProfilerAgent::willEvaluateScript()
 {
     m_activeEvaluateScript = true;
 
-    if (m_enableLegacyProfiler)
-        LegacyProfiler::profiler()->startProfiling(globalObject.globalExec(), ASCIILiteral("ScriptProfiler"), m_environment.executionStopwatch());
+#if ENABLE(SAMPLING_PROFILER)
+    if (m_enabledSamplingProfiler) {
+        SamplingProfiler* samplingProfiler = m_environment.scriptDebugServer().vm().samplingProfiler();
+        RELEASE_ASSERT(samplingProfiler);
+        samplingProfiler->noticeCurrentThreadAsJSCExecutionThread();
+    }
+#endif
 
     return m_environment.executionStopwatch()->elapsedTime();
 }
 
-void InspectorScriptProfilerAgent::didEvaluateScript(JSGlobalObject& globalObject, double startTime, ProfilingReason reason)
+void InspectorScriptProfilerAgent::didEvaluateScript(double startTime, ProfilingReason reason)
 {
     m_activeEvaluateScript = false;
 
-    if (m_enableLegacyProfiler)
-        m_profiles.append(LegacyProfiler::profiler()->stopProfiling(globalObject.globalExec(), ASCIILiteral("ScriptProfiler")));
-
     double endTime = m_environment.executionStopwatch()->elapsedTime();
 
     addEvent(startTime, endTime, reason);
@@ -141,71 +156,57 @@ void InspectorScriptProfilerAgent::addEvent(double startTime, double endTime, Pr
     m_frontendDispatcher->trackingUpdate(WTFMove(event));
 }
 
-static Ref<Protocol::Timeline::CPUProfileNodeAggregateCallInfo> buildAggregateCallInfoInspectorObject(const JSC::ProfileNode* node)
-{
-    double startTime = node->calls()[0].startTime();
-    double endTime = node->calls().last().startTime() + node->calls().last().elapsedTime();
-
-    double totalTime = 0;
-    for (const JSC::ProfileNode::Call& call : node->calls())
-        totalTime += call.elapsedTime();
-
-    return Protocol::Timeline::CPUProfileNodeAggregateCallInfo::create()
-        .setCallCount(node->calls().size())
-        .setStartTime(startTime)
-        .setEndTime(endTime)
-        .setTotalTime(totalTime)
-        .release();
-}
-
-static Ref<Protocol::Timeline::CPUProfileNode> buildInspectorObject(const JSC::ProfileNode* node)
+#if ENABLE(SAMPLING_PROFILER)
+static Ref<Protocol::ScriptProfiler::Samples> buildSamples(Vector<SamplingProfiler::StackTrace>& samplingProfilerStackTraces, double totalTime)
 {
-    auto result = Protocol::Timeline::CPUProfileNode::create()
-        .setId(node->id())
-        .setCallInfo(buildAggregateCallInfoInspectorObject(node))
-        .release();
-
-    if (!node->functionName().isEmpty())
-        result->setFunctionName(node->functionName());
-
-    if (!node->url().isEmpty()) {
-        result->setUrl(node->url());
-        result->setLineNumber(node->lineNumber());
-        result->setColumnNumber(node->columnNumber());
+    Ref<Protocol::Array<Protocol::ScriptProfiler::StackTrace>> stackTraces = Protocol::Array<Protocol::ScriptProfiler::StackTrace>::create();
+    for (SamplingProfiler::StackTrace& stackTrace : samplingProfilerStackTraces) {
+        Ref<Protocol::Array<Protocol::ScriptProfiler::StackFrame>> frames = Protocol::Array<Protocol::ScriptProfiler::StackFrame>::create();
+        for (SamplingProfiler::StackFrame& stackFrame : stackTrace.frames) {
+            Ref<Protocol::ScriptProfiler::StackFrame> frame = Protocol::ScriptProfiler::StackFrame::create()
+                .setSourceID(String::number(stackFrame.sourceID()))
+                .setName(stackFrame.displayName())
+                .setLine(stackFrame.startLine())
+                .setColumn(stackFrame.startColumn())
+                .setUrl(stackFrame.url())
+                .release();
+            frames->addItem(WTFMove(frame));
+        }
+        Ref<Protocol::ScriptProfiler::StackTrace> inspectorStackTrace = Protocol::ScriptProfiler::StackTrace::create()
+            .setTimestamp(stackTrace.timestamp)
+            .setStackFrames(WTFMove(frames))
+            .release();
+        stackTraces->addItem(WTFMove(inspectorStackTrace));
     }
 
-    if (!node->children().isEmpty()) {
-        auto children = Protocol::Array<Protocol::Timeline::CPUProfileNode>::create();
-        for (RefPtr<JSC::ProfileNode> profileNode : node->children())
-            children->addItem(buildInspectorObject(profileNode.get()));
-        result->setChildren(WTFMove(children));
-    }
-
-    return result;
-}
-
-static Ref<Protocol::Timeline::CPUProfile> buildProfileInspectorObject(const JSC::Profile* profile)
-{
-    auto rootNodes = Protocol::Array<Protocol::Timeline::CPUProfileNode>::create();
-    for (RefPtr<JSC::ProfileNode> profileNode : profile->rootNode()->children())
-        rootNodes->addItem(buildInspectorObject(profileNode.get()));
-
-    return Protocol::Timeline::CPUProfile::create()
-        .setRootNodes(WTFMove(rootNodes))
+    return Protocol::ScriptProfiler::Samples::create()
+        .setStackTraces(WTFMove(stackTraces))
+        .setTotalTime(totalTime)
         .release();
 }
+#endif // ENABLE(SAMPLING_PROFILER)
 
 void InspectorScriptProfilerAgent::trackingComplete()
 {
-    RefPtr<Inspector::Protocol::Array<InspectorValue>> profiles = Inspector::Protocol::Array<InspectorValue>::create();
-    for (auto& profile : m_profiles) {
-        Ref<InspectorValue> value = buildProfileInspectorObject(profile.get());
-        profiles->addItem(WTFMove(value));
-    }
-
-    m_frontendDispatcher->trackingComplete(profiles);
-
-    m_profiles.clear();
+#if ENABLE(SAMPLING_PROFILER)
+    if (m_enabledSamplingProfiler) {
+        SamplingProfiler* samplingProfiler = m_environment.scriptDebugServer().vm().samplingProfiler();
+        RELEASE_ASSERT(samplingProfiler);
+        LockHolder locker(samplingProfiler->getLock());
+        samplingProfiler->stop(locker);
+        Ref<Protocol::ScriptProfiler::Samples> samples = buildSamples(samplingProfiler->stackTraces(locker), samplingProfiler->totalTime(locker));
+        samplingProfiler->clearData(locker);
+
+        locker.unlockEarly();
+
+        m_enabledSamplingProfiler = false;
+
+        m_frontendDispatcher->trackingComplete(WTFMove(samples));
+    } else
+        m_frontendDispatcher->trackingComplete(nullptr);
+#else
+    m_frontendDispatcher->trackingComplete(nullptr);
+#endif // ENABLE(SAMPLING_PROFILER)
 }
 
 } // namespace Inspector
index 3f40164..0a5286d 100644 (file)
@@ -50,13 +50,13 @@ public:
     virtual void willDestroyFrontendAndBackend(DisconnectReason) override;
 
     // ScriptProfilerBackendDispatcherHandler
-    virtual void startTracking(ErrorString&, const bool* profile) override;
+    virtual void startTracking(ErrorString&, const bool* includeSamples) override;
     virtual void stopTracking(ErrorString&) override;
 
     // Debugger::ProfilingClient
     virtual bool isAlreadyProfiling() const override;
-    virtual double willEvaluateScript(JSC::JSGlobalObject&) override;
-    virtual void didEvaluateScript(JSC::JSGlobalObject&, double, JSC::ProfilingReason) override;
+    virtual double willEvaluateScript() override;
+    virtual void didEvaluateScript(double, JSC::ProfilingReason) override;
 
 private:
     struct Event {
@@ -70,10 +70,9 @@ private:
 
     std::unique_ptr<ScriptProfilerFrontendDispatcher> m_frontendDispatcher;
     RefPtr<ScriptProfilerBackendDispatcher> m_backendDispatcher;
-    Vector<RefPtr<JSC::Profile>> m_profiles;
     InspectorEnvironment& m_environment;
     bool m_tracking { false };
-    bool m_enableLegacyProfiler { false };
+    bool m_enabledSamplingProfiler { false };
     bool m_activeEvaluateScript { false };
 };
 
index fa7de25..dc17edf 100644 (file)
                 { "name": "endTime", "type": "number" },
                 { "name": "type", "$ref": "EventType" }
             ]
+        },
+        {
+            "id": "StackFrame",
+            "type": "object",
+            "properties": [
+                { "name": "sourceID", "$ref": "Debugger.ScriptId", "description": "Unique script identifier." },
+                { "name": "name", "type": "string", "description": "A displayable name for the stack frame. i.e function name, (program), etc." },
+                { "name": "line", "type": "integer" },
+                { "name": "column", "type": "integer" },
+                { "name": "url", "type": "string" }
+            ]
+        },
+        {
+            "id": "StackTrace",
+            "type": "object",
+            "properties": [
+                { "name": "timestamp", "type": "number" },
+                { "name": "stackFrames", "type": "array", "items": { "$ref": "StackFrame" }, "description": "First array item is the bottom of the call stack and last array item is the top of the call stack." }
+            ]
+        },
+        {
+            "id": "Samples",
+            "type": "object",
+            "properties": [
+                { "name": "totalTime", "type": "number", "description": "Total execution time of the profiler's data. (Note: not total elapsed time.)" },
+                { "name": "stackTraces", "type": "array", "items": { "$ref": "StackTrace" } }
+            ]
         }
     ],
     "commands": [
@@ -22,7 +49,7 @@
             "name": "startTracking",
             "description": "Start tracking script evaluations.",
             "parameters": [
-                { "name": "profile", "type": "boolean", "optional": true, "description": "Profile script evaluations, defaults to false." }
+                { "name": "includeSamples", "type": "boolean", "optional": true, "description": "Start the sampling profiler, defaults to false." }
             ]
         },
         {
@@ -49,7 +76,7 @@
             "name": "trackingComplete",
             "description": "When tracking is complete the backend will send any buffered data, such as profiling information.",
             "parameters": [
-                { "name": "profiles", "type": "array", "items": { "type": "any" }, "optional": true }
+                { "name": "samples", "$ref": "Samples", "optional": true, "description": "Stack traces." }
             ]
         }
     ]
index 182a220..fc26f5d 100644 (file)
@@ -1642,7 +1642,7 @@ EncodedJSValue JSC_HOST_CALL functionStartSamplingProfiler(ExecState* exec)
 EncodedJSValue JSC_HOST_CALL functionSamplingProfilerStackTraces(ExecState* exec)
 {
     RELEASE_ASSERT(exec->vm().samplingProfiler());
-    String jsonString = exec->vm().samplingProfiler()->stacktracesAsJSON();
+    String jsonString = exec->vm().samplingProfiler()->stackTracesAsJSON();
     exec->vm().samplingProfiler()->clearData();
     EncodedJSValue result = JSValue::encode(JSONParse(exec, jsonString));
     RELEASE_ASSERT(!exec->hadException());
index cdd7385..33b3dfb 100644 (file)
@@ -405,6 +405,12 @@ void SamplingProfiler::shutdown()
 void SamplingProfiler::start()
 {
     LockHolder locker(m_lock);
+    start(locker);
+}
+
+void SamplingProfiler::start(const LockHolder& locker)
+{
+    ASSERT(m_lock.isLocked());
     m_isActive = true;
     dispatchIfNecessary(locker);
 }
@@ -412,6 +418,12 @@ void SamplingProfiler::start()
 void SamplingProfiler::stop()
 {
     LockHolder locker(m_lock);
+    stop(locker);
+}
+
+void SamplingProfiler::stop(const LockHolder&)
+{
+    ASSERT(m_lock.isLocked());
     m_isActive = false;
     reportStats();
 }
@@ -469,43 +481,138 @@ void SamplingProfiler::noticeVMEntry()
 void SamplingProfiler::clearData()
 {
     LockHolder locker(m_lock);
+    clearData(locker);
+}
+
+void SamplingProfiler::clearData(const LockHolder&)
+{
+    ASSERT(m_lock.isLocked());
     m_stackTraces.clear();
     m_seenExecutables.clear();
     m_indexOfNextStackTraceToVerify = 0;
 }
 
-static String displayName(const SamplingProfiler::StackFrame& stackFrame)
+String SamplingProfiler::StackFrame::displayName()
 {
-    if (stackFrame.frameType == FrameType::Unknown)
-        return ASCIILiteral("<unknown>");
-    if (stackFrame.frameType == FrameType::Host)
-        return ASCIILiteral("<host>");
-    RELEASE_ASSERT(stackFrame.frameType != FrameType::UnverifiedCallee);
+    if (frameType == FrameType::Unknown)
+        return ASCIILiteral("(unknown)");
+    if (frameType == FrameType::Host)
+        return ASCIILiteral("(host)");
+    RELEASE_ASSERT(frameType != FrameType::UnverifiedCallee);
 
-    ExecutableBase* executable = stackFrame.u.verifiedExecutable;
+    ExecutableBase* executable = u.verifiedExecutable;
     if (executable->isHostFunction())
-        return ASCIILiteral("<host>");
+        return static_cast<NativeExecutable*>(executable)->name();
+
+    if (executable->isFunctionExecutable())
+        return static_cast<FunctionExecutable*>(executable)->inferredName().string();
+    if (executable->isProgramExecutable() || executable->isEvalExecutable())
+        return ASCIILiteral("(program)");
+    if (executable->isModuleProgramExecutable())
+        return ASCIILiteral("(module)");
+
+    RELEASE_ASSERT_NOT_REACHED();
+    return String();
+}
+
+String SamplingProfiler::StackFrame::displayNameForJSONTests()
+{
+    if (frameType == FrameType::Unknown)
+        return ASCIILiteral("(unknown)");
+    if (frameType == FrameType::Host)
+        return ASCIILiteral("(host)");
+    RELEASE_ASSERT(frameType != FrameType::UnverifiedCallee);
+
+    ExecutableBase* executable = u.verifiedExecutable;
+    if (executable->isHostFunction())
+        return static_cast<NativeExecutable*>(executable)->name();
 
     if (executable->isFunctionExecutable()) {
         String result = static_cast<FunctionExecutable*>(executable)->inferredName().string();
-        if (!result.isEmpty())
-            return result;
-        return ASCIILiteral("<anonymous-function>");
+        if (result.isEmpty())
+            return ASCIILiteral("(anonymous function)");
+        return result;
     }
     if (executable->isEvalExecutable())
-        return ASCIILiteral("<eval>");
+        return ASCIILiteral("(eval)");
     if (executable->isProgramExecutable())
-        return ASCIILiteral("<global>");
+        return ASCIILiteral("(program)");
     if (executable->isModuleProgramExecutable())
-        return ASCIILiteral("<module>");
+        return ASCIILiteral("(module)");
 
     RELEASE_ASSERT_NOT_REACHED();
-    return "";
+    return String();
+}
+
+int SamplingProfiler::StackFrame::startLine()
+{
+    if (frameType == FrameType::Unknown || frameType == FrameType::Host)
+        return -1;
+    RELEASE_ASSERT(frameType != FrameType::UnverifiedCallee);
+
+    ExecutableBase* executable = u.verifiedExecutable;
+    if (executable->isHostFunction())
+        return -1;
+    return static_cast<ScriptExecutable*>(executable)->firstLine();
+}
+
+unsigned SamplingProfiler::StackFrame::startColumn()
+{
+    if (frameType == FrameType::Unknown || frameType == FrameType::Host)
+        return -1;
+    RELEASE_ASSERT(frameType != FrameType::UnverifiedCallee);
+
+    ExecutableBase* executable = u.verifiedExecutable;
+    if (executable->isHostFunction())
+        return -1;
+
+    return static_cast<ScriptExecutable*>(executable)->startColumn();
 }
 
-String SamplingProfiler::stacktracesAsJSON()
+intptr_t SamplingProfiler::StackFrame::sourceID()
 {
-    m_lock.lock();
+    if (frameType == FrameType::Unknown || frameType == FrameType::Host)
+        return -1;
+    RELEASE_ASSERT(frameType != FrameType::UnverifiedCallee);
+
+    ExecutableBase* executable = u.verifiedExecutable;
+    if (executable->isHostFunction())
+        return -1;
+
+    return static_cast<ScriptExecutable*>(executable)->sourceID();
+}
+
+String SamplingProfiler::StackFrame::url()
+{
+    if (frameType == FrameType::Unknown || frameType == FrameType::Host)
+        return emptyString();
+    RELEASE_ASSERT(frameType != FrameType::UnverifiedCallee);
+
+    ExecutableBase* executable = u.verifiedExecutable;
+    if (executable->isHostFunction())
+        return emptyString();
+
+    String url = static_cast<ScriptExecutable*>(executable)->sourceURL();
+    if (url.isEmpty())
+        return static_cast<ScriptExecutable*>(executable)->source().provider()->sourceURL(); // Fall back to sourceURL directive.
+    return url;
+}
+
+Vector<SamplingProfiler::StackTrace>& SamplingProfiler::stackTraces(const LockHolder&)
+{
+    ASSERT(m_lock.isLocked());
+    {
+        HeapIterationScope heapIterationScope(m_vm.heap);
+        processUnverifiedStackTraces();
+    }
+
+    return m_stackTraces;
+}
+
+String SamplingProfiler::stackTracesAsJSON()
+{
+    LockHolder locker(m_lock);
+
     {
         HeapIterationScope heapIterationScope(m_vm.heap);
         processUnverifiedStackTraces();
@@ -519,14 +626,14 @@ String SamplingProfiler::stacktracesAsJSON()
         if (loopedOnce)
             json.appendLiteral(",");
     };
-    for (const StackTrace& stackTrace : m_stackTraces) {
+    for (StackTrace& stackTrace : m_stackTraces) {
         comma();
         json.appendLiteral("[");
         loopedOnce = false;
-        for (const StackFrame& stackFrame : stackTrace.frames) {
+        for (StackFrame& stackFrame : stackTrace.frames) {
             comma();
             json.appendLiteral("\"");
-            json.append(displayName(stackFrame));
+            json.append(stackFrame.displayNameForJSONTests());
             json.appendLiteral("\"");
             loopedOnce = true;
         }
@@ -536,8 +643,6 @@ String SamplingProfiler::stacktracesAsJSON()
 
     json.appendLiteral("]");
 
-    m_lock.unlock();
-
     return json.toString();
 }
 
index da9ae48..ec0c0a9 100644 (file)
@@ -50,6 +50,7 @@ public:
         Host, 
         Unknown 
     };
+
     struct StackFrame {
         StackFrame(FrameType frameType, EncodedJSValue callee)
             : frameType(frameType)
@@ -72,7 +73,15 @@ public:
             EncodedJSValue unverifiedCallee;
             ExecutableBase* verifiedExecutable;
         } u;
+
+        String displayName();
+        String displayNameForJSONTests(); // Used for JSC stress tests because they want the "(anonymous function)" string for anonymous functions and they want "(eval)" for eval'd code.
+        int startLine();
+        unsigned startColumn();
+        intptr_t sourceID();
+        String url();
     };
+
     struct StackTrace {
         bool needsVerification;
         double timestamp;
@@ -88,13 +97,18 @@ public:
     Lock& getLock() { return m_lock; }
     void setTimingInterval(std::chrono::microseconds interval) { m_timingInterval = interval; }
     JS_EXPORT_PRIVATE void start();
+    void start(const LockHolder&);
     void stop();
-    const Vector<StackTrace>& stackTraces() const { return m_stackTraces; }
-    JS_EXPORT_PRIVATE String stacktracesAsJSON();
+    void stop(const LockHolder&);
+    Vector<StackTrace>& stackTraces(const LockHolder&);
+    JS_EXPORT_PRIVATE String stackTracesAsJSON();
     JS_EXPORT_PRIVATE void noticeCurrentThreadAsJSCExecutionThread();
     void noticeCurrentThreadAsJSCExecutionThread(const LockHolder&);
     JS_EXPORT_PRIVATE void clearData();
+    void clearData(const LockHolder&);
     void processUnverifiedStackTraces(); // You should call this only after acquiring the lock.
+    double totalTime(const LockHolder&) { return m_totalTime; }
+    void setStopWatch(const LockHolder&, Ref<Stopwatch>&& stopwatch) { m_stopwatch = WTFMove(stopwatch); }
 
 private:
     void dispatchIfNecessary(const LockHolder&);
index cb4aa71..5876df7 100644 (file)
@@ -18,5 +18,5 @@ if (platformSupportsSamplingProfiler()) {
         });
     }
 
-    runTest(baz, ["<anonymous-function>", "foo", "baz"]);
+    runTest(baz, ["(anonymous function)", "foo", "baz"]);
 }
index 6bafe2a..6c0f21a 100644 (file)
@@ -17,7 +17,7 @@ if (platformSupportsSamplingProfiler()) {
     function nothing(x) { return x; }
     noInline(nothing);
 
-    runTest(foo, ["<host>", "bar", "foo"]);
+    runTest(foo, ["(host)", "bar", "foo"]);
 
     function top() { 
         let x = 0;
index f380eb1..3767a23 100644 (file)
@@ -38,7 +38,7 @@ function doesTreeHaveStackTrace(tree, stackTrace, isRunFromRunTest = true, verbo
     // stack trace should be top-down array with the deepest
     // call frame at index 0.
     if (isRunFromRunTest)
-        stackTrace = [...stackTrace, "runTest", "<global>"];
+        stackTrace = [...stackTrace, "runTest", "(program)"];
     else
         stackTrace = [...stackTrace];
     
index d201ada..66a4b0b 100644 (file)
@@ -1,3 +1,48 @@
+2016-01-20  Saam barati  <sbarati@apple.com>
+
+        Web Inspector: Hook the sampling profiler into the Timelines UI
+        https://bugs.webkit.org/show_bug.cgi?id=152766
+        <rdar://problem/24066360>
+
+        Reviewed by Joseph Pecoraro.
+
+        The main change in this patch is to swap in the SamplingProfiler
+        in place of the LegacyProfiler. To do this, we've created a data
+        structure called CallingContextTree which aggregates the SamplingProfiler's
+        data into an easy to manage tree. To see how the data structure works,
+        consider the following program:
+        ```
+        function bar() { // run code here for a long time. }
+        function baz() { // run code here for a long time. }
+        function foo() { bar(); baz(); }
+        foo();
+        ```
+        From this program, we will create a tree like this:
+                        (program)
+                            |
+                            |
+                           foo
+                           | |
+                          /   \
+                         /     \
+                        bar     baz
+        
+        From this type of tree, we can easily create a CPUProfile payload
+        object. Because the Timelines UI knows how to interact with the
+        CPUProfile object and display it, we currently map the tree to this object
+        to make it trivially easy to display the SamplingProfiler's data. In the future,
+        we may want to find ways to work directly with the CallingContextTree instead
+        of mapping it into another object.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Controllers/TimelineManager.js:
+        * UserInterface/Main.html:
+        * UserInterface/Models/CallingContextTree.js: Added.
+        * UserInterface/Models/ScriptInstrument.js:
+        * UserInterface/Protocol/ScriptProfilerObserver.js:
+        * UserInterface/TestStub.html:
+        * UserInterface/Views/ScriptTimelineView.js:
+
 2016-01-19  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Uncaught exception when logging an Error object
index db44333..ee16bda 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index d69473d..80e2d5e 100644 (file)
@@ -45,6 +45,8 @@ WebInspector.TimelineManager = class TimelineManager extends WebInspector.Object
         this._webTimelineScriptRecordsExpectingScriptProfilerEvents = null;
         this._scriptProfilerRecords = null;
 
+        this._callingContextTree = null;
+
         this.reset();
     }
 
@@ -682,15 +684,23 @@ WebInspector.TimelineManager = class TimelineManager extends WebInspector.Object
             this._addRecord(record);
     }
 
-    scriptProfilerTrackingCompleted(profiles)
+    scriptProfilerTrackingCompleted(samples)
     {
         console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length);
 
-        // Associate the profiles with the ScriptProfiler created records.
-        if (profiles) {
-            console.assert(this._scriptProfilerRecords.length === profiles.length, this._scriptProfilerRecords.length, profiles.length);
-            for (let i = 0; i < this._scriptProfilerRecords.length; ++i)
-                this._scriptProfilerRecords[i].profilePayload = profiles[i];
+        if (samples) {
+            if (!this._callingContextTree)
+                this._callingContextTree = new WebInspector.CallingContextTree;
+
+            // Associate the stackTraces with the ScriptProfiler created records.
+            let stackTraces = samples.stackTraces;
+            for (let i = 0; i < stackTraces.length; i++)
+                this._callingContextTree.updateTreeWithStackTrace(stackTraces[i]);
+
+            for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
+                let record = this._scriptProfilerRecords[i];
+                record.profilePayload = this._callingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
+            }
         }
 
         // Associate the ScriptProfiler created records with Web Timeline records.
index 1193114..8d1660f 100644 (file)
     <script src="Models/BackForwardEntry.js"></script>
     <script src="Models/Branch.js"></script>
     <script src="Models/Breakpoint.js"></script>
+    <script src="Models/CallingContextTree.js"></script>
     <script src="Models/CSSCompletions.js"></script>
     <script src="Models/CSSKeywordCompletions.js"></script>
     <script src="Models/CSSMedia.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Models/CallingContextTree.js b/Source/WebInspectorUI/UserInterface/Models/CallingContextTree.js
new file mode 100644 (file)
index 0000000..7daeaca
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.CallingContextTree = class CallingContextTree extends WebInspector.Object
+{
+    constructor()
+    {
+        super();
+
+        this._root = new WebInspector.CCTNode(-1, -1, -1, "<root>", null);
+        this._totalNumberOfSamples = 0;
+    }
+
+    // Public
+
+    get totalNumberOfSamples() { return this._totalNumberOfSamples; }
+    
+    updateTreeWithStackTrace({timestamp, stackFrames})
+    {
+        this._totalNumberOfSamples++;
+        let node = this._root;
+        node.addTimestamp(timestamp);
+        for (let i = stackFrames.length; i--; ) {
+            let stackFrame = stackFrames[i];
+            node = node.findOrMakeChild(stackFrame);
+            node.addTimestamp(timestamp);
+        }
+    }
+
+    toCPUProfilePayload(startTime, endTime)
+    {
+        let cpuProfile = {};
+        let roots = [];
+        let numSamplesInTimeRange = this._root.filteredTimestamps(startTime, endTime).length;
+
+        this._root.forEachChild((child) => {
+            if (child.hasStackTraceInTimeRange(startTime, endTime))
+                roots.push(child.toCPUProfileNode(numSamplesInTimeRange, startTime, endTime)); 
+        });
+
+        cpuProfile.rootNodes = roots;
+        return cpuProfile;
+    }
+
+    forEachNode(callback)
+    {
+        this._root.forEachNode(callback);
+    }
+
+    // Testing.
+
+    static __test_makeTreeFromProtocolMessageObject(messageObject)
+    {
+        let tree = new WebInspector.CallingContextTree;
+        let stackTraces = messageObject.params.samples.stackTraces;
+        for (let i = 0; i < stackTraces.length; i++)
+            tree.updateTreeWithStackTrace(stackTraces[i]);
+        return tree;
+    }
+
+    __test_matchesStackTrace(stackTrace)
+    {
+        // StackTrace should have top frame first in the array and bottom frame last.
+        // We don't look for a match that traces down the tree from the root; instead,
+        // we match by looking at all the leafs, and matching while walking up the tree
+        // towards the root. If we successfully make the walk, we've got a match that
+        // suffices for a particular test. A successful match doesn't mean we actually
+        // walk all the way up to the root; it just means we didn't fail while walking
+        // in the direction of the root.
+        let leaves = this.__test_buildLeafLinkedLists();
+
+        outer:
+        for (let node of leaves) {
+            for (let stackNode of stackTrace) {
+                for (let propertyName of Object.getOwnPropertyNames(stackNode)) {
+                    if (stackNode[propertyName] !== node[propertyName])
+                        continue outer;
+                }
+                node = node.parent;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    __test_buildLeafLinkedLists()
+    {
+        let result = [];
+        let parent = null;
+        this._root.__test_buildLeafLinkedLists(parent, result);
+        return result;
+    }
+};
+
+WebInspector.CCTNode = class CCTNode extends WebInspector.Object
+{
+    constructor(sourceID, line, column, name, url)
+    {
+        super();
+
+        this._children = {};
+        this._sourceID = sourceID;
+        this._line = line;
+        this._column = column;
+        this._name = name;
+        this._url = url;
+        this._timestamps = [];
+        this._uid = WebInspector.CCTNode.__uid++;
+    }
+
+    // Static and Private
+
+    static _hash(stackFrame)
+    {
+        return stackFrame.name + ":" + stackFrame.sourceID + ":" + stackFrame.line + ":" + stackFrame.column;
+    }
+
+    // Public
+
+    get sourceID() { return this._sourceID; }
+    get line() { return this._line; }
+    get column() { return this._column; }
+    get name() { return this._name; }
+    get uid() { return this._uid; }
+    get url() { return this._url; }
+
+    hasStackTraceInTimeRange(startTime, endTime)
+    {
+        console.assert(startTime <= endTime);
+        if (startTime > endTime)
+            return false;
+
+        let timestamps = this._timestamps;
+        let length = timestamps.length;
+        if (!length)
+            return false;
+
+        let index = timestamps.lowerBound(startTime);
+        if (index === length)
+            return false;
+        console.assert(startTime <= timestamps[index]);
+
+        let hasTimestampInRange = timestamps[index] <= endTime;
+        return hasTimestampInRange;
+    }
+
+    filteredTimestamps(startTime, endTime)
+    {
+        let index = this._timestamps.lowerBound(startTime); // The left-most (smallest) item that is >= startTime.
+        let result = [];
+        for (; index < this._timestamps.length; index++) {
+            let timestamp = this._timestamps[index];
+            console.assert(startTime <= timestamp);
+            if (!(timestamp <= endTime))
+                break;
+            result.push(timestamp);
+        }
+        return result;
+    }
+
+    hasChildren()
+    {
+        return !!Object.getOwnPropertyNames(this._children).length;
+    }
+
+    findOrMakeChild(stackFrame)
+    {
+        let hash = WebInspector.CCTNode._hash(stackFrame);
+        let node = this._children[hash];
+        if (node)
+            return node;
+        node = new WebInspector.CCTNode(stackFrame.sourceID, stackFrame.line, stackFrame.column, stackFrame.name, stackFrame.url);
+        this._children[hash] = node;
+        return node;
+    }
+
+    addTimestamp(timestamp)
+    {
+        console.assert(!this._timestamps.length || this._timestamps.lastValue <= timestamp, "Expected timestamps to be added in sorted, increasing, order.");
+        this._timestamps.push(timestamp);
+    }
+
+    forEachChild(callback)
+    {
+        for (let propertyName of Object.getOwnPropertyNames(this._children))
+            callback(this._children[propertyName]);
+    }
+
+    forEachNode(callback)
+    {
+        callback(this);
+        this.forEachChild(function(child) {
+            child.forEachNode(callback);
+        });
+    }
+
+    toCPUProfileNode(numSamples, startTime, endTime)
+    {
+        let children = [];
+        this.forEachChild((child) => {
+            if (child.hasStackTraceInTimeRange(startTime, endTime))
+                children.push(child.toCPUProfileNode(numSamples, startTime, endTime));
+        });
+        let cpuProfileNode = {
+            id: this._uid,
+            functionName: this._name,
+            url: this._url,
+            lineNumber: this._line,
+            columnNumber: this._column,
+            children: children
+        };
+
+        let timestamps = [];
+        let frameStartTime = Number.MAX_VALUE;
+        let frameEndTime = Number.MIN_VALUE;
+        for (let i = 0; i < this._timestamps.length; i++) {
+            let timestamp = this._timestamps[i];
+            if (startTime <= timestamp && timestamp <= endTime) {
+                timestamps.push(timestamp);
+                frameStartTime = Math.min(frameStartTime, timestamp);
+                frameEndTime = Math.max(frameEndTime, timestamp);
+            }
+        }
+
+        cpuProfileNode.callInfo = {
+            callCount: timestamps.length, // Totally not callCount, but oh well, this makes life easier because of field names.
+            startTime: frameStartTime,
+            endTime: frameEndTime,
+            totalTime: (timestamps.length / numSamples) * (endTime - startTime)
+        };
+
+        return cpuProfileNode;
+    }
+
+    // Testing.
+
+    __test_buildLeafLinkedLists(parent, result)
+    {
+        let linkedListNode = {
+            name: this._name,
+            url: this._url,
+            parent: parent
+        };
+        if (this.hasChildren()) {
+            this.forEachChild((child) => {
+                child.__test_buildLeafLinkedLists(linkedListNode, result);
+            });
+        } else {
+            // We're a leaf.
+            result.push(linkedListNode);
+        }
+    }
+};
+
+WebInspector.CCTNode.__uid = 0;
+
index f0028dd..60aced5 100644 (file)
@@ -41,9 +41,9 @@ WebInspector.ScriptInstrument = class ScriptInstrument extends WebInspector.Inst
         }
 
         // FIXME: Make this some UI visible option.
-        const includeProfiles = true;
+        const includeSamples = true;
 
-        ScriptProfilerAgent.startTracking(includeProfiles);
+        ScriptProfilerAgent.startTracking(includeSamples);
     }
 
     stopInstrumentation()
index cd2a70f..e12976a 100644 (file)
@@ -37,8 +37,8 @@ WebInspector.ScriptProfilerObserver = class ScriptProfilerObserver
         WebInspector.timelineManager.scriptProfilerTrackingUpdated(event);
     }
 
-    trackingComplete(profiles)
+    trackingComplete(samples)
     {
-        WebInspector.timelineManager.scriptProfilerTrackingCompleted(profiles);
+        WebInspector.timelineManager.scriptProfilerTrackingCompleted(samples);
     }
 };
index c341e3c..a73881f 100644 (file)
@@ -33,6 +33,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     <script src="Base/LinkedList.js"></script>
     <script src="Base/ListMultimap.js"></script>
     <script src="Base/Object.js"></script>
+    <script src="Base/Utilities.js"></script>
+
+    <script src="Models/CallingContextTree.js"></script>
 
     <script src="Test/TestSuite.js"></script>
     <script src="Test/TestHarness.js"></script>
index 61a1271..c8e7e5e 100644 (file)
@@ -38,7 +38,13 @@ WebInspector.ScriptTimelineView = class ScriptTimelineView extends WebInspector.
         columns.location.title = WebInspector.UIString("Location");
         columns.location.width = "15%";
 
-        columns.callCount.title = WebInspector.UIString("Calls");
+        let isSamplingProfiler = !!window.ScriptProfilerAgent;
+        if (isSamplingProfiler)
+            columns.callCount.title = WebInspector.UIString("Samples");
+        else {
+            // COMPATIBILITY(iOS 9): ScriptProfilerAgent did not exist yet, we had call counts, not samples.
+            columns.callCount.title = WebInspector.UIString("Calls");
+        }
         columns.callCount.width = "5%";
         columns.callCount.aligned = "right";