go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/cmd/statsd-to-tsmon/converter.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"context"
    19  	"strconv"
    20  
    21  	"go.chromium.org/luci/common/errors"
    22  	"go.chromium.org/luci/common/tsmon/metric"
    23  )
    24  
    25  var (
    26  	// ErrSkipped is returned by ConvertMetric if the metric is not mentioned in
    27  	// the config and thus should not be reported to tsmon.
    28  	ErrSkipped = errors.New("the metric doesn't match the filter")
    29  
    30  	// ErrUnexpectedType is returned by ConvertMetric if statsd metric type
    31  	// doesn't match the metric type specified in the config.
    32  	ErrUnexpectedType = errors.New("the metric has unexpected type")
    33  )
    34  
    35  // ConvertMetric uses the config as a template to construct a tsmon metric point
    36  // from a statsd metric point.
    37  func ConvertMetric(ctx context.Context, cfg *Config, m *StatsdMetric) error {
    38  	rule := cfg.FindMatchingRule(m.Name)
    39  	if rule == nil {
    40  		return ErrSkipped
    41  	}
    42  
    43  	// Now that we really know the value is needed, parse it.
    44  	val, err := strconv.ParseInt(string(m.Value), 10, 64)
    45  	if err != nil {
    46  		return ErrMalformedStatsdLine
    47  	}
    48  
    49  	// Assemble values of metric fields. Some of them come directly from the
    50  	// config rule, and some are picked from the parsed statsd metric name.
    51  	fields := make([]any, len(rule.Fields))
    52  	for i, spec := range rule.Fields {
    53  		switch val := spec.(type) {
    54  		case string:
    55  			fields[i] = val
    56  		case NameComponentIndex:
    57  			fields[i] = string(m.Name[int(val)])
    58  		default:
    59  			panic("impossible")
    60  		}
    61  	}
    62  
    63  	// Send the value to tsmon.
    64  	switch m.Type {
    65  	case StatsdMetricGauge:
    66  		// metric.Counter is a metric.Int too, but we want Int specifically.
    67  		if _, ok := rule.Metric.(metric.Counter); ok {
    68  			return ErrUnexpectedType
    69  		}
    70  		if tm, ok := rule.Metric.(metric.Int); ok {
    71  			tm.Set(ctx, val, fields...)
    72  		} else {
    73  			return ErrUnexpectedType
    74  		}
    75  
    76  	case StatsdMetricCounter:
    77  		if tm, ok := rule.Metric.(metric.Counter); ok {
    78  			tm.Add(ctx, val, fields...)
    79  		} else {
    80  			return ErrUnexpectedType
    81  		}
    82  
    83  	case StatsdMetricTimer:
    84  		if tm, ok := rule.Metric.(metric.CumulativeDistribution); ok {
    85  			tm.Add(ctx, float64(val), fields...)
    86  		} else {
    87  			return ErrUnexpectedType
    88  		}
    89  
    90  	default:
    91  		panic("impossible")
    92  	}
    93  
    94  	return nil
    95  }