diff --git a/static/app/components/events/contexts/knownContext/art.spec.tsx b/static/app/components/events/contexts/knownContext/art.spec.tsx
new file mode 100644
index 000000000000..78f041feae6a
--- /dev/null
+++ b/static/app/components/events/contexts/knownContext/art.spec.tsx
@@ -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(
+
+ );
+
+ 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();
+ });
+});
diff --git a/static/app/components/events/contexts/knownContext/art.tsx b/static/app/components/events/contexts/knownContext/art.tsx
new file mode 100644
index 000000000000..41b8887ab838
--- /dev/null
+++ b/static/app/components/events/contexts/knownContext/art.tsx
@@ -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;
+}): 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]?.[''],
+ };
+ }
+ });
+}
diff --git a/static/app/components/events/contexts/utils.tsx b/static/app/components/events/contexts/utils.tsx
index d57ba74602a8..13e63ab882ff 100644
--- a/static/app/components/events/contexts/utils.tsx
+++ b/static/app/components/events/contexts/utils.tsx
@@ -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';
@@ -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':
@@ -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