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
27 changes: 26 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import build from './build'
import { setVerbose } from './build/utils'
import doc from './doc'
import lint from './lint'
import perf from './performance'
import test from './test'

const program = createCommand()
Expand Down Expand Up @@ -52,13 +53,37 @@ program

program
.command('test')
.description('Run tests')
.description('Run tests using Jest')
.option('-w --watch')
.action(async (options: { watch?: boolean }) => {
await test(options.watch)
})

program
.command('perf')
.description('⚠️ EXPERIMENTAL Run performance tests and benchmarks (interface may change)')
.option('--pattern <pattern>', 'Test file pattern (default: **/*.perf.ts)')
.option('--iterations <number>', 'Number of iterations', '1')
.addOption(new Option('--output <format>', 'Output format')
.choices(['console', 'json', 'both'])
.default('console'))
.option('--output-file <file>', 'Output file path', 'performance-results.json')
.action(async (options: {
pattern?: string
iterations?: string
output?: 'json' | 'console' | 'both'
outputFile?: string
}) => {
await perf({
testPattern: options.pattern,
iterations: options.iterations
? parseInt(options.iterations, 10)
: undefined,
outputFormat: options.output,
outputFile: options.outputFile
})
})

program.parse(process.argv)

export { }
27 changes: 27 additions & 0 deletions src/performance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Performance testing module for rete-cli
*
* This module provides comprehensive performance testing capabilities including:
* - Memory usage tracking per test
* - Duration measurement with fallback strategies
* - Jest-style output formatting
* - JSON result export
* - Garbage collection handling
*/

export { MemoryUtils } from './memory-utils'
export { OutputFormatter } from './output-formatter'
export { PerformanceReporter } from './reporter'
export { ResultSaver } from './result-saver'
export { default } from './runner'
export { TestEstimator } from './test-estimator'
export type {
FileResult,
JestTestResult,
MemorySnapshot,
PerformanceConfig,
PerformanceMetrics,
Test,
TestResult,
TestStatus
} from './types'
130 changes: 130 additions & 0 deletions src/performance/memory-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* Memory measurement utilities for performance testing
*/

export class MemoryUtils {
private gcDetectionThreshold = 10 * 1024 * 1024 // 10MB threshold for GC detection

/**
* Force garbage collection if available
*/
forceGarbageCollection(): void {
if (global.gc) {
try {
global.gc()
// Wait for GC to complete
this.waitForMemoryStabilization()
} catch (error) {
// GC might not be available or might fail, log and continue
const errorMessage = error instanceof Error
? error.message
: String(error)

console.warn('Warning: Could not force garbage collection:', errorMessage)
}
}
}

/**
* Establish memory baseline by taking multiple readings
*/
establishMemoryBaseline(): number {
// Force GC if available to establish a clean baseline
this.forceGarbageCollection()

// Take multiple readings to establish stable baseline
const readings: number[] = []

for (let i = 0; i < 3; i++) {
readings.push(process.memoryUsage().heapUsed)
// Small delay between readings
const start = Date.now()

while (Date.now() - start < 10) {
// Busy wait for 10ms
}
}

return Math.min(...readings)
}

/**
* Get stabilized memory reading with GC interference detection
*/
getStabilizedMemoryReading(): number {
const readings: number[] = []

// Take multiple memory readings to detect GC interference
for (let i = 0; i < 5; i++) {
readings.push(process.memoryUsage().heapUsed)

// Small delay between readings
const start = Date.now()

while (Date.now() - start < 5) {
// Busy wait for 5ms
}
}

// Check for GC interference (large drops in memory)
const hasGcInterference = this.detectGarbageCollection(readings)

if (hasGcInterference) {
// If GC occurred, wait a bit and take fresh readings
this.waitForMemoryStabilization()
return this.getCleanMemoryReading()
}

// Return the median reading to avoid outliers
return this.getMedianValue(readings)
}

/**
* Detect if garbage collection occurred during readings
*/
private detectGarbageCollection(readings: number[]): boolean {
for (let i = 1; i < readings.length; i++) {
const memoryDrop = readings[i - 1] - readings[i]

// If memory dropped significantly, likely GC occurred
if (memoryDrop > this.gcDetectionThreshold) {
return true
}
}

return false
}

/**
* Wait for memory to stabilize after GC
*/
private waitForMemoryStabilization(): void {
const stabilizationTime = 50 // 50ms
const start = Date.now()

while (Date.now() - start < stabilizationTime) {
// Busy wait
}
}

/**
* Get a single clean memory reading after stabilization
*/
private getCleanMemoryReading(): number {
return process.memoryUsage().heapUsed
}

/**
* Calculate median value from array of numbers
*/
private getMedianValue(values: number[]): number {
const sorted = [...values].sort((a, b) => a - b)
const mid = Math.floor(sorted.length / 2)

if (sorted.length % 2 === 0) {
return (sorted[mid - 1] + sorted[mid]) / 2
}

return sorted[mid]
}
}
Loading
Loading