diff --git a/docs/sources/datasources/graphite.md b/docs/sources/datasources/graphite.md index 4bb4dd68168..a4dbd79fd0d 100644 --- a/docs/sources/datasources/graphite.md +++ b/docs/sources/datasources/graphite.md @@ -138,9 +138,34 @@ For more details, see the [Graphite docs on the autocomplete API for tags](http: ### Query variable -The query you specify in the query field should be a metric find type of query. For example, a query like `prod.servers.*` will fill the +The query you specify in the query field should be a metric find type of query. For example, a query like `prod.servers.*` fills the variable with all possible values that exist in the wildcard position. +The results contain all possible values occurring only at the last level of the query. To get full metric names matching the query +use expand function (`expand(*.servers.*)`). + +#### Comparison between expanded and non-expanded metric search results + +The expanded query returns the full names of matching metrics. In combination with regex, it can extract any part of the metric name. By contrast, a non-expanded query only returns the last part of the metric name. It does not allow you to extract other parts of metric names. + +Here are some example metrics: +- `prod.servers.001.cpu` +- `prod.servers.002.cpu` +- `test.servers.001.cpu` + +The following examples show how expanded and non-expanded queries can be used to fetch specific parts of the metrics name. + +| non-expanded query | results | expanded query | expanded results | +|--------------|---------|----------------|------------------| +| `*` | prod, test | `expand(*)` | prod, test +| `*.servers` | servers | `expand(*.servers)` | prod.servers, test.servers | +| `test.servers` | servers | `expand(test.servers)` | test.servers | +| `*.servers.*` | 001,002 | `expand(*.servers.*)` | prod.servers.001, prod.servers.002, test.servers.001 | +| `test.servers.*` | 001 | `expand(test.servers.*)` | test.servers.001 | +| `*.servers.*.cpu` | cpu | `expand(*.servers.*.cpu)` | prod.servers.001.cpu, prod.servers.002.cpu, test.servers.001.cpu | + +As you can see from the results, the non-expanded query is the same as an expanded query with a regex matching the last part of the name. + You can also create nested variables that use other variables in their definition. For example `apps.$app.servers.*` uses the variable `$app` in its query definition. diff --git a/public/app/plugins/datasource/graphite/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts index dda57e9d442..ad6bfbacea2 100644 --- a/public/app/plugins/datasource/graphite/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/datasource.test.ts @@ -512,6 +512,16 @@ describe('graphiteDatasource', () => { expect(requestOptions.data).toEqual('query=app.*'); expect(results).not.toBe(null); }); + + it('should request expanded metrics', () => { + ctx.ds.metricFindQuery('expand(*.servers.*)').then((data: any) => { + results = data; + }); + + expect(requestOptions.url).toBe('/api/datasources/proxy/1/metrics/expand'); + expect(requestOptions.params.query).toBe('*.servers.*'); + expect(results).not.toBe(null); + }); }); }); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 095e42889e9..b45deb3c897 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -365,6 +365,8 @@ export class GraphiteDatasource extends DataSourceApi< metricFindQuery(query: string, optionalOptions?: any): Promise { const options: any = optionalOptions || {}; + + // First attempt to check for tag-related functions (using empty wildcard for interpolation) let interpolatedQuery = this.templateSrv.replace( query, getSearchFilterScopedVar({ query, wildcardChar: '', options: optionalOptions }) @@ -386,26 +388,60 @@ export class GraphiteDatasource extends DataSourceApi< return this.getTagsAutoComplete(expressions, undefined, options); } + // If no tag-related query was found, perform metric-based search (using * as the wildcard for interpolation) + let useExpand = query.match(/^expand\((.*)\)$/); + query = useExpand ? useExpand[1] : query; + interpolatedQuery = this.templateSrv.replace( query, getSearchFilterScopedVar({ query, wildcardChar: '*', options: optionalOptions }) ); + let range; + if (options.range) { + range = { + from: this.translateTime(options.range.from, false, options.timezone), + until: this.translateTime(options.range.to, true, options.timezone), + }; + } + + if (useExpand) { + return this.requestMetricExpand(interpolatedQuery, options.requestId, range); + } else { + return this.requestMetricFind(interpolatedQuery, options.requestId, range); + } + } + + /** + * Search for metrics matching giving pattern using /metrics/find endpoint. It will + * return all possible values at the last level of the query, for example: + * + * metrics: prod.servers.001.cpu, prod.servers.002.cpu + * query: *.servers.* + * result: 001, 002 + * + * For more complex searches use requestMetricExpand + */ + private requestMetricFind( + query: string, + requestId: string, + range?: { from: any; until: any } + ): Promise { const httpOptions: any = { method: 'POST', url: '/metrics/find', params: {}, - data: `query=${interpolatedQuery}`, + data: `query=${query}`, headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, // for cancellations - requestId: options.requestId, + requestId: requestId, }; - if (options.range) { - httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone); - httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone); + if (range) { + httpOptions.params.from = range.from; + httpOptions.params.until = range.until; } return this.doGraphiteRequest(httpOptions) @@ -422,6 +458,46 @@ export class GraphiteDatasource extends DataSourceApi< .toPromise(); } + /** + * Search for metrics matching giving pattern using /metrics/expand endpoint. + * The result will contain all metrics (with full name) matching provided query. + * It's a more flexible version of /metrics/find endpoint (@see requestMetricFind) + */ + private requestMetricExpand( + query: string, + requestId: string, + range?: { from: any; until: any } + ): Promise { + const httpOptions: any = { + method: 'GET', + url: '/metrics/expand', + params: { query }, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + // for cancellations + requestId, + }; + + if (range) { + httpOptions.params.from = range.from; + httpOptions.params.until = range.until; + } + + return this.doGraphiteRequest(httpOptions) + .pipe( + map((results: any) => { + return _map(results.data.results, (metric) => { + return { + text: metric, + expandable: false, + }; + }); + }) + ) + .toPromise(); + } + getTags(optionalOptions: any) { const options = optionalOptions || {};