-
Notifications
You must be signed in to change notification settings - Fork 387
/
version_collector.go
137 lines (114 loc) · 4.38 KB
/
version_collector.go
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package metainfo
import (
"fmt"
"sort"
"strings"
"github.com/blang/semver"
"github.com/spacemonkeygo/monkit/v3"
"go.uber.org/zap"
"storj.io/common/useragent"
)
const uplinkProduct = "uplink"
type transfer string
const upload = transfer("upload")
const download = transfer("download")
var knownUserAgents = []string{
"rclone", "gateway-st", "gateway-mt", "linksharing", "uplink-cli", "transfer-sh", "filezilla", "duplicati",
"comet", "orbiter", "uplink-php", "nextcloud", "aws-cli", "ipfs-go-ds-storj",
}
type versionOccurrence struct {
Product string
Version string
Method string
}
type versionCollector struct {
log *zap.Logger
}
func newVersionCollector(log *zap.Logger) *versionCollector {
return &versionCollector{
log: log,
}
}
func (vc *versionCollector) collect(useragentRaw []byte, method string) {
if len(useragentRaw) == 0 {
return
}
entries, err := useragent.ParseEntries(useragentRaw)
if err != nil {
vc.log.Warn("unable to collect uplink version", zap.Error(err))
mon.Meter("user_agents", monkit.NewSeriesTag("user_agent", "unparseable")).Mark(1)
return
}
// foundProducts tracks potentially multiple noteworthy products names from the user-agent
var foundProducts []string
for _, entry := range entries {
product := strings.ToLower(entry.Product)
if product == uplinkProduct {
vo := versionOccurrence{Product: product, Version: entry.Version, Method: method}
vc.sendUplinkMetric(vo)
} else if contains(knownUserAgents, product) && !contains(foundProducts, product) {
foundProducts = append(foundProducts, product)
}
}
if len(foundProducts) > 0 {
sort.Strings(foundProducts)
// concatenate all known products for this metric, EG "gateway-mt + rclone"
mon.Meter("user_agents", monkit.NewSeriesTag("user_agent", strings.Join(foundProducts, " + "))).Mark(1)
} else { // lets keep also general value for user agents with no known product
mon.Meter("user_agents", monkit.NewSeriesTag("user_agent", "other")).Mark(1)
}
}
func (vc *versionCollector) sendUplinkMetric(vo versionOccurrence) {
if vo.Version == "" {
vo.Version = "unknown"
} else {
// use only minor to avoid using too many resources and
// minimize risk of abusing by sending lots of different versions
semVer, err := semver.ParseTolerant(vo.Version)
if err != nil {
vc.log.Warn("invalid uplink library user agent version", zap.String("version", vo.Version), zap.Error(err))
return
}
// keep number of possible versions very limited
if semVer.Major != 1 || semVer.Minor > 30 {
vc.log.Warn("invalid uplink library user agent version", zap.String("version", vo.Version), zap.Error(err))
return
}
vo.Version = fmt.Sprintf("v%d.%d", 1, semVer.Minor)
}
mon.Meter("uplink_versions", monkit.NewSeriesTag("version", vo.Version), monkit.NewSeriesTag("method", vo.Method)).Mark(1)
}
func (vc *versionCollector) collectTransferStats(useragentRaw []byte, transfer transfer, transferSize int) {
entries, err := useragent.ParseEntries(useragentRaw)
if err != nil {
vc.log.Warn("unable to collect transfer statistics", zap.Error(err))
mon.Meter("user_agents_transfer_stats", monkit.NewSeriesTag("user_agent", "unparseable"), monkit.NewSeriesTag("type", string(transfer))).Mark(transferSize)
return
}
// foundProducts tracks potentially multiple noteworthy products names from the user-agent
var foundProducts []string
for _, entry := range entries {
product := strings.ToLower(entry.Product)
if contains(knownUserAgents, product) && !contains(foundProducts, product) {
foundProducts = append(foundProducts, product)
}
}
if len(foundProducts) > 0 {
sort.Strings(foundProducts)
// concatenate all known products for this metric, EG "gateway-mt + rclone"
mon.Meter("user_agents_transfer_stats", monkit.NewSeriesTag("user_agent", strings.Join(foundProducts, " + ")), monkit.NewSeriesTag("type", string(transfer))).Mark(transferSize)
} else { // lets keep also general value for user agents with no known product
mon.Meter("user_agents_transfer_stats", monkit.NewSeriesTag("user_agent", "other"), monkit.NewSeriesTag("type", string(transfer))).Mark(transferSize)
}
}
// contains returns true if the given string is contained in the given slice.
func contains(slice []string, testValue string) bool {
for _, sliceValue := range slice {
if sliceValue == testValue {
return true
}
}
return false
}