netdata/netdata

View on GitHub
src/go/collectors/go.d.plugin/pkg/stm/stm.go

Summary

Maintainability
A
0 mins
Test Coverage
// SPDX-License-Identifier: GPL-3.0-or-later

package stm

import (
    "fmt"
    "log"
    "reflect"
    "strconv"
    "strings"
)

const (
    fieldTagName = "stm"
    structKey    = "STMKey"
)

type (
    Value interface {
        WriteTo(rv map[string]int64, key string, mul, div int)
    }
)

// ToMap converts struct to a map[string]int64 based on 'stm' tags
func ToMap(s ...interface{}) map[string]int64 {
    rv := map[string]int64{}
    for _, v := range s {
        value := reflect.Indirect(reflect.ValueOf(v))
        toMap(value, rv, "", 1, 1)
    }
    return rv
}

func toMap(value reflect.Value, rv map[string]int64, key string, mul, div int) {
    if !value.IsValid() {
        log.Panicf("value is not valid key=%s", key)
    }
    if value.CanInterface() {
        val, ok := value.Interface().(Value)
        if ok {
            val.WriteTo(rv, key, mul, div)
            return
        }
    }
    switch value.Kind() {
    case reflect.Ptr:
        convertPtr(value, rv, key, mul, div)
    case reflect.Struct:
        convertStruct(value, rv, key)
    case reflect.Array, reflect.Slice:
        convertArraySlice(value, rv, key, mul, div)
    case reflect.Map:
        convertMap(value, rv, key, mul, div)
    case reflect.Bool:
        convertBool(value, rv, key)
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        convertInteger(value, rv, key, mul, div)
    case reflect.Float32, reflect.Float64:
        convertFloat(value, rv, key, mul, div)
    case reflect.Interface:
        convertInterface(value, rv, key, mul, div)
    default:
        log.Panicf("unsupported data type: %v", value.Kind())
    }
}

func convertPtr(value reflect.Value, rv map[string]int64, key string, mul, div int) {
    if !value.IsNil() {
        toMap(value.Elem(), rv, key, mul, div)
    }
}

func convertStruct(value reflect.Value, rv map[string]int64, key string) {
    t := value.Type()
    k := value.FieldByName(structKey)
    if k.Kind() == reflect.String {
        key = joinPrefix(key, k.String())
    }
    for i := 0; i < t.NumField(); i++ {
        ft := t.Field(i)
        tag, ok := ft.Tag.Lookup(fieldTagName)
        if !ok || ft.Name == structKey {
            continue
        }
        value := value.Field(i)
        prefix, mul, div := parseTag(tag)
        toMap(value, rv, joinPrefix(key, prefix), mul, div)
    }
}

func convertMap(value reflect.Value, rv map[string]int64, key string, mul, div int) {
    if value.IsNil() {
        log.Panicf("value is nil key=%s", key)
    }
    for _, k := range value.MapKeys() {
        toMap(value.MapIndex(k), rv, joinPrefix(key, k.String()), mul, div)
    }
}

func convertArraySlice(value reflect.Value, rv map[string]int64, key string, mul, div int) {
    for i := 0; i < value.Len(); i++ {
        toMap(value.Index(i), rv, key, mul, div)
    }
}

func convertBool(value reflect.Value, rv map[string]int64, key string) {
    if _, ok := rv[key]; ok {
        log.Panic("duplicate key: ", key)
    }
    if value.Bool() {
        rv[key] = 1
    } else {
        rv[key] = 0
    }
}

func convertInteger(value reflect.Value, rv map[string]int64, key string, mul, div int) {
    if _, ok := rv[key]; ok {
        log.Panic("duplicate key: ", key)
    }
    intVal := value.Int()
    rv[key] = intVal * int64(mul) / int64(div)
}

func convertFloat(value reflect.Value, rv map[string]int64, key string, mul, div int) {
    if _, ok := rv[key]; ok {
        log.Panic("duplicate key: ", key)
    }
    floatVal := value.Float()
    rv[key] = int64(floatVal * float64(mul) / float64(div))
}

func convertInterface(value reflect.Value, rv map[string]int64, key string, mul, div int) {
    fv := reflect.ValueOf(value.Interface())
    toMap(fv, rv, key, mul, div)
}

func joinPrefix(prefix, key string) string {
    if prefix == "" {
        return key
    }
    if key == "" {
        return prefix
    }
    return prefix + "_" + key
}

func parseTag(tag string) (prefix string, mul int, div int) {
    tokens := strings.Split(tag, ",")
    mul = 1
    div = 1
    var err error
    switch len(tokens) {
    case 3:
        div, err = strconv.Atoi(tokens[2])
        if err != nil {
            log.Panic(err)
        }
        fallthrough
    case 2:
        mul, err = strconv.Atoi(tokens[1])
        if err != nil {
            log.Panic(err)
        }
        fallthrough
    case 1:
        prefix = tokens[0]
    default:
        log.Panic(fmt.Errorf("invalid tag format: %s", tag))
    }
    return
}