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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ jobs:
- run:
name: Add examples/* to yarn workspace
command: |
npm pkg delete workspaces[7]
npm pkg delete workspaces[6]
npm pkg delete workspaces[5]
npm pkg delete workspaces[4]
Expand Down
4 changes: 4 additions & 0 deletions examples/benchmark-native/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: '@react-native',
};
85 changes: 85 additions & 0 deletions examples/benchmark-native/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# OSX
#
.DS_Store

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
**/.xcode.env.local

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
.kotlin/

# node.js
#
node_modules/
npm-debug.log
yarn-error.log

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/

**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output

# Bundle artifact
*.jsbundle

# Ruby / CocoaPods
**/Pods/
/vendor/bundle/

# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*

# testing
/coverage

# Local measurement artifacts
/artifacts/
*.hprof

# Generated BuildManifest asset (prepare step); scripts remain tracked
android/app/src/main/assets/build-manifest.json

# Ephemeral local JDK used only for CI-less verification (never commit)
/.jdk/

# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
5 changes: 5 additions & 0 deletions examples/benchmark-native/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
arrowParens: 'avoid',
singleQuote: true,
trailingComma: 'all',
};
1 change: 1 addition & 0 deletions examples/benchmark-native/.watchmanconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
199 changes: 199 additions & 0 deletions examples/benchmark-native/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* Minimal Run/status UI + auto-run from Android intent extras.
* Prevents overlapping runs. Sustained visual update during measurement.
*/
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Pressable, StyleSheet, Text, View, StatusBar } from 'react-native';

import BenchNative from './src/BenchNative';
import { setAnimationTickListener } from './src/measure';
import { executeMeasurement } from './src/runOrchestration';
import { scenarioId } from './src/scenario';
import type { GCScenarioConfig, LaunchConfig } from './src/types';
import { parseLaunchConfig } from './src/validateConfig';

const DEFAULT_CONFIG: GCScenarioConfig = {
candidateKind: 'entity',
pattern: 'unique',
count: 1000,
control: 'gc',
};

export default function App() {
const [status, setStatus] = useState('Ready');
const [lastId, setLastId] = useState<string | null>(null);
const [animPhase, setAnimPhase] = useState(0);
const [config, setConfig] = useState<GCScenarioConfig>(DEFAULT_CONFIG);
const [samples, setSamples] = useState(1);
const [label, setLabel] = useState<string | undefined>(undefined);
const runningRef = useRef(false);
const autoStarted = useRef(false);

const runWithConfig = useCallback(
async (cfg: GCScenarioConfig, sampleCount: number, runLabel?: string) => {
if (runningRef.current) {
setStatus('Busy — overlapping runs blocked');
return;
}
runningRef.current = true;
setLastId(scenarioId(cfg));
try {
await executeMeasurement({
config: cfg,
samples: sampleCount,
label: runLabel,
onStatus: setStatus,
});
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
setStatus(`Error: ${message}`);
} finally {
runningRef.current = false;
}
},
[],
);

const runOnce = useCallback(() => {
void runWithConfig(config, samples, label);
}, [config, label, runWithConfig, samples]);

useEffect(() => {
setAnimationTickListener(t => {
setAnimPhase(t);
});
return () => setAnimationTickListener(null);
}, []);

useEffect(() => {
let cancelled = false;
(async () => {
try {
const raw = await BenchNative.getLaunchConfig();
if (cancelled) return;
let parsed: LaunchConfig;
try {
parsed = parseLaunchConfig(raw);
} catch (e) {
setStatus(
`Invalid launch config: ${e instanceof Error ? e.message : String(e)}`,
);
return;
}
const cfg: GCScenarioConfig = {
candidateKind: parsed.candidateKind,
pattern: parsed.pattern,
count: parsed.count,
control: parsed.control,
};
setConfig(cfg);
setSamples(parsed.samples);
setLabel(parsed.label);
if (parsed.autoRun && !autoStarted.current) {
autoStarted.current = true;
setTimeout(() => {
if (cancelled) return;
void runWithConfig(cfg, parsed.samples, parsed.label);
}, 50);
}
} catch (e) {
if (!cancelled) {
setStatus(
`Launch config unavailable: ${e instanceof Error ? e.message : String(e)}`,
);
}
}
})();
return () => {
cancelled = true;
};
}, [runWithConfig]);

const pulse = ((animPhase / 16) % 40) / 40;

return (
<View style={styles.root}>
<StatusBar barStyle="dark-content" />
<Text style={styles.title}>GC Bench (Hermes release)</Text>
<Text style={styles.meta}>
{config.candidateKind}/{config.pattern}/{config.count}/{config.control}
</Text>
{lastId ? <Text style={styles.id}>{lastId}</Text> : null}
<Text style={styles.status}>{status}</Text>

<View
style={[
styles.anim,
{
transform: [{ translateX: pulse * 120 - 60 }],
opacity: 0.35 + pulse * 0.65,
},
]}
/>

<Pressable
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed,
]}
onPress={runOnce}
accessibilityRole="button"
accessibilityLabel="Run GC benchmark"
>
<Text style={styles.buttonText}>Run</Text>
</Pressable>
</View>
);
}

const styles = StyleSheet.create({
root: {
flex: 1,
padding: 24,
justifyContent: 'center',
backgroundColor: '#f2f4f7',
},
title: {
fontSize: 22,
fontWeight: '600',
marginBottom: 8,
color: '#111',
},
meta: {
fontSize: 14,
color: '#444',
marginBottom: 4,
},
id: {
fontSize: 12,
color: '#666',
marginBottom: 12,
fontFamily: 'monospace',
},
status: {
fontSize: 14,
color: '#222',
marginBottom: 24,
},
anim: {
width: 48,
height: 48,
backgroundColor: '#2a6f97',
marginBottom: 32,
alignSelf: 'center',
},
button: {
backgroundColor: '#1b4332',
paddingVertical: 14,
paddingHorizontal: 28,
alignSelf: 'flex-start',
},
buttonPressed: {
opacity: 0.85,
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});
Loading