prometheus

Форк
0
287 строк · 9.2 Кб
1
import $ from 'jquery';
2

3
import { escapeHTML } from '../../utils';
4
import { GraphData, GraphExemplar, GraphProps, GraphSeries } from './Graph';
5
import moment from 'moment-timezone';
6
import { colorPool } from './ColorPool';
7
import { prepareHeatmapData } from './GraphHeatmapHelpers';
8
import { GraphDisplayMode } from './Panel';
9

10
export const formatValue = (y: number | null): string => {
11
  if (y === null) {
12
    return 'null';
13
  }
14
  const absY = Math.abs(y);
15

16
  if (absY >= 1e24) {
17
    return (y / 1e24).toFixed(2) + 'Y';
18
  } else if (absY >= 1e21) {
19
    return (y / 1e21).toFixed(2) + 'Z';
20
  } else if (absY >= 1e18) {
21
    return (y / 1e18).toFixed(2) + 'E';
22
  } else if (absY >= 1e15) {
23
    return (y / 1e15).toFixed(2) + 'P';
24
  } else if (absY >= 1e12) {
25
    return (y / 1e12).toFixed(2) + 'T';
26
  } else if (absY >= 1e9) {
27
    return (y / 1e9).toFixed(2) + 'G';
28
  } else if (absY >= 1e6) {
29
    return (y / 1e6).toFixed(2) + 'M';
30
  } else if (absY >= 1e3) {
31
    return (y / 1e3).toFixed(2) + 'k';
32
  } else if (absY >= 1) {
33
    return y.toFixed(2);
34
  } else if (absY === 0) {
35
    return y.toFixed(2);
36
  } else if (absY < 1e-23) {
37
    return (y / 1e-24).toFixed(2) + 'y';
38
  } else if (absY < 1e-20) {
39
    return (y / 1e-21).toFixed(2) + 'z';
40
  } else if (absY < 1e-17) {
41
    return (y / 1e-18).toFixed(2) + 'a';
42
  } else if (absY < 1e-14) {
43
    return (y / 1e-15).toFixed(2) + 'f';
44
  } else if (absY < 1e-11) {
45
    return (y / 1e-12).toFixed(2) + 'p';
46
  } else if (absY < 1e-8) {
47
    return (y / 1e-9).toFixed(2) + 'n';
48
  } else if (absY < 1e-5) {
49
    return (y / 1e-6).toFixed(2) + 'µ';
50
  } else if (absY < 1e-2) {
51
    return (y / 1e-3).toFixed(2) + 'm';
52
  } else if (absY <= 1) {
53
    return y.toFixed(2);
54
  }
55
  throw Error("couldn't format a value, this is a bug");
56
};
57

58
export const getHoverColor = (color: string, opacity: number, stacked: boolean): string => {
59
  const { r, g, b } = $.color.parse(color);
60
  if (!stacked) {
61
    return `rgba(${r}, ${g}, ${b}, ${opacity})`;
62
  }
63
  /*
64
    Unfortunately flot doesn't take into consideration
65
    the alpha value when adjusting the color on the stacked series.
66
    TODO: find better way to set the opacity.
67
  */
68
  const base = (1 - opacity) * 255;
69
  return `rgb(${Math.round(base + opacity * r)},${Math.round(base + opacity * g)},${Math.round(base + opacity * b)})`;
70
};
71

72
export const toHoverColor =
73
  (index: number, stacked: boolean) =>
74
  (
75
    series: GraphSeries,
76
    i: number
77
  ): { color: string; data: (number | null)[][]; index: number; labels: { [p: string]: string } } => ({
78
    ...series,
79
    color: getHoverColor(series.color, i !== index ? 0.3 : 1, stacked),
80
  });
81

82
export const getOptions = (stacked: boolean, useLocalTime: boolean): jquery.flot.plotOptions => {
83
  return {
84
    grid: {
85
      hoverable: true,
86
      clickable: true,
87
      autoHighlight: true,
88
      mouseActiveRadius: 100,
89
    },
90
    legend: {
91
      show: false,
92
    },
93
    xaxis: {
94
      mode: 'time',
95
      showTicks: true,
96
      showMinorTicks: true,
97
      timeBase: 'milliseconds',
98
      timezone: useLocalTime ? 'browser' : undefined,
99
    },
100
    yaxis: {
101
      tickFormatter: formatValue,
102
    },
103
    crosshair: {
104
      mode: 'xy',
105
      color: '#bbb',
106
    },
107
    tooltip: {
108
      show: true,
109
      cssClass: 'graph-tooltip',
110
      content: (_, xval, yval, { series }): string => {
111
        const both = series as GraphExemplar | GraphSeries;
112
        const { labels, color } = both;
113
        let dateTime = moment(xval);
114
        if (!useLocalTime) {
115
          dateTime = dateTime.utc();
116
        }
117

118
        const formatLabels = (labels: { [key: string]: string }): string => `
119
            <div class="labels">
120
              ${Object.keys(labels).length === 0 ? '<div class="mb-1 font-italic">no labels</div>' : ''}
121
              ${labels['__name__'] ? `<div class="mb-1"><strong>${labels['__name__']}</strong></div>` : ''}
122
              ${Object.keys(labels)
123
                .filter((k) => k !== '__name__')
124
                .map((k) => `<div class="mb-1"><strong>${k}</strong>: ${escapeHTML(labels[k])}</div>`)
125
                .join('')}
126
            </div>`;
127

128
        return `
129
            <div class="date">${dateTime.format('YYYY-MM-DD HH:mm:ss Z')}</div>
130
            <div>
131
              <span class="detail-swatch" style="background-color: ${color}"></span>
132
              <span>${labels.__name__ || 'value'}: <strong>${yval}</strong></span>
133
            </div>
134
            <div class="mt-2 mb-1 font-weight-bold">${'seriesLabels' in both ? 'Trace exemplar:' : 'Series:'}</div>
135
            ${formatLabels(labels)}
136
            ${
137
              'seriesLabels' in both
138
                ? `
139
            <div class="mt-2 mb-1 font-weight-bold">Associated series:</div>${formatLabels(both.seriesLabels)}
140
`
141
                : ''
142
            }
143
          `.trimEnd();
144
      },
145
      defaultTheme: false,
146
      lines: true,
147
    },
148
    series: {
149
      stack: false, // Stacking is set on a per-series basis because exemplar symbols don't support it.
150
      heatmap: false,
151
      lines: {
152
        lineWidth: stacked ? 1 : 2,
153
        steps: false,
154
        fill: stacked,
155
      },
156
      shadowSize: 0,
157
    },
158
    selection: {
159
      mode: 'x',
160
    },
161
  };
162
};
163

