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 }