Alexander Zobnin 7 роки тому
батько
коміт
18a90667ba

+ 58 - 0
public/app/core/utils/ticks.ts

@@ -156,3 +156,61 @@ export function getFlotTickDecimals(data, axis) {
   const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10);
   return { tickDecimals, scaledDecimals };
 }
+
+/**
+ * Format timestamp similar to Grafana graph panel.
+ * @param ticks Number of ticks
+ * @param min Time from (in milliseconds)
+ * @param max Time to (in milliseconds)
+ */
+export function grafanaTimeFormat(ticks, min, max) {
+  if (min && max && ticks) {
+    let range = max - min;
+    let secPerTick = range / ticks / 1000;
+    let oneDay = 86400000;
+    let oneYear = 31536000000;
+
+    if (secPerTick <= 45) {
+      return '%H:%M:%S';
+    }
+    if (secPerTick <= 7200 || range <= oneDay) {
+      return '%H:%M';
+    }
+    if (secPerTick <= 80000) {
+      return '%m/%d %H:%M';
+    }
+    if (secPerTick <= 2419200 || range <= oneYear) {
+      return '%m/%d';
+    }
+    return '%Y-%m';
+  }
+
+  return '%H:%M';
+}
+
+/**
+ * Logarithm of value for arbitrary base.
+ */
+export function logp(value, base) {
+  return Math.log(value) / Math.log(base);
+}
+
+/**
+ * Get decimal precision of number (3.14 => 2)
+ */
+export function getPrecision(num: number): number {
+  let str = num.toString();
+  return getStringPrecision(str);
+}
+
+/**
+ * Get decimal precision of number stored as a string ("3.14" => 2)
+ */
+export function getStringPrecision(num: string): number {
+  let dot_index = num.indexOf('.');
+  if (dot_index === -1) {
+    return 0;
+  } else {
+    return num.length - dot_index - 1;
+  }
+}

+ 17 - 2
public/app/plugins/panel/heatmap/heatmap_ctrl.ts

@@ -89,6 +89,8 @@ let colorSchemes = [
   { name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'darm' },
 ];
 
