github.com/google/cadvisor@v0.49.1/utils/timed_store.go (about)

     1  // Copyright 2014 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 utils
    16  
    17  import (
    18  	"sort"
    19  	"time"
    20  )
    21  
    22  type timedStoreDataSlice []timedStoreData
    23  
    24  func (t timedStoreDataSlice) Less(i, j int) bool {
    25  	return t[i].timestamp.Before(t[j].timestamp)
    26  }
    27  
    28  func (t timedStoreDataSlice) Len() int {
    29  	return len(t)
    30  }
    31  
    32  func (t timedStoreDataSlice) Swap(i, j int) {
    33  	t[i], t[j] = t[j], t[i]
    34  }
    35  
    36  // A time-based buffer for ContainerStats.
    37  // Holds information for a specific time period and/or a max number of items.
    38  type TimedStore struct {
    39  	buffer   timedStoreDataSlice
    40  	age      time.Duration
    41  	maxItems int
    42  }
    43  
    44  type timedStoreData struct {
    45  	timestamp time.Time
    46  	data      interface{}
    47  }
    48  
    49  // Returns a new thread-compatible TimedStore.
    50  // A maxItems value of -1 means no limit.
    51  func NewTimedStore(age time.Duration, maxItems int) *TimedStore {
    52  	return &TimedStore{
    53  		buffer:   make(timedStoreDataSlice, 0),
    54  		age:      age,
    55  		maxItems: maxItems,
    56  	}
    57  }
    58  
    59  // Adds an element to the start of the buffer (removing one from the end if necessary).
    60  func (s *TimedStore) Add(timestamp time.Time, item interface{}) {
    61  	data := timedStoreData{
    62  		timestamp: timestamp,
    63  		data:      item,
    64  	}
    65  	// Common case: data is added in order.
    66  	if len(s.buffer) == 0 || !timestamp.Before(s.buffer[len(s.buffer)-1].timestamp) {
    67  		s.buffer = append(s.buffer, data)
    68  	} else {
    69  		// Data is out of order; insert it in the correct position.
    70  		index := sort.Search(len(s.buffer), func(index int) bool {
    71  			return s.buffer[index].timestamp.After(timestamp)
    72  		})
    73  		s.buffer = append(s.buffer, timedStoreData{}) // Make room to shift the elements
    74  		copy(s.buffer[index+1:], s.buffer[index:])    // Shift the elements over
    75  		s.buffer[index] = data
    76  	}
    77  
    78  	// Remove any elements before eviction time.
    79  	// TODO(rjnagal): This is assuming that the added entry has timestamp close to now.
    80  	evictTime := timestamp.Add(-s.age)
    81  	index := sort.Search(len(s.buffer), func(index int) bool {
    82  		return s.buffer[index].timestamp.After(evictTime)
    83  	})
    84  	if index < len(s.buffer) {
    85  		s.buffer = s.buffer[index:]
    86  	}
    87  
    88  	// Remove any elements if over our max size.
    89  	if s.maxItems >= 0 && len(s.buffer) > s.maxItems {
    90  		startIndex := len(s.buffer) - s.maxItems
    91  		s.buffer = s.buffer[startIndex:]
    92  	}
    93  }
    94  
    95  // Returns up to maxResult elements in the specified time period (inclusive).
    96  // Results are from first to last. maxResults of -1 means no limit.
    97  func (s *TimedStore) InTimeRange(start, end time.Time, maxResults int) []interface{} {
    98  	// No stats, return empty.
    99  	if len(s.buffer) == 0 {
   100  		return []interface{}{}
   101  	}
   102  
   103  	var startIndex int
   104  	if start.IsZero() {
   105  		// None specified, start at the beginning.
   106  		startIndex = len(s.buffer) - 1
   107  	} else {
   108  		// Start is the index before the elements smaller than it. We do this by
   109  		// finding the first element smaller than start and taking the index
   110  		// before that element
   111  		startIndex = sort.Search(len(s.buffer), func(index int) bool {
   112  			// buffer[index] < start
   113  			return s.getData(index).timestamp.Before(start)
   114  		}) - 1
   115  		// Check if start is after all the data we have.
   116  		if startIndex < 0 {
   117  			return []interface{}{}
   118  		}
   119  	}
   120  
   121  	var endIndex int
   122  	if end.IsZero() {
   123  		// None specified, end with the latest stats.
   124  		endIndex = 0
   125  	} else {
   126  		// End is the first index smaller than or equal to it (so, not larger).
   127  		endIndex = sort.Search(len(s.buffer), func(index int) bool {
   128  			// buffer[index] <= t -> !(buffer[index] > t)
   129  			return !s.getData(index).timestamp.After(end)
   130  		})
   131  		// Check if end is before all the data we have.
   132  		if endIndex == len(s.buffer) {
   133  			return []interface{}{}
   134  		}
   135  	}
   136  
   137  	// Trim to maxResults size.
   138  	numResults := startIndex - endIndex + 1
   139  	if maxResults != -1 && numResults > maxResults {
   140  		startIndex -= numResults - maxResults
   141  		numResults = maxResults
   142  	}
   143  
   144  	// Return in sorted timestamp order so from the "back" to "front".
   145  	result := make([]interface{}, numResults)
   146  	for i := 0; i < numResults; i++ {
   147  		result[i] = s.Get(startIndex - i)
   148  	}
   149  	return result
   150  }
   151  
   152  // Gets the element at the specified index. Note that elements are output in LIFO order.
   153  func (s *TimedStore) Get(index int) interface{} {
   154  	return s.getData(index).data
   155  }
   156  
   157  // Gets the data at the specified index. Note that elements are output in LIFO order.
   158  func (s *TimedStore) getData(index int) timedStoreData {
   159  	return s.buffer[len(s.buffer)-index-1]
   160  }
   161  
   162  func (s *TimedStore) Size() int {
   163  	return len(s.buffer)
   164  }