UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

349 lines (313 loc) 11.9 kB
// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @implements {SDK.SDKModelObserver<!SDK.CPUProfilerModel>} * @implements {SDK.TracingManagerClient} * @unrestricted */ Timeline.TimelineController = class { /** * @param {!SDK.Target} target * @param {!Timeline.TimelineController.Client} client */ constructor(target, client) { this._tracingManager = target.model(SDK.TracingManager); this._performanceModel = new Timeline.PerformanceModel(); this._performanceModel.setMainTarget(target); this._client = client; const backingStorage = new Bindings.TempFileBackingStorage(); this._tracingModel = new SDK.TracingModel(backingStorage); /** @type {!Array<!Timeline.ExtensionTracingSession>} */ this._extensionSessions = []; SDK.targetManager.observeModels(SDK.CPUProfilerModel, this); } dispose() { SDK.targetManager.unobserveModels(SDK.CPUProfilerModel, this); } /** * @return {!SDK.Target} */ mainTarget() { return this._tracingManager.target(); } /** * @param {!Timeline.TimelineController.RecordingOptions} options * @param {!Array<!Extensions.ExtensionTraceProvider>} providers * @return {!Promise} */ startRecording(options, providers) { this._extensionTraceProviders = Extensions.extensionServer.traceProviders().slice(); /** * @param {string} category * @return {string} */ function disabledByDefault(category) { return 'disabled-by-default-' + category; } const categoriesArray = [ '-*', 'devtools.timeline', 'v8.execute', disabledByDefault('devtools.timeline'), disabledByDefault('devtools.timeline.frame'), SDK.TracingModel.TopLevelEventCategory, TimelineModel.TimelineModel.Category.Console, TimelineModel.TimelineModel.Category.UserTiming ]; categoriesArray.push(TimelineModel.TimelineModel.Category.LatencyInfo); if (Runtime.experiments.isEnabled('timelineFlowEvents')) categoriesArray.push('devtools.timeline.async'); if (Runtime.experiments.isEnabled('timelineV8RuntimeCallStats') && options.enableJSSampling) categoriesArray.push(disabledByDefault('v8.runtime_stats_sampling')); if (Runtime.experiments.isEnabled('timelineTracingJSProfile') && options.enableJSSampling) { categoriesArray.push(disabledByDefault('v8.cpu_profiler')); if (Common.moduleSetting('highResolutionCpuProfiling').get()) categoriesArray.push(disabledByDefault('v8.cpu_profiler.hires')); } categoriesArray.push(disabledByDefault('devtools.timeline.stack')); if (Runtime.experiments.isEnabled('timelineInvalidationTracking')) categoriesArray.push(disabledByDefault('devtools.timeline.invalidationTracking')); if (options.capturePictures) { categoriesArray.push( disabledByDefault('devtools.timeline.layers'), disabledByDefault('devtools.timeline.picture'), disabledByDefault('blink.graphics_context_annotations')); } if (options.captureFilmStrip) categoriesArray.push(disabledByDefault('devtools.screenshot')); this._extensionSessions = providers.map(provider => new Timeline.ExtensionTracingSession(provider, this._performanceModel)); this._extensionSessions.forEach(session => session.start()); const startPromise = this._startRecordingWithCategories(categoriesArray.join(','), options.enableJSSampling); this._performanceModel.setRecordStartTime(Date.now()); return startPromise; } /** * @return {!Promise<!Timeline.PerformanceModel>} */ async stopRecording() { const tracingStoppedPromises = []; tracingStoppedPromises.push(new Promise(resolve => this._tracingCompleteCallback = resolve)); tracingStoppedPromises.push(this._stopProfilingOnAllModels()); this._tracingManager.stop(); tracingStoppedPromises.push(SDK.targetManager.resumeAllTargets()); this._client.loadingStarted(); const extensionCompletionPromises = this._extensionSessions.map(session => session.stop()); if (extensionCompletionPromises.length) { tracingStoppedPromises.push( Promise.race([Promise.all(extensionCompletionPromises), new Promise(r => setTimeout(r, 5000))])); } await Promise.all(tracingStoppedPromises); this._allSourcesFinished(); return this._performanceModel; } /** * @override * @param {!SDK.CPUProfilerModel} cpuProfilerModel */ modelAdded(cpuProfilerModel) { if (this._profiling) cpuProfilerModel.startRecording(); } /** * @override * @param {!SDK.CPUProfilerModel} cpuProfilerModel */ modelRemoved(cpuProfilerModel) { // FIXME: We'd like to stop profiling on the target and retrieve a profile // but it's too late. Backend connection is closed. } /** * @return {!Promise} */ _startProfilingOnAllModels() { this._profiling = true; const models = SDK.targetManager.models(SDK.CPUProfilerModel); return Promise.all(models.map(model => model.startRecording())); } /** * @param {string} targetId * @param {?Protocol.Profiler.Profile} cpuProfile */ _addCpuProfile(targetId, cpuProfile) { if (!cpuProfile) { Common.console.warn(Common.UIString('CPU profile for a target is not available.')); return; } if (!this._cpuProfiles) this._cpuProfiles = new Map(); this._cpuProfiles.set(targetId, cpuProfile); } /** * @return {!Promise} */ _stopProfilingOnAllModels() { const models = this._profiling ? SDK.targetManager.models(SDK.CPUProfilerModel) : []; this._profiling = false; const promises = []; for (const model of models) { const targetId = model.target().id(); const modelPromise = model.stopRecording().then(this._addCpuProfile.bind(this, targetId)); promises.push(modelPromise); } return Promise.all(promises); } /** * @param {string} categories * @param {boolean=} enableJSSampling * @return {!Promise} */ _startRecordingWithCategories(categories, enableJSSampling) { SDK.targetManager.suspendAllTargets(); const profilingStartedPromise = enableJSSampling && !Runtime.experiments.isEnabled('timelineTracingJSProfile') ? this._startProfilingOnAllModels() : Promise.resolve(); const samplingFrequencyHz = Common.moduleSetting('highResolutionCpuProfiling').get() ? 10000 : 1000; const options = 'sampling-frequency=' + samplingFrequencyHz; return profilingStartedPromise.then(() => this._tracingManager.start(this, categories, options)); } /** * @param {!Array.<!SDK.TracingManager.EventPayload>} events * @override */ traceEventsCollected(events) { this._tracingModel.addEvents(events); } /** * @override */ tracingComplete() { this._tracingCompleteCallback(); this._tracingCompleteCallback = null; } _allSourcesFinished() { this._client.processingStarted(); setTimeout(() => this._finalizeTrace(), 0); } _finalizeTrace() { this._injectCpuProfileEvents(); this._tracingModel.tracingComplete(); this._client.loadingComplete(this._tracingModel); } /** * @param {number} pid * @param {number} tid * @param {?Protocol.Profiler.Profile} cpuProfile */ _injectCpuProfileEvent(pid, tid, cpuProfile) { if (!cpuProfile) return; const cpuProfileEvent = /** @type {!SDK.TracingManager.EventPayload} */ ({ cat: SDK.TracingModel.DevToolsMetadataEventCategory, ph: SDK.TracingModel.Phase.Instant, ts: this._tracingModel.maximumRecordTime() * 1000, pid: pid, tid: tid, name: TimelineModel.TimelineModel.RecordType.CpuProfile, args: {data: {cpuProfile: cpuProfile}} }); this._tracingModel.addEvents([cpuProfileEvent]); } /** * @return {?Map<string, number>} */ _buildTargetToProcessIdMap() { const metadataEventTypes = TimelineModel.TimelineModel.DevToolsMetadataEvent; const metadataEvents = this._tracingModel.devToolsMetadataEvents(); const browserMetaEvent = metadataEvents.find(e => e.name === metadataEventTypes.TracingStartedInBrowser); if (!browserMetaEvent) return null; /** @type {!Multimap<string, string>} */ const pseudoPidToFrames = new Multimap(); /** @type {!Map<string, number>} */ const targetIdToPid = new Map(); const frames = browserMetaEvent.args.data['frames']; for (const frameInfo of frames) targetIdToPid.set(frameInfo.frame, frameInfo.processId); for (const event of metadataEvents) { const data = event.args.data; switch (event.name) { case metadataEventTypes.FrameCommittedInBrowser: if (data.processId) targetIdToPid.set(data.frame, data.processId); else pseudoPidToFrames.set(data.processPseudoId, data.frame); break; case metadataEventTypes.ProcessReadyInBrowser: for (const frame of pseudoPidToFrames.get(data.processPseudoId) || []) targetIdToPid.set(frame, data.processId); break; } } const mainFrame = frames.find(frame => !frame.parent); const mainRendererProcessId = mainFrame.processId; const mainProcess = this._tracingModel.processById(mainRendererProcessId); if (mainProcess) targetIdToPid.set(SDK.targetManager.mainTarget().id(), mainProcess.id()); return targetIdToPid; } _injectCpuProfileEvents() { if (!this._cpuProfiles) return; const metadataEventTypes = TimelineModel.TimelineModel.DevToolsMetadataEvent; const metadataEvents = this._tracingModel.devToolsMetadataEvents(); const targetIdToPid = this._buildTargetToProcessIdMap(); if (targetIdToPid) { for (const [id, profile] of this._cpuProfiles) { const pid = targetIdToPid.get(id); if (!pid) continue; const process = this._tracingModel.processById(pid); const thread = process && process.threadByName(TimelineModel.TimelineModel.RendererMainThreadName); if (thread) this._injectCpuProfileEvent(pid, thread.id(), profile); } } else { // Legacy backends support. const mainMetaEvent = metadataEvents.filter(event => event.name === metadataEventTypes.TracingStartedInPage).peekLast(); if (mainMetaEvent) { const pid = mainMetaEvent.thread.process().id(); const mainCpuProfile = this._cpuProfiles.get(this._tracingManager.target().id()); this._injectCpuProfileEvent(pid, mainMetaEvent.thread.id(), mainCpuProfile); } } const workerMetaEvents = metadataEvents.filter(event => event.name === metadataEventTypes.TracingSessionIdForWorker); for (const metaEvent of workerMetaEvents) { const workerId = metaEvent.args['data']['workerId']; const cpuProfile = this._cpuProfiles.get(workerId); this._injectCpuProfileEvent( metaEvent.thread.process().id(), metaEvent.args['data']['workerThreadId'], cpuProfile); } this._cpuProfiles = null; } /** * @param {number} usage * @override */ tracingBufferUsage(usage) { this._client.recordingProgress(usage); } /** * @param {number} progress * @override */ eventsRetrievalProgress(progress) { this._client.loadingProgress(progress); } }; /** * @interface * @extends {Timeline.TimelineLoader.Client} */ Timeline.TimelineController.Client = function() {}; Timeline.TimelineController.Client.prototype = { /** * @param {number} usage */ recordingProgress(usage) {}, }; /** * @typedef {!{ * enableJSSampling: (boolean|undefined), * capturePictures: (boolean|undefined), * captureFilmStrip: (boolean|undefined) * }} */ Timeline.TimelineController.RecordingOptions;