aboutsummaryrefslogtreecommitdiff
path: root/hotline/stats.go
blob: f272f5b2a0488b8f570e3c59301d1fc8d94a4754 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package hotline

import (
	"sync"
	"time"
)

// StatKey identifies a single stat counter.
type StatKey int

// Stat counter keys. numStats must remain last; it sizes the counter array.
const (
	StatCurrentlyConnected StatKey = iota
	StatDownloadsInProgress
	StatUploadsInProgress
	StatWaitingDownloads
	StatConnectionPeak
	StatConnectionCounter
	StatDownloadCounter
	StatUploadCounter

	numStats
)

type Counter interface {
	Increment(keys ...StatKey)
	Decrement(keys ...StatKey)
	Set(key StatKey, val int)
	Max(key StatKey, val int)
	Get(key StatKey) int
	Values() StatValues
}

// StatValues is a point-in-time snapshot of all counters. Its JSON tags define
// the wire format served at GET /api/v1/stats.
type StatValues struct {
	CurrentlyConnected  int       `json:"CurrentlyConnected"`
	DownloadsInProgress int       `json:"DownloadsInProgress"`
	UploadsInProgress   int       `json:"UploadsInProgress"`
	WaitingDownloads    int       `json:"WaitingDownloads"`
	ConnectionPeak      int       `json:"ConnectionPeak"`
	ConnectionCounter   int       `json:"ConnectionCounter"`
	DownloadCounter     int       `json:"DownloadCounter"`
	UploadCounter       int       `json:"UploadCounter"`
	Since               time.Time `json:"Since"`
}

type Stats struct {
	stats [numStats]int
	since time.Time

	mu sync.RWMutex
}

func NewStats() *Stats {
	return &Stats{since: time.Now()}
}

func (s *Stats) Increment(keys ...StatKey) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for _, key := range keys {
		s.stats[key]++
	}
}

func (s *Stats) Decrement(keys ...StatKey) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for _, key := range keys {
		if s.stats[key] > 0 {
			s.stats[key]--
		}
	}
}

func (s *Stats) Set(key StatKey, val int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.stats[key] = val
}

// Max sets key to val only if val is greater than the current value. The
// compare-and-set happens under a single lock so concurrent callers cannot
// clobber each other's update.
func (s *Stats) Max(key StatKey, val int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	if val > s.stats[key] {
		s.stats[key] = val
	}
}

func (s *Stats) Get(key StatKey) int {
	s.mu.RLock()
	defer s.mu.RUnlock()

	return s.stats[key]
}

func (s *Stats) Values() StatValues {
	s.mu.RLock()
	defer s.mu.RUnlock()

	return StatValues{
		CurrentlyConnected:  s.stats[StatCurrentlyConnected],
		DownloadsInProgress: s.stats[StatDownloadsInProgress],
		UploadsInProgress:   s.stats[StatUploadsInProgress],
		WaitingDownloads:    s.stats[StatWaitingDownloads],
		ConnectionPeak:      s.stats[StatConnectionPeak],
		ConnectionCounter:   s.stats[StatConnectionCounter],
		DownloadCounter:     s.stats[StatDownloadCounter],
		UploadCounter:       s.stats[StatUploadCounter],
		Since:               s.since,
	}
}