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  }