+const ds_support_histogram_sort = ['prometheus', 'elasticsearch'];
+
 export class HeatmapCtrl extends MetricsPanelCtrl {
   static templateUrl = 'module.html';
 
@@ -207,15 +209,20 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
   }
 
   convertHistogramToHeatmapData() {
+    const panelDatasource = this.getPanelDataSourceType();
     let xBucketSize, yBucketSize, bucketsData, tsBuckets;
 
+    // Try to sort series by bucket bound, if datasource doesn't do it.
+    if (!_.includes(ds_support_histogram_sort, panelDatasource)) {
+      this.series.sort(sortSeriesByLabel);
+    }
+
     // Convert histogram to heatmap. Each histogram bucket represented by the series which name is
     // a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as X axis labels.
-    this.series.sort(sortSeriesByLabel);
     bucketsData = histogramToHeatmap(this.series);
 
     tsBuckets = _.map(this.series, 'label');
-    if (this.datasource && this.datasource.type === 'prometheus') {
+    if (panelDatasource === 'prometheus') {
       // Prometheus labels are upper inclusive bounds, so add empty bottom bucket label.
       tsBuckets = [''].concat(tsBuckets);
     } else {
@@ -241,6 +248,14 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
     };
   }
 
+  getPanelDataSourceType() {
+    if (this.datasource.meta && this.datasource.meta.id) {
+      return this.datasource.meta.id;
+    } else {
+      return 'unknown';
+    }
+  }
+
   onDataReceived(dataList) {
     this.series = dataList.map(this.seriesHandler.bind(this));
 

+ 7 - 3
public/app/plugins/panel/heatmap/heatmap_data_converter.ts

@@ -67,7 +67,7 @@ function sortSeriesByLabel(s1, s2) {
     label1 = parseHistogramLabel(s1.label);
     label2 = parseHistogramLabel(s2.label);
   } catch (err) {
-    console.log(err);
+    console.log(err.message || err);
     return 0;
   }
 
@@ -83,10 +83,14 @@ function sortSeriesByLabel(s1, s2) {
 }
 
 function parseHistogramLabel(label: string): number {
-  if (label === '+Inf') {
+  if (label === '+Inf' || label === 'inf') {
     return +Infinity;
   }
-  return Number(label);
+  const value = Number(label);
+  if (isNaN(value)) {
+    throw new Error(`Error parsing histogram label: ${label} is not a number`);
+  }
+  return value;
 }
 
 /**

+ 7 - 25
public/app/plugins/panel/heatmap/heatmap_tooltip.ts

@@ -100,14 +100,14 @@ export class HeatmapTooltip {
     let countValueFormatter, bucketBoundFormatter;
     if (_.isNumber(this.panel.tooltipDecimals)) {
       countValueFormatter = this.countValueFormatter(this.panel.tooltipDecimals, null);
-      bucketBoundFormatter = this.bucketBoundFormatter(this.panel.tooltipDecimals, null);
+      bucketBoundFormatter = this.panelCtrl.tickValueFormatter(this.panelCtrl.decimals, null);
     } else {
       // auto decimals
       // legend and tooltip gets one more decimal precision
       // than graph legend ticks
       let decimals = (this.panelCtrl.decimals || -1) + 1;
       countValueFormatter = this.countValueFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
-      bucketBoundFormatter = this.bucketBoundFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
+      bucketBoundFormatter = this.panelCtrl.tickValueFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
     }
 
     let tooltipHtml = `<div class="graph-tooltip-time">${time}</div>
@@ -116,19 +116,13 @@ export class HeatmapTooltip {
     if (yData) {
       if (yData.bounds) {
         if (data.tsBuckets) {
-          const decimals = this.panelCtrl.decimals || 0;
+          // Use Y-axis labels
           const tickFormatter = valIndex => {
-            let valueFormatted = data.tsBuckets[valIndex];
-            if (!_.isNaN(_.toNumber(valueFormatted)) && valueFormatted !== '') {
-              // Try to format numeric tick labels
-              valueFormatted = this.bucketBoundFormatter(decimals)(_.toNumber(valueFormatted));
-            }
-            return valueFormatted;
+            return data.tsBucketsFormatted ? data.tsBucketsFormatted[valIndex] : data.tsBuckets[valIndex];
           };
-          const tsBucketsTickFormatter = tickFormatter.bind(this);
 
-          boundBottom = tsBucketsTickFormatter(yBucketIndex);
-          boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tsBucketsTickFormatter(yBucketIndex + 1) : '';
+          boundBottom = tickFormatter(yBucketIndex);
+          boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : '';
         } else {
           // Display 0 if bucket is a special 'zero' bucket
           let bottom = yData.y ? yData.bounds.bottom : 0;
@@ -282,21 +276,9 @@ export class HeatmapTooltip {
   }
 
   countValueFormatter(decimals, scaledDecimals = null) {
-    let format = 'none';
+    let format = 'short';
     return function(value) {
       return kbn.valueFormats[format](value, decimals, scaledDecimals);
     };
   }
-
-  bucketBoundFormatter(decimals, scaledDecimals = null) {
-    let format = this.panel.yAxis.format;
-    return function(value) {
-      try {
-        return format !== 'none' ? kbn.valueFormats[format](value, decimals, scaledDecimals) : value;
-      } catch (err) {
-        console.error(err.message || err);
-        return value;
-      }
-    };
-  }
 }

+ 20 - 58
public/app/plugins/panel/heatmap/rendering.ts

@@ -4,7 +4,7 @@ import moment from 'moment';
 import * as d3 from 'd3';
 import kbn from 'app/core/utils/kbn';
 import { appEvents, contextSrv } from 'app/core/core';
-import { tickStep, getScaledDecimals, getFlotTickSize } from 'app/core/utils/ticks';
+import * as ticksUtils from 'app/core/utils/ticks';
 import { HeatmapTooltip } from './heatmap_tooltip';
 import { mergeZeroBuckets } from './heatmap_data_converter';
 import { getColorScale, getOpacityScale } from './color_scale';
@@ -108,7 +108,7 @@ export default function link(scope, elem, attrs, ctrl) {
       .range([0, chartWidth]);
 
     let ticks = chartWidth / DEFAULT_X_TICK_SIZE_PX;
-    let grafanaTimeFormatter = grafanaTimeFormat(ticks, timeRange.from, timeRange.to);
+    let grafanaTimeFormatter = ticksUtils.grafanaTimeFormat(ticks, timeRange.from, timeRange.to);
     let timeFormat;
     let dashboardTimeZone = ctrl.dashboard.getTimezone();
     if (dashboardTimeZone === 'utc') {
@@ -141,7 +141,7 @@ export default function link(scope, elem, attrs, ctrl) {
 
   function addYAxis() {
     let ticks = Math.ceil(chartHeight / DEFAULT_Y_TICK_SIZE_PX);
-    let tick_interval = tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks);
+    let tick_interval = ticksUtils.tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks);
     let { y_min, y_max } = wideYAxisRange(data.heatmapStats.min, data.heatmapStats.max, tick_interval);
 
     // Rewrite min and max if it have been set explicitly
@@ -149,14 +149,14 @@ export default function link(scope, elem, attrs, ctrl) {
     y_max = panel.yAxis.max !== null ? panel.yAxis.max : y_max;
 
     // Adjust ticks after Y range widening
-    tick_interval = tickStep(y_min, y_max, ticks);
+    tick_interval = ticksUtils.tickStep(y_min, y_max, ticks);
     ticks = Math.ceil((y_max - y_min) / tick_interval);
 
-    let decimalsAuto = getPrecision(tick_interval);
+    let decimalsAuto = ticksUtils.getPrecision(tick_interval);
     let decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
     // Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
-    let flot_tick_size = getFlotTickSize(y_min, y_max, ticks, decimalsAuto);
-    let scaledDecimals = getScaledDecimals(decimals, flot_tick_size);
+    let flot_tick_size = ticksUtils.getFlotTickSize(y_min, y_max, ticks, decimalsAuto);
+    let scaledDecimals = ticksUtils.getScaledDecimals(decimals, flot_tick_size);
     ctrl.decimals = decimals;
     ctrl.scaledDecimals = scaledDecimals;
 
@@ -248,12 +248,12 @@ export default function link(scope, elem, attrs, ctrl) {
     let domain = yScale.domain();
     let tick_values = logScaleTickValues(domain, log_base);
 
-    let decimalsAuto = getPrecision(y_min);
+    let decimalsAuto = ticksUtils.getPrecision(y_min);
     let decimals = panel.yAxis.decimals || decimalsAuto;
 
     // Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
-    let flot_tick_size = getFlotTickSize(y_min, y_max, tick_values.length, decimalsAuto);
-    let scaledDecimals = getScaledDecimals(decimals, flot_tick_size);
+    let flot_tick_size = ticksUtils.getFlotTickSize(y_min, y_max, tick_values.length, decimalsAuto);
+    let scaledDecimals = ticksUtils.getScaledDecimals(decimals, flot_tick_size);
     ctrl.decimals = decimals;
     ctrl.scaledDecimals = scaledDecimals;
 
@@ -305,7 +305,7 @@ export default function link(scope, elem, attrs, ctrl) {
       .range([chartHeight, 0]);
 
     const tick_values = _.map(tsBuckets, (b, i) => i);
-    const decimalsAuto = _.max(_.map(tsBuckets, getStringPrecision));
+    const decimalsAuto = _.max(_.map(tsBuckets, ticksUtils.getStringPrecision));
     const decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
     ctrl.decimals = decimals;
 
@@ -318,6 +318,9 @@ export default function link(scope, elem, attrs, ctrl) {
       return valueFormatted;
     }
 
+    const tsBucketsFormatted = _.map(tsBuckets, (v, i) => tickFormatter(i));
+    data.tsBucketsFormatted = tsBucketsFormatted;
+
     let yAxis = d3
       .axisLeft(yScale)
       .tickValues(tick_values)
@@ -361,11 +364,11 @@ export default function link(scope, elem, attrs, ctrl) {
   }
 
   function adjustLogMax(max, base) {
-    return Math.pow(base, Math.ceil(logp(max, base)));
+    return Math.pow(base, Math.ceil(ticksUtils.logp(max, base)));
   }
 
   function adjustLogMin(min, base) {
-    return Math.pow(base, Math.floor(logp(min, base)));
+    return Math.pow(base, Math.floor(ticksUtils.logp(min, base)));
   }
 
   function logScaleTickValues(domain, base) {
@@ -374,14 +377,14 @@ export default function link(scope, elem, attrs, ctrl) {
     let tickValues = [];
 
     if (domainMin < 1) {
-      let under_one_ticks = Math.floor(logp(domainMin, base));
+      let under_one_ticks = Math.floor(ticksUtils.logp(domainMin, base));
       for (let i = under_one_ticks; i < 0; i++) {
         let tick_value = Math.pow(base, i);
         tickValues.push(tick_value);
       }
     }
 
-    let ticks = Math.ceil(logp(domainMax, base));
+    let ticks = Math.ceil(ticksUtils.logp(domainMax, base));
     for (let i = 0; i <= ticks; i++) {
       let tick_value = Math.pow(base, i);
       tickValues.push(tick_value);
@@ -402,6 +405,8 @@ export default function link(scope, elem, attrs, ctrl) {
     };
   }
 
+  ctrl.tickValueFormatter = tickValueFormatter;
+
   function fixYAxisTickSize() {
     heatmap
       .select('.axis-y')
@@ -827,46 +832,3 @@ export default function link(scope, elem, attrs, ctrl) {
   $heatmap.on('mousemove', onMouseMove);
   $heatmap.on('mouseleave', onMouseLeave);
 }
-
-function grafanaTimeFormat(ticks, min, max) {
-  if (min && max && ticks) {
-    let range = max - min;
-    let secPerTick = range / ticks / 1000;
-    let oneDay = 86400000;
-    let oneYear = 31536000000;
-
-    if (secPerTick <= 45) {
-      return '%H:%M:%S';
-    }
-    if (secPerTick <= 7200 || range <= oneDay) {
-      return '%H:%M';
-    }
-    if (secPerTick <= 80000) {
-      return '%m/%d %H:%M';
-    }
-    if (secPerTick <= 2419200 || range <= oneYear) {
-      return '%m/%d';
-    }
-    return '%Y-%m';
-  }
-
-  return '%H:%M';
-}
-
-function logp(value, base) {
-  return Math.log(value) / Math.log(base);
-}
-
-function getPrecision(num: number): number {
-  let str = num.toString();
-  return getStringPrecision(str);
-}
-
-function getStringPrecision(num: string): number {
-  let dot_index = num.indexOf('.');
-  if (dot_index === -1) {
-    return 0;
-  } else {
-    return num.length - dot_index - 1;
-  }
-}