github.com/mrgossett/heapster@v0.18.2/model/util.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 "strings" 20 "time" 21 22 "k8s.io/heapster/store/daystore" 23 "k8s.io/heapster/store/statstore" 24 ) 25 26 // latestTimestamp returns its largest time.Time argument 27 func latestTimestamp(first time.Time, second time.Time) time.Time { 28 if first.After(second) { 29 return first 30 } 31 return second 32 } 33 34 // newInfoType is an InfoType Constructor, which returns a new InfoType. 35 // Initial fields for the new InfoType can be provided as arguments. 36 // A nil argument results in a newly-allocated map for that field. 37 func newInfoType(metrics map[string]*daystore.DayStore, labels map[string]string, context map[string]*statstore.TimePoint) InfoType { 38 if metrics == nil { 39 metrics = make(map[string]*daystore.DayStore) 40 } 41 if labels == nil { 42 labels = make(map[string]string) 43 } 44 if context == nil { 45 context = make(map[string]*statstore.TimePoint) 46 } 47 return InfoType{ 48 Creation: time.Time{}, 49 Metrics: metrics, 50 Labels: labels, 51 Context: context, 52 } 53 } 54 55 // addContainerToMap creates or finds a ContainerInfo element under a map[string]*ContainerInfo 56 func addContainerToMap(container_name string, dict map[string]*ContainerInfo) *ContainerInfo { 57 var container_ptr *ContainerInfo 58 59 if val, ok := dict[container_name]; ok { 60 // A container already exists under that name, return the address 61 container_ptr = val 62 } else { 63 container_ptr = &ContainerInfo{ 64 InfoType: newInfoType(nil, nil, nil), 65 } 66 dict[container_name] = container_ptr 67 } 68 return container_ptr 69 } 70 71 // addTimePoints adds the values of two TimePoints as uint64. 72 // addTimePoints returns a new TimePoint with the added Value fields 73 // and the Timestamp of the first TimePoint. 74 func addTimePoints(tp1 statstore.TimePoint, tp2 statstore.TimePoint) statstore.TimePoint { 75 maxTS := tp1.Timestamp 76 if maxTS.Before(tp2.Timestamp) { 77 maxTS = tp2.Timestamp 78 } 79 return statstore.TimePoint{ 80 Timestamp: maxTS, 81 Value: tp1.Value + tp2.Value, 82 } 83 } 84 85 // popTPSlice pops the first element of a TimePoint Slice, removing it from the slice. 86 // popTPSlice receives a *[]TimePoint and returns its first element. 87 func popTPSlice(tps_ptr *[]statstore.TimePoint) *statstore.TimePoint { 88 if tps_ptr == nil { 89 return nil 90 } 91 tps := *tps_ptr 92 if len(tps) == 0 { 93 return nil 94 } 95 res := tps[0] 96 if len(tps) == 1 { 97 (*tps_ptr) = tps[0:0] 98 } 99 (*tps_ptr) = tps[1:] 100 return &res 101 } 102 103 // addMatchingTimeseries performs addition over two timeseries with unique timestamps. 104 // addMatchingTimeseries returns a []TimePoint of the resulting aggregated timeseries. 105 // Assumes time-descending order of both []TimePoint parameters and the return slice. 106 func addMatchingTimeseries(left []statstore.TimePoint, right []statstore.TimePoint) []statstore.TimePoint { 107 var cur_left *statstore.TimePoint 108 var cur_right *statstore.TimePoint 109 result := []statstore.TimePoint{} 110 111 // Merge timeseries into result until either one is empty 112 cur_left = popTPSlice(&left) 113 cur_right = popTPSlice(&right) 114 for cur_left != nil && cur_right != nil { 115 result = append(result, addTimePoints(*cur_left, *cur_right)) 116 if cur_left.Timestamp.Equal(cur_right.Timestamp) { 117 cur_left = popTPSlice(&left) 118 cur_right = popTPSlice(&right) 119 } else if cur_left.Timestamp.After(cur_right.Timestamp) { 120 cur_left = popTPSlice(&left) 121 } else { 122 cur_right = popTPSlice(&right) 123 } 124 } 125 if cur_left == nil && cur_right != nil { 126 result = append(result, *cur_right) 127 } else if cur_left != nil && cur_right == nil { 128 result = append(result, *cur_left) 129 } 130 131 // Append leftover elements from non-empty timeseries 132 if len(left) > 0 { 133 result = append(result, left...) 134 } else if len(right) > 0 { 135 result = append(result, right...) 136 } 137 138 return result 139 } 140 141 // instantFromCumulativeMetric calculates the value of an instantaneous metric from two 142 // points of a cumulative metric, such as cpu/usage. 143 // The inputs are the value and timestamp of the newer cumulative datapoint, 144 // and a pointer to a TimePoint holding the previous cumulative datapoint. 145 func instantFromCumulativeMetric(value uint64, stamp time.Time, prev *statstore.TimePoint) (uint64, error) { 146 if prev == nil { 147 return uint64(0), fmt.Errorf("unable to calculate instant metric with nil previous TimePoint") 148 } 149 if !stamp.After(prev.Timestamp) { 150 return uint64(0), fmt.Errorf("the previous TimePoint is not earlier in time than the newer one") 151 } 152 tdelta := uint64(stamp.Sub(prev.Timestamp).Nanoseconds()) 153 // Divide metric by nanoseconds that have elapsed, multiply by 1000 to get an unsigned metric 154 if value < prev.Value { 155 return uint64(0), fmt.Errorf("the provided value %d is less than the previous one %d", value, prev.Value) 156 } 157 // Divide metric by nanoseconds that have elapsed, multiply by 1000 to get an unsigned metric 158 vdelta := (value - prev.Value) * 1000 159 160 instaVal := vdelta / tdelta 161 prev.Value = value 162 prev.Timestamp = stamp 163 return instaVal, nil 164 } 165 166 // getStats extracts derived stats from an InfoType. 167 func getStats(info InfoType) map[string]StatBundle { 168 res := make(map[string]StatBundle) 169 for key, ds := range info.Metrics { 170 last, lastMax, _ := ds.Hour.Last() 171 minAvg := last.Value 172 minPct := lastMax 173 minMax := lastMax 174 hourAvg, _ := ds.Hour.Average() 175 hourPct, _ := ds.Hour.Percentile(0.95) 176 hourMax, _ := ds.Hour.Max() 177 dayAvg, _ := ds.Average() 178 dayPct, _ := ds.NinetyFifth() 179 dayMax, _ := ds.Max() 180 181 res[key] = StatBundle{ 182 Minute: Stats{ 183 Average: minAvg, 184 NinetyFifth: minPct, 185 Max: minMax, 186 }, 187 Hour: Stats{ 188 Average: hourAvg, 189 NinetyFifth: hourPct, 190 Max: hourMax, 191 }, 192 Day: Stats{ 193 Average: dayAvg, 194 NinetyFifth: dayPct, 195 Max: dayMax, 196 }, 197 } 198 } 199 return res 200 } 201 202 func epsilonFromMetric(metric string) uint64 { 203 switch metric { 204 case cpuLimit: 205 return cpuLimitEpsilon 206 case cpuUsage: 207 return cpuUsageEpsilon 208 case memLimit: 209 return memLimitEpsilon 210 case memUsage: 211 return memUsageEpsilon 212 case memWorking: 213 return memWorkingEpsilon 214 default: 215 if strings.Contains(metric, fsLimit) { 216 return fsLimitEpsilon 217 } 218 219 if strings.Contains(metric, fsUsage) { 220 return fsUsageEpsilon 221 } 222 223 return defaultEpsilon 224 } 225 }