@@ -3,6 +3,7 @@ package codenames
3
3
import (
4
4
"crypto/subtle"
5
5
"encoding/json"
6
+ "fmt"
6
7
"html/template"
7
8
"io"
8
9
"log"
@@ -31,18 +32,22 @@ type Server struct {
31
32
32
33
tpl * template.Template
33
34
gameIDWords []string
35
+ hooks Hooks
34
36
35
37
mu sync.Mutex
36
38
games map [string ]* GameHandle
37
39
defaultWords []string
38
40
mux * http.ServeMux
39
41
40
- statOpenRequests int64 // atomic access
41
- statTotalRequests int64 //atomic access
42
+ statGamesCompleted int64 // atomic access
43
+ statOpenRequests int64 // atomic access
44
+ statTotalRequests int64 // atomic access
42
45
}
43
46
44
47
type Store interface {
45
48
Save (* Game ) error
49
+ CounterAdd (string , int64 ) error
50
+ GetCounter (statPrefix string ) (int64 , error )
46
51
}
47
52
48
53
type GameHandle struct {
@@ -131,7 +136,7 @@ func (s *Server) getGameLocked(gameID string) (*GameHandle, bool) {
131
136
if ok {
132
137
return gh , ok
133
138
}
134
- gh = newHandle (newGame (gameID , randomState (s .defaultWords ), GameOptions {}), s .Store )
139
+ gh = newHandle (newGame (gameID , randomState (s .defaultWords ), GameOptions {Hooks : s . hooks }), s .Store )
135
140
s .games [gameID ] = gh
136
141
return gh , true
137
142
}
@@ -151,7 +156,7 @@ func (s *Server) handleGameState(rw http.ResponseWriter, req *http.Request) {
151
156
s .mu .Lock ()
152
157
gh , ok := s .getGameLocked (body .GameID )
153
158
if ! ok {
154
- gh = newHandle (newGame (body .GameID , randomState (s .defaultWords ), GameOptions {}), s .Store )
159
+ gh = newHandle (newGame (body .GameID , randomState (s .defaultWords ), GameOptions {Hooks : s . hooks }), s .Store )
155
160
s .games [body .GameID ] = gh
156
161
s .mu .Unlock ()
157
162
writeGame (rw , gh )
@@ -273,6 +278,7 @@ func (s *Server) handleNextGame(rw http.ResponseWriter, req *http.Request) {
273
278
opts := GameOptions {
274
279
TimerDurationMS : request .TimerDurationMS ,
275
280
EnforceTimer : request .EnforceTimer ,
281
+ Hooks : s .hooks ,
276
282
}
277
283
278
284
var ok bool
@@ -297,19 +303,18 @@ func (s *Server) handleNextGame(rw http.ResponseWriter, req *http.Request) {
297
303
}
298
304
299
305
type statsResponse struct {
300
- GamesTotal int `json:"games_total"`
301
- GamesInProgress int `json:"games_in_progress"`
302
- GamesCreatedOneHour int `json:"games_created_1h"`
303
- RequestsTotal int64 `json:"requests_total_process_lifetime"`
304
- RequestsInFlight int64 `json:"requests_in_flight"`
306
+ GamesCompleted int64 `json:"games_completed"`
307
+ MemGamesTotal int `json:"mem_games_total"`
308
+ MemGamesInProgress int `json:"mem_games_in_progress"`
309
+ MemGamesCreatedOneHour int `json:"mem_games_created_1h"`
310
+ RequestsTotal int64 `json:"requests_total_process_lifetime"`
311
+ RequestsInFlight int64 `json:"requests_in_flight"`
305
312
}
306
313
307
314
func (s * Server ) handleStats (rw http.ResponseWriter , req * http.Request ) {
308
315
hourAgo := time .Now ().Add (- time .Hour )
309
316
310
317
s .mu .Lock ()
311
- defer s .mu .Unlock ()
312
-
313
318
var inProgress , createdWithinAnHour int
314
319
for _ , gh := range s .games {
315
320
gh .mu .Lock ()
@@ -321,12 +326,23 @@ func (s *Server) handleStats(rw http.ResponseWriter, req *http.Request) {
321
326
}
322
327
gh .mu .Unlock ()
323
328
}
329
+ s .mu .Unlock ()
330
+
331
+ // Sum up the count of games completed that's on disk and in-memory.
332
+ diskGamesCompleted , err := s .Store .GetCounter ("games/completed/" )
333
+ if err != nil {
334
+ http .Error (rw , err .Error (), 400 )
335
+ return
336
+ }
337
+ memGamesCompleted := atomic .LoadInt64 (& s .statGamesCompleted )
338
+
324
339
writeJSON (rw , statsResponse {
325
- GamesTotal : len (s .games ),
326
- GamesInProgress : inProgress ,
327
- GamesCreatedOneHour : createdWithinAnHour ,
328
- RequestsTotal : atomic .LoadInt64 (& s .statTotalRequests ),
329
- RequestsInFlight : atomic .LoadInt64 (& s .statOpenRequests ),
340
+ GamesCompleted : diskGamesCompleted + memGamesCompleted ,
341
+ MemGamesTotal : len (s .games ),
342
+ MemGamesInProgress : inProgress ,
343
+ MemGamesCreatedOneHour : createdWithinAnHour ,
344
+ RequestsTotal : atomic .LoadInt64 (& s .statTotalRequests ),
345
+ RequestsInFlight : atomic .LoadInt64 (& s .statOpenRequests ),
330
346
})
331
347
}
332
348
@@ -384,8 +400,11 @@ func (s *Server) Start(games map[string]*Game) error {
384
400
s .Store = discardStore {}
385
401
}
386
402
403
+ s .hooks .Complete = func () { atomic .AddInt64 (& s .statGamesCompleted , 1 ) }
404
+
387
405
if games != nil {
388
406
for _ , g := range games {
407
+ g .GameOptions .Hooks = s .hooks
389
408
s .games [g .ID ] = newHandle (g , s .Store )
390
409
}
391
410
}
@@ -396,6 +415,19 @@ func (s *Server) Start(games map[string]*Game) error {
396
415
}
397
416
}()
398
417
418
+ // Periodically persist some in-memory stats.
419
+ go func () {
420
+ const hourFormat = "06010215"
421
+ for range time .Tick (time .Minute ) {
422
+ hourKey := time .Now ().UTC ().Format (hourFormat ) + "utc"
423
+ v := atomic .LoadInt64 (& s .statGamesCompleted )
424
+ if v > 0 {
425
+ atomic .AddInt64 (& s .statGamesCompleted , - v )
426
+ s .Store .CounterAdd (fmt .Sprintf ("games/completed/%s" , hourKey ), v )
427
+ }
428
+ }
429
+ }()
430
+
399
431
return s .Server .ListenAndServe ()
400
432
}
401
433
0 commit comments