Skip to content

KAFKA-19011: Improve EndToEndLatency Tool with argument parser and message key/header support#20301

Merged
chia7712 merged 12 commits into
apache:trunkfrom
Rancho-7:KAFKA-19011
Sep 3, 2025
Merged

KAFKA-19011: Improve EndToEndLatency Tool with argument parser and message key/header support#20301
chia7712 merged 12 commits into
apache:trunkfrom
Rancho-7:KAFKA-19011

Conversation

@Rancho-7

@Rancho-7 Rancho-7 commented Aug 4, 2025

Copy link
Copy Markdown
Collaborator

jira: https://issues.apache.org/jira/browse/KAFKA-19011 kip:
https://cwiki.apache.org/confluence/display/KAFKA/KIP-1172%3A+Improve+EndToEndLatency+tool

This PR improves the usability and maintainability of the
kafka-e2e-latency.sh tool:

  • Replaces fixed-index argument parsing with a proper argument parser
    (joptsimple)
  • Adds support for configuring:
    • -record-key-size: size of the message key
    • -num-headers: number of headers per message
    • -record-header-key-size: size of each header key
    • -record-header-size: size of each header value
  • Renames existing arguments to align with Kafka CLI conventions:
    • broker_list → bootstrap-server
    • num_messages → num-records
    • message_size_bytes → record-size
    • properties_file → command-config

Reviewers: Jhen-Yung Hsu jhenyunghsu@gmail.com, Ken Huang
s7133700@gmail.com, Chia-Ping Tsai chia7712@gmail.com

@github-actions github-actions Bot added triage PRs from the community tools labels Aug 4, 2025
@Rancho-7

Rancho-7 commented Aug 4, 2025

Copy link
Copy Markdown
Collaborator Author

The updated usage of the tool with argument parser:

(base) lansg:bin/ (KAFKA-19011✗) $ ./kafka-e2e-latency.sh  --bootstrap-server localhost:9092 --topic test-topic --num-records 1000 --producer-acks 1 --record-size 512 --record-key-size 64 --num-headers 5 --record-header-key-size 64 --record-header-size 128
0       26.187541
Avg latency: 1.2853 ms
Percentiles: 50th = 1, 99th = 3, 99.9th = 26

It's compatible with the legacy version:

(base) lansg:bin/ (KAFKA-19011✗) $ ./kafka-e2e-latency.sh localhost:9092 test-topic 1000 1 512                                                       [21:21:28]
WARNING: Positional argument usage is deprecated and will be removed in Apache Kafka 5.0. Please use named arguments instead: --bootstrap-server, --topic, --num-records, --producer-acks, --record-size, --command-config
0       45.907666
Avg latency: 1.5202 ms
Percentiles: 50th = 1, 99th = 6, 99.9th = 88

@Yunyung Yunyung left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the PR. Partial review, some comments left. Also, could you add the KIP to the title or description?

Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated
Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated
Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated
Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated

// validate 'producer-acks'
String acksValue = options.valueOf(acksOpt);
if (!List.of("1", "all").contains(acksValue)) {

@Yunyung Yunyung Aug 4, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What if -1?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The legacy version only had these two options, so I keep it the same. Since -1 has the same effect as all, I think one is enough?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to keep this behavior

}

