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