├── .gitignore ├── .travis.yml ├── LICENCE.txt ├── Makefile ├── README.md ├── counter.go ├── counter_test.go ├── glide.lock ├── glide.yaml ├── gostats.go ├── gostats_test.go ├── grafana.json └── sample └── sample.go /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /sample/sample 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | - 1.6 6 | - tip 7 | 8 | install: 9 | make test-setup 10 | 11 | allow_failures: 12 | - go: tip 13 | 14 | script: 15 | - make test 16 | 17 | env: 18 | - "GO15VENDOREXPERIMENT=1" 19 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Sam Rudge 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | GLIDEBIN=${GOPATH}/bin/glide 3 | GOFILES=$(shell find . -path ./vendor -prune -o -name '*.go' -print) 4 | 5 | $(GLIDEBIN): 6 | go get github.com/Masterminds/glide 7 | 8 | test-setup: $(GLIDEBIN) 9 | ${GOPATH}/bin/glide install 10 | 11 | test: test-setup 12 | go build 13 | go test -v ./ 14 | gofmt -l ${GOFILES} | read && echo "gofmt failures" && gofmt -d ${GOFILES} && exit 1 || true 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-Stats 2 | 3 | Generic GoLang internals instrumentation 4 | 5 | [![Build Status](https://travis-ci.org/samarudge/go-stats.svg?branch=master)](https://travis-ci.org/samarudge/go-stats) 6 | [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)]() 7 | 8 | ![](http://f.cl.ly/items/3q3K381r2D0K3O3S2H0Q/Screen%20Shot%202016-04-03%20at%2018.56.08.png?v=9fe2d412) 9 | 10 | Usage; 11 | 12 | ```go 13 | package main 14 | 15 | import "github.com/samarudge/go-stats" 16 | 17 | func main(){ 18 | gostats.Start("statsd-host:8125", 10, "application-name") 19 | } 20 | ``` 21 | 22 | For a sample Grafana dashboard see graphite.json 23 | 24 | Metrics exported; 25 | 26 | | Metric | Source | Description | Unit | 27 | |----------------------------|----------------------------------|----------------------------------------|--------------------| 28 | | cgo.calls | runtime.NumCgoCall() | Number of Cgo Calls | calls per second | 29 | | gc.pauseTimeMs | runtime.ReadMemStats | Pause time of last GC run | MS | 30 | | gc.pauseTimeNs | runtime.ReadMemStats | Pause time of last GC run | NS | 31 | | gc.period | runtime.ReadMemStats | Time between last two GC runs | MS | 32 | | gc.perSecond | runtime.ReadMemStats | Number of GCs per second | runs per second | 33 | | goroutines.total | runtime.NumGoroutine() | Number of currently running goroutines | total | 34 | | memory.counters.Frees | runtime.ReadMemStats.Frees | Number of frees issued to the system | frees per second | 35 | | memory.counters.Mallocs | runtime.ReadMemStats.Mallocs | Number of Mallocs issued to the system | mallocs per second | 36 | | memory.heap.Idle | runtime.ReadMemStats.HeapIdle | Memory on the heap not in use | bytes | 37 | | memory.heap.InUse | runtime.ReadMemStats.HeapInuse | Memory on the heap in use | bytes | 38 | | memory.objects.HeapObjects | runtime.ReadMemStats.HeapObjects | Total objects on the heap | # Objects | 39 | | memory.summary.Alloc | runtime.ReadMemStats.Alloc | Total bytes allocated | bytes | 40 | | memory.summary.System | runtime.ReadMemStats.HeapSys | Total bytes acquired from system | bytes | 41 | 42 | More documentation coming soon... -------------------------------------------------------------------------------- /counter.go: -------------------------------------------------------------------------------- 1 | package gostats 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type counter struct { 9 | LastUpdate time.Time 10 | LastValue int64 11 | } 12 | 13 | var counterTracker = make(map[string]counter) 14 | var trackerMutex = &sync.Mutex{} 15 | 16 | func perSecondCounter(name string, value int64) float64 { 17 | trackerMutex.Lock() 18 | defer trackerMutex.Unlock() 19 | updateTime := time.Now() 20 | var tracker counter 21 | 22 | tracker, found := counterTracker[name] 23 | if !found { 24 | tracker.LastUpdate = updateTime 25 | tracker.LastValue = value 26 | counterTracker[name] = tracker 27 | } 28 | 29 | secondsSince := updateTime.Sub(tracker.LastUpdate).Seconds() 30 | valueDiff := value - tracker.LastValue 31 | 32 | tracker.LastUpdate = updateTime 33 | tracker.LastValue = value 34 | counterTracker[name] = tracker 35 | 36 | if secondsSince == 0 { 37 | return float64(0) 38 | } 39 | 40 | changePerSecond := float64(valueDiff) / secondsSince 41 | return changePerSecond 42 | } 43 | -------------------------------------------------------------------------------- /counter_test.go: -------------------------------------------------------------------------------- 1 | package gostats 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestCounter(t *testing.T) { 9 | v := perSecondCounter("counterTest", 10) 10 | assert.Equal(t, float64(0), v, "first counter should be zero") 11 | } 12 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 2beda8a911e8467f912452827689314f5c7a671492dab4841b12ba05d051039d 2 | updated: 2016-04-13T12:43:34.769266939+01:00 3 | imports: 4 | - name: github.com/davecgh/go-spew 5 | version: 2df174808ee097f90d259e432cc04442cf60be21 6 | subpackages: 7 | - spew 8 | - name: github.com/pmezard/go-difflib 9 | version: d8ed2627bdf02c080bf22230dbb337003b7aba2d 10 | subpackages: 11 | - difflib 12 | - name: github.com/quipo/statsd 13 | version: 494c2067718d0c58116be15337f466e393273617 14 | subpackages: 15 | - event 16 | - name: github.com/stretchr/objx 17 | version: cbeaeb16a013161a98496fad62933b1d21786672 18 | - name: github.com/stretchr/testify 19 | version: f390dcf405f7b83c997eac1b06768bb9f44dec18 20 | subpackages: 21 | - assert 22 | - http 23 | - mock 24 | devImports: [] 25 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/samarudge/go-stats 2 | import: 3 | - package: github.com/quipo/statsd 4 | version: 494c2067718d0c58116be15337f466e393273617 5 | - package: github.com/stretchr/testify 6 | version: v1.1.3 7 | -------------------------------------------------------------------------------- /gostats.go: -------------------------------------------------------------------------------- 1 | package gostats 2 | 3 | import ( 4 | "github.com/quipo/statsd" 5 | "os" 6 | "regexp" 7 | "runtime" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type collectorList []func() map[string]float64 13 | 14 | type GoStats struct { 15 | ClientName string 16 | Hostname string 17 | PushInterval time.Duration 18 | StatsdHost string 19 | PushTicker *time.Ticker 20 | Conn *statsd.StatsdClient 21 | Collectors collectorList 22 | } 23 | 24 | func sanitizeMetricName(name string) string { 25 | for _, c := range []string{"/", ".", " "} { 26 | name = strings.Replace(name, c, "_", -1) 27 | } 28 | 29 | r := regexp.MustCompile("[^a-zA-Z0-9-_]") 30 | name = r.ReplaceAllString(name, "") 31 | 32 | return name 33 | } 34 | 35 | func New() *GoStats { 36 | s := GoStats{} 37 | s.ClientName = "gostats" 38 | host, _ := os.Hostname() 39 | s.Hostname = sanitizeMetricName(host) 40 | 41 | s.Collectors = collectorList{memStats, goRoutines, cgoCalls, gcs} 42 | 43 | return &s 44 | } 45 | 46 | func Start(statsdHost string, pushInterval int, clientName string) (*GoStats, error) { 47 | s := New() 48 | 49 | s.StatsdHost = statsdHost 50 | s.PushInterval = time.Duration(pushInterval) * time.Second 51 | s.ClientName = clientName 52 | 53 | err := s.Start() 54 | 55 | return s, err 56 | } 57 | 58 | func (s *GoStats) MetricBase() string { 59 | return strings.Join([]string{"gostats", s.ClientName, s.Hostname, ""}, ".") 60 | } 61 | 62 | func (s *GoStats) Start() error { 63 | s.Conn = statsd.NewStatsdClient(s.StatsdHost, s.MetricBase()) 64 | err := s.Conn.CreateSocket() 65 | if err != nil { 66 | return err 67 | } 68 | 69 | s.PushTicker = time.NewTicker(s.PushInterval) 70 | 71 | go s.startSender() 72 | 73 | return nil 74 | } 75 | 76 | func (s *GoStats) Stop() { 77 | s.PushTicker.Stop() 78 | } 79 | 80 | func (s *GoStats) startSender() { 81 | buffer := statsd.NewStatsdBuffer(s.PushInterval, s.Conn) 82 | for { 83 | select { 84 | case <-s.PushTicker.C: 85 | s.doSend(buffer) 86 | } 87 | } 88 | } 89 | 90 | func (s *GoStats) doSend(b *statsd.StatsdBuffer) { 91 | for _, collector := range s.Collectors { 92 | metrics := collector() 93 | 94 | for metricName, metricValue := range metrics { 95 | b.FGauge(metricName, metricValue) 96 | } 97 | } 98 | } 99 | 100 | func memStats() map[string]float64 { 101 | m := runtime.MemStats{} 102 | runtime.ReadMemStats(&m) 103 | metrics := map[string]float64{ 104 | "memory.objects.HeapObjects": float64(m.HeapObjects), 105 | "memory.summary.Alloc": float64(m.Alloc), 106 | "memory.counters.Mallocs": perSecondCounter("mallocs", int64(m.Mallocs)), 107 | "memory.counters.Frees": perSecondCounter("frees", int64(m.Frees)), 108 | "memory.summary.System": float64(m.HeapSys), 109 | "memory.heap.Idle": float64(m.HeapIdle), 110 | "memory.heap.InUse": float64(m.HeapInuse), 111 | } 112 | 113 | return metrics 114 | } 115 | 116 | func goRoutines() map[string]float64 { 117 | return map[string]float64{ 118 | "goroutines.total": float64(runtime.NumGoroutine()), 119 | } 120 | } 121 | 122 | func cgoCalls() map[string]float64 { 123 | return map[string]float64{ 124 | "cgo.calls": perSecondCounter("cgoCalls", runtime.NumCgoCall()), 125 | } 126 | } 127 | 128 | var lastGcPause float64 129 | var lastGcTime uint64 130 | var lastGcPeriod float64 131 | 132 | func gcs() map[string]float64 { 133 | m := runtime.MemStats{} 134 | runtime.ReadMemStats(&m) 135 | gcPause := float64(m.PauseNs[(m.NumGC+255)%256]) 136 | if gcPause > 0 { 137 | lastGcPause = gcPause 138 | } 139 | 140 | if m.LastGC > lastGcTime { 141 | lastGcPeriod = float64(m.LastGC - lastGcTime) 142 | if lastGcPeriod == float64(m.LastGC) { 143 | lastGcPeriod = 0 144 | } 145 | 146 | lastGcPeriod = lastGcPeriod / 1000000 147 | 148 | lastGcTime = m.LastGC 149 | } 150 | 151 | return map[string]float64{ 152 | "gc.perSecond": perSecondCounter("gcs-total", int64(m.NumGC)), 153 | "gc.pauseTimeNs": lastGcPause, 154 | "gc.pauseTimeMs": lastGcPause / float64(1000000), 155 | "gc.period": lastGcPeriod, 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /gostats_test.go: -------------------------------------------------------------------------------- 1 | package gostats 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "os" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | type metricNameTest struct { 12 | Source string 13 | Expected string 14 | } 15 | 16 | func TestSanitizeMetricName(t *testing.T) { 17 | cases := []metricNameTest{ 18 | metricNameTest{"mymetricname", "mymetricname"}, 19 | metricNameTest{"my metric name", "my_metric_name"}, 20 | metricNameTest{"my/metric/name", "my_metric_name"}, 21 | metricNameTest{"my.metric name", "my_metric_name"}, 22 | metricNameTest{"my-metric/name", "my-metric_name"}, 23 | metricNameTest{"my-metric@name", "my-metricname"}, 24 | } 25 | 26 | for _, c := range cases { 27 | n := sanitizeMetricName(c.Source) 28 | if n != c.Expected { 29 | assert.Equal(t, c.Expected, n, "metric name should be sanitized correctly") 30 | } 31 | } 32 | } 33 | 34 | func TestNew(t *testing.T) { 35 | s := New() 36 | h, _ := os.Hostname() 37 | assert.Equal(t, sanitizeMetricName(h), s.Hostname, "hostname should be set") 38 | assert.Equal(t, "gostats", s.ClientName, "default client name should be set") 39 | 40 | s.Hostname = "localhost" 41 | assert.Equal(t, "gostats.gostats.localhost.", s.MetricBase(), "metric base should be correct") 42 | } 43 | 44 | func TestStart(t *testing.T) { 45 | s, err := Start("localhost:8015", 5, "testclient") 46 | defer s.Stop() 47 | 48 | assert.Nil(t, err) 49 | 50 | s.Hostname = "localhost" 51 | assert.Equal(t, "gostats.testclient.localhost.", s.MetricBase(), "metric base should be correct") 52 | assert.Equal(t, time.Duration(5*time.Second), s.PushInterval, "push interval should be correct") 53 | assert.Equal(t, "localhost:8015", s.StatsdHost, "statsd host should be correct") 54 | } 55 | 56 | func returnsKeys(t *testing.T, expectedKeys []string, response map[string]float64) { 57 | for _, k := range expectedKeys { 58 | _, found := response[k] 59 | assert.True(t, found, fmt.Sprintf("Should expose metric %s", k)) 60 | } 61 | } 62 | 63 | func TestMemStats(t *testing.T) { 64 | returnsKeys(t, []string{ 65 | "memory.objects.HeapObjects", 66 | "memory.summary.Alloc", 67 | "memory.counters.Mallocs", 68 | "memory.counters.Frees", 69 | "memory.summary.System", 70 | "memory.heap.Idle", 71 | "memory.heap.InUse", 72 | }, memStats()) 73 | } 74 | 75 | func TestGoRoutines(t *testing.T) { 76 | returnsKeys(t, []string{ 77 | "goroutines.total", 78 | }, goRoutines()) 79 | } 80 | 81 | func TestCgoCalls(t *testing.T) { 82 | returnsKeys(t, []string{ 83 | "cgo.calls", 84 | }, cgoCalls()) 85 | } 86 | 87 | func TestGcs(t *testing.T) { 88 | gcs := gcs() 89 | returnsKeys(t, []string{ 90 | "gc.perSecond", 91 | "gc.pauseTimeNs", 92 | "gc.pauseTimeMs", 93 | }, gcs) 94 | 95 | assert.Equal(t, gcs["gc.pauseTimeNs"]/float64(1000000), gcs["gc.pauseTimeMs"], "Pause time NS should convert to MS") 96 | } 97 | -------------------------------------------------------------------------------- /grafana.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8, 3 | "title": "Go-Stats", 4 | "originalTitle": "Go-Stats", 5 | "tags": [], 6 | "style": "dark", 7 | "timezone": "browser", 8 | "editable": true, 9 | "hideControls": false, 10 | "sharedCrosshair": false, 11 | "rows": [ 12 | { 13 | "collapse": false, 14 | "editable": true, 15 | "height": "25px", 16 | "panels": [ 17 | { 18 | "cacheTimeout": null, 19 | "colorBackground": false, 20 | "colorValue": false, 21 | "colors": [ 22 | "rgba(245, 54, 54, 0.9)", 23 | "rgba(237, 129, 40, 0.89)", 24 | "rgba(50, 172, 45, 0.97)" 25 | ], 26 | "datasource": null, 27 | "editable": true, 28 | "error": false, 29 | "format": "bytes", 30 | "hideTimeOverride": true, 31 | "id": 1, 32 | "interval": null, 33 | "isNew": true, 34 | "links": [], 35 | "maxDataPoints": 100, 36 | "nullPointMode": "connected", 37 | "nullText": null, 38 | "postfix": "", 39 | "postfixFontSize": "50%", 40 | "prefix": "", 41 | "prefixFontSize": "50%", 42 | "span": 3, 43 | "sparkline": { 44 | "fillColor": "rgba(31, 118, 189, 0.18)", 45 | "full": true, 46 | "lineColor": "rgb(31, 120, 193)", 47 | "show": true 48 | }, 49 | "targets": [ 50 | { 51 | "refId": "A", 52 | "target": "stats.gauges.gostats.$Process.$Host.memory.summary.System", 53 | "textEditor": true 54 | } 55 | ], 56 | "thresholds": "", 57 | "timeFrom": "1h", 58 | "title": "System Memory", 59 | "type": "singlestat", 60 | "valueFontSize": "80%", 61 | "valueMaps": [ 62 | { 63 | "op": "=", 64 | "text": "N/A", 65 | "value": "null" 66 | } 67 | ], 68 | "valueName": "current" 69 | }, 70 | { 71 | "cacheTimeout": null, 72 | "colorBackground": false, 73 | "colorValue": false, 74 | "colors": [ 75 | "rgba(245, 54, 54, 0.9)", 76 | "rgba(237, 129, 40, 0.89)", 77 | "rgba(50, 172, 45, 0.97)" 78 | ], 79 | "datasource": null, 80 | "editable": true, 81 | "error": false, 82 | "format": "none", 83 | "hideTimeOverride": true, 84 | "id": 5, 85 | "interval": null, 86 | "isNew": true, 87 | "links": [], 88 | "maxDataPoints": 100, 89 | "nullPointMode": "connected", 90 | "nullText": null, 91 | "postfix": "", 92 | "postfixFontSize": "50%", 93 | "prefix": "", 94 | "prefixFontSize": "50%", 95 | "span": 3, 96 | "sparkline": { 97 | "fillColor": "rgba(31, 118, 189, 0.18)", 98 | "full": true, 99 | "lineColor": "rgb(31, 120, 193)", 100 | "show": true 101 | }, 102 | "targets": [ 103 | { 104 | "refId": "A", 105 | "target": "stats.gauges.gostats.$Process.$Host.goroutines.total" 106 | } 107 | ], 108 | "thresholds": "", 109 | "timeFrom": "1h", 110 | "title": "Goroutines", 111 | "type": "singlestat", 112 | "valueFontSize": "80%", 113 | "valueMaps": [ 114 | { 115 | "op": "=", 116 | "text": "N/A", 117 | "value": "null" 118 | } 119 | ], 120 | "valueName": "current" 121 | }, 122 | { 123 | "cacheTimeout": null, 124 | "colorBackground": false, 125 | "colorValue": false, 126 | "colors": [ 127 | "rgba(245, 54, 54, 0.9)", 128 | "rgba(237, 129, 40, 0.89)", 129 | "rgba(50, 172, 45, 0.97)" 130 | ], 131 | "datasource": null, 132 | "editable": true, 133 | "error": false, 134 | "format": "ns", 135 | "id": 6, 136 | "interval": null, 137 | "isNew": true, 138 | "links": [], 139 | "maxDataPoints": 100, 140 | "nullPointMode": "connected", 141 | "nullText": null, 142 | "postfix": "", 143 | "postfixFontSize": "50%", 144 | "prefix": "", 145 | "prefixFontSize": "50%", 146 | "span": 3, 147 | "sparkline": { 148 | "fillColor": "rgba(31, 118, 189, 0.18)", 149 | "full": true, 150 | "lineColor": "rgb(31, 120, 193)", 151 | "show": true 152 | }, 153 | "targets": [ 154 | { 155 | "refId": "A", 156 | "target": "stats.gauges.gostats.$Process.$Host.gc.pauseTimeNs" 157 | } 158 | ], 159 | "thresholds": "", 160 | "title": "GC Duration", 161 | "type": "singlestat", 162 | "valueFontSize": "80%", 163 | "valueMaps": [ 164 | { 165 | "op": "=", 166 | "text": "N/A", 167 | "value": "null" 168 | } 169 | ], 170 | "valueName": "current" 171 | }, 172 | { 173 | "cacheTimeout": null, 174 | "colorBackground": false, 175 | "colorValue": false, 176 | "colors": [ 177 | "rgba(245, 54, 54, 0.9)", 178 | "rgba(237, 129, 40, 0.89)", 179 | "rgba(50, 172, 45, 0.97)" 180 | ], 181 | "datasource": null, 182 | "editable": true, 183 | "error": false, 184 | "format": "ms", 185 | "id": 7, 186 | "interval": null, 187 | "isNew": true, 188 | "links": [], 189 | "maxDataPoints": 100, 190 | "nullPointMode": "connected", 191 | "nullText": null, 192 | "postfix": "", 193 | "postfixFontSize": "50%", 194 | "prefix": "", 195 | "prefixFontSize": "50%", 196 | "span": 3, 197 | "sparkline": { 198 | "fillColor": "rgba(31, 118, 189, 0.18)", 199 | "full": true, 200 | "lineColor": "rgb(31, 120, 193)", 201 | "show": true 202 | }, 203 | "targets": [ 204 | { 205 | "refId": "A", 206 | "target": "stats.gauges.gostats.$Process.$Host.gc.period", 207 | "textEditor": true 208 | } 209 | ], 210 | "thresholds": "", 211 | "title": "GC Every", 212 | "type": "singlestat", 213 | "valueFontSize": "80%", 214 | "valueMaps": [ 215 | { 216 | "op": "=", 217 | "text": "N/A", 218 | "value": "null" 219 | } 220 | ], 221 | "valueName": "current" 222 | } 223 | ], 224 | "title": "Row" 225 | }, 226 | { 227 | "collapse": false, 228 | "editable": true, 229 | "height": "250px", 230 | "panels": [ 231 | { 232 | "aliasColors": {}, 233 | "bars": false, 234 | "datasource": null, 235 | "editable": true, 236 | "error": false, 237 | "fill": 0, 238 | "grid": { 239 | "leftLogBase": 1, 240 | "leftMax": null, 241 | "leftMin": 0, 242 | "rightLogBase": 1, 243 | "rightMax": null, 244 | "rightMin": null, 245 | "threshold1": null, 246 | "threshold1Color": "rgba(216, 200, 27, 0.27)", 247 | "threshold2": null, 248 | "threshold2Color": "rgba(234, 112, 112, 0.22)" 249 | }, 250 | "id": 2, 251 | "isNew": true, 252 | "legend": { 253 | "avg": false, 254 | "current": false, 255 | "max": false, 256 | "min": false, 257 | "show": true, 258 | "total": false, 259 | "values": false 260 | }, 261 | "lines": true, 262 | "linewidth": 3, 263 | "links": [], 264 | "nullPointMode": "connected", 265 | "percentage": false, 266 | "pointradius": 5, 267 | "points": false, 268 | "renderer": "flot", 269 | "seriesOverrides": [ 270 | { 271 | "alias": "/(Idle|InUse)/", 272 | "fill": 8, 273 | "linewidth": 0, 274 | "stack": true 275 | } 276 | ], 277 | "span": 6, 278 | "stack": false, 279 | "steppedLine": false, 280 | "targets": [ 281 | { 282 | "refId": "A", 283 | "target": "alias(stats.gauges.gostats.$Process.$Host.memory.summary.System, 'System Allocation')" 284 | }, 285 | { 286 | "refId": "B", 287 | "target": "aliasByNode(stats.gauges.gostats.$Process.$Host.memory.heap.InUse, 7)" 288 | }, 289 | { 290 | "refId": "C", 291 | "target": "aliasByNode(stats.gauges.gostats.$Process.$Host.memory.heap.Idle, 7)" 292 | } 293 | ], 294 | "timeFrom": null, 295 | "timeShift": null, 296 | "title": "Memory Usage", 297 | "tooltip": { 298 | "shared": true, 299 | "value_type": "cumulative" 300 | }, 301 | "type": "graph", 302 | "x-axis": true, 303 | "y-axis": true, 304 | "y_formats": [ 305 | "bytes", 306 | "short" 307 | ] 308 | }, 309 | { 310 | "aliasColors": {}, 311 | "bars": false, 312 | "datasource": null, 313 | "editable": true, 314 | "error": false, 315 | "fill": 1, 316 | "grid": { 317 | "leftLogBase": 1, 318 | "leftMax": null, 319 | "leftMin": 0, 320 | "rightLogBase": 1, 321 | "rightMax": null, 322 | "rightMin": null, 323 | "threshold1": null, 324 | "threshold1Color": "rgba(216, 200, 27, 0.27)", 325 | "threshold2": null, 326 | "threshold2Color": "rgba(234, 112, 112, 0.22)" 327 | }, 328 | "id": 3, 329 | "isNew": true, 330 | "legend": { 331 | "avg": false, 332 | "current": false, 333 | "max": false, 334 | "min": false, 335 | "show": true, 336 | "total": false, 337 | "values": false 338 | }, 339 | "lines": true, 340 | "linewidth": 2, 341 | "links": [], 342 | "nullPointMode": "connected", 343 | "percentage": false, 344 | "pointradius": 5, 345 | "points": false, 346 | "renderer": "flot", 347 | "seriesOverrides": [], 348 | "span": 6, 349 | "stack": false, 350 | "steppedLine": false, 351 | "targets": [ 352 | { 353 | "refId": "A", 354 | "target": "aliasByNode(stats.gauges.gostats.$Process.$Host.memory.counters.*, 7)" 355 | } 356 | ], 357 | "timeFrom": null, 358 | "timeShift": null, 359 | "title": "Mallocs & Frees", 360 | "tooltip": { 361 | "shared": true, 362 | "value_type": "cumulative" 363 | }, 364 | "type": "graph", 365 | "x-axis": true, 366 | "y-axis": true, 367 | "y_formats": [ 368 | "short", 369 | "short" 370 | ] 371 | } 372 | ], 373 | "title": "New row" 374 | }, 375 | { 376 | "collapse": false, 377 | "editable": true, 378 | "height": "250px", 379 | "panels": [ 380 | { 381 | "aliasColors": {}, 382 | "bars": false, 383 | "datasource": null, 384 | "editable": true, 385 | "error": false, 386 | "fill": 1, 387 | "grid": { 388 | "leftLogBase": 1, 389 | "leftMax": null, 390 | "leftMin": null, 391 | "rightLogBase": 1, 392 | "rightMax": null, 393 | "rightMin": 0, 394 | "threshold1": null, 395 | "threshold1Color": "rgba(216, 200, 27, 0.27)", 396 | "threshold2": null, 397 | "threshold2Color": "rgba(234, 112, 112, 0.22)" 398 | }, 399 | "id": 4, 400 | "isNew": true, 401 | "legend": { 402 | "avg": false, 403 | "current": false, 404 | "max": false, 405 | "min": false, 406 | "show": true, 407 | "total": false, 408 | "values": false 409 | }, 410 | "lines": true, 411 | "linewidth": 2, 412 | "links": [], 413 | "nullPointMode": "connected", 414 | "percentage": false, 415 | "pointradius": 5, 416 | "points": false, 417 | "renderer": "flot", 418 | "seriesOverrides": [ 419 | { 420 | "alias": "Pause Time", 421 | "yaxis": 2 422 | } 423 | ], 424 | "span": 12, 425 | "stack": false, 426 | "steppedLine": false, 427 | "targets": [ 428 | { 429 | "refId": "A", 430 | "target": "alias(stats.gauges.gostats.$Process.$Host.gc.perSecond, 'GCs Per Second')" 431 | }, 432 | { 433 | "refId": "B", 434 | "target": "alias(stats.gauges.gostats.$Process.$Host.gc.pauseTimeNs, 'Pause Time')" 435 | } 436 | ], 437 | "timeFrom": null, 438 | "timeShift": null, 439 | "title": "GC Stats", 440 | "tooltip": { 441 | "shared": true, 442 | "value_type": "cumulative" 443 | }, 444 | "type": "graph", 445 | "x-axis": true, 446 | "y-axis": true, 447 | "y_formats": [ 448 | "short", 449 | "ns" 450 | ] 451 | } 452 | ], 453 | "title": "New row" 454 | } 455 | ], 456 | "time": { 457 | "from": "now-24h", 458 | "to": "now" 459 | }, 460 | "timepicker": { 461 | "now": true, 462 | "refresh_intervals": [ 463 | "5s", 464 | "10s", 465 | "30s", 466 | "1m", 467 | "5m", 468 | "15m", 469 | "30m", 470 | "1h", 471 | "2h", 472 | "1d" 473 | ], 474 | "time_options": [ 475 | "5m", 476 | "15m", 477 | "1h", 478 | "6h", 479 | "12h", 480 | "24h", 481 | "2d", 482 | "7d", 483 | "30d" 484 | ] 485 | }, 486 | "templating": { 487 | "list": [ 488 | { 489 | "allFormat": "glob", 490 | "current": {}, 491 | "datasource": null, 492 | "includeAll": false, 493 | "multi": false, 494 | "multiFormat": "glob", 495 | "name": "Host", 496 | "options": [], 497 | "query": "stats.gauges.gostats.*.*", 498 | "refresh": true, 499 | "type": "query" 500 | }, 501 | { 502 | "allFormat": "glob", 503 | "current": {}, 504 | "datasource": null, 505 | "includeAll": false, 506 | "multi": false, 507 | "multiFormat": "glob", 508 | "name": "Process", 509 | "options": [], 510 | "query": "stats.gauges.gostats.*", 511 | "refresh": true, 512 | "type": "query" 513 | } 514 | ] 515 | }, 516 | "annotations": { 517 | "list": [] 518 | }, 519 | "refresh": "5s", 520 | "schemaVersion": 8, 521 | "version": 9, 522 | "links": [] 523 | } 524 | -------------------------------------------------------------------------------- /sample/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "github.com/samarudge/go-stats" 7 | "net/http" 8 | _ "net/http/pprof" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | go http.ListenAndServe("localhost:6060", nil) 15 | 16 | gostats.Start("localhost:8127", 1, "sample") 17 | 18 | time.Sleep(5 * time.Second) 19 | 20 | fmt.Println("Starting generation of random bytes") 21 | var randBytes []byte 22 | bytesToRead := 128 * 1024 23 | for i := 0; i <= bytesToRead; i++ { 24 | byteChunk := make([]byte, 1024) 25 | _, err := rand.Read(byteChunk) 26 | if err != nil { 27 | fmt.Println("error:", err) 28 | return 29 | } 30 | 31 | randBytes = append(randBytes, byteChunk...) 32 | 33 | time.Sleep((60 * time.Second) / time.Duration(bytesToRead)) 34 | } 35 | fmt.Println(" - Done") 36 | time.Sleep(30) 37 | 38 | fmt.Println("Forcing GC") 39 | randBytes = []byte{} 40 | runtime.GC() 41 | fmt.Println("Done") 42 | 43 | time.Sleep(10) 44 | } 45 | --------------------------------------------------------------------------------