Skip to content

Commit 63b5357

Browse files
joeyjacksoncwbriones
authored andcommitted
Kafka instrument (#160)
* kafka source init * kafka consumer wrapper and listener * unit test and checkstyle * exception handling * unit tests for kafka source * interfaces and wrappers for kafka source * removed kafka dep * integration tests for kafka source * config deserialization * Fixed deserializer * Fixed deserializer * styling * kafka docker * pipeline config example * style * error checking * error checking * integration test kafka source from config * style * added parser to kafka source * example pipeline * Fail integration test on send fail to kafka server * requested changes * requested changes * configurable backoff time for kafka source * fixed conf deserializer * concurrent parsing workers * multi worker unit test * queue holds record values instead of records * style * instrument init * todo * mock observer for multithreaded testing * configurable buffer queue size * moved fill queue integration test to unit test * style * ensure queue fills in queue filled test * refactor kafka source constructors * style * fix injector in integration tests * instrumentation testing init * unit tests for instrumentation counter * unit test gauge metric * more instrumentation metrics * remove prinln * new metric names * metrics unit tests * requested changes * nonnull annotate
1 parent 1b558bc commit 63b5357

File tree

4 files changed

+353
-59
lines changed

4 files changed

+353
-59
lines changed

src/main/java/com/arpnetworking/metrics/common/sources/KafkaSource.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@
1616
package com.arpnetworking.metrics.common.sources;
1717

1818
import com.arpnetworking.logback.annotations.LogValue;
19+
import com.arpnetworking.metrics.Units;
1920
import com.arpnetworking.metrics.common.kafka.ConsumerListener;
2021
import com.arpnetworking.metrics.common.kafka.RunnableConsumer;
2122
import com.arpnetworking.metrics.common.kafka.RunnableConsumerImpl;
2223
import com.arpnetworking.metrics.common.parsers.Parser;
2324
import com.arpnetworking.metrics.common.parsers.exceptions.ParsingException;
25+
import com.arpnetworking.metrics.incubator.PeriodicMetrics;
2426
import com.arpnetworking.steno.LogValueMapFactory;
2527
import com.arpnetworking.steno.Logger;
2628
import com.arpnetworking.steno.LoggerFactory;
29+
import com.fasterxml.jackson.annotation.JacksonInject;
30+
import com.google.common.base.Stopwatch;
2731
import net.sf.oval.constraint.CheckWith;
2832
import net.sf.oval.constraint.CheckWithCheck;
2933
import net.sf.oval.constraint.Min;
@@ -33,11 +37,13 @@
3337
import org.apache.kafka.common.KafkaException;
3438

3539
import java.time.Duration;
40+
import java.util.Optional;
3641
import java.util.concurrent.ArrayBlockingQueue;
3742
import java.util.concurrent.BlockingQueue;
3843
import java.util.concurrent.ExecutorService;
3944
import java.util.concurrent.Executors;
4045
import java.util.concurrent.TimeUnit;
46+
import java.util.concurrent.atomic.AtomicLong;
4147

4248
/**
4349
* Produce instances of {@link com.arpnetworking.metrics.mad.model.Record} from the values of entries
@@ -49,7 +55,6 @@
4955
* @author Joey Jackson (jjackson at dropbox dot com)
5056
*/
5157
public final class KafkaSource<T, V> extends BaseSource {
52-
5358
private static final Logger LOGGER = LoggerFactory.getLogger(KafkaSource.class);
5459

5560
private final Consumer<?, V> _consumer;
@@ -63,6 +68,18 @@ public final class KafkaSource<T, V> extends BaseSource {
6368
private final Integer _numWorkerThreads;
6469
private final BlockingQueue<V> _buffer;
6570
private final ParsingWorker _parsingWorker = new ParsingWorker();
71+
private final PeriodicMetrics _periodicMetrics;
72+
private final AtomicLong _currentRecordsProcessedCount = new AtomicLong(0);
73+
private final AtomicLong _currentRecordsIngestedCount = new AtomicLong(0);
74+
private final String _parsingTimeMetricName = "sources/kafka/" + getMetricSafeName() + "/parsing_time";
75+
// CHECKSTYLE.OFF: VisibilityModifierCheck - Package private for use in testing
76+
final String _recordsInCountMetricName = "sources/kafka/" + getMetricSafeName() + "/records_in";
77+
final String _recordsOutCountMetricName = "sources/kafka/" + getMetricSafeName() + "/records_out";
78+
final String _parsingExceptionCountMetricName = "sources/kafka/" + getMetricSafeName() + "/parsing_exceptions";
79+
final String _kafkaExceptionCountMetricName = "sources/kafka/" + getMetricSafeName() + "/kafka_exceptions";
80+
final String _consumerExceptionCountMetricName = "sources/kafka/" + getMetricSafeName() + "/consumer_exceptions";
81+
final String _queueSizeGaugeMetricName = "sources/kafka/" + getMetricSafeName() + "/queue_size";
82+
// CHECKSTYLE.ON: VisibilityModifierCheck
6683

6784
@Override
6885
public void start() {
@@ -146,6 +163,13 @@ private KafkaSource(final Builder<T, V> builder, final Logger logger, final Bloc
146163
_parserExecutor = Executors.newFixedThreadPool(_numWorkerThreads);
147164
_shutdownAwaitTime = builder._shutdownAwaitTime;
148165
_backoffTime = builder._backoffTime;
166+
_periodicMetrics = builder._periodicMetrics;
167+
_periodicMetrics.registerPolledMetric(periodicMetrics ->
168+
periodicMetrics.recordCounter(_recordsOutCountMetricName,
169+
_currentRecordsProcessedCount.getAndSet(0)));
170+
_periodicMetrics.registerPolledMetric(periodicMetrics ->
171+
periodicMetrics.recordCounter(_recordsInCountMetricName,
172+
_currentRecordsIngestedCount.getAndSet(0)));
149173
_logger = logger;
150174
_buffer = buffer;
151175
}
@@ -157,18 +181,25 @@ private class ParsingWorker implements Runnable {
157181
public void run() {
158182
while (_isRunning || !_buffer.isEmpty()) { // Empty the queue before stopping the workers
159183
final V value = _buffer.poll();
184+
_periodicMetrics.recordGauge(_queueSizeGaugeMetricName, _buffer.size());
160185
if (value != null) {
161186
final T record;
162187
try {
188+
final Stopwatch parsingTimer = Stopwatch.createStarted();
163189
record = _parser.parse(value);
190+
parsingTimer.stop();
191+
_periodicMetrics.recordTimer(_parsingTimeMetricName,
192+
parsingTimer.elapsed(TimeUnit.NANOSECONDS), Optional.of(Units.NANOSECOND));
164193
} catch (final ParsingException e) {
194+
_periodicMetrics.recordCounter(_parsingExceptionCountMetricName, 1);
165195
_logger.error()
166196
.setMessage("Failed to parse data")
167197
.setThrowable(e)
168198
.log();
169-
return;
199+
continue;
170200
}
171201
KafkaSource.this.notify(record);
202+
_currentRecordsProcessedCount.getAndIncrement();
172203
} else {
173204
// Queue is empty
174205
try {
@@ -192,6 +223,8 @@ private class LogConsumerListener implements ConsumerListener<V> {
192223
public void handle(final ConsumerRecord<?, V> consumerRecord) {
193224
try {
194225
_buffer.put(consumerRecord.value());
226+
_currentRecordsIngestedCount.getAndIncrement();
227+
_periodicMetrics.recordGauge(_queueSizeGaugeMetricName, _buffer.size());
195228
} catch (final InterruptedException e) {
196229
_logger.info()
197230
.setMessage("Consumer thread interrupted")
@@ -215,6 +248,7 @@ public void handle(final Throwable throwable) {
215248
.log();
216249
_runnableConsumer.stop();
217250
} else if (throwable instanceof KafkaException) {
251+
_periodicMetrics.recordCounter(_kafkaExceptionCountMetricName, 1);
218252
_logger.error()
219253
.setMessage("Consumer received Kafka Exception")
220254
.addData("source", KafkaSource.this)
@@ -223,6 +257,7 @@ public void handle(final Throwable throwable) {
223257
.log();
224258
backoff(throwable);
225259
} else {
260+
_periodicMetrics.recordCounter(_consumerExceptionCountMetricName, 1);
226261
_logger.error()
227262
.setMessage("Consumer thread error")
228263
.addData("source", KafkaSource.this)
@@ -351,6 +386,17 @@ public Builder<T, V> setBufferSize(final Integer bufferSize) {
351386
return this;
352387
}
353388

389+
/**
390+
* Sets {@code PeriodicMetrics} for instrumentation of {@link KafkaSource}.
391+
*
392+
* @param periodicMetrics The {@code PeriodicMetrics} for the {@link KafkaSource}.
393+
* @return This instance of {@link KafkaSource.Builder}.
394+
*/
395+
public Builder<T, V> setPeriodicMetrics(final PeriodicMetrics periodicMetrics) {
396+
_periodicMetrics = periodicMetrics;
397+
return this;
398+
}
399+
354400
@Override
355401
protected Builder<T, V> self() {
356402
return this;
@@ -375,6 +421,9 @@ protected Builder<T, V> self() {
375421
@NotNull
376422
@Min(1)
377423
private Integer _bufferSize = 1000;
424+
@JacksonInject
425+
@NotNull
426+
private PeriodicMetrics _periodicMetrics;
378427

379428
private static class PositiveDuration implements CheckWithCheck.SimpleCheck {
380429
@Override

src/test/java/com/arpnetworking/metrics/common/integration/KafkaIT.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@
1919
import com.arpnetworking.commons.observer.Observer;
2020
import com.arpnetworking.metrics.common.kafka.ConsumerDeserializer;
2121
import com.arpnetworking.metrics.common.sources.KafkaSource;
22+
import com.arpnetworking.metrics.incubator.PeriodicMetrics;
2223
import com.arpnetworking.test.StringParser;
2324
import com.fasterxml.jackson.core.type.TypeReference;
2425
import com.fasterxml.jackson.databind.ObjectMapper;
26+
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
2527
import com.fasterxml.jackson.databind.module.SimpleModule;
28+
import com.fasterxml.jackson.module.guice.GuiceAnnotationIntrospector;
29+
import com.fasterxml.jackson.module.guice.GuiceInjectableValues;
2630
import com.google.common.collect.Maps;
31+
import com.google.inject.AbstractModule;
32+
import com.google.inject.Guice;
33+
import com.google.inject.Injector;
2734
import org.apache.kafka.clients.admin.AdminClient;
2835
import org.apache.kafka.clients.admin.AdminClientConfig;
2936
import org.apache.kafka.clients.admin.CreateTopicsResult;
@@ -79,13 +86,15 @@ public class KafkaIT {
7986
private String _topicName;
8087
private KafkaSource<String, String> _source;
8188
private List<ProducerRecord<Integer, String>> _producerRecords;
89+
private PeriodicMetrics _periodicMetrics;
8290

8391
@Before
8492
public void setUp() throws TimeoutException {
8593
// Create kafka topic
8694
_topicName = createTopicName();
8795
createTopic(_topicName);
8896
setupKafka();
97+
_periodicMetrics = Mockito.mock(PeriodicMetrics.class);
8998
}
9099

91100
@After
@@ -105,6 +114,7 @@ public void testKafkaSourceSingleObserver() {
105114
.setParser(new StringParser())
106115
.setConsumer(_consumer)
107116
.setPollTime(POLL_DURATION)
117+
.setPeriodicMetrics(_periodicMetrics)
108118
.build();
109119

110120
// Observe records
@@ -126,6 +136,7 @@ public void testKafkaSourceMultipleObservers() {
126136
.setParser(new StringParser())
127137
.setConsumer(_consumer)
128138
.setPollTime(POLL_DURATION)
139+
.setPeriodicMetrics(_periodicMetrics)
129140
.build();
130141

131142
// Observe records
@@ -166,9 +177,26 @@ public void testKafkaSourceFromConfig() throws IOException {
166177
+ "\n}";
167178

168179
final ObjectMapper mapper = ObjectMapperFactory.createInstance();
180+
169181
final SimpleModule module = new SimpleModule("KafkaConsumer");
170182
module.addDeserializer(Consumer.class, new ConsumerDeserializer<>());
171183
mapper.registerModule(module);
184+
185+
final GuiceAnnotationIntrospector guiceIntrospector = new GuiceAnnotationIntrospector();
186+
final Injector injector = Guice.createInjector(new AbstractModule() {
187+
@Override
188+
protected void configure() {
189+
super.configure();
190+
bind(PeriodicMetrics.class).toInstance(_periodicMetrics);
191+
}
192+
});
193+
mapper.setInjectableValues(new GuiceInjectableValues(injector));
194+
mapper.setAnnotationIntrospectors(
195+
new AnnotationIntrospectorPair(
196+
guiceIntrospector, mapper.getSerializationConfig().getAnnotationIntrospector()),
197+
new AnnotationIntrospectorPair(
198+
guiceIntrospector, mapper.getDeserializationConfig().getAnnotationIntrospector()));
199+
172200
_source = mapper.readValue(jsonString, new KafkaSourceStringType());
173201

174202
// Observe records
@@ -191,6 +219,7 @@ public void testKafkaSourceMultipleWorkerThreads() {
191219
.setConsumer(_consumer)
192220
.setPollTime(POLL_DURATION)
193221
.setNumWorkerThreads(4)
222+
.setPeriodicMetrics(_periodicMetrics)
194223
.build();
195224

196225
// Observe records

0 commit comments

Comments
 (0)