@grafana/flamegraph
Version:
Grafana flamegraph visualization component
279 lines (276 loc) • 8.76 kB
JavaScript
import { jsx } from 'react/jsx-runtime';
import { css } from '@emotion/css';
import { useState, useCallback, useLayoutEffect, useEffect, useRef } from 'react';
import { escapeStringForRegex } from '@grafana/data';
import FlameGraphCallTreeContainer from './CallTree/FlameGraphCallTreeContainer.mjs';
import FlameGraph from './FlameGraph/FlameGraph.mjs';
import FlameGraphTopTableContainer from './TopTable/FlameGraphTopTableContainer.mjs';
import { FLAMEGRAPH_CONTAINER_HEIGHT } from './constants.mjs';
import { useColorScheme } from './hooks.mjs';
import { PaneView } from './types.mjs';
;
const FlameGraphPane = ({
paneView,
dataContainer,
search,
matchedLabels,
onTableSymbolClick,
onTextAlignSelected,
onTableSort,
showFlameGraphOnly,
disableCollapsing,
getExtraContextMenuButtons,
viewMode,
paneViewForContextMenu,
setSearch,
resetKey,
keepFocusOnDataChange,
focusedItemIndexes,
setFocusedItemIndexes,
sharedSandwichItem,
setSharedSandwichItem
}) => {
const [focusedItemData, setFocusedItemData] = useState();
const [rangeMin, setRangeMin] = useState(0);
const [rangeMax, setRangeMax] = useState(1);
const [textAlign, setTextAlign] = useState("left");
const [localSandwichItem, setLocalSandwichItem] = useState();
const isUsingSharedSandwich = setSharedSandwichItem !== void 0;
const sandwichItem = isUsingSharedSandwich ? sharedSandwichItem : localSandwichItem;
const setSandwichItem = useCallback(
(item) => {
if (isUsingSharedSandwich && setSharedSandwichItem) {
setSharedSandwichItem(item);
} else {
setLocalSandwichItem(item);
}
},
[isUsingSharedSandwich, setSharedSandwichItem]
);
const [collapsedMap, setCollapsedMap] = useState(() => dataContainer.getCollapsedMap());
const [colorScheme, setColorScheme] = useColorScheme(dataContainer);
const styles = getStyles();
useLayoutEffect(() => {
setCollapsedMap(dataContainer.getCollapsedMap());
}, [dataContainer]);
useEffect(() => {
if (resetKey !== void 0 && resetKey > 0) {
setFocusedItemData(void 0);
setRangeMin(0);
setRangeMax(1);
setLocalSandwichItem(void 0);
}
}, [resetKey]);
useEffect(() => {
var _a;
if (!keepFocusOnDataChange) {
setFocusedItemData(void 0);
setRangeMin(0);
setRangeMax(1);
setSandwichItem(void 0);
return;
}
if (dataContainer && focusedItemData) {
const item = (_a = dataContainer.getNodesWithLabel(focusedItemData.label)) == null ? void 0 : _a[0];
if (item) {
setFocusedItemData({ ...focusedItemData, item });
const levels = dataContainer.getLevels();
const totalViewTicks = levels.length ? levels[0][0].value : 0;
setRangeMin(item.start / totalViewTicks);
setRangeMax((item.start + item.value) / totalViewTicks);
} else {
setFocusedItemData({
...focusedItemData,
item: {
start: 0,
value: 0,
itemIndexes: [],
children: [],
level: 0
}
});
setRangeMin(0);
setRangeMax(1);
}
}
}, [dataContainer, keepFocusOnDataChange]);
const weSetFocusRef = useRef(false);
useEffect(() => {
if (!focusedItemIndexes || focusedItemIndexes.length === 0) {
return;
}
if (weSetFocusRef.current) {
weSetFocusRef.current = false;
return;
}
const currentIndexes = focusedItemData == null ? void 0 : focusedItemData.item.itemIndexes;
if (currentIndexes && currentIndexes.length === focusedItemIndexes.length) {
const matches = currentIndexes.every((val, idx) => val === focusedItemIndexes[idx]);
if (matches) {
return;
}
}
const levels = dataContainer.getLevels();
for (const level of levels) {
for (const item of level) {
if (item.itemIndexes.length === focusedItemIndexes.length && item.itemIndexes.every((val, idx) => val === focusedItemIndexes[idx])) {
const label = dataContainer.getLabel(item.itemIndexes[0]);
const totalViewTicks = levels[0][0].value;
setFocusedItemData({ label, item, posX: 0, posY: 0 });
setRangeMin(item.start / totalViewTicks);
setRangeMax((item.start + item.value) / totalViewTicks);
return;
}
}
}
}, [focusedItemIndexes, dataContainer, focusedItemData]);
const resetFocus = useCallback(() => {
setFocusedItemData(void 0);
setRangeMin(0);
setRangeMax(1);
setFocusedItemIndexes == null ? void 0 : setFocusedItemIndexes(void 0);
}, [setFocusedItemIndexes]);
const resetSandwich = useCallback(() => {
setSandwichItem(void 0);
}, [setSandwichItem]);
const onSymbolClick = useCallback(
(symbol) => {
const anchored = `^${escapeStringForRegex(symbol)}$`;
if (search === anchored) {
setSearch("");
} else {
onTableSymbolClick == null ? void 0 : onTableSymbolClick(symbol);
setSearch(anchored);
resetFocus();
}
},
[search, setSearch, resetFocus, onTableSymbolClick]
);
const onCallTreeSymbolClick = useCallback(
(symbol) => {
onTableSymbolClick == null ? void 0 : onTableSymbolClick(symbol);
},
[onTableSymbolClick]
);
const onCallTreeSearch = useCallback(
(symbol) => {
const anchored = `^${escapeStringForRegex(symbol)}$`;
if (search === anchored) {
setSearch("");
} else {
onTableSymbolClick == null ? void 0 : onTableSymbolClick(symbol);
setSearch(anchored);
resetFocus();
}
},
[search, setSearch, resetFocus, onTableSymbolClick]
);
const onTopTableSearch = useCallback(
(str) => {
if (!str) {
setSearch("");
return;
}
setSearch(`^${escapeStringForRegex(str)}$`);
},
[setSearch]
);
let content;
switch (paneView) {
case PaneView.TopTable:
content = /* @__PURE__ */ jsx("div", { className: styles.tableContainer, children: /* @__PURE__ */ jsx(
FlameGraphTopTableContainer,
{
data: dataContainer,
onSymbolClick,
search,
matchedLabels,
sandwichItem,
onSandwich: setSandwichItem,
onSearch: onTopTableSearch,
onTableSort,
colorScheme
}
) });
break;
case PaneView.FlameGraph:
default:
content = /* @__PURE__ */ jsx(
FlameGraph,
{
data: dataContainer,
rangeMin,
rangeMax,
matchedLabels,
setRangeMin,
setRangeMax,
onItemFocused: (data) => {
setFocusedItemData(data);
weSetFocusRef.current = true;
setFocusedItemIndexes == null ? void 0 : setFocusedItemIndexes(data.item.itemIndexes);
},
focusedItemData,
textAlign,
onTextAlignChange: (align) => {
setTextAlign(align);
onTextAlignSelected == null ? void 0 : onTextAlignSelected(align);
},
sandwichItem,
onSandwich: (label) => {
resetFocus();
setSandwichItem(label);
},
onFocusPillClick: resetFocus,
onSandwichPillClick: resetSandwich,
colorScheme,
onColorSchemeChange: setColorScheme,
isDiffMode: dataContainer.isDiffFlamegraph(),
showFlameGraphOnly,
collapsing: !disableCollapsing,
getExtraContextMenuButtons,
enableNewUI: true,
viewMode,
paneView: paneViewForContextMenu,
search,
collapsedMap,
setCollapsedMap
}
);
break;
case PaneView.CallTree:
content = /* @__PURE__ */ jsx("div", { className: styles.tableContainer, children: /* @__PURE__ */ jsx(
FlameGraphCallTreeContainer,
{
data: dataContainer,
onSymbolClick: onCallTreeSymbolClick,
sandwichItem,
onSandwich: setSandwichItem,
onTableSort,
search,
onSearch: onCallTreeSearch,
focusedItemIndexes,
setFocusedItemIndexes,
getExtraContextMenuButtons,
viewMode,
paneView: paneViewForContextMenu
}
) });
break;
}
return /* @__PURE__ */ jsx("div", { className: styles.paneWrapper, children: content });
};
function getStyles() {
return {
paneWrapper: css({
width: "100%",
height: "100%"
}),
tableContainer: css({
height: FLAMEGRAPH_CONTAINER_HEIGHT,
minWidth: 0,
overflow: "hidden"
})
};
}
export { FlameGraphPane as default };
//# sourceMappingURL=FlameGraphPane.mjs.map