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  }