diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 1121bd7a72f..fc10b771cd9 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -32,6 +32,7 @@ import { getThemeColor } from 'app/core/utils/colors'; import { sortInAscendingOrder, deduplicateLogRowsById } from 'app/core/utils/explore'; import { getGraphSeriesModel } from 'app/plugins/panel/graph2/getGraphSeriesModel'; +import { decimalSIPrefix } from '@grafana/data/src/valueFormats/symbolFormatters'; export const LogLevelColor = { [LogLevel.critical]: colors[7], @@ -375,6 +376,26 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi }); } + // Hack to print loki stats in Explore. Should be using proper stats display via drawer in Explore (rework in 7.1) + let totalBytes = 0; + for (const series of logSeries) { + const totalBytesKey = series.meta?.custom?.lokiQueryStatKey; + if (totalBytesKey && series.meta.stats) { + const byteStat = series.meta.stats.find(stat => stat.title === totalBytesKey); + if (byteStat) { + totalBytes += byteStat.value; + } + } + } + if (totalBytes > 0) { + const { text, suffix } = decimalSIPrefix('B')(totalBytes); + meta.push({ + label: 'Total bytes processed', + value: `${text} ${suffix}`, + kind: LogsMetaKind.String, + }); + } + return { hasUniqueLabels, meta, diff --git a/public/app/plugins/datasource/loki/result_transformer.test.ts b/public/app/plugins/datasource/loki/result_transformer.test.ts index ec0f1fa86f1..5070ebda594 100644 --- a/public/app/plugins/datasource/loki/result_transformer.test.ts +++ b/public/app/plugins/datasource/loki/result_transformer.test.ts @@ -1,5 +1,5 @@ import { CircularDataFrame, FieldCache, FieldType, MutableDataFrame } from '@grafana/data'; -import { LokiStreamResult, LokiTailResponse } from './types'; +import { LokiStreamResult, LokiTailResponse, LokiStreamResponse, LokiResultType } from './types'; import * as ResultTransformer from './result_transformer'; import { enhanceDataFrame } from './result_transformer'; @@ -18,6 +18,19 @@ const streamResult: LokiStreamResult[] = [ }, ]; +const lokiResponse: LokiStreamResponse = { + status: 'success', + data: { + result: streamResult, + resultType: LokiResultType.Stream, + stats: { + summary: { + bytesTotal: 900, + }, + }, + }, +}; + describe('loki result transformer', () => { afterAll(() => { jest.restoreAllMocks(); @@ -45,7 +58,7 @@ describe('loki result transformer', () => { describe('lokiStreamsToDataframes', () => { it('should enhance data frames', () => { jest.spyOn(ResultTransformer, 'enhanceDataFrame'); - const dataFrames = ResultTransformer.lokiStreamsToDataframes(streamResult, { refId: 'B' }, 500, { + const dataFrames = ResultTransformer.lokiStreamsToDataframes(lokiResponse, { refId: 'B' }, 500, { derivedFields: [ { matcherRegex: 'trace=(w+)', diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index fe45629725d..94a3328cfb5 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -14,6 +14,7 @@ import { DataFrameView, DataLink, Field, + QueryResultMetaStat, } from '@grafana/data'; import templateSrv from 'app/features/templating/template_srv'; @@ -31,6 +32,8 @@ import { LokiQuery, LokiOptions, DerivedFieldConfig, + LokiStreamResponse, + LokiStats, } from './types'; /** @@ -257,13 +260,48 @@ function getOriginalMetricName(labelData: { [key: string]: string }) { return `${metricName}{${labelPart}}`; } +export function decamelize(s: string): string { + return s.replace(/[A-Z]/g, m => ` ${m.toLowerCase()}`); +} + +// Turn loki stats { metric: value } into meta stat { title: metric, value: value } +function lokiStatsToMetaStat(stats: LokiStats): QueryResultMetaStat[] { + const result: QueryResultMetaStat[] = []; + if (!stats) { + return result; + } + for (const section in stats) { + const values = stats[section]; + for (const label in values) { + const value = values[label]; + let unit; + if (/time/i.test(label) && value) { + unit = 's'; + } else if (/bytes.*persecond/i.test(label)) { + unit = 'Bps'; + } else if (/bytes/i.test(label)) { + unit = 'decbytes'; + } + const title = `${_.capitalize(section)}: ${decamelize(label)}`; + result.push({ title, value, unit }); + } + } + return result; +} + export function lokiStreamsToDataframes( - data: LokiStreamResult[], + response: LokiStreamResponse, target: { refId: string; expr?: string; regexp?: string }, limit: number, config: LokiOptions, reverse = false ): DataFrame[] { + const data = limit > 0 ? response.data.result : []; + const stats: QueryResultMetaStat[] = lokiStatsToMetaStat(response.data.stats); + // Use custom mechanism to identify which stat we want to promote to label + const custom = { + lokiQueryStatKey: 'Summary: totalBytesProcessed', + }; const series: DataFrame[] = data.map(stream => { const dataFrame = lokiStreamResultToDataFrame(stream, reverse); enhanceDataFrame(dataFrame, config); @@ -273,6 +311,8 @@ export function lokiStreamsToDataframes( meta: { searchWords: getHighlighterExpressionsFromQuery(formatQuery(target.expr, target.regexp)), limit, + stats, + custom, }, }; }); @@ -378,7 +418,7 @@ export function processRangeQueryResponse( switch (response.data.resultType) { case LokiResultType.Stream: return of({ - data: lokiStreamsToDataframes(limit > 0 ? response.data.result : [], target, limit, config, reverse), + data: lokiStreamsToDataframes(response as LokiStreamResponse, target, limit, config, reverse), key: `${target.refId}_log`, }); diff --git a/public/app/plugins/datasource/loki/types.ts b/public/app/plugins/datasource/loki/types.ts index 9cebc847b28..306f333bc38 100644 --- a/public/app/plugins/datasource/loki/types.ts +++ b/public/app/plugins/datasource/loki/types.ts @@ -39,6 +39,12 @@ export interface LokiOptions extends DataSourceJsonData { derivedFields?: DerivedFieldConfig[]; } +export interface LokiStats { + [component: string]: { + [label: string]: number; + }; +} + export interface LokiVectorResult { metric: { [label: string]: string }; value: [number, string]; @@ -49,6 +55,7 @@ export interface LokiVectorResponse { data: { resultType: LokiResultType.Vector; result: LokiVectorResult[]; + stats?: LokiStats; }; } @@ -62,6 +69,7 @@ export interface LokiMatrixResponse { data: { resultType: LokiResultType.Matrix; result: LokiMatrixResult[]; + stats?: LokiStats; }; } @@ -75,6 +83,7 @@ export interface LokiStreamResponse { data: { resultType: LokiResultType.Stream; result: LokiStreamResult[]; + stats?: LokiStats; }; }