Browse Source

Merge pull request #2883 from mtanda/prometheus_annotation_support

Prometheus annotation support
Carl Bergquist 10 years ago
parent
commit
a3b6efdbfa

+ 60 - 5
public/app/plugins/datasource/prometheus/datasource.ts

@@ -188,6 +188,57 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
     }
   };
 
+  this.annotationQuery = function(options) {
+    var annotation = options.annotation;
+    var expr = annotation.expr || '';
+    var tagKeys = annotation.tagKeys || '';
+    var titleFormat = annotation.titleFormat || '';
+    var textFormat = annotation.textFormat || '';
+
+    if (!expr) { return $q.when([]); }
+
+    var interpolated;
+    try {
+      interpolated = templateSrv.replace(expr);
+    } catch (err) {
+      return $q.reject(err);
+    }
+
+    var query = {
+      expr: interpolated,
+      step: '60s'
+    };
+    var start = getPrometheusTime(options.range.from, false);
+    var end = getPrometheusTime(options.range.to, true);
+    return this.performTimeSeriesQuery(query, start, end).then(function(results) {
+      var eventList = [];
+      tagKeys = tagKeys.split(',');
+
+      _.each(results.data.data.result, function(series) {
+        var tags = _.chain(series.metric)
+        .filter(function(v, k) {
+          return _.contains(tagKeys, k);
+        }).value();
+
+        _.each(series.values, function(value) {
+          if (value[1] === '1') {
+            var event = {
+              annotation: annotation,
+              time: Math.floor(value[0]) * 1000,
+              title: renderTemplate(titleFormat, series.metric),
+              tags: tags,
+              text: renderTemplate(textFormat, series.metric)
+            };
+
+            eventList.push(event);
+          }
+        });
+      });
+
+      return eventList;
+    });
+  };
+
   this.testDatasource = function() {
     return this.metricFindQuery('metrics(.*)').then(function() {
       return { status: 'success', message: 'Data source is working', title: 'Success' };
@@ -240,22 +291,26 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
       return getOriginalMetricName(labelData);
     }
 
+    return renderTemplate(options.legendFormat, labelData) || '{}';
+  }
+
+  function renderTemplate(format, data) {
     var originalSettings = _.templateSettings;
     _.templateSettings = {
       interpolate: /\{\{(.+?)\}\}/g
     };
 
-    var template = _.template(templateSrv.replace(options.legendFormat));
-    var metricName;
+    var template = _.template(templateSrv.replace(format));
+    var result;
     try {
-      metricName = template(labelData);
+      result = template(data);
     } catch (e) {
-      metricName = '{}';
+      result = null;
     }
 
     _.templateSettings = originalSettings;
 
-    return metricName;
+    return result;
   }
 
   function getOriginalMetricName(labelData) {

+ 6 - 1
public/app/plugins/datasource/prometheus/module.ts

@@ -5,8 +5,13 @@ class PrometheusConfigCtrl {
   static templateUrl = 'public/app/plugins/datasource/prometheus/partials/config.html';
 }
 
+class PrometheusAnnotationsQueryCtrl {
+  static templateUrl = 'public/app/plugins/datasource/prometheus/partials/annotations.editor.html';
+}
+
 export {
   PrometheusDatasource as Datasource,
   PrometheusQueryCtrl as QueryCtrl,
-  PrometheusConfigCtrl as ConfigCtrl
+  PrometheusConfigCtrl as ConfigCtrl,
+  PrometheusAnnotationsQueryCtrl as AnnotationsQueryCtrl,
 };

+ 28 - 0
public/app/plugins/datasource/prometheus/partials/annotations.editor.html

@@ -0,0 +1,28 @@
+<div class="editor-row">
+	<div class="section">
+		<h5>Search expression</h5>
+		<div class="editor-option">
+			<input type="text" class="span6" ng-model='ctrl.annotation.expr' placeholder="ALERTS"></input>
+		</div>
+	</div>
+</div>
+
+<div class="editor-row">
+  <div class="section">
+		<h5>Field formats</h5>
+		<div class="editor-option">
+			<label class="small">Title</label>
+			<input type="text" class="input-small" ng-model='ctrl.annotation.titleFormat' placeholder="alertname"></input>
+		</div>
+
+		<div class="editor-option">
+			<label class="small">Tags</label>
+			<input type="text" class="input-small" ng-model='ctrl.annotation.tagKeys' placeholder="label1,label2"></input>
+		</div>
+
+		<div class="editor-option">
+			<label class="small">Text</label>
+			<input type="text" class="input-small" ng-model='ctrl.annotation.textFormat' placeholder="instance"></input>
+		</div>
+	</div>
+</div>

+ 2 - 1
public/app/plugins/datasource/prometheus/plugin.json

@@ -3,5 +3,6 @@
   "name": "Prometheus",
   "id": "prometheus",
 
-  "metrics": true
+  "metrics": true,
+  "annotations": true
 }

+ 41 - 0
public/app/plugins/datasource/prometheus/specs/datasource_specs.ts

@@ -157,4 +157,45 @@ describe('PrometheusDatasource', function() {
       expect(results.length).to.be(3);
     });
   });
+  describe('When performing annotationQuery', function() {
+    var results;
+    var urlExpected = 'proxied/api/v1/query_range?query=' +
+                      encodeURIComponent('ALERTS{alertstate="firing"}') +
+                      '&start=1443438675&end=1443460275&step=60s';
+    var options = {
+      annotation: {
+        expr: 'ALERTS{alertstate="firing"}',
+        tagKeys: 'job',
+        titleFormat: '{{alertname}}',
+        textFormat: '{{instance}}'
+      },
+      range: {
+        from: moment(1443438674760),
+        to: moment(1443460274760)
+      }
+    };
+    var response = {
+      status: "success",
+      data: {
+        resultType: "matrix",
+        result: [{
+          metric: {"__name__": "ALERTS", alertname: "InstanceDown", alertstate: "firing", instance: "testinstance", job: "testjob"},
+          values: [[1443454528, "1"]]
+        }]
+      }
+    };
+    beforeEach(function() {
+      ctx.$httpBackend.expect('GET', urlExpected).respond(response);
+      ctx.ds.annotationQuery(options).then(function(data) { results = data; });
+      ctx.$httpBackend.flush();
+    });
+    it('should return annotation list', function() {
+      ctx.$rootScope.$apply();
+      expect(results.length).to.be(1);
+      expect(results[0].tags).to.contain('testjob');
+      expect(results[0].title).to.be('InstanceDown');
+      expect(results[0].text).to.be('testinstance');
+      expect(results[0].time).to.be(1443454528 * 1000);
+    });
+  });
 });