164
export const normalizeData = ({ queryParams, data, exemplars, displayMode }: GraphProps): GraphData => {
165
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
166
  const { startTime, endTime, resolution } = queryParams!;
167

168
  let sum = 0;
169
  const values: number[] = [];
170
  // Exemplars are grouped into buckets by time to use for de-densifying.
171
  const buckets: { [time: number]: GraphExemplar[] } = {};
172
  for (const exemplar of exemplars || []) {
173
    for (const { labels, value, timestamp } of exemplar.exemplars) {
174
      const parsed = parseValue(value) || 0;
175
      sum += parsed;
176
      values.push(parsed);
177

178
      const bucketTime = Math.floor((timestamp / ((endTime - startTime) / 60)) * 0.8) * 1000;
179
      if (!buckets[bucketTime]) {
180
        buckets[bucketTime] = [];
181
      }
182

183
      buckets[bucketTime].push({
184
        seriesLabels: exemplar.seriesLabels,
185
        labels: labels,
186
        data: [[timestamp * 1000, parsed]],
187
        points: { symbol: exemplarSymbol },
188
        color: '#0275d8',
189
      });
190
    }
191
  }
192
  const deviation = stdDeviation(sum, values);
193

194
  const series = data.result.map(({ values, histograms, metric }, index) => {
195
    // Insert nulls for all missing steps.
196
    const data = [];
197
    let valuePos = 0;
198
    let histogramPos = 0;
199

200
    for (let t = startTime; t <= endTime; t += resolution) {
201
      // Allow for floating point inaccuracy.
202
      const currentValue = values && values[valuePos];
203
      const currentHistogram = histograms && histograms[histogramPos];
204
      if (currentValue && values.length > valuePos && currentValue[0] < t + resolution / 100) {
205
        data.push([currentValue[0] * 1000, parseValue(currentValue[1])]);
206
        valuePos++;
207
      } else if (currentHistogram && histograms.length > histogramPos && currentHistogram[0] < t + resolution / 100) {
208
        data.push([currentHistogram[0] * 1000, parseValue(currentHistogram[1].sum)]);
209
        histogramPos++;
210
      } else {
211
        data.push([t * 1000, null]);
212
      }
213
    }
214
    return {
215
      labels: metric !== null ? metric : {},
216
      color: colorPool[index % colorPool.length],
217
      stack: displayMode === GraphDisplayMode.Stacked,
218
      data,
219
      index,
220
    };
221
  });
222

223
  return {
224
    series: displayMode === GraphDisplayMode.Heatmap ? prepareHeatmapData(series) : series,
225
    exemplars: Object.values(buckets).flatMap((bucket) => {
226
      if (bucket.length === 1) {
227
        return bucket[0];
228
      }
229
      return bucket
230
        .sort((a, b) => exValue(b) - exValue(a)) // Sort exemplars by value in descending order.
231
        .reduce((exemplars: GraphExemplar[], exemplar) => {
232
          if (exemplars.length === 0) {
233
            exemplars.push(exemplar);
234
          } else {
235
            const prev = exemplars[exemplars.length - 1];
236
            // Don't plot this exemplar if it's less than two times the standard
237
            // deviation spaced from the last.
238
            if (exValue(prev) - exValue(exemplar) >= 2 * deviation) {
239
              exemplars.push(exemplar);
240
            }
241
          }
242
          return exemplars;
243
        }, []);
244
    }),
245
  };
246
};
247

248
export const parseValue = (value: string): null | number => {
249
  const val = parseFloat(value);
250
  // "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). They
251
  // can't be graphed, so show them as gaps (null).
252
  return isNaN(val) ? null : val;
253
};
254

255
const exemplarSymbol = (ctx: CanvasRenderingContext2D, x: number, y: number) => {
256
  // Center the symbol on the point.
257
  y = y - 3.5;
258

259
  // Correct if the symbol is overflowing off the grid.
260
  if (x > ctx.canvas.clientWidth - 59) {
261
    x = ctx.canvas.clientWidth - 59;
262
  }
263
  if (y > ctx.canvas.clientHeight - 40) {
264
    y = ctx.canvas.clientHeight - 40;
265
  }
266

267
  ctx.translate(x, y);
268
  ctx.rotate(Math.PI / 4);
269
  ctx.translate(-x, -y);
270

271
  ctx.fillStyle = '#92bce1';
272
  ctx.fillRect(x, y, 7, 7);
273

274
  ctx.strokeStyle = '#0275d8';
275
  ctx.lineWidth = 1;
276
  ctx.strokeRect(x, y, 7, 7);
277
};
278

279
const stdDeviation = (sum: number, values: number[]): number => {
280
  const avg = sum / values.length;
281
  let squaredAvg = 0;
282
  values.map((value) => (squaredAvg += (value - avg) ** 2));
283
  squaredAvg = squaredAvg / values.length;
284
  return Math.sqrt(squaredAvg);
285
};
286

287
const exValue = (exemplar: GraphExemplar): number => exemplar.data[0][1];
288

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.