github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/registry.go (about)

     1  // Copyright 2013 The Prometheus Authors
     2  // Copyright 2021 The Pyroscope Authors
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  // http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package discovery
    17  
    18  import (
    19  	"fmt"
    20  	"reflect"
    21  	"sort"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  
    26  	"gopkg.in/yaml.v2"
    27  
    28  	"github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/targetgroup"
    29  )
    30  
    31  const (
    32  	configFieldPrefix      = "AUTO_DISCOVERY_"
    33  	staticConfigsKey       = "static-configs"
    34  	staticConfigsFieldName = configFieldPrefix + staticConfigsKey
    35  )
    36  
    37  var (
    38  	configNames      = make(map[string]Config)
    39  	configFieldNames = make(map[reflect.Type]string)
    40  	configFields     []reflect.StructField
    41  
    42  	configTypesMu sync.Mutex
    43  	configTypes   = make(map[reflect.Type]reflect.Type)
    44  
    45  	emptyStructType = reflect.TypeOf(struct{}{})
    46  	configsType     = reflect.TypeOf(Configs{})
    47  )
    48  
    49  // RegisterConfig registers the given Config type for YAML marshaling and unmarshaling.
    50  func RegisterConfig(config Config) {
    51  	registerConfig(config.Name()+"-sd-configs", reflect.TypeOf(config), config)
    52  }
    53  
    54  func init() {
    55  	// N.B.: static_configs is the only Config type implemented by default.
    56  	// All other types are registered at init by their implementing packages.
    57  	elemTyp := reflect.TypeOf(&targetgroup.Group{})
    58  	registerConfig(staticConfigsKey, elemTyp, StaticConfig{})
    59  }
    60  
    61  // revive:disable:confusing-naming private
    62  func registerConfig(yamlKey string, elemType reflect.Type, config Config) {
    63  	name := config.Name()
    64  	if _, ok := configNames[name]; ok {
    65  		panic(fmt.Sprintf("discovery: Config named %q is already registered", name))
    66  	}
    67  	configNames[name] = config
    68  
    69  	fieldName := configFieldPrefix + yamlKey // Field must be exported.
    70  	// Kebab style workaround.
    71  	fieldName = strings.ReplaceAll(fieldName, "-", "_")
    72  	configFieldNames[elemType] = fieldName
    73  
    74  	// Insert fields in sorted order.
    75  	i := sort.Search(len(configFields), func(k int) bool {
    76  		return fieldName < configFields[k].Name
    77  	})
    78  	configFields = append(configFields, reflect.StructField{}) // Add empty field at end.
    79  	copy(configFields[i+1:], configFields[i:])                 // Shift fields to the right.
    80  	configFields[i] = reflect.StructField{                     // Write new field in place.
    81  		Name: fieldName,
    82  		Type: reflect.SliceOf(elemType),
    83  		Tag:  reflect.StructTag(`yaml:"` + yamlKey + `,omitempty"`),
    84  	}
    85  }
    86  
    87  func getConfigType(out reflect.Type) reflect.Type {
    88  	configTypesMu.Lock()
    89  	defer configTypesMu.Unlock()
    90  	if typ, ok := configTypes[out]; ok {
    91  		return typ
    92  	}
    93  	// Initial exported fields map one-to-one.
    94  	var fields []reflect.StructField
    95  	for i, n := 0, out.NumField(); i < n; i++ {
    96  		switch field := out.Field(i); {
    97  		case field.PkgPath == "" && field.Type != configsType:
    98  			fields = append(fields, field)
    99  		default:
   100  			fields = append(fields, reflect.StructField{
   101  				Name:    "_" + field.Name, // Field must be unexported.
   102  				PkgPath: out.PkgPath(),
   103  				Type:    emptyStructType,
   104  			})
   105  		}
   106  	}
   107  	// Append extra config fields on the end.
   108  	fields = append(fields, configFields...)
   109  	typ := reflect.StructOf(fields)
   110  	configTypes[out] = typ
   111  	return typ
   112  }
   113  
   114  // UnmarshalYAMLWithInlineConfigs helps implement yaml.Unmarshal for structs
   115  // that have a Configs field that should be inlined.
   116  func UnmarshalYAMLWithInlineConfigs(out interface{}, unmarshal func(interface{}) error) error {
   117  	outVal := reflect.ValueOf(out)
   118  	if outVal.Kind() != reflect.Ptr {
   119  		return fmt.Errorf("discovery: can only unmarshal into a struct pointer: %T", out)
   120  	}
   121  	outVal = outVal.Elem()
   122  	if outVal.Kind() != reflect.Struct {
   123  		return fmt.Errorf("discovery: can only unmarshal into a struct pointer: %T", out)
   124  	}
   125  	outTyp := outVal.Type()
   126  
   127  	cfgTyp := getConfigType(outTyp)
   128  	cfgPtr := reflect.New(cfgTyp)
   129  	cfgVal := cfgPtr.Elem()
   130  
   131  	// Copy shared fields (defaults) to dynamic value.
   132  	var configs *Configs
   133  	for i, n := 0, outVal.NumField(); i < n; i++ {
   134  		if outTyp.Field(i).Type == configsType {
   135  			configs = outVal.Field(i).Addr().Interface().(*Configs)
   136  			continue
   137  		}
   138  		if cfgTyp.Field(i).PkgPath != "" {
   139  			continue // Field is unexported: ignore.
   140  		}
   141  		cfgVal.Field(i).Set(outVal.Field(i))
   142  	}
   143  	if configs == nil {
   144  		return fmt.Errorf("discovery: Configs field not found in type: %T", out)
   145  	}
   146  
   147  	// Unmarshal into dynamic value.
   148  	if err := unmarshal(cfgPtr.Interface()); err != nil {
   149  		return replaceYAMLTypeError(err, cfgTyp, outTyp)
   150  	}
   151  
   152  	// Copy shared fields from dynamic value.
   153  	for i, n := 0, outVal.NumField(); i < n; i++ {
   154  		if cfgTyp.Field(i).PkgPath != "" {
   155  			continue // Field is unexported: ignore.
   156  		}
   157  		outVal.Field(i).Set(cfgVal.Field(i))
   158  	}
   159  
   160  	var err error
   161  	*configs, err = readConfigs(cfgVal, outVal.NumField())
   162  	return err
   163  }
   164  
   165  func readConfigs(structVal reflect.Value, startField int) (Configs, error) {
   166  	var (
   167  		configs Configs
   168  		targets []*targetgroup.Group
   169  	)
   170  	for i, n := startField, structVal.NumField(); i < n; i++ {
   171  		field := structVal.Field(i)
   172  		if field.Kind() != reflect.Slice {
   173  			panic("discovery: internal error: field is not a slice")
   174  		}
   175  		for k := 0; k < field.Len(); k++ {
   176  			val := field.Index(k)
   177  			if val.IsZero() || (val.Kind() == reflect.Ptr && val.Elem().IsZero()) {
   178  				key := configFieldNames[field.Type().Elem()]
   179  				key = strings.TrimPrefix(key, strings.ReplaceAll(configFieldPrefix, "_", "-"))
   180  				return nil, fmt.Errorf("empty or null section in %s", key)
   181  			}
   182  			switch c := val.Interface().(type) {
   183  			case *targetgroup.Group:
   184  				// Add index to the static config target groups for unique identification
   185  				// within scrape pool.
   186  				c.Source = strconv.Itoa(len(targets))
   187  				// Coalesce multiple static configs into a single static config.
   188  				targets = append(targets, c)
   189  			case Config:
   190  				configs = append(configs, c)
   191  			default:
   192  				panic("discovery: internal error: slice element is not a Config")
   193  			}
   194  		}
   195  	}
   196  	if len(targets) > 0 {
   197  		configs = append(configs, StaticConfig(targets))
   198  	}
   199  	return configs, nil
   200  }
   201  
   202  // MarshalYAMLWithInlineConfigs helps implement yaml.Marshal for structs
   203  // that have a Configs field that should be inlined.
   204  func MarshalYAMLWithInlineConfigs(in interface{}) (interface{}, error) {
   205  	inVal := reflect.ValueOf(in)
   206  	for inVal.Kind() == reflect.Ptr {
   207  		inVal = inVal.Elem()
   208  	}
   209  	inTyp := inVal.Type()
   210  
   211  	cfgTyp := getConfigType(inTyp)
   212  	cfgPtr := reflect.New(cfgTyp)
   213  	cfgVal := cfgPtr.Elem()
   214  
   215  	// Copy shared fields to dynamic value.
   216  	var configs *Configs
   217  	for i, n := 0, inTyp.NumField(); i < n; i++ {
   218  		if inTyp.Field(i).Type == configsType {
   219  			configs = inVal.Field(i).Addr().Interface().(*Configs)
   220  		}
   221  		if cfgTyp.Field(i).PkgPath != "" {
   222  			continue // Field is unexported: ignore.
   223  		}
   224  		cfgVal.Field(i).Set(inVal.Field(i))
   225  	}
   226  	if configs == nil {
   227  		return nil, fmt.Errorf("discovery: Configs field not found in type: %T", in)
   228  	}
   229  
   230  	if err := writeConfigs(cfgVal, *configs); err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	return cfgPtr.Interface(), nil
   235  }
   236  
   237  func writeConfigs(structVal reflect.Value, configs Configs) error {
   238  	targets := structVal.FieldByName(staticConfigsFieldName).Addr().Interface().(*[]*targetgroup.Group)
   239  	for _, c := range configs {
   240  		if sc, ok := c.(StaticConfig); ok {
   241  			*targets = append(*targets, sc...)
   242  			continue
   243  		}
   244  		fieldName, ok := configFieldNames[reflect.TypeOf(c)]
   245  		if !ok {
   246  			return fmt.Errorf("discovery: cannot marshal unregistered Config type: %T", c)
   247  		}
   248  		field := structVal.FieldByName(fieldName)
   249  		field.Set(reflect.Append(field, reflect.ValueOf(c)))
   250  	}
   251  	return nil
   252  }
   253  
   254  func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
   255  	if e, ok := err.(*yaml.TypeError); ok {
   256  		oldStr := oldTyp.String()
   257  		newStr := newTyp.String()
   258  		for i, s := range e.Errors {
   259  			e.Errors[i] = strings.Replace(s, oldStr, newStr, -1)
   260  		}
   261  	}
   262  	return err
   263  }