Skip to content

Commit

Permalink
[FAB-5465]Init common metrics module
Browse files Browse the repository at this point in the history
First commit for fabric metrics module, all exposed
interfaces are defined in types.go, currently only
one implemetation based uber-go/tally library.

For consumer, First need use IsEnabled() method to verify
if metrics module enabled, if yes get root metrics scope
instance using NewRootScope() method and then define self
sub scope and metrics. Record metrics value in code after
defined. Close() method need be invoked when peer/orderer
service stop if metrics module enabled. A example below

func reportXXX(){
   if metrics.IsEnabled() {
      metrics.NewRootScope()
	     .SubScope('XX')
	     .Counter('XXX')
	     .Inc(1)
   }
}

func useXXXreport(){
    if success {
       reportXXX()
    }
}

Change-Id: I3860a16b22321970eb51aeedb764af769e55ee73
Signed-off-by: grapebaba <[email protected]>
  • Loading branch information
GrapeBaBa committed Aug 3, 2017
1 parent d788450 commit 3cac55e
Show file tree
Hide file tree
Showing 26 changed files with 4,516 additions and 0 deletions.
60 changes: 60 additions & 0 deletions common/metrics/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package metrics

import (
"io"
"sync"
"sync/atomic"
"time"

"github.com/uber-go/tally"
)

const (
namespace string = "hyperledger.fabric"
)

var rootScope Scope
var closer io.Closer
var once sync.Once
var started uint32

//NewRootScope creates a global root metrics scope instance, all callers can only use it to extend sub scope
func NewRootScope() Scope {
once.Do(func() {
//TODO:Use config yaml
conf := config{
interval: 1 * time.Second,
reporter: "nullstatreporter",
}
rootScope, closer = newRootScope(
tally.ScopeOptions{
Prefix: namespace,
Reporter: tally.NullStatsReporter}, conf.interval)
atomic.StoreUint32(&started, 1)
})
return rootScope
}

//Close closes underlying resources used by metrics module
func Close() {
if atomic.LoadUint32(&started) == 1 {
closer.Close()
}
}

//IsEnabled represents if metrics feature enabled or not based config
func IsEnabled() bool {
//TODO:Use config yaml
return true
}

type config struct {
reporter string
interval time.Duration
}
43 changes: 43 additions & 0 deletions common/metrics/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package metrics

