/*
* Copyright 2015, The Sporting Exchange Limited
*
* 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 com.betfair.cougar.modules.zipkin.impl;
import com.betfair.cougar.modules.zipkin.api.ZipkinData;
import com.betfair.cougar.util.geolocation.RemoteAddressUtils;
import com.betfair.cougar.util.time.Clock;
import com.github.kristofa.brave.zipkin.ZipkinSpanCollector;
import com.twitter.zipkin.gen.Endpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import static com.twitter.zipkin.gen.zipkinCoreConstants.*;
/**
* An emitter capable of emitting ZipkinData information to a ZipkinSpanCollector. ZipkinEmitter should be instantiated
* per service, as it stores information about the service (attaching it to the emitted spans transparently).
*
* @see com.betfair.cougar.modules.zipkin.api.ZipkinData
*/
@ManagedResource
public class ZipkinEmitter implements InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(ZipkinEmitter.class);
private int serviceIPv4;
private String serviceName;
private ZipkinSpanCollector zipkinSpanCollector;
private final Clock clock;
private BlockingQueue<?> zipkinSpanCollectorInternalQueue;
/**
* Creates a new ZipkinEmitter. This constructor overload obtains the service IPv4 through
* com.betfair.cougar.util.geolocation.RemoteAddressUtils.
*
* @param serviceName The name of the service the emitter will correspond to
* @param zipkinSpanCollector The brave ZipkinSpanCollector to be used for emitting spans
* @param clock The clock to be used when obtaining timestamps
*/
public ZipkinEmitter(@Nonnull String serviceName, @Nonnull ZipkinSpanCollector zipkinSpanCollector,
@Nonnull Clock clock) {
this(serviceName, zipkinSpanCollector, clock, RemoteAddressUtils.getLocalhostAsIPv4Integer());
}
/**
* Creates a new ZipkinEmitter.
*
* @param serviceName The name of the service the emitter will correspond to
* @param zipkinSpanCollector The brave ZipkinSpanCollector to be used for emitting spans
* @param clock The clock to be used when obtaining timestamps
* @param serviceIPv4 The IPv4 of the service the emitter will correspond to
*/
public ZipkinEmitter(@Nonnull String serviceName, @Nonnull ZipkinSpanCollector zipkinSpanCollector,
@Nonnull Clock clock, int serviceIPv4) {
Objects.requireNonNull(serviceName);
Objects.requireNonNull(zipkinSpanCollector);
Objects.requireNonNull(clock);
this.serviceIPv4 = serviceIPv4;
this.serviceName = serviceName;
this.zipkinSpanCollector = zipkinSpanCollector;
this.clock = clock;
}
/**
* Emits a Server Receive annotation for a particular span.
*
* @param zipkinData The ZipkinData representing the span
*/
public void emitServerReceive(@Nonnull ZipkinData zipkinData) {
emitAnnotation(zipkinData, SERVER_RECV);
}
/**
* Emits a Server Send annotation for a particular span.
*
* @param zipkinData The ZipkinData representing the span
*/
public void emitServerSend(@Nonnull ZipkinData zipkinData) {
emitAnnotation(zipkinData, SERVER_SEND);
}
/**
* Emits a Client Send annotation for a particular span.
*
* @param zipkinData The ZipkinData representing the span
*/
public void emitClientSend(@Nonnull ZipkinData zipkinData) {
emitAnnotation(zipkinData, CLIENT_SEND);
}
/**
* Emits a Client Receive annotation for a particular span.
*
* @param zipkinData The ZipkinData representing the span
*/
public void emitClientReceive(@Nonnull ZipkinData zipkinData) {
emitAnnotation(zipkinData, CLIENT_RECV);
}
/**
* Builds an annotations store for a specific ZipkinData object.
* <p/>
* The returning object should be used to emit more than 1 annotation at once. After adding your annotations you
* will need to call emitAnnotations in order to send the span with all the annotations to Zipkin.
*
* @param zipkinData Zipkin request data
* @return Zipkin annotations storage capable of merging multiple annotations per emission
*/
@Nonnull
public ZipkinAnnotationsStore buildAnnotationsStore(@Nonnull ZipkinData zipkinData) {
Objects.requireNonNull(zipkinData);
Endpoint endpoint = generateEndpoint(zipkinData);
return new ZipkinAnnotationsStore(zipkinData).defaultEndpoint(endpoint);
}
/**
* Emits a pre-populated storage of annotations to Zipkin.
*
* @param zipkinAnnotationsStore Zipkin annotations storage representing the entire list of annotations to emit
*/
public void emitAnnotations(@Nonnull ZipkinAnnotationsStore zipkinAnnotationsStore) {
Objects.requireNonNull(zipkinAnnotationsStore);
zipkinSpanCollector.collect(zipkinAnnotationsStore.generate());
}
// Single annotation emission methods
/**
* Emits a single annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param s The annotation to emit
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String s) {
long timestampMillis = clock.millis();
long timestampMicros = TimeUnit.MILLISECONDS.toMicros(timestampMillis);
ZipkinAnnotationsStore store = prepareEmission(zipkinData, s).addAnnotation(timestampMicros, s);
emitAnnotations(store);
}
/**
* Emits a single (binary) string annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param key The annotation key
* @param value The annotation value
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String key, @Nonnull String value) {
Objects.requireNonNull(value);
ZipkinAnnotationsStore store = prepareEmission(zipkinData, key).addAnnotation(key, value);
emitAnnotations(store);
}
/**
* Emits a single (binary) short annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param key The annotation key
* @param value The annotation value
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String key, short value) {
ZipkinAnnotationsStore store = prepareEmission(zipkinData, key).addAnnotation(key, value);
emitAnnotations(store);
}
/**
* Emits a single (binary) int annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param key The annotation key
* @param value The annotation value
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String key, int value) {
ZipkinAnnotationsStore store = prepareEmission(zipkinData, key).addAnnotation(key, value);
emitAnnotations(store);
}
/**
* Emits a single (binary) long annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param key The annotation key
* @param value The annotation value
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String key, long value) {
ZipkinAnnotationsStore store = prepareEmission(zipkinData, key).addAnnotation(key, value);
emitAnnotations(store);
}
/**
* Emits a single (binary) double annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param key The annotation key
* @param value The annotation value
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String key, double value) {
ZipkinAnnotationsStore store = prepareEmission(zipkinData, key).addAnnotation(key, value);
emitAnnotations(store);
}
/**
* Emits a single (binary) boolean annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param key The annotation key
* @param value The annotation value
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String key, boolean value) {
ZipkinAnnotationsStore store = prepareEmission(zipkinData, key).addAnnotation(key, value);
emitAnnotations(store);
}
/**
* Emits a single (binary) byte array annotation to Zipkin.
*
* @param zipkinData Zipkin request data
* @param key The annotation key
* @param value The annotation value
*/
public void emitAnnotation(@Nonnull ZipkinData zipkinData, @Nonnull String key, byte[] value) {
ZipkinAnnotationsStore store = prepareEmission(zipkinData, key).addAnnotation(key, value);
emitAnnotations(store);
}
@Nonnull
private ZipkinAnnotationsStore prepareEmission(@Nonnull ZipkinData zipkinData, @Nonnull String s) {
Objects.requireNonNull(s);
return buildAnnotationsStore(zipkinData);
}
@Nonnull
private Endpoint generateEndpoint(@Nonnull ZipkinData zipkinData) {
return new Endpoint(serviceIPv4, zipkinData.getPort(), serviceName);
}
@Override
public void afterPropertiesSet() {
try {
Field field = zipkinSpanCollector.getClass().getDeclaredField("spanQueue");
field.setAccessible(true);
zipkinSpanCollectorInternalQueue = (BlockingQueue<?>) field.get(zipkinSpanCollector);
} catch (Exception e) {
LOGGER.warn("Unable to obtain ZipkinSpanCollector's internal queue", e);
}
}
/**
* Gets the current size of the underlying queue (of spans). This method returns -1 if ZipkinEmitter was unable to
* obtain a reference to the queue.
*
* @return The current size of the underlying queue
*/
@ManagedAttribute
public int getCurrentQueueSize() {
return zipkinSpanCollectorInternalQueue != null ? zipkinSpanCollectorInternalQueue.size() : -1;
}
/**
* Gets the remaining capacity of the underlying queue (of spans). This method returns -1 if ZipkinEmitter was
* unable to obtain a reference to the queue.
*
* @return The remaining capacity of the underlying queue
*/
@ManagedAttribute
public int getRemainingQueueCapacity() {
return zipkinSpanCollectorInternalQueue != null ? zipkinSpanCollectorInternalQueue.remainingCapacity() : -1;
}
}