/* * Copyright 2015 the original author or authors. * * 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 org.springframework.cloud.stream.module.throughput; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.context.Lifecycle; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.messaging.Message; /** * A simple handler that will count messages and log witnessed throughput at some interval. * * @author Glenn Renfro * @author Eric Bottard */ @EnableBinding(Sink.class) @EnableConfigurationProperties({ThroughputSinkProperties.class}) public class ThroughputSink implements Lifecycle { private static Logger logger = LoggerFactory.getLogger(ThroughputSink.class); private final AtomicLong counter = new AtomicLong(); private final AtomicLong start = new AtomicLong(-1); private final AtomicLong bytes = new AtomicLong(-1); private final AtomicLong intermediateCounter = new AtomicLong(); private final AtomicLong intermediateBytes = new AtomicLong(); private final TimeUnit timeUnit = TimeUnit.s; private final ExecutorService executorService = Executors.newFixedThreadPool(1); private volatile boolean running; private volatile boolean reportBytes = false; @Autowired private volatile ThroughputSinkProperties config; @Override public void start() { this.running = true; } @Override public void stop() { this.running = false; } @Override public boolean isRunning() { return running; } @ServiceActivator(inputChannel=Sink.INPUT) public void throughputSink(Message<?> message ) { if (start.get() == -1L) { synchronized (start) { if (start.get() == -1L) { // assume a homogeneous message structure - this is intended for // performance tests so we can assume that the messages are similar; // therefore we'll do our reporting based on the first message Object payload = message.getPayload(); if (payload instanceof byte[] || payload instanceof String) { reportBytes = true; } start.set(System.currentTimeMillis()); executorService.execute(new ReportStats()); } } } intermediateCounter.incrementAndGet(); if (reportBytes) { Object payload = message.getPayload(); if (payload instanceof byte[]) { intermediateBytes.addAndGet(((byte[]) payload).length); } else if (payload instanceof String) { intermediateBytes.addAndGet((((String) payload).getBytes()).length); } } } private class ReportStats implements Runnable { @Override public void run() { int reportEveryMs = config.getReportEveryMs(); while (isRunning()) { long intervalStart = System.currentTimeMillis(); try { Thread.sleep(reportEveryMs); long timeNow = System.currentTimeMillis(); long currentCounter = intermediateCounter.getAndSet(0L); long currentBytes = intermediateBytes.getAndSet(0L); long totalCounter = counter.addAndGet(currentCounter); long totalBytes = bytes.addAndGet(currentBytes); logger.info( String.format("Messages: %10d in %5.2f%s = %11.2f/s", currentCounter, (timeNow - intervalStart)/ 1000.0, timeUnit, ((double) currentCounter * 1000 / reportEveryMs))); logger.info( String.format("Messages: %10d in %5.2f%s = %11.2f/s", totalCounter, (timeNow - start.get()) / 1000.0, timeUnit, ((double) totalCounter * 1000 / (timeNow - start.get())))); if (reportBytes) { logger.info( String.format("Throughput: %12d in %5.2f%s = %11.2fMB/s, ", currentBytes, (timeNow - intervalStart)/ 1000.0, timeUnit, ((currentBytes / (1024.0 * 1024)) * 1000 / reportEveryMs))); logger.info( String.format("Throughput: %12d in %5.2f%s = %11.2fMB/s", totalBytes, (timeNow - start.get()) / 1000.0, timeUnit, ((totalBytes / (1024.0 * 1024)) * 1000 / (timeNow - start.get())))); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.warn("Thread interrupted", e); return; } } } } }