mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tempo: Embed flame graph in span details (#77537)
* Embed flame graph * Update test * Update test * Use toggle * Update test * Add tests * Use const * Cleanup * Update profile tag * Move flame graph out of tags, remove request and other cleanup + tests * Update test * Set flame graph by profile id and simplify logic * Cleanup and redrawListView * Create/use feature toggle
This commit is contained in:
@@ -3878,6 +3878,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Styles should be written using objects.", "4"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "5"]
|
||||
],
|
||||
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanFlameGraph.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/TextList.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
|
@@ -127,6 +127,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `grafanaAPIServerWithExperimentalAPIs` | Register experimental APIs with the k8s API server |
|
||||
| `featureToggleAdminPage` | Enable admin page for managing feature toggles from the Grafana front-end |
|
||||
| `traceToProfiles` | Enables linking between traces and profiles |
|
||||
| `tracesEmbeddedFlameGraph` | Enables embedding a flame graph in traces |
|
||||
| `permissionsFilterRemoveSubquery` | Alternative permission filter implementation that does not use subqueries for fetching the dashboard folder |
|
||||
| `influxdbSqlSupport` | Enable InfluxDB SQL query language support with new querying UI |
|
||||
| `angularDeprecationUI` | Display new Angular deprecation-related UI features |
|
||||
|
@@ -108,6 +108,7 @@ export interface FeatureToggles {
|
||||
awsAsyncQueryCaching?: boolean;
|
||||
splitScopes?: boolean;
|
||||
traceToProfiles?: boolean;
|
||||
tracesEmbeddedFlameGraph?: boolean;
|
||||
permissionsFilterRemoveSubquery?: boolean;
|
||||
prometheusConfigOverhaulAuth?: boolean;
|
||||
configurableSchedulerTick?: boolean;
|
||||
|
@@ -44,6 +44,7 @@ type Props = {
|
||||
onFocusPillClick: () => void;
|
||||
onSandwichPillClick: () => void;
|
||||
colorScheme: ColorScheme | ColorSchemeDiff;
|
||||
showFlameGraphOnly?: boolean;
|
||||
collapsing?: boolean;
|
||||
};
|
||||
|
||||
@@ -62,6 +63,7 @@ const FlameGraph = ({
|
||||
onFocusPillClick,
|
||||
onSandwichPillClick,
|
||||
colorScheme,
|
||||
showFlameGraphOnly,
|
||||
collapsing,
|
||||
}: Props) => {
|
||||
const styles = getStyles();
|
||||
@@ -117,6 +119,7 @@ const FlameGraph = ({
|
||||
totalProfileTicks,
|
||||
totalProfileTicksRight,
|
||||
totalViewTicks,
|
||||
showFlameGraphOnly,
|
||||
collapsedMap,
|
||||
setCollapsedMap,
|
||||
collapsing,
|
||||
|
@@ -32,6 +32,7 @@ type Props = {
|
||||
totalProfileTicks: number;
|
||||
totalProfileTicksRight?: number;
|
||||
totalViewTicks: number;
|
||||
showFlameGraphOnly?: boolean;
|
||||
|
||||
collapsedMap: CollapsedMap;
|
||||
setCollapsedMap: (collapsedMap: CollapsedMap) => void;
|
||||
@@ -56,6 +57,7 @@ const FlameGraphCanvas = ({
|
||||
root,
|
||||
direction,
|
||||
depth,
|
||||
showFlameGraphOnly,
|
||||
collapsedMap,
|
||||
setCollapsedMap,
|
||||
collapsing,
|
||||
@@ -182,7 +184,7 @@ const FlameGraphCanvas = ({
|
||||
totalTicks={totalViewTicks}
|
||||
collapseConfig={tooltipItem ? collapsedMap.get(tooltipItem) : undefined}
|
||||
/>
|
||||
{clickedItemData && (
|
||||
{!showFlameGraphOnly && clickedItemData && (
|
||||
<FlameGraphContextMenu
|
||||
itemData={clickedItemData}
|
||||
collapsing={collapsing}
|
||||
|
@@ -54,6 +54,11 @@ export type Props = {
|
||||
*/
|
||||
vertical?: boolean;
|
||||
|
||||
/**
|
||||
* If true only the flamegraph will be rendered.
|
||||
*/
|
||||
showFlameGraphOnly?: boolean;
|
||||
|
||||
/**
|
||||
* Disable behaviour where similar items in the same stack will be collapsed into single item.
|
||||
*/
|
||||
@@ -70,6 +75,7 @@ const FlameGraphContainer = ({
|
||||
stickyHeader,
|
||||
extraHeaderElements,
|
||||
vertical,
|
||||
showFlameGraphOnly,
|
||||
disableCollapsing,
|
||||
}: Props) => {
|
||||
const [focusedItemData, setFocusedItemData] = useState<ClickedItemData>();
|
||||
@@ -143,35 +149,37 @@ const FlameGraphContainer = ({
|
||||
// isn't already provided.
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<div ref={sizeRef} className={styles.container}>
|
||||
<FlameGraphHeader
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
selectedView={selectedView}
|
||||
setSelectedView={(view) => {
|
||||
setSelectedView(view);
|
||||
onViewSelected?.(view);
|
||||
}}
|
||||
containerWidth={containerWidth}
|
||||
onReset={() => {
|
||||
resetFocus();
|
||||
resetSandwich();
|
||||
}}
|
||||
textAlign={textAlign}
|
||||
onTextAlignChange={(align) => {
|
||||
setTextAlign(align);
|
||||
onTextAlignSelected?.(align);
|
||||
}}
|
||||
showResetButton={Boolean(focusedItemData || sandwichItem)}
|
||||
colorScheme={colorScheme}
|
||||
onColorSchemeChange={setColorScheme}
|
||||
stickyHeader={Boolean(stickyHeader)}
|
||||
extraHeaderElements={extraHeaderElements}
|
||||
vertical={vertical}
|
||||
isDiffMode={Boolean(dataContainer.isDiffFlamegraph())}
|
||||
/>
|
||||
{!showFlameGraphOnly && (
|
||||
<FlameGraphHeader
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
selectedView={selectedView}
|
||||
setSelectedView={(view) => {
|
||||
setSelectedView(view);
|
||||
onViewSelected?.(view);
|
||||
}}
|
||||
containerWidth={containerWidth}
|
||||
onReset={() => {
|
||||
resetFocus();
|
||||
resetSandwich();
|
||||
}}
|
||||
textAlign={textAlign}
|
||||
onTextAlignChange={(align) => {
|
||||
setTextAlign(align);
|
||||
onTextAlignSelected?.(align);
|
||||
}}
|
||||
showResetButton={Boolean(focusedItemData || sandwichItem)}
|
||||
colorScheme={colorScheme}
|
||||
onColorSchemeChange={setColorScheme}
|
||||
stickyHeader={Boolean(stickyHeader)}
|
||||
extraHeaderElements={extraHeaderElements}
|
||||
vertical={vertical}
|
||||
isDiffMode={Boolean(dataContainer.isDiffFlamegraph())}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={styles.body}>
|
||||
{selectedView !== SelectedView.FlameGraph && (
|
||||
{!showFlameGraphOnly && selectedView !== SelectedView.FlameGraph && (
|
||||
<FlameGraphTopTableContainer
|
||||
data={dataContainer}
|
||||
onSymbolClick={onSymbolClick}
|
||||
@@ -204,6 +212,7 @@ const FlameGraphContainer = ({
|
||||
onFocusPillClick={resetFocus}
|
||||
onSandwichPillClick={resetSandwich}
|
||||
colorScheme={colorScheme}
|
||||
showFlameGraphOnly={showFlameGraphOnly}
|
||||
collapsing={!disableCollapsing}
|
||||
/>
|
||||
)}
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export { default as FlameGraph, type Props } from './FlameGraphContainer';
|
||||
export { checkFields, getMessageCheckFieldsResult } from './FlameGraph/dataTransform';
|
||||
export { data } from './FlameGraph/testData/dataNestedSet';
|
||||
|
@@ -671,6 +671,13 @@ var (
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaObservabilityTracesAndProfilingSquad,
|
||||
},
|
||||
{
|
||||
Name: "tracesEmbeddedFlameGraph",
|
||||
Description: "Enables embedding a flame graph in traces",
|
||||
Stage: FeatureStageExperimental,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaObservabilityTracesAndProfilingSquad,
|
||||
},
|
||||
{
|
||||
Name: "permissionsFilterRemoveSubquery",
|
||||
Description: "Alternative permission filter implementation that does not use subqueries for fetching the dashboard folder",
|
||||
|
@@ -89,6 +89,7 @@ featureToggleAdminPage,experimental,@grafana/grafana-operator-experience-squad,f
|
||||
awsAsyncQueryCaching,preview,@grafana/aws-datasources,false,false,false,false
|
||||
splitScopes,preview,@grafana/identity-access-team,false,false,true,false
|
||||
traceToProfiles,experimental,@grafana/observability-traces-and-profiling,false,false,false,true
|
||||
tracesEmbeddedFlameGraph,experimental,@grafana/observability-traces-and-profiling,false,false,false,true
|
||||
permissionsFilterRemoveSubquery,experimental,@grafana/backend-platform,false,false,false,false
|
||||
prometheusConfigOverhaulAuth,GA,@grafana/observability-metrics,false,false,false,false
|
||||
configurableSchedulerTick,experimental,@grafana/alerting-squad,false,false,true,false
|
||||
|
|
@@ -367,6 +367,10 @@ const (
|
||||
// Enables linking between traces and profiles
|
||||
FlagTraceToProfiles = "traceToProfiles"
|
||||
|
||||
// FlagTracesEmbeddedFlameGraph
|
||||
// Enables embedding a flame graph in traces
|
||||
FlagTracesEmbeddedFlameGraph = "tracesEmbeddedFlameGraph"
|
||||
|
||||
// FlagPermissionsFilterRemoveSubquery
|
||||
// Alternative permission filter implementation that does not use subqueries for fetching the dashboard folder
|
||||
FlagPermissionsFilterRemoveSubquery = "permissionsFilterRemoveSubquery"
|
||||
|
@@ -8,11 +8,9 @@ import { TraceToProfilesData, TraceToProfilesSettings } from './TraceToProfilesS
|
||||
|
||||
const defaultOption: DataSourceSettings<TraceToProfilesData> = {
|
||||
jsonData: {
|
||||
tracesToProfilesV2: {
|
||||
tracesToProfiles: {
|
||||
datasourceUid: 'profiling1_uid',
|
||||
tags: [{ key: 'someTag', value: 'newName' }],
|
||||
spanStartTimeShift: '1m',
|
||||
spanEndTimeShift: '1m',
|
||||
customQuery: true,
|
||||
query: '{${__tags}}',
|
||||
},
|
||||
@@ -48,7 +46,6 @@ describe('TraceToProfilesSettings', () => {
|
||||
|
||||
it('should render all options', () => {
|
||||
render(<TraceToProfilesSettings options={defaultOption} onOptionsChange={() => {}} />);
|
||||
expect(screen.getByText('Select data source')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tags')).toBeInTheDocument();
|
||||
expect(screen.getByText('Profile type')).toBeInTheDocument();
|
||||
expect(screen.getByText('Use custom query')).toBeInTheDocument();
|
||||
|
@@ -78,7 +78,6 @@ export function TraceToProfilesSettings({ options, onOptionsChange }: Props) {
|
||||
noDefault={true}
|
||||
width={40}
|
||||
onChange={(ds: DataSourceInstanceSettings) => {
|
||||
console.log(options.jsonData.tracesToProfiles, ds);
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToProfiles', {
|
||||
...options.jsonData.tracesToProfiles,
|
||||
datasourceUid: ds.uid,
|
||||
|
@@ -516,7 +516,6 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
dataFrames={dataFrames}
|
||||
splitOpenFn={this.onSplitOpen('traceView')}
|
||||
scrollElement={this.scrollElement}
|
||||
queryResponse={queryResponse}
|
||||
/>
|
||||
</ContentOutlineItem>
|
||||
)
|
||||
|
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
|
||||
import React, { createRef } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { DataFrame, MutableDataFrame, getDefaultTimeRange, LoadingState } from '@grafana/data';
|
||||
import { DataFrame, MutableDataFrame } from '@grafana/data';
|
||||
import { DataSourceSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
@@ -14,11 +14,6 @@ import { transformDataFrames } from './utils/transform';
|
||||
|
||||
function getTraceView(frames: DataFrame[]) {
|
||||
const store = configureStore();
|
||||
const mockPanelData = {
|
||||
state: LoadingState.Done,
|
||||
series: [],
|
||||
timeRange: getDefaultTimeRange(),
|
||||
};
|
||||
const topOfViewRef = createRef<HTMLDivElement>();
|
||||
|
||||
return (
|
||||
@@ -28,7 +23,6 @@ function getTraceView(frames: DataFrame[]) {
|
||||
dataFrames={frames}
|
||||
splitOpenFn={() => {}}
|
||||
traceProp={transformDataFrames(frames[0])!}
|
||||
queryResponse={mockPanelData}
|
||||
datasource={undefined}
|
||||
topOfViewRef={topOfViewRef}
|
||||
/>
|
||||
|
@@ -12,7 +12,6 @@ import {
|
||||
GrafanaTheme2,
|
||||
LinkModel,
|
||||
mapInternalLinkToExplore,
|
||||
PanelData,
|
||||
SplitOpen,
|
||||
} from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
@@ -38,6 +37,7 @@ import {
|
||||
} from './components';
|
||||
import memoizedTraceCriticalPath from './components/CriticalPath';
|
||||
import SpanGraph from './components/TracePageHeader/SpanGraph';
|
||||
import { TraceFlameGraphs } from './components/TraceTimelineViewer/SpanDetail';
|
||||
import { createSpanLinkFactory } from './createSpanLink';
|
||||
import { useChildrenState } from './useChildrenState';
|
||||
import { useDetailState } from './useDetailState';
|
||||
@@ -63,7 +63,6 @@ type Props = {
|
||||
scrollElement?: Element;
|
||||
scrollElementClass?: string;
|
||||
traceProp: Trace;
|
||||
queryResponse: PanelData;
|
||||
datasource: DataSourceApi<DataQuery, DataSourceJsonData, {}> | undefined;
|
||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
||||
createSpanLink?: SpanLinkFunc;
|
||||
@@ -94,6 +93,8 @@ export function TraceView(props: Props) {
|
||||
const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false);
|
||||
const [showCriticalPathSpansOnly, setShowCriticalPathSpansOnly] = useState(false);
|
||||
const [headerHeight, setHeaderHeight] = useState(100);
|
||||
const [traceFlameGraphs, setTraceFlameGraphs] = useState<TraceFlameGraphs>({});
|
||||
const [redrawListView, setRedrawListView] = useState({});
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@@ -190,6 +191,7 @@ export function TraceView(props: Props) {
|
||||
<TraceTimelineViewer
|
||||
findMatchesIDs={spanFilterMatches}
|
||||
trace={traceProp}
|
||||
traceToProfilesOptions={traceToProfilesOptions}
|
||||
datasourceType={datasourceType}
|
||||
spanBarOptions={spanBarOptions?.spanBar}
|
||||
traceTimeline={traceTimeline}
|
||||
@@ -225,6 +227,10 @@ export function TraceView(props: Props) {
|
||||
topOfViewRef={topOfViewRef}
|
||||
headerHeight={headerHeight}
|
||||
criticalPath={criticalPath}
|
||||
traceFlameGraphs={traceFlameGraphs}
|
||||
setTraceFlameGraphs={setTraceFlameGraphs}
|
||||
redrawListView={redrawListView}
|
||||
setRedrawListView={setRedrawListView}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
@@ -3,8 +3,6 @@ import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
|
||||
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
|
||||
import { frameOld } from './TraceView.test';
|
||||
@@ -19,15 +17,10 @@ jest.mock('@grafana/runtime', () => {
|
||||
|
||||
function renderTraceViewContainer(frames = [frameOld]) {
|
||||
const store = configureStore();
|
||||
const mockPanelData = {
|
||||
state: LoadingState.Done,
|
||||
series: [],
|
||||
timeRange: getDefaultTimeRange(),
|
||||
};
|
||||
|
||||
const { container, baseElement } = render(
|
||||
<Provider store={store}>
|
||||
<TraceViewContainer exploreId="left" dataFrames={frames} splitOpenFn={() => {}} queryResponse={mockPanelData} />
|
||||
<TraceViewContainer exploreId="left" dataFrames={frames} splitOpenFn={() => {}} />
|
||||
</Provider>
|
||||
);
|
||||
return {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { DataFrame, SplitOpen, PanelData } from '@grafana/data';
|
||||
import { DataFrame, SplitOpen } from '@grafana/data';
|
||||
import { PanelChrome } from '@grafana/ui/src/components/PanelChrome/PanelChrome';
|
||||
import { StoreState, useSelector } from 'app/types';
|
||||
|
||||
@@ -12,13 +12,12 @@ interface Props {
|
||||
splitOpenFn: SplitOpen;
|
||||
exploreId: string;
|
||||
scrollElement?: Element;
|
||||
queryResponse: PanelData;
|
||||
}
|
||||
|
||||
export function TraceViewContainer(props: Props) {
|
||||
// At this point we only show single trace
|
||||
const frame = props.dataFrames[0];
|
||||
const { dataFrames, splitOpenFn, exploreId, scrollElement, queryResponse } = props;
|
||||
const { dataFrames, splitOpenFn, exploreId, scrollElement } = props;
|
||||
const traceProp = useMemo(() => transformDataFrames(frame), [frame]);
|
||||
const datasource = useSelector(
|
||||
(state: StoreState) => state.explore.panes[props.exploreId]?.datasourceInstance ?? undefined
|
||||
@@ -36,7 +35,6 @@ export function TraceViewContainer(props: Props) {
|
||||
splitOpenFn={splitOpenFn}
|
||||
scrollElement={scrollElement}
|
||||
traceProp={traceProp}
|
||||
queryResponse={queryResponse}
|
||||
datasource={datasource}
|
||||
/>
|
||||
</PanelChrome>
|
||||
|
@@ -47,6 +47,7 @@ const props = {
|
||||
viewBuffer: 10,
|
||||
viewBufferMin: 5,
|
||||
windowScroller: true,
|
||||
redraw: {},
|
||||
};
|
||||
|
||||
describe('<ListView />', () => {
|
||||
|
@@ -46,6 +46,10 @@ export type TListViewProps = {
|
||||
* Number of items to draw and add to the DOM, initially.
|
||||
*/
|
||||
initialDraw?: number;
|
||||
/**
|
||||
* Trigger a redraw of the list view.
|
||||
*/
|
||||
redraw: {};
|
||||
/**
|
||||
* The parent provides fallback height measurements when there is not a
|
||||
* rendered element to measure.
|
||||
|
@@ -0,0 +1,169 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import {
|
||||
CoreApp,
|
||||
DataFrame,
|
||||
DataQueryRequest,
|
||||
DataSourceInstanceSettings,
|
||||
DataSourceJsonData,
|
||||
dateTime,
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { FlameGraph } from '@grafana/flamegraph';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { TraceToProfilesOptions } from 'app/core/components/TraceToProfiles/TraceToProfilesSettings';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { PyroscopeQueryType } from 'app/plugins/datasource/grafana-pyroscope-datasource/dataquery.gen';
|
||||
import { PyroscopeDataSource } from 'app/plugins/datasource/grafana-pyroscope-datasource/datasource';
|
||||
import { Query } from 'app/plugins/datasource/grafana-pyroscope-datasource/types';
|
||||
|
||||
import { pyroscopeProfileIdTagKey } from '../../../createSpanLink';
|
||||
import { TraceSpan } from '../../types/trace';
|
||||
|
||||
import { TraceFlameGraphs } from '.';
|
||||
|
||||
export type SpanFlameGraphProps = {
|
||||
span: TraceSpan;
|
||||
traceToProfilesOptions?: TraceToProfilesOptions;
|
||||
timeZone: TimeZone;
|
||||
traceFlameGraphs: TraceFlameGraphs;
|
||||
setTraceFlameGraphs: (flameGraphs: TraceFlameGraphs) => void;
|
||||
setRedrawListView: (redraw: {}) => void;
|
||||
};
|
||||
|
||||
export default function SpanFlameGraph(props: SpanFlameGraphProps) {
|
||||
const { span, traceToProfilesOptions, timeZone, traceFlameGraphs, setTraceFlameGraphs, setRedrawListView } = props;
|
||||
const [sizeRef, { height: containerHeight }] = useMeasure<HTMLDivElement>();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const profileTag = span.tags.filter((tag) => tag.key === pyroscopeProfileIdTagKey);
|
||||
const profileTagValue = profileTag.length > 0 ? profileTag[0].value : undefined;
|
||||
|
||||
const getTimeRangeForProfile = useCallback(() => {
|
||||
const spanStartMs = Math.floor(span.startTime / 1000) - 30000;
|
||||
const spanEndMs = (span.startTime + span.duration) / 1000 + 30000;
|
||||
const to = dateTime(spanEndMs);
|
||||
const from = dateTime(spanStartMs);
|
||||
|
||||
return {
|
||||
from,
|
||||
to,
|
||||
raw: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
};
|
||||
}, [span.duration, span.startTime]);
|
||||
|
||||
const getFlameGraphData = async (request: DataQueryRequest<Query>, datasourceUid: string) => {
|
||||
const ds = await getDatasourceSrv().get(datasourceUid);
|
||||
if (ds instanceof PyroscopeDataSource) {
|
||||
const result = await lastValueFrom(ds.query(request));
|
||||
const frame = result.data.find((x: DataFrame) => {
|
||||
return x.name === 'response';
|
||||
});
|
||||
if (frame && frame.length > 1) {
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const queryFlameGraph = useCallback(
|
||||
async (
|
||||
profilesDataSourceSettings: DataSourceInstanceSettings<DataSourceJsonData>,
|
||||
traceToProfilesOptions: TraceToProfilesOptions
|
||||
) => {
|
||||
const request = {
|
||||
requestId: 'span-flamegraph-requestId',
|
||||
interval: '2s',
|
||||
intervalMs: 2000,
|
||||
range: getTimeRangeForProfile(),
|
||||
scopedVars: {},
|
||||
app: CoreApp.Unknown,
|
||||
timezone: timeZone,
|
||||
startTime: span.startTime,
|
||||
targets: [
|
||||
{
|
||||
labelSelector: '{}',
|
||||
groupBy: [],
|
||||
profileTypeId: traceToProfilesOptions.profileTypeId ?? '',
|
||||
queryType: 'profile' as PyroscopeQueryType,
|
||||
spanSelector: [profileTagValue],
|
||||
refId: 'span-flamegraph-refId',
|
||||
datasource: {
|
||||
type: profilesDataSourceSettings.type,
|
||||
uid: profilesDataSourceSettings.uid,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const flameGraph = await getFlameGraphData(request, profilesDataSourceSettings.uid);
|
||||
|
||||
if (flameGraph && flameGraph.length > 0) {
|
||||
setTraceFlameGraphs({ ...traceFlameGraphs, [profileTagValue]: flameGraph });
|
||||
}
|
||||
},
|
||||
[getTimeRangeForProfile, profileTagValue, setTraceFlameGraphs, span.startTime, timeZone, traceFlameGraphs]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (config.featureToggles.traceToProfiles && !Object.keys(traceFlameGraphs).includes(profileTagValue)) {
|
||||
let profilesDataSourceSettings: DataSourceInstanceSettings<DataSourceJsonData> | undefined;
|
||||
if (traceToProfilesOptions && traceToProfilesOptions?.datasourceUid) {
|
||||
profilesDataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToProfilesOptions.datasourceUid);
|
||||
}
|
||||
if (traceToProfilesOptions && profilesDataSourceSettings) {
|
||||
queryFlameGraph(profilesDataSourceSettings, traceToProfilesOptions);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
setTraceFlameGraphs,
|
||||
span.tags,
|
||||
traceFlameGraphs,
|
||||
traceToProfilesOptions,
|
||||
getTimeRangeForProfile,
|
||||
span.startTime,
|
||||
timeZone,
|
||||
span.spanID,
|
||||
queryFlameGraph,
|
||||
profileTagValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setRedrawListView({});
|
||||
}, [containerHeight, setRedrawListView]);
|
||||
|
||||
if (!traceFlameGraphs[profileTagValue]) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.flameGraph} ref={sizeRef}>
|
||||
<div className={styles.flameGraphTitle}>Flame graph</div>
|
||||
<FlameGraph
|
||||
data={traceFlameGraphs[profileTagValue]}
|
||||
getTheme={() => config.theme2}
|
||||
showFlameGraphOnly={true}
|
||||
disableCollapsing={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = () => {
|
||||
return {
|
||||
flameGraph: css({
|
||||
label: 'flameGraphInSpan',
|
||||
margin: '5px',
|
||||
}),
|
||||
flameGraphTitle: css({
|
||||
label: 'flameGraphTitleInSpan',
|
||||
marginBottom: '5px',
|
||||
fontWeight: 'bold',
|
||||
}),
|
||||
};
|
||||
};
|
@@ -14,10 +14,15 @@
|
||||
|
||||
jest.mock('../utils');
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { createDataFrame, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { data } from '@grafana/flamegraph';
|
||||
import { config, DataSourceSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
import { pyroscopeProfileIdTagKey } from '../../../createSpanLink';
|
||||
import traceGenerator from '../../demo/trace-generators';
|
||||
import transformTraceData from '../../model/transform-trace-data';
|
||||
import { TraceSpanReference } from '../../types/trace';
|
||||
@@ -33,10 +38,29 @@ describe('<SpanDetail>', () => {
|
||||
const detailState = new DetailState().toggleLogs().toggleProcess().toggleReferences().toggleTags();
|
||||
const traceStartTime = 5;
|
||||
const topOfExploreViewRef = jest.fn();
|
||||
const request = {
|
||||
targets: [{ refId: 'A', target: 'query' }],
|
||||
};
|
||||
const traceToProfilesOptions = {
|
||||
datasourceUid: 'profiling1_uid',
|
||||
tags: [{ key: 'someTag', value: 'newName' }],
|
||||
customQuery: true,
|
||||
query: '{${__tags}}',
|
||||
type: 'grafana-pyroscope-datasource',
|
||||
};
|
||||
const pyroSettings = {
|
||||
uid: 'profiling1_uid',
|
||||
name: 'profiling1',
|
||||
type: 'grafana-pyroscope-datasource',
|
||||
meta: { info: { logos: { small: '' } } },
|
||||
} as unknown as DataSourceInstanceSettings;
|
||||
|
||||
const props = {
|
||||
detailState,
|
||||
span,
|
||||
traceStartTime,
|
||||
request,
|
||||
traceToProfilesOptions,
|
||||
topOfExploreViewRef,
|
||||
logItemToggle: jest.fn(),
|
||||
logsToggle: jest.fn(),
|
||||
@@ -45,8 +69,18 @@ describe('<SpanDetail>', () => {
|
||||
warningsToggle: jest.fn(),
|
||||
referencesToggle: jest.fn(),
|
||||
createFocusSpanLink: jest.fn().mockReturnValue({}),
|
||||
traceFlameGraphs: { [span.spanID]: createDataFrame(data) },
|
||||
setRedrawListView: jest.fn(),
|
||||
};
|
||||
|
||||
span.tags = [
|
||||
...span.tags,
|
||||
{
|
||||
key: pyroscopeProfileIdTagKey,
|
||||
value: span.spanID,
|
||||
},
|
||||
];
|
||||
|
||||
span.spanID = 'test-spanID';
|
||||
span.kind = 'test-kind';
|
||||
span.statusCode = 2;
|
||||
@@ -122,6 +156,15 @@ describe('<SpanDetail>', () => {
|
||||
props.processToggle.mockReset();
|
||||
props.logsToggle.mockReset();
|
||||
props.logItemToggle.mockReset();
|
||||
|
||||
setDataSourceSrv({
|
||||
getList() {
|
||||
return [pyroSettings];
|
||||
},
|
||||
getInstanceSettings() {
|
||||
return pyroSettings;
|
||||
},
|
||||
} as unknown as DataSourceSrv);
|
||||
});
|
||||
|
||||
it('renders without exploding', () => {
|
||||
@@ -197,4 +240,14 @@ describe('<SpanDetail>', () => {
|
||||
render(<SpanDetail {...(props as unknown as SpanDetailProps)} />);
|
||||
expect(screen.getByText('test-spanID')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the flame graph', async () => {
|
||||
config.featureToggles.tracesEmbeddedFlameGraph = true;
|
||||
|
||||
render(<SpanDetail {...(props as unknown as SpanDetailProps)} />);
|
||||
await act(async () => {
|
||||
expect(screen.getByText(/16.5 Bil/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/(Count)/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -17,11 +17,13 @@ import { SpanStatusCode } from '@opentelemetry/api';
|
||||
import cx from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
import { dateTimeFormat, GrafanaTheme2, IconName, LinkModel, TimeZone } from '@grafana/data';
|
||||
import { DataFrame, dateTimeFormat, GrafanaTheme2, IconName, LinkModel, TimeZone } from '@grafana/data';
|
||||
import { config, locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { DataLinkButton, Icon, TextArea, useStyles2 } from '@grafana/ui';
|
||||
import { TraceToProfilesOptions } from 'app/core/components/TraceToProfiles/TraceToProfilesSettings';
|
||||
import { RelatedProfilesTitle } from 'app/plugins/datasource/tempo/resultTransformer';
|
||||
|
||||
import { pyroscopeProfileIdTagKey } from '../../../createSpanLink';
|
||||
import { autoColor } from '../../Theme';
|
||||
import { Divider } from '../../common/Divider';
|
||||
import LabeledList from '../../common/LabeledList';
|
||||
@@ -37,6 +39,7 @@ import AccordianLogs from './AccordianLogs';
|
||||
import AccordianReferences from './AccordianReferences';
|
||||
import AccordianText from './AccordianText';
|
||||
import DetailState from './DetailState';
|
||||
import SpanFlameGraph from './SpanFlameGraph';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
@@ -106,6 +109,10 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
};
|
||||
};
|
||||
|
||||
export type TraceFlameGraphs = {
|
||||
[spanID: string]: DataFrame;
|
||||
};
|
||||
|
||||
export type SpanDetailProps = {
|
||||
detailState: DetailState;
|
||||
linksGetter: ((links: TraceKeyValuePair[], index: number) => TraceLink[]) | TNil;
|
||||
@@ -113,6 +120,7 @@ export type SpanDetailProps = {
|
||||
logsToggle: (spanID: string) => void;
|
||||
processToggle: (spanID: string) => void;
|
||||
span: TraceSpan;
|
||||
traceToProfilesOptions?: TraceToProfilesOptions;
|
||||
timeZone: TimeZone;
|
||||
tagsToggle: (spanID: string) => void;
|
||||
traceStartTime: number;
|
||||
@@ -124,6 +132,9 @@ export type SpanDetailProps = {
|
||||
focusedSpanId?: string;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
datasourceType: string;
|
||||
traceFlameGraphs: TraceFlameGraphs;
|
||||
setTraceFlameGraphs: (flameGraphs: TraceFlameGraphs) => void;
|
||||
setRedrawListView: (redraw: {}) => void;
|
||||
};
|
||||
|
||||
export default function SpanDetail(props: SpanDetailProps) {
|
||||
@@ -143,6 +154,10 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
createSpanLink,
|
||||
createFocusSpanLink,
|
||||
datasourceType,
|
||||
traceFlameGraphs,
|
||||
setTraceFlameGraphs,
|
||||
traceToProfilesOptions,
|
||||
setRedrawListView,
|
||||
} = props;
|
||||
const {
|
||||
isTagsOpen,
|
||||
@@ -194,6 +209,8 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
: []),
|
||||
];
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (span.kind) {
|
||||
overviewItems.push({
|
||||
key: KIND,
|
||||
@@ -237,8 +254,6 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
});
|
||||
}
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const createLinkButton = (link: SpanLinkDef, type: SpanLinkType, title: string, icon: IconName) => {
|
||||
return (
|
||||
<DataLinkButton
|
||||
@@ -377,6 +392,17 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
createFocusSpanLink={createFocusSpanLink}
|
||||
/>
|
||||
)}
|
||||
{config.featureToggles.tracesEmbeddedFlameGraph &&
|
||||
span.tags.some((tag) => tag.key === pyroscopeProfileIdTagKey) && (
|
||||
<SpanFlameGraph
|
||||
span={span}
|
||||
timeZone={timeZone}
|
||||
traceFlameGraphs={traceFlameGraphs}
|
||||
setTraceFlameGraphs={setTraceFlameGraphs}
|
||||
traceToProfilesOptions={traceToProfilesOptions}
|
||||
setRedrawListView={setRedrawListView}
|
||||
/>
|
||||
)}
|
||||
<small className={styles.debugInfo}>
|
||||
{/* TODO: fix keyboard a11y */}
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
|
@@ -25,6 +25,7 @@ const testSpan = {
|
||||
spanID: 'testSpanID',
|
||||
traceID: 'testTraceID',
|
||||
depth: 3,
|
||||
tags: [],
|
||||
process: {
|
||||
serviceName: 'some-service',
|
||||
tags: [{ key: 'tag-key', value: 'tag-value' }],
|
||||
@@ -46,6 +47,7 @@ const setup = (propOverrides?: SpanDetailRowProps) => {
|
||||
tagsToggle: jest.fn(),
|
||||
traceStartTime: 1000,
|
||||
theme: createTheme(),
|
||||
traceFlameGraphs: {},
|
||||
...propOverrides,
|
||||
};
|
||||
return render(<UnthemedSpanDetailRow {...(props as SpanDetailRowProps)} />);
|
||||
|
@@ -18,12 +18,13 @@ import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, LinkModel, TimeZone } from '@grafana/data';
|
||||
import { Button, clearButtonStyles, stylesFactory, withTheme2 } from '@grafana/ui';
|
||||
import { TraceToProfilesOptions } from 'app/core/components/TraceToProfiles/TraceToProfilesSettings';
|
||||
|
||||
import { autoColor } from '../Theme';
|
||||
import { SpanLinkFunc } from '../types';
|
||||
import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
||||
|
||||
import SpanDetail from './SpanDetail';
|
||||
import SpanDetail, { TraceFlameGraphs } from './SpanDetail';
|
||||
import DetailState from './SpanDetail/DetailState';
|
||||
import SpanTreeOffset from './SpanTreeOffset';
|
||||
import TimelineRow from './TimelineRow';
|
||||
@@ -84,6 +85,7 @@ export type SpanDetailRowProps = {
|
||||
warningsToggle: (spanID: string) => void;
|
||||
stackTracesToggle: (spanID: string) => void;
|
||||
span: TraceSpan;
|
||||
traceToProfilesOptions?: TraceToProfilesOptions;
|
||||
timeZone: TimeZone;
|
||||
tagsToggle: (spanID: string) => void;
|
||||
traceStartTime: number;
|
||||
@@ -96,6 +98,9 @@ export type SpanDetailRowProps = {
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
datasourceType: string;
|
||||
visibleSpanIds: string[];
|
||||
traceFlameGraphs: TraceFlameGraphs;
|
||||
setTraceFlameGraphs: (flameGraphs: TraceFlameGraphs) => void;
|
||||
setRedrawListView: (redraw: {}) => void;
|
||||
};
|
||||
|
||||
export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProps> {
|
||||
@@ -121,6 +126,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
|
||||
warningsToggle,
|
||||
stackTracesToggle,
|
||||
span,
|
||||
traceToProfilesOptions,
|
||||
timeZone,
|
||||
tagsToggle,
|
||||
traceStartTime,
|
||||
@@ -133,6 +139,9 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
|
||||
createFocusSpanLink,
|
||||
datasourceType,
|
||||
visibleSpanIds,
|
||||
traceFlameGraphs,
|
||||
setTraceFlameGraphs,
|
||||
setRedrawListView,
|
||||
} = this.props;
|
||||
const styles = getStyles(theme);
|
||||
return (
|
||||
@@ -167,6 +176,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
|
||||
warningsToggle={warningsToggle}
|
||||
stackTracesToggle={stackTracesToggle}
|
||||
span={span}
|
||||
traceToProfilesOptions={traceToProfilesOptions}
|
||||
timeZone={timeZone}
|
||||
tagsToggle={tagsToggle}
|
||||
traceStartTime={traceStartTime}
|
||||
@@ -174,6 +184,9 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
|
||||
focusedSpanId={focusedSpanId}
|
||||
createFocusSpanLink={createFocusSpanLink}
|
||||
datasourceType={datasourceType}
|
||||
traceFlameGraphs={traceFlameGraphs}
|
||||
setTraceFlameGraphs={setTraceFlameGraphs}
|
||||
setRedrawListView={setRedrawListView}
|
||||
/>
|
||||
</div>
|
||||
</TimelineRow.Cell>
|
||||
|
@@ -21,6 +21,7 @@ import { RefObject } from 'react';
|
||||
import { GrafanaTheme2, LinkModel, TimeZone } from '@grafana/data';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui';
|
||||
import { TraceToProfilesOptions } from 'app/core/components/TraceToProfiles/TraceToProfilesSettings';
|
||||
|
||||
import { PEER_SERVICE } from '../constants/tag-keys';
|
||||
import { CriticalPathSection, SpanBarOptions, SpanLinkFunc, TNil } from '../types';
|
||||
@@ -30,6 +31,7 @@ import { getColorByKey } from '../utils/color-generator';
|
||||
|
||||
import ListView from './ListView';
|
||||
import SpanBarRow from './SpanBarRow';
|
||||
import { TraceFlameGraphs } from './SpanDetail';
|
||||
import DetailState from './SpanDetail/DetailState';
|
||||
import SpanDetailRow from './SpanDetailRow';
|
||||
import {
|
||||
@@ -75,6 +77,7 @@ type TVirtualizedTraceViewOwnProps = {
|
||||
timeZone: TimeZone;
|
||||
findMatchesIDs: Set<string> | TNil;
|
||||
trace: Trace;
|
||||
traceToProfilesOptions?: TraceToProfilesOptions;
|
||||
spanBarOptions: SpanBarOptions | undefined;
|
||||
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
|
||||
childrenToggle: (spanID: string) => void;
|
||||
@@ -103,6 +106,10 @@ type TVirtualizedTraceViewOwnProps = {
|
||||
datasourceType: string;
|
||||
headerHeight: number;
|
||||
criticalPath: CriticalPathSection[];
|
||||
traceFlameGraphs: TraceFlameGraphs;
|
||||
setTraceFlameGraphs: (flameGraphs: TraceFlameGraphs) => void;
|
||||
redrawListView: {};
|
||||
setRedrawListView: (redraw: {}) => void;
|
||||
};
|
||||
|
||||
export type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TTraceTimeline;
|
||||
@@ -537,6 +544,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
detailToggle,
|
||||
spanNameColumnWidth,
|
||||
trace,
|
||||
traceToProfilesOptions,
|
||||
timeZone,
|
||||
hoverIndentGuideIds,
|
||||
addHoverIndentGuideId,
|
||||
@@ -547,6 +555,9 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
createFocusSpanLink,
|
||||
theme,
|
||||
datasourceType,
|
||||
traceFlameGraphs,
|
||||
setTraceFlameGraphs,
|
||||
setRedrawListView,
|
||||
} = this.props;
|
||||
const detailState = detailStates.get(spanID);
|
||||
if (!trace || !detailState) {
|
||||
@@ -571,6 +582,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
warningsToggle={detailWarningsToggle}
|
||||
stackTracesToggle={detailStackTracesToggle}
|
||||
span={span}
|
||||
traceToProfilesOptions={traceToProfilesOptions}
|
||||
timeZone={timeZone}
|
||||
tagsToggle={detailTagsToggle}
|
||||
traceStartTime={trace.startTime}
|
||||
@@ -582,6 +594,9 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
createFocusSpanLink={createFocusSpanLink}
|
||||
datasourceType={datasourceType}
|
||||
visibleSpanIds={visibleSpanIds}
|
||||
traceFlameGraphs={traceFlameGraphs}
|
||||
setTraceFlameGraphs={setTraceFlameGraphs}
|
||||
setRedrawListView={setRedrawListView}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -611,7 +626,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
|
||||
render() {
|
||||
const styles = getStyles();
|
||||
const { scrollElement } = this.props;
|
||||
const { scrollElement, redrawListView } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -627,6 +642,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
getIndexFromKey={this.getIndexFromKey}
|
||||
windowScroller={false}
|
||||
scrollElement={scrollElement}
|
||||
redraw={redrawListView}
|
||||
/>
|
||||
{this.props.topOfViewRef && ( // only for panel as explore uses content outline to scroll to top
|
||||
<ToolbarButton
|
||||
|
@@ -18,6 +18,7 @@ import React, { RefObject } from 'react';
|
||||
import { GrafanaTheme2, LinkModel, TimeZone } from '@grafana/data';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { stylesFactory, withTheme2 } from '@grafana/ui';
|
||||
import { TraceToProfilesOptions } from 'app/core/components/TraceToProfiles/TraceToProfilesSettings';
|
||||
|
||||
import { autoColor } from '../Theme';
|
||||
import { merge as mergeShortcuts } from '../keyboard-shortcuts';
|
||||
@@ -26,6 +27,7 @@ import { CriticalPathSection, SpanLinkFunc, TNil } from '../types';
|
||||
import TTraceTimeline from '../types/TTraceTimeline';
|
||||
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
||||
|
||||
import { TraceFlameGraphs } from './SpanDetail';
|
||||
import TimelineHeaderRow from './TimelineHeaderRow';
|
||||
import VirtualizedTraceView from './VirtualizedTraceView';
|
||||
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types';
|
||||
@@ -70,6 +72,7 @@ export type TProps = {
|
||||
findMatchesIDs: Set<string> | TNil;
|
||||
traceTimeline: TTraceTimeline;
|
||||
trace: Trace;
|
||||
traceToProfilesOptions?: TraceToProfilesOptions;
|
||||
datasourceType: string;
|
||||
spanBarOptions: SpanBarOptions | undefined;
|
||||
updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void;
|
||||
@@ -107,6 +110,10 @@ export type TProps = {
|
||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
||||
headerHeight: number;
|
||||
criticalPath: CriticalPathSection[];
|
||||
traceFlameGraphs: TraceFlameGraphs;
|
||||
setTraceFlameGraphs: (flameGraphs: TraceFlameGraphs) => void;
|
||||
redrawListView: {};
|
||||
setRedrawListView: (redraw: {}) => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@@ -17,7 +17,7 @@ import { TemplateSrv } from '../../templating/template_srv';
|
||||
|
||||
import { Trace, TraceSpan } from './components';
|
||||
import { SpanLinkType } from './components/types/links';
|
||||
import { createSpanLinkFactory } from './createSpanLink';
|
||||
import { createSpanLinkFactory, pyroscopeProfileIdTagKey } from './createSpanLink';
|
||||
|
||||
const dummyTraceData = { duration: 10, traceID: 'trace1', traceName: 'test trace' } as unknown as Trace;
|
||||
const dummyDataFrame = createDataFrame({
|
||||
@@ -1555,7 +1555,7 @@ function createTraceSpan(overrides: Partial<TraceSpan> = {}) {
|
||||
value: 'host',
|
||||
},
|
||||
{
|
||||
key: 'pyroscope.profile.id',
|
||||
key: pyroscopeProfileIdTagKey,
|
||||
value: 'hdgfljn23u982nj',
|
||||
},
|
||||
],
|
||||
|
@@ -85,7 +85,7 @@ export function createSpanLinkFactory({
|
||||
profilesDataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToProfilesOptions.datasourceUid);
|
||||
}
|
||||
const hasConfiguredPyroscopeDS = profilesDataSourceSettings?.type === 'grafana-pyroscope-datasource';
|
||||
const hasPyroscopeProfile = span.tags.filter((tag) => tag.key === 'pyroscope.profile.id').length > 0;
|
||||
const hasPyroscopeProfile = span.tags.some((tag) => tag.key === pyroscopeProfileIdTagKey);
|
||||
const shouldCreatePyroscopeLink = hasConfiguredPyroscopeDS && hasPyroscopeProfile;
|
||||
|
||||
let links: ExploreFieldLinkModel[] = [];
|
||||
@@ -135,6 +135,7 @@ const formatDefaultKeys = (keys: string[]) => {
|
||||
};
|
||||
const defaultKeys = formatDefaultKeys(['cluster', 'hostname', 'namespace', 'pod', 'service.name', 'service.namespace']);
|
||||
const defaultProfilingKeys = formatDefaultKeys(['service.name', 'service.namespace']);
|
||||
export const pyroscopeProfileIdTagKey = 'pyroscope.profile.id';
|
||||
|
||||
function legacyCreateSpanLinkFactory(
|
||||
splitOpenFn: SplitOpen,
|
||||
|
@@ -25,6 +25,7 @@ export const FlameGraphPanel = (props: PanelProps) => {
|
||||
data={props.data.series[0]}
|
||||
stickyHeader={false}
|
||||
getTheme={() => config.theme2}
|
||||
showFlameGraphOnly={props.options?.showFlameGraphOnly ?? false}
|
||||
onTableSymbolClick={() => interaction('table_item_selected')}
|
||||
onViewSelected={(view: string) => interaction('view_selected', { view })}
|
||||
onTextAlignSelected={(align: string) => interaction('text_align_selected', { align })}
|
||||
|
@@ -41,7 +41,6 @@ export const TracesPanel = ({ data, options }: PanelProps<TracesPanelOptions>) =
|
||||
dataFrames={data.series}
|
||||
scrollElementClass={styles.wrapper}
|
||||
traceProp={traceProp}
|
||||
queryResponse={data}
|
||||
datasource={dataSource.value}
|
||||
topOfViewRef={topOfViewRef}
|
||||
createSpanLink={options.createSpanLink}
|
||||
|
Reference in New Issue
Block a user