├── base
├── runtime_no_cgo.go
├── runtime_cgo.go
├── runtime_no_gccpufraction.go
├── runtime_gccpufraction.go
├── debug_test.go
├── runtime_test.go
├── debug.go
└── runtime.go
├── version.go
├── perfcounter.json
├── example
├── perfcounter.json
├── .gitignore
├── README.md
├── scripts
│ └── debug
├── main.go
└── control
├── .gitignore
├── doc
├── BENCH.md
└── API.md
├── metrics.go
├── config.go
├── http.go
├── README.md
├── falcon.go
└── perfcounter.go
/base/runtime_no_cgo.go:
--------------------------------------------------------------------------------
1 | // +build !cgo appengine
2 |
3 | package base
4 |
5 | func numCgoCall() int64 {
6 | return 0
7 | }
8 |
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | package goperfcounter
2 |
3 | // changelog:
4 | // 0.0.1: init project
5 | const (
6 | VERSION = "0.0.1"
7 | )
8 |
--------------------------------------------------------------------------------
/base/runtime_cgo.go:
--------------------------------------------------------------------------------
1 | // +build cgo
2 | // +build !appengine
3 |
4 | package base
5 |
6 | import "runtime"
7 |
8 | func numCgoCall() int64 {
9 | return runtime.NumCgoCall()
10 | }
11 |
--------------------------------------------------------------------------------
/base/runtime_no_gccpufraction.go:
--------------------------------------------------------------------------------
1 | // +build !go1.5
2 |
3 | package base
4 |
5 | import "runtime"
6 |
7 | func gcCPUFraction(memStats *runtime.MemStats) float64 {
8 | return 0
9 | }
10 |
--------------------------------------------------------------------------------
/base/runtime_gccpufraction.go:
--------------------------------------------------------------------------------
1 | // +build go1.5
2 |
3 | package base
4 |
5 | import "runtime"
6 |
7 | func gcCPUFraction(memStats *runtime.MemStats) float64 {
8 | return memStats.GCCPUFraction
9 | }
10 |
--------------------------------------------------------------------------------
/perfcounter.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": false,
3 | "hostname": "",
4 | "tags": "",
5 | "step": 60,
6 | "bases": ["runtime"],
7 | "push": {
8 | "enabled": true,
9 | "api": "http://127.0.0.1:6060/api/push"
10 | },
11 | "http": {
12 | "enabled": false,
13 | "listen": "0.0.0.0:2015"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/perfcounter.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": true,
3 | "hostname": "",
4 | "tags": "cop=xiaomi,owt=inf,pdl=falcon,module=perfcounter",
5 | "step": 20,
6 | "bases":["runtime","debug"],
7 | "push": {
8 | "enabled": true,
9 | "api": "http://127.0.0.1:6060/api/push"
10 | },
11 | "http": {
12 | "enabled": true,
13 | "listen": "0.0.0.0:2015"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 | /.idea
26 | .DS_Store
27 | *.bak
28 |
29 | # Project
30 | .gitversion
31 | /*_test.go
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 | /.idea
26 | .DS_Store
27 | *.bak
28 |
29 | # Project
30 | perfcounter
31 | .gitversion
32 | /*_test.go
33 | /var
--------------------------------------------------------------------------------
/doc/BENCH.md:
--------------------------------------------------------------------------------
1 | Bench Test
2 | ====
3 |
4 | Run
5 | ----
6 |
7 | ```bash
8 | cd $GOPATH/src/github.com/niean/goperfcounter && go test -test.bench=".*"
9 |
10 | ```
11 |
12 | Result
13 | ----
14 |
15 | ```
16 | PASS
17 | BenchmarkMeter 2000000 918 ns/op
18 | BenchmarkMeterMulti 2000000 916 ns/op
19 | BenchmarkGauge 5000000 342 ns/op
20 | BenchmarkGaugeMulti 5000000 333 ns/op
21 | BenchmarkHistogram 2000000 800 ns/op
22 | BenchmarkHistogramMulti 2000000 780 ns/op
23 | ok github.com/niean/goperfcounter 14.568s
24 |
25 | ```
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | Example Of GoPerfcounter
2 | =====
3 |
4 |
5 | 这里是,goperfcounter提供的一个完整的例子。
6 |
7 | 按照如下指令,运行这个例子。
8 |
9 | ```bash
10 | # install
11 | cd $GOPATH/src/github.com/niean
12 | git clone https://github.com/niean/goperfcounter.git
13 | cd $GOPATH/src/github.com/niean/goperfcounter
14 | go get ./...
15 |
16 | # run
17 | cd $GOPATH/src/github.com/niean/goperfcounter/example/scripts
18 | ./debug build && ./debug start
19 |
20 | # proc
21 | ./debug proc metrics/json # list all metrics in json
22 | ./debug proc metrics/falcon # list all metrics in falcon-model
23 |
24 | ```
25 |
--------------------------------------------------------------------------------
/example/scripts/debug:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ## test home
3 | testdir=$(cd $(dirname $0)/; pwd)
4 | ## word home
5 | workdir=$(dirname $testdir)
6 | cd $workdir
7 |
8 | control=./control
9 |
10 | cfg=./perfcounter.json
11 | httpport=`cat perfcounter.json | grep -A3 "\"http\":" | grep "\"listen\"" | cut -d\" -f4 | cut -d: -f2`
12 | httpprex="127.0.0.1:$httpport"
13 |
14 | ## pfc
15 | function pfc(){
16 | args=$@
17 | for i in $@; do
18 | url="$url/$i"
19 | done
20 | echo $url
21 | curl -s "$httpprex$url" | python -m json.tool
22 | }
23 |
24 | ## control
25 | function control(){
26 | $control $@
27 | }
28 |
29 | action=$1
30 | case $action in
31 | "proc")
32 | pfc "pfc" $@
33 | ;;
34 | "pfc")
35 | pfc $@
36 | ;;
37 | "")
38 | pfc "pfc/proc/metrics/gauge,counter,meter,histogram/json"
39 | ;;
40 | *)
41 | control $@
42 | ;;
43 | esac
44 |
45 |
--------------------------------------------------------------------------------
/example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 |
7 | pfc "github.com/niean/goperfcounter"
8 | )
9 |
10 | func main() {
11 | go basic() // 基础统计器
12 | go senior() // 高级统计器
13 | select {}
14 | }
15 |
16 | func basic() {
17 | for _ = range time.Tick(time.Second * time.Duration(10)) {
18 | // (常用) Meter,用于累加求和、计算变化率。使用场景如,统计首页访问次数、gvm的CG次数等。
19 | pv := int64(rand.Int() % 100)
20 | pfc.Meter("test.meter", pv)
21 | pfc.Meter("test.meter.2", pv-50)
22 |
23 | // (常用) Gauge,用于保存数值类型的瞬时记录值。使用场景如,统计队列长度、统计CPU使用率等
24 | queueSize := int64(rand.Int()%100 - 50)
25 | pfc.Gauge("test.gauge", queueSize)
26 |
27 | cpuUtil := float64(rand.Int()%10000) / float64(100)
28 | pfc.GaugeFloat64("test.gauge.float64", cpuUtil)
29 | }
30 | }
31 |
32 | func senior() {
33 | for _ = range time.Tick(time.Second) {
34 | // Histogram,使用指数衰减抽样的方式,计算被统计对象的概率分布情况。使用场景如,统计主页访问延时的概率分布
35 | delay := int64(rand.Int() % 100)
36 | pfc.Histogram("test.histogram", delay)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/base/debug_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "runtime"
5 | "runtime/debug"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func BenchmarkDebugGCStats(b *testing.B) {
11 | r := NewRegistry()
12 | RegisterDebugGCStats(r)
13 | b.ResetTimer()
14 | for i := 0; i < b.N; i++ {
15 | CaptureDebugGCStatsOnce(r)
16 | }
17 | }
18 |
19 | func TestDebugGCStatsBlocking(t *testing.T) {
20 | if g := runtime.GOMAXPROCS(0); g < 2 {
21 | t.Skipf("skipping TestDebugGCMemStatsBlocking with GOMAXPROCS=%d\n", g)
22 | return
23 | }
24 | ch := make(chan int)
25 | go testDebugGCStatsBlocking(ch)
26 | var gcStats debug.GCStats
27 | t0 := time.Now()
28 | debug.ReadGCStats(&gcStats)
29 | t1 := time.Now()
30 | t.Log("i++ during debug.ReadGCStats:", <-ch)
31 | go testDebugGCStatsBlocking(ch)
32 | d := t1.Sub(t0)
33 | t.Log(d)
34 | time.Sleep(d)
35 | t.Log("i++ during time.Sleep:", <-ch)
36 | }
37 |
38 | func testDebugGCStatsBlocking(ch chan int) {
39 | i := 0
40 | for {
41 | select {
42 | case ch <- i:
43 | return
44 | default:
45 | i++
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/metrics.go:
--------------------------------------------------------------------------------
1 | package goperfcounter
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/niean/go-metrics-lite"
7 | "github.com/niean/goperfcounter/base"
8 | )
9 |
10 | var (
11 | gpGaugeFloat64 = metrics.NewRegistry()
12 | gpCounter = metrics.NewRegistry()
13 | gpMeter = metrics.NewRegistry()
14 | gpHistogram = metrics.NewRegistry()
15 | gpDebug = metrics.NewRegistry()
16 | gpRuntime = metrics.NewRegistry()
17 | gpSelf = metrics.NewRegistry()
18 | values = make(map[string]metrics.Registry) //readonly,mappings of metrics
19 | )
20 |
21 | func init() {
22 | values["gauge"] = gpGaugeFloat64
23 | values["counter"] = gpCounter
24 | values["meter"] = gpMeter
25 | values["histogram"] = gpHistogram
26 | values["debug"] = gpDebug
27 | values["runtime"] = gpRuntime
28 | values["self"] = gpSelf
29 | }
30 |
31 | //
32 | func rawMetric(types []string) map[string]interface{} {
33 | data := make(map[string]interface{})
34 | for _, mtype := range types {
35 | if v, ok := values[mtype]; ok {
36 | data[mtype] = v.Values()
37 | }
38 | }
39 | return data
40 | }
41 |
42 | func rawMetrics() map[string]interface{} {
43 | data := make(map[string]interface{})
44 | for key, v := range values {
45 | data[key] = v.Values()
46 | }
47 | return data
48 | }
49 |
50 | func rawSizes() map[string]int64 {
51 | data := map[string]int64{}
52 | all := int64(0)
53 | for key, v := range values {
54 | kv := v.Size()
55 | all += kv
56 | data[key] = kv
57 | }
58 | data["all"] = all
59 | return data
60 | }
61 |
62 | func collectBase(bases []string) {
63 | // start base collect after 30sec
64 | time.Sleep(time.Duration(30) * time.Second)
65 |
66 | if contains(bases, "debug") {
67 | base.RegisterAndCaptureDebugGCStats(gpDebug, 5e9)
68 | }
69 |
70 | if contains(bases, "runtime") {
71 | base.RegisterAndCaptureRuntimeMemStats(gpRuntime, 5e9)
72 | }
73 | }
74 |
75 | func contains(bases []string, name string) bool {
76 | for _, n := range bases {
77 | if n == name {
78 | return true
79 | }
80 | }
81 | return false
82 | }
83 |
--------------------------------------------------------------------------------
/base/runtime_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "runtime"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func BenchmarkRuntimeMemStats(b *testing.B) {
10 | r := NewRegistry()
11 | RegisterRuntimeMemStats(r)
12 | b.ResetTimer()
13 | for i := 0; i < b.N; i++ {
14 | CaptureRuntimeMemStatsOnce(r)
15 | }
16 | }
17 |
18 | func TestRuntimeMemStats(t *testing.T) {
19 | r := NewRegistry()
20 | RegisterRuntimeMemStats(r)
21 | CaptureRuntimeMemStatsOnce(r)
22 | zero := runtimeMetrics.MemStats.PauseNs.Count() // Get a "zero" since GC may have run before these tests.
23 | runtime.GC()
24 | CaptureRuntimeMemStatsOnce(r)
25 | if count := runtimeMetrics.MemStats.PauseNs.Count(); 1 != count-zero {
26 | t.Fatal(count - zero)
27 | }
28 | runtime.GC()
29 | runtime.GC()
30 | CaptureRuntimeMemStatsOnce(r)
31 | if count := runtimeMetrics.MemStats.PauseNs.Count(); 3 != count-zero {
32 | t.Fatal(count - zero)
33 | }
34 | for i := 0; i < 256; i++ {
35 | runtime.GC()
36 | }
37 | CaptureRuntimeMemStatsOnce(r)
38 | if count := runtimeMetrics.MemStats.PauseNs.Count(); 259 != count-zero {
39 | t.Fatal(count - zero)
40 | }
41 | for i := 0; i < 257; i++ {
42 | runtime.GC()
43 | }
44 | CaptureRuntimeMemStatsOnce(r)
45 | if count := runtimeMetrics.MemStats.PauseNs.Count(); 515 != count-zero { // We lost one because there were too many GCs between captures.
46 | t.Fatal(count - zero)
47 | }
48 | }
49 |
50 | func TestRuntimeMemStatsBlocking(t *testing.T) {
51 | if g := runtime.GOMAXPROCS(0); g < 2 {
52 | t.Skipf("skipping TestRuntimeMemStatsBlocking with GOMAXPROCS=%d\n", g)
53 | }
54 | ch := make(chan int)
55 | go testRuntimeMemStatsBlocking(ch)
56 | var memStats runtime.MemStats
57 | t0 := time.Now()
58 | runtime.ReadMemStats(&memStats)
59 | t1 := time.Now()
60 | t.Log("i++ during runtime.ReadMemStats:", <-ch)
61 | go testRuntimeMemStatsBlocking(ch)
62 | d := t1.Sub(t0)
63 | t.Log(d)
64 | time.Sleep(d)
65 | t.Log("i++ during time.Sleep:", <-ch)
66 | }
67 |
68 | func testRuntimeMemStatsBlocking(ch chan int) {
69 | i := 0
70 | for {
71 | select {
72 | case ch <- i:
73 | return
74 | default:
75 | i++
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/base/debug.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "runtime/debug"
5 | "time"
6 |
7 | "github.com/niean/go-metrics-lite"
8 | )
9 |
10 | var (
11 | debugMetrics struct {
12 | GCStats struct {
13 | LastGC metrics.Gauge
14 | NumGC metrics.Gauge
15 | Pause metrics.Histogram
16 | PauseTotal metrics.Gauge
17 | }
18 | ReadGCStats metrics.Histogram
19 | }
20 | gcStats = debug.GCStats{Pause: make([]time.Duration, 11)}
21 | )
22 |
23 | func RegisterAndCaptureDebugGCStats(r metrics.Registry, d time.Duration) {
24 | registerDebugGCStats(r)
25 | go captureDebugGCStats(r, d)
26 | }
27 |
28 | // Capture new values for the Go garbage collector statistics exported in
29 | // debug.GCStats. This is designed to be called as a goroutine.
30 | func captureDebugGCStats(r metrics.Registry, d time.Duration) {
31 | for _ = range time.Tick(d) {
32 | captureDebugGCStatsOnce(r)
33 | }
34 | }
35 |
36 | // Capture new values for the Go garbage collector statistics exported in
37 | // debug.GCStats. This is designed to be called in a background goroutine.
38 | // Giving a registry which has not been given to RegisterDebugGCStats will
39 | // panic.
40 | //
41 | // Be careful (but much less so) with this because debug.ReadGCStats calls
42 | // the C function runtime·lock(runtime·mheap) which, while not a stop-the-world
43 | // operation, isn't something you want to be doing all the time.
44 | func captureDebugGCStatsOnce(r metrics.Registry) {
45 | lastGC := gcStats.LastGC
46 | t := time.Now()
47 | debug.ReadGCStats(&gcStats)
48 | debugMetrics.ReadGCStats.Update(int64(time.Since(t)))
49 |
50 | debugMetrics.GCStats.LastGC.Update(int64(gcStats.LastGC.UnixNano()))
51 | debugMetrics.GCStats.NumGC.Update(int64(gcStats.NumGC))
52 | if lastGC != gcStats.LastGC && 0 < len(gcStats.Pause) {
53 | debugMetrics.GCStats.Pause.Update(int64(gcStats.Pause[0]))
54 | }
55 | //debugMetrics.GCStats.PauseQuantiles.Update(gcStats.PauseQuantiles)
56 | debugMetrics.GCStats.PauseTotal.Update(int64(gcStats.PauseTotal))
57 | }
58 |
59 | // Register metrics for the Go garbage collector statistics exported in
60 | // debug.GCStats. The metrics are named by their fully-qualified Go symbols,
61 | // i.e. debug.GCStats.PauseTotal.
62 | func registerDebugGCStats(r metrics.Registry) {
63 | debugMetrics.GCStats.LastGC = metrics.NewGauge()
64 | debugMetrics.GCStats.NumGC = metrics.NewGauge()
65 | debugMetrics.GCStats.Pause = metrics.NewHistogram(metrics.NewExpDecaySample(1028, 0.015))
66 | debugMetrics.GCStats.PauseTotal = metrics.NewGauge()
67 | debugMetrics.ReadGCStats = metrics.NewHistogram(metrics.NewExpDecaySample(1028, 0.015))
68 |
69 | r.Register("debug.GCStats.LastGC", debugMetrics.GCStats.LastGC)
70 | r.Register("debug.GCStats.NumGC", debugMetrics.GCStats.NumGC)
71 | r.Register("debug.GCStats.Pause", debugMetrics.GCStats.Pause)
72 | r.Register("debug.GCStats.PauseTotal", debugMetrics.GCStats.PauseTotal)
73 | r.Register("debug.ReadGCStats", debugMetrics.ReadGCStats)
74 | }
75 |
--------------------------------------------------------------------------------
/example/control:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | workspace=$(cd $(dirname $0) && pwd)
3 | cd $workspace
4 |
5 | module=perfcounter
6 | app=$module
7 | conf=cfg.json
8 | pidfile=var/app.pid
9 | logfile=var/app.log
10 | gitversion=.gitversion
11 |
12 | mkdir -p var &>/dev/null
13 |
14 |
15 | ## build & pack
16 | function build() {
17 | #update_gitversion
18 | go build -o $app main.go
19 | sc=$?
20 | if [ $sc -ne 0 ];then
21 | echo "build error"
22 | exit $sc
23 | else
24 | echo -n "build ok, vsn="
25 | #version
26 | fi
27 | }
28 |
29 | function pack() {
30 | build
31 | version=`./$app -v`
32 | tar zcvf $app-$version.tar.gz control $app cfg.example.json $gitversion ./scripts/debug
33 | }
34 |
35 | function packbin() {
36 | build
37 | version=`./$app -v`
38 | tar zcvf $app-bin-$version.tar.gz $app gitversion
39 | }
40 |
41 | ## opt
42 | function start() {
43 | check_pid
44 | running=$?
45 | if [ $running -gt 0 ];then
46 | echo -n "started, pid="
47 | cat $pidfile
48 | return 1
49 | fi
50 |
51 | nohup ./$app >>$logfile 2>&1 &
52 | echo $! > $pidfile
53 | echo "start ok, pid=$!"
54 | }
55 |
56 | function stop() {
57 | pid=`cat $pidfile`
58 | kill $pid
59 | echo "stoped"
60 | }
61 |
62 | function restart() {
63 | stop && sleep 1 && start
64 | }
65 |
66 | function reload() {
67 | build && stop && sleep 1 && start && sleep 1 && printf "\n" && tailf
68 | }
69 |
70 | ## other
71 | function status() {
72 | check_pid
73 | running=$?
74 | if [ $running -gt 0 ];then
75 | echo -n "running, pid="
76 | cat $pidfile
77 | else
78 | echo "stoped"
79 | fi
80 | }
81 |
82 | function version() {
83 | v=`./$app -v`
84 | if [ -f $gitversion ];then
85 | g=`cat $gitversion`
86 | fi
87 | echo "$v $g"
88 | }
89 |
90 | function tailf() {
91 | tail -f $logfile
92 | }
93 |
94 | ## internal
95 | function check_pid() {
96 | if [ -f $pidfile ];then
97 | pid=`cat $pidfile`
98 | if [ -n $pid ]; then
99 | running=`ps -p $pid|grep -v "PID TTY" |wc -l`
100 | return $running
101 | fi
102 | fi
103 | return 0
104 | }
105 |
106 | function update_gitversion() {
107 | git log -1 --pretty=%h > $gitversion
108 | }
109 |
110 | ## usage
111 | function usage() {
112 | echo "$0 build|pack|packbin|start|stop|restart|reload|status|tail|version"
113 | }
114 |
115 | ## main
116 | action=$1
117 | case $action in
118 | ## build
119 | "build" )
120 | build
121 | ;;
122 | "pack" )
123 | pack
124 | ;;
125 | "packbin" )
126 | packbin
127 | ;;
128 | ## opt
129 | "start" )
130 | start
131 | ;;
132 | "stop" )
133 | stop
134 | ;;
135 | "restart" )
136 | restart
137 | ;;
138 | "reload" )
139 | reload
140 | ;;
141 | ## other
142 | "status" )
143 | status
144 | ;;
145 | "version" )
146 | version
147 | ;;
148 | "tail" )
149 | tailf
150 | ;;
151 | * )
152 | usage
153 | ;;
154 | esac
155 |
--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
1 | package goperfcounter
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "os"
8 | "strings"
9 | "sync"
10 | )
11 |
12 | type GlobalConfig struct {
13 | Debug bool `json:"debug"`
14 | Hostname string `json:"hostname"`
15 | Tags string `json:"tags"`
16 | Step int64 `json:"step"`
17 | Bases []string `json:"bases"`
18 | Push *PushConfig `json:"push"`
19 | Http *HttpConfig `json:"http"`
20 | }
21 | type HttpConfig struct {
22 | Enabled bool `json:"enabled"`
23 | Listen string `json:"listen"`
24 | }
25 | type PushConfig struct {
26 | Enabled bool `json:"enabled"`
27 | Api string `json:"api"`
28 | }
29 |
30 | var (
31 | configFn = "./perfcounter.json"
32 | defaultTags = ""
33 | defaultStep = int64(60) //time in sec
34 | defaultBases = []string{}
35 | defaultPush = &PushConfig{Enabled: true, Api: "http://127.0.0.1:1988/v1/push"}
36 | defaultHttp = &HttpConfig{Enabled: false, Listen: ""}
37 | )
38 |
39 | var (
40 | cfg *GlobalConfig
41 | cfgLock = new(sync.RWMutex)
42 | )
43 |
44 | //
45 | func config() *GlobalConfig {
46 | cfgLock.RLock()
47 | defer cfgLock.RUnlock()
48 | return cfg
49 | }
50 |
51 | func loadConfig() error {
52 | if !isFileExist(configFn) {
53 | return fmt.Errorf("config file not found: %s", configFn)
54 | }
55 |
56 | c, err := parseConfig(configFn)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | updateConfig(c)
62 | return nil
63 | }
64 |
65 | func setDefaultConfig() {
66 | dcfg := defaultConfig()
67 | updateConfig(dcfg)
68 | }
69 |
70 | func defaultConfig() GlobalConfig {
71 | return GlobalConfig{
72 | Debug: false,
73 | Hostname: defaultHostname(),
74 | Tags: defaultTags,
75 | Step: defaultStep,
76 | Bases: defaultBases,
77 | Push: defaultPush,
78 | Http: defaultHttp,
79 | }
80 | }
81 |
82 | //
83 | func updateConfig(c GlobalConfig) {
84 | nc := formatConfig(c)
85 | cfgLock.Lock()
86 | defer cfgLock.Unlock()
87 | cfg = &nc
88 | }
89 |
90 | func formatConfig(c GlobalConfig) GlobalConfig {
91 | nc := c
92 | if nc.Hostname == "" {
93 | nc.Hostname = defaultHostname()
94 | }
95 | if nc.Step < 1 {
96 | nc.Step = defaultStep
97 | }
98 | if nc.Tags != "" {
99 | tagsOk := true
100 | tagsSlice := strings.Split(nc.Tags, ",")
101 | for _, tag := range tagsSlice {
102 | kv := strings.Split(tag, "=")
103 | if len(kv) != 2 || kv[0] == "name" { // name是保留tag
104 | tagsOk = false
105 | break
106 | }
107 | }
108 | if !tagsOk {
109 | nc.Tags = defaultTags
110 | }
111 | }
112 | if nc.Push.Enabled && nc.Push.Api == "" {
113 | nc.Push = defaultPush
114 | }
115 | if len(nc.Bases) < 1 {
116 | nc.Bases = defaultBases
117 | }
118 |
119 | return nc
120 | }
121 |
122 | func parseConfig(cfg string) (GlobalConfig, error) {
123 | var c GlobalConfig
124 |
125 | if cfg == "" {
126 | return c, fmt.Errorf("config file not found")
127 | }
128 |
129 | configContent, err := readFileString(cfg)
130 | if err != nil {
131 | return c, fmt.Errorf("read config file %s error: %v", cfg, err.Error())
132 | }
133 |
134 | err = json.Unmarshal([]byte(configContent), &c)
135 | if err != nil {
136 | return c, fmt.Errorf("parse config file %s error: %v", cfg, err.Error())
137 | }
138 | return c, nil
139 | }
140 |
141 | func defaultHostname() string {
142 | hostname, _ := os.Hostname()
143 | return hostname
144 | }
145 |
146 | func isFileExist(fn string) bool {
147 | _, err := os.Stat(fn)
148 | return err == nil || os.IsExist(err)
149 | }
150 |
151 | func readFileString(fn string) (string, error) {
152 | b, err := ioutil.ReadFile(fn)
153 | if err != nil {
154 | return "", err
155 | }
156 | return strings.TrimSpace(string(b)), nil
157 | }
158 |
--------------------------------------------------------------------------------
/doc/API.md:
--------------------------------------------------------------------------------
1 | API
2 | ====
3 |
4 | gopfercounter提供了几种类型的统计器,分比为Gauge、Meter、Histogram。统计器的含义,参见[java-metrics](http://metrics.dropwizard.io/3.1.0/getting-started/)。
5 |
6 |
7 | Gauge
8 | ----
9 |
10 | A gauge metric is an instantaneous reading of a particular value
11 |
12 | ##### 设置
13 | + 接口: Gauge(name string, value int64)
14 | + 参数: value - 记录的数值
15 | + 例子:
16 |
17 | ```go
18 | Gauge("queueSize", int64(13))
19 | ```
20 |
21 | ##### 设置
22 | + 接口: SetGaugeValue(name string, value float64)
23 | + Alias: GaugeFloat64(name string, value float64)
24 | + 参数: value - 记录的数值
25 | + 例子:
26 |
27 | ```go
28 | GaugeFloat64("requestRate", float64(13.14))
29 | SetGaugeValue("requestRate", float64(13.14))
30 | ```
31 |
32 | ##### 获取
33 | + 接口: GetGaugeValue(name string) float64
34 | + 例子:
35 |
36 | ```go
37 | reqRate := GetGaugeValue("requestRate")
38 | ```
39 |
40 | Meter
41 | ----
42 |
43 | A meter metric which measures mean throughput and one-, five-, and fifteen-minute exponentially-weighted moving average throughputs.
44 |
45 | 关于EWMA, 点击[这里](http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average)
46 |
47 | ##### 设置
48 | + 接口: SetMeterCount(name string, value int64)
49 | + Alias: Meter(name string, value int64)
50 | + 参数: value - 该事件发生的次数
51 | + 例子:
52 |
53 | ```go
54 | // 页面访问次数统计,每来一次访问,pv加1
55 | SetMeterCount("pageView", int64(1))
56 | ```
57 |
58 | ##### 获取累计的值
59 | + 接口: GetMeterCount(name string) int64
60 | + 例子:
61 |
62 | ```go
63 | // 获取pv次数的总和
64 | pvSum := GetMeterCount("pageView")
65 | ```
66 |
67 | ##### 获取一个上报周期内的变化率
68 | + 接口: GetMeterRateStep(name string) float64
69 | + 例子:
70 |
71 | ```go
72 | // pv发生次数的时间平均,单位CPS。计时范围为,本接口两次调用的时间差,即一个上报周期。
73 | pvRateStep := GetMeterRateStep("pageView")
74 | ```
75 |
76 | ##### 获取累计的平均值
77 | + 接口: GetMeterRateMean(name string) float64
78 | + 例子:
79 |
80 | ```go
81 | // pv发生次数的时间平均,单位CPS。计时范围为,goperfcounter完成初始,至当前时刻。
82 | pvRateMean := GetMeterRateMean("pageView")
83 | ```
84 |
85 | ##### 获取1min的滑动平均
86 | + 接口: GetMeterRate1(name string) float64
87 | + 例子:
88 |
89 | ```go
90 | // pv发生次数的1min滑动平均值,单位CPS
91 | pvRate1Min := GetMeterRate1("pageView")
92 | ```
93 |
94 | ##### 获取5min的滑动平均
95 | + 接口: GetMeterRate5(name string) float64
96 | + 例子:
97 |
98 | ```go
99 | // pv发生次数的5min滑动平均值,单位CPS
100 | pvRate5Min := GetMeterRate5("pageView")
101 | ```
102 |
103 | ##### 获取15min的滑动平均
104 | + 接口: GetMeterRate15(name string) float64
105 | + 例子:
106 |
107 | ```go
108 | // pv发生次数的15min滑动平均值,单位CPS
109 | pvRate15Min := GetMeterRate15("pageView")
110 | ```
111 |
112 | Histogram
113 | ----
114 |
115 | A histogram measures the [statistical distribution](http://www.johndcook.com/standard_deviation.html) of values in a stream of data. In addition to minimum, maximum, mean, etc., it also measures median, 75th, 90th, 95th, 98th, and 99th percentiles
116 |
117 | ##### 设置
118 | + 接口: SetHistogramCount(name string, count int64)
119 | + Alias: Histogram(name string, count int64)
120 | + 参数: count - 该记录当前采样点的取值
121 | + 例子:
122 |
123 | ```go
124 | // 设置当前同时处理请求的并发度
125 | SetHistogramCount("processNum", int64(325))
126 | ```
127 |
128 | ##### 获取最大值
129 | + 接口: GetHistogramMax(name string) int64
130 | + 例子:
131 |
132 | ```go
133 | max := GetHistogramMax("processNum")
134 | ```
135 |
136 | ##### 获取最小值
137 | + 接口: GetHistogramMin(name string) int64
138 | + 例子:
139 |
140 | ```go
141 | min := GetHistogramMin("processNum")
142 | ```
143 |
144 | ##### 获取平均值
145 | + 接口: GetHistogramMean(name string) float64
146 | + 例子:
147 |
148 | ```go
149 | mean := GetHistogramMean("processNum")
150 | ```
151 |
152 | ##### 获取75thPecentile
153 | + 接口: GetHistogram75th(name string) float64
154 | + 例子:
155 |
156 | ```go
157 | // 获取所有采样数据中,处于75%的并发度
158 | pNum75th := GetHistogram75th("processNum")
159 | ```
160 |
161 | ##### 获取95thPecentile
162 | + 接口: GetHistogram95th(name string) float64
163 | + 例子:
164 |
165 | ```go
166 | // 获取所有采样数据中,处于95%的并发度
167 | pNum95th := GetHistogram95th("processNum")
168 | ```
169 |
170 | ##### 获取99thPecentile
171 | + 接口: GetHistogram99th(name string) float64
172 | + 例子:
173 |
174 | ```go
175 | // 获取所有采样数据中,处于99%的并发度
176 | pNum99th := GetHistogram99th("processNum")
177 | ```
178 |
179 |
--------------------------------------------------------------------------------
/http.go:
--------------------------------------------------------------------------------
1 | package goperfcounter
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | _ "net/http/pprof"
9 | "strings"
10 | )
11 |
12 | func startHttp(addr string, debug bool) {
13 | configCommonRoutes()
14 | configProcRoutes()
15 | if len(addr) >= 9 { //x.x.x.x:x
16 | s := &http.Server{
17 | Addr: addr,
18 | MaxHeaderBytes: 1 << 30,
19 | }
20 | go func() {
21 | if debug {
22 | log.Println("[perfcounter] http server start, listening on", addr)
23 | }
24 | s.ListenAndServe()
25 | if debug {
26 | log.Println("[perfcounter] http server stop,", addr)
27 | }
28 | }()
29 | }
30 | }
31 |
32 | // routers
33 | func configProcRoutes() {
34 | http.HandleFunc("/pfc/proc/metrics/json", func(w http.ResponseWriter, r *http.Request) {
35 | if !isLocalReq(r.RemoteAddr) {
36 | RenderJson(w, "no privilege")
37 | return
38 | }
39 | RenderJson(w, rawMetrics())
40 | })
41 | http.HandleFunc("/pfc/proc/metrics/falcon", func(w http.ResponseWriter, r *http.Request) {
42 | if !isLocalReq(r.RemoteAddr) {
43 | RenderJson(w, "no privilege")
44 | return
45 | }
46 | RenderJson(w, falconMetrics())
47 | })
48 | // url=/pfc/proc/metric/{json,falcon}
49 | http.HandleFunc("/pfc/proc/metrics/", func(w http.ResponseWriter, r *http.Request) {
50 | if !isLocalReq(r.RemoteAddr) {
51 | RenderJson(w, "no privilege")
52 | return
53 | }
54 | urlParam := r.URL.Path[len("/pfc/proc/metrics/"):]
55 | args := strings.Split(urlParam, "/")
56 | argsLen := len(args)
57 | if argsLen != 2 {
58 | RenderJson(w, "")
59 | return
60 | }
61 |
62 | types := []string{}
63 | typeslice := strings.Split(args[0], ",")
64 | for _, t := range typeslice {
65 | nt := strings.TrimSpace(t)
66 | if nt != "" {
67 | types = append(types, nt)
68 | }
69 | }
70 |
71 | if args[1] == "json" {
72 | RenderJson(w, rawMetric(types))
73 | return
74 | }
75 | if args[1] == "falcon" {
76 | RenderJson(w, falconMetric(types))
77 | return
78 | }
79 | })
80 |
81 | http.HandleFunc("/pfc/proc/metrics/size", func(w http.ResponseWriter, r *http.Request) {
82 | if !isLocalReq(r.RemoteAddr) {
83 | RenderJson(w, "no privilege")
84 | return
85 | }
86 | RenderJson(w, rawSizes())
87 | })
88 |
89 | }
90 |
91 | func configCommonRoutes() {
92 | http.HandleFunc("/pfc/health", func(w http.ResponseWriter, r *http.Request) {
93 | if !isLocalReq(r.RemoteAddr) {
94 | RenderJson(w, "no privilege")
95 | return
96 | }
97 | w.Write([]byte("ok"))
98 | })
99 |
100 | http.HandleFunc("/pfc/version", func(w http.ResponseWriter, r *http.Request) {
101 | if !isLocalReq(r.RemoteAddr) {
102 | RenderJson(w, "no privilege")
103 | return
104 | }
105 | w.Write([]byte(fmt.Sprintf("%s\n", VERSION)))
106 | })
107 |
108 | http.HandleFunc("/pfc/config", func(w http.ResponseWriter, r *http.Request) {
109 | if !isLocalReq(r.RemoteAddr) {
110 | RenderJson(w, "no privilege")
111 | return
112 | }
113 | RenderJson(w, config())
114 | })
115 |
116 | http.HandleFunc("/pfc/config/reload", func(w http.ResponseWriter, r *http.Request) {
117 | if !isLocalReq(r.RemoteAddr) {
118 | RenderJson(w, "no privilege")
119 | return
120 | }
121 | loadConfig()
122 | RenderJson(w, "ok")
123 | })
124 | }
125 |
126 | func isLocalReq(raddr string) bool {
127 | if strings.HasPrefix(raddr, "127.0.0.1") {
128 | return true
129 | }
130 | return false
131 | }
132 |
133 | // render
134 | func RenderJson(w http.ResponseWriter, data interface{}) {
135 | renderJson(w, Dto{Msg: "success", Data: data})
136 | }
137 |
138 | func RenderString(w http.ResponseWriter, msg string) {
139 | renderJson(w, map[string]string{"msg": msg})
140 | }
141 |
142 | func renderJson(w http.ResponseWriter, v interface{}) {
143 | bs, err := json.Marshal(v)
144 | if err != nil {
145 | http.Error(w, err.Error(), http.StatusInternalServerError)
146 | return
147 | }
148 | w.Header().Set("Content-Type", "application/json; charset=UTF-8")
149 | w.Write(bs)
150 | }
151 |
152 | // common http return
153 | type Dto struct {
154 | Msg string `json:"msg"`
155 | Data interface{} `json:"data"`
156 | }
157 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | GoPerfcounter
2 | ==========
3 |
4 | goperfcounter用于golang应用的业务监控。goperfcounter需要和开源监控系统[Open-Falcon](http://book.open-falcon.com/zh/index.html)一起使用。
5 |
6 | 概述
7 | -----
8 | 使用goperfcounter进行golang应用的监控,大体如下:
9 |
10 | 1. 用户在其golang应用代码中,调用goperfcounter提供的统计函数;统计函数被调用时,perfcounter会生成统计记录、并保存在内存中
11 | 2. goperfcounter会自动的、定期的将这些统计记录push给Open-Falcon的收集器([agent](https://github.com/open-falcon/agent)或[transfer](https://github.com/open-falcon/transfer))
12 | 3. 用户在Open-Falcon中,查看统计数据的绘图曲线、设置实时报警
13 |
14 | 另外,goperfcounter提供了golang应用的基础监控,包括runtime指标、debug指标等。默认情况下,基础监控是关闭的,用户可以通过[配置文件](#配置)来开启此功能。
15 |
16 | 安装
17 | -----
18 |
19 | 在golang项目中使用goperfcounter时,需要进行安装,操作如下
20 |
21 | ```bash
22 | go get github.com/niean/goperfcounter
23 |
24 | ```
25 |
26 | 使用
27 | -----
28 |
29 | 用户需要引入goperfcounter包,需要在代码片段中调用goperfcounter的[API](#API)。比如,用户想要统计函数的出错次数,可以调用`Meter`方法。
30 |
31 | ```go
32 | package xxx
33 |
34 | import (
35 | pfc "github.com/niean/goperfcounter"
36 | )
37 |
38 | func foo() {
39 | if err := bar(); err != nil {
40 | pfc.Meter("bar.called.error", int64(1))
41 | }
42 | }
43 |
44 | func bar() error {
45 | // do sth ...
46 | return nil
47 | }
48 |
49 | ```
50 |
51 | 这个调用主要会产生2个Open-Falcon统计指标,如下。其中,`timestamp `和`value`是监控数据的取值;`endpoint`默认为服务器`Hostname()`,可以通过配置文件设置;`step`默认为60s,可以通过配置文件设置;`tags`中包含一个`name=bar.called.error`的标签(`bar.called.error`为用户自定义的统计器名称),其他`tags`标签可以通过配置文件设置;`counterType `和`metric`由goperfcounter决定。
52 |
53 | ```python
54 | {
55 | "counterType": "GAUGE",
56 | "endpoint": "git",
57 | "metric": "rate",
58 | "step": 20,
59 | "tags": "module=perfcounter,name=bar.called.error",
60 | "timestamp": 1451397266,
61 | "value": 13.14
62 | },
63 | {
64 | "counterType": "GAUGE",
65 | "endpoint": "git",
66 | "metric": "sum",
67 | "step": 20,
68 | "tags": "module=perfcounter,name=bar.called.error",
69 | "timestamp": 1451397266,
70 | "value": 1023
71 | }
72 |
73 | ```
74 |
75 |
76 | 配置
77 | ----
78 | 默认情况下,goperfcounter不需要进行配置。如果用户需要定制goperfcounter的行为,可以通过配置文件来进行。配置文件需要满足以下的条件:
79 |
80 | + 配置文件必须和golang二进制文件应用文件,在同一目录
81 | + 配置文件命名,必须为```perfcounter.json```
82 |
83 | 配置文件的内容,如下
84 |
85 | ```go
86 | {
87 | "debug": false, // 是否开启调制,默认为false
88 | "hostname": "", // 机器名(也即endpoint名称),默认为本机名称
89 | "tags": "", // tags标签,默认为空。一个tag形如"key=val",多个tag用逗号分隔;name为保留字段,因此不允许设置形如"name=xxx"的tag。eg. "cop=xiaomi,module=perfcounter"
90 | "step": 60, // 上报周期,单位s,默认为60s
91 | "bases":[], // gvm基础信息采集,可选值为"debug"、"runtime",默认不采集
92 | "push": { // push数据到Open-Falcon
93 | "enabled":true, // 是否开启自动push,默认开启
94 | "api": "" // Open-Falcon接收器地址,默认为本地agent,即"http:// 127.0.0.1:1988/v1/push"
95 | },
96 | "http": { // http服务,为了安全考虑,当前只允许本地访问
97 | "enabled": false, // 是否开启http服务,默认不开启
98 | "listen": "" // http服务监听地址,默认为空。eg. "0.0.0.0:2015"表示在2015端口开启http监听
99 | }
100 | }
101 |
102 | ```
103 |
104 |
105 |
106 | API
107 | ----
108 |
109 | 几个常用接口,如下。
110 |
111 | |接口名称|例子|使用场景|
112 | |:----|:----|:---|
113 | |Meter|`// 统计页面访问次数,每来一次请求,pv加1`
`Meter("pageView", int64(1)) `|Meter用于累加计数。输出累加求和、变化率|
114 | |Gauge|`// 统计队列长度`
`Gauge("queueSize", int64(len(myQueueList))) `
`GaugeFloat64("queueSize", float64(len(myQueueList)))`|Gauge用于记录瞬时值。支持int64、float64类型|
115 | |Histogram|`// 统计线程并发度`
`Histogram("processNum", int64(326)) `| Histogram用于计算统计分布。输出最大值、最小值、平均值、75th、95th、99th等|
116 |
117 | 更详细的API介绍,请移步到[这里](https://github.com/niean/goperfcounter/blob/master/doc/API.md)。
118 |
119 |
120 |
121 | 数据上报
122 | ----
123 |
124 | goperfcounter会将各种统计器的统计结果,定时发送到Open-Falcon。每种统计器,会被转换成不同的Open-Falcon指标项,转换关系如下。每条数据,至少包含一个```name=XXX```的tag,```XXX```是用户定义的统计器名称。
125 |
126 |
| 统计器类型 | 129 |输出指标的名称 | 130 |输出指标的含义 | 131 |
|---|---|---|
| Gauge | 134 |value | 135 |最后一次的记录值(float64) | 136 |
| Meter | 139 |sum | 140 |事件发生的总次数(即所有计数的累加和) | 141 |
| rate | 144 |一个Open-Falcon上报周期(默认60s)内,事件发生的频率,单位CPS | 145 ||
| Histogram | 148 |max | 149 |采样数据的最大值 | 150 |
| min | 153 |采样数据的最小值 | 154 ||
| mean | 157 |采样数据的平均值 | 158 ||
| 75th | 161 |所有采样数据中,处于75%处的数值 | 162 ||
| 95th | 165 |所有采样数据中,处于95%处的数值 | 166 ||
| 99th | 169 |所有采样数据中,处于99%处的数值 | 170 |