diff --git a/.changeset/wild-lobsters-doubt.md b/.changeset/wild-lobsters-doubt.md
new file mode 100644
index 00000000..fda48352
--- /dev/null
+++ b/.changeset/wild-lobsters-doubt.md
@@ -0,0 +1,8 @@
+---
+'@spotlightjs/spotlight': patch
+'@spotlightjs/electron': patch
+'@spotlightjs/overlay': patch
+'@spotlightjs/astro': patch
+---
+
+Unify and simplify duration calculations and representations
diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx
index 0bd6c3ff..a716a9d9 100644
--- a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx
+++ b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx
@@ -1,10 +1,10 @@
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import sentryDataCache from '../../../../../data/sentryDataCache';
-import { getDuration } from '../../../../../utils/duration';
import DateTime from '../../../../DateTime';
import SpanDetails from '../../spans/SpanDetails';
import SpanTree from '../../spans/SpanTree';
+import { getFormattedSpanDuration } from '../../../../../utils/duration';
type TraceTreeViewProps = { traceId: string };
@@ -29,10 +29,7 @@ export default function TraceTreeview({ traceId }: TraceTreeViewProps) {
—
-
- {getDuration(trace.start_timestamp, trace.timestamp).toLocaleString()} ms
- {' '}
- recorded in{' '}
+ {getFormattedSpanDuration(trace)} recorded in{' '}
{trace.spans.length.toLocaleString()} spans
diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx
index 285b2076..1982c13f 100644
--- a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx
+++ b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx
@@ -7,7 +7,7 @@ import classNames from '../../../../../lib/classNames';
import { useSpotlightContext } from '../../../../../lib/useSpotlightContext';
import { useSentryHelpers } from '../../../data/useSentryHelpers';
import { useSentryTraces } from '../../../data/useSentryTraces';
-import { getDuration } from '../../../utils/duration';
+import { getFormattedSpanDuration } from '../../../utils/duration';
import { truncateId } from '../../../utils/text';
import HiddenItemsButton from '../../HiddenItemsButton';
import { TraceRootTxnName } from './TraceDetails/components/TraceRootTxnName';
@@ -35,7 +35,6 @@ export default function TraceList() {
/>
)}
{filteredTraces.map(trace => {
- const duration = getDuration(trace.start_timestamp, trace.timestamp);
return (
—
- {duration} ms
+ {getFormattedSpanDuration(trace)}
—
{trace.spans.length.toLocaleString()} spans, {trace.transactions.length.toLocaleString()} txns
diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx
index 2a870ede..80fc6f91 100644
--- a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx
+++ b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx
@@ -7,7 +7,7 @@ import { DB_SPAN_REGEX } from '../../../../constants';
import dataCache from '../../../../data/sentryDataCache';
import type { SentryErrorEvent, Span, TraceContext } from '../../../../types';
import { formatBytes } from '../../../../utils/bytes';
-import { getDuration } from '../../../../utils/duration';
+import { getFormattedDuration } from '../../../../utils/duration';
import DateTime from '../../../DateTime';
import { ErrorTitle } from '../../../events/error/Error';
import SpanTree from './SpanTree';
@@ -87,7 +87,7 @@ export default function SpanDetails({
}) {
const [spanNodeWidth, setSpanNodeWidth] = useState(50);
- const spanDuration = getDuration(span.start_timestamp, span.timestamp);
+ const spanDuration = span.timestamp - span.start_timestamp;
const errors = dataCache.getEventsByTrace(span.trace_id).filter(e => e.type !== 'transaction' && 'exception' in e);
@@ -115,7 +115,7 @@ export default function SpanDetails({
—
- {getDuration(startTimestamp, span.start_timestamp)} ms into trace
+ {getFormattedDuration(spanDuration)} into trace
@@ -123,11 +123,11 @@ export default function SpanDetails({
- {spanDuration} ms
+ {getFormattedDuration(spanDuration)}
diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx
index f97e3488..28bb2903 100644
--- a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx
+++ b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx
@@ -3,7 +3,7 @@ import { Link, useParams } from 'react-router-dom';
import { ReactComponent as ChevronIcon } from '~/assets/chevronDown.svg';
import classNames from '../../../../../../lib/classNames';
import type { Span, TraceContext } from '../../../../types';
-import { getDuration, getSpanDurationClassName } from '../../../../utils/duration';
+import { getSpanDurationClassName, getFormattedDuration } from '../../../../utils/duration';
import PlatformIcon from '../../../PlatformIcon';
import SpanResizer from '../../../SpanResizer';
import SpanTree from './SpanTree';
@@ -37,7 +37,7 @@ const SpanItem = ({
);
const [isResizing, setIsResizing] = useState(false);
- const spanDuration = getDuration(span.start_timestamp, span.timestamp);
+ const spanDuration = span.timestamp - span.start_timestamp;
const handleResize = (e: MouseEvent) => {
if (containerRef.current) {
@@ -117,7 +117,7 @@ const SpanItem = ({
}}
>
- {spanDuration.toLocaleString()} ms
+ {getFormattedDuration(spanDuration)}
diff --git a/packages/overlay/src/integrations/sentry/utils/duration.ts b/packages/overlay/src/integrations/sentry/utils/duration.ts
index 1b400742..f0aead3a 100644
--- a/packages/overlay/src/integrations/sentry/utils/duration.ts
+++ b/packages/overlay/src/integrations/sentry/utils/duration.ts
@@ -1,27 +1,16 @@
-export const SECOND = 1000;
-export const MINUTE = 60000;
-export const HOUR = 3600000;
-export const DAY = 86400000;
-export const WEEK = 604800000;
-export const MONTH = 2629800000;
-export const YEAR = 31557600000;
-
export const DURATION_LABELS = {
- yr: 'yr',
- mo: 'mo',
- wk: 'wk',
- d: 'd',
- hr: 'hr',
- min: 'min',
- s: 's',
- ms: 'ms',
+ 31557600000: 'yr',
+ 2629800000: 'mo',
+ 604800000: 'wk',
+ 86400000: 'd',
+ 3600000: 'hr',
+ 60000: 'min',
+ 1000: 's',
};
-export function getDuration(start: string | number, end: string | number) {
- const startTs = typeof start === 'string' ? new Date(start).getTime() : start;
- const endTs = typeof end === 'string' ? new Date(end).getTime() : end;
- return Math.floor(endTs - startTs);
-}
+const DURATIONS = Object.keys(DURATION_LABELS)
+ .map(Number)
+ .sort((a, b) => b - a);
export function getSpanDurationClassName(duration: number) {
if (duration > 1000) return 'text-red-400';
@@ -30,49 +19,19 @@ export function getSpanDurationClassName(duration: number) {
}
export function getFormattedNumber(num: number, decimalPlaces: number = 2): string {
- if (num % 1 !== 0 || (num % 1 === 0 && num.toString().includes('.'))) {
- return num.toFixed(decimalPlaces);
- } else {
- return num.toFixed(0);
- }
+ return num.toFixed(decimalPlaces).replace(/\.00$/, '');
}
export function getFormattedDuration(duration: number): string {
- if (duration >= YEAR) {
- const num = getFormattedNumber(duration / YEAR);
- return `${num}${DURATION_LABELS.yr}`;
- }
-
- if (duration >= MONTH) {
- const num = getFormattedNumber(duration / MONTH);
- return `${num}${DURATION_LABELS.mo}`;
- }
-
- if (duration >= WEEK) {
- const num = getFormattedNumber(duration / WEEK);
- return `${num}${DURATION_LABELS.wk}`;
- }
-
- if (duration >= DAY) {
- const num = getFormattedNumber(duration / DAY);
- return `${num}${DURATION_LABELS.d}`;
- }
-
- if (duration >= HOUR) {
- const num = getFormattedNumber(duration / HOUR);
- return `${num}${DURATION_LABELS.hr}`;
- }
-
- if (duration >= MINUTE) {
- const num = getFormattedNumber(duration / MINUTE);
- return `${num}${DURATION_LABELS.min}`;
- }
-
- if (duration >= SECOND) {
- const num = getFormattedNumber(duration / SECOND);
- return `${num}${DURATION_LABELS.s}`;
+ for (const limit of DURATIONS) {
+ if (duration >= limit) {
+ const num = getFormattedNumber(duration / limit);
+ return `${num}${DURATION_LABELS[limit as keyof typeof DURATION_LABELS]}`;
+ }
}
+ return `${getFormattedNumber(duration)}ms`;
+}
- const num = getFormattedNumber(duration);
- return `${num}${DURATION_LABELS.ms}`;
+export function getFormattedSpanDuration(span: { timestamp: number; start_timestamp: number }): string {
+ return getFormattedDuration(span.timestamp - span.start_timestamp);
}