diff --git a/ChangeLog.md b/ChangeLog.md index 997dd2006..7f3dd3c58 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -35,6 +35,7 @@ New Features / Enhancements: * #291 - compile templates into `nsqadmin` binary * #285/#288 - `nsq_tail` support for `-n #` to get recent # messages * #287/#294 - nsqadmin support added for showing client attributes (sample rate, TLS, compression) + * #189/#296 - add client user agent to nsqadmin ### 0.2.24 - 2013-12-07 diff --git a/nsqadmin/templates/channel.html.go b/nsqadmin/templates/channel.html.go index a604d31e0..02d5bce32 100644 --- a/nsqadmin/templates/channel.html.go +++ b/nsqadmin/templates/channel.html.go @@ -201,7 +201,7 @@ func init() { {{range .ChannelStats.Clients}} {{.ClientIdentifier}} - {{.ClientVersion}} + {{.ClientVersion}} {{if ne .ClientUserAgent ""}}({{.ClientUserAgent}}){{end}} {{if gt .SampleRate 0}} Sampled {{.SampleRate}}% diff --git a/nsqadmin/templates/node.html.go b/nsqadmin/templates/node.html.go index 9e306d4fb..2313ea3b5 100644 --- a/nsqadmin/templates/node.html.go +++ b/nsqadmin/templates/node.html.go @@ -128,6 +128,7 @@ func init() { Client Host Protocol + Attributes NSQd Host In-Flight Ready Count @@ -140,7 +141,21 @@ func init() { {{.ClientIdentifier}} - {{.ClientVersion}} + {{.ClientVersion}} {{if ne .ClientUserAgent ""}}({{.ClientUserAgent}}){{end}} + + {{if gt .SampleRate 0}} + Sampled {{.SampleRate}}% + {{end}} + {{if .TLS}} + TLS + {{end}} + {{if .Deflate}} + Delfate + {{end}} + {{if .Snappy}} + Snappy + {{end}} + {{.HostAddress}} {{.InFlightCount | commafy}} {{.ReadyCount | commafy}} diff --git a/nsqd/client_v2.go b/nsqd/client_v2.go index 094593397..e1845e11d 100644 --- a/nsqd/client_v2.go +++ b/nsqd/client_v2.go @@ -29,6 +29,7 @@ type IdentifyDataV2 struct { DeflateLevel int `json:"deflate_level"` Snappy bool `json:"snappy"` SampleRate int32 `json:"sample_rate"` + UserAgent string `json:"user_agent"` } type ClientV2 struct { @@ -42,8 +43,9 @@ type ClientV2 struct { sync.Mutex - ID int64 - context *Context + ID int64 + context *Context + UserAgent string // original connection net.Conn @@ -135,6 +137,7 @@ func (c *ClientV2) String() string { func (c *ClientV2) Identify(data IdentifyDataV2) error { c.ShortIdentifier = data.ShortId c.LongIdentifier = data.LongId + c.UserAgent = data.UserAgent err := c.SetHeartbeatInterval(data.HeartbeatInterval) if err != nil { return err @@ -155,6 +158,7 @@ func (c *ClientV2) Stats() ClientStats { Version: "V2", RemoteAddress: c.RemoteAddr().String(), Name: c.ShortIdentifier, + UserAgent: c.UserAgent, State: atomic.LoadInt32(&c.State), ReadyCount: atomic.LoadInt64(&c.ReadyCount), InFlightCount: atomic.LoadInt64(&c.InFlightCount), diff --git a/nsqd/stats.go b/nsqd/stats.go index ab8b8c82a..a8903dca5 100644 --- a/nsqd/stats.go +++ b/nsqd/stats.go @@ -76,6 +76,7 @@ type ClientStats struct { TLS bool `json:"tls"` Deflate bool `json:"deflate"` Snappy bool `json:"snappy"` + UserAgent string `json:"user_agent"` } type Topics []*Topic diff --git a/nsqd/stats_test.go b/nsqd/stats_test.go index 1cf9e968f..ee5c37293 100644 --- a/nsqd/stats_test.go +++ b/nsqd/stats_test.go @@ -1,8 +1,12 @@ package main import ( + "encoding/json" + "fmt" "github.com/bitly/go-nsq" + "github.com/bitly/nsq/util" "github.com/bmizerany/assert" + "github.com/mreiferson/go-snappystream" "io/ioutil" "log" "os" @@ -36,3 +40,50 @@ func TestStats(t *testing.T) { assert.Equal(t, len(stats[0].Channels[0].Clients), 1) log.Printf("stats: %+v", stats) } + +func TestClientAttributes(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + userAgent := "Test User Agent" + + *verbose = true + options := NewNSQDOptions() + options.SnappyEnabled = true + tcpAddr, httpAddr, nsqd := mustStartNSQD(options) + defer nsqd.Exit() + + conn, err := mustConnectNSQD(tcpAddr) + assert.Equal(t, err, nil) + + data := identifyFeatureNegotiation(t, conn, map[string]interface{}{"snappy": true, "user_agent": userAgent}) + r := struct { + Snappy bool `json:"snappy"` + UserAgent string `json:"user_agent"` + }{} + err = json.Unmarshal(data, &r) + assert.Equal(t, err, nil) + assert.Equal(t, r.Snappy, true) + + compressConn := snappystream.NewReader(conn, snappystream.SkipVerifyChecksum) + + w := snappystream.NewWriter(conn) + + rw := readWriter{compressConn, w} + + topicName := "test_client_attributes" + strconv.Itoa(int(time.Now().Unix())) + sub(t, rw, topicName, "ch") + + err = nsq.Ready(1).Write(rw) + assert.Equal(t, err, nil) + + testUrl := fmt.Sprintf("http://127.0.0.1:%d/stats?format=json", httpAddr.Port) + + statsData, err := util.ApiRequest(testUrl) + if err != nil { + t.Fatalf(err.Error()) + } + client := statsData.Get("topics").GetIndex(0).Get("channels").GetIndex(0).Get("clients").GetIndex(0) + assert.Equal(t, client.Get("user_agent").MustString(), userAgent) + assert.Equal(t, client.Get("snappy").MustBool(), true) +} diff --git a/util/lookupd/lookupd.go b/util/lookupd/lookupd.go index 535490b6b..ee95ba690 100644 --- a/util/lookupd/lookupd.go +++ b/util/lookupd/lookupd.go @@ -411,8 +411,9 @@ func GetNSQDStats(nsqdHTTPAddrs []string, selectedTopic string) ([]*TopicStats, connectedDuration := time.Now().Sub(connected).Seconds() clientInfo := &ClientInfo{ - HostAddress: addr, - ClientVersion: client.Get("version").MustString(), + HostAddress: addr, + ClientVersion: client.Get("version").MustString(), + ClientUserAgent: client.Get("user_agent").MustString(), ClientIdentifier: fmt.Sprintf("%s:%s", client.Get("name").MustString(), strings.Split(client.Get("remote_address").MustString(), ":")[1]), ConnectedDuration: time.Duration(int64(connectedDuration)) * time.Second, // truncate to second diff --git a/util/lookupd/statsinfo.go b/util/lookupd/statsinfo.go index 5f93b281c..124f2d97c 100644 --- a/util/lookupd/statsinfo.go +++ b/util/lookupd/statsinfo.go @@ -155,6 +155,7 @@ func (c *ChannelStats) Host() string { type ClientInfo struct { HostAddress string ClientVersion string + ClientUserAgent string ClientIdentifier string ConnectedDuration time.Duration InFlightCount int