@grafana/flamegraph
Version:
Grafana flamegraph visualization component
288 lines (281 loc) • 9.91 kB
JavaScript
'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