github.com/google/cadvisor@v0.49.1/collector/generic_collector.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 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 collector 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io" 21 "net/http" 22 "regexp" 23 "strconv" 24 "strings" 25 "time" 26 27 "github.com/google/cadvisor/container" 28 v1 "github.com/google/cadvisor/info/v1" 29 ) 30 31 type GenericCollector struct { 32 // name of the collector 33 name string 34 35 // holds information extracted from the config file for a collector 36 configFile Config 37 38 // holds information necessary to extract metrics 39 info *collectorInfo 40 41 // The Http client to use when connecting to metric endpoints 42 httpClient *http.Client 43 } 44 45 type collectorInfo struct { 46 // minimum polling frequency among all metrics 47 minPollingFrequency time.Duration 48 49 // regular expresssions for all metrics 50 regexps []*regexp.Regexp 51 52 // Limit for the number of srcaped metrics. If the count is higher, 53 // no metrics will be returned. 54 metricCountLimit int 55 } 56 57 // Returns a new collector using the information extracted from the configfile 58 func NewCollector(collectorName string, configFile []byte, metricCountLimit int, containerHandler container.ContainerHandler, httpClient *http.Client) (*GenericCollector, error) { 59 var configInJSON Config 60 err := json.Unmarshal(configFile, &configInJSON) 61 if err != nil { 62 return nil, err 63 } 64 65 configInJSON.Endpoint.configure(containerHandler) 66 67 // TODO : Add checks for validity of config file (eg : Accurate JSON fields) 68 69 if len(configInJSON.MetricsConfig) == 0 { 70 return nil, fmt.Errorf("No metrics provided in config") 71 } 72 73 minPollFrequency := time.Duration(0) 74 regexprs := make([]*regexp.Regexp, len(configInJSON.MetricsConfig)) 75 76 for ind, metricConfig := range configInJSON.MetricsConfig { 77 // Find the minimum specified polling frequency in metric config. 78 if metricConfig.PollingFrequency != 0 { 79 if minPollFrequency == 0 || metricConfig.PollingFrequency < minPollFrequency { 80 minPollFrequency = metricConfig.PollingFrequency 81 } 82 } 83 84 regexprs[ind], err = regexp.Compile(metricConfig.Regex) 85 if err != nil { 86 return nil, fmt.Errorf("Invalid regexp %v for metric %v", metricConfig.Regex, metricConfig.Name) 87 } 88 } 89 90 // Minimum supported polling frequency is 1s. 91 minSupportedFrequency := 1 * time.Second 92 if minPollFrequency < minSupportedFrequency { 93 minPollFrequency = minSupportedFrequency 94 } 95 96 if len(configInJSON.MetricsConfig) > metricCountLimit { 97 return nil, fmt.Errorf("Too many metrics defined: %d limit: %d", len(configInJSON.MetricsConfig), metricCountLimit) 98 } 99 100 return &GenericCollector{ 101 name: collectorName, 102 configFile: configInJSON, 103 info: &collectorInfo{ 104 minPollingFrequency: minPollFrequency, 105 regexps: regexprs, 106 metricCountLimit: metricCountLimit, 107 }, 108 httpClient: httpClient, 109 }, nil 110 } 111 112 // Returns name of the collector 113 func (collector *GenericCollector) Name() string { 114 return collector.name 115 } 116 117 func (collector *GenericCollector) configToSpec(config MetricConfig) v1.MetricSpec { 118 return v1.MetricSpec{ 119 Name: config.Name, 120 Type: config.MetricType, 121 Format: config.DataType, 122 Units: config.Units, 123 } 124 } 125 126 func (collector *GenericCollector) GetSpec() []v1.MetricSpec { 127 specs := []v1.MetricSpec{} 128 for _, metricConfig := range collector.configFile.MetricsConfig { 129 spec := collector.configToSpec(metricConfig) 130 specs = append(specs, spec) 131 } 132 return specs 133 } 134 135 // Returns collected metrics and the next collection time of the collector 136 func (collector *GenericCollector) Collect(metrics map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) { 137 currentTime := time.Now() 138 nextCollectionTime := currentTime.Add(time.Duration(collector.info.minPollingFrequency)) 139 140 uri := collector.configFile.Endpoint.URL 141 response, err := collector.httpClient.Get(uri) 142 if err != nil { 143 return nextCollectionTime, nil, err 144 } 145 146 defer response.Body.Close() 147 148 pageContent, err := io.ReadAll(response.Body) 149 if err != nil { 150 return nextCollectionTime, nil, err 151 } 152 153 var errorSlice []error 154 155 for ind, metricConfig := range collector.configFile.MetricsConfig { 156 matchString := collector.info.regexps[ind].FindStringSubmatch(string(pageContent)) 157 if matchString != nil { 158 if metricConfig.DataType == v1.FloatType { 159 regVal, err := strconv.ParseFloat(strings.TrimSpace(matchString[1]), 64) 160 if err != nil { 161 errorSlice = append(errorSlice, err) 162 } 163 metrics[metricConfig.Name] = []v1.MetricVal{ 164 {FloatValue: regVal, Timestamp: currentTime}, 165 } 166 } else if metricConfig.DataType == v1.IntType { 167 regVal, err := strconv.ParseInt(strings.TrimSpace(matchString[1]), 10, 64) 168 if err != nil { 169 errorSlice = append(errorSlice, err) 170 } 171 metrics[metricConfig.Name] = []v1.MetricVal{ 172 {IntValue: regVal, Timestamp: currentTime}, 173 } 174 175 } else { 176 errorSlice = append(errorSlice, fmt.Errorf("Unexpected value of 'data_type' for metric '%v' in config ", metricConfig.Name)) 177 } 178 } else { 179 errorSlice = append(errorSlice, fmt.Errorf("No match found for regexp: %v for metric '%v' in config", metricConfig.Regex, metricConfig.Name)) 180 } 181 } 182 return nextCollectionTime, metrics, compileErrors(errorSlice) 183 }