metrics.go 4.24 KB
Newer Older
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
/*
We export metrics in the following format:

    "snowflake-stats-end" YYYY-MM-DD HH:MM:SS (NSEC s) NL
        [At most once.]

        YYYY-MM-DD HH:MM:SS defines the end of the included measurement
        interval of length NSEC seconds (86400 seconds by default).

    "snowflake-ips" CC=NUM,CC=NUM,... NL
        [At most once.]

        List of mappings from two-letter country codes to the number of
        unique IP addresses of snowflake proxies that have polled.

    "snowflake-idle-count" NUM NL
        [At most once.]

        A count of the number of times a proxy has polled but received
        no client offer, rounded up to the nearest multiple of 8.

    "client-denied-count" NUM NL
        [At most once.]

        A count of the number of times a client has requested a proxy
        from the broker but no proxies were available, rounded up to
        the nearest multiple of 8.

    "client-snowflake-match-count" NUM NL
        [At most once.]

        A count of the number of times a client successfully received a
        proxy from the broker, rounded up to the nearest multiple of 8.
*/

Hooman's avatar
Hooman committed
36
package main
37
38
39

import (
	// "golang.org/x/net/internal/timeseries"
40
41
	"fmt"
	"log"
42
	"math"
43
44
	"net"
	"sync"
45
46
47
	"time"
)

48
49
50
51
var (
	once sync.Once
)

52
const metricsResolution = 60 * 60 * 24 * time.Second //86400 seconds
53

54
type CountryStats struct {
55
	addrs  map[string]bool
56
57
58
	counts map[string]int
}

59
60
// Implements Observable
type Metrics struct {
61
62
63
64
65
	logger  *log.Logger
	tablev4 *GeoIPv4Table
	tablev6 *GeoIPv6Table

	countryStats            CountryStats
66
	clientRoundtripEstimate time.Duration
67
68
69
	proxyIdleCount          uint
	clientDeniedCount       uint
	clientProxyMatchCount   uint
70
71
}

72
func (s CountryStats) Display() string {
73
74
75
76
	output := ""
	for cc, count := range s.counts {
		output += fmt.Sprintf("%s=%d,", cc, count)
	}
77
78
79
80
81
82

	// cut off trailing ","
	if len(output) > 0 {
		return output[:len(output)-1]
	}

83
	return output
84
85
86
87
88
89
90
}

func (m *Metrics) UpdateCountryStats(addr string) {

	var country string
	var ok bool

91
92
93
94
	if m.countryStats.addrs[addr] {
		return
	}

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
	ip := net.ParseIP(addr)
	if ip.To4() != nil {
		//This is an IPv4 address
		if m.tablev4 == nil {
			return
		}
		country, ok = GetCountryByAddr(m.tablev4, ip)
	} else {
		if m.tablev6 == nil {
			return
		}
		country, ok = GetCountryByAddr(m.tablev6, ip)
	}

	if !ok {
		country = "??"
		log.Println("Unknown geoip")
	}

114
	//update map of unique ips and counts
115
116
	m.countryStats.counts[country]++
	m.countryStats.addrs[addr] = true
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

	return
}

func (m *Metrics) LoadGeoipDatabases(geoipDB string, geoip6DB string) error {

	// Load geoip databases
	log.Println("Loading geoip databases")
	tablev4 := new(GeoIPv4Table)
	err := GeoIPLoadFile(tablev4, geoipDB)
	if err != nil {
		m.tablev4 = nil
		return err
	} else {
		m.tablev4 = tablev4
	}

	tablev6 := new(GeoIPv6Table)
	err = GeoIPLoadFile(tablev6, geoip6DB)
	if err != nil {
		m.tablev6 = nil
		return err
	} else {
		m.tablev6 = tablev6
	}

	return nil
}

146
func NewMetrics(metricsLogger *log.Logger) (*Metrics, error) {
147
	m := new(Metrics)
148
149
150

	m.countryStats = CountryStats{
		counts: make(map[string]int),
151
		addrs:  make(map[string]bool),
152
153
	}

154
155
	m.logger = metricsLogger

156
	// Write to log file every hour with updated metrics
157
158
159
160
	go once.Do(m.logMetrics)

	return m, nil
}
161

Cecylia Bocovich's avatar
Cecylia Bocovich committed
162
// Logs metrics in intervals specified by metricsResolution
163
164
165
func (m *Metrics) logMetrics() {
	heartbeat := time.Tick(metricsResolution)
	for range heartbeat {
Cecylia Bocovich's avatar
Cecylia Bocovich committed
166
167
		m.printMetrics()
		m.zeroMetrics()
168
	}
169
}
170

Cecylia Bocovich's avatar
Cecylia Bocovich committed
171
func (m *Metrics) printMetrics() {
172
	m.logger.Println("snowflake-stats-end", time.Now().UTC().Format("2006-01-02 15:04:05"), fmt.Sprintf("(%d s)", int(metricsResolution.Seconds())))
Cecylia Bocovich's avatar
Cecylia Bocovich committed
173
174
175
176
177
178
179
180
181
182
183
184
	m.logger.Println("snowflake-ips", m.countryStats.Display())
	m.logger.Println("snowflake-idle-count", binCount(m.proxyIdleCount))
	m.logger.Println("client-denied-count", binCount(m.clientDeniedCount))
	m.logger.Println("client-snowflake-match-count", binCount(m.clientProxyMatchCount))
}

// Restores all metrics to original values
func (m *Metrics) zeroMetrics() {
	m.proxyIdleCount = 0
	m.clientDeniedCount = 0
	m.clientProxyMatchCount = 0
	m.countryStats.counts = make(map[string]int)
185
	m.countryStats.addrs = make(map[string]bool)
Cecylia Bocovich's avatar
Cecylia Bocovich committed
186
187
}

188
// Rounds up a count to the nearest multiple of 8.
189
190
func binCount(count uint) uint {
	return uint((math.Ceil(float64(count) / 8)) * 8)
191
}