[go: nahoru, domu]

blob: e352c4e7fcae2f90c324830ce8770e977163d1fd [file] [log] [blame]
// Copyright 2020 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.
/*
* Copyright (C) 2008 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. ``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
* 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.
*/
/* eslint-disable rulesdir/no_underscored_properties */
import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
import * as Components from '../../ui/legacy/components/utils/utils.js';
import * as UI from '../../ui/legacy/legacy.js';
import type * as Protocol from '../../generated/protocol.js';
import {ProfileFlameChartDataProvider} from './CPUProfileFlameChart.js';
import type {Formatter, ProfileDataGridNode} from './ProfileDataGrid.js';
import type {ProfileHeader} from './ProfileHeader.js';
import {ProfileEvents, ProfileType} from './ProfileHeader.js';
import {ProfileView, WritableProfileHeader} from './ProfileView.js';
const UIStrings = {
/**
*@description Time of a single activity, as opposed to the total time
*/
selfTime: 'Self Time',
/**
*@description Text for the total time of something
*/
totalTime: 'Total Time',
/**
*@description Text in CPUProfile View of a profiler tool
*/
recordJavascriptCpuProfile: 'Record JavaScript CPU Profile',
/**
*@description Text in CPUProfile View of a profiler tool
*/
stopCpuProfiling: 'Stop CPU profiling',
/**
*@description Text in CPUProfile View of a profiler tool
*/
startCpuProfiling: 'Start CPU profiling',
/**
*@description Text in CPUProfile View of a profiler tool
*/
cpuProfiles: 'CPU PROFILES',
/**
*@description Text in CPUProfile View of a profiler tool, that show how much time a script spend executing a function.
*/
cpuProfilesShow: 'CPU profiles show where the execution time is spent in your page\'s JavaScript functions.',
/**
*@description Text in CPUProfile View of a profiler tool
*/
recording: 'Recording…',
/**
*@description Time in miliseconds
*@example {30.1} PH1
*/
fms: '{PH1} ms',
/**
*@description Text in CPUProfile View of a profiler tool
*@example {21.33} PH1
*/
formatPercent: '{PH1} %',
/**
*@description Text for the name of something
*/
name: 'Name',
/**
*@description Text for web URLs
*/
url: 'URL',
/**
*@description Text in CPUProfile View of a profiler tool
*/
aggregatedSelfTime: 'Aggregated self time',
/**
*@description Text in CPUProfile View of a profiler tool
*/
aggregatedTotalTime: 'Aggregated total time',
/**
*@description Text that indicates a JavaScript function in a CPU profile is not optimized.
*/
notOptimized: 'Not optimized',
};
const str_ = i18n.i18n.registerUIStrings('panels/profiler/CPUProfileView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class CPUProfileView extends ProfileView implements UI.SearchableView.Searchable {
profileHeader: CPUProfileHeader;
adjustedTotal: number;
constructor(profileHeader: CPUProfileHeader) {
super();
this.profileHeader = profileHeader;
this.initialize(new NodeFormatter(this));
const profile = profileHeader.profileModel();
this.adjustedTotal = profile.profileHead.total;
this.adjustedTotal -= profile.idleNode ? profile.idleNode.total : 0;
this.setProfile(profile);
}
wasShown(): void {
super.wasShown();
PerfUI.LineLevelProfile.Performance.instance().reset();
PerfUI.LineLevelProfile.Performance.instance().appendCPUProfile(this.profileHeader.profileModel());
}
columnHeader(columnId: string): Common.UIString.LocalizedString {
switch (columnId) {
case 'self':
return i18nString(UIStrings.selfTime);
case 'total':
return i18nString(UIStrings.totalTime);
}
return Common.UIString.LocalizedEmptyString;
}
createFlameChartDataProvider(): ProfileFlameChartDataProvider {
return new CPUFlameChartDataProvider(this.profileHeader.profileModel(), this.profileHeader._cpuProfilerModel);
}
}
export class CPUProfileType extends ProfileType {
_recording: boolean;
constructor() {
super(CPUProfileType.TypeId, i18nString(UIStrings.recordJavascriptCpuProfile));
this._recording = false;
SDK.TargetManager.TargetManager.instance().addModelListener(
SDK.CPUProfilerModel.CPUProfilerModel, SDK.CPUProfilerModel.Events.ConsoleProfileFinished,
this._consoleProfileFinished, this);
}
profileBeingRecorded(): ProfileHeader|null {
return super.profileBeingRecorded() as ProfileHeader | null;
}
typeName(): string {
return 'CPU';
}
fileExtension(): string {
return '.cpuprofile';
}
get buttonTooltip(): Common.UIString.LocalizedString {
return this._recording ? i18nString(UIStrings.stopCpuProfiling) : i18nString(UIStrings.startCpuProfiling);
}
buttonClicked(): boolean {
if (this._recording) {
this._stopRecordingProfile();
return false;
}
this._startRecordingProfile();
return true;
}
get treeItemTitle(): Common.UIString.LocalizedString {
return i18nString(UIStrings.cpuProfiles);
}
get description(): Common.UIString.LocalizedString {
return i18nString(UIStrings.cpuProfilesShow);
}
_consoleProfileFinished(event: Common.EventTarget.EventTargetEvent): void {
const data = (event.data as SDK.CPUProfilerModel.EventData);
const cpuProfile = (data.cpuProfile as Protocol.Profiler.Profile);
const profile = new CPUProfileHeader(data.cpuProfilerModel, this, data.title);
profile.setProtocolProfile(cpuProfile);
this.addProfile(profile);
}
_startRecordingProfile(): void {
const cpuProfilerModel = UI.Context.Context.instance().flavor(SDK.CPUProfilerModel.CPUProfilerModel);
if (this.profileBeingRecorded() || !cpuProfilerModel) {
return;
}
const profile = new CPUProfileHeader(cpuProfilerModel, this);
this.setProfileBeingRecorded(profile as ProfileHeader);
SDK.TargetManager.TargetManager.instance().suspendAllTargets();
this.addProfile(profile as ProfileHeader);
profile.updateStatus(i18nString(UIStrings.recording));
this._recording = true;
cpuProfilerModel.startRecording();
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ProfilesCPUProfileTaken);
}
async _stopRecordingProfile(): Promise<void> {
this._recording = false;
const profileBeingRecorded = this.profileBeingRecorded() as CPUProfileHeader;
if (!profileBeingRecorded || !profileBeingRecorded._cpuProfilerModel) {
return;
}
const profile = await profileBeingRecorded._cpuProfilerModel.stopRecording();
const recordedProfile = this.profileBeingRecorded() as CPUProfileHeader;
if (recordedProfile) {
if (!profile) {
throw new Error('Expected profile to be non-null');
}
recordedProfile.setProtocolProfile(profile);
recordedProfile.updateStatus('');
this.setProfileBeingRecorded(null);
}
await SDK.TargetManager.TargetManager.instance().resumeAllTargets();
this.dispatchEventToListeners(ProfileEvents.ProfileComplete, recordedProfile);
}
createProfileLoadedFromFile(title: string): ProfileHeader {
return new CPUProfileHeader(null, this, title);
}
profileBeingRecordedRemoved(): void {
this._stopRecordingProfile();
}
// eslint-disable-next-line @typescript-eslint/naming-convention
static readonly TypeId = 'CPU';
}
export class CPUProfileHeader extends WritableProfileHeader {
_cpuProfilerModel: SDK.CPUProfilerModel.CPUProfilerModel|null;
_profileModel?: SDK.CPUProfileDataModel.CPUProfileDataModel;
constructor(cpuProfilerModel: SDK.CPUProfilerModel.CPUProfilerModel|null, type: CPUProfileType, title?: string) {
super(cpuProfilerModel && cpuProfilerModel.debuggerModel(), type, title);
this._cpuProfilerModel = cpuProfilerModel;
}
createView(): ProfileView {
return new CPUProfileView(this);
}
protocolProfile(): Protocol.Profiler.Profile {
if (!this._protocolProfile) {
throw new Error('Expected _protocolProfile to be available');
}
return this._protocolProfile;
}
profileModel(): SDK.CPUProfileDataModel.CPUProfileDataModel {
if (!this._profileModel) {
throw new Error('Expected _profileModel to be available');
}
return this._profileModel;
}
setProfile(profile: Protocol.Profiler.Profile): void {
const target = this._cpuProfilerModel && this._cpuProfilerModel.target() || null;
this._profileModel = new SDK.CPUProfileDataModel.CPUProfileDataModel(profile, target);
}
}
export class NodeFormatter implements Formatter {
_profileView: CPUProfileView;
constructor(profileView: CPUProfileView) {
this._profileView = profileView;
}
formatValue(value: number): string {
return i18nString(UIStrings.fms, {PH1: value.toFixed(1)});
}
formatValueAccessibleText(value: number): string {
return this.formatValue(value);
}
formatPercent(value: number, node: ProfileDataGridNode): string {
if (this._profileView) {
const profile = this._profileView.profile();
if (profile && node.profileNode !== (profile as SDK.CPUProfileDataModel.CPUProfileDataModel).idleNode) {
return i18nString(UIStrings.formatPercent, {PH1: value.toFixed(2)});
}
}
return '';
}
linkifyNode(node: ProfileDataGridNode): Element|null {
const cpuProfilerModel = this._profileView.profileHeader._cpuProfilerModel;
const target = cpuProfilerModel ? cpuProfilerModel.target() : null;
const options = {className: 'profile-node-file', columnNumber: undefined, inlineFrameIndex: 0, tabStop: undefined};
return this._profileView.linkifier().maybeLinkifyConsoleCallFrame(target, node.profileNode.callFrame, options);
}
}
export class CPUFlameChartDataProvider extends ProfileFlameChartDataProvider {
_cpuProfile: SDK.CPUProfileDataModel.CPUProfileDataModel;
_cpuProfilerModel: SDK.CPUProfilerModel.CPUProfilerModel|null;
_entrySelfTimes?: Float32Array;
constructor(
cpuProfile: SDK.CPUProfileDataModel.CPUProfileDataModel,
cpuProfilerModel: SDK.CPUProfilerModel.CPUProfilerModel|null) {
super();
this._cpuProfile = cpuProfile;
this._cpuProfilerModel = cpuProfilerModel;
}
minimumBoundary(): number {
return this._cpuProfile.profileStartTime;
}
totalTime(): number {
return this._cpuProfile.profileHead.total;
}
entryHasDeoptReason(entryIndex: number): boolean {
const node = (this.entryNodes[entryIndex] as SDK.CPUProfileDataModel.CPUProfileNode);
return Boolean(node.deoptReason);
}
_calculateTimelineData(): PerfUI.FlameChart.TimelineData {
const entries: (CPUFlameChartDataProvider.ChartEntry|null)[] = [];
const stack: number[] = [];
let maxDepth = 5;
function onOpenFrame(): void {
stack.push(entries.length);
// Reserve space for the entry, as they have to be ordered by startTime.
// The entry itself will be put there in onCloseFrame.
entries.push(null);
}
function onCloseFrame(
depth: number, node: SDK.CPUProfileDataModel.CPUProfileNode, startTime: number, totalTime: number,
selfTime: number): void {
const index = (stack.pop() as number);
entries[index] = new CPUFlameChartDataProvider.ChartEntry(depth, totalTime, startTime, selfTime, node);
maxDepth = Math.max(maxDepth, depth);
}
this._cpuProfile.forEachFrame(onOpenFrame, onCloseFrame);
const entryNodes: SDK.CPUProfileDataModel.CPUProfileNode[] = new Array(entries.length);
const entryLevels = new Uint16Array(entries.length);
const entryTotalTimes = new Float32Array(entries.length);
const entrySelfTimes = new Float32Array(entries.length);
const entryStartTimes = new Float64Array(entries.length);
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (!entry) {
continue;
}
entryNodes[i] = entry.node;
entryLevels[i] = entry.depth;
entryTotalTimes[i] = entry.duration;
entryStartTimes[i] = entry.startTime;
entrySelfTimes[i] = entry.selfTime;
}
this._maxStackDepth = maxDepth + 1;
this.entryNodes = entryNodes;
this.timelineData_ = new PerfUI.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes, null);
this._entrySelfTimes = entrySelfTimes;
return this.timelineData_;
}
prepareHighlightedEntryInfo(entryIndex: number): Element|null {
const timelineData = this.timelineData_;
const node = this.entryNodes[entryIndex];
if (!node) {
return null;
}
const entryInfo: {
title: string,
value: string,
}[] = [];
function pushEntryInfoRow(title: string, value: string): void {
entryInfo.push({title: title, value: value});
}
function millisecondsToString(ms: number): string {
if (ms === 0) {
return '0';
}
if (ms < 1000) {
return i18nString(UIStrings.fms, {PH1: ms.toFixed(1)});
}
return i18n.i18n.secondsToString(ms / 1000, true);
}
const name = UI.UIUtils.beautifyFunctionName(node.functionName);
pushEntryInfoRow(i18nString(UIStrings.name), name);
const selfTime = millisecondsToString((this._entrySelfTimes as Float32Array)[entryIndex]);
const totalTime =
millisecondsToString((timelineData as PerfUI.FlameChart.TimelineData).entryTotalTimes[entryIndex]);
pushEntryInfoRow(i18nString(UIStrings.selfTime), selfTime);
pushEntryInfoRow(i18nString(UIStrings.totalTime), totalTime);
const linkifier = new Components.Linkifier.Linkifier();
const link = linkifier.maybeLinkifyConsoleCallFrame(
this._cpuProfilerModel && this._cpuProfilerModel.target(), node.callFrame);
if (link) {
pushEntryInfoRow(i18nString(UIStrings.url), link.textContent || '');
}
linkifier.dispose();
pushEntryInfoRow(i18nString(UIStrings.aggregatedSelfTime), i18n.i18n.secondsToString(node.self / 1000, true));
pushEntryInfoRow(i18nString(UIStrings.aggregatedTotalTime), i18n.i18n.secondsToString(node.total / 1000, true));
const deoptReason = (node as SDK.CPUProfileDataModel.CPUProfileNode).deoptReason;
if (deoptReason) {
pushEntryInfoRow(i18nString(UIStrings.notOptimized), deoptReason);
}
return ProfileView.buildPopoverTable(entryInfo);
}
}
export namespace CPUFlameChartDataProvider {
export class ChartEntry {
depth: number;
duration: number;
startTime: number;
selfTime: number;
node: SDK.CPUProfileDataModel.CPUProfileNode;
constructor(
depth: number, duration: number, startTime: number, selfTime: number,
node: SDK.CPUProfileDataModel.CPUProfileNode) {
this.depth = depth;
this.duration = duration;
this.startTime = startTime;
this.selfTime = selfTime;
this.node = node;
}
}
}