├── .travis.yml ├── LICENSE ├── README.md ├── aspects ├── aspect.go ├── aspect_test.go ├── memory.go ├── runtime.go └── time.go ├── doc.go ├── example └── custom_aspect.go ├── monitor.go └── monitor_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Máximo Cuadros 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-monitor [![Build Status](https://travis-ci.org/mcuadros/go-monitor.png?branch=master)](https://travis-ci.org/mcuadros/go-monitor) [![GoDoc](http://godoc.org/gopkg.in/mcuadros/go-monitor.v1?status.png)](http://godoc.org/gopkg.in/mcuadros/go-monitor.v1) [![GitHub release](https://img.shields.io/github/release/mcuadros/go-monitor.svg)](https://github.com/mcuadros/go-monitor/releases) 2 | ============================== 3 | 4 | The main goal of `go-monitor` is provide a simple and extensible way to build monitorizable long term execution processes or daemons via HTTP. 5 | 6 | Thanks to the defaults `aspects` you can monitorize parameters as runtime, memory, etc. for any Go processes and daemons. As well you can create your custom `aspects` for monitorize custom parameters from your applications. 7 | 8 | 9 | Installation 10 | ------------ 11 | 12 | The recommended way to install go-monitor 13 | 14 | ``` 15 | go get gopkg.in/mcuadros/go-monitor.v1 16 | ``` 17 | 18 | Examples 19 | -------- 20 | 21 | ## Default Monitor 22 | 23 | Import the package: 24 | 25 | ```go 26 | import "gopkg.in/mcuadros/go-monitor.v1" 27 | ``` 28 | 29 | Start the monitor just before of the bootstrap of your code: 30 | 31 | ```go 32 | m := monitor.NewMonitor(":9000") 33 | m.Start() 34 | ``` 35 | 36 | Now just try `curl http://localhost:9000/` 37 | 38 | ``` 39 | { 40 | "MemStats": { 41 | "Alloc": 7716521256, 42 | "TotalAlloc": 1935822232552, 43 | "Sys": 46882078488, 44 | ... 45 | }, 46 | "Runtime": { 47 | "GoVersion": "go1.3.3", 48 | "GoOs": "linux", 49 | "GoArch": "amd64", 50 | "CpuNum": 24, 51 | "GoroutineNum": 21196, 52 | "Gomaxprocs": 24, 53 | "CgoCallNum": 111584 54 | } 55 | } 56 | ``` 57 | 58 | At the `/` you can find all the aspects that are loaded by default, you can request other aspects through the URL `/,` 59 | 60 | ## Custom Monitor 61 | 62 | Define your custom aspect, in this case just a simple one that count the number of hits on it. 63 | 64 | ```go 65 | type CustomAspect struct { 66 | Count int 67 | } 68 | 69 | func (a *CustomAspect) GetStats() interface{} { 70 | a.Count++ 71 | return a.Count 72 | } 73 | 74 | func (a *CustomAspect) Name() string { 75 | return "Custom" 76 | } 77 | 78 | func (a *CustomAspect) InRoot() bool { 79 | return false 80 | } 81 | ``` 82 | 83 | Now just add the `CustomAspect` to the monitor and run it. 84 | 85 | ```go 86 | m := monitor.NewMonitor(":9000") 87 | m.AddAspect(&CustomAspect{}) 88 | m.Start() 89 | ``` 90 | 91 | Hit `http://localhost:9000/Custom` and obtain: 92 | ``` 93 | { 94 | "Custom": 5 95 | } 96 | ``` 97 | 98 | 99 | License 100 | ------- 101 | 102 | MIT, see [LICENSE](LICENSE) 103 | -------------------------------------------------------------------------------- /aspects/aspect.go: -------------------------------------------------------------------------------- 1 | package aspects 2 | 3 | type Aspect interface { 4 | //GetStats returns any marchable object with the stats 5 | GetStats() interface{} 6 | //Name returns the name of the Aspect 7 | Name() string 8 | //InRoot returns a bool, if `true` this aspect will be used at `/` 9 | InRoot() bool 10 | } 11 | -------------------------------------------------------------------------------- /aspects/aspect_test.go: -------------------------------------------------------------------------------- 1 | package aspects 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRuntimeAspect(t *testing.T) { 11 | a := &RuntimeAspect{} 12 | r := a.GetStats() 13 | 14 | assert.NotEqual(t, r.(*RuntimeAspectData).CpuNum, 0) 15 | } 16 | 17 | func TestMemoryAspect(t *testing.T) { 18 | a := &MemoryAspect{} 19 | r := a.GetStats() 20 | 21 | assert.NotEqual(t, r.(*runtime.MemStats).Frees, 0) 22 | } 23 | 24 | func TestTimeAspect(t *testing.T) { 25 | a := NewTimeAspect(true) 26 | r := a.GetStats() 27 | 28 | assert.NotEqual(t, r.(*TimeAspectData).StartTime.Nanosecond(), 0) 29 | assert.NotEqual(t, r.(*TimeAspectData).RequestTime.Nanosecond(), 0) 30 | assert.True(t, a.InRoot()) 31 | } 32 | -------------------------------------------------------------------------------- /aspects/memory.go: -------------------------------------------------------------------------------- 1 | package aspects 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | type MemoryAspect struct { 8 | ShowInRoot bool 9 | } 10 | 11 | //NewMemoryAspect returns the value of `runtime.ReadMemStats` 12 | func NewMemoryAspect(inRoot bool) *MemoryAspect { 13 | return &MemoryAspect{ShowInRoot: inRoot} 14 | } 15 | 16 | func (a *MemoryAspect) GetStats() interface{} { 17 | var mem runtime.MemStats 18 | runtime.ReadMemStats(&mem) 19 | 20 | return &mem 21 | } 22 | 23 | func (a *MemoryAspect) Name() string { 24 | return "MemStats" 25 | } 26 | 27 | func (a *MemoryAspect) InRoot() bool { 28 | return a.ShowInRoot 29 | } 30 | -------------------------------------------------------------------------------- /aspects/runtime.go: -------------------------------------------------------------------------------- 1 | package aspects 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | type RuntimeAspectData struct { 8 | GoVersion string 9 | GoOs string 10 | GoArch string 11 | CpuNum int 12 | GoroutineNum int 13 | Gomaxprocs int 14 | CgoCallNum int64 15 | } 16 | 17 | type RuntimeAspect struct { 18 | ShowInRoot bool 19 | } 20 | 21 | //NewRuntimeAspect returns several values from the runtome 22 | func NewRuntimeAspect(inRoot bool) *RuntimeAspect { 23 | return &RuntimeAspect{ShowInRoot: inRoot} 24 | } 25 | 26 | func (a *RuntimeAspect) GetStats() interface{} { 27 | return &RuntimeAspectData{ 28 | GoVersion: runtime.Version(), 29 | GoOs: runtime.GOOS, 30 | GoArch: runtime.GOARCH, 31 | CpuNum: runtime.NumCPU(), 32 | GoroutineNum: runtime.NumGoroutine(), 33 | Gomaxprocs: runtime.GOMAXPROCS(0), 34 | CgoCallNum: runtime.NumCgoCall(), 35 | } 36 | } 37 | 38 | func (a *RuntimeAspect) Name() string { 39 | return "Runtime" 40 | } 41 | 42 | func (a *RuntimeAspect) InRoot() bool { 43 | return a.ShowInRoot 44 | } 45 | -------------------------------------------------------------------------------- /aspects/time.go: -------------------------------------------------------------------------------- 1 | package aspects 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type TimeAspectData struct { 8 | StartTime time.Time 9 | RequestTime time.Time 10 | } 11 | 12 | type TimeAspect struct { 13 | StartTime time.Time 14 | ShowInRoot bool 15 | } 16 | 17 | //NewTimeAspect returns the current time and the running time 18 | func NewTimeAspect(inRoot bool) *TimeAspect { 19 | return &TimeAspect{StartTime: time.Now(), ShowInRoot: inRoot} 20 | } 21 | 22 | func (a *TimeAspect) GetStats() interface{} { 23 | return &TimeAspectData{ 24 | StartTime: a.StartTime, 25 | RequestTime: time.Now(), 26 | } 27 | } 28 | 29 | func (a *TimeAspect) Name() string { 30 | return "Time" 31 | } 32 | 33 | func (a *TimeAspect) InRoot() bool { 34 | return a.ShowInRoot 35 | } 36 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | The main goal of `go-monitor` is provide a simple and extensible way to build 3 | monitorizable long term execution processes or daemons via HTTP. 4 | 5 | Thanks to the defaults `aspects` you can monitorize parameters as runtime, 6 | memory, etc. for any Go processes and daemons. As well you can create your 7 | custom `aspects` for monitorize custom parameters from your applications. 8 | */ 9 | package monitor 10 | -------------------------------------------------------------------------------- /example/custom_aspect.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "gopkg.in/mcuadros/go-monitor.v1" 4 | 5 | func main() { 6 | m := monitor.NewMonitor(":9000") 7 | m.AddAspect(&CustomAspect{}) 8 | m.Start() 9 | } 10 | 11 | type CustomAspect struct { 12 | Count int 13 | } 14 | 15 | func (a *CustomAspect) GetStats() interface{} { 16 | a.Count++ 17 | return a.Count 18 | } 19 | 20 | func (a *CustomAspect) Name() string { 21 | return "Custom" 22 | } 23 | 24 | func (a *CustomAspect) InRoot() bool { 25 | return false 26 | } 27 | -------------------------------------------------------------------------------- /monitor.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strings" 7 | "sync" 8 | 9 | "gopkg.in/mcuadros/go-monitor.v1/aspects" 10 | ) 11 | 12 | type Monitor struct { 13 | Addr string 14 | Aspects map[string]aspects.Aspect 15 | 16 | server *http.Server 17 | mux *http.ServeMux 18 | 19 | sync.Mutex 20 | } 21 | 22 | //NewMonitor returns a new Monitor, with the standard Aspects (time, runtime and memory) 23 | func NewMonitor(addr string) *Monitor { 24 | m := NewPlainMonitor(addr) 25 | m.AddAspect(aspects.NewTimeAspect(true)) 26 | m.AddAspect(aspects.NewRuntimeAspect(true)) 27 | m.AddAspect(aspects.NewMemoryAspect(true)) 28 | 29 | return m 30 | } 31 | 32 | //NewPlainMonitor returns a new Monitor, without aspects 33 | func NewPlainMonitor(addr string) *Monitor { 34 | return &Monitor{ 35 | Addr: addr, 36 | Aspects: make(map[string]aspects.Aspect, 0), 37 | } 38 | } 39 | 40 | //AddAspect adds a new `aspects.Aspect` to the Monitor 41 | func (m *Monitor) AddAspect(a aspects.Aspect) { 42 | m.Aspects[a.Name()] = a 43 | } 44 | 45 | //Start launch the HTTP server 46 | func (m *Monitor) Start() error { 47 | m.buildServer() 48 | return m.server.ListenAndServe() 49 | } 50 | 51 | func (m *Monitor) buildServer() { 52 | m.server = &http.Server{Addr: m.Addr, Handler: m} 53 | } 54 | 55 | func (m *Monitor) ServeHTTP(w http.ResponseWriter, r *http.Request) { 56 | m.Lock() 57 | defer m.Unlock() 58 | 59 | if r.URL.Path == "/" { 60 | m.rootHandler(w, r) 61 | return 62 | } 63 | 64 | m.aspectHandler(w, r) 65 | } 66 | 67 | func (m *Monitor) rootHandler(w http.ResponseWriter, r *http.Request) { 68 | m.jsonHandle(m.getAllAspectsResults(), w, r) 69 | } 70 | 71 | func (m *Monitor) aspectHandler(w http.ResponseWriter, r *http.Request) { 72 | m.jsonHandle(m.getAspectsResults(r.URL.Path[1:]), w, r) 73 | } 74 | 75 | func (m *Monitor) jsonHandle(data interface{}, w http.ResponseWriter, r *http.Request) { 76 | json, err := json.MarshalIndent(data, "", " ") 77 | if err != nil { 78 | http.Error(w, err.Error(), http.StatusInternalServerError) 79 | return 80 | } 81 | 82 | w.Header().Set("Access-Control-Allow-Origin", "*") 83 | w.Header().Set("Content-Type", "application/json") 84 | w.Write(json) 85 | } 86 | 87 | func (m *Monitor) getAllAspectsResults() map[string]interface{} { 88 | res := make(map[string]interface{}, 0) 89 | for k, a := range m.Aspects { 90 | if a.InRoot() { 91 | res[k] = a.GetStats() 92 | } 93 | } 94 | 95 | return res 96 | } 97 | 98 | func (m *Monitor) getAspectsResults(aspects string) map[string]interface{} { 99 | res := make(map[string]interface{}, 0) 100 | for _, name := range strings.Split(aspects, ",") { 101 | if a, ok := m.Aspects[name]; ok { 102 | res[name] = a.GetStats() 103 | } 104 | } 105 | 106 | return res 107 | } 108 | -------------------------------------------------------------------------------- /monitor_test.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewMonitor(t *testing.T) { 11 | m := NewMonitor(":9000") 12 | assert.Len(t, m.Aspects, 3) 13 | } 14 | 15 | func TestMonitor_getAllAspectsResults(t *testing.T) { 16 | m := NewMonitor(":9000") 17 | r := m.getAllAspectsResults() 18 | 19 | r = m.getAspectsResults("MemStats,Runtime") 20 | assert.Len(t, r, 2) 21 | } 22 | 23 | func TestMonitor_getAspectsResults(t *testing.T) { 24 | m := NewMonitor(":9000") 25 | r := m.getAspectsResults("MemStats") 26 | 27 | assert.Len(t, r, 1) 28 | assert.NotNil(t, r["MemStats"].(*runtime.MemStats).Alloc) 29 | 30 | r = m.getAspectsResults("MemStats,Runtime") 31 | assert.Len(t, r, 2) 32 | } 33 | --------------------------------------------------------------------------------