@Override
 protected List<FDBMetric.Value<Long>> decodeValues(byte[] bytes) {
   Tuple2 tuple = Tuple2.fromBytes(bytes);
   int nvalues = tuple.size() / 2;
   List<FDBMetric.Value<Long>> result = new ArrayList<>(nvalues);
   long time = 0, value = 0;
   for (int i = 0; i < nvalues; i++) {
     time += tuple.getLong(i * 2);
     value += tuple.getLong(i * 2 + 1);
     result.add(new FDBMetric.Value<Long>(time, value));
   }
   return result;
 }
 @Override
 protected List<FDBMetric.Value<Boolean>> decodeValues(byte[] bytes) {
   Tuple2 tuple = Tuple2.fromBytes(bytes);
   int nvalues = tuple.size();
   List<FDBMetric.Value<Boolean>> result = new ArrayList<>(nvalues);
   long tvalue = 0;
   for (int i = 0; i < nvalues; i++) {
     tvalue += tuple.getLong(i);
     long time = tvalue >>> 1;
     boolean value = ((tvalue & 1) != 0);
     result.add(new FDBMetric.Value<Boolean>(time, value));
   }
   return result;
 }
 protected static Tuple2 tupleFrom(Object... keys) {
   if (BINARY_STRINGS) {
     try {
       for (int i = 0; i < keys.length; i++) {
         if (keys[i] instanceof String) {
           keys[i] = ((String) keys[i]).getBytes("UTF-8");
         }
       }
     } catch (UnsupportedEncodingException ex) {
       throw new AkibanInternalException("Error encoding binary string", ex);
     }
   }
   return Tuple2.from(keys);
 }
 protected Map<List<String>, byte[]> readConf(Transaction tr) {
   tr.options().setAccessSystemKeys();
   byte[] confKey = confSubspace.getKey();
   List<KeyValue> kvs = tr.getRange(Range.startsWith(confKey)).asList().get();
   Map<List<String>, byte[]> result = new HashMap<>();
   for (KeyValue kv : kvs) {
     byte[] tupleBytes = new byte[kv.getKey().length - confKey.length];
     System.arraycopy(kv.getKey(), confKey.length, tupleBytes, 0, tupleBytes.length);
     // TODO: It's a shame that there isn't a fromBytes with index offets.
     Tuple2 tuple = Tuple2.fromBytes(tupleBytes);
     List<String> list = new ArrayList<>(tuple.size());
     for (int i = 0; i < tuple.size(); i++) {
       if (BINARY_STRINGS) {
         try {
           list.add(new String(tuple.getBytes(i), "UTF-8"));
         } catch (UnsupportedEncodingException ex) {
           throw new AkibanInternalException("Error decoding binary string", ex);
         }
       } else {
         list.add(tuple.getString(i));
       }
     }
     result.put(list, kv.getValue());
   }
   // Initiate a watch (from this same transaction) for changes to the key
   // used to signal configuration changes.
   tr.watch(confChangesSubspace.getKey())
       .onReady(
           new Runnable() {
             @Override
             public void run() {
               confChanged = true;
               notifyBackground();
             }
           });
   return result;
 }
 @Override
 protected byte[] encodeValue(MetricLevel<Long> onto) {
   return Tuple2.from(changeTime - onto.lastTime, get() - onto.lastValue).pack();
 }
 @Override
 protected byte[] encodeValue() {
   // A timestamp and long value are stored as successive integers.
   return Tuple2.from(changeTime, get()).pack();
 }
 @Override
 protected byte[] encodeValue(MetricLevel<Boolean> onto) {
   return Tuple2.from(combine(changeTime, get()) - combine(onto.lastTime, onto.lastValue))
       .pack();
 }
 @Override
 protected byte[] encodeValue() {
   return Tuple2.from(combine(changeTime, get())).pack();
 }