259 lines
5.4 KiB
Go
259 lines
5.4 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package monitoring
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Registry to store variables and sub-registries.
|
|
// When adding or retrieving variables, all names are split on the `.`-symbol and
|
|
// intermediate registries will be generated.
|
|
type Registry struct {
|
|
mu sync.RWMutex
|
|
|
|
name string
|
|
entries map[string]entry
|
|
|
|
opts *options
|
|
}
|
|
|
|
type entry struct {
|
|
Var
|
|
Mode
|
|
}
|
|
|
|
// Var interface required for every metric to implement.
|
|
type Var interface {
|
|
Visit(Mode, Visitor)
|
|
}
|
|
|
|
// NewRegistry create a new empty unregistered registry
|
|
func NewRegistry(opts ...Option) *Registry {
|
|
return &Registry{
|
|
opts: applyOpts(nil, opts),
|
|
entries: map[string]entry{},
|
|
}
|
|
}
|
|
|
|
func (r *Registry) Do(mode Mode, f func(string, interface{})) {
|
|
r.doVisit(mode, NewKeyValueVisitor(f))
|
|
}
|
|
|
|
// Visit uses the Visitor interface to iterate the complete metrics hierarchies.
|
|
// In case of the visitor reporting an error, Visit will return immediately,
|
|
// reporting the very same error.
|
|
func (r *Registry) Visit(mode Mode, vs Visitor) {
|
|
r.doVisit(mode, vs)
|
|
}
|
|
|
|
func (r *Registry) doVisit(mode Mode, vs Visitor) {
|
|
vs.OnRegistryStart()
|
|
defer vs.OnRegistryFinished()
|
|
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
for key, v := range r.entries {
|
|
if _, isReg := v.Var.(*Registry); !isReg {
|
|
if v.Mode > mode {
|
|
continue
|
|
}
|
|
}
|
|
|
|
vs.OnKey(key)
|
|
v.Var.Visit(mode, vs)
|
|
}
|
|
}
|
|
|
|
// NewRegistry creates and register a new registry
|
|
func (r *Registry) NewRegistry(name string, opts ...Option) *Registry {
|
|
v := &Registry{
|
|
name: fullName(r, name),
|
|
opts: applyOpts(r.opts, opts),
|
|
entries: map[string]entry{},
|
|
}
|
|
r.Add(name, v, v.opts.mode)
|
|
return v
|
|
}
|
|
|
|
// Get tries to find a registered variable by name.
|
|
func (r *Registry) Get(name string) Var {
|
|
v, err := r.find(name)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return v.Var
|
|
}
|
|
|
|
// GetRegistry tries to find a sub-registry by name.
|
|
func (r *Registry) GetRegistry(name string) *Registry {
|
|
e, err := r.find(name)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
v := e.Var
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
reg, ok := v.(*Registry)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return reg
|
|
}
|
|
|
|
// Remove removes a variable or a sub-registry by name
|
|
func (r *Registry) Remove(name string) {
|
|
r.removeNames(strings.Split(name, "."))
|
|
}
|
|
|
|
// Clear removes all entries from the current registry
|
|
func (r *Registry) Clear() error {
|
|
r.mu.Lock()
|
|
r.mu.Unlock()
|
|
|
|
if r.opts.publishExpvar {
|
|
return errors.New("Can not clear registry with metrics being exported via expvar")
|
|
}
|
|
|
|
r.entries = map[string]entry{}
|
|
return nil
|
|
}
|
|
|
|
// Add adds a new variable to the registry. The method panics if the variables
|
|
// name is already in use.
|
|
func (r *Registry) Add(name string, v Var, m Mode) {
|
|
opts := r.opts
|
|
if m != opts.mode {
|
|
tmp := *r.opts
|
|
tmp.mode = m
|
|
opts = &tmp
|
|
}
|
|
|
|
panicErr(r.addNames(strings.Split(name, "."), v, opts))
|
|
}
|
|
|
|
func (r *Registry) doAdd(name string, v Var, opts *options) {
|
|
panicErr(r.addNames(strings.Split(name, "."), v, opts))
|
|
}
|
|
|
|
func (r *Registry) addNames(names []string, v Var, opts *options) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
name := names[0]
|
|
if len(names) == 1 {
|
|
if _, found := r.entries[name]; found {
|
|
return fmt.Errorf("name %v already used", name)
|
|
}
|
|
|
|
r.entries[name] = entry{v, opts.mode}
|
|
return nil
|
|
}
|
|
|
|
if tmp, found := r.entries[name]; found {
|
|
reg, ok := tmp.Var.(*Registry)
|
|
if !ok {
|
|
return fmt.Errorf("name %v already used", name)
|
|
}
|
|
|
|
return reg.addNames(names[1:], v, opts)
|
|
}
|
|
|
|
sub := NewRegistry()
|
|
sub.opts = opts
|
|
if err := sub.addNames(names[1:], v, opts); err != nil {
|
|
return err
|
|
}
|
|
|
|
r.entries[name] = entry{sub, sub.opts.mode}
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) find(name string) (entry, error) {
|
|
return r.findNames(strings.Split(name, "."))
|
|
}
|
|
|
|
func (r *Registry) findNames(names []string) (entry, error) {
|
|
switch len(names) {
|
|
case 0:
|
|
return entry{r, r.opts.mode}, nil
|
|
case 1:
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return r.entries[names[0]], nil
|
|
}
|
|
|
|
r.mu.RLock()
|
|
next, exist := r.entries[names[0]]
|
|
r.mu.RUnlock()
|
|
|
|
if !exist {
|
|
return entry{}, errNotFound
|
|
}
|
|
|
|
if reg, ok := next.Var.(*Registry); ok {
|
|
return reg.findNames(names[1:])
|
|
}
|
|
return entry{}, errInvalidName
|
|
}
|
|
|
|
func (r *Registry) removeNames(names []string) {
|
|
switch len(names) {
|
|
case 0:
|
|
return
|
|
case 1:
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
delete(r.entries, names[0])
|
|
return
|
|
}
|
|
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
next, exists := r.entries[names[0]]
|
|
|
|
// if name does not exist => don't remove anything
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
sub, ok := next.Var.(*Registry)
|
|
if ok {
|
|
sub.removeNames(names[1:])
|
|
sub.mu.RLock()
|
|
sub.mu.RUnlock()
|
|
|
|
if len(sub.entries) == 0 {
|
|
delete(r.entries, names[0])
|
|
}
|
|
}
|
|
}
|
|
|
|
func panicErr(err error) {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|