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

Monitor command #1033

Merged
merged 1 commit into from May 28, 2019
Merged

Monitor command #1033

merged 1 commit into from May 28, 2019

Conversation

RoeyPrat
Copy link

This is a rebase of #194 by @dougk7.
He implemented the MONITOR command in a similar way to PubSub, using a generator to get the messages.
I also fixed some PEP8 and formatting issues and added the command to README.
This issue was also mentioned in #805.
Thanks!

@itamarhaber itamarhaber added this to Needs review in v3 Nov 2, 2018
v3 automation moved this from Needs review to Reviewer approved Nov 5, 2018
Copy link
Member

@itamarhaber itamarhaber left a comment

Choose a reason for hiding this comment

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

LGTM. @RoeyPrat question: does it make sense to have a close()/exit thing here to release the connection?

Copy link
Contributor

@andymccurdy andymccurdy left a comment

Choose a reason for hiding this comment

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

  • There's no connection cleanup or flow control here. Presumably someone isn't going to monitor all commands forever. There should be some way for a user to say "OK, I'm done monitoring." Such a method would likely call self.connection.disconnect and return self.connection to the connection_pool. Other parts of the library have a reset() method that does this.

  • Is there a benefit in separating the Monitor object init from sending the MONITOR command? What's the reasoning that I have to start this by calling client.monitor().monitor() rather than just client.monitor()?

  • This might be a great use case for a context manager. The connection can be cleaned up automatically in __exit__. Maybe something like this:

with client.monitor() as m:
    for command in m.listen():
        ...
        if <some behavior indicates that we want to stop>:
            m.stop()  # maybe this trips a flag that controls the while loop in listen()
  • The unit tests need to be written in py.test format like the rest of the test suite. Your tests weren't run and I suspect that if they did, they'd fail, because...

  • In parse_response() you're comparing a potential bytes response to a string. That will fail in Python 3 unless decode_responses=True. Instead, use the bool_ok response handler (you can invoke it directly) to get a boolean.

  • Also in parse_response() you're splitting a potential bytes string (the command) with a string (' '). That will fail in Python 3 unless decode_responses=True. We should split the command on ' ' or b(' ') depending on the value of decode_responses.

  • Should we also parse out the client section ([0 127.0.0.1:47832]) of the response into a separate field in the dict returned to the user?

  • It might also be nice to have another field in the response called command_name. This would always be a native string that's the command name that was executed, e.g., 'ZADD' or 'CONFIG GET'. Note that this could be two words. If we did this, it shouldn't remove the command name from the command field. I don't see this as critical and am happy to merge without this. But it could be quite nice for a user.

v3 automation moved this from Reviewer approved to Needs review Nov 6, 2018
@RoeyPrat
Copy link
Author

RoeyPrat commented Nov 7, 2018

@andymccurdy I changed monitor to use context manager. also, the message is parsed.

redis/client.py Outdated

def __enter__(self):
self.connection.send_command('MONITOR')
assert self.next_command()
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest reading the response off the connection and checking it with bool_ok here rather than at the top of next_command.

If the response is not OK, I think we should raise a RedisError (or a subclass) rather than an AssertionError.

Copy link
Author

Choose a reason for hiding this comment

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

ok. I'm setting error to RedisError('MONITOR failed: %s' % response)

# This part with the OK is only needed when we connect initially.
if bool_ok(response):
return True
command_time, command_data = response.split(' ', 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to fail on Python3 with decode_responses=False. I'm looking at another solution for this now...

Copy link
Author

Choose a reason for hiding this comment

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

for now, I'm setting "If decode_responses=False, return monitor output without parsing".

@RoeyPrat
Copy link
Author

I still need to create tests for this feature...

@andymccurdy
Copy link
Contributor

@RoeyPrat I took a look at this a bit yesterday and found some issues.

  1. When inspecting the response, it looks like the command key is set to just the first word of the command name. For instance, when another client calls GET foo, the command key within the monitor dict is simply ('GET',). command should either contain the full command, including all the args, or we should have a separate field called command_name that is just GET. And whatever we choose, this should just be a string, not a tuple.

  2. As mentioned in a comment above, some command names are two words. The current regex only parses the first word and leaves the rest behind.

  3. I think the db field should be an int rather than a string. We represent it as an int everywhere else in the client.

  4. We're not taking any steps to verify that the response we're getting back from next_command corresponds to the sample command being issued in the test (in your test case, PING). In fact, the response from next_command() in your test actually describes a SELECT command which the connection is running automatically just after the socket is connected. If the test database is shared with other users or processes, we could actually see activity from those as well.

  5. I discovered the above issue while trying to write a test that shows how the current implementation fails with certain encoding issues. I'm trying to get around the test limitations now and will update this when I do.

@andymccurdy
Copy link
Contributor

@RoeyPrat I took your PR and made some changes attempting to fix the problems I outlined above. The branch is here: https://github.com/andymccurdy/redis-py/tree/monitor

I think everything works as expected with this. Can you review and let me know what you think? Thanks!

@RoeyPrat
Copy link
Author

@andymccurdy - looks great! I see no problem in your branch.

@andymccurdy andymccurdy merged commit 9aba44f into redis:master May 28, 2019
v3 automation moved this from Needs review to Done May 28, 2019
@andymccurdy
Copy link
Contributor

Merged. Thanks @RoeyPrat

@RoeyPrat RoeyPrat deleted the roey-monitor branch June 24, 2019 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
v3
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

4 participants