├── Makefile ├── Readme.md ├── main.go └── pkg └── nsqd ├── nsqd.go └── stats.go /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | gox -os="linux darwin" -arch=amd64 4 | 5 | clean: 6 | git clean -fd 7 | 8 | .PHONY: build 9 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # nsqtop 3 | 4 | [NSQ](http://nsq.io/) "top" program, similar to nsq_stat for monitoring NSQD nodes from the terminal. The main difference is you do not have to specify the topic/channel, it displays all topics & channels. 5 | 6 | ## Installation 7 | 8 | Via `go` or grab one of the [binaries](https://github.com/tj/nsqtop/releases). 9 | 10 | ```sh 11 | $ curl -sf https://gobinaries.com/tj/nsqtop | sh 12 | ``` 13 | 14 | ## License 15 | 16 | MIT 17 | 18 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/dustin/go-humanize" 4 | import "github.com/tj/nsqtop/pkg/nsqd" 5 | import "github.com/segmentio/go-log" 6 | import "github.com/tj/go-gracefully" 7 | import "github.com/tj/docopt" 8 | import stdlog "log" 9 | import "io/ioutil" 10 | import "time" 11 | import "fmt" 12 | 13 | var Version = "0.1.0" 14 | 15 | const Usage = ` 16 | Usage: 17 | nsqtop [--interval n] [--nsqd-http-address a...] 18 | nsqtop -h | --help 19 | nsqtop --version 20 | 21 | Options: 22 | -a, --nsqd-http-address a nsqd http address [default: 0.0.0.0:4151] 23 | -i, --interval n refresh interval [default: 1s] 24 | -h, --help output help information 25 | -v, --version output version 26 | 27 | ` 28 | 29 | func main() { 30 | stdlog.SetOutput(ioutil.Discard) 31 | 32 | args, err := docopt.Parse(Usage, nil, true, Version, false) 33 | log.Check(err) 34 | 35 | addrs := args["--nsqd-http-address"].([]string) 36 | 37 | d, err := time.ParseDuration(args["--interval"].(string)) 38 | log.Check(err) 39 | 40 | go loop(d, addrs) 41 | 42 | gracefully.Shutdown() 43 | } 44 | 45 | func loop(d time.Duration, addrs []string) { 46 | for _ = range time.Tick(d) { 47 | for _, addr := range addrs { 48 | nsq := nsqd.New(addr) 49 | 50 | stats, err := nsq.Stats() 51 | log.Check(err) 52 | 53 | fmt.Printf("\033[2J\033[0f") 54 | fmt.Printf("\n\n\n\033[1m%30s\033[0m\n", addr) 55 | fmt.Printf("%30s %30s %15s %15s %15s %15s\n", "topic", "channel", "depth", "in-flight", "deferred", "timeouts") 56 | for _, topic := range stats.Topics { 57 | fmt.Printf("%30s %30s %15s %15s %15s %15s\n", 58 | topic.Name, 59 | "∙", 60 | humanize.Comma(topic.Depth), 61 | humanize.Comma(topic.InFlightCount), 62 | humanize.Comma(topic.DeferredCount), 63 | humanize.Comma(topic.TimeoutCount)) 64 | 65 | for _, channel := range topic.Channels { 66 | fmt.Printf("%30s %30s %15s %15s %15s %15s\n", 67 | "∙", 68 | channel.Name, 69 | humanize.Comma(channel.Depth), 70 | humanize.Comma(channel.InFlightCount), 71 | humanize.Comma(channel.DeferredCount), 72 | humanize.Comma(channel.TimeoutCount)) 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /pkg/nsqd/nsqd.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import "encoding/json" 4 | import "net/http" 5 | import "fmt" 6 | 7 | // NSQD client. 8 | type NSQD struct { 9 | Address string 10 | } 11 | 12 | // New client with the http address. 13 | func New(addr string) *NSQD { 14 | return &NSQD{ 15 | Address: addr, 16 | } 17 | } 18 | 19 | // Stats for topics and channels. 20 | func (n *NSQD) Stats() (*Stats, error) { 21 | url := fmt.Sprintf("http://%s/stats?format=json", n.Address) 22 | 23 | req, err := http.Get(url) 24 | if err != nil { 25 | return nil, err 26 | } 27 | defer req.Body.Close() 28 | 29 | var s *stats 30 | err = json.NewDecoder(req.Body).Decode(&s) 31 | return s.Data, err 32 | } 33 | -------------------------------------------------------------------------------- /pkg/nsqd/stats.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | // stats with envelope. 4 | type stats struct { 5 | Data *Stats 6 | } 7 | 8 | // Stats. 9 | type Stats struct { 10 | Version string `json:"version"` 11 | Health string `json:"health"` 12 | Topics []*Topic `json:"topics"` 13 | } 14 | 15 | // Topic stats. 16 | type Topic struct { 17 | Name string `json:"topic_name"` 18 | InFlightCount int64 `json:"in_flight_count"` 19 | DeferredCount int64 `json:"deferred_count"` 20 | MessageCount int64 `json:"message_count"` 21 | RequeueCount int64 `json:"requeue_count"` 22 | TimeoutCount int64 `json:"timeout_count"` 23 | BackendDepth int64 `json:"backend_depth"` 24 | Depth int64 `json:"depth"` 25 | Paused bool `json:"paused"` 26 | Channels []*Channel `json:"channels"` 27 | } 28 | 29 | // Channel stats. 30 | type Channel struct { 31 | Name string `json:"channel_name"` 32 | InFlightCount int64 `json:"in_flight_count"` 33 | DeferredCount int64 `json:"deferred_count"` 34 | MessageCount int64 `json:"message_count"` 35 | RequeueCount int64 `json:"requeue_count"` 36 | TimeoutCount int64 `json:"timeout_count"` 37 | BackendDepth int64 `json:"backend_depth"` 38 | Depth int64 `json:"depth"` 39 | Paused bool `json:"paused"` 40 | } 41 | --------------------------------------------------------------------------------