// Visible for testing
static String[] convertLegacyArgsIfNeeded(String[] args) throws Exception {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we add Deprecated tag?

@Rancho-7 Rancho-7 Aug 5, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for pointing that out! Added it.

@github-actions github-actions Bot removed the triage PRs from the community label Aug 5, 2025
@chia7712

chia7712 commented Aug 7, 2025

Copy link
Copy Markdown
Member

@Rancho-7 could you please run e2e on you local?

@Rancho-7

Copy link
Copy Markdown
Collaborator Author

@chia7712 I ran the tests with:

TC_PATHS="tests/kafkatest/sanity_checks/test_performance_services.py tests/kafkatest/benchmarks/core/benchmark_test.py" bash tests/docker/run_tests.sh

e2e result:

kafkatest@fair-sun-wilts-fin-03:/opt/kafka/kafka/results/latest$ cat report.txt
====================================================================================================
SESSION REPORT (ALL TESTS)
ducktape version: 0.12.0
session_id:       2025-08-12--002
run time:         73 minutes 38.234 seconds
tests run:        78
passed:           78
flaky:            0
failed:           0
ignored:          0
====================================================================================================

@m1a2st m1a2st left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @Rancho-7 for this patch, a little comments

Comment thread tools/src/test/java/org/apache/kafka/tools/EndToEndLatencyTest.java Outdated
Comment thread tools/src/test/java/org/apache/kafka/tools/EndToEndLatencyTest.java

@chia7712 chia7712 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Rancho-7 thanks for this patch.


// validate 'producer-acks'
String acksValue = options.valueOf(acksOpt);
if (!List.of("1", "all").contains(acksValue)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to keep this behavior

// optional
Optional<String> propertiesFile = opts.options.has(opts.commandConfigOpt) ?
Optional.of(opts.options.valueOf(opts.commandConfigOpt)) : Optional.empty();
int recordKeySize = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not add a default value to recordKeyOpt instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

You're right, using a default value would be much cleaner. Updated it.

Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated
Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated

if (sentRecordKey != null) {
if (record.key() == null) {
throw new RuntimeException("Expected message key but received null");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we commit the offsets too?


for (int i = 0; i < numHeaders; i++) {
String headerKey = new String(randomBytesOfLen(random, keySize), StandardCharsets.UTF_8);
byte[] headerValue = randomBytesOfLen(random, valueSize);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps we should treat -1 as null for header value, since null is legal.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point. Done.

return args;
}

boolean hasNamedArgs = Arrays.stream(args).anyMatch(arg -> arg.startsWith("--"));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

- is legal character in topic names, so it could cause an issue if users create a topic like topic--aaa. Perhaps we should check the required ops instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Absolutely right! This would be a much safer approach. Thanks for catching this!

@chia7712 chia7712 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Rancho-7 thanks for this patch!

newArgs.add(legacyArgs[4]);

// properties_file -> --command-config
if (legacyArgs.length == 6 && !legacyArgs[5].trim().isEmpty()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Excuse me, why to add !legacyArgs[5].trim().isEmpty() condition?

@Rancho-7 Rancho-7 Aug 25, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed it, thanks!

.withOptionalArg()
.describedAs("bytes")
.ofType(Integer.class)
.defaultsTo(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not discussed in the KIP. By default, the number of records is one, so should we create a non-empty header by default?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, that does feel a bit odd. Since the header is optional, maybe we should set the default num-headers to 0, and leave record-header-key-size and record-header-size empty?

.withOptionalArg()
.describedAs("count")
.ofType(Integer.class)
.defaultsTo(1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you please alos add those default values to the KIP?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The KIP mentions that the default value for num-headers is set to 1, but it doesn't specify the default value for the other arguments. I will add that information to the KIP. Thanks!

for (int i = 0; i < numHeaders; i++) {
String headerKey = new String(randomBytesOfLen(random, keySize), StandardCharsets.UTF_8);
byte[] headerValue = valueSize == -1 ? null : randomBytesOfLen(random, valueSize);
headers.add(new RecordHeader(headerKey, headerValue));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

RecordHeader is internal class. Perhaps we could add a record class instead

List<Header> headers = new ArrayList<>();

for (int i = 0; i < numHeaders; i++) {
String headerKey = new String(randomBytesOfLen(random, keySize), StandardCharsets.UTF_8);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have you considered directly generating a random string?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not yet. The randomBytesOfLen function was used in the earlier version, and I found it could be reused, so I kept it.

@chia7712 chia7712 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Rancho-7 thanks for the updates!

throw new IllegalArgumentException("Latency testing requires synchronous acknowledgement. Please use 1 or all");
}
// optional
Optional<String> propertiesFile = opts.options.has(opts.commandConfigOpt) ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional<String> propertiesFile = Optional.ofNullable(opts.options.valueOf(opts.commandConfigOpt));

if (options.has(recordHeaderKeySizeOpt) && options.valueOf(recordHeaderKeySizeOpt) < 0) {
CommandLineUtils.printUsageAndExit(parser, "Value for --record-header-key-size must be a non-negative integer.");
}
if (options.has(recordHeaderValueSizeOpt) && options.valueOf(recordHeaderValueSizeOpt) < -1) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is options.has(recordHeaderValueSizeOpt) necessary since recordHeaderValueSizeOpt has default value?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for catching this! I have removed all these unnecessary checks.

Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated
Comment thread tools/src/main/java/org/apache/kafka/tools/EndToEndLatency.java Outdated
@chia7712
chia7712 merged commit ef10a52 into apache:trunk Sep 3, 2025
24 checks passed
eduwercamacaro pushed a commit to littlehorse-enterprises/kafka that referenced this pull request Nov 12, 2025
…sage key/header support (apache#20301)

jira: https://issues.apache.org/jira/browse/KAFKA-19011  kip:

https://cwiki.apache.org/confluence/display/KAFKA/KIP-1172%3A+Improve+EndToEndLatency+tool

This PR improves the usability and maintainability of the
`kafka-e2e-latency.sh` tool:

- Replaces fixed-index argument parsing with a proper argument parser
(joptsimple)
- Adds support for configuring:
    - -record-key-size: size of the message key
    - -num-headers: number of headers per message
    - -record-header-key-size: size of each header key
    - -record-header-size: size of each header value
- Renames existing arguments to align with Kafka CLI conventions:
    - broker_list → bootstrap-server
    - num_messages → num-records
    - message_size_bytes → record-size
    - properties_file → command-config
    -

Reviewers: Jhen-Yung Hsu <jhenyunghsu@gmail.com>, Ken Huang
 <s7133700@gmail.com>, Chia-Ping Tsai <chia7712@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants