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  }