BoundedAsyncReporter(Builder builder, Encoder<S> encoder) { this.pending = new ByteBoundedQueue(builder.queuedMaxSpans, builder.queuedMaxBytes); this.sender = builder.sender; this.messageMaxBytes = builder.messageMaxBytes; this.messageTimeoutNanos = builder.messageTimeoutNanos; this.closeTimeoutNanos = builder.closeTimeoutNanos; this.close = new CountDownLatch(builder.messageTimeoutNanos > 0 ? 1 : 0); this.metrics = builder.metrics; this.encoder = encoder; }
@Override public void close() { if (!closed.compareAndSet(false, true)) return; // already closed try { // wait for in-flight spans to send if (!close.await(closeTimeoutNanos, TimeUnit.NANOSECONDS)) { logger.warning("Timed out waiting for in-flight spans to send"); } } catch (InterruptedException e) { logger.warning("Interrupted waiting for in-flight spans to send"); Thread.currentThread().interrupt(); } int count = pending.clear(); if (count > 0) { metrics.incrementSpansDropped(count); logger.warning("Dropped " + count + " spans due to AsyncReporter.close()"); } }
/** Blocks for up to nanosTimeout for elements to appear. Then, consume as many as possible. */ int drainTo(Consumer consumer, long nanosTimeout) { try { // This may be called by multiple threads. If one is holding a lock, another is waiting. We // use lockInterruptibly to ensure the one waiting can be interrupted. lock.lockInterruptibly(); try { long nanosLeft = nanosTimeout; while (count == 0) { if (nanosLeft <= 0) return 0; nanosLeft = available.awaitNanos(nanosLeft); } return doDrain(consumer); } finally { lock.unlock(); } } catch (InterruptedException e) { return 0; } }
/** Returns true if the was encoded and accepted onto the queue. */ @Override public void report(S span) { if (span == null) throw new NullPointerException("span == null"); metrics.incrementSpans(1); byte[] next = encoder.encode(span); int messageSizeOfNextSpan = sender.messageSizeInBytes(Collections.singletonList(next)); metrics.incrementSpanBytes(next.length); if (closed.get() || // don't enqueue something larger than we can drain messageSizeOfNextSpan > messageMaxBytes || !pending.offer(next)) { metrics.incrementSpansDropped(1); } }
void flush(BufferNextMessage bundler) { if (closed.get()) throw new IllegalStateException("closed"); pending.drainTo(bundler, bundler.remainingNanos()); // record after flushing reduces the amount of gauge events vs on doing this on report metrics.updateQueuedSpans(pending.count); metrics.updateQueuedBytes(pending.sizeInBytes); // loop around if we are running, and the bundle isn't full // if we are closed, try to send what's pending if (!bundler.isReady() && !closed.get()) return; // Signal that we are about to send a message of a known size in bytes metrics.incrementMessages(); metrics.incrementMessageBytes(bundler.sizeInBytes()); List<byte[]> nextMessage = bundler.drain(); // In failure case, we increment messages and spans dropped. Callback failureCallback = sendSpansCallback(nextMessage.size()); try { sender.sendSpans(nextMessage, failureCallback); } catch (RuntimeException e) { failureCallback.onError(e); // Raise in case the sender was closed out-of-band. if (e instanceof IllegalStateException) throw e; } }
/** Builds an async reporter that encodes arbitrary spans as they are reported. */ public <S> AsyncReporter<S> build(Encoder<S> encoder) { if (encoder == null) throw new NullPointerException("encoder == null"); if (encoder.encoding() != sender.encoding()) { throw new IllegalArgumentException(String.format( "Encoder doesn't match Sender: %s %s", encoder.encoding(), sender.encoding())); } final BoundedAsyncReporter<S> result = new BoundedAsyncReporter<>(this, encoder); if (messageTimeoutNanos > 0) { // Start a thread that flushes the queue in a loop. final BufferNextMessage consumer = new BufferNextMessage(sender, messageMaxBytes, messageTimeoutNanos); final Thread flushThread = new Thread(() -> { try { while (!result.closed.get()) { result.flush(consumer); } } finally { for (byte[] next : consumer.drain()) result.pending.offer(next); result.close.countDown(); } }, "AsyncReporter(" + sender + ")"); flushThread.setDaemon(true); flushThread.start(); } return result; } }