1
+ #!/usr/bin/env tsx
2
+
3
+ import { RIVET_ENDPOINT , createActor , destroyActor } from "./utils" ;
4
+
5
+ const PARALLEL_WORKERS = 20 ;
6
+ const TEST_DURATION_MS = 15000 ;
7
+
8
+ let activeActors : Set < string > = new Set ( ) ;
9
+ let shouldExit = false ;
10
+ let completedLoops = 0 ;
11
+ let totalLoopDuration = 0 ;
12
+ let startTime = Date . now ( ) ;
13
+
14
+ async function actorLoop ( workerId : number ) : Promise < void > {
15
+ while ( ! shouldExit ) {
16
+ const loopStart = Date . now ( ) ;
17
+ let actorId : string | undefined ;
18
+
19
+ try {
20
+ const actorResponse = await createActor ( "default" , "test-runner" ) ;
21
+ actorId = actorResponse . actor . actor_id ;
22
+ activeActors . add ( actorId ) ;
23
+
24
+ const actorPingResponse = await fetch ( `${ RIVET_ENDPOINT } /ping` , {
25
+ method : "GET" ,
26
+ headers : {
27
+ "X-Rivet-Target" : "actor" ,
28
+ "X-Rivet-Actor" : actorId ,
29
+ } ,
30
+ } ) ;
31
+
32
+ if ( ! actorPingResponse . ok ) {
33
+ throw new Error ( `Ping failed: ${ actorPingResponse . status } ` ) ;
34
+ }
35
+
36
+ await destroyActor ( "default" , actorId ) ;
37
+ activeActors . delete ( actorId ) ;
38
+
39
+ const loopDuration = Date . now ( ) - loopStart ;
40
+ completedLoops ++ ;
41
+ totalLoopDuration += loopDuration ;
42
+
43
+ } catch ( error ) {
44
+ console . error ( `Worker ${ workerId } error:` , error ) ;
45
+ if ( actorId ) {
46
+ try {
47
+ await destroyActor ( "default" , actorId ) ;
48
+ activeActors . delete ( actorId ) ;
49
+ } catch { }
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ function printProgress ( ) {
56
+ const elapsed = ( Date . now ( ) - startTime ) / 1000 ;
57
+ const actorsPerSecond = completedLoops / elapsed ;
58
+ const avgLoopDuration = completedLoops > 0 ? totalLoopDuration / completedLoops : 0 ;
59
+
60
+ process . stdout . write (
61
+ `\rElapsed: ${ elapsed . toFixed ( 1 ) } s | ` +
62
+ `Completed: ${ completedLoops } | ` +
63
+ `Actors/sec: ${ actorsPerSecond . toFixed ( 2 ) } | ` +
64
+ `Avg loop: ${ avgLoopDuration . toFixed ( 0 ) } ms | ` +
65
+ `Active: ${ activeActors . size } `
66
+ ) ;
67
+ }
68
+
69
+ async function cleanup ( ) {
70
+ console . log ( "\n\nCleaning up active actors..." ) ;
71
+ shouldExit = true ;
72
+
73
+ const cleanupPromises = Array . from ( activeActors ) . map ( async ( actorId ) => {
74
+ try {
75
+ await destroyActor ( "default" , actorId ) ;
76
+ console . log ( `Cleaned up actor: ${ actorId } ` ) ;
77
+ } catch ( error ) {
78
+ console . error ( `Failed to cleanup actor ${ actorId } :` , error ) ;
79
+ }
80
+ } ) ;
81
+
82
+ await Promise . all ( cleanupPromises ) ;
83
+ console . log ( "Cleanup complete" ) ;
84
+ }
85
+
86
+ async function main ( ) {
87
+ console . log ( `Starting actor stress test...` ) ;
88
+ console . log ( `Running ${ PARALLEL_WORKERS } workers for ${ TEST_DURATION_MS / 1000 } seconds\n` ) ;
89
+
90
+ process . on ( "SIGINT" , async ( ) => {
91
+ console . log ( "\nReceived SIGINT" ) ;
92
+ await cleanup ( ) ;
93
+ process . exit ( 0 ) ;
94
+ } ) ;
95
+
96
+ process . on ( "SIGTERM" , async ( ) => {
97
+ console . log ( "\nReceived SIGTERM" ) ;
98
+ await cleanup ( ) ;
99
+ process . exit ( 0 ) ;
100
+ } ) ;
101
+
102
+ const progressInterval = setInterval ( printProgress , 100 ) ;
103
+
104
+ const workers = Array . from ( { length : PARALLEL_WORKERS } , ( _ , i ) => actorLoop ( i ) ) ;
105
+
106
+ setTimeout ( async ( ) => {
107
+ shouldExit = true ;
108
+ clearInterval ( progressInterval ) ;
109
+ printProgress ( ) ;
110
+ console . log ( "\n\nTest duration complete, waiting for workers to finish..." ) ;
111
+ await Promise . all ( workers ) ;
112
+
113
+ const elapsed = ( Date . now ( ) - startTime ) / 1000 ;
114
+ console . log ( "\n=== Final Results ===" ) ;
115
+ console . log ( `Total runtime: ${ elapsed . toFixed ( 2 ) } s` ) ;
116
+ console . log ( `Total completed loops: ${ completedLoops } ` ) ;
117
+ console . log ( `Average actors/second: ${ ( completedLoops / elapsed ) . toFixed ( 2 ) } ` ) ;
118
+ console . log ( `Average loop duration: ${ ( totalLoopDuration / completedLoops ) . toFixed ( 0 ) } ms` ) ;
119
+
120
+ process . exit ( 0 ) ;
121
+ } , TEST_DURATION_MS ) ;
122
+
123
+ await Promise . all ( workers ) ;
124
+ }
125
+
126
+ main ( ) . catch ( ( error ) => {
127
+ console . error ( "Fatal error:" , error ) ;
128
+ cleanup ( ) . then ( ( ) => process . exit ( 1 ) ) ;
129
+ } ) ;
0 commit comments