UNPKG

@grafana/flamegraph

Version:

Grafana flamegraph visualization component

288 lines (281 loc) • 9.91 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var jsxRuntime = require('react/jsx-runtime'); var css = require('@emotion/css'); var react = require('react'); var AutoSizer = require('react-virtualized-auto-sizer'); var data = require('@grafana/data'); var ui = require('@grafana/ui'); var colors = require('../FlameGraph/colors.cjs'); var constants = require('../constants.cjs'); var types = require('../types.cjs'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var AutoSizer__default = /*#__PURE__*/_interopDefaultCompat(AutoSizer); "use strict"; const FlameGraphTopTableContainer = react.memo( ({ data, onSymbolClick, search, matchedLabels, onSearch, sandwichItem, onSandwich, onTableSort, colorScheme }) => { const table = react.useMemo(() => buildFilteredTable(data, matchedLabels), [data, matchedLabels]); const styles = ui.useStyles2(getStyles); const theme = ui.useTheme2(); const [sort, setSort] = react.useState([{ displayName: "Self", desc: true }]); return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.topTableContainer, "data-testid": "topTable", children: /* @__PURE__ */ jsxRuntime.jsx(AutoSizer__default.default, { 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__ */ jsxRuntime.jsx( ui.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 buildFilteredTable(data, matchedLabels) { let filteredTable = /* @__PURE__ */ Object.create(null); const callStack = []; 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); const level = data.getLevel(i); while (callStack.length > level) { callStack.pop(); } const isRecursive = callStack.some((entry) => entry === label); if (!matchedLabels || matchedLabels.has(label)) { filteredTable[label] = filteredTable[label] || {}; filteredTable[label].self = filteredTable[label].self ? filteredTable[label].self + self : self; if (!isRecursive) { filteredTable[label].total = filteredTable[label].total ? filteredTable[label].total + value : value; filteredTable[label].totalRight = filteredTable[label].totalRight ? filteredTable[label].totalRight + valueRight : valueRight; } } callStack.push(label); } return filteredTable; } function buildTableDataFrame(data$1, table, width, onSymbolClick, onSearch, onSandwich, theme, colorScheme, search, sandwichItem) { const actionField = createActionField(onSandwich, onSearch, search, sandwichItem); const symbolField = { type: data.FieldType.string, name: "Symbol", values: [], config: { custom: { width: width - actionColumnWidth - constants.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$1.isDiffFlamegraph()) { symbolField.config.custom.width = width - actionColumnWidth - constants.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 = ui.TableCellDisplayMode.ColorText; const [removeColor, addColor] = colorScheme === types.ColorSchemeDiff.DiffColorBlind ? [colors.diffColorBlindColors[0], colors.diffColorBlindColors[2]] : [colors.diffDefaultColors[0], colors.diffDefaultColors[2]]; diffField.config.mappings = [ { type: data.MappingType.ValueToText, options: { [Infinity]: { text: "new", color: addColor } } }, { type: data.MappingType.ValueToText, options: { [-100]: { text: "removed", color: removeColor } } }, { type: data.MappingType.RangeToText, options: { from: 0, to: Infinity, result: { color: addColor } } }, { type: data.MappingType.RangeToText, options: { from: -Infinity, to: 0, result: { color: removeColor } } } ]; const levels = data$1.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$1.selfField.config.unit); const totalField = createNumberField("Total", data$1.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 = data.applyFieldOverrides({ data: [frame], fieldConfig: { defaults: {}, overrides: [] }, replaceVariables: (value) => value, theme }); return dataFrames[0]; } function createNumberField(name, unit) { const tableFieldOptions = { width: constants.TOP_TABLE_COLUMN_WIDTH, align: "auto", inspect: false, cellOptions: { type: ui.TableCellDisplayMode.Auto } }; return { type: data.FieldType.number, name, values: [], config: { unit, custom: tableFieldOptions } }; } const actionColumnWidth = 61; function createActionField(onSandwich, onSearch, search, sandwichItem) { const options = { type: ui.TableCellDisplayMode.Custom, cellComponent: (props) => { return /* @__PURE__ */ jsxRuntime.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: data.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 === `^${data.escapeStringForRegex(String(symbol))}$`; const isSandwiched = props.sandwichItem === symbol; return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.actionCellWrapper, children: [ /* @__PURE__ */ jsxRuntime.jsx( ui.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__ */ jsxRuntime.jsx( ui.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.css({ label: "topTableContainer", padding: theme.spacing(1), backgroundColor: theme.colors.background.secondary, height: "100%" }) }; }; const getStylesActionCell = () => { return { actionCellWrapper: css.css({ label: "actionCellWrapper", display: "flex", height: "24px" }), actionCellButton: css.css({ label: "actionCellButton", marginRight: 0, width: "24px" }) }; }; exports.buildFilteredTable = buildFilteredTable; exports.default = FlameGraphTopTableContainer; //# sourceMappingURL=FlameGraphTopTableContainer.cjs.map