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

Scheduled post #848

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open

Scheduled post #848

wants to merge 21 commits into from

Conversation

harshilsharma63
Copy link
Member

Summary

Adding scheduled posts support to load test tool.

Ticket Link

Fixes https://mattermost.atlassian.net/browse/MM-61486

@harshilsharma63 harshilsharma63 marked this pull request as ready for review November 14, 2024 07:01
Copy link
Member

@agnivade agnivade left a comment

Choose a reason for hiding this comment

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

It looks like some debug fmt.Println statements are still left over from debugging. Let's clean them up.

Copy link
Member

@agarciamontoro agarciamontoro left a comment

Choose a reason for hiding this comment

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

Thank you so much for all your work on this, great job! I left some comments below, and I also agree with Agniva: we should remove all the fmt.Println lines.

loadtest/control/simulcontroller/controller.go Outdated Show resolved Hide resolved
Comment on lines 61 to 66
if rand.Float64() < 0.02 {
if err := control.AttachFilesToDraft(u, &scheduledPost.Draft); err != nil {
fmt.Println("createScheduledPost: AttachFilesToDraft error", err)
return control.UserActionResponse{Err: control.NewUserError(err)}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

We should probably move this 0.02 ratio to a global constant, we're using it in many places now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Only in 5 places all over excluding tests. I don't think a constant for 2% makes sense. Its doesn't have any special meaning.

Copy link
Member

Choose a reason for hiding this comment

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

Well, I didn't mean the number 0.02 specifically, but a constant for when to attach files to a post, for which we use 2%. We use that exact meaning elsewhere (probably less than 5 places, but more than 1, I'd say)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

loadtest/control/simulcontroller/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/control/simulcontroller/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/control/simulcontroller/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/store/memstore/random.go Outdated Show resolved Hide resolved
loadtest/user/userentity/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/user/userentity/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/control/simulcontroller/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/utils.go Outdated Show resolved Hide resolved
@@ -52,6 +52,7 @@ type MemStore struct {
featureFlags map[string]bool
report *model.PerformanceReport
channelBookmarks map[string]*model.ChannelBookmarkWithFileInfo
scheduledPosts map[string]map[string]*model.ScheduledPost
Copy link
Member

Choose a reason for hiding this comment

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

Also, please update the (s *MemStore) Clear() method to clear this field as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

return selectedPost, nil
}

func (s *MemStore) DeleteScheduledPost(scheduledPostID string) {
Copy link
Member

Choose a reason for hiding this comment

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

The delete and update methods should not be in random.go. Let's move this to memstore/store.go.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@harshilsharma63
Copy link
Member Author

@agnivade @agarciamontoro can you please re-review this? I've made the fixes.

Copy link
Member

@agnivade agnivade left a comment

Choose a reason for hiding this comment

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

I will leave the store logic to Alejandro.

@@ -200,6 +204,10 @@ type MutableUserStore interface {
// SetDrafts stores the given drafts.
SetDrafts(teamId string, drafts []*model.Draft) error

// scheduled posts
SetScheduledPost(teamId string, scheduledPost *model.ScheduledPost) error
GetRandomScheduledPost() (*model.ScheduledPost, error)
Copy link
Member

Choose a reason for hiding this comment

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

MutableUserStore embeds UserStore. So there is no need to repeat GetRandomScheduledPost here. UserStore should have GetRandomScheduledPost. And MutableUserStore should have the Set/Update/Delete methods.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 66 to 67
for teamIdInResponse := range scheduledPostsByTeam {
for _, scheduledPost := range scheduledPostsByTeam[teamIdInResponse] {
Copy link
Member

Choose a reason for hiding this comment

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

You can just do for _, v := range scheduledPostsByTeam { instead of 2 loops.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd still need two loops. scheduledPostsByTeam is a map[string][]*model.ScheduledPosts, and I need to iterate the individual scheduled posts from that array. I could use for _, v := range scheduledPostsByTeam but then I'd need another loop to iterate items from v.

Copy link
Member

Choose a reason for hiding this comment

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

Ah right. Indeed.

Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer using the value from range as well, that way you don't need to access it later. Quite a nit, but I had to stop and try to understand why we weren't doing that, so I guess it's better to just stick to the convention.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

loadtest/utils.go Outdated Show resolved Hide resolved
Copy link
Member

@agnivade agnivade left a comment

Choose a reason for hiding this comment

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

Looking good!

Copy link
Member

@agarciamontoro agarciamontoro left a comment

Choose a reason for hiding this comment

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

Thanks, @harshilsharma63, and sorry for the delay in the review! Some more comments below.

loadtest/control/simulcontroller/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/control/simulcontroller/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/control/simulcontroller/scheduled_posts.go Outdated Show resolved Hide resolved
loadtest/store/memstore/store.go Outdated Show resolved Hide resolved
loadtest/store/memstore/store.go Outdated Show resolved Hide resolved
Comment on lines 40 to 45
err = ue.store.SetScheduledPost(teamId, updatedScheduledPost)
if err != nil {
return err
}

ue.Store().UpdateScheduledPost(teamId, updatedScheduledPost)
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to set it and then update it? Also, we should check the error from UpdateScheduledPost

Copy link
Member Author

Choose a reason for hiding this comment

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

Just update should be fine, fixed this.

Comment on lines 1390 to 1401
func (s *MemStore) DeleteScheduledPost(scheduledPostID string) {
s.lock.Lock()
defer s.lock.Unlock()

for teamId := range s.scheduledPosts {
if _, ok := s.scheduledPosts[teamId][scheduledPostID]; ok {
delete(s.scheduledPosts[teamId], scheduledPostID)
break
}
}
}

Copy link
Member

Choose a reason for hiding this comment

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

Should we error if the post doesn't exist?

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't really matter.

Comment on lines 56 to 57
ue.Store().DeleteScheduledPost(scheduledPostId)
return nil
Copy link
Member

Choose a reason for hiding this comment

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

Right now DeleteScheduledPost doesn't return an error, but maybe it should (I left a comment for that). If we decide that's a good decision, then we need to return its error.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think its of any use returning error from delete method, it doesn't matter

Comment on lines 66 to 67
for teamIdInResponse := range scheduledPostsByTeam {
for _, scheduledPost := range scheduledPostsByTeam[teamIdInResponse] {
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer using the value from range as well, that way you don't need to access it later. Quite a nit, but I had to stop and try to understand why we weren't doing that, so I guess it's better to just stick to the convention.

loadtest/utils.go Outdated Show resolved Hide resolved
Copy link
Member

@agarciamontoro agarciamontoro left a comment

Choose a reason for hiding this comment

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

Thanks, @harshilsharma63! I've left a comment in the RandomFutureTime function, and I have an additional ask: I do think we should error out in MemStore's DeleteScheduledPost and UpdateScheduledPost if the post is not found, and log an error if that's the case. It shouldn't happen and may point to other problems in the load-test.

Comment on lines 93 to 94
// RandomFutureTime returns a random Unix timestamp, in milliseconds, in the interval
// [now+deltaStart, now+maxUntil]
Copy link
Member

Choose a reason for hiding this comment

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

The comment is actually correct, thanks! But it surfaced a bug in this function: if maxUntil is smaller than deltaStart (let's say, you call RandomFutureTime(10*time.Second, 9*time.Second), then rand.Int63n panics because it gets a negative number). We should fix the function so that the interval is actually [now+deltaStart, now+deltaStart+maxUntil], and add unit testing for this function.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@agnivade
Copy link
Member

Both Alejandro and Claudio are out. I will take another quick look and ask for a stamp approval.

@agnivade
Copy link
Member

I gave it a test run and found some minor issues. We should be good to go after that.

1. Incorrect error/info logging when there are no scheduled posts.

Log line is:

error [2024-12-23 14:51:17.856 +05:30] control/simulcontroller.(*SimulController).sendScheduledPostNow loadtest/control/simulcontroller/scheduled_posts.go:135 no scheduled posts available caller="loadtest/loadte
st.go:60" controller_id=4 user_id=anoxyyicofbozjrig7og95i6py
...
error [2024-12-23 14:51:34.667 +05:30] control/simulcontroller.(*SimulController).deleteScheduledPost loadtest/control/simulcontroller/scheduled_posts.go:113 no scheduled posts available caller="loadtest/loadtes
t.go:60" controller_id=10 user_id=bpjjn1f67jg8xnya68m41ki6dc

Note that the log level is error, but it's just a case of scheduled posts not being available. And we are correctly doing it here:

scheduledPost, err := u.Store().GetRandomScheduledPost()
if err != nil {
return control.UserActionResponse{Err: control.NewUserError(err)}
}
if scheduledPost == nil {
return control.UserActionResponse{Info: "no scheduled posts found"}
}

But the issue is that GetRandomScheduledPost() already returns an error if no scheduled posts are found.

The right way is to return an error variable and check for it. See for example RandomTeam:

	if len(teams) == 0 {
		return model.Team{}, ErrTeamStoreEmpty
	}

and then we check for the error at call site:

team, err := u.Store().RandomTeam(store.SelectMemberOf | store.SelectNotCurrent)
if errors.Is(err, memstore.ErrTeamStoreEmpty) {
return control.UserActionResponse{Info: "no other team to switch to"}
} else if err != nil {
return control.UserActionResponse{Err: control.NewUserError(err)}
}

We should apply the same logic while calling GetRandomScheduledPost as well.

2. Invalid scheduled at time:

error [2024-12-23 14:52:18.420 +05:30] control/simulcontroller.(*SimulController).createScheduledPost loadtest/control/simulcontroller/scheduled_posts.go:63 Invalid scheduled at time., id=m9txr5p37bf63kg4xwq7upjxcy caller="loadtest/loadtest.go:60" controller_id=9 user_id=b1ysdcjc5prodfef1386jdjawy

This is what I see in the server logs:

debug [2024-12-23 15:01:16.543 +05:30] Invalid scheduled at time.                    caller="web/context.go:120" path=/api/v4/posts/schedule request_id=xzx84obdgjgdugma4setb8a1ye ip_addr=127.0.0.1 user_id=eh1k7cuhmtntfx8y9ziihatmkr method=POST err_where=ScheduledPost.IsValid http_code=400 error="ScheduledPost.IsValid: Invalid scheduled at time., id=qoafc3rp3tf5zjsqpcck9ew55w"

If we fix the above 2 issues, we should be good to go.

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

Successfully merging this pull request may close these issues.

3 participants