Browse Source

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
Erik Sundell 7 years ago
parent
commit
c5af0bf1c5

+ 10 - 0
docs/sources/features/datasources/stackdriver.md

@@ -156,6 +156,16 @@ Example Alias By: `{{metric.type}} - {{metric.labels.instance_name}}`
 
 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
 
 Instead of hard-coding things like server, application and sensor name in you metric queries you can use variables in their place.

+ 19 - 4
pkg/tsdb/stackdriver/stackdriver.go

@@ -355,11 +355,21 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver
 func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackdriverResponse, query *StackdriverQuery) error {
 	metricLabels := 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 {
 		points := make([]tsdb.TimePoint, 0)
 
 		defaultMetricName := series.Metric.Type
+		if len(resourceTypes) > 1 {
+			defaultMetricName += " " + series.Resource.Type
+		}
 
 		for key, value := range series.Metric.Labels {
 			if !containsLabel(metricLabels[key], value) {
@@ -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))
 			}
 
-			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{
 				Name:   metricName,
@@ -429,7 +439,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
 						bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
 						additionalLabels := map[string]string{"bucket": bucketBound}
 						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),
 						}
 						if maxKey < i {
@@ -445,7 +455,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
 						bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
 						additionalLabels := map[string]string{"bucket": bucketBound}
 						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),
 						}
 					}
@@ -460,6 +470,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
 	queryRes.Meta.Set("resourceLabels", resourceLabels)
 	queryRes.Meta.Set("metricLabels", metricLabels)
 	queryRes.Meta.Set("groupBys", query.GroupBys)
+	queryRes.Meta.Set("resourceTypes", resourceTypes)
 
 	return nil
 }
@@ -473,7 +484,7 @@ func containsLabel(labels []string, newLabel string) bool {
 	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 == "" {
 		return defaultMetricName
 	}
@@ -487,6 +498,10 @@ func formatLegendKeys(metricType string, defaultMetricName string, metricLabels
 			return []byte(metricType)
 		}
 
+		if metaPartName == "resource.type" && resourceType != "" {
+			return []byte(resourceType)
+		}
+
 		metricPart := replaceWithMetricPart(metaPartName, metricType)
 
 		if metricPart != nil {

+ 24 - 26
public/app/plugins/datasource/stackdriver/datasource.ts

@@ -107,34 +107,32 @@ export default class StackdriverDatasource {
   }
 
   async query(options) {
-    this.queryPromise = new Promise(async resolve => {
-      const result = [];
-      const data = await this.getTimeSeries(options);
-      if (data.results) {
-        Object['values'](data.results).forEach(queryRes => {
-          if (!queryRes.series) {
-            return;
+    const result = [];
+    const data = await this.getTimeSeries(options);
+    if (data.results) {
+      Object['values'](data.results).forEach(queryRes => {
+        if (!queryRes.series) {
+          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;
-          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);
-          });
+          result.push(timeSerie);
         });
-      }
-
-      resolve({ data: result });
-    });
-    return this.queryPromise;
+      });
+      return { data: result };
+    } else {
+      return { data: [] };
+    }
   }
 
   async annotationQuery(options) {

+ 1 - 1
public/app/plugins/datasource/stackdriver/filter_segments.ts

@@ -44,7 +44,7 @@ export class FilterSegments {
         this.removeSegment.value = DefaultRemoveFilterValue;
         return Promise.resolve([this.removeSegment]);
       } else {
-        return this.getFilterKeysFunc();
+        return this.getFilterKeysFunc(segment, DefaultRemoveFilterValue);
       }
     }
 

+ 1 - 1
public/app/plugins/datasource/stackdriver/partials/query.filter.html

@@ -28,7 +28,7 @@
   <div class="gf-form">
     <span class="gf-form-label query-keyword width-9">Group By</span>
     <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 class="gf-form gf-form--grow">

+ 0 - 1
public/app/plugins/datasource/stackdriver/query_ctrl.ts

@@ -95,6 +95,5 @@ export class StackdriverQueryCtrl extends QueryCtrl {
         this.lastQueryError = jsonBody.error.message;
       }
     }
-    console.error(err);
   }
 }

+ 57 - 29
public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts

@@ -1,6 +1,6 @@
 import coreModule from 'app/core/core_module';
 import _ from 'lodash';
-import { FilterSegments, DefaultRemoveFilterValue } from './filter_segments';
+import { FilterSegments } from './filter_segments';
 import appEvents from 'app/core/app_events';
 
 export class StackdriverFilter {
@@ -26,8 +26,10 @@ export class StackdriverFilter {
 export class StackdriverFilterCtrl {
   metricLabels: { [key: string]: string[] };
   resourceLabels: { [key: string]: string[] };
+  resourceTypes: string[];
 
   defaultRemoveGroupByValue = '-- remove group by --';
+  resourceTypeValue = 'resource.type';
   loadLabelsPromise: Promise<any>;
 
   service: string;
@@ -72,7 +74,7 @@ export class StackdriverFilterCtrl {
     this.filterSegments = new FilterSegments(
       this.uiSegmentSrv,
       this.target,
-      this.getGroupBys.bind(this, null, null, DefaultRemoveFilterValue, false),
+      this.getFilterKeys.bind(this),
       this.getFilterValues.bind(this)
     );
     this.filterSegments.buildSegmentModel();
@@ -151,6 +153,7 @@ export class StackdriverFilterCtrl {
         const data = await this.datasource.getLabels(this.target.metricType, this.target.refId);
         this.metricLabels = data.results[this.target.refId].meta.metricLabels;
         this.resourceLabels = data.results[this.target.refId].meta.resourceLabels;
+        this.resourceTypes = data.results[this.target.refId].meta.resourceTypes;
         resolve();
       } catch (error) {
         if (error.data && error.data.message) {
@@ -191,45 +194,66 @@ export class StackdriverFilterCtrl {
     this.$rootScope.$broadcast('metricTypeChanged');
   }
 
-  async getGroupBys(segment, index, removeText?: string, removeUsed = true) {
+  async createLabelKeyElements() {
     await this.loadLabelsPromise;
 
-    const metricLabels = Object.keys(this.metricLabels || {})
-      .filter(ml => {
-        if (!removeUsed) {
-          return true;
-        }
-        return this.target.aggregation.groupBys.indexOf('metric.label.' + ml) === -1;
-      })
-      .map(l => {
-        return this.uiSegmentSrv.newSegment({
-          value: `metric.label.${l}`,
-          expandable: false,
-        });
+    let elements = Object.keys(this.metricLabels || {}).map(l => {
+      return this.uiSegmentSrv.newSegment({
+        value: `metric.label.${l}`,
+        expandable: false,
       });
+    });
 
-    const resourceLabels = Object.keys(this.resourceLabels || {})
-      .filter(ml => {
-        if (!removeUsed) {
-          return true;
-        }
-
-        return this.target.aggregation.groupBys.indexOf('resource.label.' + ml) === -1;
-      })
-      .map(l => {
+    elements = [
+      ...elements,
+      ...Object.keys(this.resourceLabels || {}).map(l => {
         return this.uiSegmentSrv.newSegment({
           value: `resource.label.${l}`,
           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';
-    if (noValueOrPlusButton && metricLabels.length === 0 && resourceLabels.length === 0) {
-      return Promise.resolve([]);
+    if (noValueOrPlusButton && elements.length === 0) {
+      return [];
     }
 
-    this.removeSegment.value = removeText || this.defaultRemoveGroupByValue;
-    return Promise.resolve([...metricLabels, ...resourceLabels, this.removeSegment]);
+    this.removeSegment.value = this.defaultRemoveGroupByValue;
+    return [...elements, this.removeSegment];
   }
 
   groupByChanged(segment, index) {
@@ -273,6 +297,10 @@ export class StackdriverFilterCtrl {
       return this.resourceLabels[shortKey];
     }
 
+    if (filterKey === this.resourceTypeValue) {
+      return this.resourceTypes;
+    }
+
     return [];
   }