Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions static/app/components/events/contexts/knownContext/art.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {EventFixture} from 'sentry-fixture/event';

import {render, screen} from 'sentry-test/reactTestingLibrary';

import {ContextCard} from 'sentry/components/events/contexts/contextCard';
import {
getARTContextData,
type ARTContext,
} from 'sentry/components/events/contexts/knownContext/art';

const MOCK_ART_CONTEXT: ARTContext = {
'gc.blocking_count': 5,
'gc.pre_oome_count': 9,
'gc.total_time': 600,
'gc.waiting_time': 200,
'memory.free': 2048,
'memory.free_until_gc': 4096,
'memory.max': 536870912,
'memory.total': 268435456,
// Extra data is still valid and preserved
extra_data: 'something',
unknown_key: 123,
};

const MOCK_REDACTION = {
'gc.total_time': {
'': {
rem: [['organization:0', 's', 0, 0]],
len: 5,
},
},
};

describe('ARTContext', () => {
it('returns formatted values', () => {
expect(getARTContextData({data: MOCK_ART_CONTEXT})).toEqual([
{key: 'gc.blocking_count', subject: 'GC Blocking Count', value: 5},
{key: 'gc.pre_oome_count', subject: 'GC Pre-OOME Count', value: 9},
{key: 'gc.total_time', subject: 'GC Total Time', value: '600.00ms'},
{key: 'gc.waiting_time', subject: 'GC Waiting Time', value: '200.00ms'},
{key: 'memory.free', subject: 'Memory Free', value: '2.0 KiB'},
{key: 'memory.free_until_gc', subject: 'Memory Free Until GC', value: '4.0 KiB'},
{key: 'memory.max', subject: 'Memory Max', value: '512.0 MiB'},
{key: 'memory.total', subject: 'Memory Total', value: '256.0 MiB'},
{key: 'extra_data', subject: 'extra_data', value: 'something'},
{key: 'unknown_key', subject: 'unknown_key', value: 123},
]);
});

it('renders with meta annotations correctly', () => {
const event = EventFixture({
_meta: {contexts: {art: MOCK_REDACTION}},
});

render(
<ContextCard
event={event}
type="art"
alias="art"
value={{...MOCK_ART_CONTEXT, 'gc.total_time': ''}}
/>
);

expect(screen.getByText('Android Runtime')).toBeInTheDocument();
expect(screen.getByText('GC Blocking Count')).toBeInTheDocument();
expect(screen.getByText('Memory Free')).toBeInTheDocument();
expect(screen.getByText('2.0 KiB')).toBeInTheDocument();
expect(screen.getByText('GC Total Time')).toBeInTheDocument();
});
});
112 changes: 112 additions & 0 deletions static/app/components/events/contexts/knownContext/art.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {getContextKeys} from 'sentry/components/events/contexts/utils';
import {t} from 'sentry/locale';
import type {KeyValueListData} from 'sentry/types/group';
import {formatBytesBase2} from 'sentry/utils/bytes/formatBytesBase2';
import {getDuration} from 'sentry/utils/duration/getDuration';

enum ARTContextKeys {
GC_BLOCKING_COUNT = 'gc.blocking_count',
GC_PRE_OOME_COUNT = 'gc.pre_oome_count',
GC_TOTAL_TIME = 'gc.total_time',
GC_WAITING_TIME = 'gc.waiting_time',
MEMORY_FREE = 'memory.free',
MEMORY_FREE_UNTIL_GC = 'memory.free_until_gc',
MEMORY_MAX = 'memory.max',
MEMORY_TOTAL = 'memory.total',
}

export interface ARTContext {
[key: string]: any;
[ARTContextKeys.GC_BLOCKING_COUNT]?: number;
[ARTContextKeys.GC_PRE_OOME_COUNT]?: number;
[ARTContextKeys.GC_TOTAL_TIME]?: number;
[ARTContextKeys.GC_WAITING_TIME]?: number;
[ARTContextKeys.MEMORY_FREE]?: number;
[ARTContextKeys.MEMORY_FREE_UNTIL_GC]?: number;
[ARTContextKeys.MEMORY_MAX]?: number;
[ARTContextKeys.MEMORY_TOTAL]?: number;
}

function formatMilliseconds(ms: number): string {
return getDuration(ms / 1000, 2, true);
}

export function getARTContextData({
data,
meta,
}: {
data: ARTContext;
meta?: Record<keyof ARTContext, any>;
}): KeyValueListData {
return getContextKeys({data}).map(ctxKey => {
switch (ctxKey) {
case ARTContextKeys.GC_BLOCKING_COUNT:
return {
key: ctxKey,
subject: t('GC Blocking Count'),
value: data[ARTContextKeys.GC_BLOCKING_COUNT],
};
case ARTContextKeys.GC_PRE_OOME_COUNT:
return {
key: ctxKey,
subject: t('GC Pre-OOME Count'),
value: data[ARTContextKeys.GC_PRE_OOME_COUNT],
};
case ARTContextKeys.GC_TOTAL_TIME:
return {
key: ctxKey,
subject: t('GC Total Time'),
value: data[ARTContextKeys.GC_TOTAL_TIME]
? formatMilliseconds(data[ARTContextKeys.GC_TOTAL_TIME])
: data[ARTContextKeys.GC_TOTAL_TIME],
};
case ARTContextKeys.GC_WAITING_TIME:
return {
key: ctxKey,
subject: t('GC Waiting Time'),
value: data[ARTContextKeys.GC_WAITING_TIME]
? formatMilliseconds(data[ARTContextKeys.GC_WAITING_TIME])
: data[ARTContextKeys.GC_WAITING_TIME],
};
case ARTContextKeys.MEMORY_FREE:
return {
key: ctxKey,
subject: t('Memory Free'),
value: data[ARTContextKeys.MEMORY_FREE]
? formatBytesBase2(data[ARTContextKeys.MEMORY_FREE])
: data[ARTContextKeys.MEMORY_FREE],
};
case ARTContextKeys.MEMORY_FREE_UNTIL_GC:
return {
key: ctxKey,
subject: t('Memory Free Until GC'),
value: data[ARTContextKeys.MEMORY_FREE_UNTIL_GC]
? formatBytesBase2(data[ARTContextKeys.MEMORY_FREE_UNTIL_GC])
: data[ARTContextKeys.MEMORY_FREE_UNTIL_GC],
};
case ARTContextKeys.MEMORY_MAX:
return {
key: ctxKey,
subject: t('Memory Max'),
value: data[ARTContextKeys.MEMORY_MAX]
? formatBytesBase2(data[ARTContextKeys.MEMORY_MAX])
: data[ARTContextKeys.MEMORY_MAX],
};
case ARTContextKeys.MEMORY_TOTAL:
return {
key: ctxKey,
subject: t('Memory Total'),
value: data[ARTContextKeys.MEMORY_TOTAL]
? formatBytesBase2(data[ARTContextKeys.MEMORY_TOTAL])
: data[ARTContextKeys.MEMORY_TOTAL],
};
default:
return {
key: ctxKey,
subject: ctxKey,
value: data[ctxKey],
meta: meta?.[ctxKey]?.[''],
};
}
});
}
5 changes: 5 additions & 0 deletions static/app/components/events/contexts/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type ContextIconProps,
} from 'sentry/components/events/contexts/contextIcon';
import {getAppContextData} from 'sentry/components/events/contexts/knownContext/app';
import {getARTContextData} from 'sentry/components/events/contexts/knownContext/art';
import {getBrowserContextData} from 'sentry/components/events/contexts/knownContext/browser';
import {getCloudResourceContextData} from 'sentry/components/events/contexts/knownContext/cloudResource';
import {getCultureContextData} from 'sentry/components/events/contexts/knownContext/culture';
Expand Down Expand Up @@ -246,6 +247,8 @@ export function getContextTitle({
switch (contextType) {
case 'app':
return t('App');
case 'art':
return t('Android Runtime');
case 'device':
return t('Device');
case 'browser':
Expand Down Expand Up @@ -390,6 +393,8 @@ export function getFormattedContextData({
switch (contextType) {
case 'app':
return getAppContextData({data: contextValue, event, meta});
case 'art':
return getARTContextData({data: contextValue, meta});
case 'device':
return getDeviceContextData({data: contextValue, event, meta});
case 'memory_info': // Current
Expand Down
Loading