-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FAB-5465]Init common metrics module
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
Showing
26 changed files
with
4,516 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.