Skip to content

Commit

Permalink
Merge branch 'main' into fix-issue-730
Browse files Browse the repository at this point in the history
  • Loading branch information
SpencerTorres authored Feb 29, 2024
2 parents 3399df3 + d3b5eae commit e0e29bf
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
- Added `$__toTime_ms` macro that represents the dashboard "to" time in milliseconds using a `DateTime64(3)`
- Added `$__timeFilter_ms` macro that uses `DateTime64(3)` for millisecond precision time filtering
- Re-added query type selector in dashboard view. This was only visible in explore view, but somehow it affects dashboard view, and so it has been re-added. (#730)
- When OTel is enabled, Trace ID queries now use a skip index to optimize exact ID lookups on large trace datasets (#724)

### Fixes

- Fixed performance issues caused by `$__timeFilter` using a `DateTime64(3)` instead of `DateTime` (#699)
- Fixed trace queries from rounding span durations under 1ms to `0` (#720)
- Fixed missing `AND` keyword when adding a filter to a Trace ID query

## 4.0.2

Expand Down
4 changes: 2 additions & 2 deletions src/components/configEditor/LogsConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ConfigSection, ConfigSubSection } from '@grafana/experimental';
import { Input, Field } from '@grafana/ui';
import { OtelVersionSelect } from 'components/queryBuilder/OtelVersionSelect';
import { ColumnHint } from 'types/queryBuilder';
import { versions as otelVersions } from 'otel';
import otel from 'otel';
import { LabeledInput } from './LabeledInput';
import { CHLogsConfig } from 'types/config';
import allLabels from 'labels';
Expand Down Expand Up @@ -33,7 +33,7 @@ export const LogsConfig = (props: LogsConfigProps) => {
} = (props.logsConfig || {});
const labels = allLabels.components.Config.LogsConfig;

const otelConfig = otelVersions.find(v => v.version === otelVersion);
const otelConfig = otel.getVersion(otelVersion);
if (otelEnabled && otelConfig) {
timeColumn = otelConfig.logColumnMap.get(ColumnHint.Time);
levelColumn = otelConfig.logColumnMap.get(ColumnHint.LogLevel);
Expand Down
4 changes: 2 additions & 2 deletions src/components/configEditor/TracesConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ConfigSection, ConfigSubSection } from '@grafana/experimental';
import { Input, Field } from '@grafana/ui';
import { OtelVersionSelect } from 'components/queryBuilder/OtelVersionSelect';
import { ColumnHint, TimeUnit } from 'types/queryBuilder';
import { versions as otelVersions } from 'otel';
import otel from 'otel';
import { LabeledInput } from './LabeledInput';
import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect';
import { CHTracesConfig } from 'types/config';
Expand Down Expand Up @@ -45,7 +45,7 @@ export const TracesConfig = (props: TraceConfigProps) => {
} = (props.tracesConfig || {}) as CHTracesConfig;
const labels = allLabels.components.Config.TracesConfig;

const otelConfig = otelVersions.find(v => v.version === otelVersion);
const otelConfig = otel.getVersion(otelVersion);
if (otelEnabled && otelConfig) {
startTimeColumn = otelConfig.traceColumnMap.get(ColumnHint.Time);
traceIdColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceId);
Expand Down
4 changes: 2 additions & 2 deletions src/components/queryBuilder/OtelVersionSelect.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { OtelVersionSelect } from './OtelVersionSelect';
import { versions as allVersions } from 'otel';
import otel from 'otel';

describe('OtelVersionSelect', () => {
const testVersion = allVersions[0];
const testVersion = otel.getLatestVersion();
const testVersionName = testVersion.name;

it('should render with empty properties', () => {
Expand Down
8 changes: 4 additions & 4 deletions src/components/queryBuilder/OtelVersionSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { SelectableValue } from '@grafana/data';
import { InlineFormLabel, Select, Switch as GrafanaSwitch, useTheme } from '@grafana/ui';
import { versions as allVersions } from 'otel';
import otel from 'otel';
import selectors from 'labels';

interface OtelVersionSelectProps {
Expand All @@ -15,15 +15,15 @@ interface OtelVersionSelectProps {
export const OtelVersionSelect = (props: OtelVersionSelectProps) => {
const { enabled, onEnabledChange, selectedVersion, onVersionChange, wide } = props;
const { label, tooltip } = selectors.components.OtelVersionSelect;
const options: SelectableValue[] = allVersions.map(v => ({
const options: SelectableValue[] = otel.versions.map(v => ({
label: v.name,
value: v.version
}));

useEffect(() => {
// Use latest version if not set or doesn't exist (which may happen if config is broken)
if (selectedVersion === '' || !allVersions.find(v => selectedVersion === v.version)) {
onVersionChange(allVersions[0].version);
if (selectedVersion === '' || !otel.getVersion(selectedVersion)) {
onVersionChange(otel.getLatestVersion().version);
}
}, [selectedVersion, onVersionChange]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useDefaultFilters, useDefaultTimeColumn, useLogDefaultsOnMount, useOtel
import { mockDatasource } from '__mocks__/datasource';
import { ColumnHint, QueryBuilderOptions, SelectedColumn, TableColumn } from 'types/queryBuilder';
import { setColumnByHint, setOptions } from 'hooks/useBuilderOptionsState';
import { versions as otelVersions } from 'otel';
import otel from 'otel';

describe('useLogDefaultsOnMount', () => {
it('should call builderOptionsDispatch with default log columns', async () => {
Expand Down Expand Up @@ -47,7 +47,7 @@ describe('useLogDefaultsOnMount', () => {
});

describe('useOtelColumns', () => {
const testOtelVersion = otelVersions[0]; // use latest version
const testOtelVersion = otel.getLatestVersion();

it('should not call builderOptionsDispatch if OTEL is already enabled', async () => {
const builderOptionsDispatch = jest.fn();
Expand Down
4 changes: 2 additions & 2 deletions src/components/queryBuilder/views/logsQueryBuilderHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { columnFilterDateTime } from "data/columnFilters";
import { BuilderOptionsReducerAction, setColumnByHint, setOptions } from "hooks/useBuilderOptionsState";
import { useEffect, useMemo, useRef } from "react";
import { ColumnHint, DateFilterWithoutValue, Filter, FilterOperator, OrderBy, OrderByDirection, QueryBuilderOptions, SelectedColumn, StringFilter, TableColumn } from "types/queryBuilder";
import { versions as otelVersions } from 'otel';
import otel from 'otel';

/**
* Loads the default configuration for new queries. (Only runs on new queries)
Expand Down Expand Up @@ -53,7 +53,7 @@ export const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builde
return;
}

const otelConfig = otelVersions.find(v => v.version === otelVersion);
const otelConfig = otel.getVersion(otelVersion);
const logColumnMap = otelConfig?.logColumnMap;
if (!logColumnMap) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useTraceDefaultsOnMount, useOtelColumns, useDefaultFilters } from './tr
import { mockDatasource } from '__mocks__/datasource';
import { ColumnHint, QueryBuilderOptions, SelectedColumn } from 'types/queryBuilder';
import { setOptions } from 'hooks/useBuilderOptionsState';
import { versions as otelVersions } from 'otel';
import otel from 'otel';

describe('useTraceDefaultsOnMount', () => {
it('should call builderOptionsDispatch with default trace columns', async () => {
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('useTraceDefaultsOnMount', () => {
});

describe('useOtelColumns', () => {
const testOtelVersion = otelVersions[0]; // use latest version
const testOtelVersion = otel.getLatestVersion();

it('should not call builderOptionsDispatch if OTEL is already enabled', async () => {
const builderOptionsDispatch = jest.fn();
Expand Down
4 changes: 2 additions & 2 deletions src/components/queryBuilder/views/traceQueryBuilderHooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react';
import { Datasource } from 'data/CHDatasource';
import { versions as otelVersions } from 'otel';
import otel from 'otel';
import { ColumnHint, DateFilterWithoutValue, Filter, FilterOperator, NumberFilter, OrderBy, OrderByDirection, QueryBuilderOptions, SelectedColumn, StringFilter } from 'types/queryBuilder';
import { BuilderOptionsReducerAction, setOptions } from 'hooks/useBuilderOptionsState';

Expand Down Expand Up @@ -54,7 +54,7 @@ export const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builde
return;
}

const otelConfig = otelVersions.find(v => v.version === otelVersion);
const otelConfig = otel.getVersion(otelVersion);
const traceColumnMap = otelConfig?.traceColumnMap;
if (!traceColumnMap) {
return;
Expand Down
6 changes: 3 additions & 3 deletions src/data/CHDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
TIME_FIELD_ALIAS,
} from './logs';
import { generateSql, getColumnByHint, logAliasToColumnHints } from './sqlGenerator';
import { versions as otelVersions } from 'otel';
import otel from 'otel';
import { ReactNode } from 'react';
import { transformQueryResponseWithTraceAndLogLinks } from './utils';
import { pluginVersion } from 'utils/version';
Expand Down Expand Up @@ -414,7 +414,7 @@ export class Datasource
const otelEnabled = logsConfig.otelEnabled;
const otelVersion = logsConfig.otelVersion;

const otelConfig = otelVersions.find(v => v.version === otelVersion);
const otelConfig = otel.getVersion(otelVersion);
if (otelEnabled && otelConfig) {
return otelConfig.logColumnMap;
}
Expand Down Expand Up @@ -452,7 +452,7 @@ export class Datasource
const otelEnabled = traceConfig.otelEnabled;
const otelVersion = traceConfig.otelVersion;

const otelConfig = otelVersions.find(v => v.version === otelVersion);
const otelConfig = otel.getVersion(otelVersion);
if (otelEnabled && otelConfig) {
return otelConfig.traceColumnMap;
}
Expand Down
55 changes: 50 additions & 5 deletions src/data/sqlGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,50 @@ describe('SQL Generator', () => {
expect(sql).toEqual(expectedSqlParts.join(' '));
});

it('generates trace ID query', () => {
it('generates trace ID query without OTel enabled', () => {
const opts: QueryBuilderOptions = {
database: 'default',
table: 'otel_traces',
queryType: QueryType.Traces,
columns: [
{ name: 'TraceId', type: 'String', hint: ColumnHint.TraceId },
{ name: 'SpanId', type: 'String', hint: ColumnHint.TraceSpanId },
{ name: 'ParentSpanId', type: 'String', hint: ColumnHint.TraceParentSpanId },
{ name: 'ServiceName', type: 'LowCardinality(String)', hint: ColumnHint.TraceServiceName },
{ name: 'SpanName', type: 'LowCardinality(String)', hint: ColumnHint.TraceOperationName },
{ name: 'Timestamp', type: 'DateTime64(9)', hint: ColumnHint.Time },
{ name: 'Duration', type: 'Int64', hint: ColumnHint.TraceDurationTime },
{ name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags },
{ name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags },
],
filters: [],
meta: {
minimized: true,
otelEnabled: false,
otelVersion: 'latest',
traceDurationUnit: TimeUnit.Nanoseconds,
isTraceIdMode: true,
traceId: 'abcdefg'
},
limit: 1000,
orderBy: []
};
const expectedSqlParts = [
'SELECT "TraceId" as traceID, "SpanId" as spanID, "ParentSpanId" as parentSpanID,',
'"ServiceName" as serviceName, "SpanName" as operationName, "Timestamp" as startTime,',
'multiply("Duration", 0.000001) as duration,',
`arrayMap(key -> map('key', key, 'value',"SpanAttributes"[key]),`,
`mapKeys("SpanAttributes")) as tags,`,
`arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags`,
`FROM "default"."otel_traces" WHERE traceID = 'abcdefg'`,
'LIMIT 1000'
];

const sql = generateSql(opts);
expect(sql).toEqual(expectedSqlParts.join(' '));
});

it('generates trace ID query with OTel enabled', () => {
const opts: QueryBuilderOptions = {
database: 'default',
table: 'otel_traces',
Expand Down Expand Up @@ -210,13 +253,15 @@ describe('SQL Generator', () => {
orderBy: []
};
const expectedSqlParts = [
`WITH 'abcdefg' as trace_id, (SELECT min(Start) FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_start,`,
`(SELECT max(End) + 1 FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_end`,
'SELECT "TraceId" as traceID, "SpanId" as spanID, "ParentSpanId" as parentSpanID,',
'"ServiceName" as serviceName, "SpanName" as operationName, "Timestamp" as startTime,',
'multiply("Duration", 0.000001) as duration,',
'arrayMap(key -> map(\'key\', key, \'value\',"SpanAttributes"[key]),',
'mapKeys("SpanAttributes")) as tags,',
'arrayMap(key -> map(\'key\', key, \'value\',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags',
'FROM "default"."otel_traces" WHERE traceID = \'abcdefg\'',
`arrayMap(key -> map('key', key, 'value',"SpanAttributes"[key]),`,
`mapKeys("SpanAttributes")) as tags,`,
`arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags`,
`FROM "default"."otel_traces" WHERE traceID = trace_id AND startTime >= trace_start AND startTime <= trace_end`,
'LIMIT 1000'
];

Expand Down
27 changes: 25 additions & 2 deletions src/data/sqlGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BooleanFilter, BuilderMode, ColumnHint, DateFilterWithValue, FilterOperator, MultiFilter, NumberFilter, QueryBuilderOptions, QueryType, SelectedColumn, StringFilter, TimeUnit } from 'types/queryBuilder';
import otel from 'otel';

/**
* Generates a SQL string for the given QueryBuilderOptions
Expand Down Expand Up @@ -145,24 +146,46 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => {
}
const selectPartsSql = selectParts.join(', ');

// Optimize trace ID filtering for OTel enabled trace lookups
const hasTraceIdFilter = options.meta?.isTraceIdMode && options.meta?.traceId;
const otelVersion = otel.getVersion(options.meta?.otelVersion);
const applyTraceIdOptimization = hasTraceIdFilter && options.meta?.otelEnabled && otelVersion;
if (applyTraceIdOptimization) {
const traceId = options.meta!.traceId;
const timestampTable = getTableIdentifier(database, otelVersion.traceTimestampTable);
queryParts.push('WITH');
queryParts.push(`'${traceId}' as trace_id,`);
queryParts.push(`(SELECT min(Start) FROM ${timestampTable} WHERE TraceId = trace_id) as trace_start,`);
queryParts.push(`(SELECT max(End) + 1 FROM ${timestampTable} WHERE TraceId = trace_id) as trace_end`);
}

queryParts.push('SELECT');
queryParts.push(selectPartsSql);
queryParts.push('FROM');
queryParts.push(getTableIdentifier(database, table));

const hasTraceIdFilter = options.meta?.isTraceIdMode && options.meta?.traceId
const filterParts = getFilters(options);

if (hasTraceIdFilter || filterParts) {
queryParts.push('WHERE');
}

if (hasTraceIdFilter) {
if (applyTraceIdOptimization) {
queryParts.push('traceID = trace_id');
queryParts.push('AND');
queryParts.push(`startTime >= trace_start`);
queryParts.push('AND');
queryParts.push(`startTime <= trace_end`);
} else if (hasTraceIdFilter) {
const traceId = options.meta!.traceId;
queryParts.push(`traceID = '${traceId}'`);
}

if (filterParts) {
if (hasTraceIdFilter) {
queryParts.push('AND');
}

queryParts.push(filterParts);
}

Expand Down
18 changes: 17 additions & 1 deletion src/otel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ColumnHint, TimeUnit } from "types/queryBuilder";

const defaultLogsTable = 'otel_logs';
const defaultTraceTable = 'otel_traces'
const defaultTraceTable = 'otel_traces';
const defaultTraceTimestampTable = 'otel_traces_trace_id_ts';

export interface OtelVersion {
name: string;
Expand All @@ -11,6 +12,7 @@ export interface OtelVersion {
logColumnMap: Map<ColumnHint, string>;
logLevels: string[];
traceTable: string;
traceTimestampTable: string;
traceColumnMap: Map<ColumnHint, string>;
traceDurationUnit: TimeUnit.Nanoseconds;
}
Expand All @@ -35,6 +37,7 @@ const otel129: OtelVersion = {
'FATAL'
],
traceTable: defaultTraceTable,
traceTimestampTable: defaultTraceTimestampTable,
traceColumnMap: new Map<ColumnHint, string>([
[ColumnHint.Time, 'Timestamp'],
[ColumnHint.TraceId, 'TraceId'],
Expand All @@ -56,3 +59,16 @@ export const versions: readonly OtelVersion[] = [
];

export const getLatestVersion = (): OtelVersion => versions[0];
export const getVersion = (version: string | undefined): OtelVersion | undefined => {
if (!version) {
return;
}

return versions.find(v => v.version === version);
};

export default {
versions,
getLatestVersion,
getVersion
};

0 comments on commit e0e29bf

Please sign in to comment.