/**
* Copyright 2016 Yahoo 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 com.yahoo.pulsar.client.impl;
import java.io.IOException;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.yahoo.pulsar.client.api.ProducerConfiguration;
import com.yahoo.sketches.quantiles.DoublesSketch;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
public class ProducerStats implements Serializable {
private static final long serialVersionUID = 1L;
private TimerTask stat;
private Timeout statTimeout;
private ProducerImpl producer;
private PulsarClientImpl pulsarClient;
private long oldTime;
private long statsIntervalSeconds;
private final LongAdder numMsgsSent;
private final LongAdder numBytesSent;
private final LongAdder numSendFailed;
private final LongAdder numAcksReceived;
private final LongAdder totalMsgsSent;
private final LongAdder totalBytesSent;
private final LongAdder totalSendFailed;
private final LongAdder totalAcksReceived;
private final DecimalFormat dec;
private final DecimalFormat throughputFormat;
private final DoublesSketch ds;
private final double[] percentiles = { 0.5, 0.95, 0.99, 0.999, 0.9999 };
public static final ProducerStats PRODUCER_STATS_DISABLED = new ProducerStatsDisabled();
public ProducerStats() {
numMsgsSent = null;
numBytesSent = null;
numSendFailed = null;
numAcksReceived = null;
totalMsgsSent = null;
totalBytesSent = null;
totalSendFailed = null;
totalAcksReceived = null;
dec = null;
throughputFormat = null;
ds = null;
}
public ProducerStats(PulsarClientImpl pulsarClient, ProducerConfiguration conf, ProducerImpl producer) {
this.pulsarClient = pulsarClient;
this.statsIntervalSeconds = pulsarClient.getConfiguration().getStatsIntervalSeconds();
this.producer = producer;
numMsgsSent = new LongAdder();
numBytesSent = new LongAdder();
numSendFailed = new LongAdder();
numAcksReceived = new LongAdder();
totalMsgsSent = new LongAdder();
totalBytesSent = new LongAdder();
totalSendFailed = new LongAdder();
totalAcksReceived = new LongAdder();
ds = DoublesSketch.builder().build(256);
dec = new DecimalFormat("0.000");
throughputFormat = new DecimalFormat("0.00");
init(conf);
}
private void init(ProducerConfiguration conf) {
ObjectMapper m = new ObjectMapper();
m.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
ObjectWriter w = m.writerWithDefaultPrettyPrinter();
try {
log.info("Starting Pulsar producer perf with config: {}", w.writeValueAsString(conf));
log.info("Pulsar client config: {}", w.writeValueAsString(pulsarClient.getConfiguration()));
} catch (IOException e) {
log.error("Failed to dump config info: {}", e);
}
stat = (timeout) -> {
if (timeout.isCancelled()) {
return;
}
try {
long now = System.nanoTime();
double elapsed = (now - oldTime) / 1e9;
oldTime = now;
long currentNumMsgsSent = numMsgsSent.sumThenReset();
long currentNumBytesSent = numBytesSent.sumThenReset();
long currentNumSendFailedMsgs = numSendFailed.sumThenReset();
long currentNumAcksReceived = numAcksReceived.sumThenReset();
totalMsgsSent.add(currentNumMsgsSent);
totalBytesSent.add(currentNumBytesSent);
totalSendFailed.add(currentNumSendFailedMsgs);
totalAcksReceived.add(currentNumAcksReceived);
double[] percentileValues;
synchronized (ds) {
percentileValues = ds.getQuantiles(percentiles);
ds.reset();
}
if ((currentNumMsgsSent | currentNumSendFailedMsgs | currentNumAcksReceived
| currentNumMsgsSent) != 0) {
for (int i = 0; i < percentileValues.length; i++) {
if (percentileValues[i] == Double.NaN) {
percentileValues[i] = 0;
}
}
log.info(
"[{}] [{}] Pending messages: {} --- Publish throughput: {} msg/s --- {} Mbit/s --- "
+ "Latency: med: {} ms - 95pct: {} ms - 99pct: {} ms - 99.9pct: {} ms - 99.99pct: {} ms --- "
+ "Ack received rate: {} ack/s --- Failed messages: {}",
producer.getTopic(), producer.getProducerName(), producer.getPendingQueueSize(),
throughputFormat.format(currentNumMsgsSent / elapsed),
throughputFormat.format(currentNumBytesSent / elapsed / 1024 / 1024 * 8),
dec.format(percentileValues[0] / 1000.0), dec.format(percentileValues[1] / 1000.0),
dec.format(percentileValues[2] / 1000.0), dec.format(percentileValues[3] / 1000.0),
dec.format(percentileValues[4] / 1000.0),
throughputFormat.format(currentNumAcksReceived / elapsed), currentNumSendFailedMsgs);
}
} catch (Exception e) {
log.error("[{}] [{}]: {}", producer.getTopic(), producer.getProducerName(), e.getMessage());
} finally {
// schedule the next stat info
statTimeout = pulsarClient.timer().newTimeout(stat, statsIntervalSeconds, TimeUnit.SECONDS);
}
};
oldTime = System.nanoTime();
statTimeout = pulsarClient.timer().newTimeout(stat, statsIntervalSeconds, TimeUnit.SECONDS);
}
Timeout getStatTimeout() {
return statTimeout;
}
void updateNumMsgsSent(long numMsgs, long totalMsgsSize) {
numMsgsSent.add(numMsgs);
numBytesSent.add(totalMsgsSize);
}
void incrementSendFailed() {
numSendFailed.increment();
}
void incrementSendFailed(long numMsgs) {
numSendFailed.add(numMsgs);
}
void incrementNumAcksReceived(long latencyNs) {
numAcksReceived.increment();
synchronized (ds) {
ds.update(TimeUnit.NANOSECONDS.toMicros(latencyNs));
}
}
void reset() {
numMsgsSent.reset();
numBytesSent.reset();
numSendFailed.reset();
numAcksReceived.reset();
totalMsgsSent.reset();
totalBytesSent.reset();
totalSendFailed.reset();
totalAcksReceived.reset();
}
void updateCumulativeStats(ProducerStats stats) {
if (stats == null) {
return;
}
numMsgsSent.add(stats.numMsgsSent.longValue());
numBytesSent.add(stats.numBytesSent.longValue());
numSendFailed.add(stats.numSendFailed.longValue());
numAcksReceived.add(stats.numAcksReceived.longValue());
totalMsgsSent.add(stats.numMsgsSent.longValue());
totalBytesSent.add(stats.numBytesSent.longValue());
totalSendFailed.add(stats.numSendFailed.longValue());
totalAcksReceived.add(stats.numAcksReceived.longValue());
}
public long getNumMsgsSent() {
return numMsgsSent.longValue();
}
public long getNumBytesSent() {
return numBytesSent.longValue();
}
public long getNumSendFailed() {
return numSendFailed.longValue();
}
public long getNumAcksReceived() {
return numAcksReceived.longValue();
}
public long getTotalMsgsSent() {
return totalMsgsSent.longValue();
}
public long getTotalBytesSent() {
return totalBytesSent.longValue();
}
public long getTotalSendFailed() {
return totalSendFailed.longValue();
}
public long getTotalAcksReceived() {
return totalAcksReceived.longValue();
}
private static final Logger log = LoggerFactory.getLogger(ProducerStats.class);
}