prometheus

Форк
0
312 строк · 9.0 Кб
1
import moment from 'moment-timezone';
2

3
import { GraphDisplayMode, PanelDefaultOptions, PanelOptions, PanelType } from '../pages/graph/Panel';
4
import { PanelMeta } from '../pages/graph/PanelList';
5

6
export const generateID = (): string => {
7
  return `_${Math.random().toString(36).substr(2, 9)}`;
8
};
9

10
export const byEmptyString = (p: string): boolean => p.length > 0;
11

12
export const isPresent = <T>(obj: T): obj is NonNullable<T> => obj !== null && obj !== undefined;
13

14
export const escapeHTML = (str: string): string => {
15
  const entityMap: { [key: string]: string } = {
16
    '&': '&amp;',
17
    '<': '&lt;',
18
    '>': '&gt;',
19
    '"': '&quot;',
20
    "'": '&#39;',
21
    '/': '&#x2F;',
22
  };
23

24
  return String(str).replace(/[&<>"'/]/g, function (s) {
25
    return entityMap[s];
26
  });
27
};
28

29
export const metricToSeriesName = (labels: { [key: string]: string }): string => {
30
  if (labels === null) {
31
    return 'scalar';
32
  }
33
  let tsName = (labels.__name__ || '') + '{';
34
  const labelStrings: string[] = [];
35
  for (const label in labels) {
36
    if (label !== '__name__') {
37
      labelStrings.push(label + '="' + labels[label] + '"');
38
    }
39
  }
40
  tsName += labelStrings.join(', ') + '}';
41
  return tsName;
42
};
43

44
export const parseDuration = (durationStr: string): number | null => {
45
  if (durationStr === '') {
46
    return null;
47
  }
48
  if (durationStr === '0') {
49
    // Allow 0 without a unit.
50
    return 0;
51
  }
52

53
  const durationRE = new RegExp('^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$');
54
  const matches = durationStr.match(durationRE);
55
  if (!matches) {
56
    return null;
57
  }
58

59
  let dur = 0;
60

61
  // Parse the match at pos `pos` in the regex and use `mult` to turn that
62
  // into ms, then add that value to the total parsed duration.
63
  const m = (pos: number, mult: number) => {
64
    if (matches[pos] === undefined) {
65
      return;
66
    }
67
    const n = parseInt(matches[pos]);
68
    dur += n * mult;
69
  };
70

71
  m(2, 1000 * 60 * 60 * 24 * 365); // y
72
  m(4, 1000 * 60 * 60 * 24 * 7); // w
73
  m(6, 1000 * 60 * 60 * 24); // d
74
  m(8, 1000 * 60 * 60); // h
75
  m(10, 1000 * 60); // m
76
  m(12, 1000); // s
77
  m(14, 1); // ms
78

79
  return dur;
80
};
81

82
export const formatDuration = (d: number): string => {
83
  let ms = d;
84
  let r = '';
85
  if (ms === 0) {
86
    return '0s';
87
  }
88

89
  const f = (unit: string, mult: number, exact: boolean) => {
90
    if (exact && ms % mult !== 0) {
91
      return;
92
    }
93
    const v = Math.floor(ms / mult);
94
    if (v > 0) {
95
      r += `${v}${unit}`;
96
      ms -= v * mult;
97
    }
98
  };
99

100
  // Only format years and weeks if the remainder is zero, as it is often
101
  // easier to read 90d than 12w6d.
102
  f('y', 1000 * 60 * 60 * 24 * 365, true);
103
  f('w', 1000 * 60 * 60 * 24 * 7, true);
104

105
  f('d', 1000 * 60 * 60 * 24, false);
106
  f('h', 1000 * 60 * 60, false);
107
  f('m', 1000 * 60, false);
108
  f('s', 1000, false);
109
  f('ms', 1, false);
110

111
  return r;
112
};
113

114
export function parseTime(timeText: string): number {
115
  return moment.utc(timeText).valueOf();
116
}
117

118
export function formatTime(time: number): string {
119
  return moment.utc(time).format('YYYY-MM-DD HH:mm:ss');
120
}
121

122
export const now = (): number => moment().valueOf();
123

124
export const humanizeDuration = (milliseconds: number): string => {
125
  const sign = milliseconds < 0 ? '-' : '';
126
  const unsignedMillis = milliseconds < 0 ? -1 * milliseconds : milliseconds;
127
  const duration = moment.duration(unsignedMillis, 'ms');
128
  const ms = Math.floor(duration.milliseconds());
129
  const s = Math.floor(duration.seconds());
130
  const m = Math.floor(duration.minutes());
131
  const h = Math.floor(duration.hours());
132
  const d = Math.floor(duration.asDays());
133
  if (d !== 0) {
134
    return `${sign}${d}d ${h}h ${m}m ${s}s`;
135
  }
136
  if (h !== 0) {
137
    return `${sign}${h}h ${m}m ${s}s`;
138
  }
139
  if (m !== 0) {
140
    return `${sign}${m}m ${s}s`;
141
  }
142
  if (s !== 0) {
143
    return `${sign}${s}.${ms}s`;
144
  }
145
  if (unsignedMillis > 0) {
146
    return `${sign}${unsignedMillis.toFixed(3)}ms`;
147
  }
148
  return '0s';
149
};
150

151
export const formatRelative = (startStr: string, end: number): string => {
152
  const start = parseTime(startStr);
153
  if (start < 0) {
154
    return 'Never';
155
  }
156
  return humanizeDuration(end - start) + ' ago';
157
};
158

159
const paramFormat = /^g\d+\..+=.+$/;
160

161
export const decodePanelOptionsFromQueryString = (query: string): PanelMeta[] => {
162
  if (query === '') {
163
    return [];
164
  }
165
  const urlParams = query.substring(1).split('&');
166

167
  return urlParams.reduce<PanelMeta[]>((panels, urlParam, i) => {
168
    const panelsCount = panels.length;
169
    const prefix = `g${panelsCount}.`;
170
    if (urlParam.startsWith(`${prefix}expr=`)) {
171
      const prefixLen = prefix.length;
172
      return [
173
        ...panels,
174
        {
175
          id: generateID(),
176
          key: `${panelsCount}`,
177
          options: urlParams.slice(i).reduce((opts, param) => {
178
            return param.startsWith(prefix) && paramFormat.test(param)
179
              ? { ...opts, ...parseOption(param.substring(prefixLen)) }
180
              : opts;
181
          }, PanelDefaultOptions),
182
        },
183
      ];
184
    }
185
    return panels;
186
  }, []);
187
};
188

189
export const parseOption = (param: string): Partial<PanelOptions> => {
190
  const [opt, val] = param.split('=');
191
  const decodedValue = decodeURIComponent(val.replace(/\+/g, ' '));
192
  switch (opt) {
193
    case 'expr':
194
      return { expr: decodedValue };
195

196
    case 'tab':
197
      return { type: decodedValue === '0' ? PanelType.Graph : PanelType.Table };
198

199
    case 'display_mode':
200
      const validKey = Object.values(GraphDisplayMode).includes(decodedValue as GraphDisplayMode);
201
      return { displayMode: validKey ? (decodedValue as GraphDisplayMode) : GraphDisplayMode.Lines };
202

203
    case 'stacked':
204
      return { displayMode: decodedValue === '1' ? GraphDisplayMode.Stacked : GraphDisplayMode.Lines };
205

206
    case 'show_exemplars':
207
      return { showExemplars: decodedValue === '1' };
208

209
    case 'range_input':
210
      const range = parseDuration(decodedValue);
211
      return isPresent(range) ? { range } : {};
212

213
    case 'end_input':
214
    case 'moment_input':
215
      return { endTime: parseTime(decodedValue) };
216

217
    case 'step_input':
218
      const resolution = parseInt(decodedValue);
219
      return resolution > 0 ? { resolution } : {};
220
  }
221
  return {};
222
};
223

224
export const formatParam =
225
  (key: string) =>
226
  (paramName: string, value: number | string | boolean): string => {
227
    return `g${key}.${paramName}=${encodeURIComponent(value)}`;
228
  };
229

230
export const toQueryString = ({ key, options }: PanelMeta): string => {
231
  const formatWithKey = formatParam(key);
232
  const { expr, type, displayMode, range, endTime, resolution, showExemplars } = options;
233
  const time = isPresent(endTime) ? formatTime(endTime) : false;
234
  const urlParams = [
235
    formatWithKey('expr', expr),
236
    formatWithKey('tab', type === PanelType.Graph ? 0 : 1),
237
    formatWithKey('display_mode', displayMode),
238
    formatWithKey('show_exemplars', showExemplars ? 1 : 0),
239
    formatWithKey('range_input', formatDuration(range)),
240
    time ? `${formatWithKey('end_input', time)}&${formatWithKey('moment_input', time)}` : '',
241
    isPresent(resolution) ? formatWithKey('step_input', resolution) : '',
242
  ];
243
  return urlParams.filter(byEmptyString).join('&');
244
};
245

246
export const encodePanelOptionsToQueryString = (panels: PanelMeta[]): string => {
247
  return `?${panels.map(toQueryString).join('&')}`;
248
};
249

250
export const setQuerySearchFilter = (search: string) => {
251
  setQueryParam('search', search);
252
};
253

254
export const getQuerySearchFilter = (): string => {
255
  return getQueryParam('search');
256
};
257

258
export const setQueryParam = (key: string, value: string) => {
259
  const params = new URLSearchParams(window.location.search);
260
  params.set(key, value);
261
  window.history.pushState({}, '', '?' + params.toString());
262
};
263

264
export const getQueryParam = (key: string): string => {
265
  const locationSearch = window.location.search;
266
  const params = new URLSearchParams(locationSearch);
267
  return params.get(key) || '';
268
};
269

270
export const createExpressionLink = (expr: string): string => {
271
  return `../graph?g0.expr=${encodeURIComponent(expr)}&g0.tab=1&g0.display_mode=${
272
    GraphDisplayMode.Lines
273
  }&g0.show_exemplars=0.g0.range_input=1h.`;
274
};
275

276
// eslint-disable-next-line @typescript-eslint/no-explicit-any,
277
export const mapObjEntries = <T extends { [s: string]: any }, key extends keyof T, Z>(
278
  o: T,
279
  cb: ([k, v]: [string, T[key]], i: number, arr: [string, T[key]][]) => Z
280
): Z[] => Object.entries(o).map(cb);
281

282
export const callAll =
283
  (
284
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
    ...fns: Array<(...args: any) => void>
286
  ) =>
287
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
288
  (...args: any): void => {
289
    // eslint-disable-next-line prefer-spread
290
    fns.filter(Boolean).forEach((fn) => fn.apply(null, args));
291
  };
292

293
export const parsePrometheusFloat = (value: string): string | number => {
294
  if (isNaN(Number(value))) {
295
    return value;
296
  } else {
297
    return Number(value);
298
  }
299
};
300

301
export function debounce<Params extends unknown[]>(
302
  func: (...args: Params) => unknown,
303
  timeout: number
304
): (...args: Params) => void {
305
  let timer: NodeJS.Timeout;
306
  return (...args: Params) => {
307
    clearTimeout(timer);
308
    timer = setTimeout(() => {
309
      func(...args);
310
    }, timeout);
311
  };
312
}
313

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

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

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

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