Skip to content

Commit

Permalink
Add @pyroscope/flamegraph and FlameGraph component
Browse files Browse the repository at this point in the history
  • Loading branch information
HyunSu1768 committed Aug 29, 2024
1 parent b31f6b7 commit 84a5598
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 35 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@iconify/react": "^4.1.1",
"@pyroscope/flamegraph": "^0.35.6",
"@tanstack/react-query": "^5.35.1",
"@types/plotly.js": "^2.33.3",
"@types/plotly.js-dist-min": "^2.3.4",
Expand Down
76 changes: 76 additions & 0 deletions src/components/Trace/FlameGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useMemo } from 'react';
import '@pyroscope/flamegraph/dist/index.css';
import { FlamegraphRenderer } from '@pyroscope/flamegraph';
import { SpansType, SpanType } from '@/utils/types/traceType.ts';

export const FlameGraph = ({ spans } : SpansType) => {
const flamegraphData = useMemo(() => convertTraceToFlamegraph(spans.spans), [spans]);

if (!flamegraphData) {
return <div>No valid flamegraph data available</div>;
}

return (
<FlamegraphRenderer
profile={flamegraphData}
onlyDisplay="flamegraph"
showToolbar={false}
showCredit={false}
/>
);
};

// 매개변수를 SpanType[] 타입으로 유지합니다.
function convertTraceToFlamegraph(spans: SpanType[]) {
if (spans.length === 0) return null;

const nameMap: { [key: string]: number } = {};
const levels: number[][] = [];

if(!spans[0]) return;

const traceStartTime = spans[0].start_time_unix_nano;
const traceEndTime = spans[0].end_time_unix_nano;
const traceDuration = traceEndTime - traceStartTime;

function addToNameMap(name: string): number {
if (nameMap[name] === undefined) {
nameMap[name] = Object.keys(nameMap).length;
}
return nameMap[name];
}

spans.forEach((span, index) => {
const startTime = span.start_time_unix_nano;
const endTime = span.end_time_unix_nano;
const duration = endTime - startTime;
const startOffset = startTime - traceStartTime;
const nameIndex = addToNameMap(span.name);

if (!levels[index]) levels[index] = [];
levels[index].push(startOffset, duration, 0, nameIndex);
});

const rootSpan = spans[0];
return {
version: 1,
flamebearer: {
names: Object.keys(nameMap),
levels,
numTicks: traceDuration,
maxSelf: Math.max(...levels.flat()),
},
metadata: {
appName: 'your-app-name',
name: `${rootSpan.name} Flamegraph`,
startTime: traceStartTime,
endTime: traceEndTime,
query: 'your-app-query',
maxNodes: 1024,
format: 'single' as const,
sampleRate: 1000000000,
spyName: 'opentelemetry' as const,
units: 'trace_samples' as const, // 'milliseconds' 대신 'samples' 사용
},
};
}
76 changes: 41 additions & 35 deletions src/components/Trace/TraceInformation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import styled from '@emotion/styled';
import { useResizable } from '@/hooks/useResizable';
import { getDetailTrace } from '@/utils/apis/trace';
import { SpanType } from '@/utils/types/traceType';
import { FlameGraph } from '@/components/Trace/FlameGraph.tsx';

type PropsType = {
selectedTrace: string | null;
setSelectedTrace: React.Dispatch<React.SetStateAction<string | null>>;
};

export const TraceInformation = ({ selectedTrace, setSelectedTrace }: PropsType) => {
const { width, boxRef, handleMouseDown } = useResizable(400, 300, window.innerWidth - 280);
const { width, boxRef, handleMouseDown } = useResizable(800, 300, window.innerWidth - 280);
const [data, setData] = useState<SpanType[]>();

console.log(data);

useEffect(() => {
if (selectedTrace) {
getDetailTrace(selectedTrace).then((res) => {
setData(res.data.spans);
setData(res.data);
});
}
}, [selectedTrace]);
Expand All @@ -32,56 +35,59 @@ export const TraceInformation = ({ selectedTrace, setSelectedTrace }: PropsType)
<h2>Trace View</h2>
<b onClick={() => setSelectedTrace(null)}>닫기</b>
</TitleContainer>
<div>
{selectedTrace && <FlameGraph spans={data}/>}
</div>
</Content>
)}
</Wrapper>
);
};

const Wrapper = styled.div<{ selectedTrace: string | null; width: number }>`
position: fixed;
top: 80px;
right: 0;
width: ${({ width }) => `${width}px`};
height: calc(100vh - 80px);
background-color: ${theme.color.gray1};
z-index: 1;
display: ${({ selectedTrace }) => (selectedTrace ? 'flex' : 'none')};
flex-direction: row;
position: fixed;
top: 80px;
right: 0;
width: ${({ width }) => `${width}px`};
height: calc(100vh - 80px);
background-color: ${theme.color.gray1};
z-index: 1;
display: ${({ selectedTrace }) => (selectedTrace ? 'flex' : 'none')};
flex-direction: row;
`;

const Content = styled.div`
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
border-left: 1px solid ${theme.color.gray4};
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
border-left: 1px solid ${theme.color.gray4};
`;

const Resizer = styled.div`
width: 4px;
height: 100%;
background-color: rgba(0, 0, 0, 0);
cursor: ew-resize;
transition: 0.2s linear;
:hover {
background-color: ${theme.color.gray3};
}
width: 4px;
height: 100%;
background-color: rgba(0, 0, 0, 0);
cursor: ew-resize;
transition: 0.2s linear;
:hover {
background-color: ${theme.color.gray3};
}
`;

const TitleContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
> b {
color: red;
cursor: pointer;
}
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
> b {
color: red;
cursor: pointer;
}
`;

const ColorBox = styled.div`
width: 100%;
height: 14px;
background-color: ${theme.color.main};
width: 100%;
height: 14px;
background-color: ${theme.color.main};
`;
4 changes: 4 additions & 0 deletions src/utils/types/traceType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ export type SpanType = {
[key: string]: string;
};
};

export type SpansType = {
spans: SpanType[]
}

0 comments on commit 84a5598

Please sign in to comment.