github.com/mrgossett/heapster@v0.18.2/model/aggregation.go (about)

     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  
    15  package model
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  
    21  	"k8s.io/heapster/store/daystore"
    22  	"k8s.io/heapster/store/statstore"
    23  )
    24  
    25  // aggregationStep performs a Metric Aggregation step on the cluster.
    26  // The Metrics fields of all Namespaces, Pods and the Cluster are populated,
    27  // by Timeseries summation of the respective Metrics fields.
    28  // aggregationStep should be called after new data is present in the cluster,
    29  // but before the cluster timestamp is updated.
    30  
    31  // The latestTime argument represents the latest time of metrics found in the model,
    32  // which should cause all aggregated metrics to remain constant up until that time.
    33  func (rc *realModel) aggregationStep(latestTime time.Time) error {
    34  	rc.lock.Lock()
    35  	defer rc.lock.Unlock()
    36  
    37  	// Perform Node Metric Aggregation
    38  	node_c := make(chan error)
    39  	go rc.aggregateNodeMetrics(node_c, latestTime)
    40  
    41  	// Initiate bottom-up aggregation for Kubernetes stats
    42  	kube_c := make(chan error)
    43  	go rc.aggregateKubeMetrics(kube_c, latestTime)
    44  
    45  	errs := make([]error, 2)
    46  	errs[0] = <-node_c
    47  	errs[1] = <-kube_c
    48  
    49  	if errs[0] != nil {
    50  		return errs[0]
    51  	}
    52  	if errs[1] != nil {
    53  		return errs[1]
    54  	}
    55  
    56  	return nil
    57  }
    58  
    59  // aggregateNodeMetrics populates the Cluster.InfoType.Metrics field by adding up all node metrics.
    60  // Assumes an appropriate lock is already taken by the caller.
    61  func (rc *realModel) aggregateNodeMetrics(c chan error, latestTime time.Time) {
    62  	if len(rc.Nodes) == 0 {
    63  		// Fail silently if the cluster has no nodes
    64  		c <- nil
    65  		return
    66  	}
    67  
    68  	sources := []*InfoType{}
    69  	for _, node := range rc.Nodes {
    70  		sources = append(sources, &(node.InfoType))
    71  	}
    72  	c <- rc.aggregateMetrics(&rc.ClusterInfo.InfoType, sources, latestTime)
    73  }
    74  
    75  // aggregateKubeMetrics initiates depth-first aggregation of Kubernetes metrics.
    76  // Assumes an appropriate lock is already taken by the caller.
    77  func (rc *realModel) aggregateKubeMetrics(c chan error, latestTime time.Time) {
    78  	if len(rc.Namespaces) == 0 {
    79  		// Fail silently if the cluster has no namespaces
    80  		c <- nil
    81  		return
    82  	}
    83  
    84  	// Perform aggregation for all the namespaces
    85  	chans := make([]chan error, 0)
    86  	for _, namespace := range rc.Namespaces {
    87  		chans = append(chans, make(chan error))
    88  		go rc.aggregateNamespaceMetrics(namespace, chans[len(chans)-1], latestTime)
    89  	}
    90  
    91  	errs := make([]error, len(chans))
    92  	for _, channel := range chans {
    93  		errs = append(errs, <-channel)
    94  	}
    95  
    96  	for _, err := range errs {
    97  		if err != nil {
    98  			c <- err
    99  			return
   100  		}
   101  	}
   102  
   103  	c <- nil
   104  }
   105  
   106  // aggregateNamespaceMetrics populates a NamespaceInfo.Metrics field by aggregating all PodInfo.
   107  // Assumes an appropriate lock is already taken by the caller.
   108  func (rc *realModel) aggregateNamespaceMetrics(namespace *NamespaceInfo, c chan error, latestTime time.Time) {
   109  	if namespace == nil {
   110  		c <- fmt.Errorf("nil Namespace pointer passed for aggregation")
   111  		return
   112  	}
   113  	if len(namespace.Pods) == 0 {
   114  		// Fail silently if the namespace has no pods
   115  		c <- nil
   116  		return
   117  	}
   118  
   119  	// Perform aggregation on all the Pods
   120  	chans := make([]chan error, 0)
   121  	for _, pod := range namespace.Pods {
   122  		chans = append(chans, make(chan error))
   123  		go rc.aggregatePodMetrics(pod, chans[len(chans)-1], latestTime)
   124  	}
   125  
   126  	errs := make([]error, len(chans))
   127  	for _, channel := range chans {
   128  		errs = append(errs, <-channel)
   129  	}
   130  
   131  	for _, err := range errs {
   132  		if err != nil {
   133  			c <- err
   134  			return
   135  		}
   136  	}
   137  
   138  	// Collect the Pod InfoTypes after aggregation is complete
   139  	sources := []*InfoType{}
   140  	for _, pod := range namespace.Pods {
   141  		sources = append(sources, &(pod.InfoType))
   142  	}
   143  	c <- rc.aggregateMetrics(&namespace.InfoType, sources, latestTime)
   144  }
   145  
   146  // aggregatePodMetrics populates a PodInfo.Metrics field by aggregating all ContainerInfo.
   147  // Assumes an appropriate lock is already taken by the caller.
   148  func (rc *realModel) aggregatePodMetrics(pod *PodInfo, c chan error, latestTime time.Time) {
   149  	if pod == nil {
   150  		c <- fmt.Errorf("nil Pod pointer passed for aggregation")
   151  		return
   152  	}
   153  	if len(pod.Containers) == 0 {
   154  		// Fail silently if the pod has no containers
   155  		c <- nil
   156  		return
   157  	}
   158  
   159  	// Collect the Container InfoTypes
   160  	sources := []*InfoType{}
   161  	for _, container := range pod.Containers {
   162  		sources = append(sources, &(container.InfoType))
   163  	}
   164  	c <- rc.aggregateMetrics(&pod.InfoType, sources, latestTime)
   165  }
   166  
   167  // aggregateMetrics populates an InfoType by adding metrics across a slice of InfoTypes.
   168  // Only metrics taken after the cluster timestamp are affected.
   169  // Assumes an appropriate lock is already taken by the caller.
   170  func (rc *realModel) aggregateMetrics(target *InfoType, sources []*InfoType, latestTime time.Time) error {
   171  	zeroTime := time.Time{}
   172  
   173  	if target == nil {
   174  		return fmt.Errorf("nil InfoType pointer provided as aggregation target")
   175  	}
   176  	if len(sources) == 0 {
   177  		return fmt.Errorf("empty sources slice provided")
   178  	}
   179  	for _, source := range sources {
   180  		if source == nil {
   181  			return fmt.Errorf("nil InfoType pointer provided as an aggregation source")
   182  		}
   183  		if source == target {
   184  			return fmt.Errorf("target InfoType pointer is provided as a source")
   185  		}
   186  	}
   187  
   188  	if latestTime.Equal(zeroTime) {
   189  		return fmt.Errorf("aggregateMetrics called with a zero latestTime argument")
   190  	}
   191  
   192  	// Create a map of []TimePoint as a timeseries accumulator per metric
   193  	newMetrics := make(map[string][]statstore.TimePoint)
   194  
   195  	// Reduce the sources slice with timeseries addition for each metric
   196  	for _, info := range sources {
   197  		for key, ds := range info.Metrics {
   198  			_, ok := newMetrics[key]
   199  			if !ok {
   200  				// Metric does not exist on target map, create a new timeseries
   201  				newMetrics[key] = []statstore.TimePoint{}
   202  			}
   203  			// Perform timeseries addition between the accumulator and the current source
   204  			sourceDS := (*ds).Hour.Get(rc.timestamp, zeroTime)
   205  			newMetrics[key] = addMatchingTimeseries(newMetrics[key], sourceDS)
   206  		}
   207  	}
   208  
   209  	// Put all the new values in the DayStores under target
   210  	for key, tpSlice := range newMetrics {
   211  		if len(tpSlice) == 0 {
   212  			continue
   213  		}
   214  		_, ok := target.Metrics[key]
   215  		if !ok {
   216  			// Metric does not exist on target InfoType, create DayStore
   217  			target.Metrics[key] = daystore.NewDayStore(epsilonFromMetric(key), rc.resolution)
   218  		}
   219  
   220  		// Put the added TimeSeries in the corresponding DayStore, in time-ascending order
   221  		for i := len(tpSlice) - 1; i >= 0; i-- {
   222  			err := target.Metrics[key].Put(tpSlice[i])
   223  			if err != nil {
   224  				return fmt.Errorf("error while performing aggregation: %s", err)
   225  			}
   226  		}
   227  
   228  		// Put a TimePoint with the latest aggregated value at the latest model resolution.
   229  		// Causes the DayStore to assume the aggregated metric remained constant until the -
   230  		// next cluster timestamp.
   231  		newTP := statstore.TimePoint{
   232  			Timestamp: latestTime,
   233  			Value:     tpSlice[0].Value,
   234  		}
   235  		err := target.Metrics[key].Put(newTP)
   236  		if err != nil {
   237  			return fmt.Errorf("error while performing aggregation: %s", err)
   238  		}
   239  
   240  	}
   241  
   242  	// Set the creation time of the entity to the earliest one that we have found data for.
   243  	earliestCreation := sources[0].Creation
   244  	for _, info := range sources[1:] {
   245  		if info.Creation.Before(earliestCreation) && info.Creation.After(time.Time{}) {
   246  			earliestCreation = info.Creation
   247  		}
   248  	}
   249  	if earliestCreation.Before(target.Creation) || target.Creation.Equal(time.Time{}) {
   250  		target.Creation = earliestCreation
   251  	}
   252  
   253  	return nil
   254  }