hot-reload-performance.test.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. /**
  2. * Performance Test for Hot-Reload Configuration
  3. *
  4. * Validates that configuration hot-reload operations meet the <100ms performance requirement
  5. * and that service remains available during configuration updates.
  6. */
  7. import { CredentialManager } from '@/core/credential-manager/CredentialManager'
  8. import { ConfigLoader } from '@/core/credential-manager/ConfigLoader'
  9. import { Platform } from '@/types/credential'
  10. import fs from 'fs/promises'
  11. import path from 'path'
  12. import { tmpdir } from 'os'
  13. describe('Hot-Reload Performance Test', () => {
  14. let credentialManager: CredentialManager
  15. let configLoader: ConfigLoader
  16. let tempConfigPath: string
  17. let tempDir: string
  18. beforeEach(async () => {
  19. // Create temporary directory for test configs
  20. tempDir = await fs.mkdtemp(path.join(tmpdir(), 'hot-reload-perf-test-'))
  21. tempConfigPath = path.join(tempDir, 'performance-config.json')
  22. // Initial small configuration
  23. const initialConfig = {
  24. accounts: [
  25. {
  26. id: 'initial-perf-account',
  27. name: 'Initial Performance Account',
  28. platform: Platform.PACIFICA,
  29. enabled: true,
  30. credentials: {
  31. type: 'ed25519' as const,
  32. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  33. }
  34. }
  35. ],
  36. hedging: {
  37. platforms: {
  38. [Platform.PACIFICA]: {
  39. enabled: true,
  40. primaryAccounts: ['initial-perf-account'],
  41. backupAccounts: [],
  42. loadBalanceStrategy: 'round-robin',
  43. healthCheckInterval: 30000,
  44. failoverThreshold: 3
  45. }
  46. },
  47. hedging: {
  48. enableCrossplatformBalancing: false,
  49. maxAccountsPerPlatform: 5,
  50. reservationTimeoutMs: 60000
  51. }
  52. }
  53. }
  54. await fs.writeFile(tempConfigPath, JSON.stringify(initialConfig, null, 2))
  55. credentialManager = new CredentialManager()
  56. configLoader = new ConfigLoader()
  57. await credentialManager.loadConfigurationFromFile(tempConfigPath, {
  58. enableHotReload: true,
  59. watchDebounceMs: 50 // Faster for testing
  60. })
  61. })
  62. afterEach(async () => {
  63. await credentialManager.shutdown()
  64. // Clean up temp files
  65. try {
  66. await fs.rm(tempDir, { recursive: true })
  67. } catch (error) {
  68. // Ignore cleanup errors
  69. }
  70. })
  71. describe('Single Configuration Reload Performance', () => {
  72. test('should reload small configuration within 100ms', async () => {
  73. const updatedConfig = {
  74. accounts: [
  75. {
  76. id: 'updated-perf-account',
  77. name: 'Updated Performance Account',
  78. platform: Platform.PACIFICA,
  79. enabled: true,
  80. credentials: {
  81. type: 'ed25519' as const,
  82. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  83. }
  84. }
  85. ],
  86. hedging: {
  87. platforms: {
  88. [Platform.PACIFICA]: {
  89. enabled: true,
  90. primaryAccounts: ['updated-perf-account'],
  91. backupAccounts: [],
  92. loadBalanceStrategy: 'round-robin',
  93. healthCheckInterval: 30000,
  94. failoverThreshold: 3
  95. }
  96. },
  97. hedging: {
  98. enableCrossplatformBalancing: false,
  99. maxAccountsPerPlatform: 5,
  100. reservationTimeoutMs: 60000
  101. }
  102. }
  103. }
  104. const startTime = performance.now()
  105. await fs.writeFile(tempConfigPath, JSON.stringify(updatedConfig, null, 2))
  106. // Wait for reload to complete
  107. let reloaded = false
  108. const maxWaitTime = 100
  109. const checkInterval = 5
  110. for (let elapsed = 0; elapsed < maxWaitTime; elapsed += checkInterval) {
  111. await new Promise(resolve => setTimeout(resolve, checkInterval))
  112. const account = credentialManager.getAccount('updated-perf-account')
  113. if (account) {
  114. reloaded = true
  115. break
  116. }
  117. }
  118. const reloadTime = performance.now() - startTime
  119. expect(reloaded).toBe(true)
  120. expect(reloadTime).toBeLessThan(100) // Performance requirement
  121. })
  122. test('should reload medium-sized configuration within 100ms', async () => {
  123. // Configuration with 10 accounts across 3 platforms
  124. const mediumConfig = {
  125. accounts: Array.from({ length: 10 }, (_, i) => ({
  126. id: `perf-account-${i}`,
  127. name: `Performance Account ${i}`,
  128. platform: [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE][i % 3],
  129. enabled: true,
  130. credentials: i % 3 === 0 ? {
  131. type: 'ed25519' as const,
  132. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  133. } : i % 3 === 1 ? {
  134. type: 'secp256k1' as const,
  135. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  136. } : {
  137. type: 'hmac' as const,
  138. apiKey: `api-key-${i}`,
  139. secretKey: `secret-key-${i}`
  140. }
  141. })),
  142. hedging: {
  143. platforms: {
  144. [Platform.PACIFICA]: {
  145. enabled: true,
  146. primaryAccounts: ['perf-account-0', 'perf-account-3', 'perf-account-6'],
  147. backupAccounts: ['perf-account-9'],
  148. loadBalanceStrategy: 'round-robin',
  149. healthCheckInterval: 30000,
  150. failoverThreshold: 3
  151. },
  152. [Platform.ASTER]: {
  153. enabled: true,
  154. primaryAccounts: ['perf-account-1', 'perf-account-4', 'perf-account-7'],
  155. backupAccounts: [],
  156. loadBalanceStrategy: 'weighted',
  157. healthCheckInterval: 30000,
  158. failoverThreshold: 3
  159. },
  160. [Platform.BINANCE]: {
  161. enabled: true,
  162. primaryAccounts: ['perf-account-2', 'perf-account-5', 'perf-account-8'],
  163. backupAccounts: [],
  164. loadBalanceStrategy: 'least-used',
  165. healthCheckInterval: 30000,
  166. failoverThreshold: 3
  167. }
  168. },
  169. hedging: {
  170. enableCrossplatformBalancing: true,
  171. maxAccountsPerPlatform: 10,
  172. reservationTimeoutMs: 60000
  173. }
  174. }
  175. }
  176. const startTime = performance.now()
  177. await fs.writeFile(tempConfigPath, JSON.stringify(mediumConfig, null, 2))
  178. // Wait for reload to complete
  179. let reloaded = false
  180. const maxWaitTime = 100
  181. for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 5) {
  182. await new Promise(resolve => setTimeout(resolve, 5))
  183. const accounts = credentialManager.getAllAccounts()
  184. if (accounts.length === 10) {
  185. reloaded = true
  186. break
  187. }
  188. }
  189. const reloadTime = performance.now() - startTime
  190. expect(reloaded).toBe(true)
  191. expect(reloadTime).toBeLessThan(100) // Performance requirement
  192. // Verify all accounts were loaded
  193. const accounts = credentialManager.getAllAccounts()
  194. expect(accounts).toHaveLength(10)
  195. })
  196. test('should handle large configuration efficiently', async () => {
  197. // Configuration with 50 accounts (stress test)
  198. const largeConfig = {
  199. accounts: Array.from({ length: 50 }, (_, i) => ({
  200. id: `large-perf-account-${i}`,
  201. name: `Large Performance Account ${i}`,
  202. platform: [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE][i % 3],
  203. enabled: true,
  204. credentials: i % 3 === 0 ? {
  205. type: 'ed25519' as const,
  206. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  207. } : i % 3 === 1 ? {
  208. type: 'secp256k1' as const,
  209. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  210. } : {
  211. type: 'hmac' as const,
  212. apiKey: `large-api-key-${i}`,
  213. secretKey: `large-secret-key-${i}`
  214. }
  215. })),
  216. hedging: {
  217. platforms: {
  218. [Platform.PACIFICA]: {
  219. enabled: true,
  220. primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${i * 3}`),
  221. backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${30 + i * 3}`),
  222. loadBalanceStrategy: 'round-robin',
  223. healthCheckInterval: 30000,
  224. failoverThreshold: 3
  225. },
  226. [Platform.ASTER]: {
  227. enabled: true,
  228. primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${1 + i * 3}`),
  229. backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${31 + i * 3}`),
  230. loadBalanceStrategy: 'weighted',
  231. healthCheckInterval: 30000,
  232. failoverThreshold: 3
  233. },
  234. [Platform.BINANCE]: {
  235. enabled: true,
  236. primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${2 + i * 3}`),
  237. backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${32 + i * 3}`),
  238. loadBalanceStrategy: 'least-used',
  239. healthCheckInterval: 30000,
  240. failoverThreshold: 3
  241. }
  242. },
  243. hedging: {
  244. enableCrossplatformBalancing: true,
  245. maxAccountsPerPlatform: 20,
  246. reservationTimeoutMs: 60000
  247. }
  248. }
  249. }
  250. const startTime = performance.now()
  251. await fs.writeFile(tempConfigPath, JSON.stringify(largeConfig, null, 2))
  252. // Wait for reload to complete (allow more time for large config)
  253. let reloaded = false
  254. const maxWaitTime = 200 // Allow 200ms for large config
  255. for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) {
  256. await new Promise(resolve => setTimeout(resolve, 10))
  257. const accounts = credentialManager.getAllAccounts()
  258. if (accounts.length === 50) {
  259. reloaded = true
  260. break
  261. }
  262. }
  263. const reloadTime = performance.now() - startTime
  264. expect(reloaded).toBe(true)
  265. expect(reloadTime).toBeLessThan(200) // Allow more time for large config
  266. // Verify all accounts were loaded
  267. const accounts = credentialManager.getAllAccounts()
  268. expect(accounts).toHaveLength(50)
  269. })
  270. })
  271. describe('Rapid Configuration Changes Performance', () => {
  272. test('should handle rapid successive configuration changes', async () => {
  273. const configs = Array.from({ length: 5 }, (_, i) => ({
  274. accounts: [
  275. {
  276. id: `rapid-account-${i}`,
  277. name: `Rapid Account ${i}`,
  278. platform: Platform.PACIFICA,
  279. enabled: true,
  280. credentials: {
  281. type: 'ed25519' as const,
  282. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  283. }
  284. }
  285. ],
  286. hedging: {
  287. platforms: {
  288. [Platform.PACIFICA]: {
  289. enabled: true,
  290. primaryAccounts: [`rapid-account-${i}`],
  291. backupAccounts: [],
  292. loadBalanceStrategy: 'round-robin',
  293. healthCheckInterval: 30000,
  294. failoverThreshold: 3
  295. }
  296. },
  297. hedging: {
  298. enableCrossplatformBalancing: false,
  299. maxAccountsPerPlatform: 5,
  300. reservationTimeoutMs: 60000
  301. }
  302. }
  303. }))
  304. const startTime = performance.now()
  305. // Rapid succession of changes
  306. for (const config of configs) {
  307. await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
  308. await new Promise(resolve => setTimeout(resolve, 20)) // Small delay
  309. }
  310. // Wait for final state
  311. let finalStateReached = false
  312. const maxWaitTime = 200
  313. for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) {
  314. await new Promise(resolve => setTimeout(resolve, 10))
  315. const account = credentialManager.getAccount('rapid-account-4')
  316. if (account) {
  317. finalStateReached = true
  318. break
  319. }
  320. }
  321. const totalTime = performance.now() - startTime
  322. expect(finalStateReached).toBe(true)
  323. expect(totalTime).toBeLessThan(300) // All changes should complete within 300ms
  324. // Verify final state
  325. const accounts = credentialManager.getAllAccounts()
  326. expect(accounts).toHaveLength(1)
  327. expect(accounts[0].id).toBe('rapid-account-4')
  328. })
  329. test('should maintain service availability during rapid changes', async () => {
  330. const message = new TextEncoder().encode('availability test during changes')
  331. let serviceAvailability = 0
  332. let totalChecks = 0
  333. // Start making configuration changes in background
  334. const configChangePromise = (async () => {
  335. for (let i = 0; i < 10; i++) {
  336. const config = {
  337. accounts: [
  338. {
  339. id: 'service-test-account',
  340. name: `Service Test Account ${i}`,
  341. platform: Platform.PACIFICA,
  342. enabled: true,
  343. credentials: {
  344. type: 'ed25519' as const,
  345. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  346. }
  347. }
  348. ],
  349. hedging: {
  350. platforms: {
  351. [Platform.PACIFICA]: {
  352. enabled: true,
  353. primaryAccounts: ['service-test-account'],
  354. backupAccounts: [],
  355. loadBalanceStrategy: 'round-robin',
  356. healthCheckInterval: 30000,
  357. failoverThreshold: 3
  358. }
  359. },
  360. hedging: {
  361. enableCrossplatformBalancing: false,
  362. maxAccountsPerPlatform: 5,
  363. reservationTimeoutMs: 60000
  364. }
  365. }
  366. }
  367. await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
  368. await new Promise(resolve => setTimeout(resolve, 30))
  369. }
  370. })()
  371. // Continuously check service availability
  372. const availabilityCheckPromise = (async () => {
  373. const startTime = Date.now()
  374. while (Date.now() - startTime < 500) { // Check for 500ms
  375. try {
  376. const result = await credentialManager.sign('service-test-account', message)
  377. if (result.success) {
  378. serviceAvailability++
  379. }
  380. } catch (error) {
  381. // Service unavailable during this check
  382. }
  383. totalChecks++
  384. await new Promise(resolve => setTimeout(resolve, 25))
  385. }
  386. })()
  387. await Promise.all([configChangePromise, availabilityCheckPromise])
  388. // Service should be available most of the time (>80%)
  389. const availabilityRate = serviceAvailability / totalChecks
  390. expect(availabilityRate).toBeGreaterThan(0.8)
  391. expect(totalChecks).toBeGreaterThan(10) // Should have made multiple checks
  392. })
  393. })
  394. describe('File System Performance', () => {
  395. test('should efficiently detect file changes', async () => {
  396. const changeCount = 10
  397. const detectedChanges: number[] = []
  398. // Monitor how quickly changes are detected
  399. for (let i = 0; i < changeCount; i++) {
  400. const config = {
  401. accounts: [
  402. {
  403. id: `detection-account-${i}`,
  404. name: `Detection Account ${i}`,
  405. platform: Platform.PACIFICA,
  406. enabled: true,
  407. credentials: {
  408. type: 'ed25519' as const,
  409. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  410. }
  411. }
  412. ],
  413. hedging: {
  414. platforms: {
  415. [Platform.PACIFICA]: {
  416. enabled: true,
  417. primaryAccounts: [`detection-account-${i}`],
  418. backupAccounts: [],
  419. loadBalanceStrategy: 'round-robin',
  420. healthCheckInterval: 30000,
  421. failoverThreshold: 3
  422. }
  423. },
  424. hedging: {
  425. enableCrossplatformBalancing: false,
  426. maxAccountsPerPlatform: 5,
  427. reservationTimeoutMs: 60000
  428. }
  429. }
  430. }
  431. const writeStartTime = performance.now()
  432. await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
  433. // Wait for detection
  434. let detected = false
  435. const maxDetectionTime = 100
  436. for (let elapsed = 0; elapsed < maxDetectionTime; elapsed += 5) {
  437. await new Promise(resolve => setTimeout(resolve, 5))
  438. const account = credentialManager.getAccount(`detection-account-${i}`)
  439. if (account) {
  440. detected = true
  441. detectedChanges.push(performance.now() - writeStartTime)
  442. break
  443. }
  444. }
  445. expect(detected).toBe(true)
  446. }
  447. // All changes should be detected quickly
  448. expect(detectedChanges).toHaveLength(changeCount)
  449. const averageDetectionTime = detectedChanges.reduce((a, b) => a + b, 0) / changeCount
  450. const maxDetectionTime = Math.max(...detectedChanges)
  451. expect(averageDetectionTime).toBeLessThan(75) // Average < 75ms
  452. expect(maxDetectionTime).toBeLessThan(100) // Max < 100ms
  453. })
  454. test('should handle file system errors gracefully', async () => {
  455. // Create invalid JSON to test error handling performance
  456. const invalidConfigs = [
  457. '{ invalid json',
  458. '{ "accounts": [{ "invalid": }',
  459. '{ "accounts": [], "hedging": { missing bracket',
  460. '', // Empty file
  461. 'not json at all'
  462. ]
  463. for (const invalidConfig of invalidConfigs) {
  464. const startTime = performance.now()
  465. await fs.writeFile(tempConfigPath, invalidConfig)
  466. // Wait a bit to see if system handles error
  467. await new Promise(resolve => setTimeout(resolve, 100))
  468. const errorHandlingTime = performance.now() - startTime
  469. // Should handle errors quickly without crashing
  470. expect(errorHandlingTime).toBeLessThan(150)
  471. // System should still be responsive
  472. const accounts = credentialManager.getAllAccounts()
  473. expect(Array.isArray(accounts)).toBe(true) // Should not crash
  474. }
  475. })
  476. })
  477. describe('Memory Performance During Reloads', () => {
  478. test('should not leak memory during repeated reloads', async () => {
  479. const initialMemory = process.memoryUsage()
  480. // Perform many configuration reloads
  481. for (let i = 0; i < 20; i++) {
  482. const config = {
  483. accounts: [
  484. {
  485. id: `memory-test-${i}`,
  486. name: `Memory Test ${i}`,
  487. platform: Platform.PACIFICA,
  488. enabled: true,
  489. credentials: {
  490. type: 'ed25519' as const,
  491. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  492. }
  493. }
  494. ],
  495. hedging: {
  496. platforms: {
  497. [Platform.PACIFICA]: {
  498. enabled: true,
  499. primaryAccounts: [`memory-test-${i}`],
  500. backupAccounts: [],
  501. loadBalanceStrategy: 'round-robin',
  502. healthCheckInterval: 30000,
  503. failoverThreshold: 3
  504. }
  505. },
  506. hedging: {
  507. enableCrossplatformBalancing: false,
  508. maxAccountsPerPlatform: 5,
  509. reservationTimeoutMs: 60000
  510. }
  511. }
  512. }
  513. await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
  514. await new Promise(resolve => setTimeout(resolve, 60)) // Wait for reload
  515. }
  516. // Force garbage collection if available
  517. if (global.gc) {
  518. global.gc()
  519. await new Promise(resolve => setTimeout(resolve, 100))
  520. }
  521. const finalMemory = process.memoryUsage()
  522. // Memory increase should be reasonable (less than 5MB)
  523. const heapIncrease = finalMemory.heapUsed - initialMemory.heapUsed
  524. expect(heapIncrease).toBeLessThan(5 * 1024 * 1024) // 5MB
  525. })
  526. test('should maintain performance under memory pressure', async () => {
  527. // Create some memory pressure
  528. const memoryPressure: string[] = []
  529. for (let i = 0; i < 1000; i++) {
  530. memoryPressure.push('x'.repeat(1000)) // 1MB total
  531. }
  532. const config = {
  533. accounts: [
  534. {
  535. id: 'pressure-test-account',
  536. name: 'Pressure Test Account',
  537. platform: Platform.PACIFICA,
  538. enabled: true,
  539. credentials: {
  540. type: 'ed25519' as const,
  541. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  542. }
  543. }
  544. ],
  545. hedging: {
  546. platforms: {
  547. [Platform.PACIFICA]: {
  548. enabled: true,
  549. primaryAccounts: ['pressure-test-account'],
  550. backupAccounts: [],
  551. loadBalanceStrategy: 'round-robin',
  552. healthCheckInterval: 30000,
  553. failoverThreshold: 3
  554. }
  555. },
  556. hedging: {
  557. enableCrossplatformBalancing: false,
  558. maxAccountsPerPlatform: 5,
  559. reservationTimeoutMs: 60000
  560. }
  561. }
  562. }
  563. const startTime = performance.now()
  564. await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2))
  565. // Wait for reload
  566. let reloaded = false
  567. const maxWaitTime = 150 // Allow extra time under pressure
  568. for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) {
  569. await new Promise(resolve => setTimeout(resolve, 10))
  570. const account = credentialManager.getAccount('pressure-test-account')
  571. if (account) {
  572. reloaded = true
  573. break
  574. }
  575. }
  576. const reloadTime = performance.now() - startTime
  577. expect(reloaded).toBe(true)
  578. expect(reloadTime).toBeLessThan(150) // Should still meet requirements under pressure
  579. // Clean up memory pressure
  580. memoryPressure.length = 0
  581. })
  582. })
  583. })