storj/cmd/statreceiver/downgrade.go

150 lines
3.8 KiB
Go
Raw Normal View History

// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"bytes"
"time"
)
// Note to readers: All of the tag iteration and field searching code does not bother to
// handle escaped spaces or commas because all of the keys we intend to migrate do not
// contain them. In particular, we have no package import paths with spaces or commas and
// it is impossible to have a function name with a space or comma in it. Additionally, all
// of the monkit/v3 field names do not have spaces or commas. This could become invalid if
// someone decides to break it, but this code is also temporary.
// MetricDowngrade downgrades known v3 metrics into v2 versions for backwards compat.
type MetricDowngrade struct {
dest MetricDest
}
// knownMetrics is decoded in main.go using a provided toml configuration file.
var knownMetrics map[string]string
// NewMetricDowngrade constructs a MetricDowngrade that passes known v3 metrics as
// v2 metrics to the provided dest.
func NewMetricDowngrade(dest MetricDest) *MetricDowngrade {
return &MetricDowngrade{
dest: dest,
}
}
// Metric implements MetricDest
func (k *MetricDowngrade) Metric(application, instance string, key []byte, val float64, ts time.Time) error {
comma := bytes.IndexByte(key, ',')
if comma < 0 {
return nil
}
if string(key[:comma]) == "function_times" {
return k.handleFunctionTimes(application, instance, key[comma+1:], val, ts)
}
if string(key[:comma]) == "function" {
return k.handleFunction(application, instance, key[comma+1:], val, ts)
}
v2key, ok := knownMetrics[string(key[:comma])]
if !ok {
return nil
}
space := bytes.LastIndexByte(key, ' ')
if space < 0 {
return nil
}
out := make([]byte, 0, len(v2key)+1+len(key)-space)
out = append(out, v2key...)
out = append(out, '.')
out = append(out, key[space+1:]...)
return k.dest.Metric(application, instance, out, val, ts)
}
func (k *MetricDowngrade) handleFunctionTimes(application, instance string, key []byte, val float64, ts time.Time) error {
var name, kind, scope string
iterateTags(key, func(tag []byte) {
if len(tag) < 6 {
return
}
switch {
case string(tag[:5]) == "name=":
name = string(tag[5:])
case string(tag[:5]) == "kind=":
kind = string(tag[5:])
case string(tag[:6]) == "scope=":
scope = string(tag[6:])
}
})
if name == "" || kind == "" || scope == "" {
return nil
}
space := bytes.LastIndexByte(key, ' ')
if space < 0 {
return nil
}
out := make([]byte, 0, len(scope)+1+len(name)+1+len(kind)+7+(len(key)-space))
out = append(out, scope...)
out = append(out, '.')
out = append(out, name...)
out = append(out, '.')
out = append(out, kind...)
out = append(out, "_times_"...)
out = append(out, key[space+1:]...)
return k.dest.Metric(application, instance, out, val, ts)
}
func (k *MetricDowngrade) handleFunction(application, instance string, key []byte, val float64, ts time.Time) error {
var name, scope string
iterateTags(key, func(tag []byte) {
if len(tag) < 6 {
return
}
switch {
case string(tag[:5]) == "name=":
name = string(tag[5:])
case string(tag[:6]) == "scope=":
scope = string(tag[6:])
}
})
if name == "" || scope == "" {
return nil
}
space := bytes.LastIndexByte(key, ' ')
if space < 0 {
return nil
}
out := make([]byte, 0, len(scope)+1+len(name)+1+(len(key)-space))
out = append(out, scope...)
out = append(out, '.')
out = append(out, name...)
out = append(out, '.')
out = append(out, key[space+1:]...)
return k.dest.Metric(application, instance, out, val, ts)
}
func iterateTags(key []byte, cb func([]byte)) {
for len(key) > 0 {
comma := bytes.IndexByte(key, ',')
if comma == -1 {
break
}
cb(key[:comma])
key = key[comma+1:]
}
space := bytes.IndexByte(key, ' ')
if space >= 0 {
cb(key[:space])
}
}