storj/satellite/metainfo/version_collector.go
Bill Thorp cf03209c16 satellite/metainfo: only collect metric "other" once per user agent
A user-agent string can contain multiple "products", in the case of
Gateway-MT at least this includes the HTTP client's full user agent.
This means that "other" is often logged even when we know the Storj
product, and sometimes logged more than once per call to "collect".

This makes sure that "other" is only logged if a product isn't
identified, and only logged once.

Change-Id: I8536f7eb32877e36fec97dab7b8d477ccb10f92e
2022-02-09 18:18:55 +00:00

106 lines
2.6 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package metainfo
import (
"fmt"
"strings"
"github.com/blang/semver"
"github.com/spacemonkeygo/monkit/v3"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/common/useragent"
)
const uplinkProduct = "uplink"
var knownUserAgents = []string{
"rclone", "gateway-st", "gateway-mt", "linksharing", "uplink-cli", "transfer-sh", "filezilla", "duplicati",
"comet", "orbiter",
}
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) error {
if len(useragentRaw) == 0 {
return nil
}
entries, err := useragent.ParseEntries(useragentRaw)
if err != nil {
return errs.New("invalid user agent %q: %v", string(useragentRaw), err)
}
foundProduct := false
for _, entry := range entries {
if strings.EqualFold(entry.Product, uplinkProduct) {
vo := versionOccurrence{
Product: entry.Product,
Version: entry.Version,
Method: method,
}
vc.sendUplinkMetric(vo)
} else if knownUserAgent(entry.Product) {
// for known user agents monitor only product
mon.Meter("user_agents", monkit.NewSeriesTag("user_agent", strings.ToLower(entry.Product))).Mark(1)
foundProduct = true
}
}
if !foundProduct { // lets keep also general value for other user agents
mon.Meter("user_agents", monkit.NewSeriesTag("user_agent", "other")).Mark(1)
}
return nil
}
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 knownUserAgent(userAgent string) bool {
for _, knownUserAgent := range knownUserAgents {
if strings.EqualFold(userAgent, knownUserAgent) {
return true
}
}
return false
}