package org.infinispan.stats.wrappers;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.infinispan.stats.container.ExtendedStatistic.ASYNC_COMPLETE_NOTIFY_TIME;
import static org.infinispan.stats.container.ExtendedStatistic.CLUSTERED_GET_COMMAND_SIZE;
import static org.infinispan.stats.container.ExtendedStatistic.COMMIT_COMMAND_SIZE;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_ASYNC_COMPLETE_NOTIFY;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_NODES_COMMIT;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_NODES_COMPLETE_NOTIFY;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_NODES_GET;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_NODES_PREPARE;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_NODES_ROLLBACK;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_SYNC_COMMIT;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_SYNC_GET;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_SYNC_PREPARE;
import static org.infinispan.stats.container.ExtendedStatistic.NUM_SYNC_ROLLBACK;
import static org.infinispan.stats.container.ExtendedStatistic.PREPARE_COMMAND_SIZE;
import static org.infinispan.stats.container.ExtendedStatistic.SYNC_COMMIT_TIME;
import static org.infinispan.stats.container.ExtendedStatistic.SYNC_GET_TIME;
import static org.infinispan.stats.container.ExtendedStatistic.SYNC_PREPARE_TIME;
import static org.infinispan.stats.container.ExtendedStatistic.SYNC_ROLLBACK_TIME;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import org.infinispan.commands.ReplicableCommand;
import org.infinispan.commands.remote.ClusteredGetCommand;
import org.infinispan.commands.remote.recovery.TxCompletionNotificationCommand;
import org.infinispan.commands.tx.CommitCommand;
import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.commands.tx.RollbackCommand;
import org.infinispan.remoting.inboundhandler.DeliverOrder;
import org.infinispan.remoting.responses.Response;
import org.infinispan.remoting.rpc.ResponseMode;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.rpc.RpcOptions;
import org.infinispan.remoting.rpc.RpcOptionsBuilder;
import org.infinispan.remoting.transport.Address;
import org.infinispan.remoting.transport.Transport;
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
import org.infinispan.stats.CacheStatisticManager;
import org.infinispan.stats.container.ExtendedStatistic;
import org.infinispan.stats.logging.Log;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.TimeService;
import org.infinispan.util.logging.LogFactory;
/**
* Takes statistics about the RPC invocations.
*
* @author Roberto Palmieri
* @author Sebastiano Peluso
* @author Diego Didona
* @author Pedro Ruivo
* @since 6.0
*/
public class ExtendedStatisticRpcManager implements RpcManager {
private static final Log log = LogFactory.getLog(ExtendedStatisticRpcManager.class, Log.class);
private static final boolean trace = log.isTraceEnabled();
private final RpcManager actual;
private final CacheStatisticManager cacheStatisticManager;
private final org.infinispan.commons.marshall.StreamingMarshaller marshaller;
private final TimeService timeService;
public ExtendedStatisticRpcManager(RpcManager actual, CacheStatisticManager cacheStatisticManager,
TimeService timeService) {
this.actual = actual;
this.cacheStatisticManager = cacheStatisticManager;
Transport t = actual.getTransport();
if (t instanceof JGroupsTransport) {
marshaller = ((JGroupsTransport) t).getCommandAwareRpcDispatcher().getIspnMarshaller();
} else {
marshaller = null;
}
this.timeService = timeService;
}
@Override
public CompletableFuture<Map<Address, Response>> invokeRemotelyAsync(Collection<Address> recipients,
ReplicableCommand rpc,
RpcOptions options) {
long start = timeService.time();
CompletableFuture<Map<Address, Response>> future = actual.invokeRemotelyAsync(recipients, rpc, options);
updateStats(rpc, options.responseMode().isSynchronous(), timeService.timeDuration(start, NANOSECONDS), recipients);
return future;
}
@Override
public Map<Address, Response> invokeRemotely(Collection<Address> recipients, ReplicableCommand rpc, RpcOptions options) {
long start = timeService.time();
Map<Address, Response> responseMap = actual.invokeRemotely(recipients, rpc, options);
updateStats(rpc, options.responseMode().isSynchronous(), timeService.timeDuration(start, NANOSECONDS), recipients);
return responseMap;
}
@Override
public Map<Address, Response> invokeRemotely(Map<Address, ReplicableCommand> rpcs, RpcOptions options) {
long start = timeService.time();
Map<Address, Response> responseMap = actual.invokeRemotely(rpcs, options);
for (Entry<Address, ReplicableCommand> entry : rpcs.entrySet()) {
// TODO: This is giving a time for all rpcs combined...
updateStats(entry.getValue(), options.responseMode().isSynchronous(),
timeService.timeDuration(start, NANOSECONDS), Collections.singleton(entry.getKey()));
}
return responseMap;
}
@Override
public void sendTo(Address destination, ReplicableCommand command, DeliverOrder deliverOrder) {
actual.sendTo(destination, command, deliverOrder);
}
@Override
public void sendToMany(Collection<Address> destinations, ReplicableCommand command, DeliverOrder deliverOrder) {
actual.sendToMany(destinations, command, deliverOrder);
}
@Override
public RpcOptionsBuilder getRpcOptionsBuilder(ResponseMode responseMode) {
return actual.getRpcOptionsBuilder(responseMode);
}
@Override
public RpcOptionsBuilder getRpcOptionsBuilder(ResponseMode responseMode, DeliverOrder deliverOrder) {
return actual.getRpcOptionsBuilder(responseMode, deliverOrder);
}
@Override
public RpcOptions getDefaultRpcOptions(boolean sync) {
return actual.getDefaultRpcOptions(sync);
}
@Override
public RpcOptions getDefaultRpcOptions(boolean sync, DeliverOrder deliverOrder) {
return actual.getDefaultRpcOptions(sync, deliverOrder);
}
@Override
public Transport getTransport() {
return actual.getTransport();
}
@Override
public List<Address> getMembers() {
return actual.getMembers();
}
@Override
public Address getAddress() {
return actual.getAddress();
}
@Override
public int getTopologyId() {
return actual.getTopologyId();
}
private void updateStats(ReplicableCommand command, boolean sync, long duration, Collection<Address> recipients) {
ExtendedStatistic durationStat;
ExtendedStatistic counterStat;
ExtendedStatistic recipientSizeStat;
ExtendedStatistic commandSizeStat = null;
GlobalTransaction globalTransaction;
if (command instanceof PrepareCommand) {
durationStat = SYNC_PREPARE_TIME;
counterStat = NUM_SYNC_PREPARE;
recipientSizeStat = NUM_NODES_PREPARE;
commandSizeStat = PREPARE_COMMAND_SIZE;
globalTransaction = ((PrepareCommand) command).getGlobalTransaction();
} else if (command instanceof RollbackCommand) {
durationStat = SYNC_ROLLBACK_TIME;
counterStat = NUM_SYNC_ROLLBACK;
recipientSizeStat = NUM_NODES_ROLLBACK;
globalTransaction = ((RollbackCommand) command).getGlobalTransaction();
} else if (command instanceof CommitCommand) {
durationStat = SYNC_COMMIT_TIME;
counterStat = NUM_SYNC_COMMIT;
recipientSizeStat = NUM_NODES_COMMIT;
commandSizeStat = COMMIT_COMMAND_SIZE;
globalTransaction = ((CommitCommand) command).getGlobalTransaction();
} else if (command instanceof TxCompletionNotificationCommand) {
durationStat = ASYNC_COMPLETE_NOTIFY_TIME;
counterStat = NUM_ASYNC_COMPLETE_NOTIFY;
recipientSizeStat = NUM_NODES_COMPLETE_NOTIFY;
globalTransaction = ((TxCompletionNotificationCommand) command).getGlobalTransaction();
} else if (command instanceof ClusteredGetCommand && !((ClusteredGetCommand) command).isWrite()) {
durationStat = SYNC_GET_TIME;
counterStat = NUM_SYNC_GET;
recipientSizeStat = NUM_NODES_GET;
commandSizeStat = CLUSTERED_GET_COMMAND_SIZE;
globalTransaction = null;
} else {
if (trace) {
log.tracef("Does not update stats for command %s. The command is not needed", command);
}
return;
}
if (trace) {
log.tracef("Update stats for command %s. Is sync? %s. Duration stat is %s, counter stats is %s, " +
"recipient size stat is %s", command, sync, durationStat, counterStat, recipientSizeStat);
}
cacheStatisticManager.add(durationStat, duration, globalTransaction, true);
cacheStatisticManager.increment(counterStat, globalTransaction, true);
cacheStatisticManager.add(recipientSizeStat, recipientListSize(recipients), globalTransaction, true);
if (commandSizeStat != null) {
cacheStatisticManager.add(commandSizeStat, getCommandSize(command), globalTransaction, true);
}
}
private int recipientListSize(Collection<Address> recipients) {
return recipients == null ? actual.getTransport().getMembers().size() : recipients.size();
}
private int getCommandSize(ReplicableCommand command) {
try {
CountingDataOutput dataOutput = new CountingDataOutput();
ObjectOutput byteOutput = marshaller.startObjectOutput(dataOutput, false, 0);
marshaller.objectToObjectStream(command, byteOutput);
marshaller.finishObjectOutput(byteOutput);
return dataOutput.getCount();
} catch (Exception e) {
return 0;
}
}
private static class CountingDataOutput extends OutputStream {
private int count;
private CountingDataOutput() {
this.count = 0;
}
public int getCount() {
return count;
}
@Override
public void write(int b) throws IOException {
count++;
}
@Override
public void write(byte[] b) throws IOException {
count += b.length;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
count += len;
}
@Override
public void flush() throws IOException {
}
@Override
public void close() throws IOException {
}
}
}