Added MacOS SDK
This commit is contained in:
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.
|
||||
*/
|
||||
|
||||
WI.TimelineRecording = class TimelineRecording extends WI.Object
|
||||
{
|
||||
constructor(identifier, displayName, instruments)
|
||||
{
|
||||
super();
|
||||
|
||||
this._identifier = identifier;
|
||||
this._timelines = new Map;
|
||||
this._displayName = displayName;
|
||||
this._capturing = false;
|
||||
this._readonly = false;
|
||||
this._imported = false;
|
||||
this._instruments = instruments || [];
|
||||
|
||||
this._startTime = NaN;
|
||||
this._endTime = NaN;
|
||||
|
||||
this._discontinuityStartTime = NaN;
|
||||
this._discontinuities = null;
|
||||
this._firstRecordOfTypeAfterDiscontinuity = new Set;
|
||||
|
||||
this._exportDataRecords = null;
|
||||
this._exportDataMarkers = null;
|
||||
this._exportDataMemoryPressureEvents = null;
|
||||
this._exportDataSampleStackTraces = null;
|
||||
this._exportDataSampleDurations = null;
|
||||
|
||||
this._topDownCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopDown);
|
||||
this._bottomUpCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.BottomUp);
|
||||
this._topFunctionsTopDownCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopFunctionsTopDown);
|
||||
this._topFunctionsBottomUpCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopFunctionsBottomUp);
|
||||
|
||||
for (let type of WI.TimelineManager.availableTimelineTypes()) {
|
||||
let timeline = WI.Timeline.create(type);
|
||||
this._timelines.set(type, timeline);
|
||||
timeline.addEventListener(WI.Timeline.Event.TimesUpdated, this._timelineTimesUpdated, this);
|
||||
}
|
||||
|
||||
this.reset(true);
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
static sourceCodeTimelinesSupported()
|
||||
{
|
||||
// FIXME: Support Network Timeline in ServiceWorker.
|
||||
return WI.sharedApp.isWebDebuggable();
|
||||
}
|
||||
|
||||
// Import / Export
|
||||
|
||||
static async import(identifier, json, displayName)
|
||||
{
|
||||
let {startTime, endTime, discontinuities, instrumentTypes, records, markers, memoryPressureEvents, sampleStackTraces, sampleDurations} = json;
|
||||
let importedDisplayName = WI.UIString("Imported - %s").format(displayName);
|
||||
let instruments = instrumentTypes.map((type) => WI.Instrument.createForTimelineType(type));
|
||||
let recording = new WI.TimelineRecording(identifier, importedDisplayName, instruments);
|
||||
|
||||
recording._readonly = true;
|
||||
recording._imported = true;
|
||||
recording._startTime = startTime;
|
||||
recording._endTime = endTime;
|
||||
recording._discontinuities = discontinuities;
|
||||
|
||||
recording.initializeCallingContextTrees(sampleStackTraces, sampleDurations);
|
||||
|
||||
for (let recordJSON of records) {
|
||||
let record = await WI.TimelineRecord.fromJSON(recordJSON);
|
||||
if (record) {
|
||||
recording.addRecord(record);
|
||||
|
||||
if (record instanceof WI.ScriptTimelineRecord)
|
||||
record.profilePayload = recording._topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
|
||||
}
|
||||
}
|
||||
|
||||
for (let memoryPressureJSON of memoryPressureEvents) {
|
||||
let memoryPressureEvent = WI.MemoryPressureEvent.fromJSON(memoryPressureJSON);
|
||||
if (memoryPressureEvent)
|
||||
recording.addMemoryPressureEvent(memoryPressureEvent);
|
||||
}
|
||||
|
||||
// Add markers once we've transitioned the active recording.
|
||||
setTimeout(() => {
|
||||
recording.__importing = true;
|
||||
|
||||
for (let markerJSON of markers) {
|
||||
let marker = WI.TimelineMarker.fromJSON(markerJSON);
|
||||
if (marker)
|
||||
recording.addEventMarker(marker);
|
||||
}
|
||||
|
||||
recording.__importing = false;
|
||||
});
|
||||
|
||||
return recording;
|
||||
}
|
||||
|
||||
exportData()
|
||||
{
|
||||
console.assert(this.canExport(), "Attempted to export a recording which should not be exportable.");
|
||||
|
||||
// FIXME: Overview data (sourceCodeTimelinesMap).
|
||||
// FIXME: Record hierarchy (parent / child relationship) is lost.
|
||||
|
||||
return {
|
||||
displayName: this._displayName,
|
||||
startTime: this._startTime,
|
||||
endTime: this._endTime,
|
||||
discontinuities: this._discontinuities,
|
||||
instrumentTypes: this._instruments.map((instrument) => instrument.timelineRecordType),
|
||||
records: this._exportDataRecords,
|
||||
markers: this._exportDataMarkers,
|
||||
memoryPressureEvents: this._exportDataMemoryPressureEvents,
|
||||
sampleStackTraces: this._exportDataSampleStackTraces,
|
||||
sampleDurations: this._exportDataSampleDurations,
|
||||
};
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
get displayName() { return this._displayName; }
|
||||
get identifier() { return this._identifier; }
|
||||
get timelines() { return this._timelines; }
|
||||
get instruments() { return this._instruments; }
|
||||
get capturing() { return this._capturing; }
|
||||
get readonly() { return this._readonly; }
|
||||
get imported() { return this._imported; }
|
||||
get startTime() { return this._startTime; }
|
||||
get endTime() { return this._endTime; }
|
||||
|
||||
get topDownCallingContextTree() { return this._topDownCallingContextTree; }
|
||||
get bottomUpCallingContextTree() { return this._bottomUpCallingContextTree; }
|
||||
get topFunctionsTopDownCallingContextTree() { return this._topFunctionsTopDownCallingContextTree; }
|
||||
get topFunctionsBottomUpCallingContextTree() { return this._topFunctionsBottomUpCallingContextTree; }
|
||||
|
||||
start(initiatedByBackend)
|
||||
{
|
||||
console.assert(!this._capturing, "Attempted to start an already started session.");
|
||||
console.assert(!this._readonly, "Attempted to start a readonly session.");
|
||||
|
||||
this._capturing = true;
|
||||
|
||||
for (let instrument of this._instruments)
|
||||
instrument.startInstrumentation(initiatedByBackend);
|
||||
|
||||
if (!isNaN(this._discontinuityStartTime)) {
|
||||
for (let instrument of this._instruments)
|
||||
this._firstRecordOfTypeAfterDiscontinuity.add(instrument.timelineRecordType);
|
||||
}
|
||||
}
|
||||
|
||||
stop(initiatedByBackend)
|
||||
{
|
||||
console.assert(this._capturing, "Attempted to stop an already stopped session.");
|
||||
console.assert(!this._readonly, "Attempted to stop a readonly session.");
|
||||
|
||||
this._capturing = false;
|
||||
|
||||
for (let instrument of this._instruments)
|
||||
instrument.stopInstrumentation(initiatedByBackend);
|
||||
}
|
||||
|
||||
capturingStarted(startTime)
|
||||
{
|
||||
// A discontinuity occurs when the recording is stopped and resumed at
|
||||
// a future time. Capturing started signals the end of the current
|
||||
// discontinuity, if one exists.
|
||||
if (!isNaN(this._discontinuityStartTime)) {
|
||||
this._discontinuities.push({
|
||||
startTime: this._discontinuityStartTime,
|
||||
endTime: startTime,
|
||||
});
|
||||
this._discontinuityStartTime = NaN;
|
||||
}
|
||||
}
|
||||
|
||||
capturingStopped(endTime)
|
||||
{
|
||||
this._discontinuityStartTime = endTime;
|
||||
}
|
||||
|
||||
saveIdentityToCookie()
|
||||
{
|
||||
// Do nothing. Timeline recordings are not persisted when the inspector is
|
||||
// re-opened, so do not attempt to restore by identifier or display name.
|
||||
}
|
||||
|
||||
isEmpty()
|
||||
{
|
||||
for (var timeline of this._timelines.values()) {
|
||||
if (timeline.records.length)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unloaded(importing)
|
||||
{
|
||||
console.assert(importing || !this.isEmpty(), "Shouldn't unload an empty recording; it should be reused instead.");
|
||||
|
||||
this._readonly = true;
|
||||
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.Unloaded);
|
||||
}
|
||||
|
||||
reset(suppressEvents)
|
||||
{
|
||||
console.assert(!this._readonly, "Can't reset a read-only recording.");
|
||||
|
||||
this._sourceCodeTimelinesMap = new Map;
|
||||
|
||||
this._startTime = NaN;
|
||||
this._endTime = NaN;
|
||||
|
||||
this._discontinuityStartTime = NaN;
|
||||
this._discontinuities = [];
|
||||
this._firstRecordOfTypeAfterDiscontinuity.clear();
|
||||
|
||||
this._exportDataRecords = [];
|
||||
this._exportDataMarkers = [];
|
||||
this._exportDataMemoryPressureEvents = [];
|
||||
this._exportDataSampleStackTraces = [];
|
||||
this._exportDataSampleDurations = [];
|
||||
|
||||
this._topDownCallingContextTree.reset();
|
||||
this._bottomUpCallingContextTree.reset();
|
||||
this._topFunctionsTopDownCallingContextTree.reset();
|
||||
this._topFunctionsBottomUpCallingContextTree.reset();
|
||||
|
||||
for (var timeline of this._timelines.values())
|
||||
timeline.reset(suppressEvents);
|
||||
|
||||
WI.RenderingFrameTimelineRecord.resetFrameIndex();
|
||||
|
||||
if (!suppressEvents) {
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.Reset);
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.TimesUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
get sourceCodeTimelines()
|
||||
{
|
||||
let timelines = [];
|
||||
for (let timelinesForSourceCode of this._sourceCodeTimelinesMap.values())
|
||||
timelines.pushAll(timelinesForSourceCode.values());
|
||||
return timelines;
|
||||
}
|
||||
|
||||
timelineForInstrument(instrument)
|
||||
{
|
||||
return this._timelines.get(instrument.timelineRecordType);
|
||||
}
|
||||
|
||||
instrumentForTimeline(timeline)
|
||||
{
|
||||
return this._instruments.find((instrument) => instrument.timelineRecordType === timeline.type);
|
||||
}
|
||||
|
||||
timelineForRecordType(recordType)
|
||||
{
|
||||
return this._timelines.get(recordType);
|
||||
}
|
||||
|
||||
addInstrument(instrument)
|
||||
{
|
||||
console.assert(instrument instanceof WI.Instrument, instrument);
|
||||
console.assert(!this._instruments.includes(instrument), this._instruments, instrument);
|
||||
|
||||
this._instruments.push(instrument);
|
||||
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.InstrumentAdded, {instrument});
|
||||
}
|
||||
|
||||
removeInstrument(instrument)
|
||||
{
|
||||
console.assert(instrument instanceof WI.Instrument, instrument);
|
||||
console.assert(this._instruments.includes(instrument), this._instruments, instrument);
|
||||
|
||||
this._instruments.remove(instrument);
|
||||
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.InstrumentRemoved, {instrument});
|
||||
}
|
||||
|
||||
addEventMarker(marker)
|
||||
{
|
||||
this._exportDataMarkers.push(marker);
|
||||
|
||||
if (!this._capturing && !this.__importing)
|
||||
return;
|
||||
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.MarkerAdded, {marker});
|
||||
}
|
||||
|
||||
addRecord(record)
|
||||
{
|
||||
this._exportDataRecords.push(record);
|
||||
|
||||
let timeline = this._timelines.get(record.type);
|
||||
console.assert(timeline, record, this._timelines);
|
||||
if (!timeline)
|
||||
return;
|
||||
|
||||
let discontinuity = null;
|
||||
if (this._firstRecordOfTypeAfterDiscontinuity.take(record.type))
|
||||
discontinuity = this._discontinuities.lastValue;
|
||||
|
||||
// Add the record to the global timeline by type.
|
||||
timeline.addRecord(record, {discontinuity});
|
||||
|
||||
// Some records don't have source code timelines.
|
||||
if (record.type === WI.TimelineRecord.Type.Network
|
||||
|| record.type === WI.TimelineRecord.Type.RenderingFrame
|
||||
|| record.type === WI.TimelineRecord.Type.CPU
|
||||
|| record.type === WI.TimelineRecord.Type.Memory
|
||||
|| record.type === WI.TimelineRecord.Type.HeapAllocations
|
||||
|| record.type === WI.TimelineRecord.Type.Screenshots)
|
||||
return;
|
||||
|
||||
if (!WI.TimelineRecording.sourceCodeTimelinesSupported())
|
||||
return;
|
||||
|
||||
// Add the record to the source code timelines.
|
||||
let sourceCode = null;
|
||||
if (record.sourceCodeLocation)
|
||||
sourceCode = record.sourceCodeLocation.sourceCode;
|
||||
else if (record.type === WI.TimelineRecord.Type.Media) {
|
||||
if (record.domNode && record.domNode.frame)
|
||||
sourceCode = record.domNode.frame.mainResource;
|
||||
}
|
||||
if (!sourceCode)
|
||||
sourceCode = WI.networkManager.mainFrame.provisionalMainResource || WI.networkManager.mainFrame.mainResource;
|
||||
|
||||
var sourceCodeTimelines = this._sourceCodeTimelinesMap.get(sourceCode);
|
||||
if (!sourceCodeTimelines) {
|
||||
sourceCodeTimelines = new Map;
|
||||
this._sourceCodeTimelinesMap.set(sourceCode, sourceCodeTimelines);
|
||||
}
|
||||
|
||||
var newTimeline = false;
|
||||
var key = this._keyForRecord(record);
|
||||
var sourceCodeTimeline = sourceCodeTimelines.get(key);
|
||||
if (!sourceCodeTimeline) {
|
||||
sourceCodeTimeline = new WI.SourceCodeTimeline(sourceCode, record.sourceCodeLocation, record.type, record.eventType);
|
||||
sourceCodeTimelines.set(key, sourceCodeTimeline);
|
||||
newTimeline = true;
|
||||
}
|
||||
|
||||
sourceCodeTimeline.addRecord(record);
|
||||
|
||||
if (newTimeline)
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.SourceCodeTimelineAdded, {sourceCodeTimeline});
|
||||
}
|
||||
|
||||
addMemoryPressureEvent(memoryPressureEvent)
|
||||
{
|
||||
this._exportDataMemoryPressureEvents.push(memoryPressureEvent);
|
||||
|
||||
let memoryTimeline = this._timelines.get(WI.TimelineRecord.Type.Memory);
|
||||
console.assert(memoryTimeline, this._timelines);
|
||||
if (!memoryTimeline)
|
||||
return;
|
||||
|
||||
memoryTimeline.addMemoryPressureEvent(memoryPressureEvent);
|
||||
}
|
||||
|
||||
discontinuitiesInTimeRange(startTime, endTime)
|
||||
{
|
||||
return this._discontinuities.filter((item) => item.startTime <= endTime && item.endTime >= startTime);
|
||||
}
|
||||
|
||||
addScriptInstrumentForProgrammaticCapture()
|
||||
{
|
||||
for (let instrument of this._instruments) {
|
||||
if (instrument instanceof WI.ScriptInstrument)
|
||||
return;
|
||||
}
|
||||
|
||||
this.addInstrument(new WI.ScriptInstrument);
|
||||
|
||||
let instrumentTypes = this._instruments.map((instrument) => instrument.timelineRecordType);
|
||||
WI.timelineManager.enabledTimelineTypes = instrumentTypes;
|
||||
}
|
||||
|
||||
computeElapsedTime(timestamp)
|
||||
{
|
||||
if (!timestamp || isNaN(timestamp))
|
||||
return NaN;
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
initializeTimeBoundsIfNecessary(timestamp)
|
||||
{
|
||||
if (isNaN(this._startTime)) {
|
||||
console.assert(isNaN(this._endTime));
|
||||
|
||||
this._startTime = timestamp;
|
||||
this._endTime = timestamp;
|
||||
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.TimesUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
initializeCallingContextTrees(stackTraces, sampleDurations)
|
||||
{
|
||||
this._exportDataSampleStackTraces.pushAll(stackTraces);
|
||||
this._exportDataSampleDurations.pushAll(sampleDurations);
|
||||
|
||||
for (let i = 0; i < stackTraces.length; i++) {
|
||||
this._topDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
|
||||
this._bottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
|
||||
this._topFunctionsTopDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
|
||||
this._topFunctionsBottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
get exportMode()
|
||||
{
|
||||
return WI.FileUtilities.SaveMode.SingleFile;
|
||||
}
|
||||
|
||||
canExport()
|
||||
{
|
||||
if (!WI.FileUtilities.canSave(this.exportMode))
|
||||
return false;
|
||||
|
||||
if (this._capturing)
|
||||
return false;
|
||||
|
||||
if (isNaN(this._startTime))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_keyForRecord(record)
|
||||
{
|
||||
var key = record.type;
|
||||
if (record instanceof WI.ScriptTimelineRecord || record instanceof WI.LayoutTimelineRecord)
|
||||
key += ":" + record.eventType;
|
||||
if (record instanceof WI.ScriptTimelineRecord && record.eventType === WI.ScriptTimelineRecord.EventType.EventDispatched)
|
||||
key += ":" + record.details;
|
||||
if (record instanceof WI.MediaTimelineRecord) {
|
||||
key += ":" + record.eventType;
|
||||
if (record.eventType === WI.MediaTimelineRecord.EventType.DOMEvent) {
|
||||
if (record.domEvent && record.domEvent.eventName)
|
||||
key += ":" + record.domEvent.eventName;
|
||||
} else if (record.eventType === WI.MediaTimelineRecord.EventType.PowerEfficientPlaybackStateChanged)
|
||||
key += ":" + (record.isPowerEfficient ? "enabled" : "disabled");
|
||||
}
|
||||
if (record.sourceCodeLocation)
|
||||
key += ":" + record.sourceCodeLocation.lineNumber + ":" + record.sourceCodeLocation.columnNumber;
|
||||
return key;
|
||||
}
|
||||
|
||||
_timelineTimesUpdated(event)
|
||||
{
|
||||
var timeline = event.target;
|
||||
var changed = false;
|
||||
|
||||
if (isNaN(this._startTime) || timeline.startTime < this._startTime) {
|
||||
this._startTime = timeline.startTime;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (isNaN(this._endTime) || this._endTime < timeline.endTime) {
|
||||
this._endTime = timeline.endTime;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this.dispatchEventToListeners(WI.TimelineRecording.Event.TimesUpdated);
|
||||
}
|
||||
};
|
||||
|
||||
WI.TimelineRecording.Event = {
|
||||
Reset: "timeline-recording-reset",
|
||||
Unloaded: "timeline-recording-unloaded",
|
||||
SourceCodeTimelineAdded: "timeline-recording-source-code-timeline-added",
|
||||
InstrumentAdded: "timeline-recording-instrument-added",
|
||||
InstrumentRemoved: "timeline-recording-instrument-removed",
|
||||
TimesUpdated: "timeline-recording-times-updated",
|
||||
MarkerAdded: "timeline-recording-marker-added",
|
||||
};
|
||||
|
||||
WI.TimelineRecording.SerializationVersion = 1;
|
||||
Reference in New Issue
Block a user