UNPKG

@grafana/flamegraph

Version:

Grafana flamegraph visualization component

268 lines (265 loc) • 9.14 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import { css } from '@emotion/css'; import { memo, useMemo, useState } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; import { FieldType, MappingType, applyFieldOverrides } from '@grafana/data'; import { useStyles2, useTheme2, Table, TableCellDisplayMode, IconButton } from '@grafana/ui'; import { diffColorBlindColors, diffDefaultColors } from '../FlameGraph/colors.mjs'; import { TOP_TABLE_COLUMN_WIDTH } from '../constants.mjs'; import { ColorSchemeDiff } from '../types.mjs'; const FlameGraphTopTableContainer = memo( ({ data, onSymbolClick, search, matchedLabels, onSearch, sandwichItem, onSandwich, onTableSort, colorScheme }) => { const table = useMemo(() => { let filteredTable = {}; for (let i = 0; i < data.data.length; i++) { const value = data.getValue(i); const valueRight = data.getValueRight(i); const self = data.getSelf(i); const label = data.getLabel(i); if (!matchedLabels || matchedLabels.has(label)) { filteredTable[label] = filteredTable[label] || {}; filteredTable[label].self = filteredTable[label].self ? filteredTable[label].self + self : self; filteredTable[label].total = filteredTable[label].total ? filteredTable[label].total + value : value; filteredTable[label].totalRight = filteredTable[label].totalRight ? filteredTable[label].totalRight + valueRight : valueRight; } } return filteredTable; }, [data, matchedLabels]); const styles = useStyles2(getStyles); const theme = useTheme2(); const [sort, setSort] = useState([{ displayName: "Self", desc: true }]); return /* @__PURE__ */ jsx("div", { className: styles.topTableContainer, "data-testid": "topTable", children: /* @__PURE__ */ jsx(AutoSizer, { style: { width: "100%" }, children: ({ width, height }) => { if (width < 3 || height < 3) { return null; } const frame = buildTableDataFrame( data, table, width, onSymbolClick, onSearch, onSandwich, theme, colorScheme, search, sandwichItem ); return /* @__PURE__ */ jsx( Table, { initialSortBy: sort, onSortByChange: (s) => { if (s && s.length) { onTableSort == null ? void 0 : onTableSort(s[0].displayName + "_" + (s[0].desc ? "desc" : "asc")); } setSort(s); }, data: frame, width, height } ); } }) }); } ); FlameGraphTopTableContainer.displayName = "FlameGraphTopTableContainer"; function buildTableDataFrame(data, table, width, onSymbolClick, onSearch, onSandwich, theme, colorScheme, search, sandwichItem) { const actionField = createActionField(onSandwich, onSearch, search, sandwichItem); const symbolField = { type: FieldType.string, name: "Symbol", values: [], config: { custom: { width: width - actionColumnWidth - TOP_TABLE_COLUMN_WIDTH * 2 }, links: [ { title: "Highlight symbol", url: "", onClick: (e) => { const field = e.origin.field; const value = field.values[e.origin.rowIndex]; onSymbolClick(value); } } ] } }; let frame; if (data.isDiffFlamegraph()) { symbolField.config.custom.width = width - actionColumnWidth - TOP_TABLE_COLUMN_WIDTH * 3; const baselineField = createNumberField("Baseline", "percent"); const comparisonField = createNumberField("Comparison", "percent"); const diffField = createNumberField("Diff", "percent"); diffField.config.custom.cellOptions.type = TableCellDisplayMode.ColorText; const [removeColor, addColor] = colorScheme === ColorSchemeDiff.DiffColorBlind ? [diffColorBlindColors[0], diffColorBlindColors[2]] : [diffDefaultColors[0], diffDefaultColors[2]]; diffField.config.mappings = [ { type: MappingType.ValueToText, options: { [Infinity]: { text: "new", color: addColor } } }, { type: MappingType.ValueToText, options: { [-100]: { text: "removed", color: removeColor } } }, { type: MappingType.RangeToText, options: { from: 0, to: Infinity, result: { color: addColor } } }, { type: MappingType.RangeToText, options: { from: -Infinity, to: 0, result: { color: removeColor } } } ]; const levels = data.getLevels(); const totalTicks = levels.length ? levels[0][0].value : 0; const totalTicksRight = levels.length ? levels[0][0].valueRight : void 0; for (let key in table) { actionField.values.push(null); symbolField.values.push(key); const ticksLeft = table[key].total; const ticksRight = table[key].totalRight; const totalTicksLeft = totalTicks - totalTicksRight; const percentageLeft = Math.round(1e4 * ticksLeft / totalTicksLeft) / 100; const percentageRight = Math.round(1e4 * ticksRight / totalTicksRight) / 100; const diff = (percentageRight - percentageLeft) / percentageLeft * 100; diffField.values.push(diff); baselineField.values.push(percentageLeft); comparisonField.values.push(percentageRight); } frame = { fields: [actionField, symbolField, baselineField, comparisonField, diffField], length: symbolField.values.length }; } else { const selfField = createNumberField("Self", data.selfField.config.unit); const totalField = createNumberField("Total", data.valueField.config.unit); for (let key in table) { actionField.values.push(null); symbolField.values.push(key); selfField.values.push(table[key].self); totalField.values.push(table[key].total); } frame = { fields: [actionField, symbolField, selfField, totalField], length: symbolField.values.length }; } const dataFrames = applyFieldOverrides({ data: [frame], fieldConfig: { defaults: {}, overrides: [] }, replaceVariables: (value) => value, theme }); return dataFrames[0]; } function createNumberField(name, unit) { const tableFieldOptions = { width: TOP_TABLE_COLUMN_WIDTH, align: "auto", inspect: false, cellOptions: { type: TableCellDisplayMode.Auto } }; return { type: FieldType.number, name, values: [], config: { unit, custom: tableFieldOptions } }; } const actionColumnWidth = 61; function createActionField(onSandwich, onSearch, search, sandwichItem) { const options = { type: TableCellDisplayMode.Custom, cellComponent: (props) => { return /* @__PURE__ */ jsx( ActionCell, { frame: props.frame, onSandwich, onSearch, search, sandwichItem, rowIndex: props.rowIndex } ); } }; const actionFieldTableConfig = { filterable: false, width: actionColumnWidth, hideHeader: true, inspect: false, align: "auto", cellOptions: options }; return { type: FieldType.number, name: "actions", values: [], config: { custom: actionFieldTableConfig } }; } function ActionCell(props) { var _a; const styles = getStylesActionCell(); const symbol = (_a = props.frame.fields.find((f) => f.name === "Symbol")) == null ? void 0 : _a.values[props.rowIndex]; const isSearched = props.search === symbol; const isSandwiched = props.sandwichItem === symbol; return /* @__PURE__ */ jsxs("div", { className: styles.actionCellWrapper, children: [ /* @__PURE__ */ jsx( IconButton, { className: styles.actionCellButton, name: "search", variant: isSearched ? "primary" : "secondary", tooltip: isSearched ? "Clear from search" : "Search for symbol", "aria-label": isSearched ? "Clear from search" : "Search for symbol", onClick: () => { props.onSearch(isSearched ? "" : symbol); } } ), /* @__PURE__ */ jsx( IconButton, { className: styles.actionCellButton, name: "gf-show-context", tooltip: isSandwiched ? "Remove from sandwich view" : "Show in sandwich view", variant: isSandwiched ? "primary" : "secondary", "aria-label": isSandwiched ? "Remove from sandwich view" : "Show in sandwich view", onClick: () => { props.onSandwich(isSandwiched ? void 0 : symbol); } } ) ] }); } const getStyles = (theme) => { return { topTableContainer: css({ label: "topTableContainer", padding: theme.spacing(1), backgroundColor: theme.colors.background.secondary, height: "100%" }) }; }; const getStylesActionCell = () => { return { actionCellWrapper: css({ label: "actionCellWrapper", display: "flex", height: "24px" }), actionCellButton: css({ label: "actionCellButton", marginRight: 0, width: "24px" }) }; }; export { FlameGraphTopTableContainer as default }; //# sourceMappingURL=FlameGraphTopTableContainer.mjs.map