metrics.go 3.01 KB
Newer Older
Hooman's avatar
Hooman committed
1
package main
2
3
4

import (
	// "golang.org/x/net/internal/timeseries"
5
6
	"fmt"
	"log"
7
	"math"
8
9
	"net"
	"sync"
10
11
12
	"time"
)

13
14
15
16
var (
	once sync.Once
)

17
const metricsResolution = 86400 * time.Second
18

19
type CountryStats struct {
20
	ips    map[string]bool
21
22
23
	counts map[string]int
}

24
25
// Implements Observable
type Metrics struct {
26
27
28
29
30
	logger  *log.Logger
	tablev4 *GeoIPv4Table
	tablev6 *GeoIPv6Table

	countryStats            CountryStats
31
	clientRoundtripEstimate time.Duration
32
33
34
	proxyIdleCount          int
	clientDeniedCount       int
	clientProxyMatchCount   int
35
36
}

37
func (s CountryStats) Display() string {
38
39
40
41
42
	output := ""
	for cc, count := range s.counts {
		output += fmt.Sprintf("%s=%d,", cc, count)
	}
	return output
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
}

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

	var country string
	var ok bool

	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")
	}

69
70
71
72
73
	//update map of unique ips and counts
	if !m.countryStats.ips[addr] {
		m.countryStats.counts[country]++
		m.countryStats.ips[addr] = true
	}
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

	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
}

103
func NewMetrics(metricsLogger *log.Logger) (*Metrics, error) {
104
	m := new(Metrics)
105
106
107

	m.countryStats = CountryStats{
		counts: make(map[string]int),
108
		ips:    make(map[string]bool),
109
110
	}

111
112
	m.logger = metricsLogger

113
	// Write to log file every hour with updated metrics
114
115
116
117
	go once.Do(m.logMetrics)

	return m, nil
}
118

Cecylia Bocovich's avatar
Cecylia Bocovich committed
119
// Logs metrics in intervals specified by metricsResolution
120
121
122
func (m *Metrics) logMetrics() {
	heartbeat := time.Tick(metricsResolution)
	for range heartbeat {
Cecylia Bocovich's avatar
Cecylia Bocovich committed
123
124
		m.printMetrics()
		m.zeroMetrics()
125
	}
126
}
127

Cecylia Bocovich's avatar
Cecylia Bocovich committed
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
func (m *Metrics) printMetrics() {
	m.logger.Println("snowflake-stats-end", time.Now().UTC().Format("2006-01-02 15:04:05"), "(", int(metricsResolution.Seconds()), "s)")
	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)
	m.countryStats.ips = make(map[string]bool)
}

145
146
147
148
// Rounds up a count to the nearest multiple of 8.
func binCount(count int) int {
	return int((math.Ceil(float64(count) / 8)) * 8)
}