@ -29,10 +29,11 @@ import {
dateTime ,
AbsoluteTimeRange ,
sortInAscendingOrder ,
rangeUtil ,
} from '@grafana/data' ;
import { getThemeColor } from 'app/core/utils/colors' ;
import { SIPrefix } from '@grafana/data/src/valueFormats/symbolFormatters ';
export const LIMIT_LABEL = 'Line limit ';
export const LogLevelColor = {
[ LogLevel . critical ] : colors [ 7 ] ,
@ -204,15 +205,21 @@ export function dataFrameToLogsModel(
const { logSeries } = separateLogsAndMetrics ( dataFrame ) ;
const logsModel = logSeriesToLogsModel ( logSeries ) ;
// unification: Removed logic for using metrics data in LogsModel as with the unification changes this would result
// in the incorrect data being used. Instead logs series are always derived from logs.
if ( logsModel ) {
// Create histogram metrics from logs using the interval as bucket size for the line count
if ( intervalMs && logsModel . rows . length > 0 ) {
const sortedRows = logsModel . rows . sort ( sortInAscendingOrder ) ;
const { visibleRange , bucketSize } = getSeriesProperties ( sortedRows , intervalMs , absoluteRange ) ;
const { visibleRange , bucketSize , visibleRangeMs , requestedRangeMs } = getSeriesProperties (
sortedRows ,
intervalMs ,
absoluteRange
) ;
logsModel . visibleRange = visibleRange ;
logsModel . series = makeSeriesForLogs ( sortedRows , bucketSize , timeZone ) ;
if ( logsModel . meta ) {
logsModel . meta = adjustMetaInfo ( logsModel , visibleRangeMs , requestedRangeMs ) ;
}
} else {
logsModel . series = [ ] ;
}
@ -245,23 +252,31 @@ export function getSeriesProperties(
let visibleRange = absoluteRange ;
let resolutionIntervalMs = intervalMs ;
let bucketSize = Math . max ( resolutionIntervalMs * pxPerBar , minimumBucketSize ) ;
let visibleRangeMs ;
let requestedRangeMs ;
// Clamp time range to visible logs otherwise big parts of the graph might look empty
if ( absoluteRange ) {
const earliest = sortedRows [ 0 ] . timeEpochMs ;
const latest = absoluteRange . to ;
const visibleRangeMs = latest - earliest ;
const earliestTsLogs = sortedRows [ 0 ] . timeEpochMs ;
requestedRangeMs = absoluteRange . to - absoluteRange . from ;
visibleRangeMs = absoluteRange . to - earliestTsLogs ;
if ( visibleRangeMs > 0 ) {
// Adjust interval bucket size for potentially shorter visible range
const clampingFactor = visibleRangeMs / ( absoluteRange . to - absoluteRange . from ) ;
const clampingFactor = visibleRangeMs / requestedRangeMs ;
resolutionIntervalMs *= clampingFactor ;
// Minimum bucketsize of 1s for nicer graphing
bucketSize = Math . max ( Math . ceil ( resolutionIntervalMs * pxPerBar ) , minimumBucketSize ) ;
// makeSeriesForLogs() aligns dataspoints with time buckets, so we do the same here to not cut off data
const adjustedEarliest = Math . floor ( earliest / bucketSize ) * bucketSize ;
visibleRange = { from : adjustedEarliest , to : latest } ;
const adjustedEarliest = Math . floor ( earliestTsLogs / bucketSize ) * bucketSize ;
visibleRange = { from : adjustedEarliest , to : absoluteRange.to } ;
} else {
// We use visibleRangeMs to calculate range coverage of received logs. However, some data sources are rounding up range in requests. This means that received logs
// can (in edge cases) be outside of the requested range and visibleRangeMs < 0. In that case, we want to change visibleRangeMs to be 1 so we can calculate coverage.
visibleRangeMs = 1 ;
}
}
return { bucketSize , visibleRange } ;
return { bucketSize , visibleRange , visibleRangeMs , requestedRangeMs } ;
}
function separateLogsAndMetrics ( dataFrames : DataFrame [ ] ) {
@ -413,26 +428,19 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi
acc [ elem . refId ] = elem . meta . limit ;
return acc ;
} , { } )
) . reduce ( ( acc : number , elem : any ) = > ( acc += elem ) , 0 ) ;
) . reduce ( ( acc : number , elem : any ) = > ( acc += elem ) , 0 ) as number ;
if ( limits . length > 0 ) {
if ( limitValue > 0 ) {
meta . push ( {
label : 'Limit' ,
value : ` ${ limitValue } ( ${ rows . length } returned) ` ,
kind : LogsMetaKind.String ,
label : LIMIT_LABEL ,
value : limitValue ,
kind : LogsMetaKind.Number ,
} ) ;
}
// Hack to print loki stats in Explore. Should be using proper stats display via drawer in Explore (rework in 7.1)
let totalBytes = 0 ;
const queriesVisited : { [ refId : string ] : boolean } = { } ;
// To add just 1 error message
let errorMetaAdded = false ;
for ( const series of logSeries ) {
const totalBytesKey = series . meta ? . custom ? . lokiQueryStatKey ;
const { refId } = series ; // Stats are per query, keeping track by refId
if ( ! errorMetaAdded && series . meta ? . custom ? . error ) {
meta . push ( {
label : '' ,
@ -441,28 +449,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi
} ) ;
errorMetaAdded = true ;
}
if ( refId && ! queriesVisited [ refId ] ) {
if ( totalBytesKey && series . meta ? . stats ) {
const byteStat = series . meta . stats . find ( ( stat ) = > stat . displayName === totalBytesKey ) ;
if ( byteStat ) {
totalBytes += byteStat . value ;
}
}
queriesVisited [ refId ] = true ;
}
}
if ( totalBytes > 0 ) {
const { text , suffix } = SIPrefix ( 'B' ) ( totalBytes ) ;
meta . push ( {
label : 'Total bytes processed' ,
value : ` ${ text } ${ suffix } ` ,
kind : LogsMetaKind.String ,
} ) ;
}
return {
hasUniqueLabels ,
meta ,
@ -480,3 +467,33 @@ function getIdField(fieldCache: FieldCache): FieldWithIndex | undefined {
}
return undefined ;
}
// Used to add additional information to Line limit meta info
function adjustMetaInfo ( logsModel : LogsModel , visibleRangeMs? : number , requestedRangeMs? : number ) : LogsMetaItem [ ] {
let logsModelMeta = [ . . . logsModel . meta ! ] ;
const limitIndex = logsModelMeta . findIndex ( ( meta ) = > meta . label === LIMIT_LABEL ) ;
const limit = limitIndex && logsModelMeta [ limitIndex ] ? . value ;
if ( limit && limit > 0 ) {
let metaLimitValue ;
if ( limit === logsModel . rows . length && visibleRangeMs && requestedRangeMs ) {
const coverage = ( ( visibleRangeMs / requestedRangeMs ) * 100 ) . toFixed ( 2 ) ;
metaLimitValue = ` ${ limit } reached, received logs cover ${ coverage } % ( ${ rangeUtil . msRangeToTimeString (
visibleRangeMs
) } ) of your selected time range ( $ { rangeUtil . msRangeToTimeString ( requestedRangeMs ) } ) ` ;
} else {
metaLimitValue = ` ${ limit } ( ${ logsModel . rows . length } returned) ` ;
}
logsModelMeta [ limitIndex ] = {
label : LIMIT_LABEL ,
value : metaLimitValue ,
kind : LogsMetaKind.String ,
} ;
}
return logsModelMeta ;
}