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 }