Commit c5af0bf1 by Erik Sundell Committed by Daniel Lee

Resource type filter (#13784)

* stackdriver: add resource type to filter and group bys

* stackdriver: remove not used param

* stackdriver: refactor filter and group by code

* stackdriver: remove resource type if its already in filter list

* stackdriver: remove debug logging

* stackdriver: remove more debug logging

* stackdriver: append resource type to legend name if there are more than one type present in the response

* stackdriver: only make new request if filter has real value

* stackdriver: format legend support for resource type

* stackdriver: add resource type to documentation

* stackdriver: not returning promise from query function

* stackdriver: fix refactoring bug

* stackdriver: remove not used import
parent edba0880
...@@ -156,6 +156,16 @@ Example Alias By: `{{metric.type}} - {{metric.labels.instance_name}}` ...@@ -156,6 +156,16 @@ Example Alias By: `{{metric.type}} - {{metric.labels.instance_name}}`
Example Result: `compute.googleapis.com/instance/cpu/usage_time - server1-prod` Example Result: `compute.googleapis.com/instance/cpu/usage_time - server1-prod`
It is also possible to resolve the name of the Monitored Resource Type.
| Alias Pattern Format | Description | Example Result |
| ------------------------ | ------------------------------------------------| ---------------- |
| `{{resource.type}}` | returns the name of the monitored resource type | `gce_instance` |
Example Alias By: `{{resource.type}} - {{metric.type}}`
Example Result: `gce_instance - compute.googleapis.com/instance/cpu/usage_time`
## Templating ## Templating
Instead of hard-coding things like server, application and sensor name in you metric queries you can use variables in their place. Instead of hard-coding things like server, application and sensor name in you metric queries you can use variables in their place.
......
...@@ -355,11 +355,21 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver ...@@ -355,11 +355,21 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver
func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackdriverResponse, query *StackdriverQuery) error { func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackdriverResponse, query *StackdriverQuery) error {
metricLabels := make(map[string][]string) metricLabels := make(map[string][]string)
resourceLabels := make(map[string][]string) resourceLabels := make(map[string][]string)
var resourceTypes []string
for _, series := range data.TimeSeries {
if !containsLabel(resourceTypes, series.Resource.Type) {
resourceTypes = append(resourceTypes, series.Resource.Type)
}
}
for _, series := range data.TimeSeries { for _, series := range data.TimeSeries {
points := make([]tsdb.TimePoint, 0) points := make([]tsdb.TimePoint, 0)
defaultMetricName := series.Metric.Type defaultMetricName := series.Metric.Type
if len(resourceTypes) > 1 {
defaultMetricName += " " + series.Resource.Type
}
for key, value := range series.Metric.Labels { for key, value := range series.Metric.Labels {
if !containsLabel(metricLabels[key], value) { if !containsLabel(metricLabels[key], value) {
...@@ -403,7 +413,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta ...@@ -403,7 +413,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000)) points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000))
} }
metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, make(map[string]string), query) metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, series.Resource.Type, series.Metric.Labels, series.Resource.Labels, make(map[string]string), query)
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
Name: metricName, Name: metricName,
...@@ -429,7 +439,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta ...@@ -429,7 +439,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i) bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
additionalLabels := map[string]string{"bucket": bucketBound} additionalLabels := map[string]string{"bucket": bucketBound}
buckets[i] = &tsdb.TimeSeries{ buckets[i] = &tsdb.TimeSeries{
Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, additionalLabels, query), Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Resource.Type, series.Metric.Labels, series.Resource.Labels, additionalLabels, query),
Points: make([]tsdb.TimePoint, 0), Points: make([]tsdb.TimePoint, 0),
} }
if maxKey < i { if maxKey < i {
...@@ -445,7 +455,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta ...@@ -445,7 +455,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i) bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
additionalLabels := map[string]string{"bucket": bucketBound} additionalLabels := map[string]string{"bucket": bucketBound}
buckets[i] = &tsdb.TimeSeries{ buckets[i] = &tsdb.TimeSeries{
Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, additionalLabels, query), Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Resource.Type, series.Metric.Labels, series.Resource.Labels, additionalLabels, query),
Points: make([]tsdb.TimePoint, 0), Points: make([]tsdb.TimePoint, 0),
} }
} }
...@@ -460,6 +470,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta ...@@ -460,6 +470,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
queryRes.Meta.Set("resourceLabels", resourceLabels) queryRes.Meta.Set("resourceLabels", resourceLabels)
queryRes.Meta.Set("metricLabels", metricLabels) queryRes.Meta.Set("metricLabels", metricLabels)
queryRes.Meta.Set("groupBys", query.GroupBys) queryRes.Meta.Set("groupBys", query.GroupBys)
queryRes.Meta.Set("resourceTypes", resourceTypes)
return nil return nil
} }
...@@ -473,7 +484,7 @@ func containsLabel(labels []string, newLabel string) bool { ...@@ -473,7 +484,7 @@ func containsLabel(labels []string, newLabel string) bool {
return false return false
} }
func formatLegendKeys(metricType string, defaultMetricName string, metricLabels map[string]string, resourceLabels map[string]string, additionalLabels map[string]string, query *StackdriverQuery) string { func formatLegendKeys(metricType string, defaultMetricName string, resourceType string, metricLabels map[string]string, resourceLabels map[string]string, additionalLabels map[string]string, query *StackdriverQuery) string {
if query.AliasBy == "" { if query.AliasBy == "" {
return defaultMetricName return defaultMetricName
} }
...@@ -487,6 +498,10 @@ func formatLegendKeys(metricType string, defaultMetricName string, metricLabels ...@@ -487,6 +498,10 @@ func formatLegendKeys(metricType string, defaultMetricName string, metricLabels
return []byte(metricType) return []byte(metricType)
} }
if metaPartName == "resource.type" && resourceType != "" {
return []byte(resourceType)
}
metricPart := replaceWithMetricPart(metaPartName, metricType) metricPart := replaceWithMetricPart(metaPartName, metricType)
if metricPart != nil { if metricPart != nil {
......
...@@ -107,34 +107,32 @@ export default class StackdriverDatasource { ...@@ -107,34 +107,32 @@ export default class StackdriverDatasource {
} }
async query(options) { async query(options) {
this.queryPromise = new Promise(async resolve => { const result = [];
const result = []; const data = await this.getTimeSeries(options);
const data = await this.getTimeSeries(options); if (data.results) {
if (data.results) { Object['values'](data.results).forEach(queryRes => {
Object['values'](data.results).forEach(queryRes => { if (!queryRes.series) {
if (!queryRes.series) { return;
return; }
this.projectName = queryRes.meta.defaultProject;
const unit = this.resolvePanelUnitFromTargets(options.targets);
queryRes.series.forEach(series => {
let timeSerie: any = {
target: series.name,
datapoints: series.points,
refId: queryRes.refId,
meta: queryRes.meta,
};
if (unit) {
timeSerie = { ...timeSerie, unit };
} }
this.projectName = queryRes.meta.defaultProject; result.push(timeSerie);
const unit = this.resolvePanelUnitFromTargets(options.targets);
queryRes.series.forEach(series => {
let timeSerie: any = {
target: series.name,
datapoints: series.points,
refId: queryRes.refId,
meta: queryRes.meta,
};
if (unit) {
timeSerie = { ...timeSerie, unit };
}
result.push(timeSerie);
});
}); });
} });
return { data: result };
resolve({ data: result }); } else {
}); return { data: [] };
return this.queryPromise; }
} }
async annotationQuery(options) { async annotationQuery(options) {
......
...@@ -44,7 +44,7 @@ export class FilterSegments { ...@@ -44,7 +44,7 @@ export class FilterSegments {
this.removeSegment.value = DefaultRemoveFilterValue; this.removeSegment.value = DefaultRemoveFilterValue;
return Promise.resolve([this.removeSegment]); return Promise.resolve([this.removeSegment]);
} else { } else {
return this.getFilterKeysFunc(); return this.getFilterKeysFunc(segment, DefaultRemoveFilterValue);
} }
} }
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label query-keyword width-9">Group By</span> <span class="gf-form-label query-keyword width-9">Group By</span>
<div class="gf-form" ng-repeat="segment in ctrl.groupBySegments"> <div class="gf-form" ng-repeat="segment in ctrl.groupBySegments">
<metric-segment segment="segment" get-options="ctrl.getGroupBys(segment, $index)" on-change="ctrl.groupByChanged(segment, $index)"></metric-segment> <metric-segment segment="segment" get-options="ctrl.getGroupBys(segment)" on-change="ctrl.groupByChanged(segment, $index)"></metric-segment>
</div> </div>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
......
...@@ -95,6 +95,5 @@ export class StackdriverQueryCtrl extends QueryCtrl { ...@@ -95,6 +95,5 @@ export class StackdriverQueryCtrl extends QueryCtrl {
this.lastQueryError = jsonBody.error.message; this.lastQueryError = jsonBody.error.message;
} }
} }
console.error(err);
} }
} }
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import _ from 'lodash'; import _ from 'lodash';
import { FilterSegments, DefaultRemoveFilterValue } from './filter_segments'; import { FilterSegments } from './filter_segments';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
export class StackdriverFilter { export class StackdriverFilter {
...@@ -26,8 +26,10 @@ export class StackdriverFilter { ...@@ -26,8 +26,10 @@ export class StackdriverFilter {
export class StackdriverFilterCtrl { export class StackdriverFilterCtrl {
metricLabels: { [key: string]: string[] }; metricLabels: { [key: string]: string[] };
resourceLabels: { [key: string]: string[] }; resourceLabels: { [key: string]: string[] };
resourceTypes: string[];
defaultRemoveGroupByValue = '-- remove group by --'; defaultRemoveGroupByValue = '-- remove group by --';
resourceTypeValue = 'resource.type';
loadLabelsPromise: Promise<any>; loadLabelsPromise: Promise<any>;
service: string; service: string;
...@@ -72,7 +74,7 @@ export class StackdriverFilterCtrl { ...@@ -72,7 +74,7 @@ export class StackdriverFilterCtrl {
this.filterSegments = new FilterSegments( this.filterSegments = new FilterSegments(
this.uiSegmentSrv, this.uiSegmentSrv,
this.target, this.target,
this.getGroupBys.bind(this, null, null, DefaultRemoveFilterValue, false), this.getFilterKeys.bind(this),
this.getFilterValues.bind(this) this.getFilterValues.bind(this)
); );
this.filterSegments.buildSegmentModel(); this.filterSegments.buildSegmentModel();
...@@ -151,6 +153,7 @@ export class StackdriverFilterCtrl { ...@@ -151,6 +153,7 @@ export class StackdriverFilterCtrl {
const data = await this.datasource.getLabels(this.target.metricType, this.target.refId); const data = await this.datasource.getLabels(this.target.metricType, this.target.refId);
this.metricLabels = data.results[this.target.refId].meta.metricLabels; this.metricLabels = data.results[this.target.refId].meta.metricLabels;
this.resourceLabels = data.results[this.target.refId].meta.resourceLabels; this.resourceLabels = data.results[this.target.refId].meta.resourceLabels;
this.resourceTypes = data.results[this.target.refId].meta.resourceTypes;
resolve(); resolve();
} catch (error) { } catch (error) {
if (error.data && error.data.message) { if (error.data && error.data.message) {
...@@ -191,45 +194,66 @@ export class StackdriverFilterCtrl { ...@@ -191,45 +194,66 @@ export class StackdriverFilterCtrl {
this.$rootScope.$broadcast('metricTypeChanged'); this.$rootScope.$broadcast('metricTypeChanged');
} }
async getGroupBys(segment, index, removeText?: string, removeUsed = true) { async createLabelKeyElements() {
await this.loadLabelsPromise; await this.loadLabelsPromise;
const metricLabels = Object.keys(this.metricLabels || {}) let elements = Object.keys(this.metricLabels || {}).map(l => {
.filter(ml => { return this.uiSegmentSrv.newSegment({
if (!removeUsed) { value: `metric.label.${l}`,
return true; expandable: false,
}
return this.target.aggregation.groupBys.indexOf('metric.label.' + ml) === -1;
})
.map(l => {
return this.uiSegmentSrv.newSegment({
value: `metric.label.${l}`,
expandable: false,
});
}); });
});
const resourceLabels = Object.keys(this.resourceLabels || {}) elements = [
.filter(ml => { ...elements,
if (!removeUsed) { ...Object.keys(this.resourceLabels || {}).map(l => {
return true;
}
return this.target.aggregation.groupBys.indexOf('resource.label.' + ml) === -1;
})
.map(l => {
return this.uiSegmentSrv.newSegment({ return this.uiSegmentSrv.newSegment({
value: `resource.label.${l}`, value: `resource.label.${l}`,
expandable: false, expandable: false,
}); });
}); }),
];
if (this.resourceTypes && this.resourceTypes.length > 0) {
elements = [
...elements,
this.uiSegmentSrv.newSegment({
value: this.resourceTypeValue,
expandable: false,
}),
];
}
return elements;
}
async getFilterKeys(segment, removeText?: string) {
let elements = await this.createLabelKeyElements();
if (this.target.filters.indexOf(this.resourceTypeValue) !== -1) {
elements = elements.filter(e => e.value !== this.resourceTypeValue);
}
const noValueOrPlusButton = !segment || segment.type === 'plus-button';
if (noValueOrPlusButton && elements.length === 0) {
return [];
}
this.removeSegment.value = removeText;
return [...elements, this.removeSegment];
}
async getGroupBys(segment) {
let elements = await this.createLabelKeyElements();
elements = elements.filter(e => this.target.aggregation.groupBys.indexOf(e.value) === -1);
const noValueOrPlusButton = !segment || segment.type === 'plus-button'; const noValueOrPlusButton = !segment || segment.type === 'plus-button';
if (noValueOrPlusButton && metricLabels.length === 0 && resourceLabels.length === 0) { if (noValueOrPlusButton && elements.length === 0) {
return Promise.resolve([]); return [];
} }
this.removeSegment.value = removeText || this.defaultRemoveGroupByValue; this.removeSegment.value = this.defaultRemoveGroupByValue;
return Promise.resolve([...metricLabels, ...resourceLabels, this.removeSegment]); return [...elements, this.removeSegment];
} }
groupByChanged(segment, index) { groupByChanged(segment, index) {
...@@ -273,6 +297,10 @@ export class StackdriverFilterCtrl { ...@@ -273,6 +297,10 @@ export class StackdriverFilterCtrl {
return this.resourceLabels[shortKey]; return this.resourceLabels[shortKey];
} }
if (filterKey === this.resourceTypeValue) {
return this.resourceTypes;
}
return []; return [];
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment