|
@@ -16,30 +16,23 @@ import {
|
|
|
TWENTY_FOUR_HOURS,
|
|
|
TWO_WEEKS,
|
|
|
} from 'sentry/components/charts/utils';
|
|
|
-import {t} from 'sentry/locale';
|
|
|
-import {MetricsApiResponse} from 'sentry/types';
|
|
|
import {
|
|
|
+ normalizeDateTimeParams,
|
|
|
+ parseStatsPeriod,
|
|
|
+} from 'sentry/components/organizations/pageFilters/parse';
|
|
|
+import {t} from 'sentry/locale';
|
|
|
+import {MetricsApiResponse, PageFilters} from 'sentry/types';
|
|
|
+import type {
|
|
|
MetricMeta,
|
|
|
MetricsApiRequestMetric,
|
|
|
MetricsApiRequestQuery,
|
|
|
MetricsApiRequestQueryOptions,
|
|
|
MetricsGroup,
|
|
|
MetricsOperation,
|
|
|
- MetricType,
|
|
|
MRI,
|
|
|
UseCase,
|
|
|
} from 'sentry/types/metrics';
|
|
|
-import {defined, formatBytesBase2, formatBytesBase10} from 'sentry/utils';
|
|
|
import {isMeasurement as isMeasurementName} from 'sentry/utils/discover/fields';
|
|
|
-import {
|
|
|
- DAY,
|
|
|
- formatNumberWithDynamicDecimalPoints,
|
|
|
- HOUR,
|
|
|
- MINUTE,
|
|
|
- MONTH,
|
|
|
- SECOND,
|
|
|
- WEEK,
|
|
|
-} from 'sentry/utils/formatters';
|
|
|
import {getMeasurements} from 'sentry/utils/measurements/measurements';
|
|
|
import {
|
|
|
formatMRI,
|
|
@@ -49,38 +42,14 @@ import {
|
|
|
parseField,
|
|
|
parseMRI,
|
|
|
} from 'sentry/utils/metrics/mri';
|
|
|
+import type {
|
|
|
+ DdmQueryParams,
|
|
|
+ MetricsQuery,
|
|
|
+ MetricsQuerySubject,
|
|
|
+ MetricWidgetQueryParams,
|
|
|
+} from 'sentry/utils/metrics/types';
|
|
|
+import {MetricDisplayType} from 'sentry/utils/metrics/types';
|
|
|
import useRouter from 'sentry/utils/useRouter';
|
|
|
-import {DEFAULT_SORT_STATE} from 'sentry/views/ddm/constants';
|
|
|
-
|
|
|
-import {
|
|
|
- normalizeDateTimeParams,
|
|
|
- parseStatsPeriod,
|
|
|
-} from '../../components/organizations/pageFilters/parse';
|
|
|
-import {DateString, PageFilters} from '../../types/core';
|
|
|
-
|
|
|
-export const METRICS_DOCS_URL =
|
|
|
- 'https://develop.sentry.dev/delightful-developer-metrics/';
|
|
|
-
|
|
|
-export enum MetricDisplayType {
|
|
|
- LINE = 'line',
|
|
|
- AREA = 'area',
|
|
|
- BAR = 'bar',
|
|
|
-}
|
|
|
-
|
|
|
-export const metricDisplayTypeOptions = [
|
|
|
- {
|
|
|
- value: MetricDisplayType.LINE,
|
|
|
- label: t('Line'),
|
|
|
- },
|
|
|
- {
|
|
|
- value: MetricDisplayType.AREA,
|
|
|
- label: t('Area'),
|
|
|
- },
|
|
|
- {
|
|
|
- value: MetricDisplayType.BAR,
|
|
|
- label: t('Bar'),
|
|
|
- },
|
|
|
-];
|
|
|
|
|
|
export function getDefaultMetricDisplayType(
|
|
|
mri: MetricsQuery['mri'],
|
|
@@ -104,117 +73,6 @@ export const getMetricDisplayType = (displayType: unknown): MetricDisplayType =>
|
|
|
return MetricDisplayType.LINE;
|
|
|
};
|
|
|
|
|
|
-export type MetricTag = {
|
|
|
- key: string;
|
|
|
-};
|
|
|
-
|
|
|
-export type SortState = {
|
|
|
- name: 'name' | 'avg' | 'min' | 'max' | 'sum' | undefined;
|
|
|
- order: 'asc' | 'desc';
|
|
|
-};
|
|
|
-
|
|
|
-export interface MetricWidgetQueryParams extends MetricsQuerySubject {
|
|
|
- displayType: MetricDisplayType;
|
|
|
- focusedSeries?: {
|
|
|
- seriesName: string;
|
|
|
- groupBy?: Record<string, string>;
|
|
|
- };
|
|
|
- highlightedSample?: string | null;
|
|
|
- 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;
|
|
|
- title?: string;
|
|
|
-};
|
|
|
-
|
|
|
-export type MetricsQuerySubject = Pick<
|
|
|
- MetricsQuery,
|
|
|
- 'mri' | 'op' | 'query' | 'groupBy' | 'title'
|
|
|
->;
|
|
|
-
|
|
|
-export type MetricCodeLocationFrame = {
|
|
|
- absPath?: string;
|
|
|
- contextLine?: string;
|
|
|
- filename?: string;
|
|
|
- function?: string;
|
|
|
- lineNo?: number;
|
|
|
- module?: string;
|
|
|
- platform?: string;
|
|
|
- postContext?: string[];
|
|
|
- preContext?: string[];
|
|
|
-};
|
|
|
-
|
|
|
-export type MetricMetaCodeLocation = {
|
|
|
- mri: string;
|
|
|
- timestamp: number;
|
|
|
- codeLocations?: MetricCodeLocationFrame[];
|
|
|
- frames?: MetricCodeLocationFrame[];
|
|
|
- metricSpans?: MetricCorrelation[];
|
|
|
-};
|
|
|
-
|
|
|
-export type MetricCorrelation = {
|
|
|
- duration: number;
|
|
|
- metricSummaries: {
|
|
|
- spanId: string;
|
|
|
- count?: number;
|
|
|
- max?: number;
|
|
|
- min?: number;
|
|
|
- sum?: number;
|
|
|
- }[];
|
|
|
- profileId: string;
|
|
|
- projectId: number;
|
|
|
- segmentName: string;
|
|
|
- spansDetails: {
|
|
|
- spanDuration: number;
|
|
|
- spanId: string;
|
|
|
- spanTimestamp: string;
|
|
|
- }[];
|
|
|
- spansNumber: number;
|
|
|
- timestamp: string;
|
|
|
- traceId: string;
|
|
|
- transactionId: string;
|
|
|
- spansSummary?: {
|
|
|
- spanDuration: number;
|
|
|
- spanOp: string;
|
|
|
- }[];
|
|
|
-};
|
|
|
-
|
|
|
-export type MetricRange = {
|
|
|
- end?: DateString;
|
|
|
- max?: number;
|
|
|
- min?: number;
|
|
|
- start?: DateString;
|
|
|
-};
|
|
|
-
|
|
|
-export const emptyWidget: MetricWidgetQueryParams = {
|
|
|
- mri: 'd:transactions/duration@millisecond' satisfies MRI,
|
|
|
- op: 'avg',
|
|
|
- query: '',
|
|
|
- groupBy: [],
|
|
|
- sort: DEFAULT_SORT_STATE,
|
|
|
- displayType: MetricDisplayType.LINE,
|
|
|
- title: undefined,
|
|
|
-};
|
|
|
-
|
|
|
export function getDdmUrl(
|
|
|
orgSlug: string,
|
|
|
{
|
|
@@ -318,14 +176,6 @@ export function getDateTimeParams({start, end, period}: PageFilters['datetime'])
|
|
|
: {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'),
|
|
|
-};
|
|
|
-
|
|
|
export function getDefaultMetricOp(mri: MRI): MetricsOperation {
|
|
|
const parsedMRI = parseMRI(mri);
|
|
|
switch (parsedMRI?.type) {
|
|
@@ -340,206 +190,6 @@ export function getDefaultMetricOp(mri: MRI): MetricsOperation {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Converts from "c" to "counter"
|
|
|
-export function getReadableMetricType(type?: string) {
|
|
|
- return metricTypeToReadable[type as MetricType] ?? t('unknown');
|
|
|
-}
|
|
|
-
|
|
|
-const MILLISECOND = 1;
|
|
|
-const MICROSECOND = MILLISECOND / 1000;
|
|
|
-
|
|
|
-export function formatDuration(seconds: number): string {
|
|
|
- if (!seconds) {
|
|
|
- return '0ms';
|
|
|
- }
|
|
|
- const absValue = Math.abs(seconds * 1000);
|
|
|
- // value in milliseconds
|
|
|
- const msValue = seconds * 1000;
|
|
|
-
|
|
|
- let unit: FormattingSupportedMetricUnit | 'month' = 'nanosecond';
|
|
|
- let value = msValue * 1000000;
|
|
|
-
|
|
|
- if (absValue >= MONTH) {
|
|
|
- unit = 'month';
|
|
|
- value = msValue / MONTH;
|
|
|
- } else if (absValue >= WEEK) {
|
|
|
- unit = 'week';
|
|
|
- value = msValue / WEEK;
|
|
|
- } else if (absValue >= DAY) {
|
|
|
- unit = 'day';
|
|
|
- value = msValue / DAY;
|
|
|
- } else if (absValue >= HOUR) {
|
|
|
- unit = 'hour';
|
|
|
- value = msValue / HOUR;
|
|
|
- } else if (absValue >= MINUTE) {
|
|
|
- unit = 'minute';
|
|
|
- value = msValue / MINUTE;
|
|
|
- } else if (absValue >= SECOND) {
|
|
|
- unit = 'second';
|
|
|
- value = msValue / SECOND;
|
|
|
- } else if (absValue >= MILLISECOND) {
|
|
|
- unit = 'millisecond';
|
|
|
- value = msValue;
|
|
|
- } else if (absValue >= MICROSECOND) {
|
|
|
- unit = 'microsecond';
|
|
|
- value = msValue * 1000;
|
|
|
- }
|
|
|
-
|
|
|
- return `${formatNumberWithDynamicDecimalPoints(value)}${
|
|
|
- unit === 'month' ? 'mo' : METRIC_UNIT_TO_SHORT[unit]
|
|
|
- }`;
|
|
|
-}
|
|
|
-
|
|
|
-// The metric units that we have support for in the UI
|
|
|
-// others will still be displayed, but will not have any effect on formatting
|
|
|
-export const formattingSupportedMetricUnits = [
|
|
|
- 'none',
|
|
|
- 'nanosecond',
|
|
|
- 'microsecond',
|
|
|
- 'millisecond',
|
|
|
- 'second',
|
|
|
- 'minute',
|
|
|
- 'hour',
|
|
|
- 'day',
|
|
|
- 'week',
|
|
|
- 'ratio',
|
|
|
- 'percent',
|
|
|
- 'bit',
|
|
|
- 'byte',
|
|
|
- 'kibibyte',
|
|
|
- 'kilobyte',
|
|
|
- 'mebibyte',
|
|
|
- 'megabyte',
|
|
|
- 'gibibyte',
|
|
|
- 'gigabyte',
|
|
|
- 'tebibyte',
|
|
|
- 'terabyte',
|
|
|
- 'pebibyte',
|
|
|
- 'petabyte',
|
|
|
- 'exbibyte',
|
|
|
- 'exabyte',
|
|
|
-] as const;
|
|
|
-
|
|
|
-type FormattingSupportedMetricUnit = (typeof formattingSupportedMetricUnits)[number];
|
|
|
-
|
|
|
-const METRIC_UNIT_TO_SHORT: Record<FormattingSupportedMetricUnit, string> = {
|
|
|
- nanosecond: 'ns',
|
|
|
- microsecond: 'μs',
|
|
|
- millisecond: 'ms',
|
|
|
- second: 's',
|
|
|
- minute: 'min',
|
|
|
- hour: 'hr',
|
|
|
- day: 'day',
|
|
|
- week: 'wk',
|
|
|
- ratio: '%',
|
|
|
- percent: '%',
|
|
|
- bit: 'b',
|
|
|
- byte: 'B',
|
|
|
- kibibyte: 'KiB',
|
|
|
- kilobyte: 'KB',
|
|
|
- mebibyte: 'MiB',
|
|
|
- megabyte: 'MB',
|
|
|
- gibibyte: 'GiB',
|
|
|
- gigabyte: 'GB',
|
|
|
- tebibyte: 'TiB',
|
|
|
- terabyte: 'TB',
|
|
|
- pebibyte: 'PiB',
|
|
|
- petabyte: 'PB',
|
|
|
- exbibyte: 'EiB',
|
|
|
- exabyte: 'EB',
|
|
|
- none: '',
|
|
|
-};
|
|
|
-
|
|
|
-export function formatMetricUsingUnit(value: number | null, unit: string) {
|
|
|
- if (!defined(value)) {
|
|
|
- return '\u2014';
|
|
|
- }
|
|
|
-
|
|
|
- switch (unit as FormattingSupportedMetricUnit) {
|
|
|
- case 'nanosecond':
|
|
|
- return formatDuration(value / 1000000000);
|
|
|
- case 'microsecond':
|
|
|
- return formatDuration(value / 1000000);
|
|
|
- case 'millisecond':
|
|
|
- return formatDuration(value / 1000);
|
|
|
- case 'second':
|
|
|
- return formatDuration(value);
|
|
|
- case 'minute':
|
|
|
- return formatDuration(value * 60);
|
|
|
- case 'hour':
|
|
|
- return formatDuration(value * 60 * 60);
|
|
|
- case 'day':
|
|
|
- return formatDuration(value * 60 * 60 * 24);
|
|
|
- case 'week':
|
|
|
- return formatDuration(value * 60 * 60 * 24 * 7);
|
|
|
- case 'ratio':
|
|
|
- return `${formatNumberWithDynamicDecimalPoints(value * 100)}%`;
|
|
|
- case 'percent':
|
|
|
- return `${formatNumberWithDynamicDecimalPoints(value)}%`;
|
|
|
- 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();
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const getShortMetricUnit = (unit: string): string => METRIC_UNIT_TO_SHORT[unit] ?? '';
|
|
|
-
|
|
|
-export function formatMetricUsingFixedUnit(
|
|
|
- value: number | null,
|
|
|
- unit: string,
|
|
|
- op?: string
|
|
|
-) {
|
|
|
- if (value === null) {
|
|
|
- return '\u2014';
|
|
|
- }
|
|
|
-
|
|
|
- const formattedNumber = formatNumberWithDynamicDecimalPoints(value);
|
|
|
-
|
|
|
- return op === 'count'
|
|
|
- ? formattedNumber
|
|
|
- : `${formattedNumber}${getShortMetricUnit(unit)}`.trim();
|
|
|
-}
|
|
|
-
|
|
|
-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);
|
|
|
}
|