By Zongzheng Xi, from Alibaba Cloud Storage Team
>> How is the Flame Graph Created? Exploring Flame Graphs in Pyroscope Source Code (1)
The version of the Pyroscope source code described in this article is v0.35.1. Source code analysis mainly focuses on the flame graph part (/packages/pyroscope-flamegraph) and the model definition part (/packages/pyroscope-models), which is not involved on the collection side for the time being.
--- src
|--- convert -> Some tools, conversion methods, including diff two profiles, flamebearer conversion method to tree, etc.
|--- fitMode -> The in-row sorting mode of each row of the table with the flame graph. There are two types: Head First and Tail First.
|--- FlameGraph -> Flame graph main folder
| |--- FlameGraphComponent -> Flame graph component main folder
| | |--- ...
| | |--- color.ts -> Flame graph color hashing strategy and Diff linear gradient color matching logic
| | |--- colorPalette.ts -> Flame graph color palette
| | |--- constants.ts -> Flame graph canvas displays global configurations, such as the width of each bar and the spacing between them.
| | |--- ContextMenu.tsx -> Menu component popped up by right-click flame graph.
| | |--- ContextMenuHighlight.tsx -> Provides highlighting effect for bar in the right-clicked point (called out ContextMenu) in the flame graph.
| | |--- DiffLegend.tsx -> Color bar component in the middle of the color wheel configuration (default and color-blind mode) of the flame map Diff.
| | |--- DiffLegendPaletteDropdown.tsx -> Drop-down box component of the palette configuration of the flame graph Diff.
| | |--- Flamegraph_render.ts -> Flame graph core drawing rendering code, based on flamebarear, including canvas drawing logic, zoom logic, collapse logic, and highlight linkage logic.
| | |--- Flamegraph.ts -> Flame graph core class drives Flamegraph_render, includes all component operation logic implementations, adapts to the binary search (xyToBarIndex) of the flameborder data structure in the call stack, and implements a data controllable component that closely cooperates with canvas.
| | |--- Header.tsx -> Flame graph title component, which mainly changes title according to unit, and displays DiffLegendPaletteDropdown if it is Diff.
| | |--- Highlight.tsx -> Flame graph highlight polish, set EventListener for canvas to listen for mouse events and add/remove flame graph bar highlight.
| | |--- index.tsx -> Flame graph entry component, which connects other related components (such as ContextMenu, Tooltip, and Header), encapsulates the xyToData logic for calling FlameGraph, and passes it to other sub-components.
| | |--- LogoLink.tsx -> svg logo component of Pyroscope
| | |--- murmur3.ts -> MurmurHash3 hash algorithm, which can map data of any length to a fixed-length hash value for similar color display of similar call stack layers in flame graphs.
| | |--- testData.ts -> Data format of flame graph examples, including SimpleTree, ComplexTree, and DiffTree
| | |--- utils.ts -> Auxiliary method for calculating the ratio of the two parts when calculating Diff.
| | |--- viewTypes.ts -> The display mode of the flame graph, including 'flamegraph' | 'both' | 'table' | 'sandwih'.
| |--- decode.ts -> Execute the decode method when the flame graph is mounted/changed/covert, and perform secondary operation on the level of the original data structure.
| |--- FlameGraphRenderer.tsx -> Full-function entry of flame graph, including Toolbar and three forms (table, flame graph, sandwich).
|--- format -> Unit formatting tool folder
| |--- format.ts -> Formatter of different units is different, roughly divided into Duration, Objects, Bytes, and Nanoseconds. It is used in Tooltips and tables.
|--- Tooltip -> Box that appears when hovering.
| |--- ...
| |--- FlamegraphTooltip.tsx -> Tooltip component used for flame graph, obtains bar data through xyToData, and displays it through Formatter.
| |--- TableTooltip.tsx -> Tooltip widget used for tables, which obtains data by using the data callback method of the table and displays it by using the Formatter.
| |--- Tooltip.tsx -> Tooltip component is implemented, and the baselineDataExpression is used to determine whether the hover type is a flame graph or a table.
|--
- FlamegraphRenderer.tsx -> Wrapper for FlameGraph/FlameGraphRenderer.tsx.
|--- index.tsx -> Expose components such as Flamegraph.
|--- ProfilerTable.tsx -> Implementation of the flame graph table, including singleRow and DoubleRow (Diff view).
|--- search.ts -> Search tool class, which determines whether the bar name is the same as the search content.
|--- SharedQueryInput.tsx -> Search box function implementation
|--- Toolbar.tsx -> Flame graph controls bar implementation. You can switch views and sorting, etc.
--- src
|--- decode.ts -> TODO: Ideally, this should be moved to the FlamegraphRenderer component. Now it requires too many changes.
|--- flamebearer.ts -> The data structure of the flame graph in the old version.
|--- groups.ts -> Flame graph main folder
|--- index.ts -> Expose index file.
|--- profile.ts -> The new version of the flame graph data structure (the essence is the same and the new version is driven by zod).
|--- spyName.ts -> SPY constants and data structure definitions in different languages.
|--- trace.ts -> Trace-related schema definition
|--- units.ts -> Unit-related constants and data structure definitions
const SimpleTree = {
version: 1,
flamebearer: {
names: [
'total',
'runtime.mcall',
'runtime.park_m',
'runtime.schedule',
'runtime.resetspinning',
'runtime.wakep',
'runtime.startm',
'runtime.notewakeup',
'runtime.semawakeup',
'runtime.pthread_cond_signal',
'runtime.findrunnable',
'runtime.netpoll',
'runtime.kevent',
'runtime.main',
'main.main',
'github.com/pyroscope-io/client/pyroscope.TagWrapper',
'runtime/pprof.Do',
'github.com/pyroscope-io/client/pyroscope.TagWrapper.func1',
'main.main.func1',
'main.slowFunction',
'main.slowFunction.func1',
'main.work',
'runtime.asyncPreempt',
'main.fastFunction',
'main.fastFunction.func1',
],
levels: [
[0, 609, 0, 0],
[0, 606, 0, 13, 0, 3, 0, 1],
[0, 606, 0, 14, 0, 3, 0, 2],
[0, 606, 0, 15, 0, 3, 0, 3],
[0, 606, 0, 16, 0, 1, 0, 10, 0, 2, 0, 4],
[0, 606, 0, 17, 0, 1, 0, 11, 0, 2, 0, 5],
[0, 606, 0, 18, 0, 1, 1, 12, 0, 2, 0, 6],
[0, 100, 0, 23, 0, 506, 0, 19, 1, 2, 0, 7],
[0, 100, 0, 15, 0, 506, 0, 16, 1, 2, 0, 8],
[0, 100, 0, 16, 0, 506, 0, 20, 1, 2, 2, 9],
[0, 100, 0, 17, 0, 506, 493, 21],
[0, 100, 0, 24, 493, 13, 13, 22],
[0, 100, 97, 21],
[97, 3, 3, 22],
],
numTicks: 609,
maxSelf: 493,
},
metadata: {
appName: 'simple.golang.app.cpu',
name: 'simple.golang.app.cpu 2022-09-06T12:16:31Z',
startTime: 1662466591,
endTime: 1662470191,
query: 'simple.golang.app.cpu{}',
maxNodes: 1024,
format: 'single' as const,
sampleRate: 100,
spyName: 'gospy' as const,
units: 'samples' as const,
}
};
The data structure is the sample data on the pyroscope github, which means the data structure passed into the flame graph component. The example renders the following effect.
Most of the content in the data is easy to understand, as implied by the names. The key is what names
and levels
mean. This part can be inferred from models/flamebearer.ts
in the source code.
The levels
is a data structure in the shape of a flame graph. It is a two-dimensional array. Each row corresponds to each row in the flame graph. In each row, the four numbers of single-type flame graphs describe a bar. For example, the first row has one bar, and the second row has two bars. Among the four numbers describing the bar, the first column represents offset, which represents the distance from the previous bar to be vacated in the current row. The second column represents the total length of this bar. The third column represents the exclusive (self) length of this bar, which means the sum of the (possibly multi-segment) lengths occupied by itself after all the sub-call stacks of this bar are removed. The fourth column indicates the index of the name array corresponding to the name above the bar.
The flame graph in Diff format is similar to that in Single format, but changes from a group of 4 to a group of 7 values. An example set of levels is as follows:
"levels": [
[0, 20464695, 0, 0, 22639351, 0, 0],
[
0, 1573488, 0, 0, 0, 0, 1, 0, 524336, 0, 0, 524336, 0, 2, 0, 1049728, 0,
0, 524864, 0, 3, 0, 3149185, 0, 0, 3674049, 0, 4, 0, 13643094, 0, 0,
17391238, 0, 5, 0, 524864, 0, 0, 524864, 0, 6
],
[
0, 1573488, 0, 0, 0, 0, 7, 0, 524336, 524336, 0, 524336, 524336, 8, 0,
1049728, 0, 0, 524864, 0, 9, 0, 3149185, 0, 0, 3674049, 0, 10, 0,
13643094, 0, 0, 17391238, 0, 11, 0, 524864, 0, 0, 524864, 0, 12
],
[
0, 1573488, 0, 0, 0, 0, 13, 524336, 1049728, 0, 524336, 524864, 0, 14,
0, 3149185, 0, 0, 3674049, 0, 15, 0, 524361, 524361, 0, 524360, 524360,
16, 0, 2146687, 0, 0, 5366719, 0, 17, 0, 528394, 528394, 0, 528394,
528394, 18, 0, 0, 0, 0, 524292, 0, 19, 0, 9387757, 0, 0, 9397105, 0, 20,
0, 525440, 0, 0, 525440, 0, 21, 0, 0, 0, 0, 524928, 0, 22, 0, 530455, 0,
0, 0, 0, 23, 0, 524864, 0, 0, 524864, 0, 24
],
[
0, 1573488, 1573488, 0, 0, 0, 25, 524336, 1049728, 0, 524336, 524864, 0,
26, 0, 3149185, 0, 0, 3674049, 0, 14, 524361, 2146687, 0, 524360,
5366719, 0, 27, 528394, 0, 0, 528394, 524292, 0, 28, 0, 9387757, 695248,
0, 9397105, 695248, 29, 0, 525440, 0, 0, 525440, 0, 30, 0, 0, 0, 0,
524928, 0, 31, 0, 530455, 0, 0, 0, 0, 32, 0, 524864, 0, 0, 524864, 0, 33
],
[
2097824, 1049728, 0, 524336, 524864, 0, 34, 0, 3149185, 0, 0, 3674049,
0, 26, 524361, 2146687, 0, 524360, 5366719, 0, 35, 528394, 0, 0, 528394,
524292, 0, 36, 695248, 8692509, 789507, 695248, 8701857, 526338, 37, 0,
525440, 0, 0, 525440, 0, 38, 0, 0, 0, 0, 524928, 0, 39, 0, 530455, 0, 0,
0, 0, 40, 0, 524864, 0, 0, 524864, 0, 14
],
[
2097824, 1049728, 0, 524336, 524864, 0, 41, 0, 3149185, 0, 0, 3674049,
0, 34, 524361, 2146687, 0, 524360, 5366719, 0, 42, 528394, 0, 0, 528394,
524292, 0, 43, 1484755, 7903001, 7903001, 1221586, 8175519, 8175519, 44,
0, 525440, 525440, 0, 525440, 525440, 45, 0, 0, 0, 0, 524928, 0, 46, 0,
530455, 0, 0, 0, 0, 47, 0, 524864, 0, 0, 524864, 0, 48
],
[
2097824, 1049728, 0, 524336, 524864, 0, 49, 0, 3149185, 0, 0, 3674049,
0, 41, 524361, 2146687, 0, 524360, 5366719, 0, 50, 528394, 0, 0, 528394,
524292, 0, 51, 9913197, 0, 0, 9922545, 524928, 0, 52, 0, 530455, 0, 0,
0, 0, 53, 0, 524864, 0, 0, 524864, 0, 54
],
[
2097824, 1049728, 1049728, 524336, 524864, 524864, 55, 0, 3149185, 0, 0,
3674049, 0, 49, 524361, 2146687, 2146687, 524360, 5366719, 5366719, 56,
528394, 0, 0, 528394, 524292, 524292, 57, 9913197, 0, 0, 9922545,
524928, 0, 58, 0, 530455, 530455, 0, 0, 0, 59, 0, 524864, 0, 0, 524864,
0, 41
],
[
3147552, 3149185, 3149185, 1049200, 3674049, 3674049, 55, 13112639, 0,
0, 16866310, 524928, 0, 60, 530455, 524864, 0, 0, 524864, 0, 49
],
[
19409376, 0, 0, 21589559, 524928, 524928, 61, 530455, 524864, 524864, 0,
524864, 524864, 55
]
]
The specific meanings of the seven values in a group are as follows.
Number of Digits | Description | Combination Calculation |
0 | leftOffset |
barOffset = level[0] + level[3] barTotal = level[1] + level[4] barTotalDiff = level[4] - level[1] barSelf = level[2] + level[5] barSelfDiff = level[5] - level[2]
|
1 | barLeftTotal | |
2 | leftSelf | |
3 | rightOffset | |
4 | barRightTotal | |
5 | rightSelf | |
6 | name_index |
export type Flamebearer = {
/**
* List of names
*/
names: string[];
/**
* List of level
*
* This is NOT the same as in the flamebearer
* that we receive from the server.
* As in there are some transformations required
* (see deltaDiffWrapper)
*/
levels: number[][];
numTicks: number;
maxSelf: number;
/**
* Sample Rate, used in text information
*/
sampleRate: number;
units: Units;
spyName: SpyName;
// format: 'double' | 'single';
// leftTicks?: number;
// rightTicks?: number;
} & addTicks;
export type addTicks =
| { format: 'double'; leftTicks: number; rightTicks: number }
| { format: 'single' };
export const singleFF = {
format: 'single' as const,
jStep: 4,
jName: 3,
getBarOffset: (level: number[], j: number) => level[j],
getBarTotal: (level: number[], j: number) => level[j + 1],
getBarTotalDiff: (level: number[], j: number) => 0,
getBarSelf: (level: number[], j: number) => level[j + 2],
getBarSelfDiff: (level: number[], j: number) => 0,
getBarName: (level: number[], j: number) => level[j + 3],
};
export const doubleFF = {
format: 'double' as const,
jStep: 7,
jName: 6,
getBarOffset: (level: number[], j: number) => level[j] + level[j + 3],
getBarTotal: (level: number[], j: number) => level[j + 4] + level[j + 1],
getBarTotalLeft: (level: number[], j: number) => level[j + 1],
getBarTotalRght: (level: number[], j: number) => level[j + 4],
getBarTotalDiff: (level: number[], j: number) => {
return level[j + 4] - level[j + 1];
},
getBarSelf: (level: number[], j: number) => level[j + 5] + level[j + 2],
getBarSelfLeft: (level: number[], j: number) => level[j + 2],
getBarSelfRght: (level: number[], j: number) => level[j + 5],
getBarSelfDiff: (level: number[], j: number) => level[j + 5] - level[j + 2],
getBarName: (level: number[], j: number) => level[j + 6],
};
Maybe (from true-myth library) is widely used in pyroscope's data model. The problems solved by the model and the common writing methods are briefly described here. For more usage methods and details, see https://true-myth.js.org/.
Maybe mainly addresses the null/undefined problem. It provides a consistent and defined approach to handle null/undefined throughout the codebase. By encapsulating values in containers, regardless of whether they contain something, operations can be safely executed. These containers allow us to make reliable assumptions about parameter values when writing functions, as we extract the question "does this variable contain a valid value?" to the API boundary, eliminating the need to handle this concern in each function.
I believe that Pyroscope's use of methods like Maybe to drive xyToData demonstrates a consideration for code cleanliness and maintainability.
Let's denote A as the Maybe<T>
type value that may or may not exist. If the value exists, it is represented as Just(value)
. If it doesn't exist, it is Nothing
, offering a type-safe container to handle the possibility of null values. Using Maybe allows you to avoid null/undefined checks in your codebase and use it like a worry-free array. TypeScript checks the behavior of this type at compile time, with minimal runtime overhead, mainly due to the small cost of container objects and lightweight wrapping/unwrapping functionality. In terms of usage, Maybe follows a method call rule.
import Maybe from 'true-myth/maybe';
// Construct a `Just` where you have a value to use, and the function accepts
// a `Maybe`.
const aKnownNumber = Maybe.just(12);
// Construct a `Nothing` where you don't have a value to use, but the
// function requires a value (and accepts a `Maybe`).
const aKnownNothing = Maybe.nothing<string>();
// Construct a `Maybe` where you don't know whether the value will exist or
// not, using `of`.
type WhoKnows = { mightBeAThing?: boolean[] };
const whoKnows: WhoKnows = {};
const wrappedWhoKnows = Maybe.of(whoKnows.mightBeAThing);
console.log(toString(wrappedWhoKnows)); // Nothing
const whoElseKnows: WhoKnows = { mightBeAThing: [true, false] };
const wrappedWhoElseKnows = Maybe.of(whoElseKnows.mightBeAThing);
console.log(toString(wrappedWhoElseKnows)); // "Just(true,false)"
import { isVoid } from 'true-myth/utils';
import Maybe, { Just, Nothing } from 'true-myth/maybe';
// Construct a `Just` where you have a value to use, and the function accepts
// a `Maybe`.
const aKnownNumber = new Just(12);
// Once the item is constructed, you can apply methods directly on it.
const fromMappedJust = aKnownNumber.map((x) => x * 2).unwrapOr(0);
console.log(fromMappedJust); // 24
// Construct a `Nothing` where you don't have a value to use, but the
// function requires a value (and accepts a `Maybe<string>`).
const aKnownNothing = new Nothing();
// The same operations will behave safely on a `Nothing` as on a `Just`:
const fromMappedNothing = aKnownNothing.map((x) => x * 2).unwrapOr(0);
console.log(fromMappedNothing); // 0
// Construct a `Maybe` where you don't know whether the value will exist or
// not, using `isVoid` to decide which to construct.
type WhoKnows = { mightBeAThing?: boolean[] };
const whoKnows: WhoKnows = {};
const wrappedWhoKnows = !isVoid(whoKnows.mightBeAThing)
? new Just(whoKnows.mightBeAThing)
: new Nothing();
console.log(wrappedWhoKnows.toString()); // Nothing
const whoElseKnows: WhoKnows = { mightBeAThing: [true, false] };
const wrappedWhoElseKnows = !isVoid(whoElseKnows.mightBeAThing)
? new Just(whoElseKnows.mightBeAThing)
: new Nothing();
console.log(wrappedWhoElseKnows.toString()); // "Just(true,false)"
1. bar: Describes one "bar" in the flame graph. The same line may contain multiple "bars".
2. Node: Refers to a reference to the bar in the flame graph. The data structure is {i, j}
, which is the index of the flame graph.
3. XYWithinBounds: Indicates the XY coordinates within the canvas range. The data structure is {x, y}
, which is the XY of MouseEvent.
4. this.zoom: The node zoomed in in the current state. Zooming in means the effect of the operation of clicking the flame map with the left button.
5. this.focusedNode: The node that is focused on in the current state. Right-click Collapsed Nodes Above.
Start by clicking
The OnClick event bound to the canvas is as follows.
const onClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
const opt = getFlamegraph().xyToBar(
e.nativeEvent.offsetX,
e.nativeEvent.offsetY
);
// opt is the Maybe<XYWithinBounds> object that is retrieved based on the xy position. Then, a bar that contains index, position, and data is constructed based on a series of xyToData methods (described in detail later).
opt.match({
// Click on an unreasonable position in canvas and do nothing.
Nothing: () => {},
// If the position is reasonable, retrieve the bar data (including index, position, and data).
Just: (bar) => {
// Zoom is the currently enlarged Maybe<Node> object (not necessarily opt). Zoom means the effect of the operation of clicking the flame graph with the left button.
zoom.match({
// If no zoom exists, zoom is executed at the current position (opt).
Nothing: () => {
onZoom(opt);
},
// If there is currently a node z that has been zoomed in, take out the bar data.
Just: (z) => {
// Determine whether opt and zoom are the same index.
if (bar.i === z.i && bar.j === z.j) {
// If yes, the cancellation is amplified and restored.
onZoom(Maybe.nothing());
} else {
// If no, perform the zoom operation on the currently clicked opt.
onZoom(opt);
}
},
});
},
});
};
The xyToIndex
method is the core method of i, j
of the mouse screen x, y
into the data structure.
private xyToBarIndex(x: number, y: number) {
if (x < 0 || y < 0) {
throw new Error(`x and y must be bigger than 0. x = ${x}, y = ${y}`);
}
// It means that if the bar on the top in focused mode is clicked, or if the Total in non-focused mode is clicked, { i: 0, j: 0 } is returned.
if (this.isFocused() && y <= BAR_HEIGHT) {
return { i: 0, j: 0 };
}
// When performing collapse (focusing operation), there will be a virtual collapsed node at the top. We need to reduce it here.
const computedY = this.isFocused() ? y - BAR_HEIGHT : y;
const compensatedFocusedY = this.focusedNode.mapOrElse(
() => 0,
(node) => {
return node.i <= 0 ? 0 : node.i;
}
);
// Think of it as a set of if-else.
const compensation = this.zoom.match({
Just: () => {
return this.focusedNode.match({
Just: () => {
// There are focus and zoom, mainly focus.
return compensatedFocusedY;
},
Nothing: () => {
// Only zoom
return 0;
},
});
},
Nothing: () => {
return this.focusedNode.match({
Just: () => {
// Only focus
return compensatedFocusedY;
},
Nothing: () => {
// Neither focus nor zoom
return 0;
},
});
},
});
// You can locate the position of i based on the preceding information.
const i = Math.floor(computedY / PX_PER_LEVEL) + compensation;
if (i >= 0 && i < this.flamebearer.levels.length) {
const level = this.flamebearer.levels[i];
if (!level) {
throw new Error(`Could not find level: '${i}'`);
}
// The location of j is found by using a binary search algorithm.
const j = this.binarySearchLevel(x, level);
return { i, j };
}
return { i: 0, j: 0 };
}
In the xyToIndex
method, the position of the flame is calculated by discussing the state classification of the i
graph, and then the binary search is carried out in the level
where i
is located to find j
.
// binary search of a block in a stack level
private binarySearchLevel(x: number, level: number[]) {
const { ff } = this;
let i = 0;
let j = level.length - ff.jStep;
while (i <= j) {
/* eslint-disable-next-line no-bitwise */
const m = ff.jStep * ((i / ff.jStep + j / ff.jStep) >> 1);
const x0 = this.tickToX(ff.getBarOffset(level, m));
const x1 = this.tickToX(
ff.getBarOffset(level, m) + ff.getBarTotal(level, m)
);
if (x0 <= x && x1 >= x) {
return x1 - x0 > COLLAPSE_THRESHOLD ? m : -1;
}
if (x0 > x) {
j = m - ff.jStep;
} else {
i = m + ff.jStep;
}
}
return -1;
}
The algorithm cleverly combines the characteristics of binary search and flame graph. Please note that the concept of i and j of binary search is distinguished from the concept of i and j of the flame graph. Here, the i and j of the binary search only represent the index of Array represented by a row in flame graph. The binary search is carried out on the Array, but the bar dimension is jumped through jStep
(Single jStep = 4; Diff jStep = 7), so that m
's landing point must be the start time of bar in the middle of i and j. After M is determined, the relevant bar information can be obtained through getBarTotal
and getBarOffset
described in the data structure, and then passed into tickToX
. The final result is the real x range of the intermediate bar, which is compared with the incoming x range. If it falls in the range, the j-index of bar is determined. Otherwise, the binary search method is continued.
tickToX
method will not be developed too much here. The judgment logic is complicated, but the judgment principle is similar to that of xyToIndex
. It is to classify and discuss zoom
and focusedNode
, determine the current Range (which may be changed by zoom and focus operations), and then determine the Px occupied by each Tick, which can be calculated.
With xyToIndex
capabilities, along with Maybe and data structures, you can easily expose the ability to get data.
private xyToBarPosition = (xy: XYWithinBounds) => {
const { ff } = this;
const { i, j } = this.xyToBarIndex(xy.x, xy.y);
const topLevel = this.focusedNode.mapOrElse(
() => 0,
(node) => (node.i < 0 ? 0 : node.i - 1)
);
const level = this.flamebearer.levels[i];
if (!level) {
throw new Error(`Could not find level: '${i}'`);
}
const posX = Math.max(this.tickToX(ff.getBarOffset(level, j)), 0);
// lower bound is 0
const posY = Math.max((i - topLevel) * PX_PER_LEVEL, 0);
const sw = Math.min(
this.tickToX(ff.getBarOffset(level, j) + ff.getBarTotal(level, j)) - posX,
this.getCanvasWidth()
);
return {
x: posX,
y: posY,
width: sw,
};
};
private xyToBarData = (xy: XYWithinBounds) => {
const { i, j } = this.xyToBarIndex(xy.x, xy.y);
const level = this.flamebearer.levels[i];
if (!level) {
throw new Error(`Could not find level: '${i}'`);
}
switch (this.flamebearer.format) {
case 'single': {
const ff = singleFF;
return {
format: 'single' as const,
name: this.flamebearer.names[ff.getBarName(level, j)],
self: ff.getBarSelf(level, j),
offset: ff.getBarOffset(level, j),
total: ff.getBarTotal(level, j),
};
}
case 'double': {
const ff = doubleFF;
return {
format: 'double' as const,
barTotal: ff.getBarTotal(level, j),
totalLeft: ff.getBarTotalLeft(level, j),
totalRight: ff.getBarTotalRght(level, j),
totalDiff: ff.getBarTotalDiff(level, j),
name: this.flamebearer.names[ff.getBarName(level, j)],
};
}
default: {
throw new Error(`Unsupported type`);
}
}
};
The logic is simple and will not be mentioned here.
The performance monitoring feature of Log Service is developed based on Apache 2.0, which is an open-source protocol of Pyroscope v0.35. It optimizes the ability of integrating log service feature.
Pyroscope v0.35.1 | SLS |
❌ProfileTable A large number of reRender Issues | ✅Performance optimization: The rendering performance of the flame graph is about 50% higher than that of the open-source version. |
❌You cannot select multiple tags in the same tag. The UI supports fewer tags. | ✅Logic optimization: Log service provides more flexible tag selection logic and supports not only the aggregation logic of SUM but also the profile aggregation logic of AVG. |
☑️ When the call stack is deep, the table display is lengthy, and the flame graph interaction capability is monotonous. | ✅Interaction optimization: Deep stack optimization, search integration, one-click diff, and flame graph interaction menus. |
❌No unified integration of related resources. | ✅Experience optimization: Integrates the highly interactive and open SLS dashboard ecosystem to provide more imagination. |
Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.
How is the Flame Graph Created? Exploring Flame Graphs in Pyroscope Source Code (1)
1,048 posts | 257 followers
FollowAlibaba Cloud Community - January 8, 2024
Alibaba Cloud Native - February 2, 2024
Alibaba Cloud Native - April 16, 2024
Alibaba Developer - August 18, 2020
Alibaba Cloud Native Community - May 9, 2023
Alibaba Cloud Native Community - April 26, 2024
1,048 posts | 257 followers
FollowPlan and optimize your storage budget with flexible storage services
Learn MoreA cost-effective, efficient and easy-to-manage hybrid cloud storage solution.
Learn MoreProvides scalable, distributed, and high-performance block storage and object storage services in a software-defined manner.
Learn MoreBuild a Data Lake with Alibaba Cloud Object Storage Service (OSS) with 99.9999999999% (12 9s) availability, 99.995% SLA, and high scalability
Learn MoreMore Posts by Alibaba Cloud Community