2015-08-28 10:24:00 +00:00
|
|
|
/*
|
|
|
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package metrics
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang/glog"
|
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2016-02-05 21:58:03 +00:00
|
|
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
2015-08-28 10:24:00 +00:00
|
|
|
"k8s.io/kubernetes/pkg/labels"
|
|
|
|
|
|
|
|
heapster "k8s.io/heapster/api/v1/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2015-11-02 16:14:08 +00:00
|
|
|
DefaultHeapsterNamespace = "kube-system"
|
2015-11-06 17:55:31 +00:00
|
|
|
DefaultHeapsterScheme = "http"
|
2015-11-02 16:14:08 +00:00
|
|
|
DefaultHeapsterService = "heapster"
|
2015-11-06 17:55:31 +00:00
|
|
|
DefaultHeapsterPort = "" // use the first exposed port on the service
|
2015-08-28 10:24:00 +00:00
|
|
|
)
|
|
|
|
|
2015-09-24 09:09:40 +00:00
|
|
|
var heapsterQueryStart = -5 * time.Minute
|
2015-08-28 10:24:00 +00:00
|
|
|
|
2015-10-13 15:24:23 +00:00
|
|
|
// MetricsClient is an interface for getting metrics for pods.
|
2015-08-28 10:24:00 +00:00
|
|
|
type MetricsClient interface {
|
2015-12-02 08:24:17 +00:00
|
|
|
// GetCPUUtilization returns the average utilization over all pods represented as a percent of requested CPU
|
|
|
|
// (e.g. 70 means that an average pod uses 70% of the requested CPU)
|
|
|
|
// and the time of generation of the oldest of utilization reports for pods.
|
2016-03-09 00:27:13 +00:00
|
|
|
GetCPUUtilization(namespace string, selector labels.Selector) (*int, time.Time, error)
|
2016-01-27 20:14:45 +00:00
|
|
|
|
|
|
|
// GetCustomMetric returns the average value of the given custom metrics from the
|
|
|
|
// pods picked using the namespace and selector passed as arguments.
|
2016-03-09 00:27:13 +00:00
|
|
|
GetCustomMetric(customMetricName string, namespace string, selector labels.Selector) (*float64, time.Time, error)
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2016-01-27 20:14:45 +00:00
|
|
|
type intAndFloat struct {
|
|
|
|
intValue int64
|
|
|
|
floatValue float64
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2015-12-02 08:24:17 +00:00
|
|
|
// Aggregates results into ResourceConsumption. Also returns number of pods included in the aggregation.
|
2016-01-27 20:14:45 +00:00
|
|
|
type metricAggregator func(heapster.MetricResultList) (intAndFloat, int, time.Time)
|
2015-08-28 10:24:00 +00:00
|
|
|
|
|
|
|
type metricDefinition struct {
|
|
|
|
name string
|
|
|
|
aggregator metricAggregator
|
|
|
|
}
|
|
|
|
|
2015-10-13 15:24:23 +00:00
|
|
|
// HeapsterMetricsClient is Heapster-based implementation of MetricsClient
|
2015-08-28 10:24:00 +00:00
|
|
|
type HeapsterMetricsClient struct {
|
2016-01-29 06:34:08 +00:00
|
|
|
client clientset.Interface
|
2016-01-27 20:14:45 +00:00
|
|
|
heapsterNamespace string
|
|
|
|
heapsterScheme string
|
|
|
|
heapsterService string
|
|
|
|
heapsterPort string
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2016-01-27 20:14:45 +00:00
|
|
|
var averageFunction = func(metrics heapster.MetricResultList) (intAndFloat, int, time.Time) {
|
2016-02-26 12:44:16 +00:00
|
|
|
sum, count, timestamp := calculateSumFromTimeSample(metrics, time.Minute)
|
2016-01-27 20:14:45 +00:00
|
|
|
result := intAndFloat{0, 0}
|
|
|
|
if count > 0 {
|
|
|
|
result.intValue = sum.intValue / int64(count)
|
|
|
|
result.floatValue = sum.floatValue / float64(count)
|
|
|
|
}
|
|
|
|
return result, count, timestamp
|
|
|
|
}
|
|
|
|
|
|
|
|
var heapsterCpuUsageMetricDefinition = metricDefinition{"cpu-usage", averageFunction}
|
|
|
|
|
|
|
|
func getHeapsterCustomMetricDefinition(metricName string) metricDefinition {
|
2016-02-15 19:27:07 +00:00
|
|
|
return metricDefinition{"custom/" + metricName, averageFunction}
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2015-10-13 15:24:23 +00:00
|
|
|
// NewHeapsterMetricsClient returns a new instance of Heapster-based implementation of MetricsClient interface.
|
2016-01-29 06:34:08 +00:00
|
|
|
func NewHeapsterMetricsClient(client clientset.Interface, namespace, scheme, service, port string) *HeapsterMetricsClient {
|
2015-10-13 15:24:23 +00:00
|
|
|
return &HeapsterMetricsClient{
|
2016-01-27 20:14:45 +00:00
|
|
|
client: client,
|
|
|
|
heapsterNamespace: namespace,
|
|
|
|
heapsterScheme: scheme,
|
|
|
|
heapsterService: service,
|
|
|
|
heapsterPort: port,
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-09 00:27:13 +00:00
|
|
|
func (h *HeapsterMetricsClient) GetCPUUtilization(namespace string, selector labels.Selector) (*int, time.Time, error) {
|
2016-01-27 20:14:45 +00:00
|
|
|
avgConsumption, avgRequest, timestamp, err := h.GetCpuConsumptionAndRequestInMillis(namespace, selector)
|
2015-10-13 15:24:23 +00:00
|
|
|
if err != nil {
|
2015-12-02 08:24:17 +00:00
|
|
|
return nil, time.Time{}, fmt.Errorf("failed to get CPU consumption and request: %v", err)
|
2015-10-13 15:24:23 +00:00
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
utilization := int((avgConsumption * 100) / avgRequest)
|
|
|
|
return &utilization, timestamp, nil
|
2015-10-13 15:24:23 +00:00
|
|
|
}
|
|
|
|
|
2016-03-09 00:27:13 +00:00
|
|
|
func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace string, selector labels.Selector) (avgConsumption int64,
|
2016-01-27 20:14:45 +00:00
|
|
|
avgRequest int64, timestamp time.Time, err error) {
|
|
|
|
|
2016-02-03 21:21:05 +00:00
|
|
|
podList, err := h.client.Core().Pods(namespace).
|
2016-03-09 00:27:13 +00:00
|
|
|
List(api.ListOptions{LabelSelector: selector})
|
2015-08-28 10:24:00 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2016-01-27 20:14:45 +00:00
|
|
|
return 0, 0, time.Time{}, fmt.Errorf("failed to get pod list: %v", err)
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
podNames := []string{}
|
2016-01-27 20:14:45 +00:00
|
|
|
requestSum := int64(0)
|
2015-10-13 15:24:23 +00:00
|
|
|
missing := false
|
2015-08-28 10:24:00 +00:00
|
|
|
for _, pod := range podList.Items {
|
2016-02-19 15:36:18 +00:00
|
|
|
if pod.Status.Phase == api.PodPending {
|
|
|
|
// Skip pending pods.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-08-28 10:24:00 +00:00
|
|
|
podNames = append(podNames, pod.Name)
|
2015-10-13 15:24:23 +00:00
|
|
|
for _, container := range pod.Spec.Containers {
|
2016-01-27 20:14:45 +00:00
|
|
|
containerRequest := container.Resources.Requests[api.ResourceCPU]
|
2015-10-13 15:24:23 +00:00
|
|
|
if containerRequest.Amount != nil {
|
2016-01-27 20:14:45 +00:00
|
|
|
requestSum += containerRequest.MilliValue()
|
2015-10-13 15:24:23 +00:00
|
|
|
} else {
|
|
|
|
missing = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-19 15:36:18 +00:00
|
|
|
if len(podNames) == 0 && len(podList.Items) > 0 {
|
|
|
|
return 0, 0, time.Time{}, fmt.Errorf("no running pods")
|
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
if missing || requestSum == 0 {
|
|
|
|
return 0, 0, time.Time{}, fmt.Errorf("some pods do not have request for cpu")
|
2015-10-13 15:24:23 +00:00
|
|
|
}
|
2016-03-09 00:27:13 +00:00
|
|
|
glog.V(4).Infof("%s %s - sum of CPU requested: %d", namespace, selector, requestSum)
|
2016-01-27 20:14:45 +00:00
|
|
|
requestAvg := requestSum / int64(len(podList.Items))
|
|
|
|
// Consumption is already averaged and in millis.
|
|
|
|
consumption, timestamp, err := h.getForPods(heapsterCpuUsageMetricDefinition, namespace, podNames)
|
2015-10-13 15:24:23 +00:00
|
|
|
if err != nil {
|
2016-01-27 20:14:45 +00:00
|
|
|
return 0, 0, time.Time{}, err
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
return consumption.intValue, requestAvg, timestamp, nil
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2016-01-27 20:14:45 +00:00
|
|
|
// GetCustomMetric returns the average value of the given custom metric from the
|
|
|
|
// pods picked using the namespace and selector passed as arguments.
|
2016-03-09 00:27:13 +00:00
|
|
|
func (h *HeapsterMetricsClient) GetCustomMetric(customMetricName string, namespace string, selector labels.Selector) (*float64, time.Time, error) {
|
2016-01-27 20:14:45 +00:00
|
|
|
metricSpec := getHeapsterCustomMetricDefinition(customMetricName)
|
|
|
|
|
2016-03-09 00:27:13 +00:00
|
|
|
podList, err := h.client.Core().Pods(namespace).List(api.ListOptions{LabelSelector: selector})
|
2016-01-27 20:14:45 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, time.Time{}, fmt.Errorf("failed to get pod list: %v", err)
|
|
|
|
}
|
|
|
|
podNames := []string{}
|
|
|
|
for _, pod := range podList.Items {
|
2016-02-19 15:36:18 +00:00
|
|
|
if pod.Status.Phase == api.PodPending {
|
|
|
|
// Skip pending pods.
|
|
|
|
continue
|
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
podNames = append(podNames, pod.Name)
|
|
|
|
}
|
2016-02-19 15:36:18 +00:00
|
|
|
if len(podNames) == 0 && len(podList.Items) > 0 {
|
|
|
|
return nil, time.Time{}, fmt.Errorf("no running pods")
|
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
|
|
|
|
value, timestamp, err := h.getForPods(metricSpec, namespace, podNames)
|
|
|
|
if err != nil {
|
|
|
|
return nil, time.Time{}, err
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
return &value.floatValue, timestamp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HeapsterMetricsClient) getForPods(metricSpec metricDefinition, namespace string, podNames []string) (*intAndFloat, time.Time, error) {
|
|
|
|
|
2015-08-28 10:24:00 +00:00
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
startTime := now.Add(heapsterQueryStart)
|
|
|
|
metricPath := fmt.Sprintf("/api/v1/model/namespaces/%s/pod-list/%s/metrics/%s",
|
2015-10-13 15:24:23 +00:00
|
|
|
namespace,
|
2015-08-28 10:24:00 +00:00
|
|
|
strings.Join(podNames, ","),
|
|
|
|
metricSpec.name)
|
|
|
|
|
2016-02-03 21:21:05 +00:00
|
|
|
resultRaw, err := h.client.Core().Services(h.heapsterNamespace).
|
2015-11-06 17:55:31 +00:00
|
|
|
ProxyGet(h.heapsterScheme, h.heapsterService, h.heapsterPort, metricPath, map[string]string{"start": startTime.Format(time.RFC3339)}).
|
2015-08-28 10:24:00 +00:00
|
|
|
DoRaw()
|
|
|
|
|
|
|
|
if err != nil {
|
2015-12-02 08:24:17 +00:00
|
|
|
return nil, time.Time{}, fmt.Errorf("failed to get pods metrics: %v", err)
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var metrics heapster.MetricResultList
|
|
|
|
err = json.Unmarshal(resultRaw, &metrics)
|
|
|
|
if err != nil {
|
2015-12-02 08:24:17 +00:00
|
|
|
return nil, time.Time{}, fmt.Errorf("failed to unmarshall heapster response: %v", err)
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2016-02-08 16:07:05 +00:00
|
|
|
glog.V(4).Infof("Heapster metrics result: %s", string(resultRaw))
|
2015-08-28 10:24:00 +00:00
|
|
|
|
2016-01-27 20:14:45 +00:00
|
|
|
sum, count, timestamp := metricSpec.aggregator(metrics)
|
2015-08-28 10:24:00 +00:00
|
|
|
if count != len(podNames) {
|
2015-12-02 08:24:17 +00:00
|
|
|
return nil, time.Time{}, fmt.Errorf("metrics obtained for %d/%d of pods", count, len(podNames))
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2016-01-27 20:14:45 +00:00
|
|
|
return &sum, timestamp, nil
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 12:44:16 +00:00
|
|
|
func calculateSumFromTimeSample(metrics heapster.MetricResultList, duration time.Duration) (sum intAndFloat, count int, timestamp time.Time) {
|
2016-01-27 20:14:45 +00:00
|
|
|
sum = intAndFloat{0, 0}
|
2015-12-02 08:24:17 +00:00
|
|
|
count = 0
|
|
|
|
timestamp = time.Time{}
|
|
|
|
var oldest *time.Time // creation time of the oldest of used samples across pods
|
|
|
|
oldest = nil
|
2015-08-28 10:24:00 +00:00
|
|
|
for _, metrics := range metrics.Items {
|
2015-12-02 08:24:17 +00:00
|
|
|
var newest *heapster.MetricPoint // creation time of the newest sample for pod
|
2015-08-28 10:24:00 +00:00
|
|
|
newest = nil
|
2015-09-23 10:00:20 +00:00
|
|
|
for i, metricPoint := range metrics.Metrics {
|
2015-08-28 10:24:00 +00:00
|
|
|
if newest == nil || newest.Timestamp.Before(metricPoint.Timestamp) {
|
2015-09-23 10:00:20 +00:00
|
|
|
newest = &metrics.Metrics[i]
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if newest != nil {
|
2015-12-02 08:24:17 +00:00
|
|
|
if oldest == nil || newest.Timestamp.Before(*oldest) {
|
|
|
|
oldest = &newest.Timestamp
|
|
|
|
}
|
2016-02-26 12:44:16 +00:00
|
|
|
intervalSum := intAndFloat{0, 0}
|
|
|
|
intSumCount := 0
|
|
|
|
floatSumCount := 0
|
|
|
|
for _, metricPoint := range metrics.Metrics {
|
|
|
|
if metricPoint.Timestamp.Add(duration).After(newest.Timestamp) {
|
|
|
|
intervalSum.intValue += int64(metricPoint.Value)
|
|
|
|
intSumCount++
|
|
|
|
if metricPoint.FloatValue != nil {
|
|
|
|
intervalSum.floatValue += *metricPoint.FloatValue
|
|
|
|
floatSumCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
if newest.FloatValue == nil {
|
2016-02-26 12:44:16 +00:00
|
|
|
if intSumCount > 0 {
|
|
|
|
sum.intValue += int64(intervalSum.intValue / int64(intSumCount))
|
|
|
|
sum.floatValue += float64(intervalSum.intValue / int64(intSumCount))
|
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
} else {
|
2016-02-26 12:44:16 +00:00
|
|
|
if floatSumCount > 0 {
|
|
|
|
sum.intValue += int64(intervalSum.floatValue / float64(floatSumCount))
|
|
|
|
sum.floatValue += intervalSum.floatValue / float64(floatSumCount)
|
|
|
|
}
|
2016-01-27 20:14:45 +00:00
|
|
|
}
|
2015-08-28 10:24:00 +00:00
|
|
|
count++
|
|
|
|
}
|
|
|
|
}
|
2015-12-02 08:24:17 +00:00
|
|
|
if oldest != nil {
|
|
|
|
timestamp = *oldest
|
|
|
|
}
|
|
|
|
return sum, count, timestamp
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|