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"
|
|
|
|
"k8s.io/kubernetes/pkg/api/resource"
|
2015-09-09 22:49:26 +00:00
|
|
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
2015-08-28 10:24:00 +00:00
|
|
|
"k8s.io/kubernetes/pkg/fields"
|
|
|
|
"k8s.io/kubernetes/pkg/labels"
|
|
|
|
|
|
|
|
heapster "k8s.io/heapster/api/v1/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2015-11-02 16:14:08 +00:00
|
|
|
DefaultHeapsterNamespace = "kube-system"
|
|
|
|
DefaultHeapsterService = "heapster"
|
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-10-13 15:24:23 +00:00
|
|
|
// GetCPUUtilization returns 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.
|
|
|
|
GetCPUUtilization(namespace string, selector map[string]string) (*int, error)
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2015-10-13 15:24:23 +00:00
|
|
|
// ResourceConsumption specifies consumption of a particular resource.
|
|
|
|
type ResourceConsumption struct {
|
|
|
|
Resource api.ResourceName
|
|
|
|
Quantity resource.Quantity
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Aggregates results into ResourceConsumption. Also returns number of
|
|
|
|
// pods included in the aggregation.
|
2015-10-13 15:24:23 +00:00
|
|
|
type metricAggregator func(heapster.MetricResultList) (ResourceConsumption, int)
|
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 {
|
|
|
|
client client.Interface
|
|
|
|
resourceDefinitions map[api.ResourceName]metricDefinition
|
2015-11-02 16:14:08 +00:00
|
|
|
heapsterNamespace string
|
|
|
|
heapsterService string
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var heapsterMetricDefinitions = map[api.ResourceName]metricDefinition{
|
|
|
|
api.ResourceCPU: {"cpu-usage",
|
2015-10-13 15:24:23 +00:00
|
|
|
func(metrics heapster.MetricResultList) (ResourceConsumption, int) {
|
2015-08-28 10:24:00 +00:00
|
|
|
sum, count := calculateSumFromLatestSample(metrics)
|
|
|
|
value := "0"
|
|
|
|
if count > 0 {
|
|
|
|
// assumes that cpu usage is in millis
|
|
|
|
value = fmt.Sprintf("%dm", sum/uint64(count))
|
|
|
|
}
|
2015-10-13 15:24:23 +00:00
|
|
|
return ResourceConsumption{Resource: api.ResourceCPU, Quantity: resource.MustParse(value)}, count
|
2015-08-28 10:24:00 +00:00
|
|
|
}},
|
2015-09-08 10:12:28 +00:00
|
|
|
api.ResourceMemory: {"memory-usage",
|
2015-10-13 15:24:23 +00:00
|
|
|
func(metrics heapster.MetricResultList) (ResourceConsumption, int) {
|
2015-09-08 10:12:28 +00:00
|
|
|
sum, count := calculateSumFromLatestSample(metrics)
|
|
|
|
value := int64(0)
|
|
|
|
if count > 0 {
|
|
|
|
value = int64(sum) / int64(count)
|
|
|
|
}
|
2015-10-13 15:24:23 +00:00
|
|
|
return ResourceConsumption{Resource: api.ResourceMemory, Quantity: *resource.NewQuantity(value, resource.DecimalSI)}, count
|
2015-09-08 10:12:28 +00:00
|
|
|
}},
|
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.
|
2015-11-02 16:14:08 +00:00
|
|
|
func NewHeapsterMetricsClient(client client.Interface, namespace, service string) *HeapsterMetricsClient {
|
2015-10-13 15:24:23 +00:00
|
|
|
return &HeapsterMetricsClient{
|
|
|
|
client: client,
|
2015-08-28 10:24:00 +00:00
|
|
|
resourceDefinitions: heapsterMetricDefinitions,
|
2015-11-02 16:14:08 +00:00
|
|
|
heapsterNamespace: namespace,
|
|
|
|
heapsterService: service,
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-13 15:24:23 +00:00
|
|
|
func (h *HeapsterMetricsClient) GetCPUUtilization(namespace string, selector map[string]string) (*int, error) {
|
|
|
|
consumption, request, err := h.GetResourceConsumptionAndRequest(api.ResourceCPU, namespace, selector)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get CPU consumption and request: %v", err)
|
|
|
|
}
|
|
|
|
utilization := new(int)
|
|
|
|
*utilization = int(float64(consumption.Quantity.MilliValue()) / float64(request.MilliValue()) * 100)
|
|
|
|
return utilization, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HeapsterMetricsClient) GetResourceConsumptionAndRequest(resourceName api.ResourceName, namespace string, selector map[string]string) (consumption *ResourceConsumption, request *resource.Quantity, err error) {
|
|
|
|
podList, err := h.client.Pods(namespace).
|
2015-08-28 10:24:00 +00:00
|
|
|
List(labels.SelectorFromSet(labels.Set(selector)), fields.Everything())
|
|
|
|
|
|
|
|
if err != nil {
|
2015-10-13 15:24:23 +00:00
|
|
|
return nil, nil, fmt.Errorf("failed to get pod list: %v", err)
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
podNames := []string{}
|
2015-10-13 15:24:23 +00:00
|
|
|
sum := resource.MustParse("0")
|
|
|
|
missing := false
|
2015-08-28 10:24:00 +00:00
|
|
|
for _, pod := range podList.Items {
|
|
|
|
podNames = append(podNames, pod.Name)
|
2015-10-13 15:24:23 +00:00
|
|
|
for _, container := range pod.Spec.Containers {
|
|
|
|
containerRequest := container.Resources.Requests[resourceName]
|
|
|
|
if containerRequest.Amount != nil {
|
|
|
|
sum.Add(containerRequest)
|
|
|
|
} else {
|
|
|
|
missing = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if missing || sum.Cmp(resource.MustParse("0")) == 0 {
|
|
|
|
return nil, nil, fmt.Errorf("some pods do not have request for %s", resourceName)
|
|
|
|
}
|
|
|
|
glog.Infof("Sum of %s requested: %v", resourceName, sum)
|
|
|
|
avg := resource.MustParse(fmt.Sprintf("%dm", sum.MilliValue()/int64(len(podList.Items))))
|
|
|
|
request = &avg
|
|
|
|
consumption, err = h.getForPods(resourceName, namespace, podNames)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
2015-10-13 15:24:23 +00:00
|
|
|
return consumption, request, nil
|
2015-08-28 10:24:00 +00:00
|
|
|
}
|
|
|
|
|
2015-10-13 15:24:23 +00:00
|
|
|
func (h *HeapsterMetricsClient) getForPods(resourceName api.ResourceName, namespace string, podNames []string) (*ResourceConsumption, error) {
|
2015-08-28 10:24:00 +00:00
|
|
|
metricSpec, metricDefined := h.resourceDefinitions[resourceName]
|
|
|
|
if !metricDefined {
|
|
|
|
return nil, fmt.Errorf("heapster metric not defined for %v", resourceName)
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
|
2015-11-02 16:14:08 +00:00
|
|
|
resultRaw, err := h.client.Services(h.heapsterNamespace).
|
|
|
|
ProxyGet(h.heapsterService, metricPath, map[string]string{"start": startTime.Format(time.RFC3339)}).
|
2015-08-28 10:24:00 +00:00
|
|
|
DoRaw()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get pods metrics: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var metrics heapster.MetricResultList
|
|
|
|
err = json.Unmarshal(resultRaw, &metrics)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to unmarshall heapster response: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.Infof("Metrics available: %s", string(resultRaw))
|
|
|
|
|
|
|
|
currentConsumption, count := metricSpec.aggregator(metrics)
|
|
|
|
if count != len(podNames) {
|
|
|
|
return nil, fmt.Errorf("metrics obtained for %d/%d of pods", count, len(podNames))
|
|
|
|
}
|
|
|
|
|
|
|
|
return ¤tConsumption, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func calculateSumFromLatestSample(metrics heapster.MetricResultList) (uint64, int) {
|
|
|
|
sum := uint64(0)
|
|
|
|
count := 0
|
|
|
|
for _, metrics := range metrics.Items {
|
|
|
|
var newest *heapster.MetricPoint
|
|
|
|
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 {
|
|
|
|
sum += newest.Value
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sum, count
|
|
|
|
}
|