123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- import {InjectedRouter} from 'react-router';
- import moment from 'moment';
- import * as qs from 'query-string';
- import {
- DateTimeObject,
- getDiffInMinutes,
- getInterval,
- } from 'sentry/components/charts/utils';
- import {t} from 'sentry/locale';
- import {
- MetricMeta,
- MetricsApiRequestMetric,
- MetricsApiRequestQuery,
- MetricsGroup,
- MetricType,
- MRI,
- UseCase,
- } from 'sentry/types/metrics';
- import {defined, formatBytesBase2, formatBytesBase10} from 'sentry/utils';
- import {formatPercentage, getDuration} from 'sentry/utils/formatters';
- import {formatMRI, getUseCaseFromMRI, parseField} from 'sentry/utils/metrics/mri';
- import {DateString, PageFilters} from '../../types/core';
- export enum MetricDisplayType {
- LINE = 'line',
- AREA = 'area',
- BAR = 'bar',
- TABLE = 'table',
- }
- export const defaultMetricDisplayType = MetricDisplayType.LINE;
- export type MetricTag = {
- key: string;
- };
- export type SortState = {
- name: 'name' | 'avg' | 'min' | 'max' | 'sum' | undefined;
- order: 'asc' | 'desc';
- };
- export interface MetricWidgetQueryParams
- extends Pick<MetricsQuery, 'mri' | 'op' | 'query' | 'groupBy'> {
- displayType: MetricDisplayType;
- focusedSeries?: string;
- position?: number;
- powerUserMode?: boolean;
- showSummaryTable?: boolean;
- sort?: SortState;
- }
- export interface DdmQueryParams {
- widgets: string; // stringified json representation of MetricWidgetQueryParams
- end?: DateString;
- environment?: string[];
- project?: number[];
- start?: DateString;
- statsPeriod?: string | null;
- utc?: boolean | null;
- }
- export type MetricsQuery = {
- datetime: PageFilters['datetime'];
- environments: PageFilters['environments'];
- mri: MRI;
- projects: PageFilters['projects'];
- groupBy?: string[];
- op?: string;
- query?: string;
- };
- export type MetricMetaCodeLocation = {
- frames: {
- absPath?: string;
- filename?: string;
- function?: string;
- lineNo?: number;
- module?: string;
- }[];
- mri: string;
- timestamp: number;
- };
- export function getDdmUrl(
- orgSlug: string,
- {
- widgets,
- start,
- end,
- statsPeriod,
- project,
- ...otherParams
- }: Omit<DdmQueryParams, 'project' | 'widgets'> & {
- widgets: MetricWidgetQueryParams[];
- project?: (string | number)[];
- }
- ) {
- const urlParams: Partial<DdmQueryParams> = {
- ...otherParams,
- project: project?.map(id => (typeof id === 'string' ? parseInt(id, 10) : id)),
- widgets: JSON.stringify(widgets),
- };
- if (statsPeriod) {
- urlParams.statsPeriod = statsPeriod;
- } else {
- urlParams.start = start;
- urlParams.end = end;
- }
- return `/organizations/${orgSlug}/ddm/?${qs.stringify(urlParams)}`;
- }
- export function getMetricsApiRequestQuery(
- {field, query, groupBy}: MetricsApiRequestMetric,
- {projects, environments, datetime}: PageFilters,
- overrides: Partial<MetricsApiRequestQuery>
- ): MetricsApiRequestQuery {
- const {mri: mri} = parseField(field) ?? {};
- const useCase = getUseCaseFromMRI(mri) ?? 'custom';
- const interval = getMetricsInterval(datetime, useCase);
- const queryToSend = {
- ...getDateTimeParams(datetime),
- query,
- project: projects,
- environment: environments,
- field,
- useCase,
- interval,
- groupBy,
- allowPrivate: true, // TODO(ddm): reconsider before widening audience
- // max result groups
- per_page: 10,
- };
- return {...queryToSend, ...overrides};
- }
- // Wraps getInterval since other users of this function, and other metric use cases do not have support for 10s granularity
- export function getMetricsInterval(dateTimeObj: DateTimeObject, useCase: UseCase) {
- const interval = getInterval(dateTimeObj, 'metrics');
- if (interval !== '1m') {
- return interval;
- }
- const diffInMinutes = getDiffInMinutes(dateTimeObj);
- if (diffInMinutes <= 60 && useCase === 'custom') {
- return '10s';
- }
- return interval;
- }
- export function getDateTimeParams({start, end, period}: PageFilters['datetime']) {
- return period
- ? {statsPeriod: period}
- : {start: moment(start).toISOString(), end: moment(end).toISOString()};
- }
- const metricTypeToReadable: Record<MetricType, string> = {
- c: t('counter'),
- g: t('gauge'),
- d: t('distribution'),
- s: t('set'),
- e: t('derived'),
- };
- // Converts from "c" to "counter"
- export function getReadableMetricType(type?: string) {
- return metricTypeToReadable[type as MetricType] ?? t('unknown');
- }
- export function formatMetricUsingUnit(value: number | null, unit: string) {
- if (!defined(value)) {
- return '\u2014';
- }
- switch (unit) {
- case 'nanosecond':
- return getDuration(value / 1000000000, 2, true);
- case 'microsecond':
- return getDuration(value / 1000000, 2, true);
- case 'millisecond':
- return getDuration(value / 1000, 2, true);
- case 'second':
- return getDuration(value, 2, true);
- case 'minute':
- return getDuration(value * 60, 2, true);
- case 'hour':
- return getDuration(value * 60 * 60, 2, true);
- case 'day':
- return getDuration(value * 60 * 60 * 24, 2, true);
- case 'week':
- return getDuration(value * 60 * 60 * 24 * 7, 2, true);
- case 'ratio':
- return formatPercentage(value, 2);
- case 'percent':
- return formatPercentage(value / 100, 2);
- case 'bit':
- return formatBytesBase2(value / 8);
- case 'byte':
- return formatBytesBase10(value);
- case 'kibibyte':
- return formatBytesBase2(value * 1024);
- case 'kilobyte':
- return formatBytesBase10(value, 1);
- case 'mebibyte':
- return formatBytesBase2(value * 1024 ** 2);
- case 'megabyte':
- return formatBytesBase10(value, 2);
- case 'gibibyte':
- return formatBytesBase2(value * 1024 ** 3);
- case 'gigabyte':
- return formatBytesBase10(value, 3);
- case 'tebibyte':
- return formatBytesBase2(value * 1024 ** 4);
- case 'terabyte':
- return formatBytesBase10(value, 4);
- case 'pebibyte':
- return formatBytesBase2(value * 1024 ** 5);
- case 'petabyte':
- return formatBytesBase10(value, 5);
- case 'exbibyte':
- return formatBytesBase2(value * 1024 ** 6);
- case 'exabyte':
- return formatBytesBase10(value, 6);
- case 'none':
- default:
- return value.toLocaleString();
- }
- }
- export function formatMetricsUsingUnitAndOp(
- value: number | null,
- unit: string,
- operation?: string
- ) {
- if (operation === 'count') {
- // if the operation is count, we want to ignore the unit and always format the value as a number
- return value?.toLocaleString() ?? '';
- }
- return formatMetricUsingUnit(value, unit);
- }
- export function isAllowedOp(op: string) {
- return !['max_timestamp', 'min_timestamp', 'histogram'].includes(op);
- }
- export function updateQuery(router: InjectedRouter, partialQuery: Record<string, any>) {
- router.push({
- ...router.location,
- query: {
- ...router.location.query,
- ...partialQuery,
- },
- });
- }
- export function clearQuery(router: InjectedRouter) {
- router.push({
- ...router.location,
- query: {},
- });
- }
- // TODO(ddm): there has to be a nicer way to do this
- export function getSeriesName(
- group: MetricsGroup,
- isOnlyGroup = false,
- groupBy: MetricsQuery['groupBy']
- ) {
- if (isOnlyGroup && !groupBy?.length) {
- const field = Object.keys(group.series)?.[0];
- const {mri} = parseField(field) ?? {mri: field};
- const name = formatMRI(mri as MRI);
- return name ?? '(none)';
- }
- return Object.entries(group.by)
- .map(([key, value]) => `${key}:${String(value).length ? value : t('none')}`)
- .join(', ');
- }
- export function groupByOp(metrics: MetricMeta[]): Record<string, MetricMeta[]> {
- const uniqueOperations = [
- ...new Set(metrics.flatMap(field => field.operations).filter(isAllowedOp)),
- ].sort();
- const groupedByOp = uniqueOperations.reduce((result, op) => {
- result[op] = metrics.filter(field => field.operations.includes(op));
- return result;
- }, {});
- return groupedByOp;
- }
|