123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661 |
- /**
- * Performance Test for Hot-Reload Configuration
- *
- * Validates that configuration hot-reload operations meet the <100ms performance requirement
- * and that service remains available during configuration updates.
- */
- import { CredentialManager } from '@/core/credential-manager/CredentialManager'
- import { ConfigLoader } from '@/core/credential-manager/ConfigLoader'
- import { Platform } from '@/types/credential'
- import fs from 'fs/promises'
- import path from 'path'
- import { tmpdir } from 'os'
- describe('Hot-Reload Performance Test', () => {
- let credentialManager: CredentialManager
- let configLoader: ConfigLoader
- let tempConfigPath: string
- let tempDir: string
- beforeEach(async () => {
- // Create temporary directory for test configs
- tempDir = await fs.mkdtemp(path.join(tmpdir(), 'hot-reload-perf-test-'))
- tempConfigPath = path.join(tempDir, 'performance-config.json')
- // Initial small configuration
- const initialConfig = {
- accounts: [
- {
- id: 'initial-perf-account',
- name: 'Initial Performance Account',
- platform: Platform.PACIFICA,
- enabled: true,
- credentials: {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- }
- }
- ],
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: ['initial-perf-account'],
- backupAccounts: [],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: false,
- maxAccountsPerPlatform: 5,
- reservationTimeoutMs: 60000
- }
- }
- }
- await fs.writeFile(tempConfigPath, JSON.stringify(initialConfig, null, 2))
- credentialManager = new CredentialManager()
- configLoader = new ConfigLoader()
- await credentialManager.loadConfigurationFromFile(tempConfigPath, {
- enableHotReload: true,
- watchDebounceMs: 50 // Faster for testing
- })
- })
- afterEach(async () => {
- await credentialManager.shutdown()
- // Clean up temp files
- try {
- await fs.rm(tempDir, { recursive: true })
- } catch (error) {
- // Ignore cleanup errors
- }
- })
- describe('Single Configuration Reload Performance', () => {
- test('should reload small configuration within 100ms', async () => {
- const updatedConfig = {
- accounts: [
- {
- id: 'updated-perf-account',
- name: 'Updated Performance Account',
- platform: Platform.PACIFICA,
- enabled: true,
- credentials: {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- }
- }
- ],
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: ['updated-perf-account'],
- backupAccounts: [],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: false,
- maxAccountsPerPlatform: 5,
- reservationTimeoutMs: 60000
- }
- }
- }
- const startTime = performance.now()
- await fs.writeFile(tempConfigPath, JSON.stringify(updatedConfig, null, 2))
- // Wait for reload to complete
- let reloaded = false
- const maxWaitTime = 100
- const checkInterval = 5
- for (let elapsed = 0; elapsed < maxWaitTime; elapsed += checkInterval) {
- await new Promise(resolve => setTimeout(resolve, checkInterval))
- const account = credentialManager.getAccount('updated-perf-account')
- if (account) {
- reloaded = true
- break
- }
- }
- const reloadTime = performance.now() - startTime
- expect(reloaded).toBe(true)
- expect(reloadTime).toBeLessThan(100) // Performance requirement
- })
- test('should reload medium-sized configuration within 100ms', async () => {
- // Configuration with 10 accounts across 3 platforms
- const mediumConfig = {
- accounts: Array.from({ length: 10 }, (_, i) => ({
- id: `perf-account-${i}`,
- name: `Performance Account ${i}`,
- platform: [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE][i % 3],
- enabled: true,
- credentials: i % 3 === 0 ? {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- } : i % 3 === 1 ? {
- type: 'secp256k1' as const,
- privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
- } : {
- type: 'hmac' as const,
- apiKey: `api-key-${i}`,
- secretKey: `secret-key-${i}`
- }
- })),
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: ['perf-account-0', 'perf-account-3', 'perf-account-6'],
- backupAccounts: ['perf-account-9'],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- },
- [Platform.ASTER]: {
- enabled: true,
- primaryAccounts: ['perf-account-1', 'perf-account-4', 'perf-account-7'],
- backupAccounts: [],
- loadBalanceStrategy: 'weighted',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- },
- [Platform.BINANCE]: {
- enabled: true,
- primaryAccounts: ['perf-account-2', 'perf-account-5', 'perf-account-8'],
- backupAccounts: [],
- loadBalanceStrategy: 'least-used',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: true,
- maxAccountsPerPlatform: 10,
- reservationTimeoutMs: 60000
- }
- }
- }
- const startTime = performance.now()
- await fs.writeFile(tempConfigPath, JSON.stringify(mediumConfig, null, 2))
- // Wait for reload to complete
- let reloaded = false
- const maxWaitTime = 100
- for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 5) {
- await new Promise(resolve => setTimeout(resolve, 5))
- const accounts = credentialManager.getAllAccounts()
- if (accounts.length === 10) {
- reloaded = true
- break
- }
- }
- const reloadTime = performance.now() - startTime
- expect(reloaded).toBe(true)
- expect(reloadTime).toBeLessThan(100) // Performance requirement
- // Verify all accounts were loaded
- const accounts = credentialManager.getAllAccounts()
- expect(accounts).toHaveLength(10)
- })
- test('should handle large configuration efficiently', async () => {
- // Configuration with 50 accounts (stress test)
- const largeConfig = {
- accounts: Array.from({ length: 50 }, (_, i) => ({
- id: `large-perf-account-${i}`,
- name: `Large Performance Account ${i}`,
- platform: [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE][i % 3],
- enabled: true,
- credentials: i % 3 === 0 ? {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- } : i % 3 === 1 ? {
- type: 'secp256k1' as const,
- privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
- } : {
- type: 'hmac' as const,
- apiKey: `large-api-key-${i}`,
- secretKey: `large-secret-key-${i}`
- }
- })),
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${i * 3}`),
- backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${30 + i * 3}`),
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- },
- [Platform.ASTER]: {
- enabled: true,
- primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${1 + i * 3}`),
- backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${31 + i * 3}`),
- loadBalanceStrategy: 'weighted',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- },
- [Platform.BINANCE]: {
- enabled: true,
- primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${2 + i * 3}`),
- backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${32 + i * 3}`),
- loadBalanceStrategy: 'least-used',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: true,
- maxAccountsPerPlatform: 20,
- reservationTimeoutMs: 60000
- }
- }
- }
- const startTime = performance.now()
- await fs.writeFile(tempConfigPath, JSON.stringify(largeConfig, null, 2))
- // Wait for reload to complete (allow more time for large config)
- let reloaded = false
- const maxWaitTime = 200 // Allow 200ms for large config
- for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) {
- await new Promise(resolve => setTimeout(resolve, 10))
- const accounts = credentialManager.getAllAccounts()
- if (accounts.length === 50) {
- reloaded = true
- break
- }
- }
- const reloadTime = performance.now() - startTime
- expect(reloaded).toBe(true)
- expect(reloadTime).toBeLessThan(200) // Allow more time for large config
- // Verify all accounts were loaded
- const accounts = credentialManager.getAllAccounts()
- expect(accounts).toHaveLength(50)
- })
- })
- describe('Rapid Configuration Changes Performance', () => {
- test('should handle rapid successive configuration changes', async () => {
- const configs = Array.from({ length: 5 }, (_, i) => ({
- accounts: [
- {
- id: `rapid-account-${i}`,
- name: `Rapid Account ${i}`,
- platform: Platform.PACIFICA,
- enabled: true,
- credentials: {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- }
- }
- ],
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: [`rapid-account-${i}`],
- backupAccounts: [],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: false,
- maxAccountsPerPlatform: 5,
- reservationTimeoutMs: 60000
- }
- }
- }))
- const startTime = performance.now()
- // Rapid succession of changes
- for (const config of configs) {
- await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
- await new Promise(resolve => setTimeout(resolve, 20)) // Small delay
- }
- // Wait for final state
- let finalStateReached = false
- const maxWaitTime = 200
- for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) {
- await new Promise(resolve => setTimeout(resolve, 10))
- const account = credentialManager.getAccount('rapid-account-4')
- if (account) {
- finalStateReached = true
- break
- }
- }
- const totalTime = performance.now() - startTime
- expect(finalStateReached).toBe(true)
- expect(totalTime).toBeLessThan(300) // All changes should complete within 300ms
- // Verify final state
- const accounts = credentialManager.getAllAccounts()
- expect(accounts).toHaveLength(1)
- expect(accounts[0].id).toBe('rapid-account-4')
- })
- test('should maintain service availability during rapid changes', async () => {
- const message = new TextEncoder().encode('availability test during changes')
- let serviceAvailability = 0
- let totalChecks = 0
- // Start making configuration changes in background
- const configChangePromise = (async () => {
- for (let i = 0; i < 10; i++) {
- const config = {
- accounts: [
- {
- id: 'service-test-account',
- name: `Service Test Account ${i}`,
- platform: Platform.PACIFICA,
- enabled: true,
- credentials: {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- }
- }
- ],
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: ['service-test-account'],
- backupAccounts: [],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: false,
- maxAccountsPerPlatform: 5,
- reservationTimeoutMs: 60000
- }
- }
- }
- await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
- await new Promise(resolve => setTimeout(resolve, 30))
- }
- })()
- // Continuously check service availability
- const availabilityCheckPromise = (async () => {
- const startTime = Date.now()
- while (Date.now() - startTime < 500) { // Check for 500ms
- try {
- const result = await credentialManager.sign('service-test-account', message)
- if (result.success) {
- serviceAvailability++
- }
- } catch (error) {
- // Service unavailable during this check
- }
- totalChecks++
- await new Promise(resolve => setTimeout(resolve, 25))
- }
- })()
- await Promise.all([configChangePromise, availabilityCheckPromise])
- // Service should be available most of the time (>80%)
- const availabilityRate = serviceAvailability / totalChecks
- expect(availabilityRate).toBeGreaterThan(0.8)
- expect(totalChecks).toBeGreaterThan(10) // Should have made multiple checks
- })
- })
- describe('File System Performance', () => {
- test('should efficiently detect file changes', async () => {
- const changeCount = 10
- const detectedChanges: number[] = []
- // Monitor how quickly changes are detected
- for (let i = 0; i < changeCount; i++) {
- const config = {
- accounts: [
- {
- id: `detection-account-${i}`,
- name: `Detection Account ${i}`,
- platform: Platform.PACIFICA,
- enabled: true,
- credentials: {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- }
- }
- ],
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: [`detection-account-${i}`],
- backupAccounts: [],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: false,
- maxAccountsPerPlatform: 5,
- reservationTimeoutMs: 60000
- }
- }
- }
- const writeStartTime = performance.now()
- await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
- // Wait for detection
- let detected = false
- const maxDetectionTime = 100
- for (let elapsed = 0; elapsed < maxDetectionTime; elapsed += 5) {
- await new Promise(resolve => setTimeout(resolve, 5))
- const account = credentialManager.getAccount(`detection-account-${i}`)
- if (account) {
- detected = true
- detectedChanges.push(performance.now() - writeStartTime)
- break
- }
- }
- expect(detected).toBe(true)
- }
- // All changes should be detected quickly
- expect(detectedChanges).toHaveLength(changeCount)
- const averageDetectionTime = detectedChanges.reduce((a, b) => a + b, 0) / changeCount
- const maxDetectionTime = Math.max(...detectedChanges)
- expect(averageDetectionTime).toBeLessThan(75) // Average < 75ms
- expect(maxDetectionTime).toBeLessThan(100) // Max < 100ms
- })
- test('should handle file system errors gracefully', async () => {
- // Create invalid JSON to test error handling performance
- const invalidConfigs = [
- '{ invalid json',
- '{ "accounts": [{ "invalid": }',
- '{ "accounts": [], "hedging": { missing bracket',
- '', // Empty file
- 'not json at all'
- ]
- for (const invalidConfig of invalidConfigs) {
- const startTime = performance.now()
- await fs.writeFile(tempConfigPath, invalidConfig)
- // Wait a bit to see if system handles error
- await new Promise(resolve => setTimeout(resolve, 100))
- const errorHandlingTime = performance.now() - startTime
- // Should handle errors quickly without crashing
- expect(errorHandlingTime).toBeLessThan(150)
- // System should still be responsive
- const accounts = credentialManager.getAllAccounts()
- expect(Array.isArray(accounts)).toBe(true) // Should not crash
- }
- })
- })
- describe('Memory Performance During Reloads', () => {
- test('should not leak memory during repeated reloads', async () => {
- const initialMemory = process.memoryUsage()
- // Perform many configuration reloads
- for (let i = 0; i < 20; i++) {
- const config = {
- accounts: [
- {
- id: `memory-test-${i}`,
- name: `Memory Test ${i}`,
- platform: Platform.PACIFICA,
- enabled: true,
- credentials: {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- }
- }
- ],
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: [`memory-test-${i}`],
- backupAccounts: [],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: false,
- maxAccountsPerPlatform: 5,
- reservationTimeoutMs: 60000
- }
- }
- }
- await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
- await new Promise(resolve => setTimeout(resolve, 60)) // Wait for reload
- }
- // Force garbage collection if available
- if (global.gc) {
- global.gc()
- await new Promise(resolve => setTimeout(resolve, 100))
- }
- const finalMemory = process.memoryUsage()
- // Memory increase should be reasonable (less than 5MB)
- const heapIncrease = finalMemory.heapUsed - initialMemory.heapUsed
- expect(heapIncrease).toBeLessThan(5 * 1024 * 1024) // 5MB
- })
- test('should maintain performance under memory pressure', async () => {
- // Create some memory pressure
- const memoryPressure: string[] = []
- for (let i = 0; i < 1000; i++) {
- memoryPressure.push('x'.repeat(1000)) // 1MB total
- }
- const config = {
- accounts: [
- {
- id: 'pressure-test-account',
- name: 'Pressure Test Account',
- platform: Platform.PACIFICA,
- enabled: true,
- credentials: {
- type: 'ed25519' as const,
- privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
- }
- }
- ],
- hedging: {
- platforms: {
- [Platform.PACIFICA]: {
- enabled: true,
- primaryAccounts: ['pressure-test-account'],
- backupAccounts: [],
- loadBalanceStrategy: 'round-robin',
- healthCheckInterval: 30000,
- failoverThreshold: 3
- }
- },
- hedging: {
- enableCrossplatformBalancing: false,
- maxAccountsPerPlatform: 5,
- reservationTimeoutMs: 60000
- }
- }
- }
- const startTime = performance.now()
- await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
- // Wait for reload
- let reloaded = false
- const maxWaitTime = 150 // Allow extra time under pressure
- for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) {
- await new Promise(resolve => setTimeout(resolve, 10))
- const account = credentialManager.getAccount('pressure-test-account')
- if (account) {
- reloaded = true
- break
- }
- }
- const reloadTime = performance.now() - startTime
- expect(reloaded).toBe(true)
- expect(reloadTime).toBeLessThan(150) // Should still meet requirements under pressure
- // Clean up memory pressure
- memoryPressure.length = 0
- })
- })
- })
|