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 }