Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nsqadmin/nsqd: optimize /stats #925

Closed
wants to merge 5 commits into from

Conversation

jinhailang
Copy link

@jinhailang jinhailang commented Aug 8, 2017

  • optimize: If "topic" was specified trims to only that topic, if "channel" was specified trims to only that channel.
  • optimize: For the Topic page we just need the count of clients, we don't need all their info. For the Channel page, we do need all the client info, but not for all Channels, just for the one.

Nsqd and nsqadmin are compatible with older versions.

@mreiferson mreiferson changed the title optimize: gets stats nsqadmin/nsqd: optimize /stats Aug 8, 2017
@mreiferson mreiferson added the perf label Aug 8, 2017
@@ -7,6 +7,6 @@ $.ajaxPrefilter(function(options) {
'X-UserAgent': USER_AGENT,
'Accept': 'application/vnd.nsq; version=1.0'
});
options['timeout'] = 20 * 1000;
options['timeout'] = 120 * 1000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to change this timeout. We never want to wait this long for something to load.

(I understand you need to increase it in order for it to work for your setup. I guess we'd hope to make the stats requests just not take so long somehow.)

nsqd/stats.go Outdated
}
}
n.RUnlock()
realTopic.RLock()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have to handle the possibility that the topic does not exist.

nsqd/stats.go Outdated
for _, c := range realChannels {
c.RLock()
clients := make([]ClientStats, 0, len(c.clients))
for _, client := range c.clients {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another optimization that is possible here: For the Topic page we just need the count of clients, we don't need all their info. For the Channel page, we do need all the client info, but not for all Channels, just for the one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm ... we'd need to add a new param, to omit the client connection details from the stats response, to keep compatibility with anything out there that depends on the full stats responses. Luckily, the /stats endpoint will ignore params it is not expecting, so new nsqadmin will still work with old nsqd, it'll just get more info than it needs.

(The topic and channel params to /stats have existed for quite a while, since nsqd 0.3.6)

@ploxiln
Copy link
Member

ploxiln commented Aug 9, 2017

There's a lot of duplication between GetStats() and GetTopicStats() - it would be nice if most of the implementation were shared, e.g. GetStats() could just call the new function with a blank topic to mean "all topics".

@@ -387,7 +387,7 @@ func TestReconfigure(t *testing.T) {
nsqd.triggerOptsNotification()
test.Equal(t, 2, len(nsqd.getOpts().NSQLookupdTCPAddresses))

time.Sleep(200 * time.Millisecond)
time.Sleep(300 * time.Millisecond)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here may be error, I guess sleep time is too short.

2017-08-10 10 55 40

2017-08-10 10 56 42

@jinhailang
Copy link
Author

I've completed. If there is time, then again to help me review the code, thank you!
@ploxiln @mreiferson

Copy link
Member

@mreiferson mreiferson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking better!

totalClients += int64(len(cs.Clients))
} else {
totalClients += int64(cs.ClientCount)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than baking this logic into the client each time, we should just always return client_count as an int, separately from clients (the array).

@@ -530,7 +530,7 @@ func (c *ClusterInfo) GetNSQDTopicProducers(topic string, nsqdHTTPAddrs []string
//
// if selectedTopic is empty, this will return stats for *all* topic/channels
// and the ChannelStats dict will be keyed by topic + ':' + channel
func (c *ClusterInfo) GetNSQDStats(producers Producers, selectedTopic string) ([]*TopicStats, map[string]*ChannelStats, error) {
func (c *ClusterInfo) GetNSQDStats(producers Producers, selectedTopic, selectChannel string) ([]*TopicStats, map[string]*ChannelStats, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/selectChannel/selectedChannel/

var (
state TopicStats
stats []TopicStats
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: we don't typically declare variables like this, we instead prefer var for each one.

stats := s.ctx.nsqd.GetStats()
var (
state TopicStats
stats []TopicStats
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the state variable is awkwardly named, how about we call these stats TopicStats and allStats []TopicStats?

} else {
state, err = s.ctx.nsqd.GetChannelStats(topicName, channelName)
}
if nil != err {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: we typically prefer the variable in question to be on the left side of the comparison, e.g. if err != nil {

if nil != err {
s.ctx.nsqd.logf(LOG_ERROR, "failed to get stats - %s", err)
return nil, http_api.Err{404, "topic/channel is not found"}
} else if nil == stats {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be an else if, because the block above returns. Also, same minor note applies here, please move the variable to the left side of the comparison.

} else if nil == stats {
stats = []TopicStats{state}
}

health := s.ctx.nsqd.GetHealth()
startTime := s.ctx.nsqd.GetStartTime()
uptime := time.Since(startTime)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After these changes above, Is any of the topic/channel looping code necessary below this line?

n.RLock()
var realTopic *Topic
for _, t := range n.topicMap {
if topic == t.name {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if t.name == topic {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same applies to function below

}
n.RUnlock()

if nil == realTopic {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if realTopic == nil {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same applies to function below


if nil == realTopic {
return TopicStats{}, errors.New("the topic is not found.")
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the above block returns, we can drop the else and un-indent this block

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same applies to function below

@andyxning
Copy link
Member

@mreiferson @jinhailang @ploxiln I have not found this PR ahead. But, i also proposed a PR #929 to make stats query more efficient and some of the thoughts are the same with @jinhailang

@mreiferson
Copy link
Member

@jinhailang thanks for this, but going to close it in favor of #929!

@mreiferson mreiferson closed this Aug 26, 2017
@jinhailang
Copy link
Author

I'm really sorry that I plan to revise this weekend. I have modified it, but can not commit it, if necessary, it may be available as an alternative.
I have been on the production environment to run for some time, the effect is very good, thank you for your help! @ploxiln @mreiferson

@ploxiln
Copy link
Member

ploxiln commented Aug 29, 2017

Just by the way, #929 does not include the option to exclude all individual client stats from topic stats, which I think is what gave the big performance gain for you. Someone could still possibly do that, separately after #929 is merged.

EDIT: missed the word not (it was sort of important)

@andyxning
Copy link
Member

andyxning commented Aug 29, 2017

Someone could still possibly do that, separately after #929 is merged.

Agreed with @ploxiln .

@jinhailang The object is to make stats api for efficient. :)

@mreiferson
Copy link
Member

Ahh, I missed that, good catch. Yes, we should do that too but let's do it as a follow up at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants