2021-08-19 06:38:31 +02:00
import { cloneDeep , defaults } from 'lodash' ;
2022-04-22 14:33:13 +01:00
import LRU from 'lru-cache' ;
import React from 'react' ;
2021-10-12 13:16:09 +02:00
import { forkJoin , lastValueFrom , merge , Observable , of , OperatorFunction , pipe , throwError } from 'rxjs' ;
2021-08-19 06:38:31 +02:00
import { catchError , filter , map , tap } from 'rxjs/operators' ;
2022-04-22 14:33:13 +01:00
2019-10-31 10:48:05 +01:00
import {
AnnotationEvent ,
2019-12-27 09:11:16 +01:00
CoreApp ,
2019-10-31 10:48:05 +01:00
DataQueryError ,
DataQueryRequest ,
DataQueryResponse ,
DataSourceInstanceSettings ,
2021-12-14 14:36:47 +01:00
DataSourceWithQueryExportSupport ,
DataSourceWithQueryImportSupport ,
2020-02-05 16:40:37 +01:00
dateMath ,
DateTime ,
2021-12-14 14:36:47 +01:00
AbstractQuery ,
2020-02-05 16:40:37 +01:00
LoadingState ,
2020-10-01 12:58:06 +02:00
rangeUtil ,
2020-01-24 09:50:09 +01:00
ScopedVars ,
2020-02-05 16:40:37 +01:00
TimeRange ,
2021-11-02 16:46:20 +01:00
DataFrame ,
2021-11-24 12:43:39 +01:00
dateTime ,
2019-10-31 10:48:05 +01:00
} from '@grafana/data' ;
2021-11-02 16:46:20 +01:00
import {
BackendSrvRequest ,
FetchError ,
FetchResponse ,
getBackendSrv ,
DataSourceWithBackend ,
BackendDataSourceResponse ,
toDataQueryResponse ,
} from '@grafana/runtime' ;
2022-04-22 14:33:13 +01:00
import { Badge , BadgeColor , Tooltip } from '@grafana/ui' ;
2020-10-01 12:58:06 +02:00
import { safeStringifyValue } from 'app/core/utils/explore' ;
2022-05-18 10:45:26 +02:00
import { discoverDataSourceFeatures } from 'app/features/alerting/unified/api/buildInfo' ;
2020-10-01 18:51:23 +01:00
import { getTimeSrv , TimeSrv } from 'app/features/dashboard/services/TimeSrv' ;
import { getTemplateSrv , TemplateSrv } from 'app/features/templating/template_srv' ;
2022-05-18 10:45:26 +02:00
import { PromApplication , PromApiFeatures } from 'app/types/unified-alerting-dto' ;
2022-04-22 14:33:13 +01:00
2022-03-15 17:37:20 +01:00
import { addLabelToQuery } from './add_label_to_query' ;
2022-05-24 17:43:58 +02:00
import { AnnotationQueryEditor } from './components/AnnotationQueryEditor' ;
2020-10-01 12:58:06 +02:00
import PrometheusLanguageProvider from './language_provider' ;
2018-11-16 14:31:51 +00:00
import { expandRecordingRules } from './language_utils' ;
2022-04-22 14:33:13 +01:00
import { renderLegendFormat } from './legend' ;
import PrometheusMetricFindQuery from './metric_find_query' ;
2021-08-19 06:38:31 +02:00
import { getInitHints , getQueryHints } from './query_hints' ;
2021-12-14 19:19:16 -08:00
import { getOriginalMetricName , transform , transformV2 } from './result_transformer' ;
2020-10-01 12:58:06 +02:00
import {
2021-01-15 16:20:20 +01:00
ExemplarTraceIdDestination ,
2020-10-01 12:58:06 +02:00
PromDataErrorResponse ,
PromDataSuccessResponse ,
2021-01-15 16:20:20 +01:00
PromExemplarData ,
2020-10-01 12:58:06 +02:00
PromMatrixData ,
PromOptions ,
PromQuery ,
PromQueryRequest ,
2021-10-26 14:47:40 +02:00
PromQueryType ,
2020-10-01 12:58:06 +02:00
PromScalarData ,
PromVectorData ,
} from './types' ;
2020-11-18 15:10:32 +01:00
import { PrometheusVariableSupport } from './variables' ;
2019-06-18 11:01:12 +02:00
2022-05-31 11:50:23 +02:00
const ANNOTATION_QUERY_STEP_DEFAULT = '60s' ;
2021-04-06 18:35:00 +02:00
const GET_AND_POST_METADATA_ENDPOINTS = [ 'api/v1/query' , 'api/v1/query_range' , 'api/v1/series' , 'api/v1/labels' ] ;
2020-02-05 16:40:37 +01:00
2021-12-14 14:36:47 +01:00
export class PrometheusDatasource
extends DataSourceWithBackend < PromQuery , PromOptions >
2022-02-02 12:02:32 +00:00
implements DataSourceWithQueryImportSupport < PromQuery > , DataSourceWithQueryExportSupport < PromQuery >
{
2017-08-10 09:33:17 +02:00
type : string ;
editorSrc : string ;
2018-08-08 16:56:21 +02:00
ruleMappings : { [ index : string ] : string } ;
2017-08-10 09:33:17 +02:00
url : string ;
2021-11-02 16:46:20 +01:00
id : number ;
2017-08-10 09:33:17 +02:00
directUrl : string ;
2021-09-17 13:39:26 +02:00
access : 'direct' | 'proxy' ;
2017-08-10 09:33:17 +02:00
basicAuth : any ;
withCredentials : any ;
2022-02-18 10:23:06 +01:00
metricsNameCache = new LRU < string , string [ ] > ( { max : 10 } ) ;
2017-11-15 11:22:00 +01:00
interval : string ;
2021-10-18 11:21:30 +02:00
queryTimeout : string | undefined ;
2017-11-12 16:38:10 +09:00
httpMethod : string ;
2018-10-25 12:24:24 +02:00
languageProvider : PrometheusLanguageProvider ;
2021-01-15 16:20:20 +01:00
exemplarTraceIdDestinations : ExemplarTraceIdDestination [ ] | undefined ;
2020-05-05 18:02:55 +02:00
lookupsDisabled : boolean ;
2019-09-18 18:59:23 +02:00
customQueryParameters : any ;
2021-10-12 13:16:09 +02:00
exemplarsAvailable : boolean ;
2022-04-04 19:30:17 +02:00
subType : PromApplication ;
rulerEnabled : boolean ;
2017-08-10 09:33:17 +02:00
2020-10-01 18:51:23 +01:00
constructor (
instanceSettings : DataSourceInstanceSettings < PromOptions > ,
private readonly templateSrv : TemplateSrv = getTemplateSrv ( ) ,
2022-01-31 07:57:14 +01:00
private readonly timeSrv : TimeSrv = getTimeSrv ( ) ,
languageProvider? : PrometheusLanguageProvider
2020-10-01 18:51:23 +01:00
) {
2019-05-10 02:37:43 -07:00
super ( instanceSettings ) ;
2017-12-21 08:39:31 +01:00
this . type = 'prometheus' ;
2022-04-04 19:30:17 +02:00
this . subType = PromApplication . Prometheus ;
this . rulerEnabled = false ;
2017-12-21 08:39:31 +01:00
this . editorSrc = 'app/features/prometheus/partials/query.editor.html' ;
2021-11-02 16:46:20 +01:00
this . id = instanceSettings . id ;
2020-07-06 21:16:27 +02:00
this . url = instanceSettings . url ! ;
2021-09-17 13:39:26 +02:00
this . access = instanceSettings . access ;
2017-08-10 09:33:17 +02:00
this . basicAuth = instanceSettings . basicAuth ;
this . withCredentials = instanceSettings . withCredentials ;
2017-12-21 08:39:31 +01:00
this . interval = instanceSettings . jsonData . timeInterval || '15s' ;
2018-05-01 12:42:58 +09:00
this . queryTimeout = instanceSettings . jsonData . queryTimeout ;
2021-05-25 18:56:46 +02:00
this . httpMethod = instanceSettings . jsonData . httpMethod || 'POST' ;
2021-10-18 11:21:30 +02:00
// `directUrl` is never undefined, we set it at https://github.com/grafana/grafana/blob/main/pkg/api/frontendsettings.go#L108
// here we "fall back" to this.url to make typescript happy, but it should never happen
this . directUrl = instanceSettings . jsonData . directUrl ? ? this . url ;
2021-01-15 16:20:20 +01:00
this . exemplarTraceIdDestinations = instanceSettings . jsonData . exemplarTraceIdDestinations ;
2018-08-08 16:56:21 +02:00
this . ruleMappings = { } ;
2022-01-31 07:57:14 +01:00
this . languageProvider = languageProvider ? ? new PrometheusLanguageProvider ( this ) ;
2020-07-06 21:16:27 +02:00
this . lookupsDisabled = instanceSettings . jsonData . disableMetricsLookup ? ? false ;
2019-09-18 18:59:23 +02:00
this . customQueryParameters = new URLSearchParams ( instanceSettings . jsonData . customQueryParameters ) ;
2020-11-18 15:10:32 +01:00
this . variables = new PrometheusVariableSupport ( this , this . templateSrv , this . timeSrv ) ;
2021-10-12 13:16:09 +02:00
this . exemplarsAvailable = true ;
2022-05-24 17:43:58 +02:00
// This needs to be here and cannot be static because of how annotations typing affects casting of data source
// objects to DataSourceApi types.
// We don't use the default processing for prometheus.
// See standardAnnotationSupport.ts/[shouldUseMappingUI|shouldUseLegacyRunner]
this . annotations = {
QueryEditor : AnnotationQueryEditor ,
} ;
2018-08-08 16:56:21 +02:00
}
2021-10-12 13:16:09 +02:00
init = async ( ) = > {
2018-08-08 16:56:21 +02:00
this . loadRules ( ) ;
2021-10-12 13:16:09 +02:00
this . exemplarsAvailable = await this . areExemplarsAvailable ( ) ;
2019-05-10 02:37:43 -07:00
} ;
2017-08-10 09:33:17 +02:00
2019-04-10 09:31:32 -07:00
getQueryDisplayText ( query : PromQuery ) {
return query . expr ;
}
2019-11-21 15:36:56 +00:00
_addTracingHeaders ( httpOptions : PromQueryRequest , options : DataQueryRequest < PromQuery > ) {
httpOptions . headers = { } ;
2019-04-30 13:28:35 +02:00
const proxyMode = ! this . url . match ( /^http/ ) ;
if ( proxyMode ) {
httpOptions . headers [ 'X-Dashboard-Id' ] = options . dashboardId ;
httpOptions . headers [ 'X-Panel-Id' ] = options . panelId ;
}
}
2021-05-12 19:30:41 +02:00
/ * *
* Any request done from this data source should go through here as it contains some common processing for the
* request . Any processing done here needs to be also copied on the backend as this goes through data source proxy
* but not through the same code as alerting .
* /
_request < T = any > (
url : string ,
data : Record < string , string > | null ,
overrides : Partial < BackendSrvRequest > = { }
) : Observable < FetchResponse < T > > {
data = data || { } ;
for ( const [ key , value ] of this . customQueryParameters ) {
if ( data [ key ] == null ) {
data [ key ] = value ;
}
}
2020-07-09 15:16:35 +02:00
const options : BackendSrvRequest = defaults ( overrides , {
2016-02-01 23:24:08 +01:00
url : this.url + url ,
2018-04-24 12:27:37 +02:00
method : this.httpMethod ,
2019-05-01 17:23:47 +02:00
headers : { } ,
2018-08-29 14:27:29 +02:00
} ) ;
2018-04-24 12:27:37 +02:00
if ( options . method === 'GET' ) {
2019-11-21 15:36:56 +00:00
if ( data && Object . keys ( data ) . length ) {
2017-11-12 16:38:10 +09:00
options . url =
options . url +
2020-11-26 11:56:14 +02:00
( options . url . search ( /\?/ ) >= 0 ? '&' : '?' ) +
2019-11-21 15:36:56 +00:00
Object . entries ( data )
. map ( ( [ k , v ] ) = > ` ${ encodeURIComponent ( k ) } = ${ encodeURIComponent ( v ) } ` )
. join ( '&' ) ;
2017-11-12 16:38:10 +09:00
}
} else {
2020-07-09 15:16:35 +02:00
options . headers ! [ 'Content-Type' ] = 'application/x-www-form-urlencoded' ;
2017-11-12 16:38:10 +09:00
options . data = data ;
}
2016-02-01 23:24:08 +01:00
if ( this . basicAuth || this . withCredentials ) {
options . withCredentials = true ;
}
2017-08-10 09:33:17 +02:00
2016-02-01 23:24:08 +01:00
if ( this . basicAuth ) {
2020-07-09 15:16:35 +02:00
options . headers ! . Authorization = this . basicAuth ;
2016-02-01 23:24:08 +01:00
}
2020-08-20 21:03:59 -07:00
return getBackendSrv ( ) . fetch < T > ( options ) ;
2016-03-03 10:49:30 +01:00
}
2021-12-14 14:36:47 +01:00
async importFromAbstractQueries ( abstractQueries : AbstractQuery [ ] ) : Promise < PromQuery [ ] > {
return abstractQueries . map ( ( abstractQuery ) = > this . languageProvider . importFromAbstractQuery ( abstractQuery ) ) ;
}
async exportToAbstractQueries ( queries : PromQuery [ ] ) : Promise < AbstractQuery [ ] > {
return queries . map ( ( query ) = > this . languageProvider . exportToAbstractQuery ( query ) ) ;
}
2018-04-20 15:28:04 +02:00
// Use this for tab completion features, wont publish response to other components
2021-04-27 16:16:48 +02:00
async metadataRequest < T = any > ( url : string , params = { } ) {
2021-02-23 16:31:03 +01:00
// If URL includes endpoint that supports POST and GET method, try to use configured method. This might fail as POST is supported only in v2.10+.
2021-04-06 18:35:00 +02:00
if ( GET_AND_POST_METADATA_ENDPOINTS . some ( ( endpoint ) = > url . includes ( endpoint ) ) ) {
2021-02-23 16:31:03 +01:00
try {
2022-02-02 12:02:32 +00:00
return await lastValueFrom ( this . _request < T > ( url , params , { method : this.httpMethod , hideFromInspector : true } ) ) ;
2021-02-23 16:31:03 +01:00
} catch ( err ) {
// If status code of error is Method Not Allowed (405) and HTTP method is POST, retry with GET
if ( this . httpMethod === 'POST' && err . status === 405 ) {
console . warn ( ` Couldn't use configured POST HTTP method for this request. Trying to use GET method instead. ` ) ;
} else {
throw err ;
}
}
}
2022-02-02 12:02:32 +00:00
return await lastValueFrom ( this . _request < T > ( url , params , { method : 'GET' , hideFromInspector : true } ) ) ; // toPromise until we change getTagValues, getTagKeys to Observable
2018-04-20 15:28:04 +02:00
}
2019-11-21 15:36:56 +00:00
interpolateQueryExpr ( value : string | string [ ] = [ ] , variable : any ) {
2016-03-01 21:35:55 +01:00
// if no multi or include all do not regexEscape
if ( ! variable . multi && ! variable . includeAll ) {
2018-03-27 16:16:00 +02:00
return prometheusRegularEscape ( value ) ;
2016-03-01 21:35:55 +01:00
}
2017-12-21 08:39:31 +01:00
if ( typeof value === 'string' ) {
2016-09-16 16:50:30 +02:00
return prometheusSpecialRegexEscape ( value ) ;
2016-03-03 10:49:30 +01:00
}
2021-01-20 07:59:48 +01:00
const escapedValues = value . map ( ( val ) = > prometheusSpecialRegexEscape ( val ) ) ;
2020-08-13 19:15:34 +02:00
if ( escapedValues . length === 1 ) {
return escapedValues [ 0 ] ;
}
2020-08-10 15:36:15 +02:00
return '(' + escapedValues . join ( '|' ) + ')' ;
2017-08-10 09:33:17 +02:00
}
2016-03-01 21:35:55 +01:00
2019-05-13 09:38:19 +02:00
targetContainsTemplate ( target : PromQuery ) {
2022-02-15 08:53:42 +01:00
return this . templateSrv . containsTemplate ( target . expr ) ;
2017-08-10 09:33:17 +02:00
}
2016-10-11 16:00:59 +02:00
2019-06-03 14:54:32 +02:00
prepareTargets = ( options : DataQueryRequest < PromQuery > , start : number , end : number ) = > {
const queries : PromQueryRequest [ ] = [ ] ;
const activeTargets : PromQuery [ ] = [ ] ;
2021-05-14 15:57:40 +02:00
const clonedTargets = cloneDeep ( options . targets ) ;
2016-09-14 09:17:10 +02:00
2021-05-14 15:57:40 +02:00
for ( const target of clonedTargets ) {
2016-02-01 23:24:08 +01:00
if ( ! target . expr || target . hide ) {
2017-05-19 10:24:41 +02:00
continue ;
2016-02-01 23:24:08 +01:00
}
2016-09-14 09:17:10 +02:00
2019-09-12 17:28:46 +02:00
target . requestId = options . panelId + target . refId ;
2021-05-28 15:40:15 +02:00
const metricName = this . languageProvider . histogramMetrics . find ( ( m ) = > target . expr . includes ( m ) ) ;
2019-09-12 17:28:46 +02:00
2020-11-20 12:08:10 +01:00
// In Explore, we run both (instant and range) queries if both are true (selected) or both are undefined (legacy Explore queries)
if ( options . app === CoreApp . Explore && target . range === target . instant ) {
2020-09-22 17:31:42 +02:00
// Create instant target
2019-11-21 15:36:56 +00:00
const instantTarget : any = cloneDeep ( target ) ;
2019-06-03 14:54:32 +02:00
instantTarget . format = 'table' ;
instantTarget . instant = true ;
2020-09-22 17:31:42 +02:00
instantTarget . range = false ;
2019-06-03 14:54:32 +02:00
instantTarget . valueWithRefId = true ;
delete instantTarget . maxDataPoints ;
instantTarget . requestId += '_instant' ;
2019-09-12 17:28:46 +02:00
2020-09-22 17:31:42 +02:00
// Create range target
const rangeTarget : any = cloneDeep ( target ) ;
rangeTarget . format = 'time_series' ;
rangeTarget . instant = false ;
instantTarget . range = true ;
2021-01-15 16:20:20 +01:00
// Create exemplar query
if ( target . exemplar ) {
2021-05-28 15:40:15 +02:00
// Only create exemplar target for different metric names
if (
! metricName ||
( metricName && ! activeTargets . some ( ( activeTarget ) = > activeTarget . expr . includes ( metricName ) ) )
) {
const exemplarTarget = cloneDeep ( target ) ;
exemplarTarget . instant = false ;
exemplarTarget . requestId += '_exemplar' ;
queries . push ( this . createQuery ( exemplarTarget , options , start , end ) ) ;
activeTargets . push ( exemplarTarget ) ;
}
2021-01-15 16:20:20 +01:00
instantTarget . exemplar = false ;
rangeTarget . exemplar = false ;
}
2020-09-22 17:31:42 +02:00
// Add both targets to activeTargets and queries arrays
activeTargets . push ( instantTarget , rangeTarget ) ;
queries . push (
this . createQuery ( instantTarget , options , start , end ) ,
this . createQuery ( rangeTarget , options , start , end )
) ;
2020-09-25 14:34:33 +02:00
// If running only instant query in Explore, format as table
2020-11-20 12:08:10 +01:00
} else if ( target . instant && options . app === CoreApp . Explore ) {
2020-09-25 14:34:33 +02:00
const instantTarget : any = cloneDeep ( target ) ;
instantTarget . format = 'table' ;
queries . push ( this . createQuery ( instantTarget , options , start , end ) ) ;
activeTargets . push ( instantTarget ) ;
2020-09-22 17:31:42 +02:00
} else {
2021-04-06 18:35:00 +02:00
// It doesn't make sense to query for exemplars in dashboard if only instant is selected
if ( target . exemplar && ! target . instant ) {
2021-05-28 15:40:15 +02:00
if (
! metricName ||
( metricName && ! activeTargets . some ( ( activeTarget ) = > activeTarget . expr . includes ( metricName ) ) )
) {
const exemplarTarget = cloneDeep ( target ) ;
exemplarTarget . requestId += '_exemplar' ;
queries . push ( this . createQuery ( exemplarTarget , options , start , end ) ) ;
activeTargets . push ( exemplarTarget ) ;
}
2021-01-15 16:20:20 +01:00
target . exemplar = false ;
2021-04-06 18:35:00 +02:00
}
2019-09-12 17:28:46 +02:00
queries . push ( this . createQuery ( target , options , start , end ) ) ;
2020-09-22 17:31:42 +02:00
activeTargets . push ( target ) ;
2019-09-12 17:28:46 +02:00
}
2017-04-25 12:57:23 +02:00
}
2016-02-01 23:24:08 +01:00
2019-06-03 14:54:32 +02:00
return {
queries ,
activeTargets ,
} ;
} ;
2021-12-09 11:30:23 +01:00
shouldRunExemplarQuery ( target : PromQuery , request : DataQueryRequest < PromQuery > ) : boolean {
2021-10-12 13:16:09 +02:00
if ( target . exemplar ) {
2021-12-09 11:30:23 +01:00
// We check all already processed targets and only create exemplar target for not used metric names
const metricName = this . languageProvider . histogramMetrics . find ( ( m ) = > target . expr . includes ( m ) ) ;
// Remove targets that weren't processed yet (in targets array they are after current target)
const currentTargetIdx = request . targets . findIndex ( ( t ) = > t . refId === target . refId ) ;
2022-06-01 11:13:57 +02:00
const targets = request . targets . slice ( 0 , currentTargetIdx ) . filter ( ( t ) = > ! t . hide ) ;
2021-10-12 13:16:09 +02:00
2021-12-09 11:30:23 +01:00
if ( ! metricName || ( metricName && ! targets . some ( ( t ) = > t . expr . includes ( metricName ) ) ) ) {
return true ;
2021-10-12 13:16:09 +02:00
}
2021-12-09 11:30:23 +01:00
return false ;
2021-10-12 13:16:09 +02:00
}
return false ;
}
processTargetV2 ( target : PromQuery , request : DataQueryRequest < PromQuery > ) {
const processedTarget = {
. . . target ,
2021-10-26 14:47:40 +02:00
queryType : PromQueryType.timeSeriesQuery ,
2021-12-09 11:30:23 +01:00
exemplar : this.shouldRunExemplarQuery ( target , request ) ,
2021-10-12 13:16:09 +02:00
requestId : request.panelId + target . refId ,
// We need to pass utcOffsetSec to backend to calculate aligned range
utcOffsetSec : this.timeSrv.timeRange ( ) . to . utcOffset ( ) * 60 ,
} ;
return processedTarget ;
}
query ( request : DataQueryRequest < PromQuery > ) : Observable < DataQueryResponse > {
2021-10-15 13:37:27 +02:00
if ( this . access === 'proxy' ) {
2021-10-12 13:16:09 +02:00
const targets = request . targets . map ( ( target ) = > this . processTargetV2 ( target , request ) ) ;
return super
. query ( { . . . request , targets } )
. pipe (
map ( ( response ) = >
transformV2 ( response , request , { exemplarTraceIdDestinations : this.exemplarTraceIdDestinations } )
)
) ;
2021-09-17 13:39:26 +02:00
// Run queries trough browser/proxy
} else {
2021-10-12 13:16:09 +02:00
const start = this . getPrometheusTime ( request . range . from , false ) ;
const end = this . getPrometheusTime ( request . range . to , true ) ;
const { queries , activeTargets } = this . prepareTargets ( request , start , end ) ;
2021-09-17 13:39:26 +02:00
// No valid targets, return the empty result to save a round trip.
if ( ! queries || ! queries . length ) {
return of ( {
data : [ ] ,
state : LoadingState.Done ,
} ) ;
}
2019-06-03 14:54:32 +02:00
2021-10-12 13:16:09 +02:00
if ( request . app === CoreApp . Explore ) {
2021-09-17 13:39:26 +02:00
return this . exploreQuery ( queries , activeTargets , end ) ;
}
2016-02-01 23:24:08 +01:00
2021-10-12 13:16:09 +02:00
return this . panelsQuery ( queries , activeTargets , end , request . requestId , request . scopedVars ) ;
2019-10-03 05:15:59 -07:00
}
}
private exploreQuery ( queries : PromQueryRequest [ ] , activeTargets : PromQuery [ ] , end : number ) {
2019-09-13 15:08:29 +02:00
let runningQueriesCount = queries . length ;
2020-09-22 17:31:42 +02:00
2019-09-12 17:28:46 +02:00
const subQueries = queries . map ( ( query , index ) = > {
const target = activeTargets [ index ] ;
2019-09-03 17:24:22 +02:00
2020-10-01 12:58:06 +02:00
const filterAndMapResponse = pipe (
2019-09-13 15:08:29 +02:00
// Decrease the counter here. We assume that each request returns only single value and then completes
// (should hold until there is some streaming requests involved).
tap ( ( ) = > runningQueriesCount -- ) ,
2019-09-12 17:28:46 +02:00
filter ( ( response : any ) = > ( response . cancelled ? false : true ) ) ,
map ( ( response : any ) = > {
2021-01-15 16:20:20 +01:00
const data = transform ( response , {
query ,
target ,
responseListLength : queries.length ,
exemplarTraceIdDestinations : this.exemplarTraceIdDestinations ,
} ) ;
2019-09-12 17:28:46 +02:00
return {
data ,
key : query.requestId ,
2019-09-13 15:08:29 +02:00
state : runningQueriesCount === 0 ? LoadingState.Done : LoadingState.Loading ,
2019-09-12 17:28:46 +02:00
} as DataQueryResponse ;
} )
) ;
2020-10-01 12:58:06 +02:00
2021-05-12 19:30:41 +02:00
return this . runQuery ( query , end , filterAndMapResponse ) ;
2016-02-01 23:24:08 +01:00
} ) ;
2019-05-10 14:00:39 +02:00
2019-09-12 17:28:46 +02:00
return merge ( . . . subQueries ) ;
2017-08-10 09:33:17 +02:00
}
2016-02-01 23:24:08 +01:00
2020-08-19 08:29:23 +02:00
private panelsQuery (
queries : PromQueryRequest [ ] ,
activeTargets : PromQuery [ ] ,
end : number ,
requestId : string ,
scopedVars : ScopedVars
) {
2020-10-01 12:58:06 +02:00
const observables = queries . map ( ( query , index ) = > {
2019-10-03 05:15:59 -07:00
const target = activeTargets [ index ] ;
2020-10-01 12:58:06 +02:00
const filterAndMapResponse = pipe (
2019-10-03 05:15:59 -07:00
filter ( ( response : any ) = > ( response . cancelled ? false : true ) ) ,
map ( ( response : any ) = > {
2021-01-15 16:20:20 +01:00
const data = transform ( response , {
query ,
target ,
responseListLength : queries.length ,
scopedVars ,
exemplarTraceIdDestinations : this.exemplarTraceIdDestinations ,
} ) ;
2019-10-03 05:15:59 -07:00
return data ;
} )
) ;
2020-10-01 12:58:06 +02:00
2021-05-12 19:30:41 +02:00
return this . runQuery ( query , end , filterAndMapResponse ) ;
2019-10-03 05:15:59 -07:00
} ) ;
return forkJoin ( observables ) . pipe (
2021-01-20 07:59:48 +01:00
map ( ( results ) = > {
2019-10-03 05:15:59 -07:00
const data = results . reduce ( ( result , current ) = > {
return [ . . . result , . . . current ] ;
} , [ ] ) ;
return {
data ,
key : requestId ,
state : LoadingState.Done ,
} ;
} )
) ;
}
2021-05-12 19:30:41 +02:00
private runQuery < T > ( query : PromQueryRequest , end : number , filter : OperatorFunction < any , T > ) : Observable < T > {
if ( query . instant ) {
return this . performInstantQuery ( query , end ) . pipe ( filter ) ;
}
if ( query . exemplar ) {
return this . getExemplars ( query ) . pipe (
2021-07-28 16:34:46 +02:00
catchError ( ( ) = > {
2021-05-12 19:30:41 +02:00
return of ( {
data : [ ] ,
state : LoadingState.Done ,
} ) ;
} ) ,
filter
) ;
}
return this . performTimeSeriesQuery ( query , query . start , query . end ) . pipe ( filter ) ;
}
2019-06-03 14:54:32 +02:00
createQuery ( target : PromQuery , options : DataQueryRequest < PromQuery > , start : number , end : number ) {
const query : PromQueryRequest = {
2018-08-08 16:50:30 +02:00
hinting : target.hinting ,
instant : target.instant ,
2021-01-15 16:20:20 +01:00
exemplar : target.exemplar ,
2019-06-03 14:54:32 +02:00
step : 0 ,
expr : '' ,
2019-09-12 17:28:46 +02:00
requestId : target.requestId ,
refId : target.refId ,
2019-06-03 14:54:32 +02:00
start : 0 ,
end : 0 ,
2018-08-08 16:50:30 +02:00
} ;
2018-08-29 14:27:29 +02:00
const range = Math . ceil ( end - start ) ;
2021-09-09 13:05:08 +01:00
2019-01-14 17:40:47 +01:00
// options.interval is the dynamically calculated interval
2020-09-02 23:54:06 -07:00
let interval : number = rangeUtil . intervalToSeconds ( options . interval ) ;
2020-08-25 18:55:08 +02:00
// Minimum interval ("Min step"), if specified for the query, or same as interval otherwise.
2021-09-09 13:05:08 +01:00
const minInterval = rangeUtil . intervalToSeconds (
2020-10-01 18:51:23 +01:00
this . templateSrv . replace ( target . interval || options . interval , options . scopedVars )
2017-12-19 16:06:54 +01:00
) ;
2020-08-25 18:55:08 +02:00
// Scrape interval as specified for the query ("Min step") or otherwise taken from the datasource.
2020-09-10 20:31:53 +02:00
// Min step field can have template variables in it, make sure to replace it.
const scrapeInterval = target . interval
2020-10-01 18:51:23 +01:00
? rangeUtil . intervalToSeconds ( this . templateSrv . replace ( target . interval , options . scopedVars ) )
2020-09-10 20:31:53 +02:00
: rangeUtil . intervalToSeconds ( this . interval ) ;
2018-08-29 14:27:29 +02:00
const intervalFactor = target . intervalFactor || 1 ;
2017-10-04 15:30:07 +02:00
// Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits
2021-09-09 13:05:08 +01:00
const adjustedInterval = this . adjustInterval ( interval , minInterval , range , intervalFactor ) ;
2020-08-13 18:58:40 +02:00
let scopedVars = {
. . . options . scopedVars ,
. . . this . getRangeScopedVars ( options . range ) ,
2020-08-25 18:55:08 +02:00
. . . this . getRateIntervalScopedVariable ( adjustedInterval , scrapeInterval ) ,
2020-08-13 18:58:40 +02:00
} ;
2017-10-04 15:30:07 +02:00
// If the interval was adjusted, make a shallow copy of scopedVars with updated interval vars
if ( interval !== adjustedInterval ) {
interval = adjustedInterval ;
scopedVars = Object . assign ( { } , options . scopedVars , {
2017-12-21 08:39:31 +01:00
__interval : { text : interval + 's' , value : interval + 's' } ,
2018-07-16 13:02:36 +02:00
__interval_ms : { text : interval * 1000 , value : interval * 1000 } ,
2020-08-25 18:55:08 +02:00
. . . this . getRateIntervalScopedVariable ( interval , scrapeInterval ) ,
2019-06-06 10:03:14 +02:00
. . . this . getRangeScopedVars ( options . range ) ,
2017-10-04 15:30:07 +02:00
} ) ;
}
2017-10-26 11:46:33 +02:00
query . step = interval ;
2017-10-04 15:30:07 +02:00
2018-09-12 18:10:57 +02:00
let expr = target . expr ;
// Apply adhoc filters
2021-11-15 17:11:21 +01:00
expr = this . enhanceExprWithAdHocFilters ( expr ) ;
2018-09-12 18:10:57 +02:00
2017-10-04 15:30:07 +02:00
// Only replace vars in expression after having (possibly) updated interval vars
2020-10-01 18:51:23 +01:00
query . expr = this . templateSrv . replace ( expr , scopedVars , this . interpolateQueryExpr ) ;
2018-05-09 09:13:56 +02:00
2019-03-21 10:25:20 +01:00
// Align query interval with step to allow query caching and to ensure
// that about-same-time query results look the same.
2020-10-01 18:51:23 +01:00
const adjusted = alignRange ( start , end , query . step , this . timeSrv . timeRange ( ) . to . utcOffset ( ) * 60 ) ;
2018-05-09 09:13:56 +02:00
query . start = adjusted . start ;
query . end = adjusted . end ;
2019-04-30 13:28:35 +02:00
this . _addTracingHeaders ( query , options ) ;
2018-05-09 09:13:56 +02:00
2017-10-04 15:30:07 +02:00
return query ;
}
2020-08-25 18:55:08 +02:00
getRateIntervalScopedVariable ( interval : number , scrapeInterval : number ) {
// Fall back to the default scrape interval of 15s if scrapeInterval is 0 for some reason.
if ( scrapeInterval === 0 ) {
scrapeInterval = 15 ;
2020-08-13 18:58:40 +02:00
}
2020-08-25 18:55:08 +02:00
const rateInterval = Math . max ( interval + scrapeInterval , 4 * scrapeInterval ) ;
2020-08-13 18:58:40 +02:00
return { __rate_interval : { text : rateInterval + 's' , value : rateInterval + 's' } } ;
}
2021-09-09 13:05:08 +01:00
adjustInterval ( interval : number , minInterval : number , range : number , intervalFactor : number ) {
2017-09-12 16:36:27 +02:00
// Prometheus will drop queries that might return more than 11000 data points.
2019-12-23 08:28:08 +01:00
// Calculate a safe interval as an additional minimum to take into account.
2020-02-06 09:28:20 -05:00
// Fractional safeIntervals are allowed, however serve little purpose if the interval is greater than 1
// If this is the case take the ceil of the value.
let safeInterval = range / 11000 ;
if ( safeInterval > 1 ) {
safeInterval = Math . ceil ( safeInterval ) ;
}
2021-09-09 13:05:08 +01:00
return Math . max ( interval * intervalFactor , minInterval , safeInterval ) ;
2017-08-10 09:33:17 +02:00
}
2017-03-09 14:53:50 +09:00
2019-06-18 11:01:12 +02:00
performTimeSeriesQuery ( query : PromQueryRequest , start : number , end : number ) {
2016-09-28 18:54:25 +09:00
if ( start > end ) {
2017-12-21 08:39:31 +01:00
throw { message : 'Invalid time range' } ;
2016-09-28 18:54:25 +09:00
}
2018-08-29 14:27:29 +02:00
const url = '/api/v1/query_range' ;
2019-06-18 11:01:12 +02:00
const data : any = {
2017-11-12 16:38:10 +09:00
query : query.expr ,
2019-06-18 11:01:12 +02:00
start ,
end ,
2017-11-12 16:38:10 +09:00
step : query.step ,
} ;
2019-06-18 11:01:12 +02:00
2018-05-01 12:42:58 +09:00
if ( this . queryTimeout ) {
data [ 'timeout' ] = this . queryTimeout ;
}
2019-06-18 11:01:12 +02:00
2020-10-01 12:58:06 +02:00
return this . _request < PromDataSuccessResponse < PromMatrixData > > ( url , data , {
requestId : query.requestId ,
headers : query.headers ,
} ) . pipe (
catchError ( ( err : FetchError < PromDataErrorResponse < PromMatrixData > > ) = > {
2020-08-20 21:03:59 -07:00
if ( err . cancelled ) {
return of ( err ) ;
}
2019-06-18 11:07:09 +01:00
2020-08-20 21:03:59 -07:00
return throwError ( this . handleErrors ( err , query ) ) ;
} )
) ;
2017-08-10 09:33:17 +02:00
}
2016-02-01 23:24:08 +01:00
2021-05-12 19:30:41 +02:00
performInstantQuery (
query : PromQueryRequest ,
time : number
) : Observable < FetchResponse < PromDataSuccessResponse < PromVectorData | PromScalarData > > | FetchError > {
2018-08-29 14:27:29 +02:00
const url = '/api/v1/query' ;
2019-06-18 11:01:12 +02:00
const data : any = {
2017-11-12 16:38:10 +09:00
query : query.expr ,
2019-06-18 11:01:12 +02:00
time ,
2017-11-12 16:38:10 +09:00
} ;
2019-06-18 11:01:12 +02:00
2018-05-01 12:42:58 +09:00
if ( this . queryTimeout ) {
data [ 'timeout' ] = this . queryTimeout ;
}
2019-06-18 11:01:12 +02:00
2020-10-01 12:58:06 +02:00
return this . _request < PromDataSuccessResponse < PromVectorData | PromScalarData > > ( url , data , {
requestId : query.requestId ,
headers : query.headers ,
} ) . pipe (
catchError ( ( err : FetchError < PromDataErrorResponse < PromVectorData | PromScalarData > > ) = > {
2020-08-20 21:03:59 -07:00
if ( err . cancelled ) {
return of ( err ) ;
}
2019-06-18 11:07:09 +01:00
2020-08-20 21:03:59 -07:00
return throwError ( this . handleErrors ( err , query ) ) ;
} )
) ;
2017-09-07 01:03:02 +09:00
}
2019-05-10 14:00:39 +02:00
handleErrors = ( err : any , target : PromQuery ) = > {
const error : DataQueryError = {
2019-10-29 10:37:36 +00:00
message : ( err && err . statusText ) || 'Unknown error during query transaction. Please check JS console logs.' ,
2019-05-10 14:00:39 +02:00
refId : target.refId ,
} ;
if ( err . data ) {
if ( typeof err . data === 'string' ) {
error . message = err . data ;
} else if ( err . data . error ) {
error . message = safeStringifyValue ( err . data . error ) ;
}
} else if ( err . message ) {
error . message = err . message ;
} else if ( typeof err === 'string' ) {
error . message = err ;
}
error . status = err . status ;
error . statusText = err . statusText ;
2019-06-18 11:07:09 +01:00
return error ;
2019-05-10 14:00:39 +02:00
} ;
2019-06-18 11:01:12 +02:00
metricFindQuery ( query : string ) {
2017-12-19 16:06:54 +01:00
if ( ! query ) {
2019-11-21 15:36:56 +00:00
return Promise . resolve ( [ ] ) ;
2017-12-19 16:06:54 +01:00
}
2016-02-01 23:24:08 +01:00
2018-08-26 17:14:40 +02:00
const scopedVars = {
2018-07-12 15:32:32 +02:00
__interval : { text : this.interval , value : this.interval } ,
2020-09-02 23:54:06 -07:00
__interval_ms : { text : rangeUtil.intervalToMs ( this . interval ) , value : rangeUtil.intervalToMs ( this . interval ) } ,
2020-10-01 18:51:23 +01:00
. . . this . getRangeScopedVars ( this . timeSrv . timeRange ( ) ) ,
2018-07-12 15:32:32 +02:00
} ;
2020-10-01 18:51:23 +01:00
const interpolated = this . templateSrv . replace ( query , scopedVars , this . interpolateQueryExpr ) ;
2019-11-21 15:36:56 +00:00
const metricFindQuery = new PrometheusMetricFindQuery ( this , interpolated ) ;
2015-12-09 11:22:00 +09:00
return metricFindQuery . process ( ) ;
2017-08-10 09:33:17 +02:00
}
2016-02-01 23:24:08 +01:00
2020-10-01 18:51:23 +01:00
getRangeScopedVars ( range : TimeRange = this . timeSrv . timeRange ( ) ) {
2018-08-26 17:14:40 +02:00
const msRange = range . to . diff ( range . from ) ;
const sRange = Math . round ( msRange / 1000 ) ;
2018-07-12 15:32:32 +02:00
return {
__range_ms : { text : msRange , value : msRange } ,
2018-08-13 07:53:41 +02:00
__range_s : { text : sRange , value : sRange } ,
2020-02-11 15:28:06 +02:00
__range : { text : sRange + 's' , value : sRange + 's' } ,
2018-07-12 15:32:32 +02:00
} ;
}
2020-02-18 16:10:15 +01:00
async annotationQuery ( options : any ) : Promise < AnnotationEvent [ ] > {
2018-08-29 14:27:29 +02:00
const annotation = options . annotation ;
2021-11-02 16:46:20 +01:00
const { expr = '' } = annotation ;
2015-10-02 03:19:25 +09:00
2017-12-19 16:06:54 +01:00
if ( ! expr ) {
2019-11-21 15:36:56 +00:00
return Promise . resolve ( [ ] ) ;
2017-12-19 16:06:54 +01:00
}
2015-10-02 03:19:25 +09:00
2021-11-02 16:46:20 +01:00
const step = options . annotation . step || ANNOTATION_QUERY_STEP_DEFAULT ;
2019-09-12 17:28:46 +02:00
const queryModel = {
expr ,
2021-11-02 16:46:20 +01:00
range : true ,
instant : false ,
exemplar : false ,
interval : step ,
queryType : PromQueryType.timeSeriesQuery ,
2019-09-12 17:28:46 +02:00
refId : 'X' ,
2021-11-09 07:24:41 -08:00
datasource : this.getRef ( ) ,
2019-09-12 17:28:46 +02:00
} ;
2021-11-02 16:46:20 +01:00
return await lastValueFrom (
getBackendSrv ( )
. fetch < BackendDataSourceResponse > ( {
url : '/api/ds/query' ,
method : 'POST' ,
data : {
from : ( this . getPrometheusTime ( options . range . from , false ) * 1000 ) . toString ( ) ,
to : ( this . getPrometheusTime ( options . range . to , true ) * 1000 ) . toString ( ) ,
2021-12-22 16:50:58 +01:00
queries : [ this . applyTemplateVariables ( queryModel , { } ) ] ,
2021-11-02 16:46:20 +01:00
} ,
requestId : ` prom-query- ${ annotation . name } ` ,
} )
. pipe (
map ( ( rsp : FetchResponse < BackendDataSourceResponse > ) = > {
2022-01-07 22:04:30 +08:00
return this . processAnnotationResponse ( options , rsp . data ) ;
2021-11-02 16:46:20 +01:00
} )
)
) ;
}
2015-10-02 03:19:25 +09:00
2022-01-07 22:04:30 +08:00
processAnnotationResponse = ( options : any , data : BackendDataSourceResponse ) = > {
2021-11-02 16:46:20 +01:00
const frames : DataFrame [ ] = toDataQueryResponse ( { data : data } ) . data ;
if ( ! frames || ! frames . length ) {
2019-11-21 15:36:56 +00:00
return [ ] ;
}
2018-05-01 12:49:18 +09:00
2021-11-02 16:46:20 +01:00
const annotation = options . annotation ;
const { tagKeys = '' , titleFormat = '' , textFormat = '' } = annotation ;
const step = rangeUtil . intervalToSeconds ( annotation . step || ANNOTATION_QUERY_STEP_DEFAULT ) * 1000 ;
const tagKeysArray = tagKeys . split ( ',' ) ;
2020-02-18 16:10:15 +01:00
2022-01-07 22:04:30 +08:00
const eventList : AnnotationEvent [ ] = [ ] ;
2020-07-06 21:16:27 +02:00
2022-01-07 22:04:30 +08:00
for ( const frame of frames ) {
const timeField = frame . fields [ 0 ] ;
const valueField = frame . fields [ 1 ] ;
const labels = valueField ? . labels || { } ;
2020-02-18 16:10:15 +01:00
2022-01-07 22:04:30 +08:00
const tags = Object . keys ( labels )
. filter ( ( label ) = > tagKeysArray . includes ( label ) )
. map ( ( label ) = > labels [ label ] ) ;
2020-02-18 16:10:15 +01:00
2022-01-07 22:04:30 +08:00
const timeValueTuple : Array < [ number , number ] > = [ ] ;
let idx = 0 ;
valueField . values . toArray ( ) . forEach ( ( value : string ) = > {
let timeStampValue : number ;
let valueValue : number ;
const time = timeField . values . get ( idx ) ;
// If we want to use value as a time, we use value as timeStampValue and valueValue will be 1
if ( options . annotation . useValueForTime ) {
timeStampValue = Math . floor ( parseFloat ( value ) ) ;
valueValue = 1 ;
} else {
timeStampValue = Math . floor ( parseFloat ( time ) ) ;
valueValue = parseFloat ( value ) ;
}
idx ++ ;
timeValueTuple . push ( [ timeStampValue , valueValue ] ) ;
} ) ;
const activeValues = timeValueTuple . filter ( ( value ) = > value [ 1 ] >= 1 ) ;
const activeValuesTimestamps = activeValues . map ( ( value ) = > value [ 0 ] ) ;
// Instead of creating singular annotation for each active event we group events into region if they are less
// or equal to `step` apart.
let latestEvent : AnnotationEvent | null = null ;
for ( const timestamp of activeValuesTimestamps ) {
// We already have event `open` and we have new event that is inside the `step` so we just update the end.
if ( latestEvent && ( latestEvent . timeEnd ? ? 0 ) + step >= timestamp ) {
latestEvent . timeEnd = timestamp ;
continue ;
}
// Event exists but new one is outside of the `step` so we add it to eventList.
if ( latestEvent ) {
eventList . push ( latestEvent ) ;
}
// We start a new region.
latestEvent = {
time : timestamp ,
timeEnd : timestamp ,
annotation ,
title : renderLegendFormat ( titleFormat , labels ) ,
tags ,
text : renderLegendFormat ( textFormat , labels ) ,
} ;
2020-07-06 21:16:27 +02:00
}
2020-02-18 16:10:15 +01:00
if ( latestEvent ) {
2022-01-07 22:04:30 +08:00
// Finish up last point if we have one
latestEvent . timeEnd = activeValuesTimestamps [ activeValuesTimestamps . length - 1 ] ;
2020-02-18 16:10:15 +01:00
eventList . push ( latestEvent ) ;
2019-11-21 15:36:56 +00:00
}
2021-11-02 16:46:20 +01:00
}
2019-11-21 15:36:56 +00:00
return eventList ;
2021-11-02 16:46:20 +01:00
} ;
2015-10-02 03:19:25 +09:00
2021-01-15 16:20:20 +01:00
getExemplars ( query : PromQueryRequest ) {
2021-01-19 10:45:15 +01:00
const url = '/api/v1/query_exemplars' ;
2021-01-15 16:20:20 +01:00
return this . _request < PromDataSuccessResponse < PromExemplarData > > (
url ,
{ query : query.expr , start : query.start.toString ( ) , end : query.end.toString ( ) } ,
{ requestId : query.requestId , headers : query.headers }
) ;
}
2022-04-04 19:30:17 +02:00
async getSubtitle ( ) : Promise < JSX.Element | null > {
const buildInfo = await this . getBuildInfo ( ) ;
return buildInfo ? this . getBuildInfoMessage ( buildInfo ) : null ;
}
2022-01-06 07:09:31 -07:00
async getTagKeys ( options? : any ) {
if ( options ? . series ) {
// Get tags for the provided series only
const seriesLabels : Array < Record < string , string [ ] > > = await Promise . all (
options . series . map ( ( series : string ) = > this . languageProvider . fetchSeriesLabels ( series ) )
) ;
const uniqueLabels = [ . . . new Set ( . . . seriesLabels . map ( ( value ) = > Object . keys ( value ) ) ) ] ;
return uniqueLabels . map ( ( value : any ) = > ( { text : value } ) ) ;
} else {
// Get all tags
const result = await this . metadataRequest ( '/api/v1/labels' ) ;
return result ? . data ? . data ? . map ( ( value : any ) = > ( { text : value } ) ) ? ? [ ] ;
}
2019-02-07 13:54:25 +09:00
}
2021-11-11 14:27:59 +01:00
async getTagValues ( options : { key? : string } = { } ) {
2019-11-21 15:36:56 +00:00
const result = await this . metadataRequest ( ` /api/v1/label/ ${ options . key } /values ` ) ;
return result ? . data ? . data ? . map ( ( value : any ) = > ( { text : value } ) ) ? ? [ ] ;
2019-02-07 13:54:25 +09:00
}
2022-04-04 19:30:17 +02:00
async getBuildInfo() {
try {
2022-05-18 10:45:26 +02:00
const buildInfo = await discoverDataSourceFeatures ( { url : this.url , name : this.name , type : 'prometheus' } ) ;
2022-04-04 19:30:17 +02:00
return buildInfo ;
} catch ( error ) {
// We don't want to break the rest of functionality if build info does not work correctly
return undefined ;
}
}
2022-05-18 10:45:26 +02:00
getBuildInfoMessage ( buildInfo : PromApiFeatures ) {
2022-04-04 19:30:17 +02:00
const enabled = < Badge color = "green" icon = "check" text = "Ruler API enabled" / > ;
const disabled = < Badge color = "orange" icon = "exclamation-triangle" text = "Ruler API not enabled" / > ;
const unsupported = (
< Tooltip
placement = "top"
content = "Prometheus does not allow editing rules, connect to either a Mimir or Cortex datasource to manage alerts via Grafana."
>
< div >
< Badge color = "red" icon = "exclamation-triangle" text = "Ruler API not supported" / >
< / div >
< / Tooltip >
) ;
const LOGOS = {
2022-05-18 10:45:26 +02:00
[ PromApplication . Lotex ] : '/public/app/plugins/datasource/prometheus/img/cortex_logo.svg' ,
2022-04-04 19:30:17 +02:00
[ PromApplication . Mimir ] : '/public/app/plugins/datasource/prometheus/img/mimir_logo.svg' ,
[ PromApplication . Prometheus ] : '/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg' ,
} ;
const COLORS : Record < PromApplication , BadgeColor > = {
2022-05-18 10:45:26 +02:00
[ PromApplication . Lotex ] : 'blue' ,
2022-04-04 19:30:17 +02:00
[ PromApplication . Mimir ] : 'orange' ,
[ PromApplication . Prometheus ] : 'red' ,
} ;
2022-05-18 10:45:26 +02:00
const AppDisplayNames : Record < PromApplication , string > = {
[ PromApplication . Lotex ] : 'Cortex' ,
[ PromApplication . Mimir ] : 'Mimir' ,
[ PromApplication . Prometheus ] : 'Prometheus' ,
} ;
2022-04-04 19:30:17 +02:00
// this will inform the user about what "subtype" the datasource is; Mimir, Cortex or vanilla Prometheus
const applicationSubType = (
< Badge
text = {
< span >
< img
style = { { width : 14 , height : 14 , verticalAlign : 'text-bottom' } }
src = { LOGOS [ buildInfo . application ? ? PromApplication . Prometheus ] }
/ > { ' ' }
2022-05-18 10:45:26 +02:00
{ buildInfo . application ? AppDisplayNames [ buildInfo . application ] : 'Unknown' }
2022-04-04 19:30:17 +02:00
< / span >
}
color = { COLORS [ buildInfo . application ? ? PromApplication . Prometheus ] }
/ >
) ;
return (
< div
style = { {
display : 'grid' ,
gridTemplateColumns : 'max-content max-content' ,
rowGap : '0.5rem' ,
columnGap : '2rem' ,
marginTop : '1rem' ,
} }
>
< div > Type < / div >
< div > { applicationSubType } < / div >
< >
< div > Ruler API < / div >
{ /* Prometheus does not have a Ruler API – so show that it is not supported */ }
{ buildInfo . application === PromApplication . Prometheus && < div > { unsupported } < / div > }
{ buildInfo . application !== PromApplication . Prometheus && (
< div > { buildInfo . features . rulerApiEnabled ? enabled : disabled } < / div >
) }
< / >
< / div >
) ;
}
2019-11-21 15:36:56 +00:00
async testDatasource() {
2018-08-26 17:14:40 +02:00
const now = new Date ( ) . getTime ( ) ;
2021-11-24 12:43:39 +01:00
const request : DataQueryRequest < PromQuery > = {
targets : [ { refId : 'test' , expr : '1+1' , instant : true } ] ,
requestId : ` ${ this . id } -health ` ,
scopedVars : { } ,
dashboardId : 0 ,
panelId : 0 ,
interval : '1m' ,
intervalMs : 60000 ,
maxDataPoints : 1 ,
range : {
from : dateTime ( now - 1000 ) ,
to : dateTime ( now ) ,
} ,
} as DataQueryRequest < PromQuery > ;
2022-04-04 19:30:17 +02:00
const buildInfo = await this . getBuildInfo ( ) ;
2021-11-24 12:43:39 +01:00
return lastValueFrom ( this . query ( request ) )
. then ( ( res : DataQueryResponse ) = > {
if ( ! res || ! res . data || res . state !== LoadingState . Done ) {
return { status : 'error' , message : ` Error reading Prometheus: ${ res ? . error ? . message } ` } ;
} else {
2022-04-04 19:30:17 +02:00
return {
status : 'success' ,
message : 'Data source is working' ,
details : buildInfo && {
verboseMessage : this.getBuildInfoMessage ( buildInfo ) ,
} ,
} ;
2021-11-24 12:43:39 +01:00
}
} )
. catch ( ( err : any ) = > {
console . error ( 'Prometheus Error' , err ) ;
return { status : 'error' , message : err.message } ;
} ) ;
2017-08-10 09:33:17 +02:00
}
2016-09-28 21:31:26 +09:00
2020-01-24 09:50:09 +01:00
interpolateVariablesInQueries ( queries : PromQuery [ ] , scopedVars : ScopedVars ) : PromQuery [ ] {
2019-10-08 17:01:20 +02:00
let expandedQueries = queries ;
2019-11-21 15:36:56 +00:00
if ( queries && queries . length ) {
2021-01-20 07:59:48 +01:00
expandedQueries = queries . map ( ( query ) = > {
2019-09-05 13:44:37 +01:00
const expandedQuery = {
. . . query ,
2021-10-29 10:57:24 -07:00
datasource : this.getRef ( ) ,
2020-10-01 18:51:23 +01:00
expr : this.templateSrv.replace ( query . expr , scopedVars , this . interpolateQueryExpr ) ,
2021-07-07 08:43:46 +02:00
interval : this.templateSrv.replace ( query . interval , scopedVars ) ,
2019-09-05 13:44:37 +01:00
} ;
return expandedQuery ;
} ) ;
2016-02-01 23:24:08 +01:00
}
2019-10-08 17:01:20 +02:00
return expandedQueries ;
2017-08-10 09:33:17 +02:00
}
2016-02-01 23:24:08 +01:00
2019-01-18 17:19:35 +01:00
getQueryHints ( query : PromQuery , result : any [ ] ) {
2019-11-21 15:36:56 +00:00
return getQueryHints ( query . expr ? ? '' , result , this ) ;
2018-10-23 15:12:53 +02:00
}
2021-05-28 17:10:10 +02:00
getInitHints() {
return getInitHints ( this ) ;
}
2019-11-21 15:36:56 +00:00
async loadRules() {
try {
const res = await this . metadataRequest ( '/api/v1/rules' ) ;
2020-08-20 21:03:59 -07:00
const groups = res . data ? . data ? . groups ;
2019-11-21 15:36:56 +00:00
if ( groups ) {
this . ruleMappings = extractRuleMappingFromGroups ( groups ) ;
}
} catch ( e ) {
console . log ( 'Rules API is experimental. Ignore next error.' ) ;
console . error ( e ) ;
}
2018-08-08 16:56:21 +02:00
}
2021-10-12 13:16:09 +02:00
async areExemplarsAvailable() {
try {
const res = await this . metadataRequest ( '/api/v1/query_exemplars' , { query : 'test' } ) ;
2021-10-22 16:41:16 +02:00
if ( res . data . status === 'success' ) {
2021-10-12 13:16:09 +02:00
return true ;
}
return false ;
} catch ( err ) {
return false ;
}
}
2019-01-18 17:19:35 +01:00
modifyQuery ( query : PromQuery , action : any ) : PromQuery {
2019-11-21 15:36:56 +00:00
let expression = query . expr ? ? '' ;
2018-08-08 16:50:30 +02:00
switch ( action . type ) {
case 'ADD_FILTER' : {
2018-11-21 16:28:30 +01:00
expression = addLabelToQuery ( expression , action . key , action . value ) ;
2018-11-21 14:45:57 +01:00
break ;
2018-08-08 16:50:30 +02:00
}
2020-06-10 07:09:02 +02:00
case 'ADD_FILTER_OUT' : {
expression = addLabelToQuery ( expression , action . key , action . value , '!=' ) ;
break ;
}
2018-08-08 16:50:30 +02:00
case 'ADD_HISTOGRAM_QUANTILE' : {
2022-02-11 16:50:35 +01:00
expression = ` histogram_quantile(0.95, sum(rate( ${ expression } [ $ __rate_interval])) by (le)) ` ;
2018-11-21 14:45:57 +01:00
break ;
2018-08-08 16:50:30 +02:00
}
case 'ADD_RATE' : {
2022-02-11 16:50:35 +01:00
expression = ` rate( ${ expression } [ $ __rate_interval]) ` ;
2018-11-21 14:45:57 +01:00
break ;
2018-08-08 16:50:30 +02:00
}
2018-10-28 21:03:39 +08:00
case 'ADD_SUM' : {
2018-11-21 16:28:30 +01:00
expression = ` sum( ${ expression . trim ( ) } ) by ( $ 1) ` ;
2018-11-21 14:45:57 +01:00
break ;
2018-10-28 21:03:39 +08:00
}
2018-08-08 16:56:21 +02:00
case 'EXPAND_RULES' : {
2018-11-16 14:31:51 +00:00
if ( action . mapping ) {
2018-11-21 16:28:30 +01:00
expression = expandRecordingRules ( expression , action . mapping ) ;
2018-08-08 16:56:21 +02:00
}
2018-11-21 14:45:57 +01:00
break ;
2018-08-08 16:56:21 +02:00
}
2018-08-08 16:50:30 +02:00
default :
2018-11-21 14:45:57 +01:00
break ;
2018-08-03 10:20:13 +02:00
}
2018-11-21 16:28:30 +01:00
return { . . . query , expr : expression } ;
2018-08-03 10:20:13 +02:00
}
2019-06-18 11:01:12 +02:00
getPrometheusTime ( date : string | DateTime , roundUp : boolean ) {
2019-11-21 15:36:56 +00:00
if ( typeof date === 'string' ) {
2020-07-06 21:16:27 +02:00
date = dateMath . parse ( date , roundUp ) ! ;
2016-02-01 23:24:08 +01:00
}
2019-11-21 15:36:56 +00:00
2016-02-02 12:52:43 +01:00
return Math . ceil ( date . valueOf ( ) / 1000 ) ;
2017-08-10 09:33:17 +02:00
}
2018-04-25 15:36:00 +02:00
2021-05-12 11:49:20 +02:00
getTimeRangeParams ( ) : { start : string ; end : string } {
2020-10-01 18:51:23 +01:00
const range = this . timeSrv . timeRange ( ) ;
2018-06-13 14:03:52 +09:00
return {
2021-05-12 11:49:20 +02:00
start : this.getPrometheusTime ( range . from , false ) . toString ( ) ,
end : this.getPrometheusTime ( range . to , true ) . toString ( ) ,
2018-06-13 14:03:52 +09:00
} ;
}
2019-06-18 11:01:12 +02:00
getOriginalMetricName ( labelData : { [ key : string ] : string } ) {
2020-10-01 12:58:06 +02:00
return getOriginalMetricName ( labelData ) ;
2018-04-25 15:36:00 +02:00
}
2021-09-17 13:39:26 +02:00
2021-11-15 17:11:21 +01:00
enhanceExprWithAdHocFilters ( expr : string ) {
const adhocFilters = this . templateSrv . getAdhocFilters ( this . name ) ;
2022-03-15 17:37:20 +01:00
const finalQuery = adhocFilters . reduce ( ( acc : string , filter : { key? : any ; operator? : any ; value? : any } ) = > {
2021-11-15 17:11:21 +01:00
const { key , operator } = filter ;
let { value } = filter ;
if ( operator === '=~' || operator === '!~' ) {
value = prometheusRegularEscape ( value ) ;
}
return addLabelToQuery ( acc , key , value , operator ) ;
2022-03-15 17:37:20 +01:00
} , expr ) ;
2021-11-15 17:11:21 +01:00
return finalQuery ;
}
2021-09-17 13:39:26 +02:00
// Used when running queries trough backend
filterQuery ( query : PromQuery ) : boolean {
if ( query . hide || ! query . expr ) {
return false ;
}
return true ;
}
// Used when running queries trough backend
applyTemplateVariables ( target : PromQuery , scopedVars : ScopedVars ) : Record < string , any > {
const variables = cloneDeep ( scopedVars ) ;
2021-11-15 17:11:21 +01:00
2021-09-17 13:39:26 +02:00
// We want to interpolate these variables on backend
delete variables . __interval ;
delete variables . __interval_ms ;
2021-11-15 17:11:21 +01:00
//Add ad hoc filters
const expr = this . enhanceExprWithAdHocFilters ( target . expr ) ;
2021-09-17 13:39:26 +02:00
return {
. . . target ,
2021-10-22 09:58:59 +02:00
legendFormat : this.templateSrv.replace ( target . legendFormat , variables ) ,
2021-11-15 17:11:21 +01:00
expr : this.templateSrv.replace ( expr , variables , this . interpolateQueryExpr ) ,
2021-12-02 13:32:02 +01:00
interval : this.templateSrv.replace ( target . interval , variables ) ,
2021-09-17 13:39:26 +02:00
} ;
}
2022-02-03 11:40:19 +01:00
getVariables ( ) : string [ ] {
return this . templateSrv . getVariables ( ) . map ( ( v ) = > ` $ ${ v . name } ` ) ;
}
interpolateString ( string : string ) {
return this . templateSrv . replace ( string , undefined , this . interpolateQueryExpr ) ;
}
2016-02-01 23:24:08 +01:00
}
2019-01-18 17:19:35 +01:00
2019-03-21 10:25:20 +01:00
/ * *
* Align query range to step .
* Rounds start and end down to a multiple of step .
* @param start Timestamp marking the beginning of the range .
* @param end Timestamp marking the end of the range .
* @param step Interval to align start and end with .
2019-06-26 01:55:36 -04:00
* @param utcOffsetSec Number of seconds current timezone is offset from UTC
2019-03-21 10:25:20 +01:00
* /
2019-06-26 01:55:36 -04:00
export function alignRange (
start : number ,
end : number ,
step : number ,
utcOffsetSec : number
) : { end : number ; start : number } {
const alignedEnd = Math . floor ( ( end + utcOffsetSec ) / step ) * step - utcOffsetSec ;
const alignedStart = Math . floor ( ( start + utcOffsetSec ) / step ) * step - utcOffsetSec ;
2019-01-18 17:19:35 +01:00
return {
end : alignedEnd ,
start : alignedStart ,
} ;
}
export function extractRuleMappingFromGroups ( groups : any [ ] ) {
return groups . reduce (
( mapping , group ) = >
2019-02-19 15:41:35 +01:00
group . rules
2019-06-18 11:01:12 +02:00
. filter ( ( rule : any ) = > rule . type === 'recording' )
2019-02-19 15:41:35 +01:00
. reduce (
2019-06-18 11:01:12 +02:00
( acc : { [ key : string ] : string } , rule : any ) = > ( {
2019-02-19 15:41:35 +01:00
. . . acc ,
[ rule . name ] : rule . query ,
} ) ,
mapping
) ,
2019-01-18 17:19:35 +01:00
{ }
) ;
}
2021-09-30 15:50:02 +02:00
// NOTE: these two functions are very similar to the escapeLabelValueIn* functions
// in language_utils.ts, but they are not exactly the same algorithm, and we found
// no way to reuse one in the another or vice versa.
2019-06-18 11:01:12 +02:00
export function prometheusRegularEscape ( value : any ) {
2020-07-30 18:07:22 +02:00
return typeof value === 'string' ? value . replace ( /\\/g , '\\\\' ) . replace ( /'/g , "\\\\'" ) : value ;
2019-01-18 17:19:35 +01:00
}
2019-06-18 11:01:12 +02:00
export function prometheusSpecialRegexEscape ( value : any ) {
2020-07-30 18:07:22 +02:00
return typeof value === 'string' ? value . replace ( /\\/g , '\\\\\\\\' ) . replace ( /[$^*{}\[\]\'+?.()|]/g , '\\\\$&' ) : value ;
2019-01-18 17:19:35 +01:00
}