123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578 |
- import type {ReactElement} from 'react';
- import {
- Fragment,
- useCallback,
- useEffect,
- useLayoutEffect,
- useMemo,
- useState,
- } from 'react';
- import * as Sentry from '@sentry/react';
- import {mat3, vec2} from 'gl-matrix';
- import {addErrorMessage} from 'sentry/actionCreators/indicator';
- import {FlamegraphContextMenu} from 'sentry/components/profiling/flamegraph/flamegraphContextMenu';
- import {FlamegraphOptionsMenu} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/flamegraphOptionsMenu';
- import {FlamegraphSearch} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/flamegraphSearch';
- import type {FlamegraphThreadSelectorProps} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/flamegraphThreadSelector';
- import {FlamegraphThreadSelector} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/flamegraphThreadSelector';
- import {FlamegraphToolbar} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/flamegraphToolbar';
- import type {FlamegraphViewSelectMenuProps} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/flamegraphViewSelectMenu';
- import {FlamegraphViewSelectMenu} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/flamegraphViewSelectMenu';
- import {FlamegraphZoomView} from 'sentry/components/profiling/flamegraph/flamegraphZoomView';
- import {FlamegraphZoomViewMinimap} from 'sentry/components/profiling/flamegraph/flamegraphZoomViewMinimap';
- import {t} from 'sentry/locale';
- import type {EntrySpans, EventTransaction} from 'sentry/types/event';
- import {EntryType} from 'sentry/types/event';
- import {defined} from 'sentry/utils';
- import {
- CanvasPoolManager,
- useCanvasScheduler,
- } from 'sentry/utils/profiling/canvasScheduler';
- import {CanvasView} from 'sentry/utils/profiling/canvasView';
- import {Flamegraph as FlamegraphModel} from 'sentry/utils/profiling/flamegraph';
- import type {FlamegraphSearch as FlamegraphSearchType} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphSearch';
- import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
- import {useFlamegraphProfiles} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphProfiles';
- import {useFlamegraphSearch} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphSearch';
- import {useDispatchFlamegraphState} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphState';
- import {useFlamegraphZoomPosition} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphZoomPosition';
- import {useFlamegraphTheme} from 'sentry/utils/profiling/flamegraph/useFlamegraphTheme';
- import {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
- import type {ProfileSeriesMeasurement} from 'sentry/utils/profiling/flamegraphChart';
- import {FlamegraphChart as FlamegraphChartModel} from 'sentry/utils/profiling/flamegraphChart';
- import type {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
- import {
- computeConfigViewWithStrategy,
- computeMinZoomConfigViewForFrames,
- formatColorForFrame,
- initializeFlamegraphRenderer,
- useResizeCanvasObserver,
- } from 'sentry/utils/profiling/gl/utils';
- import type {ContinuousProfile} from 'sentry/utils/profiling/profile/continuousProfile';
- import type {ContinuousProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
- import {FlamegraphRenderer2D} from 'sentry/utils/profiling/renderers/flamegraphRenderer2D';
- import {FlamegraphRendererWebGL} from 'sentry/utils/profiling/renderers/flamegraphRendererWebGL';
- import type {SpanChartNode} from 'sentry/utils/profiling/spanChart';
- import {SpanChart} from 'sentry/utils/profiling/spanChart';
- import {SpanTree} from 'sentry/utils/profiling/spanTree';
- import {Rect} from 'sentry/utils/profiling/speedscope';
- import type {UIFrameMeasurements} from 'sentry/utils/profiling/uiFrames';
- import {UIFrames} from 'sentry/utils/profiling/uiFrames';
- import {
- fromNanoJoulesToWatts,
- type ProfilingFormatterUnit,
- } from 'sentry/utils/profiling/units/units';
- import {formatTo} from 'sentry/utils/profiling/units/units';
- import {useDevicePixelRatio} from 'sentry/utils/useDevicePixelRatio';
- import {useMemoWithPrevious} from 'sentry/utils/useMemoWithPrevious';
- import {
- useContinuousProfile,
- useContinuousProfileSegment,
- } from 'sentry/views/profiling/continuousProfileProvider';
- import {useContinuousProfileGroup} from 'sentry/views/profiling/profileGroupProvider';
- import {FlamegraphDrawer} from './flamegraphDrawer/flamegraphDrawer';
- import {FlamegraphWarnings} from './flamegraphOverlays/FlamegraphWarnings';
- import {useViewKeyboardNavigation} from './interactions/useViewKeyboardNavigation';
- import {FlamegraphChart} from './flamegraphChart';
- import {FlamegraphLayout} from './flamegraphLayout';
- import {FlamegraphSpans} from './flamegraphSpans';
- import {FlamegraphUIFrames} from './flamegraphUIFrames';
- function collectAllSpanEntriesFromTransaction(
- transaction: EventTransaction
- ): EntrySpans['data'] {
- if (!transaction.entries.length) {
- return [];
- }
- const spans = transaction.entries.filter(
- (e): e is EntrySpans => e.type === EntryType.SPANS
- );
- let allSpans: EntrySpans['data'] = [];
- for (const span of spans) {
- allSpans = allSpans.concat(span.data);
- }
- return allSpans;
- }
- function getMaxConfigSpace(
- profileGroup: ContinuousProfileGroup,
- transaction: EventTransaction | null,
- unit: ProfilingFormatterUnit | string
- ): Rect {
- const maxProfileDuration = Math.max(...profileGroup.profiles.map(p => p.duration));
- if (transaction) {
- // TODO: Adjust the alignment based on the profile's timestamp if it does
- // not match the transaction's start timestamp
- const transactionDuration = transaction.endTimestamp - transaction.startTimestamp;
- // On most platforms, profile duration < transaction duration, however
- // there is one beloved platform where that is not true; android.
- // Hence, we should take the max of the two to ensure both the transaction
- // and profile are fully visible to the user.
- const duration = Math.max(
- formatTo(transactionDuration, 'seconds', unit),
- maxProfileDuration
- );
- return new Rect(0, 0, duration, 0);
- }
- // No transaction was found, so best we can do is align it to the starting
- // position of the profiles - find the max of profile durations
- return new Rect(0, 0, maxProfileDuration, 0);
- }
- function getProfileOffset(
- profile: ContinuousProfile | undefined,
- startedAtMs: number | null
- ): Rect {
- if (!profile || !startedAtMs) {
- return Rect.Empty();
- }
- return new Rect(profile.startedAt * 1e3 - startedAtMs, 0, 0, 0);
- }
- function getTransactionOffset(
- transaction: EventTransaction | null,
- startedAtMs: number | null
- ): Rect {
- if (!transaction || !startedAtMs) {
- return Rect.Empty();
- }
- return new Rect(transaction.startTimestamp * 1e3 - startedAtMs, 0, 0, 0);
- }
- function convertContinuousProfileMeasurementsToUIFrames(
- measurement: ContinuousProfileGroup['measurements']['slow_frame_renders']
- ): UIFrameMeasurements | undefined {
- if (!measurement) {
- return undefined;
- }
- const measurements: UIFrameMeasurements = {
- unit: measurement.unit,
- values: [],
- };
- for (let i = 0; i < measurement.values.length; i++) {
- const value = measurement.values[i];
- const next = measurement.values[i + 1] ?? value;
- measurements.values.push({
- elapsed: next.timestamp - value.timestamp,
- value: value.value,
- });
- }
- return measurements;
- }
- type FlamegraphCandidate = {
- frame: FlamegraphFrame;
- threadId: number;
- isActiveThread?: boolean; // this is the thread referred to by the active profile index
- };
- function findLongestMatchingFrame(
- flamegraph: FlamegraphModel,
- focusFrame: FlamegraphSearchType['highlightFrames']
- ): FlamegraphFrame | null {
- if (focusFrame === null) {
- return null;
- }
- let longestFrame: FlamegraphFrame | null = null;
- const frames: FlamegraphFrame[] = [...flamegraph.root.children];
- while (frames.length > 0) {
- const frame = frames.pop()!;
- if (
- focusFrame.name === frame.frame.name &&
- // the image name on a frame is optional treat it the same as the empty string
- (focusFrame.package === (frame.frame.package || '') ||
- focusFrame.package === (frame.frame.module || '')) &&
- (longestFrame?.node?.totalWeight || 0) < frame.node.totalWeight
- ) {
- longestFrame = frame;
- }
- for (let i = 0; i < frame.children.length; i++) {
- frames.push(frame.children[i]);
- }
- }
- return longestFrame;
- }
- const LOADING_OR_FALLBACK_FLAMEGRAPH = FlamegraphModel.Empty();
- const LOADING_OR_FALLBACK_UIFRAMES = UIFrames.Empty;
- const LOADING_OR_FALLBACK_SPAN_TREE = SpanTree.Empty;
- const LOADING_OR_FALLBACK_BATTERY_CHART = FlamegraphChartModel.Empty;
- const LOADING_OR_FALLBACK_CPU_CHART = FlamegraphChartModel.Empty;
- const LOADING_OR_FALLBACK_MEMORY_CHART = FlamegraphChartModel.Empty;
- const noopFormatDuration = () => '';
- export function ContinuousFlamegraph(): ReactElement {
- const devicePixelRatio = useDevicePixelRatio();
- const dispatch = useDispatchFlamegraphState();
- const profiles = useContinuousProfile();
- const profileGroup = useContinuousProfileGroup();
- const segment = useContinuousProfileSegment();
- const flamegraphTheme = useFlamegraphTheme();
- const position = useFlamegraphZoomPosition();
- const {colorCoding, sorting, view} = useFlamegraphPreferences();
- const {highlightFrames} = useFlamegraphSearch();
- const flamegraphProfiles = useFlamegraphProfiles();
- const start: number | null = useMemo(() => {
- const qs = new URLSearchParams(window.location.search);
- const startedAt = qs.get('start');
- if (!startedAt) {
- return null;
- }
- const sinceEpoch = new Date(startedAt).getTime();
- return isNaN(sinceEpoch) ? null : sinceEpoch;
- }, []);
- const [flamegraphCanvasRef, setFlamegraphCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [flamegraphOverlayCanvasRef, setFlamegraphOverlayCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [spansCanvasRef, setSpansCanvasRef] = useState<HTMLCanvasElement | null>(null);
- const [flamegraphMiniMapCanvasRef, setFlamegraphMiniMapCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [flamegraphMiniMapOverlayCanvasRef, setFlamegraphMiniMapOverlayCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [uiFramesCanvasRef, setUIFramesCanvasRef] = useState<HTMLCanvasElement | null>(
- null
- );
- const [batteryChartCanvasRef, setBatteryChartCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [cpuChartCanvasRef, setCpuChartCanvasRef] = useState<HTMLCanvasElement | null>(
- null
- );
- const [memoryChartCanvasRef, setMemoryChartCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const canvasPoolManager = useMemo(() => new CanvasPoolManager(), []);
- const scheduler = useCanvasScheduler(canvasPoolManager);
- const hasUIFrames = useMemo(() => {
- const platform = profileGroup.metadata.platform;
- return platform === 'cocoa' || platform === 'android';
- }, [profileGroup.metadata.platform]);
- const hasBatteryChart = useMemo(() => {
- const platform = profileGroup.metadata.platform;
- return platform === 'cocoa' || profileGroup.measurements?.cpu_energy_usage;
- }, [profileGroup.metadata.platform, profileGroup.measurements]);
- const hasCPUChart = useMemo(() => {
- const platform = profileGroup.metadata.platform;
- return (
- platform === 'cocoa' ||
- platform === 'android' ||
- platform === 'node' ||
- profileGroup.measurements?.cpu_usage
- );
- }, [profileGroup.metadata.platform, profileGroup.measurements]);
- const hasMemoryChart = useMemo(() => {
- const platform = profileGroup.metadata.platform;
- return (
- platform === 'cocoa' ||
- platform === 'android' ||
- platform === 'node' ||
- profileGroup.measurements?.memory_footprint
- );
- }, [profileGroup.metadata.platform, profileGroup.measurements]);
- const profile = useMemo(() => {
- return profileGroup.profiles.find(p => p.threadId === flamegraphProfiles.threadId);
- }, [profileGroup, flamegraphProfiles.threadId]);
- const spanTree: SpanTree = useMemo(() => {
- if (segment.type === 'resolved' && segment.data) {
- return new SpanTree(
- segment.data,
- collectAllSpanEntriesFromTransaction(segment.data)
- );
- }
- return LOADING_OR_FALLBACK_SPAN_TREE;
- }, [segment]);
- const spanChart = useMemo(() => {
- if (!profile) {
- return null;
- }
- return new SpanChart(spanTree, {
- unit: profile.unit,
- configSpace: getMaxConfigSpace(
- profileGroup,
- segment.type === 'resolved' ? segment.data : null,
- profile.unit
- ),
- });
- }, [spanTree, profile, profileGroup, segment]);
- const flamegraph = useMemo(() => {
- if (typeof flamegraphProfiles.threadId !== 'number') {
- return LOADING_OR_FALLBACK_FLAMEGRAPH;
- }
- // This could happen if threadId was initialized from query string, but for some
- // reason the profile was removed from the list of profiles.
- if (!profile) {
- return LOADING_OR_FALLBACK_FLAMEGRAPH;
- }
- const span = Sentry.withScope(scope => {
- scope.setTag('sorting', sorting.split(' ').join('_'));
- scope.setTag('view', view.split(' ').join('_'));
- return Sentry.startInactiveSpan({
- op: 'import',
- name: 'flamegraph.constructor',
- forceTransaction: true,
- });
- });
- const newFlamegraph = new FlamegraphModel(profile, {
- inverted: view === 'bottom up',
- sort: sorting,
- configSpace: getMaxConfigSpace(
- profileGroup,
- segment.type === 'resolved' ? segment.data : null,
- profile.unit
- ),
- });
- span?.end();
- return newFlamegraph;
- }, [profile, profileGroup, sorting, flamegraphProfiles.threadId, view, segment]);
- const uiFrames = useMemo(() => {
- if (!hasUIFrames) {
- return LOADING_OR_FALLBACK_UIFRAMES;
- }
- return new UIFrames(
- {
- slow: convertContinuousProfileMeasurementsToUIFrames(
- profileGroup.measurements?.slow_frame_renders
- ),
- frozen: convertContinuousProfileMeasurementsToUIFrames(
- profileGroup.measurements?.frozen_frame_renders
- ),
- },
- {unit: flamegraph.profile.unit},
- flamegraph.configSpace.withHeight(1)
- );
- }, [
- profileGroup.measurements,
- flamegraph.profile.unit,
- flamegraph.configSpace,
- hasUIFrames,
- ]);
- const batteryChart = useMemo(() => {
- if (!hasCPUChart) {
- return LOADING_OR_FALLBACK_BATTERY_CHART;
- }
- const measures: ProfileSeriesMeasurement[] = [];
- for (const key in profileGroup.measurements) {
- if (key === 'cpu_energy_usage') {
- const measurements = profileGroup.measurements[key]!;
- const values: ProfileSeriesMeasurement['values'] = [];
- let offset = 0;
- for (let i = 0; i < measurements.values.length; i++) {
- const value = measurements.values[i];
- const next = measurements.values[i + 1] ?? value;
- offset += (next.timestamp - value.timestamp) * 1e3;
- values.push({
- value: fromNanoJoulesToWatts(value.value, 0.1),
- elapsed: offset,
- });
- }
- // some versions of cocoa send byte so we need to correct it to watt
- measures.push({name: 'CPU energy usage', unit: 'watt', values});
- }
- }
- return new FlamegraphChartModel(
- Rect.From(flamegraph.configSpace),
- measures.length > 0 ? measures : [],
- flamegraphTheme.COLORS.BATTERY_CHART_COLORS,
- {timelineUnit: 'milliseconds'}
- );
- }, [profileGroup.measurements, flamegraph.configSpace, flamegraphTheme, hasCPUChart]);
- const CPUChart = useMemo(() => {
- if (!hasCPUChart) {
- return LOADING_OR_FALLBACK_CPU_CHART;
- }
- const cpuMeasurements: ProfileSeriesMeasurement[] = [];
- for (const key in profileGroup.measurements) {
- if (key.startsWith('cpu_usage')) {
- const name =
- key === 'cpu_usage'
- ? 'Average CPU usage'
- : `CPU Core ${key.replace('cpu_usage_', '')}`;
- const measurements = profileGroup.measurements[key]!;
- const values: ProfileSeriesMeasurement['values'] = [];
- let offset = 0;
- for (let i = 0; i < measurements.values.length; i++) {
- const value = measurements.values[i];
- const next = measurements.values[i + 1] ?? value;
- offset += (next.timestamp - value.timestamp) * 1e3;
- values.push({
- value: value.value,
- elapsed: offset,
- });
- }
- cpuMeasurements.push({name, unit: measurements?.unit, values});
- }
- }
- return new FlamegraphChartModel(
- Rect.From(flamegraph.configSpace),
- cpuMeasurements.length > 0 ? cpuMeasurements : [],
- flamegraphTheme.COLORS.CPU_CHART_COLORS,
- {timelineUnit: 'milliseconds'}
- );
- }, [profileGroup.measurements, flamegraph.configSpace, flamegraphTheme, hasCPUChart]);
- const memoryChart = useMemo(() => {
- if (!hasMemoryChart) {
- return LOADING_OR_FALLBACK_MEMORY_CHART;
- }
- const measures: ProfileSeriesMeasurement[] = [];
- const memory_footprint = profileGroup.measurements?.memory_footprint;
- if (memory_footprint) {
- const values: ProfileSeriesMeasurement['values'] = [];
- let offset = 0;
- for (let i = 0; i < memory_footprint.values.length; i++) {
- const value = memory_footprint.values[i];
- const next = memory_footprint.values[i + 1] ?? value;
- offset += (next.timestamp - value.timestamp) * 1e3;
- values.push({
- value: value.value,
- elapsed: offset,
- });
- }
- measures.push({
- unit: memory_footprint.unit,
- name: 'Heap Usage',
- values,
- });
- }
- const native_memory_footprint = profileGroup.measurements?.memory_native_footprint;
- if (native_memory_footprint) {
- const values: ProfileSeriesMeasurement['values'] = [];
- let offset = 0;
- for (let i = 0; i < native_memory_footprint.values.length; i++) {
- const value = native_memory_footprint.values[i];
- const next = native_memory_footprint.values[i + 1] ?? value;
- offset += (next.timestamp - value.timestamp) * 1e3;
- values.push({
- value: value.value,
- elapsed: offset,
- });
- }
- measures.push({
- unit: native_memory_footprint.unit,
- name: 'Native Heap Usage',
- values,
- });
- }
- return new FlamegraphChartModel(
- Rect.From(flamegraph.configSpace),
- measures.length > 0 ? measures : [],
- flamegraphTheme.COLORS.MEMORY_CHART_COLORS,
- {type: 'area', timelineUnit: 'milliseconds'}
- );
- }, [
- profileGroup.measurements,
- flamegraph.configSpace,
- flamegraphTheme,
- hasMemoryChart,
- ]);
- const flamegraphCanvas = useMemo(() => {
- if (!flamegraphCanvasRef) {
- return null;
- }
- return new FlamegraphCanvas(
- flamegraphCanvasRef,
- vec2.fromValues(0, flamegraphTheme.SIZES.TIMELINE_HEIGHT * devicePixelRatio)
- );
- }, [devicePixelRatio, flamegraphCanvasRef, flamegraphTheme]);
- const flamegraphMiniMapCanvas = useMemo(() => {
- if (!flamegraphMiniMapCanvasRef) {
- return null;
- }
- return new FlamegraphCanvas(flamegraphMiniMapCanvasRef, vec2.fromValues(0, 0));
- }, [flamegraphMiniMapCanvasRef]);
- const spansCanvas = useMemo(() => {
- if (!spansCanvasRef) {
- return null;
- }
- return new FlamegraphCanvas(spansCanvasRef, vec2.fromValues(0, 0));
- }, [spansCanvasRef]);
- const uiFramesCanvas = useMemo(() => {
- if (!uiFramesCanvasRef) {
- return null;
- }
- return new FlamegraphCanvas(uiFramesCanvasRef, vec2.fromValues(0, 0));
- }, [uiFramesCanvasRef]);
- const batteryChartCanvas = useMemo(() => {
- if (!batteryChartCanvasRef) {
- return null;
- }
- return new FlamegraphCanvas(batteryChartCanvasRef, vec2.fromValues(0, 0));
- }, [batteryChartCanvasRef]);
- const cpuChartCanvas = useMemo(() => {
- if (!cpuChartCanvasRef) {
- return null;
- }
- return new FlamegraphCanvas(cpuChartCanvasRef, vec2.fromValues(0, 0));
- }, [cpuChartCanvasRef]);
- const memoryChartCanvas = useMemo(() => {
- if (!memoryChartCanvasRef) {
- return null;
- }
- return new FlamegraphCanvas(memoryChartCanvasRef, vec2.fromValues(0, 0));
- }, [memoryChartCanvasRef]);
- const flamegraphView = useMemoWithPrevious<CanvasView<FlamegraphModel> | null>(
- previousView => {
- if (!flamegraphCanvas) {
- return null;
- }
- const newView = new CanvasView({
- canvas: flamegraphCanvas,
- model: flamegraph,
- options: {
- inverted: flamegraph.inverted,
- minWidth: flamegraph.profile.minFrameDuration,
- barHeight: flamegraphTheme.SIZES.BAR_HEIGHT,
- depthOffset: flamegraphTheme.SIZES.FLAMEGRAPH_DEPTH_OFFSET,
- configSpaceTransform: getProfileOffset(profile, start),
- },
- });
- if (
- // if the profile or the config space of the flamegraph has changed, we do not
- // want to persist the config view. This is to avoid a case where the new config space
- // is larger than the previous one, meaning the new view could now be zoomed in even
- // though the user did not fire any zoom events.
- previousView?.model.profile === newView.model.profile &&
- previousView.configSpace.equals(newView.configSpace)
- ) {
- if (
- // if we're still looking at the same profile but only a preference other than
- // left heavy has changed, we do want to persist the config view
- previousView.model.sort === 'left heavy' &&
- newView.model.sort === 'left heavy'
- ) {
- newView.setConfigView(
- previousView.configView.withHeight(newView.configView.height)
- );
- }
- }
- if (defined(highlightFrames)) {
- let frames = flamegraph.findAllMatchingFrames(
- highlightFrames.name,
- highlightFrames.package
- );
- if (
- !frames.length &&
- !highlightFrames.package &&
- highlightFrames.name &&
- profileGroup.metadata.platform === 'node'
- ) {
- // there is a chance that the reason we did not find any frames is because
- // for node, we try to infer some package from the frontend code.
- // If that happens, we'll try and just do a search by name. This logic
- // is duplicated in flamegraphZoomView.tsx and should be kept in sync
- frames = flamegraph.findAllMatchingFramesBy(highlightFrames.name, ['name']);
- }
- if (frames.length > 0) {
- const rectFrames = frames.map(
- f => new Rect(f.start, f.depth, f.end - f.start, 1)
- );
- const newConfigView = computeMinZoomConfigViewForFrames(
- newView.configView,
- rectFrames
- ).transformRect(newView.configSpaceTransform);
- newView.setConfigView(newConfigView);
- return newView;
- }
- }
- // Because we render empty flamechart while we fetch the data, we need to make sure
- // to have some heuristic when the data is fetched to determine if we should
- // initialize the config view to the full view or a predefined value
- else if (
- !defined(highlightFrames) &&
- position.view &&
- !position.view.isEmpty() &&
- previousView?.model === LOADING_OR_FALLBACK_FLAMEGRAPH
- ) {
- // We allow min width to be initialize to lower than view.minWidth because
- // there is a chance that user zoomed into a span duration which may have been updated
- // after the model was loaded (see L320)
- newView.setConfigView(position.view, {width: {min: 0}});
- }
- return newView;
- },
- // We skip position.view dependency because it will go into an infinite loop
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [flamegraph, flamegraphCanvas, flamegraphTheme, profile, segment, start]
- );
- const uiFramesView = useMemoWithPrevious<CanvasView<UIFrames> | null>(
- _previousView => {
- if (!flamegraphView || !flamegraphCanvas || !uiFrames) {
- return null;
- }
- const newView = new CanvasView({
- canvas: flamegraphCanvas,
- model: uiFrames,
- mode: 'stretchToFit',
- options: {
- inverted: flamegraph.inverted,
- minWidth: uiFrames.minFrameDuration,
- barHeight: 10,
- depthOffset: 0,
- configSpaceTransform: getProfileOffset(profile, start),
- },
- });
- // Initialize configView to whatever the flamegraph configView is
- newView.setConfigView(
- flamegraphView.configView.withHeight(newView.configView.height),
- {width: {min: 0}}
- );
- return newView;
- },
- [flamegraphView, flamegraphCanvas, flamegraph, uiFrames, profile, start]
- );
- const batteryChartView = useMemoWithPrevious<CanvasView<FlamegraphChartModel> | null>(
- _previousView => {
- if (!flamegraphView || !flamegraphCanvas || !batteryChart || !batteryChartCanvas) {
- return null;
- }
- const newView = new CanvasView({
- canvas: flamegraphCanvas,
- model: batteryChart,
- mode: 'anchorBottom',
- options: {
- // Invert chart so origin is at bottom left
- // corner as opposed to top left
- inverted: true,
- minWidth: uiFrames.minFrameDuration,
- barHeight: 0,
- depthOffset: 0,
- maxHeight: batteryChart.configSpace.height,
- minHeight: batteryChart.configSpace.height,
- configSpaceTransform: getProfileOffset(profile, start),
- },
- });
- // Compute the total size of the padding and stretch the view. This ensures that
- // the total range is rendered and perfectly aligned from top to bottom.
- newView.setConfigView(
- flamegraphView.configView.withHeight(newView.configView.height),
- {
- width: {min: 1},
- }
- );
- return newView;
- },
- [
- flamegraphView,
- flamegraphCanvas,
- batteryChart,
- uiFrames.minFrameDuration,
- batteryChartCanvas,
- profile,
- start,
- ]
- );
- const cpuChartView = useMemoWithPrevious<CanvasView<FlamegraphChartModel> | null>(
- _previousView => {
- if (!flamegraphView || !flamegraphCanvas || !CPUChart || !cpuChartCanvas) {
- return null;
- }
- const newView = new CanvasView({
- canvas: flamegraphCanvas,
- model: CPUChart,
- mode: 'anchorBottom',
- options: {
- // Invert chart so origin is at bottom left
- // corner as opposed to top left
- inverted: true,
- minWidth: uiFrames.minFrameDuration,
- barHeight: 0,
- depthOffset: 0,
- maxHeight: CPUChart.configSpace.height,
- minHeight: CPUChart.configSpace.height,
- configSpaceTransform: getProfileOffset(profile, start),
- },
- });
- // Compute the total size of the padding and stretch the view. This ensures that
- // the total range is rendered and perfectly aligned from top to bottom.
- newView.setConfigView(
- flamegraphView.configView.withHeight(newView.configView.height),
- {
- width: {min: 1},
- }
- );
- return newView;
- },
- [
- flamegraphView,
- flamegraphCanvas,
- CPUChart,
- uiFrames.minFrameDuration,
- cpuChartCanvas,
- profile,
- start,
- ]
- );
- const memoryChartView = useMemoWithPrevious<CanvasView<FlamegraphChartModel> | null>(
- _previousView => {
- if (!flamegraphView || !flamegraphCanvas || !memoryChart || !memoryChartCanvas) {
- return null;
- }
- const newView = new CanvasView({
- canvas: flamegraphCanvas,
- model: memoryChart,
- mode: 'anchorBottom',
- options: {
- // Invert chart so origin is at bottom left
- // corner as opposed to top left
- inverted: true,
- minWidth: uiFrames.minFrameDuration,
- barHeight: 0,
- depthOffset: 0,
- maxHeight: memoryChart.configSpace.height,
- minHeight: memoryChart.configSpace.height,
- configSpaceTransform: getProfileOffset(profile, start),
- },
- });
- // Compute the total size of the padding and stretch the view. This ensures that
- // the total range is rendered and perfectly aligned from top to bottom.
- newView.setConfigView(
- flamegraphView.configView.withHeight(newView.configView.height),
- {
- width: {min: 1},
- }
- );
- return newView;
- },
- [
- flamegraphView,
- flamegraphCanvas,
- memoryChart,
- uiFrames.minFrameDuration,
- memoryChartCanvas,
- profile,
- start,
- ]
- );
- const spansView = useMemoWithPrevious<CanvasView<SpanChart> | null>(
- _previousView => {
- if (!spansCanvas || !spanChart || !flamegraphView) {
- return null;
- }
- const newView = new CanvasView({
- canvas: spansCanvas,
- model: spanChart,
- options: {
- inverted: false,
- minWidth: spanChart.minSpanDuration,
- barHeight: flamegraphTheme.SIZES.SPANS_BAR_HEIGHT,
- depthOffset: flamegraphTheme.SIZES.SPANS_DEPTH_OFFSET,
- configSpaceTransform: getTransactionOffset(
- segment.type === 'resolved' ? segment.data : null,
- start
- ),
- },
- });
- // Initialize configView to whatever the flamegraph configView is
- newView.setConfigView(
- flamegraphView.configView.withHeight(newView.configView.height),
- {width: {min: 1}}
- );
- return newView;
- },
- [spanChart, spansCanvas, flamegraphView, flamegraphTheme.SIZES, start, segment]
- );
- // We want to make sure that the views have the same min zoom levels so that
- // if you wheel zoom on one, the other one will also zoom to the same level of detail.
- // If we dont do this, then at some point during the zoom action the views will
- // detach and only one will zoom while the other one will stay at the same zoom level.
- useEffect(() => {
- const minWidthBetweenViews = Math.min(
- flamegraphView?.minWidth ?? Number.MAX_SAFE_INTEGER,
- uiFramesView?.minWidth ?? Number.MAX_SAFE_INTEGER,
- cpuChartView?.minWidth ?? Number.MAX_SAFE_INTEGER,
- memoryChartView?.minWidth ?? Number.MAX_SAFE_INTEGER,
- batteryChartView?.minWidth ?? Number.MAX_SAFE_INTEGER,
- spansView?.minWidth ?? Number.MAX_SAFE_INTEGER
- );
- flamegraphView?.setMinWidth?.(minWidthBetweenViews);
- uiFramesView?.setMinWidth?.(minWidthBetweenViews);
- cpuChartView?.setMinWidth?.(minWidthBetweenViews);
- memoryChartView?.setMinWidth?.(minWidthBetweenViews);
- batteryChartView?.setMinWidth?.(minWidthBetweenViews);
- spansView?.setMinWidth?.(minWidthBetweenViews);
- }, [
- flamegraphView,
- uiFramesView,
- cpuChartView,
- memoryChartView,
- batteryChartView,
- spansView,
- ]);
- // Uses a useLayoutEffect to ensure that these top level/global listeners are added before
- // any of the children components effects actually run. This way we do not lose events
- // when we register/unregister these top level listeners.
- useLayoutEffect(() => {
- if (!flamegraphCanvas || !flamegraphView) {
- return undefined;
- }
- // This code below manages the synchronization of the config views between spans and flamegraph
- // We do so by listening to the config view change event and then updating the other views accordingly which
- // allows us to keep the X axis in sync between the two views but keep the Y axis independent
- const onConfigViewChange = (rect: Rect, sourceConfigViewChange: CanvasView<any>) => {
- if (sourceConfigViewChange === flamegraphView) {
- flamegraphView.setConfigView(rect.withHeight(flamegraphView.configView.height));
- if (spansView) {
- const beforeY = spansView.configView.y;
- spansView.setConfigView(
- rect.withHeight(spansView.configView.height).withY(beforeY)
- );
- }
- if (uiFramesView) {
- uiFramesView.setConfigView(rect);
- }
- if (cpuChartView) {
- cpuChartView.setConfigView(rect);
- }
- if (memoryChartView) {
- memoryChartView.setConfigView(rect);
- }
- if (batteryChartView) {
- batteryChartView.setConfigView(rect);
- }
- }
- if (sourceConfigViewChange === spansView) {
- spansView.setConfigView(rect.withHeight(spansView.configView.height));
- const beforeY = flamegraphView.configView.y;
- flamegraphView.setConfigView(
- rect.withHeight(flamegraphView.configView.height).withY(beforeY)
- );
- if (uiFramesView) {
- uiFramesView.setConfigView(rect);
- }
- if (cpuChartView) {
- cpuChartView.setConfigView(rect);
- }
- if (memoryChartView) {
- memoryChartView.setConfigView(rect);
- }
- if (batteryChartView) {
- batteryChartView.setConfigView(rect);
- }
- }
- canvasPoolManager.draw();
- };
- const onTransformConfigView = (
- mat: mat3,
- sourceTransformConfigView: CanvasView<any>
- ) => {
- if (sourceTransformConfigView === flamegraphView) {
- flamegraphView.transformConfigView(mat);
- if (spansView) {
- const beforeY = spansView.configView.y;
- spansView.transformConfigView(mat);
- spansView.setConfigView(spansView.configView.withY(beforeY));
- }
- if (uiFramesView) {
- uiFramesView.transformConfigView(mat);
- }
- if (batteryChartView) {
- batteryChartView.transformConfigView(mat);
- }
- if (cpuChartView) {
- cpuChartView.transformConfigView(mat);
- }
- if (memoryChartView) {
- memoryChartView.transformConfigView(mat);
- }
- }
- if (sourceTransformConfigView === spansView) {
- spansView.transformConfigView(mat);
- const beforeY = flamegraphView.configView.y;
- flamegraphView.transformConfigView(mat);
- flamegraphView.setConfigView(flamegraphView.configView.withY(beforeY));
- if (uiFramesView) {
- uiFramesView.transformConfigView(mat);
- }
- if (batteryChartView) {
- batteryChartView.transformConfigView(mat);
- }
- if (cpuChartView) {
- cpuChartView.transformConfigView(mat);
- }
- if (memoryChartView) {
- memoryChartView.transformConfigView(mat);
- }
- }
- if (
- sourceTransformConfigView === uiFramesView ||
- sourceTransformConfigView === cpuChartView ||
- sourceTransformConfigView === memoryChartView ||
- sourceTransformConfigView === batteryChartView
- ) {
- if (flamegraphView) {
- const beforeY = flamegraphView.configView.y;
- flamegraphView.transformConfigView(mat);
- flamegraphView.setConfigView(flamegraphView.configView.withY(beforeY));
- }
- if (uiFramesView) {
- uiFramesView.transformConfigView(mat);
- }
- if (batteryChartView) {
- batteryChartView.transformConfigView(mat);
- }
- if (cpuChartView) {
- cpuChartView.transformConfigView(mat);
- }
- if (memoryChartView) {
- memoryChartView.transformConfigView(mat);
- }
- }
- canvasPoolManager.draw();
- };
- const onResetZoom = () => {
- flamegraphView.resetConfigView(flamegraphCanvas);
- if (spansView && spansCanvas) {
- spansView.resetConfigView(spansCanvas);
- }
- if (uiFramesView && uiFramesCanvas) {
- uiFramesView.resetConfigView(uiFramesCanvas);
- }
- if (batteryChartView && batteryChartCanvas) {
- batteryChartView.resetConfigView(batteryChartCanvas);
- }
- if (cpuChartView && cpuChartCanvas) {
- cpuChartView.resetConfigView(cpuChartCanvas);
- }
- if (memoryChartView && memoryChartCanvas) {
- memoryChartView.resetConfigView(memoryChartCanvas);
- }
- canvasPoolManager.draw();
- };
- const onZoomIntoFrame = (frame: FlamegraphFrame, strategy: 'min' | 'exact') => {
- const newConfigView = computeConfigViewWithStrategy(
- strategy,
- flamegraphView.configView,
- new Rect(frame.start, frame.depth, frame.end - frame.start, 1)
- ).transformRect(flamegraphView.configSpaceTransform);
- flamegraphView.setConfigView(newConfigView);
- if (spansView) {
- spansView.setConfigView(newConfigView.withHeight(spansView.configView.height));
- }
- if (uiFramesView) {
- uiFramesView.setConfigView(
- newConfigView.withHeight(uiFramesView.configView.height)
- );
- }
- if (batteryChartView) {
- batteryChartView.setConfigView(
- newConfigView.withHeight(batteryChartView.configView.height)
- );
- }
- if (cpuChartView) {
- cpuChartView.setConfigView(
- newConfigView.withHeight(cpuChartView.configView.height)
- );
- }
- if (memoryChartView) {
- memoryChartView.setConfigView(
- newConfigView.withHeight(memoryChartView.configView.height)
- );
- }
- canvasPoolManager.draw();
- };
- const onZoomIntoSpan = (span: SpanChartNode, strategy: 'min' | 'exact') => {
- if (!spansView) {
- return;
- }
- const newConfigView = computeConfigViewWithStrategy(
- strategy,
- spansView.configView,
- new Rect(span.start, span.depth, span.end - span.start, 1)
- ).transformRect(spansView.configSpaceTransform);
- spansView.setConfigView(newConfigView);
- flamegraphView.setConfigView(
- newConfigView
- .withHeight(flamegraphView.configView.height)
- .withY(flamegraphView.configView.y)
- );
- if (uiFramesView) {
- uiFramesView.setConfigView(
- newConfigView.withHeight(uiFramesView.configView.height)
- );
- }
- if (batteryChartView) {
- batteryChartView.setConfigView(
- newConfigView.withHeight(batteryChartView.configView.height)
- );
- }
- if (cpuChartView) {
- cpuChartView.setConfigView(
- newConfigView.withHeight(cpuChartView.configView.height)
- );
- }
- if (memoryChartView) {
- memoryChartView.setConfigView(
- newConfigView.withHeight(memoryChartView.configView.height)
- );
- }
- canvasPoolManager.draw();
- };
- scheduler.on('set config view', onConfigViewChange);
- scheduler.on('transform config view', onTransformConfigView);
- scheduler.on('reset zoom', onResetZoom);
- scheduler.on('zoom at frame', onZoomIntoFrame);
- scheduler.on('zoom at span', onZoomIntoSpan);
- return () => {
- scheduler.off('set config view', onConfigViewChange);
- scheduler.off('transform config view', onTransformConfigView);
- scheduler.off('reset zoom', onResetZoom);
- scheduler.off('zoom at frame', onZoomIntoFrame);
- scheduler.off('zoom at span', onZoomIntoSpan);
- };
- }, [
- scheduler,
- canvasPoolManager,
- flamegraphCanvas,
- flamegraphView,
- spansCanvas,
- spansView,
- uiFramesCanvas,
- uiFramesView,
- cpuChartCanvas,
- cpuChartView,
- memoryChartCanvas,
- memoryChartView,
- batteryChartView,
- batteryChartCanvas,
- ]);
- const minimapCanvases = useMemo(() => {
- return [flamegraphMiniMapCanvasRef, flamegraphMiniMapOverlayCanvasRef];
- }, [flamegraphMiniMapCanvasRef, flamegraphMiniMapOverlayCanvasRef]);
- useResizeCanvasObserver(
- minimapCanvases,
- canvasPoolManager,
- flamegraphMiniMapCanvas,
- null
- );
- const spansCanvases = useMemo(() => {
- return [spansCanvasRef];
- }, [spansCanvasRef]);
- const spansCanvasBounds = useResizeCanvasObserver(
- spansCanvases,
- canvasPoolManager,
- spansCanvas,
- spansView
- );
- const uiFramesCanvases = useMemo(() => {
- return [uiFramesCanvasRef];
- }, [uiFramesCanvasRef]);
- const uiFramesCanvasBounds = useResizeCanvasObserver(
- uiFramesCanvases,
- canvasPoolManager,
- uiFramesCanvas,
- uiFramesView
- );
- const batteryChartCanvases = useMemo(() => {
- return [batteryChartCanvasRef];
- }, [batteryChartCanvasRef]);
- const batteryChartCanvasBounds = useResizeCanvasObserver(
- batteryChartCanvases,
- canvasPoolManager,
- batteryChartCanvas,
- batteryChartView
- );
- const cpuChartCanvases = useMemo(() => {
- return [cpuChartCanvasRef];
- }, [cpuChartCanvasRef]);
- const cpuChartCanvasBounds = useResizeCanvasObserver(
- cpuChartCanvases,
- canvasPoolManager,
- cpuChartCanvas,
- cpuChartView
- );
- const memoryChartCanvases = useMemo(() => {
- return [memoryChartCanvasRef];
- }, [memoryChartCanvasRef]);
- const memoryChartCanvasBounds = useResizeCanvasObserver(
- memoryChartCanvases,
- canvasPoolManager,
- memoryChartCanvas,
- memoryChartView
- );
- const flamegraphCanvases = useMemo(() => {
- return [flamegraphCanvasRef, flamegraphOverlayCanvasRef];
- }, [flamegraphCanvasRef, flamegraphOverlayCanvasRef]);
- const flamegraphCanvasBounds = useResizeCanvasObserver(
- flamegraphCanvases,
- canvasPoolManager,
- flamegraphCanvas,
- flamegraphView
- );
- const flamegraphRenderer = useMemo(() => {
- if (!flamegraphCanvasRef) {
- return null;
- }
- const renderer = initializeFlamegraphRenderer(
- [FlamegraphRendererWebGL, FlamegraphRenderer2D],
- [
- flamegraphCanvasRef,
- flamegraph,
- flamegraphTheme,
- {
- colorCoding,
- draw_border: true,
- },
- ]
- );
- if (renderer === null) {
- Sentry.captureException('Failed to initialize a flamegraph renderer');
- addErrorMessage('Failed to initialize renderer');
- return null;
- }
- return renderer;
- }, [colorCoding, flamegraph, flamegraphCanvasRef, flamegraphTheme]);
- const getFrameColor = useCallback(
- (frame: FlamegraphFrame) => {
- if (!flamegraphRenderer) {
- return '';
- }
- return formatColorForFrame(frame, flamegraphRenderer);
- },
- [flamegraphRenderer]
- );
- const physicalToConfig =
- flamegraphView && flamegraphCanvas
- ? mat3.invert(
- mat3.create(),
- flamegraphView.fromConfigView(flamegraphCanvas.physicalSpace)
- )
- : mat3.create();
- const configSpacePixel = new Rect(0, 0, 1, 1).transformRect(physicalToConfig);
- // Register keyboard navigation
- useViewKeyboardNavigation(flamegraphView, canvasPoolManager, configSpacePixel.width);
- // referenceNode is passed down to the flamegraphdrawer and is used to determine
- // the weights of each frame. In other words, in case there is no user selected root, then all
- // of the frame weights and timing are relative to the entire profile. If there is a user selected
- // root however, all weights are relative to that sub tree.
- const referenceNode = useMemo(
- () =>
- flamegraphProfiles.selectedRoot ? flamegraphProfiles.selectedRoot : flamegraph.root,
- [flamegraphProfiles.selectedRoot, flamegraph.root]
- );
- // In case a user selected root is present, we will display that root + its entire sub tree.
- // If no root is selected, we will display the entire sub tree down from the root. We start at
- // root.children because flamegraph.root is a virtual node that we do not want to show in the table.
- const rootNodes = useMemo(() => {
- return flamegraphProfiles.selectedRoot
- ? [flamegraphProfiles.selectedRoot]
- : flamegraph.root.children;
- }, [flamegraphProfiles.selectedRoot, flamegraph.root]);
- const onSortingChange: FlamegraphViewSelectMenuProps['onSortingChange'] = useCallback(
- newSorting => {
- dispatch({type: 'set sorting', payload: newSorting});
- },
- [dispatch]
- );
- const onViewChange: FlamegraphViewSelectMenuProps['onViewChange'] = useCallback(
- newView => {
- dispatch({type: 'set view', payload: newView});
- },
- [dispatch]
- );
- const onThreadIdChange: FlamegraphThreadSelectorProps['onThreadIdChange'] = useCallback(
- newThreadId => {
- dispatch({type: 'set thread id', payload: newThreadId});
- },
- [dispatch]
- );
- useEffect(() => {
- if (defined(flamegraphProfiles.threadId)) {
- return;
- }
- const threadID =
- typeof profileGroup.activeProfileIndex === 'number'
- ? profileGroup.profiles[profileGroup.activeProfileIndex]?.threadId
- : null;
- // if the state has a highlight frame specified, then we want to jump to the
- // thread containing it, highlight the frames on the thread, and change the
- // view so it's obvious where it is
- if (highlightFrames) {
- const candidate = profileGroup.profiles.reduce<FlamegraphCandidate | null>(
- (prevCandidate, currentProfile) => {
- // if the previous candidate is the active thread, it always takes priority
- if (prevCandidate?.isActiveThread) {
- return prevCandidate;
- }
- const graph = new FlamegraphModel(currentProfile, {
- inverted: false,
- sort: sorting,
- configSpace: undefined,
- });
- const frame = findLongestMatchingFrame(graph, highlightFrames);
- if (!defined(frame)) {
- return prevCandidate;
- }
- const newScore = frame.node.totalWeight || 0;
- const oldScore = prevCandidate?.frame?.node?.totalWeight || 0;
- // if we find the frame on the active thread, it always takes priority
- if (newScore > 0 && currentProfile.threadId === threadID) {
- return {
- frame,
- threadId: currentProfile.threadId,
- isActiveThread: true,
- };
- }
- return newScore <= oldScore
- ? prevCandidate
- : {
- frame,
- threadId: currentProfile.threadId,
- };
- },
- null
- );
- if (defined(candidate)) {
- dispatch({
- type: 'set thread id',
- payload: candidate.threadId,
- });
- return;
- }
- }
- // fall back case, when we finally load the active profile index from the profile,
- // make sure we update the thread id so that it is show first
- if (defined(threadID)) {
- dispatch({
- type: 'set thread id',
- payload: threadID,
- });
- }
- }, [profileGroup, highlightFrames, flamegraphProfiles.threadId, dispatch, sorting]);
- // A bit unfortunate for now, but the search component accepts a list
- // of model to search through. This will become useful as we build
- // differential flamecharts or start comparing different profiles/charts
- const flamegraphs = useMemo(() => [flamegraph], [flamegraph]);
- const spans = useMemo(() => (spanChart ? [spanChart] : []), [spanChart]);
- return (
- <Fragment>
- <FlamegraphToolbar>
- <FlamegraphThreadSelector
- profileGroup={profileGroup}
- threadId={flamegraphProfiles.threadId}
- onThreadIdChange={onThreadIdChange}
- />
- <FlamegraphViewSelectMenu
- view={view}
- sorting={sorting}
- onSortingChange={onSortingChange}
- onViewChange={onViewChange}
- />
- <FlamegraphSearch
- spans={spans}
- flamegraphs={flamegraphs}
- canvasPoolManager={canvasPoolManager}
- />
- <FlamegraphOptionsMenu canvasPoolManager={canvasPoolManager} />
- </FlamegraphToolbar>
- <FlamegraphLayout
- uiFrames={
- hasUIFrames ? (
- <FlamegraphUIFrames
- status={profiles.type}
- canvasBounds={uiFramesCanvasBounds}
- canvasPoolManager={canvasPoolManager}
- setUIFramesCanvasRef={setUIFramesCanvasRef}
- uiFramesCanvasRef={uiFramesCanvasRef}
- uiFramesCanvas={uiFramesCanvas}
- uiFramesView={uiFramesView}
- uiFrames={uiFrames}
- />
- ) : null
- }
- batteryChart={
- hasBatteryChart ? (
- <FlamegraphChart
- configViewUnit={flamegraph.profile.unit as ProfilingFormatterUnit}
- status={profiles.type}
- chartCanvasRef={batteryChartCanvasRef}
- chartCanvas={batteryChartCanvas}
- setChartCanvasRef={setBatteryChartCanvasRef}
- canvasBounds={batteryChartCanvasBounds}
- chartView={batteryChartView}
- canvasPoolManager={canvasPoolManager}
- chart={batteryChart}
- noMeasurementMessage={
- profileGroup.metadata.platform === 'cocoa'
- ? t(
- 'Upgrade to version 8.9.6 of sentry-cocoa SDK to enable battery usage collection'
- )
- : ''
- }
- />
- ) : null
- }
- memoryChart={
- hasMemoryChart ? (
- <FlamegraphChart
- configViewUnit={flamegraph.profile.unit as ProfilingFormatterUnit}
- status={profiles.type}
- chartCanvasRef={memoryChartCanvasRef}
- chartCanvas={memoryChartCanvas}
- setChartCanvasRef={setMemoryChartCanvasRef}
- canvasBounds={memoryChartCanvasBounds}
- chartView={memoryChartView}
- canvasPoolManager={canvasPoolManager}
- chart={memoryChart}
- noMeasurementMessage={
- profileGroup.metadata.platform === 'cocoa'
- ? t(
- 'Upgrade to version 8.9.6 of sentry-cocoa SDK to enable memory usage collection'
- )
- : profileGroup.metadata.platform === 'node'
- ? t(
- 'Upgrade to version 1.2.0 of @sentry/profiling-node to enable memory usage collection'
- )
- : ''
- }
- />
- ) : null
- }
- cpuChart={
- hasCPUChart ? (
- <FlamegraphChart
- configViewUnit={flamegraph.profile.unit as ProfilingFormatterUnit}
- status={profiles.type}
- chartCanvasRef={cpuChartCanvasRef}
- chartCanvas={cpuChartCanvas}
- setChartCanvasRef={setCpuChartCanvasRef}
- canvasBounds={cpuChartCanvasBounds}
- chartView={cpuChartView}
- canvasPoolManager={canvasPoolManager}
- chart={CPUChart}
- noMeasurementMessage={
- profileGroup.metadata.platform === 'cocoa'
- ? t(
- 'Upgrade to version 8.9.6 of sentry-cocoa SDK to enable CPU usage collection'
- )
- : profileGroup.metadata.platform === 'node'
- ? t(
- 'Upgrade to version 1.2.0 of @sentry/profiling-node to enable CPU usage collection'
- )
- : ''
- }
- />
- ) : null
- }
- spansTreeDepth={spanChart?.depth ?? null}
- spans={
- spanChart ? (
- <FlamegraphSpans
- canvasBounds={spansCanvasBounds}
- spanChart={spanChart}
- spansCanvas={spansCanvas}
- spansCanvasRef={spansCanvasRef}
- setSpansCanvasRef={setSpansCanvasRef}
- canvasPoolManager={canvasPoolManager}
- spansView={spansView}
- spansRequestState={segment}
- />
- ) : null
- }
- minimap={
- <FlamegraphZoomViewMinimap
- canvasPoolManager={canvasPoolManager}
- flamegraph={flamegraph}
- flamegraphMiniMapCanvas={flamegraphMiniMapCanvas}
- flamegraphMiniMapCanvasRef={flamegraphMiniMapCanvasRef}
- flamegraphMiniMapOverlayCanvasRef={flamegraphMiniMapOverlayCanvasRef}
- flamegraphMiniMapView={flamegraphView}
- setFlamegraphMiniMapCanvasRef={setFlamegraphMiniMapCanvasRef}
- setFlamegraphMiniMapOverlayCanvasRef={setFlamegraphMiniMapOverlayCanvasRef}
- />
- }
- flamegraph={
- <Fragment>
- <FlamegraphWarnings flamegraph={flamegraph} requestState={profiles} />
- <FlamegraphZoomView
- profileGroup={profileGroup}
- canvasBounds={flamegraphCanvasBounds}
- canvasPoolManager={canvasPoolManager}
- flamegraph={flamegraph}
- flamegraphRenderer={flamegraphRenderer}
- flamegraphCanvas={flamegraphCanvas}
- flamegraphCanvasRef={flamegraphCanvasRef}
- flamegraphOverlayCanvasRef={flamegraphOverlayCanvasRef}
- flamegraphView={flamegraphView}
- setFlamegraphCanvasRef={setFlamegraphCanvasRef}
- setFlamegraphOverlayCanvasRef={setFlamegraphOverlayCanvasRef}
- contextMenu={FlamegraphContextMenu}
- />
- </Fragment>
- }
- flamegraphDrawer={
- <FlamegraphDrawer
- profileTransaction={null}
- profileGroup={profileGroup}
- getFrameColor={getFrameColor}
- referenceNode={referenceNode}
- rootNodes={rootNodes}
- flamegraph={flamegraph}
- formatDuration={flamegraph ? flamegraph.formatter : noopFormatDuration}
- canvasPoolManager={canvasPoolManager}
- canvasScheduler={scheduler}
- />
- }
- />
- </Fragment>
- );
- }
|