/* * Copyright © 2014 Cask Data, Inc. * * Licensed 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. */ package co.cask.cdap.metrics.collect; import co.cask.cdap.api.metrics.MetricValues; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.io.BinaryEncoder; import co.cask.cdap.common.io.Encoder; import co.cask.cdap.internal.io.DatumWriter; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import org.apache.twill.kafka.client.Compression; import org.apache.twill.kafka.client.KafkaClient; import org.apache.twill.kafka.client.KafkaPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Iterator; /** * A {@link AggregatedMetricsCollectionService} that publish {@link co.cask.cdap.api.metrics.MetricValues} to kafka. * The partition is determined by the metric context. */ @Singleton public class KafkaMetricsCollectionService extends AggregatedMetricsCollectionService { private static final Logger LOG = LoggerFactory.getLogger(KafkaMetricsCollectionService.class); private final KafkaClient kafkaClient; private final String topicPrefix; private final KafkaPublisher.Ack ack; private final DatumWriter<MetricValues> recordWriter; private final ByteArrayOutputStream encoderOutputStream; private final Encoder encoder; private KafkaPublisher publisher; @Inject public KafkaMetricsCollectionService(KafkaClient kafkaClient, @Named(Constants.Metrics.KAFKA_TOPIC_PREFIX) String topicPrefix, DatumWriter<MetricValues> recordWriter) { this(kafkaClient, topicPrefix, KafkaPublisher.Ack.FIRE_AND_FORGET, recordWriter); } public KafkaMetricsCollectionService(KafkaClient kafkaClient, String topicPrefix, KafkaPublisher.Ack ack, DatumWriter<MetricValues> recordWriter) { this.kafkaClient = kafkaClient; this.topicPrefix = topicPrefix; this.ack = ack; this.recordWriter = recordWriter; // Parent guarantees the publish method would not get called concurrently, hence safe to reuse the same instances. this.encoderOutputStream = new ByteArrayOutputStream(1024); this.encoder = new BinaryEncoder(encoderOutputStream); } @Override protected void startUp() throws Exception { getPublisher(); } @Override protected void publish(Iterator<MetricValues> metrics) throws Exception { KafkaPublisher publisher = getPublisher(); if (publisher == null) { LOG.warn("Unable to get kafka publisher, will not be able to publish metrics."); return; } encoderOutputStream.reset(); KafkaPublisher.Preparer preparer = publisher.prepare(topicPrefix); while (metrics.hasNext()) { // Encode each MetricRecord into bytes and make it an individual kafka message in a message set. MetricValues value = metrics.next(); publishMetric(preparer, value); } preparer.send(); } private void publishMetric(KafkaPublisher.Preparer preparer, MetricValues value) throws IOException { recordWriter.encode(value, encoder); // partitioning by the context preparer.add(ByteBuffer.wrap(encoderOutputStream.toByteArray()), getPartitionKey(value)); encoderOutputStream.reset(); } private Integer getPartitionKey(MetricValues value) { // TODO: incredibly non-efficient: it is performed for each metrics data point, return value.getTags().hashCode(); } private KafkaPublisher getPublisher() { if (publisher != null) { return publisher; } try { publisher = kafkaClient.getPublisher(ack, Compression.SNAPPY); } catch (IllegalStateException e) { // can happen if there are no kafka brokers because the kafka server is down. publisher = null; } return publisher; } }