-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-2527; System Test for Quotas in Ducktape #275
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
Changes from 2 commits
cf1a116
345b997
28bd200
0d07fc7
c0fd768
f7cfad5
35f3dd8
42e5804
3b68a11
ceb4534
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # Licensed to the Apache Software Foundation (ASF) under one or more | ||
| # contributor license agreements. See the NOTICE file distributed with | ||
| # this work for additional information regarding copyright ownership. | ||
| # The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| # (the "License"); you may not use this file except in compliance with | ||
| # the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from kafkatest.services.performance import PerformanceService | ||
|
|
||
|
|
||
| class JmxMixin(PerformanceService): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think
In other words, this class should subclass
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. Will fix it. |
||
|
|
||
| def __init__(self, context, num_nodes, jmx_object_name=None, jmx_attributes=None): | ||
| super(JmxMixin, self).__init__(context, num_nodes) | ||
| self.jmx_object_name = jmx_object_name | ||
| self.jmx_attributes = jmx_attributes | ||
| self.jmx_port = 9192 | ||
|
|
||
| self.started = [False] * self.num_nodes | ||
| self.jmx_stats = [{} for x in range(self.num_nodes)] | ||
| self.maximum_jmx_value = None | ||
| self.average_jmx_value = None | ||
|
|
||
| def clean_node(self, node): | ||
| super(JmxMixin, self).clean_node(node) | ||
| node.account.kill_process("jmx", clean_shutdown=False, allow_fail=True) | ||
| node.account.ssh("rm -rf /mnt/jmx_tool.log", allow_fail=False) | ||
|
|
||
| def maybe_start_jmx_tool(self, idx, node): | ||
| if self.started[idx-1] == True or self.jmx_object_name == None: | ||
| return | ||
| self.started[idx-1] = True | ||
|
|
||
| cmd = "/opt/kafka/bin/kafka-run-class.sh kafka.tools.JmxTool " \ | ||
| "--reporting-interval 1000 --jmx-url service:jmx:rmi:///jndi/rmi://127.0.0.1:%d/jmxrmi " \ | ||
| "--object-name %s " % (self.jmx_port, self.jmx_object_name) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JmxTool allows multiple jmx object names - should this do the same? Also, we may want to support running the tool with no object-name specified (so that it queries all objects)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, JmxTool allows multiple object names and no object name as argument. But should we make it full-fledged from the beginning? I currently implemented in such a way to only satisfy the use case where we query exactly one attribute. Can we expand it later when we have use case for multiple object names or attributes?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I realized there is a better interface for JmxMixin to provide min, avg, and max value for every queried object-attribute. I will update the patch to support this. Thanks for the suggestion.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @granders JmxMixin has been updated to support query with arbitrary object name and attributes. Thanks for the suggestion. |
||
| if self.jmx_attributes != None: | ||
| cmd += "--attributes %s " % self.jmx_attributes | ||
| cmd += "> /mnt/jmx_tool.log &" | ||
|
|
||
| self.logger.debug("Start JmxTool %d command: %s", idx, cmd) | ||
| for l in node.account.ssh_capture(cmd, allow_fail=False): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since you're not capturing output, use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even though I don't need output, if I don't wait for output, then the cmd will not be executed by ssh_capture. Does it make sense? I will test it again and get back to you.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just double checked it by running the test case -- simply do Can we fix it in Ducktape side so that
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I'm misunderstanding - did you try The first simply runs the command; the second tries to capture output.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah.. my bad. Problem fixed after using |
||
| break | ||
|
|
||
| def read_jmx_output(self, idx, node): | ||
| if self.started[idx-1] == False: | ||
| return | ||
|
|
||
| cmd = "grep -v time /mnt/jmx_tool.log" | ||
| self.logger.debug("Read jmx output %d command: %s", idx, cmd) | ||
|
|
||
| for line in node.account.ssh_capture(cmd, allow_fail=False): | ||
| time_sec = int(line.split(',')[0])/1000 | ||
| value = float(line.split(',')[1]) | ||
| self.jmx_stats[idx-1][time_sec] = value | ||
|
|
||
| if any(len(dict)==0 for dict in self.jmx_stats): | ||
| return | ||
|
|
||
| # since we have read jmx stats from all nodes, calculate average and maximum of jmx stats | ||
| start_time_sec = min([min(dict.keys()) for dict in self.jmx_stats]) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename "dict" here since it is a reserved keyword
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for noting this. Will fix it. |
||
| end_time_sec = max([max(dict.keys()) for dict in self.jmx_stats]) | ||
| values = [] | ||
|
|
||
| for time_sec in xrange(start_time_sec, end_time_sec+1): | ||
| collection = [dict.get(time_sec, 0) for dict in self.jmx_stats] | ||
| values.append(sum(collection)) | ||
|
|
||
| self.average_jmx_value = sum(values)/len(values) | ||
| self.maximum_jmx_value = max(values) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,19 +13,19 @@ | |
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from kafkatest.services.performance import PerformanceService | ||
| from kafkatest.services.performance.jmx_mixin import JmxMixin | ||
|
|
||
|
|
||
| class ProducerPerformanceService(PerformanceService): | ||
| class ProducerPerformanceService(JmxMixin): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inherit from both
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will fix it. |
||
|
|
||
| logs = { | ||
| "producer_performance_log": { | ||
| "path": "/mnt/producer-performance.log", | ||
| "collect_default": True}, | ||
| } | ||
|
|
||
| def __init__(self, context, num_nodes, kafka, topic, num_records, record_size, throughput, settings={}, intermediate_stats=False): | ||
| super(ProducerPerformanceService, self).__init__(context, num_nodes) | ||
| def __init__(self, context, num_nodes, kafka, topic, num_records, record_size, throughput, settings={}, | ||
| intermediate_stats=False, client_id="producer-performance", jmx_object_name=None, jmx_attributes=None): | ||
| super(ProducerPerformanceService, self).__init__(context, num_nodes, jmx_object_name, jmx_attributes) | ||
| self.kafka = kafka | ||
| self.args = { | ||
| 'topic': topic, | ||
|
|
@@ -35,12 +35,13 @@ def __init__(self, context, num_nodes, kafka, topic, num_records, record_size, t | |
| } | ||
| self.settings = settings | ||
| self.intermediate_stats = intermediate_stats | ||
| self.client_id = client_id | ||
|
|
||
| def _worker(self, idx, node): | ||
| args = self.args.copy() | ||
| args.update({'bootstrap_servers': self.kafka.bootstrap_servers()}) | ||
| cmd = "/opt/kafka/bin/kafka-run-class.sh org.apache.kafka.clients.tools.ProducerPerformance "\ | ||
| "%(topic)s %(num_records)d %(record_size)d %(throughput)d bootstrap.servers=%(bootstrap_servers)s"\ | ||
| args.update({'bootstrap_servers': self.kafka.bootstrap_servers(), 'jmx_port': self.jmx_port, 'client_id': self.client_id}) | ||
| cmd = "JMX_PORT=%(jmx_port)d /opt/kafka/bin/kafka-run-class.sh org.apache.kafka.clients.tools.ProducerPerformance "\ | ||
| "%(topic)s %(num_records)d %(record_size)d %(throughput)d bootstrap.servers=%(bootstrap_servers)s client.id=%(client_id)s"\ | ||
| " | tee /mnt/producer-performance.log" % args | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lindong28 When I drop the pipe to tee here on line 51 and keep the one below, the producer runs as expected. |
||
|
|
||
| for key, value in self.settings.items(): | ||
|
|
@@ -62,6 +63,7 @@ def parse_stats(line): | |
| } | ||
| last = None | ||
| for line in node.account.ssh_capture(cmd): | ||
| self.maybe_start_jmx_tool(idx, node) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling I think we want the behavior to be:
I think it would look something like:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I have thought of the approach as you described -- I didn't implement it because it required the test case developer to specifically redirect output to a file and read from that file, which should not be required for developer to do if we can the right support from Ducktape. This is the reason I asked for asynchronous ssh in Ducktape during our discussion in the email. I am thinking of something like this: Does it make sense?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lindong28 Now it's a lot clearer what you meant in your original email. You're basically saying that since I think it makes a lot of sense to provide support for that. More generally, I think the current set of @granders, a patch just to add Ducktape support for the approach @lindong28 is looking for should be quick and easy. I think it can look a lot like I think there are cleaner ways to expose this, but given how many
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ewencp Thanks for your comment. As you mentioned in the ticket, I can file a follow up patch after Ducktape is updated.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ewencp In response to your question, yeah that is what I have in mind when I was thinking about the test, and is the reason I wanted to have support for "asynchronous" ssh command from Ducktape. But I didn't stated it very clearly in the email, partly because something become clearer only after I started implementation -- for example, I once thought that adding "&" would solve problem cleanly but it didn't.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lindong28 Thanks for this observation. I added a fix in confluentinc/ducktape#107 The fix is now available from pypi as version 0.3.5 - you'll want to update the ducktape dependency in tests/setup.py Note this is actually asynchronous even without running command as a background process with "&", so you should be able to run:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @granders for the patch. Will use the new API. |
||
| if self.intermediate_stats: | ||
| try: | ||
| self.stats[idx-1].append(parse_stats(line)) | ||
|
|
@@ -74,3 +76,4 @@ def parse_stats(line): | |
| self.results[idx-1] = parse_stats(last) | ||
| except: | ||
| raise Exception("Unable to parse aggregate performance statistics on node %d: %s" % (idx, last)) | ||
| self.read_jmx_output(idx, node) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is now one sanity check which fails both remotely and on my mac:
http://jenkins.confluent.io/job/kafka_system_tests_branch_builder/94/console
This is fixed by changing
console-consumertoconsole_consumer(dash to underscore)