Skip to content

Commit

Permalink
Merge pull request #10 from grafana/andre/structure-tab
Browse files Browse the repository at this point in the history
Structure tab (preview)
  • Loading branch information
adrapereira authored Apr 12, 2024
2 parents 6aaa634 + a1043a5 commit 52b2edb
Show file tree
Hide file tree
Showing 12 changed files with 6,557 additions and 70 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
build:
context: ./.config
args:
grafana_image: ${GRAFANA_IMAGE:-grafana-enterprise}
grafana_image: ${GRAFANA_IMAGE:-grafana-dev}
grafana_version: ${GRAFANA_VERSION:-latest}
ports:
- 3000:3000/tcp
Expand Down
64 changes: 0 additions & 64 deletions src/components/Explore/TracesByService/Tabs/ServicesTabScene.tsx

This file was deleted.

108 changes: 108 additions & 0 deletions src/components/Explore/TracesByService/Tabs/StructureTabScene.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react';

import {
SceneCanvasText,
SceneComponentProps,
SceneFlexItem,
SceneFlexLayout,
SceneObjectBase,
SceneObjectState,
SceneQueryRunner,
} from '@grafana/scenes';
import {explorationDS, VAR_FILTERS_EXPR} from "../../../../utils/shared";
import {TraceSearchMetadata} from "../../../../types";
import {dumpTree, mergeTraces} from "../../../../utils/trace-merge/merge";
import {LoadingState} from "@grafana/data";
import {TreeNode} from "../../../../utils/trace-merge/tree-node";
import {Stack} from "@grafana/ui";

export interface ServicesTabSceneState extends SceneObjectState {
loading?: boolean;
panel?: SceneFlexLayout;
tree?: TreeNode;
}

export class StructureTabScene extends SceneObjectBase<ServicesTabSceneState> {
constructor(state: Partial<ServicesTabSceneState>) {
super({
$data: new SceneQueryRunner({
datasource: explorationDS,
queries: [buildQuery()],
}),
...state,
});

this.addActivationHandler(this._onActivate.bind(this));
}

public _onActivate() {
this.state.$data?.subscribeToState((state) => {
console.log(state, state.data);
if(state.data?.state === LoadingState.Done){
const frame = state.data?.series[0].fields[0].values[0];
if(frame){
const response = JSON.parse(frame) as TraceSearchMetadata[];
const merged = mergeTraces(response);
this.setState({tree: merged})
console.log(dumpTree(merged, 0));
}
}
});

if (!this.state.panel) {
this.setState({
panel: this.getVizPanel(),
});
}
}

private getVizPanel() {
return new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: new SceneCanvasText({
text: 'No content available yet',
fontSize: 20,
align: 'center',
}),
}),
],
});
}

public static Component = ({ model }: SceneComponentProps<StructureTabScene>) => {
const { panel, tree } = model.useState();

if(tree){
return <Stack gap={0.5} direction={'column'}>
{tree.children.map((child) => <pre key={child.name}>{dumpTree(child, 0)}</pre>)}
</Stack>
}

if (!panel) {
return;
}

return <panel.Component model={panel} />;
};
}

function buildQuery() {
return {
refId: 'A',
query: `{${VAR_FILTERS_EXPR}} >> {status = error} | select(status, resource.service.name, name, nestedSetParent, nestedSetLeft, nestedSetRight)`,
queryType: 'traceql',
tableType: 'raw',
limit: 200,
spss: 20,
filters: [],
};
}


