vitess.io/vitess@v0.16.2/go/vt/vtorc/collection/collection.go (about) 1 /* 2 Copyright 2017 Simon J Mudd 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 /* 18 Package collection holds routines for collecting "high frequency" 19 metrics and handling their auto-expiry based on a configured retention 20 time. This becomes more interesting as the number of MySQL servers 21 monitored by vtorc increases. 22 23 Most monitoring systems look at different metrics over a period 24 like 1, 10, 30 or 60 seconds but even at second resolution vtorc 25 may have polled a number of servers. 26 27 It can be helpful to collect the raw values, and then allow external 28 monitoring to pull via an http api call either pre-cooked aggregate 29 data or the raw data for custom analysis over the period requested. 30 31 This is expected to be used for the following types of metric: 32 33 - discovery metrics (time to poll a MySQL server and collect status) 34 - queue metrics (statistics within the discovery queue itself) 35 - query metrics (statistics on the number of queries made to the 36 backend MySQL database) 37 38 VTOrc code can just add a new metric without worrying about 39 removing it later, and other code which serves API requests can 40 pull out the data when needed for the requested time period. 41 42 For current metrics two api urls have been provided: one provides 43 the raw data and the other one provides a single set of aggregate 44 data which is suitable for easy collection by monitoring systems. 45 46 Expiry is triggered by default if the collection is created via 47 CreateOrReturnCollection() and uses an expiry period of 48 DiscoveryCollectionRetentionSeconds. It can also be enabled by 49 calling StartAutoExpiration() after setting the required expire 50 period with SetExpirePeriod(). 51 52 This will trigger periodic calls (every second) to ensure the removal 53 of metrics which have passed the time specified. Not enabling expiry 54 will mean data is collected but never freed which will make 55 vtorc run out of memory eventually. 56 57 Current code uses DiscoveryCollectionRetentionSeconds as the 58 time to keep metric data. 59 */ 60 package collection 61 62 import ( 63 "errors" 64 "sync" 65 "time" 66 67 // "vitess.io/vitess/go/vt/log" 68 69 "vitess.io/vitess/go/vt/vtorc/config" 70 ) 71 72 // Metric is an interface containing a metric 73 type Metric interface { 74 When() time.Time // when the metric was taken 75 } 76 77 // Collection contains a collection of Metrics 78 type Collection struct { 79 sync.Mutex // for locking the structure 80 monitoring bool // am I monitoring the queue size? 81 collection []Metric 82 done chan struct{} // to indicate that we are finishing expiry processing 83 expirePeriod time.Duration // time to keep the collection information for 84 } 85 86 // hard-coded at every second 87 const defaultExpireTickerPeriod = time.Second 88 89 // backendMetricCollection contains the last N backend "channelled" 90 // metrics which can then be accessed via an API call for monitoring. 91 var ( 92 namedCollection map[string](*Collection) 93 namedCollectionLock sync.Mutex 94 ) 95 96 func init() { 97 namedCollection = make(map[string](*Collection)) 98 } 99 100 // StopMonitoring stops monitoring all the collections 101 func StopMonitoring() { 102 for _, q := range namedCollection { 103 q.StopAutoExpiration() 104 } 105 } 106 107 // CreateOrReturnCollection allows for creation of a new collection or 108 // returning a pointer to an existing one given the name. This allows access 109 // to the data structure from the api interface (http/api.go) and also when writing (inst). 110 func CreateOrReturnCollection(name string) *Collection { 111 namedCollectionLock.Lock() 112 defer namedCollectionLock.Unlock() 113 if q, found := namedCollection[name]; found { 114 return q 115 } 116 117 qmc := &Collection{ 118 collection: nil, 119 done: make(chan struct{}), 120 // WARNING: use a different configuration name 121 expirePeriod: time.Duration(config.DiscoveryCollectionRetentionSeconds) * time.Second, 122 } 123 go qmc.StartAutoExpiration() 124 125 namedCollection[name] = qmc 126 127 return qmc 128 } 129 130 // SetExpirePeriod determines after how long the collected data should be removed 131 func (c *Collection) SetExpirePeriod(duration time.Duration) { 132 c.Lock() 133 defer c.Unlock() 134 135 c.expirePeriod = duration 136 } 137 138 // ExpirePeriod returns the currently configured expiration period 139 func (c *Collection) ExpirePeriod() time.Duration { 140 c.Lock() 141 defer c.Unlock() 142 return c.expirePeriod 143 } 144 145 // StopAutoExpiration prepares to stop by terminating the auto-expiration process 146 func (c *Collection) StopAutoExpiration() { 147 if c == nil { 148 return 149 } 150 c.Lock() 151 if !c.monitoring { 152 c.Unlock() 153 return 154 } 155 c.monitoring = false 156 c.Unlock() 157 158 // no locking here deliberately 159 c.done <- struct{}{} 160 } 161 162 // StartAutoExpiration initiates the auto expiry procedure which 163 // periodically checks for metrics in the collection which need to 164 // be expired according to bc.ExpirePeriod. 165 func (c *Collection) StartAutoExpiration() { 166 if c == nil { 167 return 168 } 169 c.Lock() 170 if c.monitoring { 171 c.Unlock() 172 return 173 } 174 c.monitoring = true 175 c.Unlock() 176 177 //log.Infof("StartAutoExpiration: %p with expirePeriod: %v", c, c.expirePeriod) 178 ticker := time.NewTicker(defaultExpireTickerPeriod) 179 180 for { 181 select { 182 case <-ticker.C: // do the periodic expiry 183 _ = c.removeBefore(time.Now().Add(-c.expirePeriod)) 184 case <-c.done: // stop the ticker and return 185 ticker.Stop() 186 return 187 } 188 } 189 } 190 191 // Metrics returns a slice containing all the metric values 192 func (c *Collection) Metrics() []Metric { 193 if c == nil { 194 return nil 195 } 196 c.Lock() 197 defer c.Unlock() 198 199 if len(c.collection) == 0 { 200 return nil // nothing to return 201 } 202 return c.collection 203 } 204 205 // Since returns the Metrics on or after the given time. We assume 206 // the metrics are stored in ascending time. 207 // Iterate backwards until we reach the first value before the given time 208 // or the end of the array. 209 func (c *Collection) Since(t time.Time) ([]Metric, error) { 210 if c == nil { 211 return nil, errors.New("Collection.Since: c == nil") 212 } 213 c.Lock() 214 defer c.Unlock() 215 if len(c.collection) == 0 { 216 return nil, nil // nothing to return 217 } 218 last := len(c.collection) 219 first := last - 1 220 221 done := false 222 for !done { 223 if c.collection[first].When().After(t) || c.collection[first].When().Equal(t) { 224 if first == 0 { 225 break // as can't go lower 226 } 227 first-- 228 } else { 229 if first != last { 230 first++ // go back one (except if we're already at the end) 231 } 232 break 233 } 234 } 235 236 return c.collection[first:last], nil 237 } 238 239 // removeBefore is called by StartAutoExpiration and removes collection values 240 // before the given time. 241 func (c *Collection) removeBefore(t time.Time) error { 242 if c == nil { 243 return errors.New("Collection.removeBefore: c == nil") 244 } 245 c.Lock() 246 defer c.Unlock() 247 248 cLen := len(c.collection) 249 if cLen == 0 { 250 return nil // we have a collection but no data 251 } 252 // remove old data here. 253 first := 0 254 done := false 255 for !done { 256 if c.collection[first].When().Before(t) { 257 first++ 258 if first == cLen { 259 break 260 } 261 } else { 262 first-- 263 break 264 } 265 } 266 267 // get the interval we need. 268 if first == len(c.collection) { 269 c.collection = nil // remove all entries 270 } else if first != -1 { 271 c.collection = c.collection[first:] 272 } 273 return nil // no errors 274 } 275 276 // Append a new Metric to the existing collection 277 func (c *Collection) Append(m Metric) error { 278 if c == nil { 279 return errors.New("Collection.Append: c == nil") 280 } 281 c.Lock() 282 defer c.Unlock() 283 // we don't want to add nil metrics 284 if m == nil { 285 return errors.New("Collection.Append: m == nil") 286 } 287 c.collection = append(c.collection, m) 288 289 return nil 290 }