private CompletableFuture<Void> visitSecondPhaseCommand(
      TxInvocationContext ctx,
      TransactionBoundaryCommand command,
      boolean commit,
      ExtendedStatistic duration,
      ExtendedStatistic counter)
      throws Throwable {
    GlobalTransaction globalTransaction = command.getGlobalTransaction();
    if (trace) {
      log.tracef(
          "Visit 2nd phase command %s. Is it local? %s. Transaction is %s",
          command, ctx.isOriginLocal(), globalTransaction.globalId());
    }
    long start = timeService.time();
    return ctx.onReturn(
        (rCtx, rCommand, rv, throwable) -> {
          if (throwable != null) {
            throw throwable;
          }

          long end = timeService.time();
          updateTime(duration, counter, start, end, globalTransaction, rCtx.isOriginLocal());
          cacheStatisticManager.setTransactionOutcome(
              commit, globalTransaction, rCtx.isOriginLocal());
          cacheStatisticManager.terminateTransaction(globalTransaction, true, true);
          return null;
        });
  }
 private void updateTime(
     ExtendedStatistic duration,
     ExtendedStatistic counter,
     long initTime,
     long endTime,
     GlobalTransaction globalTransaction,
     boolean local) {
   cacheStatisticManager.add(
       duration,
       timeService.timeDuration(initTime, endTime, NANOSECONDS),
       globalTransaction,
       local);
   cacheStatisticManager.increment(counter, globalTransaction, local);
 }
 @ManagedOperation(
     description = "K-th percentile of remote write transactions execution time",
     displayName = "K-th Percentile Remote Write Transactions")
 public double getPercentileRemoteWriteTransaction(
     @Parameter(name = "percentile") int percentile) {
   return cacheStatisticManager.getPercentile(WR_REMOTE_EXECUTION, percentile);
 }
 @ManagedOperation(
     description = "K-th percentile of local write transactions execution time",
     displayName = "K-th Percentile Local Write Transactions")
 public double getPercentileLocalRWriteTransaction(
     @Parameter(name = "percentile") int percentile) {
   return cacheStatisticManager.getPercentile(WR_LOCAL_EXECUTION, percentile);
 }
  private void processWriteException(
      InvocationContext ctx, GlobalTransaction globalTransaction, Throwable throwable) {
    if (!ctx.isOriginLocal()) return;

    ExtendedStatistic stat = null;
    if (throwable instanceof TimeoutException) {
      if (isLockTimeout(((TimeoutException) throwable))) {
        stat = NUM_LOCK_FAILED_TIMEOUT;
      }
    } else if (throwable instanceof DeadlockDetectedException) {
      stat = NUM_LOCK_FAILED_DEADLOCK;
    } else if (throwable instanceof WriteSkewException) {
      stat = NUM_WRITE_SKEW;
    } else if (throwable instanceof RemoteException) {
      Throwable cause = throwable.getCause();
      while (cause != null) {
        if (cause instanceof TimeoutException) {
          stat = NUM_LOCK_FAILED_TIMEOUT;
          break;
        } else if (cause instanceof DeadlockDetectedException) {
          stat = NUM_LOCK_FAILED_DEADLOCK;
          break;
        } else if (cause instanceof WriteSkewException) {
          stat = NUM_WRITE_SKEW;
          break;
        }
        cause = cause.getCause();
      }
    }
    if (stat != null) {
      cacheStatisticManager.increment(stat, globalTransaction, true);
    }
  }
 // public to be used by the tests
 public double getAttribute(ExtendedStatistic statistic) {
   try {
     return cacheStatisticManager.getAttribute(statistic);
   } catch (ExtendedStatisticNotFoundException e) {
     log.unableToGetStatistic(statistic, e);
   }
   return 0;
 }
 @Override
 public CompletableFuture<Void> visitGetAllCommand(InvocationContext ctx, GetAllCommand command)
     throws Throwable {
   if (trace) {
     log.tracef(
         "Visit Get All Command %s. Is it in transaction scope? %s. Is it local? %s",
         command, ctx.isInTxScope(), ctx.isOriginLocal());
   }
   if (!ctx.isInTxScope()) {
     return ctx.continueInvocation();
   }
   long start = timeService.time();
   return ctx.onReturn(
       (rCtx, rCommand, rv, throwable) -> {
         if (throwable != null) {
           throw throwable;
         }
         long end = timeService.time();
         initStatsIfNecessary(rCtx);
         int numRemote = 0;
         Collection<?> keys = ((GetAllCommand) rCommand).getKeys();
         for (Object key : keys) {
           if (isRemote(key)) numRemote++;
         }
         // TODO: tbh this seems like it doesn't work properly for statistics as each
         // one will have the duration of all the time for all gets...  Maybe do an average
         // instead ?  Either way this isn't very indicative
         if (numRemote > 0) {
           cacheStatisticManager.add(
               NUM_REMOTE_GET, numRemote, getGlobalTransaction(rCtx), rCtx.isOriginLocal());
           cacheStatisticManager.add(
               REMOTE_GET_EXECUTION,
               timeService.timeDuration(start, end, NANOSECONDS),
               getGlobalTransaction(rCtx),
               rCtx.isOriginLocal());
         }
         cacheStatisticManager.add(
             ALL_GET_EXECUTION,
             timeService.timeDuration(start, end, NANOSECONDS),
             getGlobalTransaction(rCtx),
             rCtx.isOriginLocal());
         cacheStatisticManager.add(
             NUM_GET, keys.size(), getGlobalTransaction(rCtx), rCtx.isOriginLocal());
         return null;
       });
 }
  @Override
  public CompletableFuture<Void> visitPrepareCommand(
      TxInvocationContext ctx, PrepareCommand command) throws Throwable {
    GlobalTransaction globalTransaction = command.getGlobalTransaction();
    if (trace) {
      log.tracef(
          "Visit Prepare command %s. Is it local?. Transaction is %s",
          command, ctx.isOriginLocal(), globalTransaction.globalId());
    }
    initStatsIfNecessary(ctx);
    cacheStatisticManager.onPrepareCommand(globalTransaction, ctx.isOriginLocal());
    if (command.hasModifications()) {
      cacheStatisticManager.markAsWriteTransaction(globalTransaction, ctx.isOriginLocal());
    }

    long start = timeService.time();
    return ctx.onReturn(
        (rCtx, rCommand, rv, throwable) -> {
          if (throwable != null) {
            processWriteException(rCtx, globalTransaction, throwable);
          } else {
            long end = timeService.time();
            updateTime(
                PREPARE_EXECUTION_TIME,
                NUM_PREPARE_COMMAND,
                start,
                end,
                globalTransaction,
                rCtx.isOriginLocal());
          }

          if (((PrepareCommand) rCommand).isOnePhaseCommit()) {
            boolean local = rCtx.isOriginLocal();
            boolean success = throwable == null;
            cacheStatisticManager.setTransactionOutcome(
                success, globalTransaction, rCtx.isOriginLocal());
            cacheStatisticManager.terminateTransaction(globalTransaction, local, !local);
          }
          return null;
        });
  }
 @Override
 public CompletableFuture<Void> visitGetKeyValueCommand(
     InvocationContext ctx, GetKeyValueCommand command) throws Throwable {
   if (trace) {
     log.tracef(
         "Visit Get Key Value command %s. Is it in transaction scope? %s. Is it local? %s",
         command, ctx.isInTxScope(), ctx.isOriginLocal());
   }
   if (!ctx.isInTxScope()) {
     return ctx.continueInvocation();
   }
   long start = timeService.time();
   return ctx.onReturn(
       (rCtx, rCommand, rv, throwable) -> {
         if (throwable != null) {
           throw throwable;
         }
         long end = timeService.time();
         initStatsIfNecessary(rCtx);
         Object key = ((GetKeyValueCommand) rCommand).getKey();
         if (isRemote(key)) {
           cacheStatisticManager.increment(
               NUM_REMOTE_GET, getGlobalTransaction(rCtx), rCtx.isOriginLocal());
           cacheStatisticManager.add(
               REMOTE_GET_EXECUTION,
               timeService.timeDuration(start, end, NANOSECONDS),
               getGlobalTransaction(rCtx),
               rCtx.isOriginLocal());
         }
         cacheStatisticManager.add(
             ALL_GET_EXECUTION,
             timeService.timeDuration(start, end, NANOSECONDS),
             getGlobalTransaction(rCtx),
             rCtx.isOriginLocal());
         cacheStatisticManager.increment(
             NUM_GET, getGlobalTransaction(rCtx), rCtx.isOriginLocal());
         return null;
       });
 }
  private CompletableFuture<Void> visitWriteCommand(
      InvocationContext ctx, WriteCommand command, Object key) throws Throwable {
    if (trace) {
      log.tracef(
          "Visit write command %s. Is it in transaction scope? %s. Is it local? %s",
          command, ctx.isInTxScope(), ctx.isOriginLocal());
    }
    if (!ctx.isInTxScope()) {
      return ctx.continueInvocation();
    }
    long start = timeService.time();
    return ctx.onReturn(
        (rCtx, rCommand, rv, throwable) -> {
          long end = timeService.time();
          initStatsIfNecessary(rCtx);

          if (throwable != null) {
            processWriteException(rCtx, getGlobalTransaction(rCtx), throwable);
          } else {
            if (isRemote(key)) {
              cacheStatisticManager.add(
                  REMOTE_PUT_EXECUTION,
                  timeService.timeDuration(start, end, NANOSECONDS),
                  getGlobalTransaction(rCtx),
                  rCtx.isOriginLocal());
              cacheStatisticManager.increment(
                  NUM_REMOTE_PUT, getGlobalTransaction(rCtx), rCtx.isOriginLocal());
            }
          }

          cacheStatisticManager.increment(
              NUM_PUT, getGlobalTransaction(rCtx), rCtx.isOriginLocal());
          cacheStatisticManager.markAsWriteTransaction(
              getGlobalTransaction(rCtx), rCtx.isOriginLocal());
          return null;
        });
  }
 @ManagedOperation(
     description = "Dumps the current cache statistic values to a file",
     displayName = "Dump cache Statistics to file")
 public final void dumpStatisticToFile(@Parameter(description = "The file path") String filePath)
     throws IOException {
   PrintStream stream = null;
   try {
     stream = new PrintStream(new File(filePath));
     cacheStatisticManager.dumpCacheStatisticsTo(stream);
   } finally {
     if (stream != null) {
       stream.close();
     }
   }
 }
 private void initStatsIfNecessary(InvocationContext ctx) {
   if (ctx.isInTxScope())
     cacheStatisticManager.beginTransaction(getGlobalTransaction(ctx), ctx.isOriginLocal());
 }
 @ManagedOperation(
     description = "Dumps the current cache statistic values to System.out",
     displayName = "Dump Cache Statistics to System.out")
 public final void dumpStatisticsToSystemOut() {
   cacheStatisticManager.dumpCacheStatisticsTo(System.out);
 }
 @ManagedOperation(
     description = "Dumps the current cache statistic values",
     displayName = "Dump Cache Statistics")
 public final String dumpStatistics() {
   return cacheStatisticManager.dumpCacheStatistics();
 }
 @ManagedOperation(
     description = "Reset all the statistics collected",
     displayName = "Reset All Statistics")
 public void resetStatistics() {
   cacheStatisticManager.reset();
 }