github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/sinks/influxdb/influxdb_historical.go (about)

     1  // Copyright 2016 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 influxdb
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"regexp"
    21  	"strings"
    22  	"time"
    23  	"unicode"
    24  
    25  	"k8s.io/heapster/metrics/core"
    26  
    27  	"github.com/golang/glog"
    28  	influxdb "github.com/influxdata/influxdb/client"
    29  	influx_models "github.com/influxdata/influxdb/models"
    30  )
    31  
    32  // Historical indicates that this sink supports being used as a HistoricalSource
    33  func (sink *influxdbSink) Historical() core.HistoricalSource {
    34  	return sink
    35  }
    36  
    37  // implementation of HistoricalSource for influxdbSink
    38  
    39  // Kube pod and namespace names are limited to [a-zA-Z0-9-.], while docker also allows
    40  // underscores, so only allow these characters.  When Influx actually supports bound
    41  // parameters, this will be less necessary.
    42  var nameAllowedChars = regexp.MustCompile("^[a-zA-Z0-9_.-]+$")
    43  
    44  // metric names are restricted to prevent injection attacks
    45  var metricAllowedChars = regexp.MustCompile("^[a-zA-Z0-9_./:-]+$")
    46  
    47  // checkSanitizedKey errors out if invalid characters are found in the key, since InfluxDB does not widely
    48  // support bound parameters yet (see https://github.com/influxdata/influxdb/pull/6634) and we need to
    49  // sanitize our inputs.
    50  func (sink *influxdbSink) checkSanitizedKey(key *core.HistoricalKey) error {
    51  	if key.NodeName != "" && !nameAllowedChars.MatchString(key.NodeName) {
    52  		return fmt.Errorf("Invalid node name %q", key.NodeName)
    53  	}
    54  
    55  	if key.NamespaceName != "" && !nameAllowedChars.MatchString(key.NamespaceName) {
    56  		return fmt.Errorf("Invalid namespace name %q", key.NamespaceName)
    57  	}
    58  
    59  	if key.PodName != "" && !nameAllowedChars.MatchString(key.PodName) {
    60  		return fmt.Errorf("Invalid pod name %q", key.PodName)
    61  	}
    62  
    63  	// NB: this prevents access to some of the free containers with slashes in their name
    64  	// (e.g. system.slice/foo.bar), but the Heapster API seems to choke on the slashes anyway
    65  	if key.ContainerName != "" && !nameAllowedChars.MatchString(key.ContainerName) {
    66  		return fmt.Errorf("Invalid container name %q", key.ContainerName)
    67  	}
    68  
    69  	if key.PodId != "" && !nameAllowedChars.MatchString(key.PodId) {
    70  		return fmt.Errorf("Invalid pod id %q", key.PodId)
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  // checkSanitizedMetricName errors out if invalid characters are found in the metric name, since InfluxDB
    77  // does not widely support bound parameters yet, and we need to sanitize our inputs.
    78  func (sink *influxdbSink) checkSanitizedMetricName(name string) error {
    79  	if !metricAllowedChars.MatchString(name) {
    80  		return fmt.Errorf("Invalid metric name %q", name)
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // checkSanitizedMetricLabels errors out if invalid characters are found in the label name or label value, since
    87  // InfluxDb does not widely support bound parameters yet, and we need to sanitize our inputs.
    88  func (sink *influxdbSink) checkSanitizedMetricLabels(labels map[string]string) error {
    89  	// label names have the same restrictions as metric names, here
    90  	for k, v := range labels {
    91  		if !metricAllowedChars.MatchString(k) {
    92  			return fmt.Errorf("Invalid label name %q", k)
    93  		}
    94  
    95  		// for metric values, we're somewhat more permissive.  We allow any
    96  		// Printable unicode character, except quotation marks, which are used
    97  		// to delimit things.
    98  		if strings.ContainsRune(v, '"') || strings.ContainsRune(v, '\'') {
    99  			return fmt.Errorf("Invalid label value %q", v)
   100  		}
   101  
   102  		for _, runeVal := range v {
   103  			if !unicode.IsPrint(runeVal) {
   104  				return fmt.Errorf("Invalid label value %q", v)
   105  			}
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  // aggregationFunc converts an aggregation name into the equivalent call to an InfluxQL
   113  // aggregation function
   114  func (sink *influxdbSink) aggregationFunc(aggregationName core.AggregationType, fieldName string) string {
   115  	switch aggregationName {
   116  	case core.AggregationTypeAverage:
   117  		return fmt.Sprintf("MEAN(%q)", fieldName)
   118  	case core.AggregationTypeMaximum:
   119  		return fmt.Sprintf("MAX(%q)", fieldName)
   120  	case core.AggregationTypeMinimum:
   121  		return fmt.Sprintf("MIN(%q)", fieldName)
   122  	case core.AggregationTypeMedian:
   123  		return fmt.Sprintf("MEDIAN(%q)", fieldName)
   124  	case core.AggregationTypeCount:
   125  		return fmt.Sprintf("COUNT(%q)", fieldName)
   126  	case core.AggregationTypePercentile50:
   127  		return fmt.Sprintf("PERCENTILE(%q, 50)", fieldName)
   128  	case core.AggregationTypePercentile95:
   129  		return fmt.Sprintf("PERCENTILE(%q, 95)", fieldName)
   130  	case core.AggregationTypePercentile99:
   131  		return fmt.Sprintf("PERCENTILE(%q, 99)", fieldName)
   132  	}
   133  
   134  	// This should have been checked by the API level, so something's seriously wrong here
   135  	panic(fmt.Sprintf("Unknown aggregation type %q", aggregationName))
   136  }
   137  
   138  // keyToSelector converts a HistoricalKey to an InfluxQL predicate
   139  func (sink *influxdbSink) keyToSelector(key core.HistoricalKey) string {
   140  	typeSel := fmt.Sprintf("type = '%s'", key.ObjectType)
   141  	switch key.ObjectType {
   142  	case core.MetricSetTypeNode:
   143  		return fmt.Sprintf("%s AND %s = '%s'", typeSel, core.LabelNodename.Key, key.NodeName)
   144  	case core.MetricSetTypeSystemContainer:
   145  		return fmt.Sprintf("%s AND %s = '%s' AND %s = '%s'", typeSel, core.LabelContainerName.Key, key.ContainerName, core.LabelNodename.Key, key.NodeName)
   146  	case core.MetricSetTypeCluster:
   147  		return typeSel
   148  	case core.MetricSetTypeNamespace:
   149  		return fmt.Sprintf("%s AND %s = '%s'", typeSel, core.LabelNamespaceName.Key, key.NamespaceName)
   150  	case core.MetricSetTypePod:
   151  		if key.PodId != "" {
   152  			return fmt.Sprintf("%s AND %s = '%s'", typeSel, core.LabelPodId.Key, key.PodId)
   153  		} else {
   154  			return fmt.Sprintf("%s AND %s = '%s' AND %s = '%s'", typeSel, core.LabelNamespaceName.Key, key.NamespaceName, core.LabelPodName.Key, key.PodName)
   155  		}
   156  	case core.MetricSetTypePodContainer:
   157  		if key.PodId != "" {
   158  			return fmt.Sprintf("%s AND %s = '%s' AND %s = '%s'", typeSel, core.LabelPodId.Key, key.PodId, core.LabelContainerName.Key, key.ContainerName)
   159  		} else {
   160  			return fmt.Sprintf("%s AND %s = '%s' AND %s = '%s' AND %s = '%s'", typeSel, core.LabelNamespaceName.Key, key.NamespaceName, core.LabelPodName.Key, key.PodName, core.LabelContainerName.Key, key.ContainerName)
   161  		}
   162  	}
   163  
   164  	// These are assigned by the API, so it shouldn't be possible to reach this unless things are really broken
   165  	panic(fmt.Sprintf("Unknown metric type %q", key.ObjectType))
   166  }
   167  
   168  // labelsToPredicate composes an InfluxQL predicate based on the given map of labels
   169  func (sink *influxdbSink) labelsToPredicate(labels map[string]string) string {
   170  	if len(labels) == 0 {
   171  		return ""
   172  	}
   173  
   174  	parts := make([]string, 0, len(labels))
   175  	for k, v := range labels {
   176  		parts = append(parts, fmt.Sprintf("%q = '%s'", k, v))
   177  	}
   178  
   179  	return strings.Join(parts, " AND ")
   180  }
   181  
   182  // metricToSeriesAndField retrieves the appropriate field name and series name for a given metric
   183  // (this varies depending on whether or not WithFields is enabled)
   184  func (sink *influxdbSink) metricToSeriesAndField(metricName string) (string, string) {
   185  	if sink.c.WithFields {
   186  		seriesName := strings.SplitN(metricName, "/", 2)
   187  		if len(seriesName) > 1 {
   188  			return seriesName[0], seriesName[1]
   189  		} else {
   190  			return seriesName[0], "value"
   191  		}
   192  	} else {
   193  		return metricName, "value"
   194  	}
   195  }
   196  
   197  // composeRawQuery creates the InfluxQL query to fetch the given metric values
   198  func (sink *influxdbSink) composeRawQuery(metricName string, labels map[string]string, metricKeys []core.HistoricalKey, start, end time.Time) string {
   199  	seriesName, fieldName := sink.metricToSeriesAndField(metricName)
   200  
   201  	queries := make([]string, len(metricKeys))
   202  	for i, key := range metricKeys {
   203  		pred := sink.keyToSelector(key)
   204  		if labels != nil {
   205  			pred += fmt.Sprintf(" AND %s", sink.labelsToPredicate(labels))
   206  		}
   207  		if !start.IsZero() {
   208  			pred += fmt.Sprintf(" AND time > '%s'", start.Format(time.RFC3339))
   209  		}
   210  		if !end.IsZero() {
   211  			pred += fmt.Sprintf(" AND time < '%s'", end.Format(time.RFC3339))
   212  		}
   213  		queries[i] = fmt.Sprintf("SELECT time, %q FROM %q WHERE %s", fieldName, seriesName, pred)
   214  	}
   215  
   216  	return strings.Join(queries, "; ")
   217  }
   218  
   219  // parseRawQueryRow parses a set of timestamped metric values from unstructured JSON output into the
   220  // appropriate Heapster form
   221  func (sink *influxdbSink) parseRawQueryRow(rawRow influx_models.Row) ([]core.TimestampedMetricValue, error) {
   222  	vals := make([]core.TimestampedMetricValue, len(rawRow.Values))
   223  	wasInt := make(map[string]bool, 1)
   224  	for i, rawVal := range rawRow.Values {
   225  		val := core.TimestampedMetricValue{}
   226  
   227  		if ts, err := time.Parse(time.RFC3339, rawVal[0].(string)); err != nil {
   228  			return nil, fmt.Errorf("Unable to parse timestamp %q in series %q", rawVal[0].(string), rawRow.Name)
   229  		} else {
   230  			val.Timestamp = ts
   231  		}
   232  
   233  		if err := tryParseMetricValue("value", rawVal, &val.MetricValue, 1, wasInt); err != nil {
   234  			glog.Errorf("Unable to parse field \"value\" in series %q: %v", rawRow.Name, err)
   235  			return nil, fmt.Errorf("Unable to parse values in series %q", rawRow.Name)
   236  		}
   237  
   238  		vals[i] = val
   239  	}
   240  
   241  	if wasInt["value"] {
   242  		for i := range vals {
   243  			vals[i].MetricValue.ValueType = core.ValueInt64
   244  		}
   245  	} else {
   246  		for i := range vals {
   247  			vals[i].MetricValue.ValueType = core.ValueFloat
   248  		}
   249  	}
   250  
   251  	return vals, nil
   252  }
   253  
   254  // GetMetric retrieves the given metric for one or more objects (specified by metricKeys) of
   255  // the same type, within the given time interval
   256  func (sink *influxdbSink) GetMetric(metricName string, metricKeys []core.HistoricalKey, start, end time.Time) (map[core.HistoricalKey][]core.TimestampedMetricValue, error) {
   257  	for _, key := range metricKeys {
   258  		if err := sink.checkSanitizedKey(&key); err != nil {
   259  			return nil, err
   260  		}
   261  	}
   262  
   263  	if err := sink.checkSanitizedMetricName(metricName); err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	query := sink.composeRawQuery(metricName, nil, metricKeys, start, end)
   268  
   269  	sink.RLock()
   270  	defer sink.RUnlock()
   271  
   272  	resp, err := sink.runQuery(query)
   273  	if err != nil {
   274  		return nil, err
   275  	}
   276  
   277  	res := make(map[core.HistoricalKey][]core.TimestampedMetricValue, len(metricKeys))
   278  	for i, key := range metricKeys {
   279  		if len(resp[i].Series) < 1 {
   280  			return nil, fmt.Errorf("No results for metric %q describing %q", metricName, key.String())
   281  		}
   282  
   283  		vals, err := sink.parseRawQueryRow(resp[i].Series[0])
   284  		if err != nil {
   285  			return nil, err
   286  		}
   287  		res[key] = vals
   288  	}
   289  
   290  	return res, nil
   291  }
   292  
   293  // GetLabeledMetric retrieves the given labeled metric for one or more objects (specified by metricKeys) of
   294  // the same type, within the given time interval
   295  func (sink *influxdbSink) GetLabeledMetric(metricName string, labels map[string]string, metricKeys []core.HistoricalKey, start, end time.Time) (map[core.HistoricalKey][]core.TimestampedMetricValue, error) {
   296  	for _, key := range metricKeys {
   297  		if err := sink.checkSanitizedKey(&key); err != nil {
   298  			return nil, err
   299  		}
   300  	}
   301  
   302  	if err := sink.checkSanitizedMetricName(metricName); err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	if err := sink.checkSanitizedMetricLabels(labels); err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	query := sink.composeRawQuery(metricName, labels, metricKeys, start, end)
   311  
   312  	sink.RLock()
   313  	defer sink.RUnlock()
   314  
   315  	resp, err := sink.runQuery(query)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	res := make(map[core.HistoricalKey][]core.TimestampedMetricValue, len(metricKeys))
   321  	for i, key := range metricKeys {
   322  		if len(resp[i].Series) < 1 {
   323  			return nil, fmt.Errorf("No results for metric %q describing %q", metricName, key.String())
   324  		}
   325  
   326  		vals, err := sink.parseRawQueryRow(resp[i].Series[0])
   327  		if err != nil {
   328  			return nil, err
   329  		}
   330  		res[key] = vals
   331  	}
   332  
   333  	return res, nil
   334  }
   335  
   336  // composeAggregateQuery creates the InfluxQL query to fetch the given aggregation values
   337  func (sink *influxdbSink) composeAggregateQuery(metricName string, labels map[string]string, aggregations []core.AggregationType, metricKeys []core.HistoricalKey, start, end time.Time, bucketSize time.Duration) string {
   338  	seriesName, fieldName := sink.metricToSeriesAndField(metricName)
   339  
   340  	var bucketSizeNanoSeconds int64 = 0
   341  	if bucketSize != 0 {
   342  		bucketSizeNanoSeconds = int64(bucketSize.Nanoseconds() / int64(time.Microsecond/time.Nanosecond))
   343  	}
   344  
   345  	queries := make([]string, len(metricKeys))
   346  	for i, key := range metricKeys {
   347  		pred := sink.keyToSelector(key)
   348  		if labels != nil {
   349  			pred += fmt.Sprintf(" AND %s", sink.labelsToPredicate(labels))
   350  		}
   351  		if !start.IsZero() {
   352  			pred += fmt.Sprintf(" AND time > '%s'", start.Format(time.RFC3339))
   353  		}
   354  		if !end.IsZero() {
   355  			pred += fmt.Sprintf(" AND time < '%s'", end.Format(time.RFC3339))
   356  		}
   357  
   358  		aggParts := make([]string, len(aggregations))
   359  		for i, agg := range aggregations {
   360  			aggParts[i] = sink.aggregationFunc(agg, fieldName)
   361  		}
   362  
   363  		queries[i] = fmt.Sprintf("SELECT %s FROM %q WHERE %s", strings.Join(aggParts, ", "), seriesName, pred)
   364  
   365  		if bucketSize != 0 {
   366  			// group by time requires we have at least one time bound
   367  			if start.IsZero() && end.IsZero() {
   368  				queries[i] += fmt.Sprintf(" AND time < now()")
   369  			}
   370  
   371  			// fill(none) makes sure we skip data points will null values (otherwise we'll get a *bunch* of null
   372  			// values when we go back beyond the time where we started collecting data).
   373  			queries[i] += fmt.Sprintf(" GROUP BY time(%vu) fill(none)", bucketSizeNanoSeconds)
   374  		}
   375  	}
   376  
   377  	return strings.Join(queries, "; ")
   378  }
   379  
   380  // parseRawQueryRow parses a set of timestamped aggregation values from unstructured JSON output into the
   381  // appropriate Heapster form
   382  func (sink *influxdbSink) parseAggregateQueryRow(rawRow influx_models.Row, aggregationLookup map[core.AggregationType]int, bucketSize time.Duration) ([]core.TimestampedAggregationValue, error) {
   383  	vals := make([]core.TimestampedAggregationValue, len(rawRow.Values))
   384  	wasInt := make(map[string]bool, len(aggregationLookup))
   385  
   386  	for i, rawVal := range rawRow.Values {
   387  		val := core.TimestampedAggregationValue{
   388  			BucketSize: bucketSize,
   389  			AggregationValue: core.AggregationValue{
   390  				Aggregations: map[core.AggregationType]core.MetricValue{},
   391  			},
   392  		}
   393  
   394  		if ts, err := time.Parse(time.RFC3339, rawVal[0].(string)); err != nil {
   395  			return nil, fmt.Errorf("Unable to parse timestamp %q in series %q", rawVal[0].(string), rawRow.Name)
   396  		} else {
   397  			val.Timestamp = ts
   398  		}
   399  
   400  		// The Influx client decods numeric fields to json.Number (a string), so we have to try decoding to both types of numbers
   401  
   402  		// Count is always a uint64
   403  		if countIndex, ok := aggregationLookup[core.AggregationTypeCount]; ok {
   404  			if err := json.Unmarshal([]byte(rawVal[countIndex].(json.Number).String()), &val.Count); err != nil {
   405  				glog.Errorf("Unable to parse count value in series %q: %v", rawRow.Name, err)
   406  				return nil, fmt.Errorf("Unable to parse values in series %q", rawRow.Name)
   407  			}
   408  		}
   409  
   410  		// The rest of the aggregation values can be either float or int, so attempt to parse both
   411  		if err := populateAggregations(rawRow.Name, rawVal, &val, aggregationLookup, wasInt); err != nil {
   412  			return nil, err
   413  		}
   414  
   415  		vals[i] = val
   416  	}
   417  
   418  	// figure out whether each aggregation was full of float values, or int values
   419  	setAggregationValueTypes(vals, wasInt)
   420  
   421  	return vals, nil
   422  }
   423  
   424  // GetAggregation fetches the given aggregations for one or more objects (specified by metricKeys) of
   425  // the same type, within the given time interval, calculated over a series of buckets
   426  func (sink *influxdbSink) GetAggregation(metricName string, aggregations []core.AggregationType, metricKeys []core.HistoricalKey, start, end time.Time, bucketSize time.Duration) (map[core.HistoricalKey][]core.TimestampedAggregationValue, error) {
   427  	for _, key := range metricKeys {
   428  		if err := sink.checkSanitizedKey(&key); err != nil {
   429  			return nil, err
   430  		}
   431  	}
   432  
   433  	if err := sink.checkSanitizedMetricName(metricName); err != nil {
   434  		return nil, err
   435  	}
   436  
   437  	// make it easy to look up where the different aggregations are in the list
   438  	aggregationLookup := make(map[core.AggregationType]int, len(aggregations))
   439  	for i, agg := range aggregations {
   440  		aggregationLookup[agg] = i + 1
   441  	}
   442  
   443  	query := sink.composeAggregateQuery(metricName, nil, aggregations, metricKeys, start, end, bucketSize)
   444  
   445  	sink.RLock()
   446  	defer sink.RUnlock()
   447  
   448  	resp, err := sink.runQuery(query)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  
   453  	// TODO: when there are too many points (e.g. certain times when a start time is not specified), Influx will sometimes return only a single bucket
   454  	//       instead of returning an error.  We should detect this case and return an error ourselves (or maybe just require a start time at the API level)
   455  	res := make(map[core.HistoricalKey][]core.TimestampedAggregationValue, len(metricKeys))
   456  	for i, key := range metricKeys {
   457  		if len(resp[i].Series) < 1 {
   458  			return nil, fmt.Errorf("No results for metric %q describing %q", metricName, key.String())
   459  		}
   460  
   461  		vals, err := sink.parseAggregateQueryRow(resp[i].Series[0], aggregationLookup, bucketSize)
   462  		if err != nil {
   463  			return nil, err
   464  		}
   465  		res[key] = vals
   466  	}
   467  
   468  	return res, nil
   469  }
   470  
   471  // GetLabeledAggregation fetches the given aggregations (on labeled metrics) for one or more objects
   472  // (specified by metricKeys) of the same type, within the given time interval, calculated over a series of buckets
   473  func (sink *influxdbSink) GetLabeledAggregation(metricName string, labels map[string]string, aggregations []core.AggregationType, metricKeys []core.HistoricalKey, start, end time.Time, bucketSize time.Duration) (map[core.HistoricalKey][]core.TimestampedAggregationValue, error) {
   474  	for _, key := range metricKeys {
   475  		if err := sink.checkSanitizedKey(&key); err != nil {
   476  			return nil, err
   477  		}
   478  	}
   479  
   480  	if err := sink.checkSanitizedMetricName(metricName); err != nil {
   481  		return nil, err
   482  	}
   483  
   484  	if err := sink.checkSanitizedMetricLabels(labels); err != nil {
   485  		return nil, err
   486  	}
   487  
   488  	// make it easy to look up where the different aggregations are in the list
   489  	aggregationLookup := make(map[core.AggregationType]int, len(aggregations))
   490  	for i, agg := range aggregations {
   491  		aggregationLookup[agg] = i + 1
   492  	}
   493  
   494  	query := sink.composeAggregateQuery(metricName, labels, aggregations, metricKeys, start, end, bucketSize)
   495  
   496  	sink.RLock()
   497  	defer sink.RUnlock()
   498  
   499  	resp, err := sink.runQuery(query)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  
   504  	// TODO: when there are too many points (e.g. certain times when a start time is not specified), Influx will sometimes return only a single bucket
   505  	//       instead of returning an error.  We should detect this case and return an error ourselves (or maybe just require a start time at the API level)
   506  	res := make(map[core.HistoricalKey][]core.TimestampedAggregationValue, len(metricKeys))
   507  	for i, key := range metricKeys {
   508  		if len(resp[i].Series) < 1 {
   509  			return nil, fmt.Errorf("No results for metric %q describing %q", metricName, key.String())
   510  		}
   511  
   512  		vals, err := sink.parseAggregateQueryRow(resp[i].Series[0], aggregationLookup, bucketSize)
   513  		if err != nil {
   514  			return nil, err
   515  		}
   516  		res[key] = vals
   517  	}
   518  
   519  	return res, nil
   520  }
   521  
   522  // setAggregationValueIfPresent checks if the given metric value is present in the list of raw values, and if so,
   523  // copies it to the output format
   524  func setAggregationValueIfPresent(aggName core.AggregationType, rawVal []interface{}, aggregations *core.AggregationValue, indexLookup map[core.AggregationType]int, wasInt map[string]bool) error {
   525  	if fieldIndex, ok := indexLookup[aggName]; ok {
   526  		targetValue := &core.MetricValue{}
   527  		if err := tryParseMetricValue(string(aggName), rawVal, targetValue, fieldIndex, wasInt); err != nil {
   528  			return err
   529  		}
   530  
   531  		aggregations.Aggregations[aggName] = *targetValue
   532  	}
   533  
   534  	return nil
   535  }
   536  
   537  // tryParseMetricValue attempts to parse a raw metric value into the appropriate go type.
   538  func tryParseMetricValue(aggName string, rawVal []interface{}, targetValue *core.MetricValue, fieldIndex int, wasInt map[string]bool) error {
   539  	// the Influx client decodes numeric fields to json.Number (a string), so we have to deal with that --
   540  	// assume, starting off, that values may be either float or int.  Try int until we fail once, and always
   541  	// try float.  At the end, figure out which is which.
   542  
   543  	var rv string
   544  	if rvN, ok := rawVal[fieldIndex].(json.Number); !ok {
   545  		return fmt.Errorf("Value %q of metric %q was not a json.Number", rawVal[fieldIndex], aggName)
   546  	} else {
   547  		rv = rvN.String()
   548  	}
   549  
   550  	tryInt := false
   551  	isInt, triedBefore := wasInt[aggName]
   552  	tryInt = isInt || !triedBefore
   553  
   554  	if tryInt {
   555  		if err := json.Unmarshal([]byte(rv), &targetValue.IntValue); err != nil {
   556  			wasInt[aggName] = false
   557  		} else {
   558  			wasInt[aggName] = true
   559  		}
   560  	}
   561  
   562  	if err := json.Unmarshal([]byte(rv), &targetValue.FloatValue); err != nil {
   563  		return err
   564  	}
   565  
   566  	return nil
   567  }
   568  
   569  // GetMetricNames retrieves the available metric names for the given object
   570  func (sink *influxdbSink) GetMetricNames(metricKey core.HistoricalKey) ([]string, error) {
   571  	if err := sink.checkSanitizedKey(&metricKey); err != nil {
   572  		return nil, err
   573  	}
   574  	return sink.stringListQuery(fmt.Sprintf("SHOW MEASUREMENTS WHERE %s", sink.keyToSelector(metricKey)), "Unable to list available metrics")
   575  }
   576  
   577  // GetNodes retrieves the list of nodes in the cluster
   578  func (sink *influxdbSink) GetNodes() ([]string, error) {
   579  	return sink.stringListQuery(fmt.Sprintf("SHOW TAG VALUES WITH KEY = %s", core.LabelNodename.Key), "Unable to list all nodes")
   580  }
   581  
   582  // GetNamespaces retrieves the list of namespaces in the cluster
   583  func (sink *influxdbSink) GetNamespaces() ([]string, error) {
   584  	return sink.stringListQuery(fmt.Sprintf("SHOW TAG VALUES WITH KEY = %s", core.LabelNamespaceName.Key), "Unable to list all namespaces")
   585  }
   586  
   587  // GetPodsFromNamespace retrieves the list of pods in a given namespace
   588  func (sink *influxdbSink) GetPodsFromNamespace(namespace string) ([]string, error) {
   589  	if !nameAllowedChars.MatchString(namespace) {
   590  		return nil, fmt.Errorf("Invalid namespace name %q", namespace)
   591  	}
   592  	// This is a bit difficult for the influx query language, so we cheat a bit here --
   593  	// we just get all series for the uptime measurement for pods which match our namespace
   594  	// (any measurement should work here, though)
   595  	q := fmt.Sprintf("SHOW SERIES FROM %q WHERE %s = '%s' AND type = '%s'", core.MetricUptime.MetricDescriptor.Name, core.LabelNamespaceName.Key, namespace, core.MetricSetTypePod)
   596  	return sink.stringListQueryCol(q, core.LabelPodName.Key, fmt.Sprintf("Unable to list pods in namespace %q", namespace))
   597  }
   598  
   599  // GetSystemContainersFromNode retrieves the list of free containers for a given node
   600  func (sink *influxdbSink) GetSystemContainersFromNode(node string) ([]string, error) {
   601  	if !nameAllowedChars.MatchString(node) {
   602  		return nil, fmt.Errorf("Invalid node name %q", node)
   603  	}
   604  	// This is a bit difficult for the influx query language, so we cheat a bit here --
   605  	// we just get all series for the uptime measurement for system containers on our node
   606  	// (any measurement should work here, though)
   607  	q := fmt.Sprintf("SHOW SERIES FROM %q WHERE %s = '%s' AND type = '%s'", core.MetricUptime.MetricDescriptor.Name, core.LabelNodename.Key, node, core.MetricSetTypeSystemContainer)
   608  	return sink.stringListQueryCol(q, core.LabelContainerName.Key, fmt.Sprintf("Unable to list system containers on node %q", node))
   609  }
   610  
   611  // stringListQueryCol runs the given query, and returns all results from the given column as a string list
   612  func (sink *influxdbSink) stringListQueryCol(q, colName string, errStr string) ([]string, error) {
   613  	sink.RLock()
   614  	defer sink.RUnlock()
   615  
   616  	resp, err := sink.runQuery(q)
   617  	if err != nil {
   618  		return nil, fmt.Errorf(errStr)
   619  	}
   620  
   621  	if len(resp[0].Series) < 1 {
   622  		return nil, fmt.Errorf(errStr)
   623  	}
   624  
   625  	colInd := -1
   626  	for i, col := range resp[0].Series[0].Columns {
   627  		if col == colName {
   628  			colInd = i
   629  			break
   630  		}
   631  	}
   632  
   633  	if colInd == -1 {
   634  		glog.Errorf("%s: results did not contain the %q column", errStr, core.LabelPodName.Key)
   635  		return nil, fmt.Errorf(errStr)
   636  	}
   637  
   638  	res := make([]string, len(resp[0].Series[0].Values))
   639  	for i, rv := range resp[0].Series[0].Values {
   640  		res[i] = rv[colInd].(string)
   641  	}
   642  	return res, nil
   643  }
   644  
   645  // stringListQuery runs the given query, and returns all results from the first column as a string list
   646  func (sink *influxdbSink) stringListQuery(q string, errStr string) ([]string, error) {
   647  	sink.RLock()
   648  	defer sink.RUnlock()
   649  
   650  	resp, err := sink.runQuery(q)
   651  	if err != nil {
   652  		return nil, fmt.Errorf(errStr)
   653  	}
   654  
   655  	if len(resp[0].Series) < 1 {
   656  		return nil, fmt.Errorf(errStr)
   657  	}
   658  
   659  	res := make([]string, len(resp[0].Series[0].Values))
   660  	for i, rv := range resp[0].Series[0].Values {
   661  		res[i] = rv[0].(string)
   662  	}
   663  	return res, nil
   664  }
   665  
   666  // runQuery executes the given query against InfluxDB (using the default database for this sink)
   667  // The caller is responsible for locking the sink before use.
   668  func (sink *influxdbSink) runQuery(queryStr string) ([]influxdb.Result, error) {
   669  	// ensure we have a valid client handle before attempting to use it
   670  	if err := sink.ensureClient(); err != nil {
   671  		glog.Errorf("Unable to ensure InfluxDB client is present: %v", err)
   672  		return nil, fmt.Errorf("unable to run query: unable to connect to database")
   673  	}
   674  
   675  	q := influxdb.Query{
   676  		Command:  queryStr,
   677  		Database: sink.c.DbName,
   678  	}
   679  
   680  	glog.V(4).Infof("Executing query %q against database %q", q.Command, q.Database)
   681  
   682  	resp, err := sink.client.Query(q)
   683  	if err != nil {
   684  		glog.Errorf("Unable to perform query %q against database %q: %v", q.Command, q.Database, err)
   685  		return nil, err
   686  	} else if resp.Error() != nil {
   687  		glog.Errorf("Unable to perform query %q against database %q: %v", q.Command, q.Database, resp.Error())
   688  		return nil, resp.Error()
   689  	}
   690  
   691  	if len(resp.Results) < 1 {
   692  		glog.Errorf("Unable to perform query %q against database %q: no results returned", q.Command, q.Database)
   693  		return nil, fmt.Errorf("No results returned")
   694  	}
   695  
   696  	return resp.Results, nil
   697  }
   698  
   699  // populateAggregations extracts aggregation values from a given data point
   700  func populateAggregations(rawRowName string, rawVal []interface{}, val *core.TimestampedAggregationValue, aggregationLookup map[core.AggregationType]int, wasInt map[string]bool) error {
   701  	for _, aggregation := range core.MultiTypedAggregations {
   702  		if err := setAggregationValueIfPresent(aggregation, rawVal, &val.AggregationValue, aggregationLookup, wasInt); err != nil {
   703  			glog.Errorf("Unable to parse field %q in series %q: %v", aggregation, rawRowName, err)
   704  			return fmt.Errorf("Unable to parse values in series %q", rawRowName)
   705  		}
   706  	}
   707  
   708  	return nil
   709  }
   710  
   711  // setAggregationValueTypes inspects a set of aggregation values and figures out whether each aggregation value
   712  // returned as a float column, or an int column
   713  func setAggregationValueTypes(vals []core.TimestampedAggregationValue, wasInt map[string]bool) {
   714  	for _, aggregation := range core.MultiTypedAggregations {
   715  		if isInt, ok := wasInt[string(aggregation)]; ok && isInt {
   716  			for i := range vals {
   717  				val := vals[i].Aggregations[aggregation]
   718  				val.ValueType = core.ValueInt64
   719  				vals[i].Aggregations[aggregation] = val
   720  			}
   721  		} else if ok {
   722  			for i := range vals {
   723  				val := vals[i].Aggregations[aggregation]
   724  				val.ValueType = core.ValueFloat
   725  				vals[i].Aggregations[aggregation] = val
   726  			}
   727  		}
   728  	}
   729  }