12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568 |
- 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 {RequestState} from 'sentry/types/core';
- 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 {ProfileGroup} 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 type {ProfilingFormatterUnit} from 'sentry/utils/profiling/units/units';
- import {formatTo, fromNanoJoulesToWatts} from 'sentry/utils/profiling/units/units';
- import {useDevicePixelRatio} from 'sentry/utils/useDevicePixelRatio';
- import {useMemoWithPrevious} from 'sentry/utils/useMemoWithPrevious';
- import {useProfileGroup} from 'sentry/views/profiling/profileGroupProvider';
- import {
- useProfiles,
- useProfileTransaction,
- } from 'sentry/views/profiling/profilesProvider';
- 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 getMaxConfigSpace(
- profileGroup: ProfileGroup,
- transaction: EventTransaction | null,
- unit: ProfilingFormatterUnit | string
- ): Rect {
- 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;
- return new Rect(0, 0, formatTo(transactionDuration, 'seconds', unit), 0);
- }
- // We have a transaction, so we should do our best to align the profile
- // with the transaction's timeline.
- const maxProfileDuration = Math.max(...profileGroup.profiles.map(p => p.duration));
- // 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 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 convertProfileMeasurementsToUIFrames(
- measurement: ProfileGroup['measurements']['slow_frame_renders']
- ): UIFrameMeasurements | undefined {
- if (!measurement) {
- return undefined;
- }
- const measurements: UIFrameMeasurements = {
- unit: measurement.unit,
- values: [],
- };
- for (const value of measurement.values) {
- measurements.values.push({
- elapsed: value.elapsed_since_start_ns,
- 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 (const child of frame.children) {
- frames.push(child);
- }
- }
- return longestFrame;
- }
- function computeProfileOffset(
- profileStart: string | undefined,
- flamegraph: FlamegraphModel,
- transaction: RequestState<EventTransaction | null>
- ): number {
- let offset = flamegraph.profile.startedAt;
- const transactionStart =
- transaction.type === 'resolved' ? transaction.data?.startTimestamp ?? null : null;
- if (
- defined(transactionStart) &&
- defined(profileStart) &&
- // Android sometimes doesnt report timestamps, which end up being wrongly initialized when the data is ingested.
- profileStart !== '0001-01-01T00:00:00Z'
- ) {
- const profileStartTimestamp = new Date(profileStart).getTime() / 1e3;
- const profileOffset = profileStartTimestamp - transactionStart;
- offset += formatTo(profileOffset, 'second', flamegraph.profile.unit);
- }
- return offset;
- }
- const LOADING_OR_FALLBACK_FLAMEGRAPH = FlamegraphModel.Empty();
- const LOADING_OR_FALLBACK_SPAN_TREE = SpanTree.Empty;
- const LOADING_OR_FALLBACK_UIFRAMES = UIFrames.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 = () => '';
- function Flamegraph(): ReactElement {
- const devicePixelRatio = useDevicePixelRatio();
- const profiledTransaction = useProfileTransaction();
- const dispatch = useDispatchFlamegraphState();
- const profiles = useProfiles();
- const profileGroup = useProfileGroup();
- const flamegraphTheme = useFlamegraphTheme();
- const position = useFlamegraphZoomPosition();
- const {colorCoding, sorting, view} = useFlamegraphPreferences();
- const {highlightFrames} = useFlamegraphSearch();
- const flamegraphProfiles = useFlamegraphProfiles();
- const [flamegraphCanvasRef, setFlamegraphCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [flamegraphOverlayCanvasRef, setFlamegraphOverlayCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [flamegraphMiniMapCanvasRef, setFlamegraphMiniMapCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [flamegraphMiniMapOverlayCanvasRef, setFlamegraphMiniMapOverlayCanvasRef] =
- useState<HTMLCanvasElement | null>(null);
- const [spansCanvasRef, setSpansCanvasRef] = 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 (profiledTransaction.type === 'resolved' && profiledTransaction.data) {
- return new SpanTree(
- profiledTransaction.data,
- collectAllSpanEntriesFromTransaction(profiledTransaction.data)
- );
- }
- return LOADING_OR_FALLBACK_SPAN_TREE;
- }, [profiledTransaction]);
- const spanChart = useMemo(() => {
- if (!profile) {
- return null;
- }
- return new SpanChart(spanTree, {
- unit: profile.unit,
- configSpace: getMaxConfigSpace(
- profileGroup,
- profiledTransaction.type === 'resolved' ? profiledTransaction.data : null,
- profile.unit
- ),
- });
- }, [spanTree, profile, profileGroup, profiledTransaction]);
- 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;
- }
- // Wait for the transaction to finish loading, regardless of the results.
- // Otherwise, the rendered profile will probably shift once the transaction loads.
- if (
- profiledTransaction.type === 'loading' ||
- profiledTransaction.type === 'initial'
- ) {
- 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,
- profiledTransaction.type === 'resolved' ? profiledTransaction.data : null,
- profile.unit
- ),
- });
- span?.end();
- return newFlamegraph;
- }, [
- profile,
- profileGroup,
- profiledTransaction,
- sorting,
- flamegraphProfiles.threadId,
- view,
- ]);
- const profileOffsetFromTransaction = useMemo(
- () =>
- computeProfileOffset(
- profileGroup.metadata.timestamp,
- flamegraph,
- profiledTransaction
- ),
- [flamegraph, profiledTransaction, profileGroup.metadata.timestamp]
- );
- const uiFrames = useMemo(() => {
- if (!hasUIFrames) {
- return LOADING_OR_FALLBACK_UIFRAMES;
- }
- return new UIFrames(
- {
- slow: convertProfileMeasurementsToUIFrames(
- profileGroup.measurements?.slow_frame_renders
- ),
- frozen: convertProfileMeasurementsToUIFrames(
- 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') {
- measures.push({
- ...profileGroup.measurements[key]!,
- values: profileGroup.measurements[key]!.values.map(v => {
- return {
- elapsed: v.elapsed_since_start_ns,
- value: fromNanoJoulesToWatts(v.value, 0.1),
- };
- }),
- // some versions of cocoa send byte so we need to correct it to watt
- unit: 'watt',
- name: 'CPU energy usage',
- });
- }
- }
- return new FlamegraphChartModel(
- Rect.From(flamegraph.configSpace),
- measures.length > 0 ? measures : [],
- flamegraphTheme.COLORS.BATTERY_CHART_COLORS
- );
- }, [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'] = [];
- for (const value of measurements.values) {
- values.push({
- value: value.value,
- elapsed: value.elapsed_since_start_ns,
- });
- }
- cpuMeasurements.push({name, unit: measurements?.unit, values});
- }
- }
- return new FlamegraphChartModel(
- Rect.From(flamegraph.configSpace),
- cpuMeasurements.length > 0 ? cpuMeasurements : [],
- flamegraphTheme.COLORS.CPU_CHART_COLORS
- );
- }, [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'] = [];
- for (const value of memory_footprint.values) {
- values.push({
- value: value.value,
- elapsed: value.elapsed_since_start_ns,
- });
- }
- 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'] = [];
- for (const value of native_memory_footprint.values) {
- values.push({
- value: value.value,
- elapsed: value.elapsed_since_start_ns,
- });
- }
- 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'}
- );
- }, [
- 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: new Rect(profileOffsetFromTransaction, 0, 0, 0),
- },
- });
- 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, profileOffsetFromTransaction]
- );
- 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: new Rect(profileOffsetFromTransaction, 0, 0, 0),
- },
- });
- // 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, profileOffsetFromTransaction]
- );
- 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: new Rect(profileOffsetFromTransaction, 0, 0, 0),
- },
- });
- // 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,
- profileOffsetFromTransaction,
- ]
- );
- 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: new Rect(profileOffsetFromTransaction, 0, 0, 0),
- },
- });
- // 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,
- profileOffsetFromTransaction,
- ]
- );
- 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: new Rect(profileOffsetFromTransaction, 0, 0, 0),
- },
- });
- // 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,
- profileOffsetFromTransaction,
- ]
- );
- 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,
- },
- });
- // 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]
- );
- // 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,
- spansView?.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
- );
- flamegraphView?.setMinWidth?.(minWidthBetweenViews);
- spansView?.setMinWidth?.(minWidthBetweenViews);
- uiFramesView?.setMinWidth?.(minWidthBetweenViews);
- cpuChartView?.setMinWidth?.(minWidthBetweenViews);
- memoryChartView?.setMinWidth?.(minWidthBetweenViews);
- batteryChartView?.setMinWidth?.(minWidthBetweenViews);
- }, [
- flamegraphView,
- spansView,
- uiFramesView,
- cpuChartView,
- memoryChartView,
- batteryChartView,
- ]);
- // 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 (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);
- }
- }
- 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}
- 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}
- 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}
- 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={profiledTransaction}
- />
- ) : 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}
- filter={null}
- />
- <FlamegraphZoomView
- scheduler={scheduler}
- 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={profiledTransaction}
- profileGroup={profileGroup}
- getFrameColor={getFrameColor}
- referenceNode={referenceNode}
- rootNodes={rootNodes}
- flamegraph={flamegraph}
- formatDuration={flamegraph ? flamegraph.formatter : noopFormatDuration}
- canvasPoolManager={canvasPoolManager}
- canvasScheduler={scheduler}
- />
- }
- />
- </Fragment>
- );
- }
- export {Flamegraph};
|