Skip to content

Commit

Permalink
feat: allow selector to use kind field from span (#3663)
Browse files Browse the repository at this point in the history
* feat: allow selector to use `kind` field from span

* add doc about span.kind

* add fe support for kind field

* add fe support for kind field

* fix: map span kind in db layer

---------

Co-authored-by: Jorge Padilla <jorge.esteban.padilla@gmail.com>
  • Loading branch information
mathnogueira and jorgeepc committed Feb 19, 2024
1 parent 271e00d commit 3e1352f
Show file tree
Hide file tree
Showing 11 changed files with 51 additions and 36 deletions.
3 changes: 3 additions & 0 deletions docs/docs/concepts/selectors.mdx
Expand Up @@ -162,6 +162,9 @@ flowchart TD
### **Empty Selector**
By providing an empty selector, all spans from the trace are selected. Note that an empty selector is an empty string. Providing `span` or `span[]` as a selector will result as a syntax error.

### **Filter by span kind**
If you have more than one span with similar attributes, but different span kinds, you can use `span[kind="expected-kind"] to differentiate them.

### **Filter by Attributes**
The most basic way of filtering the spans to apply an assertion to is to use the span's attributes. A good starting example would be filtering all spans of type `http`:

Expand Down
2 changes: 2 additions & 0 deletions server/assertions/selectors/builder.go
Expand Up @@ -112,6 +112,8 @@ func getOperatorFunction(operator string) (FilterFunction, error) {
var attrValue string
if attribute == "name" {
attrValue = span.Name
} else if attribute == "kind" {
attrValue = string(span.Kind)
} else {
attrValue = span.Attributes.Get(attribute)
}
Expand Down
17 changes: 13 additions & 4 deletions server/assertions/selectors/selector_test.go
Expand Up @@ -21,7 +21,8 @@ var (
var pokeshopTrace = traces.Trace{
ID: gen.TraceID(),
RootSpan: traces.Span{
ID: postImportSpanID,
Kind: "http",
ID: postImportSpanID,
Attributes: traces.NewAttributes(map[string]string{
"service.name": "Pokeshop",
"tracetest.span.type": "http",
Expand All @@ -30,7 +31,8 @@ var pokeshopTrace = traces.Trace{
Name: "POST /import",
Children: []*traces.Span{
{
ID: insertPokemonDatabaseSpanID,
ID: insertPokemonDatabaseSpanID,
Kind: "db",
Attributes: traces.NewAttributes(map[string]string{
"service.name": "Pokeshop",
"tracetest.span.type": "db",
Expand All @@ -39,7 +41,8 @@ var pokeshopTrace = traces.Trace{
Name: "Insert pokemon into database",
},
{
ID: getPokemonFromExternalAPISpanID,
ID: getPokemonFromExternalAPISpanID,
Kind: "general",
Attributes: traces.NewAttributes(map[string]string{
"service.name": "Pokeshop-worker",
"tracetest.span.type": "http",
Expand All @@ -48,7 +51,8 @@ var pokeshopTrace = traces.Trace{
Name: "Get pokemon from external API",
Children: []*traces.Span{
{
ID: updatePokemonDatabaseSpanID,
Kind: "db",
ID: updatePokemonDatabaseSpanID,
Attributes: traces.NewAttributes(map[string]string{
"service.name": "Pokeshop-worker",
"tracetest.span.type": "db",
Expand Down Expand Up @@ -133,6 +137,11 @@ func TestSelector(t *testing.T) {
Expression: `span[service.name = "Pokeshop-worker"] span[service.name = "Pokeshop-worker"]`,
ExpectedSpanIds: []trace.SpanID{updatePokemonDatabaseSpanID},
},
{
Name: "SelectorShouldMatchSpanKind",
Expression: `span[kind="db"]`,
ExpectedSpanIds: []trace.SpanID{insertPokemonDatabaseSpanID, updatePokemonDatabaseSpanID},
},
}

for _, testCase := range testCases {
Expand Down
3 changes: 3 additions & 0 deletions server/traces/span_entitiess.go
Expand Up @@ -194,6 +194,7 @@ type SpanEvent struct {
type encodedSpan struct {
ID string
Name string
Kind string
StartTime string
EndTime string
Attributes Attributes
Expand Down Expand Up @@ -222,6 +223,7 @@ func encodeSpan(s Span) encodedSpan {
return encodedSpan{
ID: s.ID.String(),
Name: s.Name,
Kind: string(s.Kind),
StartTime: strconv.FormatInt(s.StartTime.UnixMilli(), 10),
EndTime: strconv.FormatInt(s.EndTime.UnixMilli(), 10),
Attributes: s.Attributes,
Expand Down Expand Up @@ -273,6 +275,7 @@ func (s *Span) decodeSpan(aux encodedSpan) error {

s.ID = sid
s.Name = aux.Name
s.Kind = SpanKind(aux.Kind)
s.StartTime = startTime.UTC()
s.EndTime = endTime.UTC()
s.Attributes = aux.Attributes
Expand Down
9 changes: 9 additions & 0 deletions server/traces/traces_test.go
Expand Up @@ -323,6 +323,11 @@ func TestJSONEncoding(t *testing.T) {
subSubSpan1 := createSpan("subSubSpan1")
subSpan2 := createSpan("subSpan2")

rootSpan.Kind = traces.SpanKindClient
subSpan1.Kind = traces.SpanKindConsumer
subSubSpan1.Kind = traces.SpanKindUnespecified
subSpan2.Kind = traces.SpanKindUnespecified

subSpan1.Parent = rootSpan
subSpan2.Parent = rootSpan
subSubSpan1.Parent = subSpan1
Expand Down Expand Up @@ -351,6 +356,7 @@ func TestJSONEncoding(t *testing.T) {
"RootSpan": {
"ID": "%s",
"Name":"root",
"Kind": "client",
"StartTime":"%d",
"EndTime":"%d",
"Attributes": {
Expand All @@ -360,6 +366,7 @@ func TestJSONEncoding(t *testing.T) {
{
"ID": "%s",
"Name":"subSpan1",
"Kind": "consumer",
"StartTime":"%d",
"EndTime":"%d",
"Attributes": {
Expand All @@ -369,6 +376,7 @@ func TestJSONEncoding(t *testing.T) {
{
"ID": "%s",
"StartTime":"%d",
"Kind": "unespecified",
"EndTime":"%d",
"Name":"subSubSpan1",
"Attributes": {
Expand All @@ -381,6 +389,7 @@ func TestJSONEncoding(t *testing.T) {
{
"ID": "%s",
"Name":"subSpan2",
"Kind": "unespecified",
"StartTime":"%d",
"EndTime":"%d",
"Attributes": {
Expand Down
3 changes: 1 addition & 2 deletions web/src/components/SpanDetail/Header.tsx
Expand Up @@ -2,7 +2,6 @@ import {SettingOutlined, ToolOutlined} from '@ant-design/icons';
import {Space} from 'antd';
import * as SSpanNode from 'components/Visualization/components/DAG/BaseSpanNode/BaseSpanNode.styled';
import {SemanticGroupNamesToText} from 'constants/SemanticGroupNames.constants';
import {SpanKindToText} from 'constants/Span.constants';
import Span from 'models/Span.model';
import SpanService from 'services/Span.service';
import * as S from './SpanDetail.styled';
Expand Down Expand Up @@ -33,7 +32,7 @@ const Header = ({span}: IProps) => {
<S.Column>
<S.HeaderItem>
<SettingOutlined />
<S.HeaderItemText>{`${service} ${SpanKindToText[kind]}`}</S.HeaderItemText>
<S.HeaderItemText>{`${service} ${kind}`}</S.HeaderItemText>
</S.HeaderItem>
{Boolean(system) && (
<S.HeaderItem>
Expand Down
3 changes: 1 addition & 2 deletions web/src/components/TestSpecDetail/SpanHeader.tsx
Expand Up @@ -3,7 +3,6 @@ import {SettingOutlined, ToolOutlined} from '@ant-design/icons';
import {Typography} from 'antd';
import * as SSpanNode from 'components/Visualization/components/DAG/BaseSpanNode/BaseSpanNode.styled';
import {SemanticGroupNamesToText} from 'constants/SemanticGroupNames.constants';
import {SpanKindToText} from 'constants/Span.constants';
import SpanService from 'services/Span.service';
import Span from 'models/Span.model';
import * as S from './TestSpecDetail.styled';
Expand All @@ -23,7 +22,7 @@ const SpanHeader = ({onSelectSpan, span}: IProps) => {
<S.HeaderTitle level={3}>{name}</S.HeaderTitle>
<S.HeaderItem>
<SettingOutlined />
<S.HeaderItemText>{`${service} ${SpanKindToText[kind]}`}</S.HeaderItemText>
<S.HeaderItemText>{`${service} ${kind}`}</S.HeaderItemText>
</S.HeaderItem>
{Boolean(system) && (
<S.HeaderItem>
Expand Down
Expand Up @@ -2,7 +2,6 @@ import {ClockCircleOutlined, SettingOutlined, ToolOutlined} from '@ant-design/ic
import {Handle, Position} from 'react-flow-renderer';

import {SemanticGroupNamesToText} from 'constants/SemanticGroupNames.constants';
import {SpanKindToText} from 'constants/Span.constants';
import Span from 'models/Span.model';
import * as S from './BaseSpanNode.styled';

Expand All @@ -18,7 +17,7 @@ interface IProps {
const BaseSpanNode = ({className, footer, id, isMatched, isSelected, span}: IProps) => {
return (
<S.Container className={className} data-cy={`trace-node-${span.type}`} $matched={isMatched} $selected={isSelected}>
<Handle id={id} position={Position.Top} style={{ top: 0, visibility: 'hidden' }} type="target" />
<Handle id={id} position={Position.Top} style={{top: 0, visibility: 'hidden'}} type="target" />

<S.TopLine $type={span.type} />

Expand All @@ -33,7 +32,7 @@ const BaseSpanNode = ({className, footer, id, isMatched, isSelected, span}: IPro
<S.Item>
<SettingOutlined />
<S.ItemText>
{span.service} {SpanKindToText[span.kind]}
{span.service} {span.kind}
</S.ItemText>
</S.Item>
{Boolean(span.system) && (
Expand All @@ -50,7 +49,7 @@ const BaseSpanNode = ({className, footer, id, isMatched, isSelected, span}: IPro

<S.Footer>{footer}</S.Footer>

<Handle id={id} position={Position.Bottom} style={{ bottom: 0, visibility: 'hidden' }} type="source" />
<Handle id={id} position={Position.Bottom} style={{bottom: 0, visibility: 'hidden'}} type="source" />
</S.Container>
);
};
Expand Down
16 changes: 0 additions & 16 deletions web/src/constants/Span.constants.ts
Expand Up @@ -107,19 +107,3 @@ export const SelectorAttributesWhiteList = uniq([
return sectionAttributes;
}),
]);

export enum SpanKind {
SERVER = 'SPAN_KIND_SERVER',
CLIENT = 'SPAN_KIND_CLIENT',
PRODUCER = 'SPAN_KIND_PRODUCER',
CONSUMER = 'SPAN_KIND_CONSUMER',
INTERNAL = 'SPAN_KIND_INTERNAL',
}

export const SpanKindToText = {
[SpanKind.SERVER]: 'server',
[SpanKind.CLIENT]: 'client',
[SpanKind.PRODUCER]: 'producer',
[SpanKind.CONSUMER]: 'consumer',
[SpanKind.INTERNAL]: 'internal',
};
21 changes: 15 additions & 6 deletions web/src/models/Span.model.ts
Expand Up @@ -4,13 +4,11 @@ import {
SemanticGroupsSignature,
} from 'constants/SemanticGroupNames.constants';
import {Attributes} from 'constants/SpanAttribute.constants';
import {SpanKind} from 'constants/Span.constants';
import {TSpanFlatAttribute} from 'types/Span.types';
import SpanAttribute from './SpanAttribute.model';
import {Model, TTraceSchemas} from '../types/Common.types';

const SemanticGroupNamesList = Object.values(SemanticGroupNames);
const SpanKindList = Object.values(SpanKind);

export type TRawSpan = TTraceSchemas['Span'];
type Span = Model<
Expand All @@ -22,7 +20,7 @@ type Span = Model<
signature: TSpanFlatAttribute[];
attributeList: TSpanFlatAttribute[];
children?: Span[];
kind: SpanKind;
kind: string;
service: string;
system: string;
}
Expand Down Expand Up @@ -52,13 +50,25 @@ const defaultSpan: TRawSpan = {
id: '',
parentId: '',
name: '',
kind: '',
attributes: {},
startTime: 0,
endTime: 0,
};

const Span = ({id = '', attributes = {}, startTime = 0, endTime = 0, parentId = '', name = ''} = defaultSpan): Span => {
const mappedAttributeList: TSpanFlatAttribute[] = [{key: 'name', value: name}];
const Span = ({
id = '',
attributes = {},
startTime = 0,
endTime = 0,
parentId = '',
name = '',
kind = '',
} = defaultSpan): Span => {
const mappedAttributeList: TSpanFlatAttribute[] = [
{key: 'name', value: name},
{key: 'kind', value: kind},
];
const attributeList = Object.entries(attributes)
.map<TSpanFlatAttribute>(([key, value]) => ({
value: String(value),
Expand All @@ -72,7 +82,6 @@ const Span = ({id = '', attributes = {}, startTime = 0, endTime = 0, parentId =
return {...map, [spanAttribute.name]: SpanAttribute(rawSpanAttribute)};
}, {});

const kind = SpanKindList.find(spanKind => spanKind === attributes[Attributes.KIND]) || SpanKind.INTERNAL;
const duration = attributes[Attributes.TRACETEST_SPAN_DURATION] || '0ns';
const type =
SemanticGroupNamesList.find(
Expand Down
3 changes: 1 addition & 2 deletions web/src/services/Span.service.ts
@@ -1,7 +1,6 @@
import {differenceBy, intersectionBy} from 'lodash';
import {CompareOperator} from 'constants/Operator.constants';
import {SELECTOR_DEFAULT_ATTRIBUTES, SemanticGroupNames} from 'constants/SemanticGroupNames.constants';
import {SpanKind} from 'constants/Span.constants';
import Span from 'models/Span.model';
import {TSpanFlatAttribute} from 'types/Span.types';
import {getObjectIncludesText} from 'utils/Common';
Expand All @@ -11,7 +10,7 @@ const itemSelectorKeys = SELECTOR_DEFAULT_ATTRIBUTES.flatMap(el => el.attributes

const SpanService = () => ({
getSpanInfo(span?: Span) {
const kind = span?.kind ?? SpanKind.INTERNAL;
const kind = span?.kind ?? '';
const name = span?.name ?? '';
const service = span?.service ?? '';
const system = span?.system ?? '';
Expand Down

0 comments on commit 3e1352f

Please sign in to comment.