Преглед на файлове

Histogram fix (#8727)

* histogram: don't cut negative values, issue #8628

* histogram: add percent/count option

* histogram: add tests for values normalizing

* histogram: improved ticks rendering

* histogram: fix default value in axes editor
Alexander Zobnin преди 8 години
родител
ревизия
8634c9d457

+ 6 - 0
public/app/plugins/panel/graph/axes_editor.html

@@ -67,6 +67,12 @@
 		</div>
 		</div>
 
 
 		<!-- Histogram mode -->
 		<!-- Histogram mode -->
+		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'histogram'">
+			<label class="gf-form-label width-6">Value</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select class="gf-form-input" ng-model="ctrl.panel.xaxis.histogramValue" ng-options="v as k for (k, v) in ctrl.histogramValues" ng-change="ctrl.xAxisOptionChanged()"> </select>
+			</div>
+		</div>
 		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'histogram'">
 		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'histogram'">
 			<label class="gf-form-label width-6">Buckets</label>
 			<label class="gf-form-label width-6">Buckets</label>
 			<input type="number" class="gf-form-input max-width-8" ng-model="ctrl.panel.xaxis.buckets" placeholder="auto" ng-change="ctrl.render()" ng-model-onblur bs-tooltip="'Number of buckets'" data-placement="right">
 			<input type="number" class="gf-form-input max-width-8" ng-model="ctrl.panel.xaxis.buckets" placeholder="auto" ng-change="ctrl.render()" ng-model-onblur bs-tooltip="'Number of buckets'" data-placement="right">

+ 6 - 0
public/app/plugins/panel/graph/axes_editor.ts

@@ -10,6 +10,7 @@ export class AxesEditorCtrl {
   xAxisModes: any;
   xAxisModes: any;
   xAxisStatOptions: any;
   xAxisStatOptions: any;
   xNameSegment: any;
   xNameSegment: any;
+  histogramValues: any;
 
 
   /** @ngInject **/
   /** @ngInject **/
   constructor(private $scope, private $q) {
   constructor(private $scope, private $q) {
@@ -34,6 +35,11 @@ export class AxesEditorCtrl {
       // 'Data field': 'field',
       // 'Data field': 'field',
     };
     };
 
 
+    this.histogramValues = {
+      'Percent': 'percent',
+      'Count': 'count'
+    };
+
     this.xAxisStatOptions =  [
     this.xAxisStatOptions =  [
       {text: 'Avg', value: 'avg'},
       {text: 'Avg', value: 'avg'},
       {text: 'Min', value: 'min'},
       {text: 'Min', value: 'min'},

+ 23 - 6
public/app/plugins/panel/graph/graph.ts

@@ -312,10 +312,13 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
               let histMax = _.max(_.map(data, s => s.stats.max));
               let histMax = _.max(_.map(data, s => s.stats.max));
               let ticks = panel.xaxis.buckets || panelWidth / 50;
               let ticks = panel.xaxis.buckets || panelWidth / 50;
               bucketSize = tickStep(histMin, histMax, ticks);
               bucketSize = tickStep(histMin, histMax, ticks);
-              let histogram = convertValuesToHistogram(values, bucketSize);
 
 
+              let normalize = panel.xaxis.histogramValue === 'percent';
+              let histogram = convertValuesToHistogram(values, bucketSize, normalize);
+
+              let seriesLabel = panel.xaxis.histogramValue || "count";
               data[0].data = histogram;
               data[0].data = histogram;
-              data[0].alias = data[0].label = data[0].id = "count";
+              data[0].alias = data[0].label = data[0].id = seriesLabel;
               data = [data[0]];
               data = [data[0]];
 
 
               options.series.bars.barWidth = bucketSize * 0.8;
               options.series.bars.barWidth = bucketSize * 0.8;
@@ -422,21 +425,32 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
 
 
       function addXHistogramAxis(options, bucketSize) {
       function addXHistogramAxis(options, bucketSize) {
         let ticks, min, max;
         let ticks, min, max;
+        let defaultTicks = panelWidth / 50;
 
 
         if (data.length && bucketSize) {
         if (data.length && bucketSize) {
           ticks = _.map(data[0].data, point => point[0]);
           ticks = _.map(data[0].data, point => point[0]);
+          min = _.min(ticks);
+          max = _.max(ticks);
+
+          // Adjust tick step
+          let tickStep = bucketSize;
+          let ticks_num = Math.floor((max - min) / tickStep);
+          while (ticks_num > defaultTicks) {
+            tickStep = tickStep * 2;
+            ticks_num = Math.ceil((max - min) / tickStep);
+          }
 
 
           // Expand ticks for pretty view
           // Expand ticks for pretty view
-          min = Math.max(0, _.min(ticks) - bucketSize);
-          max = _.max(ticks) + bucketSize;
+          min = Math.floor(min / tickStep) * tickStep;
+          max = Math.ceil(max / tickStep) * tickStep;
 
 
           ticks = [];
           ticks = [];
-          for (let i = min; i <= max; i += bucketSize) {
+          for (let i = min; i <= max; i += tickStep) {
             ticks.push(i);
             ticks.push(i);
           }
           }
         } else {
         } else {
           // Set defaults if no data
           // Set defaults if no data
-          ticks = panelWidth / 100;
+          ticks = defaultTicks / 2;
           min = 0;
           min = 0;
           max = 1;
           max = 1;
         }
         }
@@ -450,6 +464,9 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
           label: "Histogram",
           label: "Histogram",
           ticks: ticks
           ticks: ticks
         };
         };
+
+        // Use 'short' format for histogram values
+        configureAxisMode(options.xaxis, 'short');
       }
       }
 
 
       function addXTableAxis(options) {
       function addXTableAxis(options) {

+ 9 - 2
public/app/plugins/panel/graph/histogram.ts

@@ -26,7 +26,7 @@ export function getSeriesValues(data: any): number[] {
  * @param values
  * @param values
  * @param bucketSize
  * @param bucketSize
  */
  */
-export function convertValuesToHistogram(values: number[], bucketSize: number): any[] {
+export function convertValuesToHistogram(values: number[], bucketSize: number, normalize = false): any[] {
   let histogram = {};
   let histogram = {};
 
 
   for (let i = 0; i < values.length; i++) {
   for (let i = 0; i < values.length; i++) {
@@ -38,9 +38,16 @@ export function convertValuesToHistogram(values: number[], bucketSize: number):
     }
     }
   }
   }
 
 
-  return _.map(histogram, (count, bound) => {
+  let histogam_series = _.map(histogram, (count, bound) => {
+    if (normalize && values.length) {
+      return [Number(bound), count / values.length];
+    }
+
     return [Number(bound), count];
     return [Number(bound), count];
   });
   });
+
+  // Sort by Y axis values
+  return _.sortBy(histogam_series, point => point[0]);
 }
 }
 
 
 function getBucketBound(value: number, bucketSize: number): number {
 function getBucketBound(value: number, bucketSize: number): number {

+ 2 - 1
public/app/plugins/panel/graph/module.ts

@@ -58,7 +58,8 @@ class GraphCtrl extends MetricsPanelCtrl {
       mode: 'time',
       mode: 'time',
       name: null,
       name: null,
       values: [],
       values: [],
-      buckets: null
+      buckets: null,
+      histogramValue: 'percent'
     },
     },
     // show/hide lines
     // show/hide lines
     lines         : true,
     lines         : true,

+ 27 - 5
public/app/plugins/panel/graph/specs/histogram_specs.ts

@@ -1,7 +1,6 @@
 ///<reference path="../../../../headers/common.d.ts" />
 ///<reference path="../../../../headers/common.d.ts" />
-
+import _ from 'lodash';
 import { describe, beforeEach, it, expect } from '../../../../../test/lib/common';
 import { describe, beforeEach, it, expect } from '../../../../../test/lib/common';
-
 import { convertValuesToHistogram, getSeriesValues } from '../histogram';
 import { convertValuesToHistogram, getSeriesValues } from '../histogram';
 
 
 describe('Graph Histogam Converter', function () {
 describe('Graph Histogam Converter', function () {
@@ -11,13 +10,13 @@ describe('Graph Histogam Converter', function () {
     let bucketSize = 10;
     let bucketSize = 10;
 
 
     beforeEach(() => {
     beforeEach(() => {
-      values = [1, 2, 10, 11, 17, 20, 29];
+      values = [1, 2, 10, 11, 17, 20, 29, 30, 31, 33];
     });
     });
 
 
     it('Should convert to series-like array', () => {
     it('Should convert to series-like array', () => {
       bucketSize = 10;
       bucketSize = 10;
       let expected = [
       let expected = [
-        [0, 2], [10, 3], [20, 2]
+        [0, 2], [10, 3], [20, 2], [30, 3]
       ];
       ];
 
 
       let histogram = convertValuesToHistogram(values, bucketSize);
       let histogram = convertValuesToHistogram(values, bucketSize);
@@ -27,12 +26,35 @@ describe('Graph Histogam Converter', function () {
     it('Should not add empty buckets', () => {
     it('Should not add empty buckets', () => {
       bucketSize = 5;
       bucketSize = 5;
       let expected = [
       let expected = [
-        [0, 2], [10, 2], [15, 1], [20, 1], [25, 1]
+        [0, 2], [10, 2], [15, 1], [20, 1], [25, 1], [30, 3]
       ];
       ];
 
 
       let histogram = convertValuesToHistogram(values, bucketSize);
       let histogram = convertValuesToHistogram(values, bucketSize);
       expect(histogram).to.eql(expected);
       expect(histogram).to.eql(expected);
     });
     });
+
+    it('Should normalize values', () => {
+      bucketSize = 5;
+      let normalize = true;
+      let expected = [
+        [0, 0.2], [10, 0.2], [15, 0.1], [20, 0.1], [25, 0.1], [30, 0.3]
+      ];
+
+      let histogram = convertValuesToHistogram(values, bucketSize, normalize);
+      expect(histogram).to.eql(expected);
+    });
+
+    it('Sum of normalized values should be 1', () => {
+      bucketSize = 5;
+      let normalize = true;
+      let expected = [
+        [0, 0.2], [10, 0.2], [15, 0.1], [20, 0.1], [25, 0.1], [30, 0.3]
+      ];
+
+      let histogram = convertValuesToHistogram(values, bucketSize, normalize);
+      let sum = _.reduce(histogram, (sum, point) => sum + point[1], 0);
+      expect(sum).to.eql(1);
+    });
   });
   });
 
 
   describe('Series to values converter', () => {
   describe('Series to values converter', () => {