Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 38 additions & 13 deletions core/src/main/scala/kafka/tools/ConsumerPerformance.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,20 @@ object ConsumerPerformance {
val totalBytesRead = new AtomicLong(0)
val consumerTimeout = new AtomicBoolean(false)
var metrics: mutable.Map[MetricName, _ <: Metric] = null
val joinGroupTimeInMs = new AtomicLong(0)
val fetchTimeInMs = new AtomicLong(0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There doesn't appear to be any reason for fetchTimeInMs to be an AtomicLong. We can use a regular long and just initialize it below.


if (!config.hideHeader) {
printHeader(!config.showDetailedStats)
printHeader(config.showDetailedStats, config.useOldConsumer)
}

var startMs, endMs = 0L
if (!config.useOldConsumer) {
val consumer = new KafkaConsumer[Array[Byte], Array[Byte]](config.props)
consumer.subscribe(Collections.singletonList(config.topic))
startMs = System.currentTimeMillis
consume(consumer, List(config.topic), config.numMessages, 1000, config, totalMessagesRead, totalBytesRead)
consume(consumer, List(config.topic), config.numMessages, 1000,
config, totalMessagesRead, totalBytesRead, joinGroupTimeInMs)
endMs = System.currentTimeMillis

if (config.printMetrics) {
Expand Down Expand Up @@ -95,10 +98,25 @@ object ConsumerPerformance {
consumerConnector.shutdown()
}
val elapsedSecs = (endMs - startMs) / 1000.0

@hachikuji hachikuji Jun 7, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the main point of this patch is to compute the throughput statistics based only on the time spent fetching, so maybe we can move this below and just use fetchTimeInMs.get / 1000.0? We could also report MB.sec as two separate values: total.MB.sec which uses the total time, and fetch.MB.sec which uses only the fetch time. Similarly for nMsg.sec.

fetchTimeInMs.set((endMs - startMs) - joinGroupTimeInMs.get)
if (!config.showDetailedStats) {
val totalMBRead = (totalBytesRead.get * 1.0) / (1024 * 1024)
println("%s, %s, %.4f, %.4f, %d, %.4f".format(config.dateFormat.format(startMs), config.dateFormat.format(endMs),
totalMBRead, totalMBRead / elapsedSecs, totalMessagesRead.get, totalMessagesRead.get / elapsedSecs))
print("%s, %s, %.4f, %.4f, %d, %.4f".format(
config.dateFormat.format(startMs),
config.dateFormat.format(endMs),
totalMBRead,
totalMBRead / elapsedSecs,
totalMessagesRead.get,
totalMessagesRead.get / elapsedSecs
))
if (!config.useOldConsumer) {
print(", %d, %d, %.4f, %.4f".format(
joinGroupTimeInMs.get,
fetchTimeInMs.get,
totalMBRead / (fetchTimeInMs.get / 1000.0),
totalMessagesRead.get / (fetchTimeInMs.get / 1000.0)
))
}
}

if (metrics != null) {
Expand All @@ -107,11 +125,14 @@ object ConsumerPerformance {

}

private[tools] def printHeader(showDetailedStats: Boolean): Unit = {
if (showDetailedStats)
println("start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec")
else
println("time, threadId, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec")
private[tools] def printHeader(showDetailedStats: Boolean, useOldConsumer: Boolean): Unit = {
if (!showDetailedStats) {
println("start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec" +
s"${if (!useOldConsumer) ", rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec" else ""}"
)
} else {
println("time, threadId, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there any reason we don't include the new fields in the "detailed" view?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In the detailed view, can we safely think fetch.time.ms is always equal to elapsedMs in printProgressMessage? If yes, then the new fields fetch.MB.sec and fetch.nMsg.sec will always be equal to the existing ones MB.sec and nMsg.sec respectively, so that's why I did not have those new fields included in the detailed view. Does it make any sense?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure I follow. The fetch time is always equal to the total time minus the join group time. Can we not compute that periodically?

}
}

def consume(consumer: KafkaConsumer[Array[Byte], Array[Byte]],
Expand All @@ -120,25 +141,29 @@ object ConsumerPerformance {
timeout: Long,
config: ConsumerPerfConfig,
totalMessagesRead: AtomicLong,
totalBytesRead: AtomicLong) {
totalBytesRead: AtomicLong,
joinTime: AtomicLong) {
var bytesRead = 0L
var messagesRead = 0L
var lastBytesRead = 0L
var lastMessagesRead = 0L
var joinStart = 0L

// Wait for group join, metadata fetch, etc
val joinTimeout = 10000
val isAssigned = new AtomicBoolean(false)
consumer.subscribe(topics.asJava, new ConsumerRebalanceListener {
def onPartitionsAssigned(partitions: util.Collection[TopicPartition]) {
isAssigned.set(true)
joinTime.addAndGet(System.currentTimeMillis - joinStart)
}
def onPartitionsRevoked(partitions: util.Collection[TopicPartition]) {
isAssigned.set(false)
joinStart = System.currentTimeMillis
}})
val joinStart = System.currentTimeMillis()
val joinStartForFirstTime = System.currentTimeMillis()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since we are now reporting the join time separately, I don't think we need the loop below anymore. It was initially intended to discount the rebalance overhead, but now we account for that transparently.

while (!isAssigned.get()) {
if (System.currentTimeMillis() - joinStart >= joinTimeout) {
if (System.currentTimeMillis() - joinStartForFirstTime >= joinTimeout) {
throw new Exception("Timed out waiting for initial group join.")
}
consumer.poll(100)
Expand Down Expand Up @@ -189,7 +214,7 @@ object ConsumerPerformance {
val totalMBRead = (bytesRead * 1.0) / (1024 * 1024)
val mbRead = ((bytesRead - lastBytesRead) * 1.0) / (1024 * 1024)
println("%s, %d, %.4f, %.4f, %d, %.4f".format(dateFormat.format(endMs), id, totalMBRead,
1000.0 * (mbRead / elapsedMs), messagesRead, ((messagesRead - lastMessagesRead) / elapsedMs) * 1000.0))
1000.0 * (mbRead / elapsedMs), messagesRead, ((messagesRead - lastMessagesRead) / elapsedMs) * 1000.0))
}

class ConsumerPerfConfig(args: Array[String]) extends PerfConfig(args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ConsumerPerformanceTest {
@Test
def testHeaderMatchBody(): Unit = {
Console.withOut(outContent) {
ConsumerPerformance.printHeader(true)
ConsumerPerformance.printHeader(true, false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please provide parameter names. Also, let's add additional test cases so that we are covering all of the variations: detailed=true/false, consumer=old/new.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please address this comment.

ConsumerPerformance.printProgressMessage(1, 1024 * 1024, 0, 1, 0, 0, 1,
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
)
Expand Down