Skip to content
Merged
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
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

name: Tempo CI
env:
TZ: America/New_York
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8

on:
push:
branches:
- main
- release-c-layout-order-planner
pull_request:
branches:
- main
- release-c-layout-order-planner

jobs:
test:
name: Standard Tests
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Cache npm
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('packages/tempo/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install monorepo dependencies
run: npm ci
working-directory: ${{ github.workspace }}
Comment thread
magmacomputing marked this conversation as resolved.
- name: Run standard tests
run: npm test
working-directory: packages/tempo

test-parse-prefilter:
name: Test with parsePrefilter enabled
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.ref == 'refs/heads/release-c-layout-order-planner' || github.event.pull_request.base.ref == 'main'
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Cache npm
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('packages/tempo/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install monorepo dependencies
run: npm ci
working-directory: ${{ github.workspace }}
- name: Write parsePrefilter setup file
run: |
echo "import { Tempo } from '../src/tempo.index.ts';\nTempo.init({ parsePrefilter: true });" > packages/tempo/test/ci.prefilter.setup.js
Comment thread
magmacomputing marked this conversation as resolved.
- name: Run all tests with parsePrefilter
run: npm test
working-directory: packages/tempo
env:
TEMPO_PREFILTER_CI: 'true'
- name: Run end-to-end benchmark
run: npx tsx --conditions=development bench/bench.parse.prefilter.e2e.ts > bench-output.json
working-directory: packages/tempo
- name: Upload benchmark output
uses: actions/upload-artifact@v4
with:
name: bench-parse-prefilter-e2e
path: packages/tempo/bench-output.json
Comment thread
magmacomputing marked this conversation as resolved.
- name: Validate benchmark output
run: |
node -e "const r=require('./packages/tempo/bench-output.json');if(!r.success){console.error('Benchmark failed:',r.errors);process.exit(1)}else{console.log('Benchmark passed.')}"
working-directory: ${{ github.workspace }}
102 changes: 102 additions & 0 deletions packages/tempo/bench/bench.parse.prefilter.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import '../bin/temporal-polyfill.ts';
import { Tempo } from '../src/tempo.index.ts';
import { performance } from 'node:perf_hooks';

import fs from 'fs';

let corpus: string[] = [];
const layoutKeys = new Set([
'hourMinuteSecond', 'dayMonthYearShort', 'monthDayYearShort', 'yearMonthDayShort',
'weekDay', 'date', 'time', 'dateTime', 'timeDate', 'dayMonthYear', 'monthDayYear',
'yearMonthDay', 'offset', 'relativeOffset'
]);
try {
corpus = fs.readFileSync(new URL('./bench.parse.prefilter.ts', import.meta.url), 'utf-8')
.split(/\n/)
.filter(line => line.trim().startsWith("'") && line.includes(','))
.map(line => line.replace(/['",]/g, '').trim())
.filter(Boolean)
.filter(line => !layoutKeys.has(line));
} catch {
corpus = [
'04012026',
'310559',
'590531',
'09:30',
'monday',
'2 days ago',
'+6',
'1234567890123',
'2026-04-25',
'2026/04/25 10:30',
'11:45pm',
'tomorrow',
];
}

function runE2E(enablePrefilter: boolean, iterations: number) {
Tempo.init({
parsePrefilter: enablePrefilter,
debug: false,
catch: true,
timeZone: 'UTC',
});

let checksum = 0;
const start = performance.now();

for (let i = 0; i < iterations; i++) {
for (const input of corpus) {
const t = new Tempo(input, { timeZone: 'UTC' });
checksum += t.toDateTime().epochMilliseconds;
}
}

const elapsedMs = performance.now() - start;
const operations = iterations * corpus.length;

return {
iterations,
operations,
elapsedMs: Number(elapsedMs.toFixed(3)),
msPerIteration: Number((elapsedMs / iterations).toFixed(6)),
msPerInput: Number((elapsedMs / operations).toFixed(6)),
checksum,
};
}

const warmupIterations = Number(process.env.PREFILTER_E2E_WARMUP ?? 200);
const benchIterations = Number(process.env.PREFILTER_E2E_ITERATIONS ?? 1000);

// Warmup both paths to reduce one-time JIT effects.
runE2E(false, warmupIterations);
runE2E(true, warmupIterations);

const base = runE2E(false, benchIterations);
const pre = runE2E(true, benchIterations);

const timingDeltaPct = Number((((pre.elapsedMs - base.elapsedMs) / base.elapsedMs) * 100).toFixed(2));

const result = {
base,
pre,
timingDeltaPct,
thresholds: {
maxTimingDeltaPct: 10, // fail if prefilter is >10% slower than base
minChecksum: 1 // dummy threshold, adjust as needed
},
success: true,
errors: []
};

if (timingDeltaPct > result.thresholds.maxTimingDeltaPct) {
result.success = false;
result.errors.push(`Prefilter is too slow: ${timingDeltaPct}% > ${result.thresholds.maxTimingDeltaPct}%`);
}
if (base.checksum < result.thresholds.minChecksum || pre.checksum < result.thresholds.minChecksum) {
result.success = false;
result.errors.push('Checksum below minimum threshold');
}

console.log(JSON.stringify(result));
if (!result.success) process.exit(1);
Loading
Loading