@ -27,6 +27,7 @@ import {
getDisplayProcessor ,
getDisplayProcessor ,
textUtil ,
textUtil ,
dateTime ,
dateTime ,
AbsoluteTimeRange ,
} from '@grafana/data' ;
} from '@grafana/data' ;
import { getThemeColor } from 'app/core/utils/colors' ;
import { getThemeColor } from 'app/core/utils/colors' ;
@ -90,18 +91,14 @@ export function filterLogLevels(logRows: LogRowModel[], hiddenLogLevels: Set<Log
} ) ;
} ) ;
}
}
export function makeSeriesForLogs ( rows : LogRowModel [ ] , intervalMs : number , timeZone : TimeZone ) : GraphSeriesXY [ ] {
export function makeSeriesForLogs ( so rtedR ows : LogRowModel [ ] , bucketSize : number , timeZone : TimeZone ) : GraphSeriesXY [ ] {
// currently interval is rangeMs / resolution, which is too low for showing series as bars.
// currently interval is rangeMs / resolution, which is too low for showing series as bars.
// need at least 10px per bucket, so we multiply interval by 10. Should be solved higher up the chain
// Should be solved higher up the chain when executing queries & interval calculated and not here but this is a temporary fix.
// when executing queries & interval calculated and not here but this is a temporary fix.
// intervalMs = intervalMs * 10;
// Graph time series by log level
// Graph time series by log level
const seriesByLevel : any = { } ;
const seriesByLevel : any = { } ;
const bucketSize = intervalMs * 10 ;
const seriesList : any [ ] = [ ] ;
const seriesList : any [ ] = [ ] ;
const sortedRows = rows . sort ( sortInAscendingOrder ) ;
for ( const row of sortedRows ) {
for ( const row of sortedRows ) {
let series = seriesByLevel [ row . logLevel ] ;
let series = seriesByLevel [ row . logLevel ] ;
@ -198,16 +195,23 @@ function isLogsData(series: DataFrame) {
export function dataFrameToLogsModel (
export function dataFrameToLogsModel (
dataFrame : DataFrame [ ] ,
dataFrame : DataFrame [ ] ,
intervalMs : number | undefined ,
intervalMs : number | undefined ,
timeZone : TimeZone
timeZone : TimeZone ,
absoluteRange? : AbsoluteTimeRange
) : LogsModel {
) : LogsModel {
const { logSeries , metricSeries } = separateLogsAndMetrics ( dataFrame ) ;
const { logSeries , metricSeries } = separateLogsAndMetrics ( dataFrame ) ;
const logsModel = logSeriesToLogsModel ( logSeries ) ;
const logsModel = logSeriesToLogsModel ( logSeries ) ;
if ( logsModel ) {
if ( logsModel ) {
if ( metricSeries . length === 0 ) {
if ( metricSeries . length === 0 ) {
// Create metrics from logs
// Create histogram metrics from logs using the interval as bucket size for the line count
// If interval is not defined or 0 we cannot really compute the series
if ( intervalMs && logsModel . rows . length > 0 ) {
logsModel . series = intervalMs ? makeSeriesForLogs ( logsModel . rows , intervalMs , timeZone ) : [ ] ;
const sortedRows = logsModel . rows . sort ( sortInAscendingOrder ) ;
const { visibleRange , bucketSize } = getSeriesProperties ( sortedRows , intervalMs , absoluteRange ) ;
logsModel . visibleRange = visibleRange ;
logsModel . series = makeSeriesForLogs ( sortedRows , bucketSize , timeZone ) ;
} else {
logsModel . series = [ ] ;
}
} else {
} else {
// We got metrics in the dataFrame so process those
// We got metrics in the dataFrame so process those
logsModel . series = getGraphSeriesModel (
logsModel . series = getGraphSeriesModel (
@ -234,6 +238,43 @@ export function dataFrameToLogsModel(
} ;
} ;
}
}
/ * *
* Returns a clamped time range and interval based on the visible logs and the given range .
*
* @param sortedRows Log rows from the query response
* @param intervalMs Dynamnic data interval based on available pixel width
* @param absoluteRange Requested time range
* @param pxPerBar Default : 20 , buckets will be rendered as bars , assuming 10 px per histogram bar plus some free space around it
* /
export function getSeriesProperties (
sortedRows : LogRowModel [ ] ,
intervalMs : number ,
absoluteRange : AbsoluteTimeRange ,
pxPerBar = 20 ,
minimumBucketSize = 1000
) {
let visibleRange = absoluteRange ;
let resolutionIntervalMs = intervalMs ;
let bucketSize = Math . max ( resolutionIntervalMs * pxPerBar , minimumBucketSize ) ;
// 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 ;
if ( visibleRangeMs > 0 ) {
// Adjust interval bucket size for potentially shorter visible range
const clampingFactor = visibleRangeMs / ( absoluteRange . to - absoluteRange . from ) ;
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 } ;
}
}
return { bucketSize , visibleRange } ;
}
function separateLogsAndMetrics ( dataFrames : DataFrame [ ] ) {
function separateLogsAndMetrics ( dataFrames : DataFrame [ ] ) {
const metricSeries : DataFrame [ ] = [ ] ;
const metricSeries : DataFrame [ ] = [ ] ;
const logSeries : DataFrame [ ] = [ ] ;
const logSeries : DataFrame [ ] = [ ] ;