From b1d4ac629d53343b0ea05e19e028302e84ed77e6 Mon Sep 17 00:00:00 2001 From: Chris Larsen <clarsen@yahoo-inc.com> Date: Wed, 2 Aug 2017 23:13:45 -0700 Subject: [PATCH] [core] Add 'basicdb.count' as a config option to track and print the number of unique combinations of tables, keys and fields per database operation. This is useful for tracking distributions. Signed-off-by: Chris Larsen <clarsen@yahoo-inc.com> --- .../src/main/java/com/yahoo/ycsb/BasicDB.java | 127 +++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/yahoo/ycsb/BasicDB.java b/core/src/main/java/com/yahoo/ycsb/BasicDB.java index dfb88a0c..9e483f21 100644 --- a/core/src/main/java/com/yahoo/ycsb/BasicDB.java +++ b/core/src/main/java/com/yahoo/ycsb/BasicDB.java @@ -18,6 +18,7 @@ package com.yahoo.ycsb; import java.util.*; +import java.util.Map.Entry; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; @@ -26,6 +27,9 @@ import java.util.concurrent.locks.LockSupport; * Basic DB that just prints out the requested operations, instead of doing them against a database. */ public class BasicDB extends DB { + public static final String COUNT = "basicdb.count"; + public static final String COUNT_DEFAULT = "false"; + public static final String VERBOSE = "basicdb.verbose"; public static final String VERBOSE_DEFAULT = "true"; @@ -35,9 +39,18 @@ public class BasicDB extends DB { public static final String RANDOMIZE_DELAY = "basicdb.randomizedelay"; public static final String RANDOMIZE_DELAY_DEFAULT = "true"; + protected static final Object MUTEX = new Object(); + protected static int counter = 0; + protected static Map<Integer, Integer> reads; + protected static Map<Integer, Integer> scans; + protected static Map<Integer, Integer> updates; + protected static Map<Integer, Integer> inserts; + protected static Map<Integer, Integer> deletes; + private boolean verbose; private boolean randomizedelay; private int todelay; + protected boolean count; public BasicDB() { todelay = 0; @@ -66,11 +79,11 @@ public class BasicDB extends DB { * Initialize any state for this DB. * Called once per DB instance; there is one DB instance per client thread. */ - @SuppressWarnings("unchecked") public void init() { verbose = Boolean.parseBoolean(getProperties().getProperty(VERBOSE, VERBOSE_DEFAULT)); todelay = Integer.parseInt(getProperties().getProperty(SIMULATE_DELAY, SIMULATE_DELAY_DEFAULT)); randomizedelay = Boolean.parseBoolean(getProperties().getProperty(RANDOMIZE_DELAY, RANDOMIZE_DELAY_DEFAULT)); + count = Boolean.parseBoolean(getProperties().getProperty(COUNT, COUNT_DEFAULT)); if (verbose) { synchronized (System.out) { System.out.println("***************** properties *****************"); @@ -84,6 +97,17 @@ public class BasicDB extends DB { System.out.println("**********************************************"); } } + + synchronized (MUTEX) { + if (counter == 0 && count) { + reads = new HashMap<Integer, Integer>(); + scans = new HashMap<Integer, Integer>(); + updates = new HashMap<Integer, Integer>(); + inserts = new HashMap<Integer, Integer>(); + deletes = new HashMap<Integer, Integer>(); + } + counter++; + } } protected static final ThreadLocal<StringBuilder> TL_STRING_BUILDER = new ThreadLocal<StringBuilder>() { @@ -126,6 +150,10 @@ public class BasicDB extends DB { System.out.println(sb); } + if (count) { + incCounter(reads, hash(table, key, fields)); + } + return Status.OK; } @@ -158,6 +186,10 @@ public class BasicDB extends DB { sb.append("]"); System.out.println(sb); } + + if (count) { + incCounter(scans, hash(table, startkey, fields)); + } return Status.OK; } @@ -186,6 +218,10 @@ public class BasicDB extends DB { System.out.println(sb); } + if (count) { + incCounter(updates, hash(table, key, values)); + } + return Status.OK; } @@ -214,6 +250,10 @@ public class BasicDB extends DB { System.out.println(sb); } + if (count) { + incCounter(inserts, hash(table, key, values)); + } + return Status.OK; } @@ -234,9 +274,94 @@ public class BasicDB extends DB { System.out.println(sb); } + if (count) { + incCounter(deletes, (table + key).hashCode()); + } + return Status.OK; } + @Override + public void cleanup() { + synchronized (MUTEX) { + int countDown = --counter; + if (count && countDown < 1) { + // TODO - would be nice to call something like: + // Measurements.getMeasurements().oneOffMeasurement("READS", "Uniques", reads.size()); + System.out.println("[READS], Uniques, " + reads.size()); + System.out.println("[SCANS], Uniques, " + scans.size()); + System.out.println("[UPDATES], Uniques, " + updates.size()); + System.out.println("[INSERTS], Uniques, " + inserts.size()); + System.out.println("[DELETES], Uniques, " + deletes.size()); + } + } + } + + /** + * Increments the count on the hash in the map. + * @param map A non-null map to sync and use for incrementing. + * @param hash A hash code to increment. + */ + protected void incCounter(final Map<Integer, Integer> map, final int hash) { + synchronized (map) { + Integer ctr = map.get(hash); + if (ctr == null) { + map.put(hash, 1); + } else { + map.put(hash, ctr + 1); + } + } + } + + /** + * Hashes the table, key and fields, sorting the fields first for a consistent + * hash. + * Note that this is expensive as we generate a copy of the fields and a string + * buffer to hash on. Hashing on the objects is problematic. + * @param table The user table. + * @param key The key read or scanned. + * @param fields The fields read or scanned. + * @return The hash code. + */ + protected int hash(final String table, final String key, final Set<String> fields) { + if (fields == null) { + return (table + key).hashCode(); + } + StringBuilder buf = getStringBuilder().append(table).append(key); + List<String> sorted = new ArrayList<String>(fields); + Collections.sort(sorted); + for (final String field : sorted) { + buf.append(field); + } + return buf.toString().hashCode(); + } + + /** + * Hashes the table, key and fields, sorting the fields first for a consistent + * hash. + * Note that this is expensive as we generate a copy of the fields and a string + * buffer to hash on. Hashing on the objects is problematic. + * @param table The user table. + * @param key The key read or scanned. + * @param values The values to hash on. + * @return The hash code. + */ + protected int hash(final String table, final String key, final Map<String, ByteIterator> values) { + if (values == null) { + return (table + key).hashCode(); + } + final TreeMap<String, ByteIterator> sorted = + new TreeMap<String, ByteIterator>(values); + + StringBuilder buf = getStringBuilder().append(table).append(key); + for (final Entry<String, ByteIterator> entry : sorted.entrySet()) { + entry.getValue().reset(); + buf.append(entry.getKey()) + .append(entry.getValue().toString()); + } + return buf.toString().hashCode(); + } + /** * Short test of BasicDB */ -- GitLab