package contention.benchmark;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* This class provides statistics collecting functionality for STMs
* Adapted from Yoav Cohen's code as used in Deuce.
*
* @author Vincent Gramoli
*
*/
public class Statistics {
public enum AbortType {
ALL, BETWEEN_SUCCESSIVE_READS, BETWEEN_READ_AND_WRITE, EXTEND_ON_READ, WRITE_AFTER_READ, LOCKED_ON_WRITE, LOCKED_BEFORE_READ, LOCKED_BEFORE_ELASTIC_READ, LOCKED_ON_READ, INVALID_COMMIT, INVALID_SNAPSHOT
}
public enum CommitType {
ALL, READONLY, ELASTIC, UPDATE
}
public enum ValidationType {
ALL, READ_BACK, READ_SET, COMMIT_BACK, COMMIT_RS
}
private static final Map<Integer, Statistics> statsMap = new HashMap<Integer, Statistics>();
private static int[] txAttemptsHistBins;
static {
String histStr = System.getProperty("txDurationHist");
if (histStr == null) {
histStr = "1,2,5,20";
}
String[] histStrArr = histStr.split(",");
txAttemptsHistBins = new int[histStrArr.length + 1];
for (int i = 0; i < txAttemptsHistBins.length - 1; i++) {
String limitStr = histStrArr[i];
int limit = Integer.valueOf(limitStr);
txAttemptsHistBins[i] = limit;
}
txAttemptsHistBins[txAttemptsHistBins.length - 1] = 100;
}
public static int getTotalStarts() {
int totalStarts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalStarts += statistics.starts;
}
return totalStarts;
}
public static int getTotalCommits() {
int totalCommits = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalCommits += statistics.commits;
}
return totalCommits;
}
public static void resetAll() {
statsMap.clear();
}
public static int getNumCommitsElastic() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getCommits(CommitType.ELASTIC);
}
return total;
}
public static int getNumCommitsUpdate() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getCommits(CommitType.UPDATE);
}
return total;
}
public static int getNumCommitsReadOnly() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getCommits(CommitType.READONLY);
}
return total;
}
public static int getTotalValidations() {
int totalAborts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalAborts += statistics.getValidation(ValidationType.ALL);
}
return totalAborts;
}
public static int getReadBackValidations() {
int totalAborts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalAborts += statistics.getValidation(ValidationType.READ_BACK);
}
return totalAborts;
}
public static int getReadSetValidations() {
int totalAborts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalAborts += statistics.getValidation(ValidationType.READ_SET);
}
return totalAborts;
}
public static int getCommitBackValidations() {
int totalAborts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalAborts += statistics.getValidation(ValidationType.COMMIT_BACK);
}
return totalAborts;
}
public static int getCommitRSValidations() {
int totalAborts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalAborts += statistics.getValidation(ValidationType.COMMIT_RS);
}
return totalAborts;
}
public static int getTotalAborts() {
int totalAborts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
totalAborts += statistics.getAborts(AbortType.ALL);
}
return totalAborts;
}
public static int getNumAbortsBetweenSuccessiveReads() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.BETWEEN_SUCCESSIVE_READS);
}
return total;
}
public static int getNumAbortsBetweenReadAndWrite() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.BETWEEN_READ_AND_WRITE);
}
return total;
}
public static int getNumAbortsExtendOnRead() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.EXTEND_ON_READ);
}
return total;
}
public static int getNumAbortsWriteAfterRead() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.WRITE_AFTER_READ);
}
return total;
}
public static int getNumAbortsLockedOnWrite() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.LOCKED_ON_WRITE);
}
return total;
}
public static int getNumAbortsLockedOnRead() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.LOCKED_ON_READ);
}
return total;
}
public static int getNumAbortsLockedBeforeRead() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.LOCKED_BEFORE_READ);
}
return total;
}
public static int getNumAbortsLockedBeforeElasticRead() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.LOCKED_BEFORE_ELASTIC_READ);
}
return total;
}
public static int getNumAbortsInvalidCommit() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.INVALID_COMMIT);
}
return total;
}
public static int getNumAbortsInvalidSnapshot() {
int total = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
total += statistics.getAborts(AbortType.INVALID_SNAPSHOT);
}
return total;
}
// public static String getAbortPer() {
// int abortsSum = 0, totalStarts = 0;
// for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
// Statistics statistics = entry.getValue();
// abortsSum += statistics.getAborts(AbortType.ALL);
// }
public static String getTotalAbortsPercentage(AbortType type) {
int abortsSum = 0, totalStarts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
if (type.equals(AbortType.ALL)) {
abortsSum += statistics.getAborts(AbortType.ALL);
} else if (type.equals(AbortType.BETWEEN_SUCCESSIVE_READS)) {
abortsSum += statistics
.getAborts(AbortType.BETWEEN_SUCCESSIVE_READS);
} else if (type.equals(AbortType.BETWEEN_READ_AND_WRITE)) {
abortsSum += statistics
.getAborts(AbortType.BETWEEN_READ_AND_WRITE);
} else if (type.equals(AbortType.EXTEND_ON_READ)) {
abortsSum += statistics.getAborts(AbortType.EXTEND_ON_READ);
} else if (type.equals(AbortType.WRITE_AFTER_READ)) {
abortsSum += statistics.getAborts(AbortType.WRITE_AFTER_READ);
} else if (type.equals(AbortType.LOCKED_ON_WRITE)) {
abortsSum += statistics.getAborts(AbortType.LOCKED_ON_WRITE);
} else if (type.equals(AbortType.LOCKED_ON_READ)) {
abortsSum += statistics.getAborts(AbortType.LOCKED_ON_READ);
} else if (type.equals(AbortType.LOCKED_BEFORE_READ)) {
abortsSum += statistics.getAborts(AbortType.LOCKED_BEFORE_READ);
} else if (type.equals(AbortType.LOCKED_BEFORE_ELASTIC_READ)) {
abortsSum += statistics
.getAborts(AbortType.LOCKED_BEFORE_ELASTIC_READ);
} else if (type.equals(AbortType.INVALID_COMMIT)) {
abortsSum += statistics.getAborts(AbortType.INVALID_COMMIT);
} else if (type.equals(AbortType.INVALID_SNAPSHOT)) {
abortsSum += statistics.getAborts(AbortType.INVALID_SNAPSHOT);
}
totalStarts += statistics.starts;
}
double result = percentage(abortsSum, totalStarts);
return formatDouble(result);
}
public static String getTotalCommitsPercentage(CommitType type) {
int commitsSum = 0, totalStarts = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
if (type.equals(CommitType.ALL)) {
commitsSum += statistics.getCommits(CommitType.ALL);
} else if (type.equals(CommitType.READONLY)) {
commitsSum += statistics.getCommits(CommitType.READONLY);
} else if (type.equals(CommitType.ELASTIC)) {
commitsSum += statistics.getCommits(CommitType.ELASTIC);
} else if (type.equals(CommitType.UPDATE)) {
commitsSum += statistics.getCommits(CommitType.UPDATE);
}
totalStarts += statistics.starts;
}
double result = percentage(commitsSum, totalStarts);
return formatDouble(result);
}
public static String getTotalAvgReadSetSize() {
double sumOfAverages = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sumOfAverages += statistics.getAvgReadSetSizeOnCommit();
}
return formatDouble(average(sumOfAverages, statsMap.size()));
}
public static String getTotalAvgWriteSetSize() {
double sumOfAverages = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sumOfAverages += statistics.getAvgWriteSetSizeOnCommit();
}
return formatDouble(average(sumOfAverages, statsMap.size()));
}
public static String getTotalAvgCommitingTxTime() {
int sum = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sum += statistics.txDurationSum;
}
double avgTimeInMS = 1000 * average(sum, getTotalCommits());
return formatDouble(avgTimeInMS);
}
public static int getSumCommitingTxTime() {
int sum = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sum += statistics.txDurationSum;
}
return sum * 1000;
}
public static int getTotalReadsInROPrefix() {
int sum = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sum += statistics.readsInROPrefix;
}
return sum;
}
public static int getTotalElasticReads() {
int sum = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sum += statistics.elasticReads;
}
return sum;
}
public static String getDetailedStatistics() {
StringBuilder sb = new StringBuilder("");
addStat("Starts ", getTotalStarts(), sb);
addStat("Commits ", getTotalCommits(), sb);
addStat("Commits (%) ",
getTotalCommitsPercentage(CommitType.ALL), sb);
addStat(" |--read only (%) ",
getTotalCommitsPercentage(CommitType.READONLY), sb);
addStat(" |--elastic (%) ",
getTotalCommitsPercentage(CommitType.ELASTIC), sb);
addStat(" |--update (%) ",
getTotalCommitsPercentage(CommitType.UPDATE), sb);
addStat("Aborts ", getTotalAborts(), sb);
addStat("Aborts (%) ",
getTotalAbortsPercentage(AbortType.ALL), sb);
addStat(" |--between succ. reads (%) ",
getTotalAbortsPercentage(AbortType.BETWEEN_SUCCESSIVE_READS),
sb);
addStat(" |--between read & write (%) ",
getTotalAbortsPercentage(AbortType.BETWEEN_READ_AND_WRITE), sb);
addStat(" |--extend upon read (%) ",
getTotalAbortsPercentage(AbortType.EXTEND_ON_READ), sb);
addStat(" |--write after read (%) ",
getTotalAbortsPercentage(AbortType.WRITE_AFTER_READ), sb);
addStat(" |--locked on write (%) ",
getTotalAbortsPercentage(AbortType.LOCKED_ON_WRITE), sb);
addStat(" |--locked on read (%) ",
getTotalAbortsPercentage(AbortType.LOCKED_ON_READ), sb);
addStat(" |--locked before rread (%) ",
getTotalAbortsPercentage(AbortType.LOCKED_BEFORE_READ), sb);
addStat(" |--locked before eread (%) ",
getTotalAbortsPercentage(AbortType.LOCKED_BEFORE_ELASTIC_READ),
sb);
addStat(" |--invalid commit (%) ",
getTotalAbortsPercentage(AbortType.INVALID_COMMIT), sb);
addStat(" |--invalid snapshot (%) ",
getTotalAbortsPercentage(AbortType.INVALID_SNAPSHOT), sb);
addStat("Avg. read set size ", getTotalAvgReadSetSize(), sb);
addStat("Avg. write set size ", getTotalAvgWriteSetSize(), sb);
addStat("Avg. commiting TX time (us) ", getTotalAvgCommitingTxTime(),
sb);
addStat("Number of elastic reads ", getTotalElasticReads(), sb);
addStat("Number of reads in RO prefix ", getTotalReadsInROPrefix(), sb);
// getTxAttemptsHistogram(sb);
return sb.toString();
}
/*
* private static void getTxAttemptsHistogram(StringBuilder sb) { int[]
* totalTxDurationHistCounters = new int[txAttemptsHistBins.length]; for
* (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) { Statistics
* statistics = entry.getValue(); for (int i=0;
* i<totalTxDurationHistCounters.length; i++) {
* totalTxDurationHistCounters[i] += statistics.txAttemptsHistCounters[i]; }
* } int totalCommits = getTotalCommits(); double[] totalTxDurationHist =
* new double[txAttemptsHistBins.length]; for (int i=0;
* i<totalTxDurationHist.length; i++) { totalTxDurationHist[i] =
* percentage(totalTxDurationHistCounters[i], totalCommits); }
*
* sb.append("\n Transaction Attempts Histogram:\n"); for (int i=0;
* i<totalTxDurationHist.length; i++) { sb.append(" # attempts <= ");
* sb.append(txAttemptsHistBins[i]); sb.append(": ");
* sb.append(formatDouble(totalTxDurationHist[i])); sb.append("%\n"); } }
*/
private static void addStat(String title, Object value, StringBuilder sb) {
sb.append(" ");
sb.append(title);
sb.append(": ");
sb.append(value);
sb.append("\n");
}
private int starts = 0;
private int abortsBetweenSuccessiveReads = 0;
private int abortsBetweenReadAndWrite = 0;
private int abortsExtendOnRead = 0;
private int abortsWriteAfterRead = 0;
private int abortsLockedOnWrite = 0;
private int abortsLockedBeforeRead = 0;
private int abortsLockedBeforeElasticRead = 0;
private int abortsLockedOnRead = 0;
private int abortsInvalidCommit = 0;
private int abortsInvalidSnapshot = 0;
private int commitReadOnly = 0;
private int commitElastic = 0;
private int commitUpdate = 0;
private int commits = 0;
private int aborts = 0;
private long startTime;
private long txDurationSum = 0;
private int readsInROPrefix = 0;
private int elasticReads = 0;
private int[] txAttemptsHistCounters;
private int readSetSizeOnCommitSum = 0;
private int readSetSizeOnCommitCounter = 0;
private int writeSetSizeOnCommitSum = 0;
private int writeSetSizeOnCommitCounter = 0;
private int validations = 0;
private int validationReadBack = 0;
private int validationReadSet = 0;
private int validationCommitBack = 0;
private int validationCommitRS = 0;
public Statistics(int threadId) {
statsMap.put(threadId, this);
txAttemptsHistCounters = new int[txAttemptsHistBins.length];
}
public void reportTxStart() {
this.starts++;
this.startTime = System.currentTimeMillis();
}
public void reportValidation(ValidationType type) {
if (type.equals(ValidationType.READ_BACK)) {
this.validationReadBack++;
} else if (type.equals(ValidationType.READ_SET)) {
this.validationReadSet++;
} else if (type.equals(ValidationType.COMMIT_BACK)) {
this.validationCommitBack++;
} else if (type.equals(ValidationType.COMMIT_RS)) {
this.validationCommitRS++;
}
this.validations++;
}
public int getValidation(ValidationType type) {
if (type.equals(ValidationType.ALL)) {
return this.validations;
} else if (type.equals(ValidationType.READ_BACK)) {
return this.validationReadBack;
} else if (type.equals(ValidationType.READ_SET)) {
return this.validationReadSet;
} else if (type.equals(ValidationType.COMMIT_BACK)) {
return this.validationCommitBack;
} else if (type.equals(ValidationType.COMMIT_RS)) {
return this.validationCommitRS;
}
throw new IllegalArgumentException("ValidationType unrecognized "
+ type);
}
public void reportAbort(AbortType type) {
startTime = -1;
if (type.equals(AbortType.BETWEEN_SUCCESSIVE_READS)) {
this.abortsBetweenSuccessiveReads++;
} else if (type.equals(AbortType.BETWEEN_READ_AND_WRITE)) {
this.abortsBetweenReadAndWrite++;
} else if (type.equals(AbortType.EXTEND_ON_READ)) {
this.abortsExtendOnRead++;
} else if (type.equals(AbortType.WRITE_AFTER_READ)) {
this.abortsWriteAfterRead++;
} else if (type.equals(AbortType.LOCKED_ON_WRITE)) {
this.abortsLockedOnWrite++;
} else if (type.equals(AbortType.LOCKED_BEFORE_READ)) {
this.abortsLockedBeforeRead++;
} else if (type.equals(AbortType.LOCKED_BEFORE_ELASTIC_READ)) {
this.abortsLockedBeforeElasticRead++;
} else if (type.equals(AbortType.LOCKED_ON_READ)) {
this.abortsLockedOnRead++;
} else if (type.equals(AbortType.INVALID_COMMIT)) {
this.abortsInvalidCommit++;
} else if (type.equals(AbortType.INVALID_SNAPSHOT)) {
this.abortsInvalidSnapshot++;
}
this.aborts++;
}
public void reportCommit(CommitType type) {
startTime = -1;
if (type.equals(CommitType.READONLY)) {
this.commitReadOnly++;
} else if (type.equals(CommitType.ELASTIC)) {
this.commitElastic++;
} else if (type.equals(CommitType.UPDATE)) {
this.commitUpdate++;
}
this.commits++;
}
public void reportCommit(int attempts) {
this.commits++;
if (startTime != -1) {
long txDuration = System.currentTimeMillis() - startTime;
txDurationSum += txDuration;
for (int i = 0; i < txAttemptsHistBins.length; i++) {
int limit = txAttemptsHistBins[i];
if (attempts <= limit) {
txAttemptsHistCounters[i]++;
break;
}
}
}
}
public void reportElasticReads() {
elasticReads += 1;
}
public void reportInROPrefix() {
readsInROPrefix += 1;
}
public int getAborts(AbortType type) {
if (type.equals(AbortType.ALL)) {
return this.aborts;
} else if (type.equals(AbortType.BETWEEN_SUCCESSIVE_READS)) {
return abortsBetweenSuccessiveReads;
} else if (type.equals(AbortType.BETWEEN_READ_AND_WRITE)) {
return abortsBetweenReadAndWrite;
} else if (type.equals(AbortType.EXTEND_ON_READ)) {
return abortsExtendOnRead;
} else if (type.equals(AbortType.WRITE_AFTER_READ)) {
return abortsWriteAfterRead;
} else if (type.equals(AbortType.LOCKED_ON_WRITE)) {
return abortsLockedOnWrite;
} else if (type.equals(AbortType.LOCKED_BEFORE_ELASTIC_READ)) {
return abortsLockedBeforeElasticRead;
} else if (type.equals(AbortType.LOCKED_BEFORE_READ)) {
return abortsLockedBeforeRead;
} else if (type.equals(AbortType.LOCKED_ON_READ)) {
return abortsLockedOnRead;
} else if (type.equals(AbortType.INVALID_COMMIT)) {
return abortsInvalidCommit;
} else if (type.equals(AbortType.INVALID_SNAPSHOT)) {
return abortsInvalidSnapshot;
}
throw new IllegalArgumentException("AbortType unrecognized " + type);
}
public int getCommits(CommitType type) {
if (type.equals(AbortType.ALL)) {
return this.commits;
} else if (type.equals(CommitType.READONLY)) {
return commitReadOnly;
} else if (type.equals(CommitType.ELASTIC)) {
return commitElastic;
} else if (type.equals(CommitType.UPDATE)) {
return commitUpdate;
}
throw new IllegalArgumentException("CommitType unrecognized " + type);
}
public void reportOnCommit(int rsSize, int wsSize) {
readSetSizeOnCommitSum += rsSize;
readSetSizeOnCommitCounter++;
if (wsSize > 0) {
// We only consider writing transactions
// when we calculate average write-set size
writeSetSizeOnCommitSum += wsSize;
writeSetSizeOnCommitCounter++;
}
}
public void resetSizeStats() {
readSetSizeOnCommitSum = 0;
readSetSizeOnCommitCounter = 0;
writeSetSizeOnCommitSum = 0;
writeSetSizeOnCommitCounter = 0;
}
public static double getSumReadSetSize() {
double sumOfAverages = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sumOfAverages += statistics.getAvgReadSetSizeOnCommit();
}
return sumOfAverages;
}
public static double getSumWriteSetSize() {
double sumOfAverages = 0;
for (Map.Entry<Integer, Statistics> entry : statsMap.entrySet()) {
Statistics statistics = entry.getValue();
sumOfAverages += statistics.getAvgWriteSetSizeOnCommit();
}
return sumOfAverages;
}
public static int getStatSize() {
return statsMap.size();
}
public double getAvgReadSetSizeOnCommit() {
return average(readSetSizeOnCommitSum, readSetSizeOnCommitCounter);
}
public double getAvgWriteSetSizeOnCommit() {
return average(writeSetSizeOnCommitSum, writeSetSizeOnCommitCounter);
}
private static double percentage(int p, int w) {
return (double) p / w * 100;
}
private static double average(double sum, double n) {
return sum / n;
}
private static String formatDouble(double result) {
Formatter formatter = new Formatter(Locale.US);
return formatter.format("%.2f", result).out().toString();
}
}