diff --git a/cmd/statreceiver/downgrade.go b/cmd/statreceiver/downgrade.go new file mode 100644 index 000000000..9c5d59020 --- /dev/null +++ b/cmd/statreceiver/downgrade.go @@ -0,0 +1,220 @@ +// 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 +} + +// 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]) + } +} + +var knownMetrics = map[string]string{ + "total.bytes": "storj.io/storj/satellite/accounting/tally.total.bytes", + "total.inline_bytes": "storj.io/storj/satellite/accounting/tally.total.inline_bytes", + "total.inline_segments": "storj.io/storj/satellite/accounting/tally.total.inline_segments", + "total.objects": "storj.io/storj/satellite/accounting/tally.total.objects", + "total.remote_bytes": "storj.io/storj/satellite/accounting/tally.total.remote_bytes", + "total.remote_segments": "storj.io/storj/satellite/accounting/tally.total.remote_segments", + "total.segments": "storj.io/storj/satellite/accounting/tally.total.segments", + "audit_contained_nodes": "storj.io/storj/satellite/audit.audit_contained_nodes", + "audit_contained_nodes_global": "storj.io/storj/satellite/audit.audit_contained_nodes_global", + "audit_contained_percentage": "storj.io/storj/satellite/audit.audit_contained_percentage", + "audit_fail_nodes": "storj.io/storj/satellite/audit.audit_fail_nodes", + "audit_fail_nodes_global": "storj.io/storj/satellite/audit.audit_fail_nodes_global", + "audit_failed_percentage": "storj.io/storj/satellite/audit.audit_failed_percentage", + "audit_offline_nodes": "storj.io/storj/satellite/audit.audit_offline_nodes", + "audit_offline_nodes_global": "storj.io/storj/satellite/audit.audit_offline_nodes_global", + "audit_offline_percentage": "storj.io/storj/satellite/audit.audit_offline_percentage", + "audit_success_nodes": "storj.io/storj/satellite/audit.audit_success_nodes", + "audit_success_nodes_global": "storj.io/storj/satellite/audit.audit_success_nodes_global", + "audit_successful_percentage": "storj.io/storj/satellite/audit.audit_successful_percentage", + "audit_total_nodes": "storj.io/storj/satellite/audit.audit_total_nodes", + "audit_total_nodes_global": "storj.io/storj/satellite/audit.audit_total_nodes_global", + "audit_total_pointer_nodes": "storj.io/storj/satellite/audit.audit_total_pointer_nodes", + "audit_total_pointer_nodes_global": "storj.io/storj/satellite/audit.audit_total_pointer_nodes_global", + "audit_unknown_nodes": "storj.io/storj/satellite/audit.audit_unknown_nodes", + "audit_unknown_nodes_global": "storj.io/storj/satellite/audit.audit_unknown_nodes_global", + "audit_unknown_percentage": "storj.io/storj/satellite/audit.audit_unknown_percentage", + "audited_percentage": "storj.io/storj/satellite/audit.audited_percentage", + "reverify_contained": "storj.io/storj/satellite/audit.reverify_contained", + "reverify_contained_global": "storj.io/storj/satellite/audit.reverify_contained_global", + "reverify_contained_in_segment": "storj.io/storj/satellite/audit.reverify_contained_in_segment", + "reverify_fails": "storj.io/storj/satellite/audit.reverify_fails", + "reverify_fails_global": "storj.io/storj/satellite/audit.reverify_fails_global", + "reverify_offlines": "storj.io/storj/satellite/audit.reverify_offlines", + "reverify_offlines_global": "storj.io/storj/satellite/audit.reverify_offlines_global", + "reverify_successes": "storj.io/storj/satellite/audit.reverify_successes", + "reverify_successes_global": "storj.io/storj/satellite/audit.reverify_successes_global", + "reverify_total_in_segment": "storj.io/storj/satellite/audit.reverify_total_in_segment", + "reverify_unknown": "storj.io/storj/satellite/audit.reverify_unknown", + "reverify_unknown_global": "storj.io/storj/satellite/audit.reverify_unknown_global", + "graceful_exit_fail_max_failures_percentage": "storj.io/storj/satellite/gracefulexit.graceful_exit_fail_max_failures_percentage", + "graceful_exit_fail_validation": "storj.io/storj/satellite/gracefulexit.graceful_exit_fail_validation", + "graceful_exit_final_bytes_transferred": "storj.io/storj/satellite/gracefulexit.graceful_exit_final_bytes_transferred", + "graceful_exit_final_pieces_failed": "storj.io/storj/satellite/gracefulexit.graceful_exit_final_pieces_failed", + "graceful_exit_final_pieces_succeess": "storj.io/storj/satellite/gracefulexit.graceful_exit_final_pieces_succeess", + "graceful_exit_init_node_age_seconds": "storj.io/storj/satellite/gracefulexit.graceful_exit_init_node_age_seconds", + "graceful_exit_init_node_audit_success_count": "storj.io/storj/satellite/gracefulexit.graceful_exit_init_node_audit_success_count", + "graceful_exit_init_node_audit_total_count": "storj.io/storj/satellite/gracefulexit.graceful_exit_init_node_audit_total_count", + "graceful_exit_init_node_piece_count": "storj.io/storj/satellite/gracefulexit.graceful_exit_init_node_piece_count", + "graceful_exit_success": "storj.io/storj/satellite/gracefulexit.graceful_exit_success", + "graceful_exit_successful_pieces_transfer_ratio": "storj.io/storj/satellite/gracefulexit.graceful_exit_successful_pieces_transfer_ratio", + "graceful_exit_transfer_piece_fail": "storj.io/storj/satellite/gracefulexit.graceful_exit_transfer_piece_fail", + "graceful_exit_transfer_piece_success": "storj.io/storj/satellite/gracefulexit.graceful_exit_transfer_piece_success", + "download_failed_not_enough_pieces_uplink": "storj.io/storj/satellite/orders.download_failed_not_enough_pieces_uplink", + "checker_segment_age": "storj.io/storj/satellite/repair/checker.checker_segment_age", + "checker_segment_healthy_count": "storj.io/storj/satellite/repair/checker.checker_segment_healthy_count", + "checker_segment_time_until_irreparable": "storj.io/storj/satellite/repair/checker.checker_segment_time_until_irreparable", + "checker_segment_total_count": "storj.io/storj/satellite/repair/checker.checker_segment_total_count", + "remote_files_checked": "storj.io/storj/satellite/repair/checker.remote_files_checked", + "remote_files_lost": "storj.io/storj/satellite/repair/checker.remote_files_lost", + "remote_segments_checked": "storj.io/storj/satellite/repair/checker.remote_segments_checked", + "remote_segments_lost": "storj.io/storj/satellite/repair/checker.remote_segments_lost", + "remote_segments_needing_repair": "storj.io/storj/satellite/repair/checker.remote_segments_needing_repair", + "download_failed_not_enough_pieces_repair": "storj.io/storj/satellite/repair/repairer.download_failed_not_enough_pieces_repair", + "audit_reputation_alpha": "storj.io/storj/satellite/satellitedb.audit_reputation_alpha", + "audit_reputation_beta": "storj.io/storj/satellite/satellitedb.audit_reputation_beta", + "open_file_in_trash": "storj.io/storj/storage/filestore.open_file_in_trash", + "satellite_contact_request": "storj.io/storj/storagenode/contact.satellite_contact_request", + "satellite_gracefulexit_request": "storj.io/storj/storagenode/gracefulexit.satellite_gracefulexit_request", + "allocated_bandwidth": "storj.io/storj/storagenode/monitor.allocated_bandwidth", + "used_bandwidth": "storj.io/storj/storagenode/monitor.used_bandwidth", + "download_stripe_failed_not_enough_pieces_uplink": "storj.io/storj/uplink/eestream.download_stripe_failed_not_enough_pieces_uplink", +} diff --git a/cmd/statreceiver/main.go b/cmd/statreceiver/main.go index 024fc936d..bb3007906 100644 --- a/cmd/statreceiver/main.go +++ b/cmd/statreceiver/main.go @@ -82,6 +82,8 @@ func Main(cmd *cobra.Command, args []string) error { scope.RegisterVal("db", NewDBDest), scope.RegisterVal("pbufprep", NewPacketBufPrep), scope.RegisterVal("mbufprep", NewMetricBufPrep), + scope.RegisterVal("downgrade", NewMetricDowngrade), + scope.RegisterVal("versionsplit", NewVersionSplit), ) if err != nil { return err diff --git a/cmd/statreceiver/version.go b/cmd/statreceiver/version.go new file mode 100644 index 000000000..4a00553e8 --- /dev/null +++ b/cmd/statreceiver/version.go @@ -0,0 +1,33 @@ +// Copyright (C) 2020 Storj Labs, Inc. +// See LICENSE for copying information. + +package main + +import ( + "bytes" + "time" +) + +// VersionSplit downgrades known v3 metrics into v2 versions for backwards compat. +type VersionSplit struct { + v2dest MetricDest + v3dest MetricDest +} + +// NewVersionSplit constructs a VersionSplit that passes known v3 metrics as +// v2 metrics to the provided dest. +func NewVersionSplit(v2dest, v3dest MetricDest) *VersionSplit { + return &VersionSplit{ + v2dest: v2dest, + v3dest: v3dest, + } +} + +// Metric implements MetricDest +func (k *VersionSplit) Metric(application, instance string, key []byte, val float64, ts time.Time) error { + comma := bytes.IndexByte(key, ',') + if comma < 0 { + return k.v2dest.Metric(application, instance, key, val, ts) + } + return k.v3dest.Metric(application, instance, key, val, ts) +}