github.com/google/cloudprober@v0.11.3/metrics/map.go (about) 1 // Copyright 2017 The Cloudprober Authors. 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 metrics 16 17 import ( 18 "errors" 19 "fmt" 20 "sort" 21 "strconv" 22 "strings" 23 "sync" 24 ) 25 26 // Map implements a key-value store where keys are of type string and values 27 // are of type NumValue. 28 // It satisfies the Value interface. 29 type Map struct { 30 MapName string // Map key name 31 mu sync.RWMutex 32 m map[string]NumValue 33 keys []string 34 35 // total is only used to figure out if counter is moving up or down (reset). 36 total NumValue 37 38 // We use this to initialize new keys 39 defaultKeyValue NumValue 40 } 41 42 // NewMap returns a new Map 43 func NewMap(mapName string, defaultValue NumValue) *Map { 44 return &Map{ 45 MapName: mapName, 46 defaultKeyValue: defaultValue, 47 m: make(map[string]NumValue), 48 total: defaultValue.Clone().(NumValue), 49 } 50 } 51 52 // GetKey returns the given key's value. 53 func (m *Map) GetKey(key string) NumValue { 54 m.mu.RLock() 55 defer m.mu.RUnlock() 56 return m.m[key] 57 } 58 59 // Clone creates a clone of the Map. Clone makes sure that underlying data 60 // storage is properly cloned. 61 func (m *Map) Clone() Value { 62 m.mu.RLock() 63 defer m.mu.RUnlock() 64 newMap := &Map{ 65 MapName: m.MapName, 66 defaultKeyValue: m.defaultKeyValue.Clone().(NumValue), 67 m: make(map[string]NumValue), 68 total: m.total.Clone().(NumValue), 69 } 70 newMap.keys = make([]string, len(m.keys)) 71 for i, k := range m.keys { 72 newMap.m[k] = m.m[k].Clone().(NumValue) 73 newMap.keys[i] = m.keys[i] 74 } 75 return newMap 76 } 77 78 // Keys returns the list of keys 79 func (m *Map) Keys() []string { 80 m.mu.RLock() 81 defer m.mu.RUnlock() 82 return append([]string{}, m.keys...) 83 } 84 85 // newKey adds a new key to the map, with its value set to defaultKeyValue 86 // This is an unsafe function, callers should take care of protecting the map 87 // from race conditions. 88 func (m *Map) newKey(key string) { 89 m.keys = append(m.keys, key) 90 sort.Strings(m.keys) 91 m.m[key] = m.defaultKeyValue.Clone().(NumValue) 92 m.total.IncBy(m.defaultKeyValue) 93 } 94 95 // IncKey increments the given key's value by one. 96 func (m *Map) IncKey(key string) { 97 m.mu.Lock() 98 defer m.mu.Unlock() 99 if m.m[key] == nil { 100 m.newKey(key) 101 } 102 m.m[key].Inc() 103 m.total.Inc() 104 } 105 106 // IncKeyBy increments the given key's value by NumValue. 107 func (m *Map) IncKeyBy(key string, delta NumValue) { 108 m.mu.Lock() 109 defer m.mu.Unlock() 110 if m.m[key] == nil { 111 m.newKey(key) 112 } 113 m.m[key].IncBy(delta) 114 m.total.IncBy(delta) 115 } 116 117 // Add adds a value (type Value) to the receiver Map. A non-Map value returns 118 // an error. This is part of the Value interface. 119 func (m *Map) Add(val Value) error { 120 _, err := m.addOrSubtract(val, false) 121 return err 122 } 123 124 // SubtractCounter subtracts the provided "lastVal", assuming that value 125 // represents a counter, i.e. if "value" is less than "lastVal", we assume that 126 // counter has been reset and don't subtract. 127 func (m *Map) SubtractCounter(lastVal Value) (bool, error) { 128 return m.addOrSubtract(lastVal, true) 129 } 130 131 func (m *Map) addOrSubtract(val Value, subtract bool) (bool, error) { 132 delta, ok := val.(*Map) 133 if !ok { 134 return false, errors.New("incompatible value to add or subtract") 135 } 136 137 m.mu.Lock() 138 defer m.mu.Unlock() 139 delta.mu.RLock() 140 defer delta.mu.RUnlock() 141 142 if subtract && (m.total.Float64() < delta.total.Float64()) { 143 return true, nil 144 } 145 146 var sortRequired bool 147 for k, v := range delta.m { 148 if subtract { 149 // If a key is there in delta (lastVal) but not in the current val, 150 // assume metric has been reset. 151 if m.m[k] == nil { 152 return true, nil 153 } 154 m.m[k].SubtractCounter(v) 155 } else { 156 if m.m[k] == nil { 157 sortRequired = true 158 m.keys = append(m.keys, k) 159 m.m[k] = v 160 continue 161 } 162 m.m[k].Add(v) 163 } 164 } 165 if sortRequired { 166 sort.Strings(m.keys) 167 } 168 return false, nil 169 } 170 171 // AddInt64 generates a panic for the Map type. This is added only to satisfy 172 // the Value interface. 173 func (m *Map) AddInt64(i int64) { 174 panic("Map type doesn't implement AddInt64()") 175 } 176 177 // AddFloat64 generates a panic for the Map type. This is added only to 178 // satisfy the Value interface. 179 func (m *Map) AddFloat64(f float64) { 180 panic("Map type doesn't implement AddFloat64()") 181 } 182 183 // String returns the string representation of the receiver Map. 184 // This is part of the Value interface. 185 // map:key,k1:v1,k2:v2 186 func (m *Map) String() string { 187 m.mu.RLock() 188 defer m.mu.RUnlock() 189 190 var b strings.Builder 191 b.Grow(64) 192 193 b.WriteString("map:") 194 b.WriteString(m.MapName) 195 196 for _, k := range m.keys { 197 b.WriteByte(',') 198 b.WriteString(k) 199 b.WriteByte(':') 200 b.WriteString(m.m[k].String()) 201 } 202 return b.String() 203 } 204 205 // ParseMapFromString parses a map value string into a map object. 206 // Note that the values are always parsed as floats, so even a map with integer 207 // values will become a float map. 208 // For example: 209 // "map:code,200:10123,404:21" will be parsed as: 210 // "map:code 200:10123.000 404:21.000". 211 func ParseMapFromString(mapValue string) (*Map, error) { 212 tokens := strings.Split(mapValue, ",") 213 if len(tokens) < 1 { 214 return nil, errors.New("bad map value") 215 } 216 217 kv := strings.Split(tokens[0], ":") 218 if kv[0] != "map" { 219 return nil, errors.New("map value doesn't start with map:<key>") 220 } 221 222 m := NewMap(kv[1], NewFloat(0)) 223 224 for _, tok := range tokens[1:] { 225 kv := strings.Split(tok, ":") 226 if len(kv) != 2 { 227 return nil, errors.New("bad map value token: " + tok) 228 } 229 f, err := strconv.ParseFloat(kv[1], 64) 230 if err != nil { 231 return nil, fmt.Errorf("could not convert map key value %s to a float: %v", kv[1], err) 232 } 233 m.IncKeyBy(kv[0], NewFloat(f)) 234 } 235 236 return m, nil 237 }