package memstore import ( "errors" "math" "sync" "unsafe" "github.com/ClusterCockpit/cc-metric-store/internal/types" ) // Could also be called "node" as this forms a node in a tree structure. // Called level because "node" might be confusing here. // Can be both a leaf or a inner node. In this tree structue, inner nodes can // also hold data (in `metrics`). type Level struct { lock sync.RWMutex metrics []*chunk // Every level can store metrics. sublevels map[string]*Level // Lower levels. } // Find the correct level for the given selector, creating it if // it does not exist. Example selector in the context of the // ClusterCockpit could be: []string{ "emmy", "host123", "cpu0" }. // This function would probably benefit a lot from `level.children` beeing a `sync.Map`? func (l *Level) findLevelOrCreate(selector []string, nMetrics int) *Level { if len(selector) == 0 { return l } // Allow concurrent reads: l.lock.RLock() var child *Level var ok bool if l.sublevels == nil { // sublevels map needs to be created... l.lock.RUnlock() } else { child, ok := l.sublevels[selector[0]] l.lock.RUnlock() if ok { return child.findLevelOrCreate(selector[1:], nMetrics) } } // The level does not exist, take write lock for unqiue access: l.lock.Lock() // While this thread waited for the write lock, another thread // could have created the child node. if l.sublevels != nil { child, ok = l.sublevels[selector[0]] if ok { l.lock.Unlock() return child.findLevelOrCreate(selector[1:], nMetrics) } } child = &Level{ metrics: make([]*chunk, nMetrics), sublevels: nil, } if l.sublevels != nil { l.sublevels[selector[0]] = child } else { l.sublevels = map[string]*Level{selector[0]: child} } l.lock.Unlock() return child.findLevelOrCreate(selector[1:], nMetrics) } func (l *Level) findLevel(selector []string) *Level { if len(selector) == 0 { return l } l.lock.RLock() defer l.lock.RUnlock() lvl := l.sublevels[selector[0]] if lvl == nil { return nil } return lvl.findLevel(selector[1:]) } func (l *Level) free(t int64) (delme bool, n int) { l.lock.Lock() defer l.lock.Unlock() for i, c := range l.metrics { if c != nil { delchunk, m := c.free(t) n += m if delchunk { freeChunk(c) l.metrics[i] = nil } } } for key, l := range l.sublevels { delsublevel, m := l.free(t) n += m if delsublevel { l.sublevels[key] = nil } } return len(l.metrics) == 0 && len(l.sublevels) == 0, n } // Return an estimate of how many values are stored for all metrics and sublevels between from and to. func (l *Level) countValues(from int64, to int64) int { vals := 0 l.lock.RLock() defer l.lock.RUnlock() for _, c := range l.metrics { for c != nil { if to < c.start || c.end() < from { continue } else if c.start < from { vals += int((from - c.start) / c.frequency) } else if from < c.start { vals += int((c.start - from) / c.frequency) } else { vals += len(c.data) } c = c.prev } } for _, sublevel := range l.sublevels { vals += sublevel.countValues(from, to) } return vals } func (l *Level) sizeInBytes() int64 { l.lock.RLock() defer l.lock.RUnlock() size := int64(0) for _, b := range l.metrics { for b != nil { size += int64(len(b.data)) b = b.prev } } size *= int64(unsafe.Sizeof(types.Float(0))) for _, child := range l.sublevels { size += child.sizeInBytes() } return size } type MemoryStore struct { root Level // root of the tree structure // TODO... metrics map[string]types.MetricConfig // TODO... } // Return a new, initialized instance of a MemoryStore. // Will panic if values in the metric configurations are invalid. func NewMemoryStore(metrics map[string]types.MetricConfig) *MemoryStore { offset := 0 for key, config := range metrics { if config.Frequency == 0 { panic("invalid frequency") } metrics[key] = types.MetricConfig{ Frequency: config.Frequency, Aggregation: config.Aggregation, Offset: offset, } offset += 1 } return &MemoryStore{ root: Level{ metrics: make([]*chunk, len(metrics)), sublevels: make(map[string]*Level), }, metrics: metrics, } } func (ms *MemoryStore) GetMetricConf(metric string) (types.MetricConfig, bool) { conf, ok := ms.metrics[metric] return conf, ok } func (ms *MemoryStore) GetMetricForOffset(offset int) string { for name, mc := range ms.metrics { if mc.Offset == offset { return name } } return "" } func (ms *MemoryStore) MinFrequency() int64 { min := int64(math.MaxInt64) for _, mc := range ms.metrics { if mc.Frequency < min { min = mc.Frequency } } return min } func (m *MemoryStore) GetLevel(selector []string) *Level { return m.root.findLevelOrCreate(selector, len(m.metrics)) } func (m *MemoryStore) WriteToLevel(l *Level, selector []string, ts int64, metrics []types.Metric) error { l = l.findLevelOrCreate(selector, len(m.metrics)) l.lock.Lock() defer l.lock.Unlock() for _, metric := range metrics { if metric.Conf.Frequency == 0 { continue } c := l.metrics[metric.Conf.Offset] if c == nil { // First write to this metric and level c = newChunk(ts, metric.Conf.Frequency) l.metrics[metric.Conf.Offset] = c } nc, err := c.write(ts, metric.Value) if err != nil { return err } // Last write started a new chunk... if c != nc { l.metrics[metric.Conf.Offset] = nc } } return nil } func (m *MemoryStore) Write(selector []string, ts int64, metrics []types.Metric) error { l := m.root.findLevelOrCreate(selector, len(m.metrics)) for _, metric := range metrics { mc, ok := m.GetMetricConf(metric.Name) if !ok { continue } c := l.metrics[mc.Offset] if c == nil { // First write to this metric and level c = newChunk(ts, mc.Frequency) l.metrics[mc.Offset] = c } nc, err := c.write(ts, metric.Value) if err != nil { return err } // Last write started a new chunk... if c != nc { l.metrics[mc.Offset] = nc } } return nil } func (m *MemoryStore) Free(t int64) int { _, n := m.root.free(t) return n } func (l *Level) findBuffers(selector types.Selector, offset int, f func(c *chunk) error) error { l.lock.RLock() defer l.lock.RUnlock() if len(selector) == 0 { b := l.metrics[offset] if b != nil { return f(b) } for _, lvl := range l.sublevels { err := lvl.findBuffers(nil, offset, f) if err != nil { return err } } return nil } sel := selector[0] if len(sel.String) != 0 && l.sublevels != nil { lvl, ok := l.sublevels[sel.String] if ok { err := lvl.findBuffers(selector[1:], offset, f) if err != nil { return err } } return nil } if sel.Group != nil && l.sublevels != nil { for _, key := range sel.Group { lvl, ok := l.sublevels[key] if ok { err := lvl.findBuffers(selector[1:], offset, f) if err != nil { return err } } } return nil } if sel.Any && l.sublevels != nil { for _, lvl := range l.sublevels { if err := lvl.findBuffers(selector[1:], offset, f); err != nil { return err } } return nil } return nil } var ( ErrNoData error = errors.New("no data for this metric/level") ErrDataDoesNotAlign error = errors.New("data from lower granularities does not align") ) // Returns all values for metric `metric` from `from` to `to` for the selected level(s). // If the level does not hold the metric itself, the data will be aggregated recursively from the children. // The second and third return value are the actual from/to for the data. Those can be different from // the range asked for if no data was available. func (m *MemoryStore) Read(selector types.Selector, metric string, from, to int64) ([]types.Float, int64, int64, error) { if from > to { return nil, 0, 0, errors.New("invalid time range") } mc, ok := m.metrics[metric] if !ok { return nil, 0, 0, errors.New("unkown metric: " + metric) } n, data := 0, make([]types.Float, (to-from)/mc.Frequency+1) err := m.root.findBuffers(selector, mc.Offset, func(c *chunk) error { cdata, cfrom, cto, err := c.read(from, to, data) if err != nil { return err } if n == 0 { from, to = cfrom, cto } else if from != cfrom || to != cto || len(data) != len(cdata) { missingfront, missingback := int((from-cfrom)/mc.Frequency), int((to-cto)/mc.Frequency) if missingfront != 0 { return ErrDataDoesNotAlign } newlen := len(cdata) - missingback if newlen < 1 { return ErrDataDoesNotAlign } cdata = cdata[0:newlen] if len(cdata) != len(data) { return ErrDataDoesNotAlign } from, to = cfrom, cto } data = cdata n += 1 return nil }) if err != nil { return nil, 0, 0, err } else if n == 0 { return nil, 0, 0, errors.New("metric or host not found") } else if n > 1 { if mc.Aggregation == types.AvgAggregation { normalize := 1. / types.Float(n) for i := 0; i < len(data); i++ { data[i] *= normalize } } else if mc.Aggregation != types.SumAggregation { return nil, 0, 0, errors.New("invalid aggregation") } } return data, from, to, nil } // Returns statistics for the requested metric on the selected node/level. // Data is aggregated to the selected level the same way as in `MemoryStore.Read`. // If `Stats.Samples` is zero, the statistics should not be considered as valid. func (m *MemoryStore) Stats(selector types.Selector, metric string, from, to int64) (*types.Stats, int64, int64, error) { if from > to { return nil, 0, 0, errors.New("invalid time range") } mc, ok := m.metrics[metric] if !ok { return nil, 0, 0, errors.New("unkown metric: " + metric) } n, samples := 0, 0 avg, min, max := types.Float(0), math.MaxFloat32, -math.MaxFloat32 err := m.root.findBuffers(selector, mc.Offset, func(c *chunk) error { stats, cfrom, cto, err := c.stats(from, to) if err != nil { return err } if n == 0 { from, to = cfrom, cto } else if from != cfrom || to != cto { return ErrDataDoesNotAlign } samples += stats.Samples avg += stats.Avg min = math.Min(min, float64(stats.Min)) max = math.Max(max, float64(stats.Max)) n += 1 return nil }) if err != nil { return nil, 0, 0, err } if n == 0 { return nil, 0, 0, ErrNoData } if mc.Aggregation == types.AvgAggregation { avg /= types.Float(n) } else if n > 1 && mc.Aggregation != types.SumAggregation { return nil, 0, 0, errors.New("invalid aggregation") } return &types.Stats{ Samples: samples, Avg: avg, Min: types.Float(min), Max: types.Float(max), }, from, to, nil } // Given a selector, return a list of all children of the level selected. func (m *MemoryStore) ListChildren(selector []string) []string { lvl := &m.root for lvl != nil && len(selector) != 0 { lvl.lock.RLock() next := lvl.sublevels[selector[0]] lvl.lock.RUnlock() lvl = next selector = selector[1:] } if lvl == nil { return nil } lvl.lock.RLock() defer lvl.lock.RUnlock() children := make([]string, 0, len(lvl.sublevels)) for child := range lvl.sublevels { children = append(children, child) } return children } func (m *MemoryStore) SizeInBytes() int64 { return m.root.sizeInBytes() }