import (
"sync"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewRootScope(t *testing.T) {
s := NewRootScope()
assert.NotNil(t, s)
}

func TestNewRootScopeConcurrent(t *testing.T) {
var s1 Scope
var s2 Scope
var wg sync.WaitGroup
wg.Add(2)
go func() {
s1 = NewRootScope()
wg.Done()
}()
go func() {
s2 = NewRootScope()
wg.Done()
}()
wg.Wait()
assert.Exactly(t, &s1, &s2)
}

func TestClose(t *testing.T) {
NewRootScope()
assert.NotPanics(t, func() {
Close()
})
}
219 changes: 219 additions & 0 deletions common/metrics/tally_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package metrics

import (
"fmt"
"io"
"sync"
"time"

"github.com/uber-go/tally"
)

var scopeRegistryKey = tally.KeyForPrefixedStringMap

type counter struct {
tallyCounter tally.Counter
}

func newCounter(tallyCounter tally.Counter) *counter {
return &counter{tallyCounter: tallyCounter}
}

func (c *counter) Inc(v int64) {
c.tallyCounter.Inc(v)
}

type gauge struct {
tallyGauge tally.Gauge
}

func newGauge(tallyGauge tally.Gauge) *gauge {
return &gauge{tallyGauge: tallyGauge}
}

func (g *gauge) Update(v float64) {
g.tallyGauge.Update(v)
}

type scopeRegistry struct {
sync.RWMutex
subScopes map[string]*scope
}

type scope struct {
separator string
prefix string
tags map[string]string
tallyScope tally.Scope
registry *scopeRegistry

cm sync.RWMutex
gm sync.RWMutex

counters map[string]*counter
gauges map[string]*gauge
}

func newRootScope(opts tally.ScopeOptions, interval time.Duration) (Scope, io.Closer) {
s, closer := tally.NewRootScope(opts, interval)
return &scope{
prefix: opts.Prefix,
separator: opts.Separator,
tallyScope: s,
registry: &scopeRegistry{
subScopes: make(map[string]*scope),
},
counters: make(map[string]*counter),
gauges: make(map[string]*gauge)}, closer
}

func (s *scope) Counter(name string) Counter {
s.cm.RLock()
val, ok := s.counters[name]
s.cm.RUnlock()
if !ok {
s.cm.Lock()
val, ok = s.counters[name]
if !ok {
counter := s.tallyScope.Counter(name)
val = newCounter(counter)
s.counters[name] = val
}
s.cm.Unlock()
}
return val
}

func (s *scope) Gauge(name string) Gauge {
s.gm.RLock()
val, ok := s.gauges[name]
s.gm.RUnlock()
if !ok {
s.gm.Lock()
val, ok = s.gauges[name]
if !ok {
gauge := s.tallyScope.Gauge(name)
val = newGauge(gauge)
s.gauges[name] = val
}
s.gm.Unlock()
}
return val
}

func (s *scope) Tagged(tags map[string]string) Scope {
originTags := tags
tags = mergeRightTags(s.tags, tags)
key := scopeRegistryKey(s.prefix, tags)

s.registry.RLock()
existing, ok := s.registry.subScopes[key]
if ok {
s.registry.RUnlock()
return existing
}
s.registry.RUnlock()

s.registry.Lock()
defer s.registry.Unlock()

existing, ok = s.registry.subScopes[key]
if ok {
return existing
}

subScope := &scope{
separator: s.separator,
prefix: s.prefix,
// NB(r): Take a copy of the tags on creation
// so that it cannot be modified after set.
tags: copyStringMap(tags),
tallyScope: s.tallyScope.Tagged(originTags),
registry: s.registry,

counters: make(map[string]*counter),
gauges: make(map[string]*gauge),
}

s.registry.subScopes[key] = subScope
return subScope
}

func (s *scope) SubScope(prefix string) Scope {
key := scopeRegistryKey(s.fullyQualifiedName(prefix), s.tags)

s.registry.RLock()
existing, ok := s.registry.subScopes[key]
if ok {
s.registry.RUnlock()
return existing
}
s.registry.RUnlock()

s.registry.Lock()
defer s.registry.Unlock()

existing, ok = s.registry.subScopes[key]
if ok {
return existing
}

subScope := &scope{
separator: s.separator,
prefix: s.prefix,
// NB(r): Take a copy of the tags on creation
// so that it cannot be modified after set.
tags: copyStringMap(s.tags),
tallyScope: s.tallyScope.SubScope(prefix),
registry: s.registry,

counters: make(map[string]*counter),
gauges: make(map[string]*gauge),
}

s.registry.subScopes[key] = subScope
return subScope
}

func (s *scope) fullyQualifiedName(name string) string {
if len(s.prefix) == 0 {
return name
}
return fmt.Sprintf("%s%s%s", s.prefix, s.separator, name)
}

// mergeRightTags merges 2 sets of tags with the tags from tagsRight overriding values from tagsLeft
func mergeRightTags(tagsLeft, tagsRight map[string]string) map[string]string {
if tagsLeft == nil && tagsRight == nil {
return nil
}
if len(tagsRight) == 0 {
return tagsLeft
}
if len(tagsLeft) == 0 {
return tagsRight
}

result := make(map[string]string, len(tagsLeft)+len(tagsRight))
for k, v := range tagsLeft {
result[k] = v
}
for k, v := range tagsRight {
result[k] = v
}
return result
}

func copyStringMap(stringMap map[string]string) map[string]string {
result := make(map[string]string, len(stringMap))
for k, v := range stringMap {
result[k] = v
}
return result
}
Loading

0 comments on commit 3cac55e

Please sign in to comment.