Skip to content

Commit

Permalink
Top Table (#255)
Browse files Browse the repository at this point in the history
* Top table

* Col width

* Initial sort by

* Match top table height with flamegraph height

* Cleanup

* Yarn prettier

* Types

* useCallback so topTable does not keep rendering on tooltip hover

* Get units

* Extraction

* Min width to show top table

* Padding right

* Selected view

* Move top table to container

* Prettier

* Fix undefined

* react-table

* Styling

* Update tests and add tests for top table

* FlameGraphContainer tests for options

* Only add collapse container to explore

* Background container instead of collapse

* Update colors

* Self review

* Updates after merge

* Fix for backend tests

* Update how toptable/flamegraph/both is shown

* Update query -> search

* Default view selected

* Use df instead of levels for top table

* Search and selected view updates

* Reverse both to flamegraph click to reset search based on discussion

* Optimise prop

* Remove unwanted scroll to top
  • Loading branch information
joey-grafana authored Oct 3, 2022
1 parent ec02762 commit df6bc0a
Show file tree
Hide file tree
Showing 23 changed files with 977 additions and 133 deletions.
14 changes: 11 additions & 3 deletions grafana/fire-datasource/pkg/plugin/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ const START_OFFSET = 0
// Value or width of the bar
const VALUE_OFFSET = 1

// Self value of the bar, we don't use it at the moment but will add it to the metadata later.
// const SELF_OFFSET = 2
// Self value of the bar
const SELF_OFFSET = 2

// Index into the names array
const NAME_OFFSET = 3

Expand All @@ -121,6 +122,7 @@ const ITEM_OFFSET = 4
type ProfileTree struct {
Start int64
Value int64
Self int64
Level int
Name string
Nodes []*ProfileTree
Expand All @@ -132,6 +134,7 @@ func levelsToTree(levels []*querierv1.Level, names []string) *ProfileTree {
tree := &ProfileTree{
Start: 0,
Value: levels[0].Values[VALUE_OFFSET],
Self: levels[0].Values[SELF_OFFSET],
Level: 0,
Name: names[levels[0].Values[0]],
}
Expand Down Expand Up @@ -166,6 +169,7 @@ func levelsToTree(levels []*querierv1.Level, names []string) *ProfileTree {

itemStart := levels[currentLevel].Values[itemIndex+START_OFFSET] + offset
itemValue := levels[currentLevel].Values[itemIndex+VALUE_OFFSET]
selfValue := levels[currentLevel].Values[itemIndex+SELF_OFFSET]
itemEnd := itemStart + itemValue
parentEnd := currentParent.Start + currentParent.Value

Expand All @@ -174,6 +178,7 @@ func levelsToTree(levels []*querierv1.Level, names []string) *ProfileTree {
treeItem := &ProfileTree{
Start: itemStart,
Value: itemValue,
Self: selfValue,
Level: currentLevel,
Name: names[levels[currentLevel].Values[itemIndex+NAME_OFFSET]],
}
Expand Down Expand Up @@ -218,16 +223,19 @@ func treeToNestedSetDataFrame(tree *ProfileTree, profileTypeID string) *data.Fra

levelField := data.NewField("level", nil, []int64{})
valueField := data.NewField("value", nil, []int64{})
selfField := data.NewField("self", nil, []int64{})

// profileTypeID should encode the type of the profile with unit being the 3rd part
parts := strings.Split(profileTypeID, ":")
valueField.Config = &data.FieldConfig{Unit: normalizeUnit(parts[2])}
selfField.Config = &data.FieldConfig{Unit: normalizeUnit(parts[2])}
labelField := data.NewField("label", nil, []string{})
frame.Fields = data.Fields{levelField, valueField, labelField}
frame.Fields = data.Fields{levelField, valueField, selfField, labelField}

walkTree(tree, func(tree *ProfileTree) {
levelField.Append(int64(tree.Level))
valueField.Append(tree.Value)
selfField.Append(tree.Self)
labelField.Append(tree.Name)
})
return frame
Expand Down
30 changes: 15 additions & 15 deletions grafana/fire-datasource/pkg/plugin/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Test_query(t *testing.T) {
From: time.UnixMilli(10000),
To: time.UnixMilli(20000),
},
JSON: []byte(`{"profileTypeId":"foo:bar","labelSelector":"{app=\\\"baz\\\"}"}`),
JSON: []byte(`{"profileTypeId":"memory:alloc_objects:count:space:bytes","labelSelector":"{app=\\\"baz\\\"}"}`),
}

t.Run("query both", func(t *testing.T) {
Expand Down Expand Up @@ -65,20 +65,20 @@ func Test_profileToDataFrame(t *testing.T) {
Flamegraph: &querierv1.FlameGraph{
Names: []string{"func1", "func2", "func3"},
Levels: []*querierv1.Level{
{Values: []int64{0, 20, 0, 0}},
{Values: []int64{0, 10, 0, 1, 0, 5, 0, 2}},
{Values: []int64{0, 20, 1, 2}},
{Values: []int64{0, 10, 3, 1, 4, 5, 5, 2}},
},
Total: 987,
MaxSelf: 123,
},
},
}
frame := responseToDataFrames(resp, "memory:alloc_objects:count:space:bytes")
require.Equal(t, 3, len(frame.Fields))
require.Equal(t, 4, len(frame.Fields))
require.Equal(t, data.NewField("level", nil, []int64{0, 1, 1}), frame.Fields[0])
require.Equal(t, data.NewField("value", nil, []int64{20, 10, 5}), frame.Fields[1])
require.Equal(t, data.NewField("label", nil, []string{"func1", "func2", "func3"}), frame.Fields[2])
require.Equal(t, "memory:alloc_objects:count:space:bytes", frame.Meta.Custom.(CustomMeta).ProfileTypeID)
require.Equal(t, data.NewField("value", nil, []int64{20, 10, 5}).SetConfig(&data.FieldConfig{Unit: "short"}), frame.Fields[1])
require.Equal(t, data.NewField("self", nil, []int64{1, 3, 5}).SetConfig(&data.FieldConfig{Unit: "short"}), frame.Fields[2])
require.Equal(t, data.NewField("label", nil, []string{"func1", "func2", "func3"}), frame.Fields[3])
}

// This is where the tests for the datasource backend live.
Expand Down Expand Up @@ -131,12 +131,12 @@ func Test_levelsToTree(t *testing.T) {

func Test_treeToNestedDataFrame(t *testing.T) {
tree := &ProfileTree{
Start: 0, Value: 100, Level: 0, Name: "root", Nodes: []*ProfileTree{
Start: 0, Value: 100, Level: 0, Self: 1, Name: "root", Nodes: []*ProfileTree{
{
Start: 10, Value: 40, Level: 1, Name: "func1",
Start: 10, Value: 40, Level: 1, Self: 2, Name: "func1",
},
{Start: 60, Value: 30, Level: 1, Name: "func2", Nodes: []*ProfileTree{
{Start: 61, Value: 15, Level: 2, Name: "func1:func3"},
{Start: 60, Value: 30, Level: 1, Self: 3, Name: "func2", Nodes: []*ProfileTree{
{Start: 61, Value: 15, Level: 2, Self: 4, Name: "func1:func3"},
}},
},
}
Expand All @@ -145,10 +145,10 @@ func Test_treeToNestedDataFrame(t *testing.T) {
require.Equal(t,
[]*data.Field{
data.NewField("level", nil, []int64{0, 1, 1, 2}),
data.NewField("value", nil, []int64{100, 40, 30, 15}),
data.NewField("value", nil, []int64{100, 40, 30, 15}).SetConfig(&data.FieldConfig{Unit: "short"}),
data.NewField("self", nil, []int64{1, 2, 3, 4}).SetConfig(&data.FieldConfig{Unit: "short"}),
data.NewField("label", nil, []string{"root", "func1", "func2", "func1:func3"}),
}, frame.Fields)
require.Equal(t, "memory:alloc_objects:count:space:bytes", frame.Meta.Custom.(CustomMeta).ProfileTypeID)
}

func Test_seriesToDataFrame(t *testing.T) {
Expand All @@ -162,7 +162,7 @@ func Test_seriesToDataFrame(t *testing.T) {
frame := seriesToDataFrame(resp, "process_cpu:samples:count:cpu:nanoseconds")
require.Equal(t, 2, len(frame.Fields))
require.Equal(t, data.NewField("time", nil, []time.Time{time.UnixMilli(1000), time.UnixMilli(2000)}), frame.Fields[0])
require.Equal(t, data.NewField("samples", nil, []float64{30, 10}), frame.Fields[1])
require.Equal(t, data.NewField("samples", nil, []float64{30, 10}).SetConfig(&data.FieldConfig{Unit: "short"}), frame.Fields[1])

// with a label pair, the value field should name itself with a label pair name and not the profile type
resp = &connect.Response[querierv1.SelectSeriesResponse]{
Expand All @@ -173,7 +173,7 @@ func Test_seriesToDataFrame(t *testing.T) {
},
}
frame = seriesToDataFrame(resp, "process_cpu:samples:count:cpu:nanoseconds")
require.Equal(t, data.NewField("app", nil, []float64{30, 10}), frame.Fields[1])
require.Equal(t, data.NewField("app", nil, []float64{30, 10}).SetConfig(&data.FieldConfig{Unit: ""}), frame.Fields[1])
}

type FakeClient struct {
Expand Down
2 changes: 1 addition & 1 deletion grafana/flamegraph/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": ["@grafana/eslint-config"]
}
}
2 changes: 1 addition & 1 deletion grafana/flamegraph/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
...require('@grafana/toolkit/src/config/prettier.plugin.config.json'),
...require('@grafana/toolkit/src/config/prettier.plugin.config.json'),
};
2 changes: 1 addition & 1 deletion grafana/flamegraph/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# Flame graph panel
# Flame graph panel
9 changes: 8 additions & 1 deletion grafana/flamegraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"@testing-library/react": "12.1.4",
"@testing-library/user-event": "14.2.0",
"@types/lodash": "4.14.181",
"@types/react": "17.0.42",
"@types/react-table": "7.7.12",
"@types/react-virtualized-auto-sizer": "1.0.1",
"@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "^5.36.2",
"jest-canvas-mock": "2.4.0",
"prettier": "2.6.0",
Expand All @@ -35,7 +39,10 @@
"@grafana/ui": "9.1.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-use": "^17.4.0"
"react-table": "7.8.0",
"react-use": "^17.4.0",
"react-virtualized-auto-sizer": "1.0.6",
"react-window": "1.8.7"
},
"packageManager": "[email protected]"
}
20 changes: 11 additions & 9 deletions grafana/flamegraph/src/components/FlameGraph/FlameGraph.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { render } from '@testing-library/react';
import React, { useState } from 'react';

import FlameGraph from './FlameGraph';
import { SelectedView } from '../types';
import { data } from './testData/dataNestedSet';
import { MutableDataFrame } from '@grafana/data';
import { DataFrameView, MutableDataFrame } from '@grafana/data';
import 'jest-canvas-mock';
import { Item, nestedSetToLevels } from './dataTransform';

jest.mock('react-use', () => ({
useMeasure: () => {
Expand All @@ -19,25 +21,25 @@ describe('FlameGraph', () => {
const [topLevelIndex, setTopLevelIndex] = useState(0);
const [rangeMin, setRangeMin] = useState(0);
const [rangeMax, setRangeMax] = useState(1);
const [query] = useState('');
const [search] = useState('');
const [selectedView, _] = useState(SelectedView.Both);

const flameGraphData = new MutableDataFrame(data);
flameGraphData.meta = {
custom: {
ProfileTypeID: 'cpu:foo:bar',
},
};
const dataView = new DataFrameView<Item>(flameGraphData);
const levels = nestedSetToLevels(dataView);

return (
<FlameGraph
data={flameGraphData}
levels={levels}
topLevelIndex={topLevelIndex}
rangeMin={rangeMin}
rangeMax={rangeMax}
query={query}
search={search}
setTopLevelIndex={setTopLevelIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
selectedView={selectedView}
/>
);
};
Expand All @@ -49,7 +51,7 @@ describe('FlameGraph', () => {
it('should render correctly', async () => {
render(<FlameGraphWithProps />);

const canvas = screen.getByTestId('flamegraph') as HTMLCanvasElement;
const canvas = screen.getByTestId('flameGraph') as HTMLCanvasElement;
const ctx = canvas!.getContext('2d');
const calls = ctx!.__getDrawCalls();
expect(calls).toMatchSnapshot();
Expand Down
Loading

0 comments on commit df6bc0a

Please sign in to comment.