export function buildStructureTabScene() {
return new SceneFlexItem({
body: new StructureTabScene({}),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from '../../../utils/shared';
import { getExplorationFor } from '../../../utils/utils';
import { ShareExplorationButton } from './ShareExplorationButton';
import { buildServicesTabScene } from './Tabs/ServicesTabScene';
import { buildStructureTabScene } from './Tabs/StructureTabScene';
import { getDataSourceSrv } from '@grafana/runtime';
import { buildAttributesBreakdownActionScene } from './Tabs/AttributesBreakdown';

Expand Down Expand Up @@ -120,8 +120,8 @@ export class TracesByServiceScene extends SceneObjectBase<TraceSceneState> {

const actionViewsDefinitions: ActionViewDefinition[] = [
{ displayName: 'Attributes', value: 'attributes', getScene: buildAttributesBreakdownActionScene },
{ displayName: 'Structure', value: 'structure', getScene: buildStructureTabScene },
{ displayName: 'Spans', value: 'spans', getScene: buildTracesListScene },
{ displayName: 'Services', value: 'services', getScene: buildServicesTabScene },
];

export interface TracesActionBarState extends SceneObjectState {}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Explore/SelectStartingPointScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { Select, Tab, TabsBar, useStyles2 } from '@grafana/ui';

import { SelectAttributeWithValueAction } from './SelectAttributeWithValueAction';
import { explorationDS, VAR_DATASOURCE_EXPR, VAR_FILTERS_EXPR } from '../../utils/shared';
import {explorationDS, VAR_DATASOURCE_EXPR, VAR_FILTERS, VAR_FILTERS_EXPR} from '../../utils/shared';
import { getColorByIndex } from '../../utils/utils';
import { ByFrameRepeater } from '../../components/Explore/ByFrameRepeater';
import { map, Observable } from 'rxjs';
Expand All @@ -46,7 +46,7 @@ const VAR_PRIMARY_SIGNAL_EXPR = '${primarySignal}';

export class SelectStartingPointScene extends SceneObjectBase<TraceSelectSceneState> {
protected _variableDependency = new VariableDependencyConfig(this, {
variableNames: [VAR_GROUPBY, VAR_PRIMARY_SIGNAL],
variableNames: [VAR_GROUPBY, VAR_PRIMARY_SIGNAL, VAR_FILTERS],
});

constructor(state: Partial<TraceSelectSceneState>) {
Expand Down
101 changes: 101 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
export type TraceSearchMetadata = {
traceID: string;
rootServiceName: string;
rootTraceName: string;
startTimeUnixNano?: string;
durationMs?: number;
spanSet?: Spanset; // deprecated in Tempo, https://github.com/grafana/tempo/blob/3cc44fca03ba7d676dc77da6a18b8222546ede3c/docs/sources/tempo/api_docs/_index.md?plain=1#L619
spanSets?: Spanset[];
};

export type SearchMetrics = {
inspectedTraces?: number;
inspectedBytes?: number;
totalBlocks?: number;
completedJobs?: number;
totalJobs?: number;
totalBlockBytes?: number;
};

export enum SpanKind {
UNSPECIFIED,
INTERNAL,
SERVER,
CLIENT,
PRODUCER,
CONSUMER,
}

export type SpanAttributes = {
key: string;
value: {
stringValue?: string;
intValue?: string;
boolValue?: boolean;
doubleValue?: string;
Value?: {
string_value?: string;
int_value?: string;
bool_value?: boolean;
double_value?: string;
};
};
};

export type Span = {
durationNanos: string;
traceId?: string;
spanID: string;
traceState?: string;
parentSpanId?: string;
name?: string;
kind?: SpanKind;
startTimeUnixNano: string;
endTimeUnixNano?: string;
attributes?: SpanAttributes[];
dropped_attributes_count?: number;
};

export type Spanset = {
attributes?: SpanAttributes[];
spans: Span[];
};

export type SearchResponse = {
traces: TraceSearchMetadata[];
metrics: SearchMetrics;
};

export type Scope = {
name: string;
tags: string[];
};

// Maps to QueryRangeResponse of tempopb https://github.com/grafana/tempo/blob/cfda98fc5cb0777963f41e0949b9ad2d24b4b5b8/pkg/tempopb/tempo.proto#L360
export type TraceqlMetricsResponse = {
series: MetricsSeries[];
metrics: SearchMetrics;
};

export type MetricsSeries = {
labels: MetricsSeriesLabel[];
samples: MetricsSeriesSample[];
promLabels: string;
};

export type MetricsSeriesLabel = {
key: string;
value: ProtoValue;
};

export type ProtoValue = {
stringValue?: string;
intValue?: string;
boolValue?: boolean;
doubleValue?: string;
};

export type MetricsSeriesSample = {
timestampMs: string;
value: number;
};
2 changes: 1 addition & 1 deletion src/utils/shared.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BusEventBase } from '@grafana/data';
import { SceneObject } from '@grafana/scenes';

export type ActionViewType = 'spans' | 'attributes' | 'services';
export type ActionViewType = 'spans' | 'attributes' | 'structure';
export interface ActionViewDefinition {
displayName: string;
value: ActionViewType;
Expand Down
33 changes: 33 additions & 0 deletions src/utils/trace-merge/merge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {dumpTree, mergeTraces} from './merge';
import {SearchResponse} from '../../types';

import serviceStructResponse from './test-responses/service-struct.json';

describe('mergeTraces', () => {

beforeEach(() => {
global.console = require('console');
});

it('should not throw an error when a trace has exactly one span set', () => {
const mockResponse = serviceStructResponse as SearchResponse;
expect(() => mergeTraces(mockResponse.traces)).not.toThrow();
});

it('should correctly output a tree', () => {
const mockResponse = serviceStructResponse as SearchResponse;
const tree = mergeTraces(mockResponse.traces);
const treeDump = dumpTree(tree, 0);
console.log(treeDump);
expect(treeDump).toMatch("root 0\n" +
" Service-A:HTTP POST 11\n" +
" Service-B:cardinality_estimation 1\n" +
" Service-B:querysharding 1\n" +
" Service-C:HTTP GET 37\n" +
" Service-D:Span-name-PQR 106\n" +
" Service-E:Span-name-XYZ 3\n" +
" Service-F:HTTP Outgoing Request 1\n" +
" Service-B:step_align 1\n" +
" Service-B:split_by_interval_and_results_cache 1");
});
});
Loading

0 comments on commit 52b2edb

Please sign in to comment.