package com.neverwinterdp.kafka.tool; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import kafka.javaapi.PartitionMetadata; import kafka.javaapi.TopicMetadata; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParametersDelegate; import com.google.common.base.Stopwatch; import com.neverwinterdp.kafka.consumer.KafkaPartitionReader; import com.neverwinterdp.kafka.tool.KafkaTopicReport.ConsumerReport; import com.neverwinterdp.tool.message.MessageExtractor; import com.neverwinterdp.tool.message.MessageTracker; import com.neverwinterdp.util.text.TabularFormater; public class KafkaMessageCheckTool implements Runnable { static private String NAME = "KafkaMessageCheckTool"; @ParametersDelegate private KafkaTopicConfig topicConfig = new KafkaTopicConfig(); private MessageExtractor messageExtractor = MessageExtractor.DEFAULT_MESSAGE_EXTRACTOR; private MessageTracker messageTracker = new MessageTracker() ; private MessageCounter messageCounter = new MessageCounter(); private int numOfPartitions = 0; private boolean interrupt = false; private Thread deamonThread; private Stopwatch readDuration = Stopwatch.createUnstarted(); private boolean running = false; public KafkaMessageCheckTool(String[] args) { new JCommander(this, args); } public KafkaMessageCheckTool(KafkaTopicConfig topicConfig) { this.topicConfig = topicConfig; } public void setMessageExtractor(MessageExtractor extractor) { this.messageExtractor = extractor ; } public MessageTracker getMessageTracker() { return messageTracker ; } //TODO: replace by the KafkaTopicReport.ConsumerReport public MessageCounter getMessageCounter() { return messageCounter; } public Stopwatch getReadDuration() { return readDuration; } public void setInterrupt(boolean b) { this.interrupt = b; } synchronized public boolean waitForTermination(long maxWaitTime) throws InterruptedException { if (!running) return !running; wait(maxWaitTime); return !running; } synchronized public boolean waitForTermination() throws InterruptedException { if(!running) return !running; wait(topicConfig.consumerConfig.maxDuration); return !running; } synchronized void notifyTermination() { notifyAll(); } public void runAsDeamon() { if (deamonThread != null && deamonThread.isAlive()) { throw new RuntimeException("Deamon thread is already started"); } deamonThread = new Thread(this); deamonThread.start(); } public void run() { running = true; try { check(); } catch (Exception e) { e.printStackTrace(); } running = false; notifyTermination(); } //TODO each partition reader on a separate thread. same as SendTool public void check() throws Exception { System.out.println("KafkaMessageCheckTool: Start running kafka message check tool."); readDuration.start(); KafkaTool kafkaTool = new KafkaTool(NAME, topicConfig.zkConnect); kafkaTool.connect(); TopicMetadata topicMeta = kafkaTool.findTopicMetadata(topicConfig.topic, 3); List<PartitionMetadata> partitionMetas = topicMeta.partitionsMetadata(); this.numOfPartitions = partitionMetas.size(); kafkaTool.close(); KafkaPartitionReader[] partitionReader = new KafkaPartitionReader[partitionMetas.size()]; for (int i = 0; i < partitionReader.length; i++) { partitionReader[i] = new KafkaPartitionReader(NAME, topicConfig.zkConnect, topicConfig.topic, partitionMetas.get(i)); } interrupt = false; int lastCount = 0, cannotReadCount = 0; int batchFetch = topicConfig.consumerConfig.consumeBatchFetch ; int fetchSize = batchFetch * (topicConfig.producerConfig.messageSize + 100) ; while (messageCounter.getTotal() < topicConfig.consumerConfig.consumeMax && !interrupt) { for (int k = 0; k < partitionReader.length; k++) { List<byte[]> messages = partitionReader[k].fetch(fetchSize, batchFetch/*max read*/, 0 /*max wait*/); messageCounter.count(partitionReader[k].getPartition(), messages.size()); for(byte[] messagePayload : messages) { messageTracker.log(messageExtractor.extract(messagePayload)); } } if(lastCount == messageCounter.getTotal()) { cannotReadCount++; Thread.sleep(1000); } else { cannotReadCount = 0; } if(cannotReadCount >= 5) interrupt = true; lastCount = messageCounter.getTotal(); } //Run the last fetch to find the duplicated messages if there are some for (int k = 0; k < partitionReader.length; k++) { List<byte[]> messages = partitionReader[k].fetch(fetchSize, 100/*max read*/, 1000 /*max wait*/); messageCounter.count(partitionReader[k].getPartition(), messages.size()); for(byte[] messagePayload : messages) { messageTracker.log(messageExtractor.extract(messagePayload)); } } for (int k = 0; k < partitionReader.length; k++) { partitionReader[k].commit(); partitionReader[k].close(); } System.out.println("Read count: " + messageCounter.getTotal() +"(Stop)") ; messageTracker.optimize(); readDuration.stop(); if(topicConfig.junitReportFile != null && !topicConfig.junitReportFile.isEmpty()){ getReport().junitReport(topicConfig.junitReportFile); } } public KafkaTopicReport getReport() { KafkaTopicReport report = new KafkaTopicReport() ; report.setTopic(topicConfig.topic); report.setNumOfPartitions(numOfPartitions); report.setNumOfReplicas(topicConfig.replication); populate(report); return report ; } public void populate(KafkaTopicReport report) { ConsumerReport consumerReport = report.getConsumerReport(); consumerReport.setMessagesRead(messageCounter.totalMessages); consumerReport.setRunDuration(readDuration.elapsed(TimeUnit.MILLISECONDS)); } static public class MessageCounter { private Map<Integer, Integer> counters = new HashMap<Integer, Integer>(); //TODO use atomic integer for thread safety private int totalMessages; public int getTotal() { return totalMessages; } public Map<Integer, Integer> getCounter(){ return counters; } public int getPartitionCount(int partition) { return counters.get(partition); } public void count(int partition, int readMessage) { Integer current = counters.get(partition); if (current == null) { counters.put(partition, readMessage); } else { counters.put(partition, current.intValue() + readMessage); } totalMessages += readMessage; } public void print(Appendable out, String title) { TabularFormater formater = new TabularFormater("Partition", "Read"); formater.setTitle(title + "(" + totalMessages + ")"); formater.setIndent(" "); for (Map.Entry<Integer, Integer> entry : counters.entrySet()) { formater.addRow(entry.getKey(), entry.getValue()); } try { out.append(formater.getFormatText()).append("\n"); } catch (IOException e) { e.printStackTrace(); } } } }