geode-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dschnei...@apache.org
Subject [4/7] incubator-geode git commit: GEODE-78: Imported jvsdfx-mm from geode-1.0.0-SNAPSHOT-2.src.tar
Date Mon, 06 Jul 2015 21:46:46 GMT
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/jvsd/model/stats/StatArchiveFile.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/jvsd/model/stats/StatArchiveFile.java b/jvsdfx-mm/src/main/java/com/pivotal/jvsd/model/stats/StatArchiveFile.java
new file mode 100644
index 0000000..9bb3a80
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/jvsd/model/stats/StatArchiveFile.java
@@ -0,0 +1,3423 @@
+package com.pivotal.jvsd.model.stats;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+import java.util.zip.GZIPInputStream;
+
+import static com.pivotal.jvsd.model.stats.StatArchiveFormat.*;
+
+/**
+ * StatArchiveFile provides APIs to read statistic snapshots from an archive
+ * file.
+ */
+public class StatArchiveFile {
+
+  private static final String GEMFIRE_LOG_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS z";
+
+  protected static final NumberFormat nf = NumberFormat.getNumberInstance();
+
+  static {
+    nf.setMaximumFractionDigits(2);
+    nf.setGroupingUsed(false);
+  }
+
+  private InputStream is;
+  private DataInputStream dataIn;
+  private ValueFilter[] filters;
+  private final File archive;
+  private /* final */ int archiveVersion;
+  private /* final */ ArchiveInfo info;
+  private final boolean compressed;
+  private boolean updateOK;
+  private final boolean dump;
+  private boolean closed = false;
+  protected int resourceInstSize = 0;
+  protected ResourceInst[] resourceInstTable = null;
+  private ResourceType[] resourceTypeTable = null;
+  private final TimeStampSeries timeSeries = new TimeStampSeries();
+  private final DateFormat timeFormatter =
+      new SimpleDateFormat(GEMFIRE_LOG_FORMAT);
+  private final static int BUFFER_SIZE = 1024 * 1024;
+  private final ArrayList fileComboValues = new ArrayList();
+
+  public StatArchiveFile(String archiveName) throws IOException {
+    this(archiveName, false, null);
+  }
+
+  public StatArchiveFile(String archiveName, boolean dump,
+      ValueFilter[] filters)
+      throws IOException {
+    this.archive = new File(archiveName);
+    this.dump = dump;
+    this.compressed = archive.getPath().endsWith(".gz");
+    this.is = new FileInputStream(this.archive);
+    if (this.compressed) {
+      this.dataIn = new DataInputStream(
+          new BufferedInputStream(new GZIPInputStream(this.is, BUFFER_SIZE),
+              BUFFER_SIZE));
+    } else {
+      this.dataIn = new DataInputStream(
+          new BufferedInputStream(this.is, BUFFER_SIZE));
+    }
+    this.updateOK = this.dataIn.markSupported();
+    this.filters = createFilters(filters);
+
+    update(false);
+  }
+
+  private String getArchiveFileName() {
+    return archive.getAbsolutePath();
+  }
+
+  public List<ResourceInst> getResourceInstList() {
+    List<ResourceInst> result = new ArrayList<>();
+    for (ResourceInst r : resourceInstTable) {
+      if (r != null) {
+        result.add(r);
+      }
+    }
+    return result;
+  }
+
+  public List<ResourceType> getResourceTypeList() {
+    List<ResourceType> result = new ArrayList<>();
+    for (ResourceType t : resourceTypeTable) {
+      if (t != null) {
+        result.add(t);
+      }
+    }
+    return result;
+  }
+
+  private ValueFilter[] createFilters(ValueFilter[] allFilters) {
+    if (allFilters == null) {
+      return new ValueFilter[0];
+    }
+    ArrayList<ValueFilter> l = new ArrayList<>();
+    for (ValueFilter allFilter : allFilters) {
+      if (allFilter.archiveMatches(archive)) {
+        l.add(allFilter);
+      }
+    }
+    if (l.size() == allFilters.length) {
+      return allFilters;
+    } else {
+      ValueFilter[] result = new ValueFilter[l.size()];
+      return l.toArray(result);
+    }
+  }
+
+  void matchSpec(StatSpec spec, List matchedValues) {
+    if (spec.getCombineType() == StatSpec.FILE) {
+      // search for previous ComboValue
+      for (Object fileComboValue : this.fileComboValues) {
+        ComboValue v = (ComboValue) fileComboValue;
+        if (!spec.statMatches(v.getDescriptor().getName())) {
+          continue;
+        }
+        if (!spec.typeMatches(v.getType().getName())) {
+          continue;
+        }
+        ResourceInst[] resources = v.getResources();
+        for (ResourceInst resource : resources) {
+          if (!spec.instanceMatches(resource.getName(),
+              resource.getId())) {
+          }
+          // note: we already know the archive file matches
+        }
+        matchedValues.add(v);
+        return;
+      }
+      ArrayList l = new ArrayList();
+      matchSpec(new RawStatSpec(spec), l);
+      if (l.size() != 0) {
+        ComboValue cv = new ComboValue(l);
+        // save this in file's combo value list
+        this.fileComboValues.add(cv);
+        matchedValues.add(cv);
+      }
+    } else {
+      for (int instIdx = 0; instIdx < resourceInstSize; instIdx++) {
+        resourceInstTable[instIdx].matchSpec(spec, matchedValues);
+      }
+    }
+  }
+
+  /**
+   * Formats an archive timestamp in way consistent with GemFire log dates. It
+   * will also be formatted to reflect the time zone the archive was created
+   * in.
+   *
+   * @param ts The difference, measured in milliseconds, between the time marked
+   *           by this time stamp and midnight, January 1, 1970 UTC.
+   */
+  public String formatTimeMillis(long ts) {
+    synchronized (timeFormatter) {
+      return timeFormatter.format(new Date(ts));
+    }
+  }
+
+  /**
+   * sets the time zone this archive was written in.
+   */
+  void setTimeZone(TimeZone z) {
+    timeFormatter.setTimeZone(z);
+  }
+
+  /**
+   * Returns the time series for this archive.
+   */
+  TimeStampSeries getTimeStamps() {
+    return timeSeries;
+  }
+
+  /**
+   * Checks to see if the archive has changed since the StatArchiverReader
+   * instance was created or last updated. If the archive has additional samples
+   * then those are read the resource instances maintained by the reader are
+   * updated. <p>Once closed a reader can no longer be updated.
+   *
+   * @return true if update read some new data.
+   * @throws java.io.IOException if <code>archiveName</code> could not be opened
+   *                             read, or closed.
+   */
+  public boolean update(boolean doReset) throws IOException {
+    if (this.closed) {
+      return false;
+    }
+    if (!this.updateOK) {
+      throw new RuntimeException(
+          "Update of this type of file is not supported");
+    }
+
+    if (doReset) {
+      this.dataIn.reset();
+    }
+
+    int updateTokenCount = 0;
+    while (this.readToken()) {
+      updateTokenCount++;
+    }
+    return updateTokenCount != 0;
+  }
+
+  public void dump(PrintWriter stream) {
+    stream.print("archive=" + archive);
+    if (info != null) {
+      info.dump(stream);
+    }
+    for (ResourceType aResourceTypeTable : resourceTypeTable) {
+      if (aResourceTypeTable != null) {
+        aResourceTypeTable.dump(stream);
+      }
+    }
+    stream.print("time=");
+    timeSeries.dump(stream);
+    for (ResourceInst inst : resourceInstTable) {
+      if (inst != null) {
+        inst.dump(stream);
+      }
+    }
+  }
+
+  /**
+   * Closes the archive.
+   */
+  public void close() throws IOException {
+    if (!this.closed) {
+      this.closed = true;
+      this.is.close();
+      this.dataIn.close();
+      this.is = null;
+      this.dataIn = null;
+      int typeCount = 0;
+      if (this.resourceTypeTable != null) { // fix for bug 32320
+        for (int i = 0; i < this.resourceTypeTable.length; i++) {
+          if (this.resourceTypeTable[i] != null) {
+            if (this.resourceTypeTable[i].close()) {
+              this.resourceTypeTable[i] = null;
+            } else {
+              typeCount++;
+            }
+          }
+        }
+        ResourceType[] newTypeTable = new ResourceType[typeCount];
+        typeCount = 0;
+        for (ResourceType aResourceTypeTable : this.resourceTypeTable) {
+          if (aResourceTypeTable != null) {
+            newTypeTable[typeCount] = aResourceTypeTable;
+            typeCount++;
+          }
+        }
+        this.resourceTypeTable = newTypeTable;
+      }
+
+      if (this.resourceInstTable != null) { // fix for bug 32320
+        int instCount = 0;
+        for (int i = 0; i < this.resourceInstTable.length; i++) {
+          if (this.resourceInstTable[i] != null) {
+            if (this.resourceInstTable[i].close()) {
+              this.resourceInstTable[i] = null;
+            } else {
+              instCount++;
+            }
+          }
+        }
+        ResourceInst[] newInstTable = new ResourceInst[instCount];
+        instCount = 0;
+        for (ResourceInst aResourceInstTable : this.resourceInstTable) {
+          if (aResourceInstTable != null) {
+            newInstTable[instCount] = aResourceInstTable;
+            instCount++;
+          }
+        }
+        this.resourceInstTable = newInstTable;
+        this.resourceInstSize = instCount;
+      }
+      // optimize memory usage of timeSeries now that no more samples
+      this.timeSeries.shrink();
+      // filters are no longer needed since file will not be read from
+      this.filters = null;
+    }
+  }
+
+  /**
+   * Returns global information about the read archive. Returns null if no
+   * information is available.
+   */
+  public ArchiveInfo getArchiveInfo() {
+    return this.info;
+  }
+
+  private void readHeaderToken() throws IOException {
+    byte archiveVersion = dataIn.readByte();
+    long startTimeStamp = dataIn.readLong();
+    long systemId = dataIn.readLong();
+    long systemStartTimeStamp = dataIn.readLong();
+    int timeZoneOffset = dataIn.readInt();
+    String timeZoneName = dataIn.readUTF();
+    String systemDirectory = dataIn.readUTF();
+    String productVersion = dataIn.readUTF();
+    String os = dataIn.readUTF();
+    String machine = dataIn.readUTF();
+    if (archiveVersion <= 1) {
+      throw new RuntimeException("Archive version " + archiveVersion
+          + " is no longer supported");
+    }
+    if (archiveVersion > ARCHIVE_VERSION) {
+      throw new RuntimeException("Unsupported archive version "
+          + archiveVersion + ". The supported version is " + ARCHIVE_VERSION);
+    }
+    this.archiveVersion = archiveVersion;
+    this.info = new ArchiveInfo(this, archiveVersion,
+        startTimeStamp, systemStartTimeStamp,
+        timeZoneOffset, timeZoneName,
+        systemDirectory, systemId,
+        productVersion, os, machine);
+    // Clear all previously read types and instances
+    this.resourceInstSize = 0;
+    this.resourceInstTable = new ResourceInst[1024];
+    this.resourceTypeTable = new ResourceType[256];
+    timeSeries.setBase(startTimeStamp);
+    if (dump) {
+      info.dump(new PrintWriter(System.out));
+    }
+  }
+
+  boolean loadType(String typeName) {
+    // note we don't have instance data or descriptor data yet
+    if (filters == null || filters.length == 0) {
+      return true;
+    } else {
+      for (ValueFilter filter : filters) {
+        if (filter.typeMatches(typeName)) {
+          return true;
+        }
+      }
+      //System.out.println("DEBUG: don't load type=" + typeName);
+      return false;
+    }
+  }
+
+  boolean loadStatDescriptor(StatDescriptor stat, ResourceType type) {
+    // note we don't have instance data yet
+    if (!type.isLoaded()) {
+      return false;
+    }
+    if (filters == null || filters.length == 0) {
+      return true;
+    } else {
+      for (ValueFilter filter : filters) {
+        if (filter.statMatches(stat.getName())
+            && filter.typeMatches(type.getName())) {
+          return true;
+        }
+      }
+      //System.out.println("DEBUG: don't load stat=" + stat.getName());
+      stat.unload();
+      return false;
+    }
+  }
+
+  boolean loadInstance(String textId, long numericId, ResourceType type) {
+    if (!type.isLoaded()) {
+      return false;
+    }
+    if (filters == null || filters.length == 0) {
+      return true;
+    } else {
+      for (ValueFilter filter : filters) {
+        if (filter.typeMatches(type.getName())) {
+          if (filter.instanceMatches(textId, numericId)) {
+            StatDescriptor[] stats = type.getStats();
+            for (int j = 0; j < stats.length; j++) {
+              if (stats[j].isLoaded()) {
+                if (filter.statMatches(stats[j].getName())) {
+                  return true;
+                }
+              }
+            }
+          }
+        }
+      }
+      //System.out.println("DEBUG: don't load instance=" + textId);
+      //type.unload();
+      return false;
+    }
+  }
+
+  boolean loadStat(StatDescriptor stat, ResourceInst resource) {
+    ResourceType type = resource.getType();
+    if (!resource.isLoaded() || !type.isLoaded() || !stat.isLoaded()) {
+      return false;
+    }
+    if (filters == null || filters.length == 0) {
+      return true;
+    } else {
+      String textId = resource.getName();
+      long numericId = resource.getId();
+      for (ValueFilter filter : filters) {
+        if (filter.statMatches(stat.getName())
+            && filter.typeMatches(type.getName())
+            && filter.instanceMatches(textId, numericId)) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  private void readResourceTypeToken() throws IOException {
+    int resourceTypeId = dataIn.readInt();
+    String resourceTypeName = dataIn.readUTF();
+    String resourceTypeDesc = dataIn.readUTF();
+    int statCount = dataIn.readUnsignedShort();
+    while (resourceTypeId >= resourceTypeTable.length) {
+      ResourceType[] tmp = new ResourceType[resourceTypeTable.length + 128];
+      System.arraycopy(resourceTypeTable, 0, tmp, 0,
+          resourceTypeTable.length);
+      resourceTypeTable = tmp;
+    }
+    assert (resourceTypeTable[resourceTypeId] == null);
+
+    ResourceType rt;
+    if (loadType(resourceTypeName)) {
+      rt = new ResourceType(
+          resourceTypeName,
+          resourceTypeDesc,
+          statCount);
+      if (dump) {
+        System.out.println("ResourceType id=" + resourceTypeId
+            + " name=" + resourceTypeName
+            + " statCount=" + statCount
+            + " desc=" + resourceTypeDesc);
+      }
+    } else {
+      rt = new ResourceType(resourceTypeName, statCount);
+      if (dump) {
+        System.out.println("Not loading ResourceType id=" + resourceTypeId
+            + " name=" + resourceTypeName);
+      }
+    }
+    resourceTypeTable[resourceTypeId] = rt;
+    for (int i = 0; i < statCount; i++) {
+      String statName = dataIn.readUTF();
+      byte typeCode = dataIn.readByte();
+      boolean isCounter = dataIn.readBoolean();
+      boolean largerBetter = isCounter; // default
+      if (this.archiveVersion >= 4) {
+        largerBetter = dataIn.readBoolean();
+      }
+      String units = dataIn.readUTF();
+      String desc = dataIn.readUTF();
+      rt.addStatDescriptor(this, i, statName, isCounter, largerBetter,
+          typeCode, units, desc);
+      if (dump) {
+        System.out.println("  " + i + "=" + statName + " isCtr=" + isCounter
+            + " largerBetter=" + largerBetter
+            + " typeCode=" + typeCode + " units=" + units
+            + " desc=" + desc);
+      }
+    }
+  }
+
+  private void readResourceInstanceCreateToken(
+      boolean initialize) throws IOException {
+    int resourceInstId = dataIn.readInt();
+    String name = dataIn.readUTF();
+    long id = dataIn.readLong();
+    int resourceTypeId = dataIn.readInt();
+    while (resourceInstId >= resourceInstTable.length) {
+      ResourceInst[] tmp = new ResourceInst[resourceInstTable.length + 128];
+      System.arraycopy(resourceInstTable, 0, tmp, 0,
+          resourceInstTable.length);
+      resourceInstTable = tmp;
+    }
+    assert (resourceInstTable[resourceInstId] == null);
+    if ((resourceInstId + 1) > this.resourceInstSize) {
+      this.resourceInstSize = resourceInstId + 1;
+    }
+    boolean loadInstance = loadInstance(name, id,
+        resourceTypeTable[resourceTypeId]);
+    resourceInstTable[resourceInstId] = new ResourceInst(this,
+        name, id, resourceTypeTable[resourceTypeId], loadInstance);
+    if (dump) {
+      System.out.println(
+          (loadInstance ? "Loaded" : "Did not load") + " resource instance " + resourceInstId);
+      System.out.println(
+          "  name=" + name + " id=" + id + " typeId=" + resourceTypeId);
+    }
+    if (initialize) {
+      StatDescriptor[] stats = resourceInstTable[resourceInstId].getType().getStats();
+      for (int i = 0; i < stats.length; i++) {
+        long v;
+        switch (stats[i].getTypeCode()) {
+          case BOOLEAN_CODE:
+            v = dataIn.readByte();
+            break;
+          case BYTE_CODE:
+          case CHAR_CODE:
+            v = dataIn.readByte();
+            break;
+          case WCHAR_CODE:
+            v = dataIn.readUnsignedShort();
+            break;
+          case SHORT_CODE:
+            v = dataIn.readShort();
+            break;
+          case INT_CODE:
+          case FLOAT_CODE:
+          case LONG_CODE:
+          case DOUBLE_CODE:
+            v = readCompactValue();
+            break;
+          default:
+            throw new IOException("Unexpected typecode value"
+                + stats[i].getTypeCode());
+        }
+        resourceInstTable[resourceInstId].initialValue(i, v);
+      }
+    }
+  }
+
+  private void readResourceInstanceDeleteToken() throws IOException {
+    int resourceInstId = dataIn.readInt();
+    assert (resourceInstTable[resourceInstId] != null);
+    resourceInstTable[resourceInstId].makeInactive();
+    if (dump) {
+      System.out.println("Delete resource instance " + resourceInstId);
+    }
+  }
+
+  private int readResourceInstId() throws IOException {
+      /*
+        if (this.archiveVersion <= 1) {
+        return dataIn.readInt();
+        }
+      */
+    int token = dataIn.readUnsignedByte();
+    if (token <= MAX_BYTE_RESOURCE_INST_ID) {
+      return token;
+    } else if (token == ILLEGAL_RESOURCE_INST_ID_TOKEN) {
+      return ILLEGAL_RESOURCE_INST_ID;
+    } else if (token == SHORT_RESOURCE_INST_ID_TOKEN) {
+      return dataIn.readUnsignedShort();
+    } else { /* token == INT_RESOURCE_INST_ID_TOKEN */
+      return dataIn.readInt();
+    }
+  }
+
+  private int readTimeDelta() throws IOException {
+    int result = dataIn.readUnsignedShort();
+    if (result == INT_TIMESTAMP_TOKEN) {
+      result = dataIn.readInt();
+    }
+    return result;
+  }
+
+  private long readCompactValue() throws IOException {
+    long v = dataIn.readByte();
+    if (dump) {
+      // System.out.print("compactValue(byte1)=" + v);
+    }
+    if (v < MIN_1BYTE_COMPACT_VALUE) {
+      if (v == COMPACT_VALUE_2_TOKEN) {
+        v = dataIn.readShort();
+        if (dump) {
+          //System.out.print("compactValue(short)=" + v);
+        }
+      } else {
+        int bytesToRead = ((byte) v - COMPACT_VALUE_2_TOKEN) + 2;
+        v = dataIn.readByte(); // note the first byte will be a signed byte.
+        if (dump) {
+          System.out.print("compactValue(" + bytesToRead + ")=" + v);
+        }
+        bytesToRead--;
+        while (bytesToRead > 0) {
+          v <<= 8;
+          v |= dataIn.readUnsignedByte();
+          bytesToRead--;
+        }
+      }
+    }
+    return v;
+  }
+
+  private void readSampleToken() throws IOException {
+    int millisSinceLastSample = readTimeDelta();
+    if (dump) {
+      System.out.println("ts=" + millisSinceLastSample);
+    }
+    int resourceInstId = readResourceInstId();
+    while (resourceInstId != ILLEGAL_RESOURCE_INST_ID) {
+      if (dump) {
+        System.out.print("  instId=" + resourceInstId);
+      }
+      StatDescriptor[] stats = resourceInstTable[resourceInstId].getType().getStats();
+      int statOffset = dataIn.readUnsignedByte();
+      while (statOffset != ILLEGAL_STAT_OFFSET) {
+        long statDeltaBits;
+        switch (stats[statOffset].getTypeCode()) {
+          case BOOLEAN_CODE:
+            statDeltaBits = dataIn.readByte();
+            break;
+          case BYTE_CODE:
+          case CHAR_CODE:
+            statDeltaBits = dataIn.readByte();
+            break;
+          case WCHAR_CODE:
+            statDeltaBits = dataIn.readUnsignedShort();
+            break;
+          case SHORT_CODE:
+            statDeltaBits = dataIn.readShort();
+            break;
+          case INT_CODE:
+          case FLOAT_CODE:
+          case LONG_CODE:
+          case DOUBLE_CODE:
+            statDeltaBits = readCompactValue();
+            break;
+          default:
+            throw new IOException("Unexepcted typecode value "
+                + stats[statOffset].getTypeCode());
+        }
+        if (resourceInstTable[resourceInstId].addValueSample(statOffset,
+            statDeltaBits)) {
+          if (dump) {
+            System.out.print(" [" + statOffset + "]=" + statDeltaBits);
+          }
+        }
+        statOffset = dataIn.readUnsignedByte();
+      }
+      if (dump) {
+        System.out.println();
+      }
+      resourceInstId = readResourceInstId();
+    }
+    timeSeries.addTimeStamp(millisSinceLastSample);
+    for (ResourceInst inst : resourceInstTable) {
+      if (inst != null && inst.isActive()) {
+        inst.addTimeStamp();
+      }
+    }
+  }
+
+  /**
+   * Returns true if token read, false if eof.
+   */
+  private boolean readToken() throws IOException {
+    byte token;
+    try {
+      if (this.updateOK) {
+        this.dataIn.mark(BUFFER_SIZE);
+      }
+      token = this.dataIn.readByte();
+      switch (token) {
+        case HEADER_TOKEN:
+          readHeaderToken();
+          break;
+        case RESOURCE_TYPE_TOKEN:
+          readResourceTypeToken();
+          break;
+        case RESOURCE_INSTANCE_CREATE_TOKEN:
+          readResourceInstanceCreateToken(false);
+          break;
+        case RESOURCE_INSTANCE_INITIALIZE_TOKEN:
+          readResourceInstanceCreateToken(true);
+          break;
+        case RESOURCE_INSTANCE_DELETE_TOKEN:
+          readResourceInstanceDeleteToken();
+          break;
+        case SAMPLE_TOKEN:
+          readSampleToken();
+          break;
+        default:
+          throw new IOException("Unexpected token byte value " + token);
+      }
+      return true;
+    } catch (EOFException ignore) {
+      return false;
+    }
+  }
+
+  /**
+   * Returns the approximate amount of memory used to implement this object.
+   */
+  protected int getMemoryUsed() {
+    int result = 0;
+    for (int i = 0; i < resourceInstTable.length; i++) {
+      if (resourceInstTable[i] != null) {
+        result += resourceInstTable[i].getMemoryUsed();
+      }
+    }
+    return result;
+  }
+
+//  /**
+//   * Creates a StatArchiveReader that will read the named archive file.
+//   *
+//   * @param autoClose if its <code>true</code> then the reader will close input
+//   *                  files as soon as it finds their end.
+//   * @throws java.io.IOException if <code>archiveName</code> could not be opened
+//   *                             read, or closed.
+//   */
+//  public StatArchiveFile(String[] archiveNames, ValueFilter[] filters,
+//      boolean autoClose)
+//      throws IOException {
+//    this.archives = new StatArchiveFile[archiveNames.length];
+//    this.dump = Boolean.getBoolean("StatArchiveReader.dumpall");
+//    for (int i = 0; i < archiveNames.length; i++) {
+//      this.archives[i] = new StatArchiveFile(this, new File(archiveNames[i]),
+//          dump, filters);
+//    }
+//
+//    update(false, autoClose);
+//
+//    if (this.dump || Boolean.getBoolean("StatArchiveReader.dump")) {
+//      this.dump(new PrintWriter(System.out));
+//    }
+//  }
+//
+//  public StatArchiveFile(String[] archiveNames) throws IOException {
+//    this(archiveNames, null, true);
+//  }
+
+//  /**
+//   * Returns an array of stat values that match the specified spec. If nothing
+//   * matches then an empty array is returned.
+//   */
+//  public StatValue[] matchSpec(StatSpec spec) {
+//    if (spec.getCombineType() == StatSpec.GLOBAL) {
+//      StatValue[] allValues = matchSpec(new RawStatSpec(spec));
+//      if (allValues.length == 0) {
+//        return allValues;
+//      } else {
+//        ComboValue cv = new ComboValue(allValues);
+//        // need to save this in reader's combo value list
+//        return new StatValue[]{cv};
+//      }
+//    } else {
+//      List l = new ArrayList();
+//      com.pivotal.jvsd.model.stats.StatArchiveFile.StatArchiveFile[] archives = getArchives();
+//      for (int i = 0; i < archives.length; i++) {
+//        StatArchiveFile f = archives[i];
+//        if (spec.archiveMatches(f.getFile())) {
+//          f.matchSpec(spec, l);
+//        }
+//      }
+//      StatValue[] result = new StatValue[l.size()];
+//      return (StatValue[]) l.toArray(result);
+//    }
+//  }
+
+//  /**
+//   * Checks to see if any archives have changed since the StatArchiverReader
+//   * instance was created or last updated. If an archive has additional samples
+//   * then those are read the resource instances maintained by the reader are
+//   * updated. <p>Once closed a reader can no longer be updated.
+//   *
+//   * @return true if update read some new data.
+//   * @throws java.io.IOException if an archive could not be opened read, or
+//   *                             closed.
+//   */
+//  public boolean update() throws IOException {
+//    return update(true, false);
+//  }
+//
+//  private boolean update(boolean doReset,
+//      boolean autoClose) throws IOException {
+//    if (this.closed) {
+//      return false;
+//    }
+//    boolean result = false;
+//    com.pivotal.jvsd.model.stats.StatArchiveFile.StatArchiveFile[] archives = getArchives();
+//    for (int i = 0; i < archives.length; i++) {
+//      StatArchiveFile f = archives[i];
+//      if (f.update(doReset)) {
+//        result = true;
+//      }
+//      if (autoClose) {
+//        f.close();
+//      }
+//    }
+//    return result;
+//  }
+
+//  /**
+//   * Returns an unmodifiable list of all the {@link ResourceInst} this reader
+//   * contains.
+//   */
+//  public List getResourceInstList() {
+//    return new ResourceInstList();
+//  }
+//
+//  public StatArchiveFile[] getArchives() {
+//    return this.archives;
+//  }
+
+
+  protected static double bitsToDouble(int type, long bits) {
+    switch (type) {
+      case BOOLEAN_CODE:
+      case BYTE_CODE:
+      case CHAR_CODE:
+      case WCHAR_CODE:
+      case SHORT_CODE:
+      case INT_CODE:
+      case LONG_CODE:
+        return bits;
+      case FLOAT_CODE:
+        return Float.intBitsToFloat((int) bits);
+      case DOUBLE_CODE:
+        return Double.longBitsToDouble(bits);
+      default:
+        throw new RuntimeException("Unexpected typecode: " + type);
+    }
+  }
+
+  /**
+   * Wraps an instance of StatSpec but alwasy returns a combine type of NONE.
+   */
+  private static class RawStatSpec implements StatSpec {
+    private final StatSpec spec;
+
+    RawStatSpec(StatSpec wrappedSpec) {
+      this.spec = wrappedSpec;
+    }
+
+    public int getCombineType() {
+      return StatSpec.NONE;
+    }
+
+    public boolean typeMatches(String typeName) {
+      return spec.typeMatches(typeName);
+    }
+
+    public boolean statMatches(String statName) {
+      return spec.statMatches(statName);
+    }
+
+    public boolean instanceMatches(String textId, long numericId) {
+      return spec.instanceMatches(textId, numericId);
+    }
+
+    public boolean archiveMatches(File archive) {
+      return spec.archiveMatches(archive);
+    }
+  }
+
+//  private class ResourceInstList extends AbstractList {
+//    protected ResourceInstList() {
+//      // nothing needed.
+//    }
+//
+//    @Override
+//    public com.pivotal.jvsd.model.stats.StatArchiveFile.ResourceInst get(
+//        int idx) {
+//      int archiveIdx = 0;
+//      com.pivotal.jvsd.model.stats.StatArchiveFile.StatArchiveFile[] archives = getArchives();
+//      for (int i = 0; i < archives.length; i++) {
+//        StatArchiveFile f = archives[i];
+//        if (idx < (archiveIdx + f.resourceInstSize)) {
+//          return f.resourceInstTable[idx - archiveIdx];
+//        }
+//        archiveIdx += f.resourceInstSize;
+//      }
+//      return null;
+//    }
+//
+//    @Override
+//    public int size() {
+//      int result = 0;
+//      com.pivotal.jvsd.model.stats.StatArchiveFile.StatArchiveFile[] archives = getArchives();
+//      for (int i = 0; i < archives.length; i++) {
+//        result += archives[i].resourceInstSize;
+//      }
+//      return result;
+//    }
+//  }
+
+  /**
+   * Describes a single statistic.
+   */
+  public static class StatDescriptor {
+    private boolean loaded;
+    private String name;
+    private final int offset;
+    private final boolean isCounter;
+    private final boolean largerBetter;
+    private final byte typeCode;
+    private String units;
+    private String desc;
+
+    protected void dump(PrintWriter stream) {
+      stream.println("  " + name + ": type=" + typeCode + " offset=" + offset
+          + (isCounter ? " counter" : "")
+          + " units=" + units
+          + " largerBetter=" + largerBetter
+          + " desc=" + desc);
+    }
+
+    protected StatDescriptor(String name, int offset, boolean isCounter,
+        boolean largerBetter,
+        byte typeCode, String units, String desc) {
+      this.loaded = true;
+      this.name = name;
+      this.offset = offset;
+      this.isCounter = isCounter;
+      this.largerBetter = largerBetter;
+      this.typeCode = typeCode;
+      this.units = units;
+      this.desc = desc;
+    }
+
+    public boolean isLoaded() {
+      return this.loaded;
+    }
+
+    void unload() {
+      this.loaded = false;
+      this.name = null;
+      this.units = null;
+      this.desc = null;
+    }
+
+    /**
+     * Returns the type code of this statistic.
+     */
+    public byte getTypeCode() {
+      return this.typeCode;
+    }
+
+    /**
+     * Returns the name of this statistic.
+     */
+    public String getName() {
+      return this.name;
+    }
+
+    /**
+     * Returns true if this statistic's value will always increase.
+     */
+    public boolean isCounter() {
+      return this.isCounter;
+    }
+
+    /**
+     * Returns true if larger values indicate better performance.
+     */
+    public boolean isLargerBetter() {
+      return this.largerBetter;
+    }
+
+    /**
+     * Returns a string that describes the units this statistic measures.
+     */
+    public String getUnits() {
+      return this.units;
+    }
+
+    /**
+     * Returns a textual description of this statistic.
+     */
+    public String getDescription() {
+      return this.desc;
+    }
+
+    /**
+     * Returns the offset of this stat in its type.
+     */
+    public int getOffset() {
+      return this.offset;
+    }
+  }
+
+  public static interface StatValue {
+    /**
+     * {@link com.pivotal.jvsd.model.stats.StatArchiveFile.StatValue} filter
+     * that causes the statistic values to be unfiltered. This causes the raw
+     * values written to the archive to be used. <p>This is the default filter
+     * for non-counter statistics. To determine if a statistic is not a counter
+     * use {@link com.pivotal.jvsd.model.stats.StatArchiveFile.StatDescriptor#isCounter}.
+     */
+    public static final int FILTER_NONE = 0;
+    /**
+     * {@link com.pivotal.jvsd.model.stats.StatArchiveFile.StatValue} filter
+     * that causes the statistic values to be filtered to reflect how often they
+     * change per second.  Since the difference between two samples is used to
+     * calculate the value this causes the {@link com.pivotal.jvsd.model.stats.StatArchiveFile.StatValue}
+     * to have one less sample than {@link #FILTER_NONE}. The instance time
+     * stamp that does not have a per second value is the instance's first time
+     * stamp {@link com.pivotal.jvsd.model.stats.StatArchiveFile.ResourceInst#getFirstTimeMillis}.
+     * <p>This is the default filter for counter statistics. To determine if a
+     * statistic is a counter use {@link com.pivotal.jvsd.model.stats.StatArchiveFile.StatDescriptor#isCounter}.
+     */
+    public static final int FILTER_PERSEC = 1;
+    /**
+     * {@link com.pivotal.jvsd.model.stats.StatArchiveFile.StatValue} filter
+     * that causes the statistic values to be filtered to reflect how much they
+     * changed between sample periods.  Since the difference between two samples
+     * is used to calculate the value this causes the {@link
+     * com.pivotal.jvsd.model.stats.StatArchiveFile.StatValue} to have one less
+     * sample than {@link #FILTER_NONE}. The instance time stamp that does not
+     * have a per second value is the instance's first time stamp {@link
+     * com.pivotal.jvsd.model.stats.StatArchiveFile.ResourceInst#getFirstTimeMillis}.
+     */
+    public static final int FILTER_PERSAMPLE = 2;
+
+    /**
+     * Creates and returns a trimmed version of this stat value. Any samples
+     * taken before <code>startTime</code> and after <code>endTime</code> are
+     * discarded from the resulting value. Set a time parameter to
+     * <code>-1</code> to not trim that side.
+     */
+    public StatValue createTrimmed(long startTime, long endTime);
+
+    /**
+     * Returns true if value has data that has been trimmed off it by a start
+     * timestamp.
+     */
+    public boolean isTrimmedLeft();
+
+    /**
+     * Gets the {@link com.pivotal.jvsd.model.stats.StatArchiveFile.ResourceType
+     * type} of the resources that this value belongs to.
+     */
+    public ResourceType getType();
+
+    /**
+     * Gets the {@link com.pivotal.jvsd.model.stats.StatArchiveFile.ResourceInst
+     * resources} that this value belongs to.
+     */
+    public ResourceInst[] getResources();
+
+    /**
+     * Returns an array of timestamps for each unfiltered snapshot in this
+     * value. Each returned time stamp is the number of millis since midnight,
+     * Jan 1, 1970 UTC.
+     */
+    public long[] getRawAbsoluteTimeStamps();
+
+    /**
+     * Returns an array of timestamps for each unfiltered snapshot in this
+     * value. Each returned time stamp is the number of millis since midnight,
+     * Jan 1, 1970 UTC. The resolution is seconds.
+     */
+    public long[] getRawAbsoluteTimeStampsWithSecondRes();
+
+    /**
+     * Returns an array of doubles containing the unfiltered value of this
+     * statistic for each point in time that it was sampled.
+     */
+    public double[] getRawSnapshots();
+
+    /**
+     * Returns an array of doubles containing the filtered value of this
+     * statistic for each point in time that it was sampled.
+     */
+    public double[] getSnapshots();
+
+    /**
+     * Returns the number of samples taken of this statistic's value.
+     */
+    public int getSnapshotsSize();
+
+    /**
+     * Returns the smallest of all the samples taken of this statistic's value.
+     */
+    public double getSnapshotsMinimum();
+
+    /**
+     * Returns the largest of all the samples taken of this statistic's value.
+     */
+    public double getSnapshotsMaximum();
+
+    /**
+     * Returns the average of all the samples taken of this statistic's value.
+     */
+    public double getSnapshotsAverage();
+
+    /**
+     * Returns the standard deviation of all the samples taken of this
+     * statistic's value.
+     */
+    public double getSnapshotsStandardDeviation();
+
+    /**
+     * Returns the most recent value of all the samples taken of this
+     * statistic's value.
+     */
+    public double getSnapshotsMostRecent();
+
+    /**
+     * Returns true if sample whose value was different from previous values has
+     * been added to this StatValue since the last time this method was called.
+     */
+    public boolean hasValueChanged();
+
+    /**
+     * Returns the current filter used to calculate this statistic's values. It
+     * will be one of these values: <ul> <li> {@link #FILTER_NONE} <li> {@link
+     * #FILTER_PERSAMPLE} <li> {@link #FILTER_PERSEC} </ul>
+     */
+    public int getFilter();
+
+    /**
+     * Sets the current filter used to calculate this statistic's values. The
+     * default filter is {@link #FILTER_NONE} unless the statistic is a counter,
+     * {@link com.pivotal.jvsd.model.stats.StatArchiveFile.StatDescriptor#isCounter},
+     * in which case its {@link #FILTER_PERSEC}.
+     *
+     * @param filter It must be one of these values: <ul> <li> {@link
+     *               #FILTER_NONE} <li> {@link #FILTER_PERSAMPLE} <li> {@link
+     *               #FILTER_PERSEC} </ul>
+     * @throws IllegalArgumentException if <code>filter</code> is not a valid
+     *                                  filter constant.
+     */
+    public void setFilter(int filter);
+
+    /**
+     * Returns a description of this statistic.
+     */
+    public StatDescriptor getDescriptor();
+  }
+
+  protected static abstract class AbstractValue implements StatValue {
+    protected StatDescriptor descriptor;
+    protected int filter;
+
+    protected long startTime = -1;
+    protected long endTime = -1;
+
+    protected boolean statsValid = false;
+    protected int size;
+    protected double min;
+    protected double max;
+    protected double avg;
+    protected double stddev;
+    protected double mostRecent;
+
+    public void calcStats() {
+      if (!statsValid) {
+        getSnapshots();
+      }
+    }
+
+    public int getSnapshotsSize() {
+      calcStats();
+      return this.size;
+    }
+
+    public double getSnapshotsMinimum() {
+      calcStats();
+      return this.min;
+    }
+
+    public double getSnapshotsMaximum() {
+      calcStats();
+      return this.max;
+    }
+
+    public double getSnapshotsAverage() {
+      calcStats();
+      return this.avg;
+    }
+
+    public double getSnapshotsStandardDeviation() {
+      calcStats();
+      return this.stddev;
+    }
+
+    public double getSnapshotsMostRecent() {
+      calcStats();
+      return this.mostRecent;
+    }
+
+    public StatDescriptor getDescriptor() {
+      return this.descriptor;
+    }
+
+    public int getFilter() {
+      return this.filter;
+    }
+
+    public void setFilter(int filter) {
+      if (filter != this.filter) {
+        if (filter != FILTER_NONE
+            && filter != FILTER_PERSEC
+            && filter != FILTER_PERSAMPLE) {
+          throw new IllegalArgumentException("Filter value " + filter
+              + " must be " + FILTER_NONE + ", " + FILTER_PERSEC + " or "
+              + FILTER_PERSAMPLE);
+        }
+        this.filter = filter;
+        this.statsValid = false;
+      }
+    }
+
+    /**
+     * Calculates each stat given the result of calling getSnapshots
+     */
+    protected void calcStats(double[] values) {
+      if (statsValid) {
+        return;
+      }
+      size = values.length;
+      if (size == 0) {
+        min = 0.0;
+        max = 0.0;
+        avg = 0.0;
+        stddev = 0.0;
+        mostRecent = 0.0;
+      } else {
+        min = values[0];
+        max = values[0];
+        mostRecent = values[values.length - 1];
+        double total = values[0];
+        for (int i = 1; i < size; i++) {
+          total += values[i];
+          if (values[i] < min) {
+            min = values[i];
+          } else if (values[i] > max) {
+            max = values[i];
+          }
+        }
+        avg = total / size;
+        stddev = 0.0;
+        if (size > 1) {
+          for (int i = 0; i < size; i++) {
+            double dv = values[i] - avg;
+            stddev += (dv * dv);
+          }
+          stddev /= (size - 1);
+          stddev = Math.sqrt(stddev);
+        }
+      }
+      statsValid = true;
+    }
+
+    /**
+     * Returns a string representation of this object.
+     */
+    @Override
+    public String toString() {
+      calcStats();
+      StringBuilder result = new StringBuilder();
+      result.append(getDescriptor().getName());
+      String units = getDescriptor().getUnits();
+      if (units != null && units.length() > 0) {
+        result.append(' ').append(units);
+      }
+      if (filter == FILTER_PERSEC) {
+        result.append("/sec");
+      } else if (filter == FILTER_PERSAMPLE) {
+        result.append("/sample");
+      }
+      result.append(": samples=")
+          .append(getSnapshotsSize());
+      if (startTime != -1) {
+        result.append(" startTime=\"")
+            .append(new Date(startTime))
+            .append("\"");
+      }
+      if (endTime != -1) {
+        result.append(" endTime=\"")
+            .append(new Date(endTime))
+            .append("\"");
+      }
+      result.append(" min=")
+          .append(nf.format(min));
+      result.append(" max=")
+          .append(nf.format(max));
+      result.append(" average=")
+          .append(nf.format(avg));
+      result.append(" stddev=")
+          .append(nf.format(stddev));
+      result.append(" last=") // for bug 42532
+          .append(nf.format(mostRecent));
+      return result.toString();
+    }
+  }
+
+  /**
+   * A ComboValue is a value that is the logical combination of a set of other
+   * stat values. <p> For now ComboValue has a simple implementation that does
+   * not suppport updates.
+   */
+  private static class ComboValue extends AbstractValue {
+    private final ResourceType type;
+    private final StatValue[] values;
+
+    /**
+     * Creates a ComboValue by adding all the specified values together.
+     */
+    ComboValue(List valueList) {
+      this((StatValue[]) valueList.toArray(new StatValue[valueList.size()]));
+    }
+
+    /**
+     * Creates a ComboValue by adding all the specified values together.
+     */
+    ComboValue(StatValue[] values) {
+      this.values = values;
+      this.filter = this.values[0].getFilter();
+      String typeName = this.values[0].getType().getName();
+      String statName = this.values[0].getDescriptor().getName();
+      int bestTypeIdx = 0;
+      for (int i = 1; i < this.values.length; i++) {
+        if (this.filter != this.values[i].getFilter()) {
+          /* I'm not sure why this would happen.
+           * If it really can happen then this code should change
+           * the filter since a client has no way to select values
+           * based on the filter.
+           */
+          throw new IllegalArgumentException(
+              "Can't combine values with different filters");
+        }
+        if (!typeName.equals(this.values[i].getType().getName())) {
+          throw new IllegalArgumentException(
+              "Can't combine values with different types");
+        }
+        if (!statName.equals(this.values[i].getDescriptor().getName())) {
+          throw new IllegalArgumentException("Can't combine different stats");
+        }
+        if (this.values[i].getDescriptor().isCounter()) {
+          // its a counter which is not the default
+          if (!this.values[i].getDescriptor().isLargerBetter()) {
+            // this guy has non-defaults for both use him
+            bestTypeIdx = i;
+          } else if (this.values[bestTypeIdx].getDescriptor().isCounter()
+              == this.values[bestTypeIdx].getDescriptor().isLargerBetter()) {
+            // as long as we haven't already found a guy with defaults
+            // make this guy the best type
+            bestTypeIdx = i;
+          }
+        } else {
+          // its a gauge, see if it has a non-default largerBetter
+          if (this.values[i].getDescriptor().isLargerBetter()) {
+            // as long as we haven't already found a guy with defaults
+            if (this.values[bestTypeIdx].getDescriptor().isCounter()
+                == this.values[bestTypeIdx].getDescriptor().isLargerBetter()) {
+              // make this guy the best type
+              bestTypeIdx = i;
+            }
+          }
+        }
+      }
+      this.type = this.values[bestTypeIdx].getType();
+      this.descriptor = this.values[bestTypeIdx].getDescriptor();
+    }
+
+    private ComboValue(ComboValue original, long startTime, long endTime) {
+      this.startTime = startTime;
+      this.endTime = endTime;
+      this.type = original.getType();
+      this.descriptor = original.getDescriptor();
+      this.filter = original.getFilter();
+      this.values = new StatValue[original.values.length];
+      for (int i = 0; i < this.values.length; i++) {
+        this.values[i] = original.values[i].createTrimmed(startTime, endTime);
+      }
+    }
+
+    public StatValue createTrimmed(long startTime, long endTime) {
+      if (startTime == this.startTime && endTime == this.endTime) {
+        return this;
+      } else {
+        return new ComboValue(this, startTime, endTime);
+      }
+    }
+
+    public ResourceType getType() {
+      return this.type;
+    }
+
+    public ResourceInst[] getResources() {
+      Set set = new HashSet();
+      for (StatValue value : values) {
+        set.addAll(Arrays.asList(value.getResources()));
+      }
+      ResourceInst[] result = new ResourceInst[set.size()];
+      return (ResourceInst[]) set.toArray(result);
+    }
+
+    public boolean hasValueChanged() {
+      return true;
+    }
+
+    public static boolean closeEnough(long v1, long v2, long delta) {
+      return (v1 == v2) || ((Math.abs(v1 - v2) / 2) <= delta);
+    }
+
+    /**
+     * Return true if v is closer to prev. Return false if v is closer to next.
+     * Return true if v is the same distance from both.
+     */
+    public static boolean closer(long v, long prev, long next) {
+      return Math.abs(v - prev) <= Math.abs(v - next);
+    }
+
+
+    /**
+     * Return true if the current ts must be inserted instead of being mapped to
+     * the tsAtInsertPoint
+     */
+    private static boolean mustInsert(int nextIdx, long[] valueTimeStamps,
+        long tsAtInsertPoint) {
+      return (nextIdx < valueTimeStamps.length)
+          && (valueTimeStamps[nextIdx] <= tsAtInsertPoint);
+    }
+
+    public long[] getRawAbsoluteTimeStampsWithSecondRes() {
+      return getRawAbsoluteTimeStamps();
+    }
+
+    public long[] getRawAbsoluteTimeStamps() {
+      if (values.length == 0) {
+        return new long[0];
+      }
+//       for (int i=0; i < values.length; i++) {
+//         System.out.println("DEBUG: inst# " + i + " has "
+//                            + values[i].getRawAbsoluteTimeStamps().length
+//                            + " timestamps");
+//       }
+      long[] valueTimeStamps = values[0].getRawAbsoluteTimeStamps();
+      int tsCount = valueTimeStamps.length + 1;
+      long[] ourTimeStamps = new long[(tsCount * 2) + 1];
+      System.arraycopy(valueTimeStamps, 0, ourTimeStamps, 0,
+          valueTimeStamps.length);
+      // Note we add a MAX sample to make the insert logic simple
+      ourTimeStamps[valueTimeStamps.length] = Long.MAX_VALUE;
+      for (int i = 1; i < values.length; i++) {
+        valueTimeStamps = values[i].getRawAbsoluteTimeStamps();
+        if (valueTimeStamps.length == 0) {
+          continue;
+        }
+        int ourIdx = 0;
+        int j = 0;
+        long tsToInsert = valueTimeStamps[0] - 1000; // default to 1 second
+        if (valueTimeStamps.length > 1) {
+          tsToInsert = valueTimeStamps[0] - (valueTimeStamps[1] - valueTimeStamps[0]);
+        }
+        // tsToInsert is now initialized to a value that we can pretend
+        // was the previous timestamp inserted.
+        while (j < valueTimeStamps.length) {
+          long timeDelta = (valueTimeStamps[j] - tsToInsert) / 2;
+          tsToInsert = valueTimeStamps[j];
+          long tsAtInsertPoint = ourTimeStamps[ourIdx];
+          while (tsToInsert > tsAtInsertPoint
+              && !closeEnough(tsToInsert, tsAtInsertPoint, timeDelta)) {
+//             System.out.println("DEBUG: skipping " + ourIdx + " because it was not closeEnough");
+            ourIdx++;
+            tsAtInsertPoint = ourTimeStamps[ourIdx];
+          }
+          if (closeEnough(tsToInsert, tsAtInsertPoint, timeDelta)
+              && !mustInsert(j + 1, valueTimeStamps, tsAtInsertPoint)) {
+            // It was already in our list so just go to the next one
+            j++;
+            ourIdx++; // never put the next timestamp at this index
+            while (!closer(tsToInsert, ourTimeStamps[ourIdx - 1],
+                ourTimeStamps[ourIdx])
+                && !mustInsert(j, valueTimeStamps, ourTimeStamps[ourIdx])) {
+//               System.out.println("DEBUG: skipping mergeTs[" + (ourIdx-1) + "]="
+//                                  + tsAtInsertPoint + " because it was closer to the next one");
+              ourIdx++; // it is closer to the next one so skip forward on more
+            }
+          } else {
+            // its not in our list so add it
+            int endRunIdx = j + 1;
+            while (endRunIdx < valueTimeStamps.length
+                && valueTimeStamps[endRunIdx] < tsAtInsertPoint
+                && !closeEnough(valueTimeStamps[endRunIdx], tsAtInsertPoint,
+                timeDelta)) {
+              endRunIdx++;
+            }
+            int numToCopy = endRunIdx - j;
+//             System.out.println("DEBUG: tsToInsert=" + tsToInsert
+//                                + " tsAtInsertPoint=" + tsAtInsertPoint
+//                                + " timeDelta=" + timeDelta
+//                                + " vDelta=" + (Math.abs(tsToInsert-tsAtInsertPoint)/2)
+//                                + " numToCopy=" + numToCopy);
+//             if (j > 0) {
+//               System.out.println("DEBUG: prevTsToInsert=" + valueTimeStamps[j-1]);
+//             }
+//             if (ourIdx > 0) {
+//               System.out.println("DEBUG ourTimeStamps[" + (ourIdx-1) + "]=" + ourTimeStamps[ourIdx-1]);
+//             }
+
+//             if (numToCopy > 1) {
+//               System.out.println("DEBUG: endRunTs=" + valueTimeStamps[endRunIdx-1]);
+//             }
+            if (tsCount + numToCopy > ourTimeStamps.length) {
+              // grow our timestamp array
+              long[] tmp = new long[(tsCount + numToCopy) * 2];
+              System.arraycopy(ourTimeStamps, 0, tmp, 0, tsCount);
+              ourTimeStamps = tmp;
+            }
+            // make room for insert
+            System.arraycopy(ourTimeStamps, ourIdx,
+                ourTimeStamps, ourIdx + numToCopy,
+                tsCount - ourIdx);
+            // insert the elements
+            if (numToCopy == 1) {
+              ourTimeStamps[ourIdx] = valueTimeStamps[j];
+            } else {
+              System.arraycopy(valueTimeStamps, j,
+                  ourTimeStamps, ourIdx,
+                  numToCopy);
+            }
+            ourIdx += numToCopy;
+            tsCount += numToCopy;
+            // skip over all inserted elements
+            j += numToCopy;
+          }
+//           System.out.println("DEBUG: inst #" + i
+//                              + " valueTs[" + (j-1) + "]=" + tsToInsert
+//                              + " found/inserted at"
+//                              + " mergeTs[" + (ourIdx-1) + "]=" + ourTimeStamps[ourIdx-1]);
+        }
+      }
+//       for (int i=0; i < tsCount; i++) {
+//         System.out.println("DEBUG: mergedTs[" + i + "]=" + ourTimeStamps[i]);
+//         if (i > 0 && ourTimeStamps[i] <= ourTimeStamps[i-1]) {
+//           System.out.println("DEBUG: ERROR ts was not greater than previous");
+//         }
+//       }
+      // Now make one more pass over all the timestamps and make sure they
+      // will all fit into the current merged timestamps in ourTimeStamps
+//       boolean changed;
+//       do {
+//         changed = false;
+//         for (int i=0; i < values.length; i++) {
+//           valueTimeStamps = values[i].getRawAbsoluteTimeStamps();
+//           if (valueTimeStamps.length == 0) {
+//             continue;
+//           }
+//           int ourIdx = 0;
+//           for (int j=0; j < valueTimeStamps.length; j++) {
+//             while ((ourIdx < (tsCount-1))
+//                    && !isClosest(valueTimeStamps[j], ourTimeStamps, ourIdx)) {
+//               ourIdx++;
+//             }
+//             if (ourIdx == (tsCount-1)) {
+//               // we are at the end so we need to append all our remaining stamps [j..valueTimeStamps.length-1]
+//               // and then reappend the Long.MAX_VALUE that
+//               // is currently at tsCount-1
+//               int numToCopy = valueTimeStamps.length - j;
+//               if (tsCount+numToCopy > ourTimeStamps.length) {
+//                 // grow our timestamp array
+//                 long[] tmp = new long[tsCount+numToCopy+1];
+//                 System.arraycopy(ourTimeStamps, 0, tmp, 0, tsCount);
+//                 ourTimeStamps = tmp;
+//               }
+//               System.arraycopy(valueTimeStamps, j,
+//                                ourTimeStamps, ourIdx,
+//                                numToCopy);
+//               tsCount += numToCopy;
+//               ourTimeStamps[tsCount-1] = Long.MAX_VALUE;
+//               //changed = true;
+//               System.out.println("DEBUG: had to add " + numToCopy
+//                                  + " timestamps for inst#" + i + " starting at index " + j + " starting with ts=" + valueTimeStamps[j]);
+//               break; // our of the for j loop since we just finished off this guy
+//             } else {
+//               ourIdx++;
+//             }
+//           }
+//         }
+//       } while (changed);
+      // remove the max ts we added
+      tsCount--;
+      {
+        int startIdx = 0;
+        int endIdx = tsCount - 1;
+        if (startTime != -1) {
+          assert (ourTimeStamps[startIdx] >= startTime);
+        }
+        if (endTime != -1) {
+          assert (endIdx == startIdx - 1 || ourTimeStamps[endIdx] < endTime);
+        }
+        tsCount = (endIdx - startIdx) + 1;
+
+        // shrink and trim our timestamp array
+        long[] tmp = new long[tsCount];
+        System.arraycopy(ourTimeStamps, startIdx, tmp, 0, tsCount);
+        ourTimeStamps = tmp;
+      }
+      return ourTimeStamps;
+    }
+
+    public double[] getRawSnapshots() {
+      return getRawSnapshots(getRawAbsoluteTimeStamps());
+    }
+
+    /**
+     * Returns true if the timeStamp at curIdx is the one that ts is the closest
+     * to. We know that timeStamps[curIdx-1], if it exists, was not the
+     * closest.
+     */
+    private static boolean isClosest(long ts, long[] timeStamps, int curIdx) {
+      if (curIdx >= (timeStamps.length - 1)) {
+        // curIdx is the last one so it must be the closest
+        return true;
+      }
+      if (ts == timeStamps[curIdx]) {
+        return true;
+      }
+      return closer(ts, timeStamps[curIdx], timeStamps[curIdx + 1]);
+    }
+
+    public boolean isTrimmedLeft() {
+      for (StatValue value : this.values) {
+        if (value.isTrimmedLeft()) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    private double[] getRawSnapshots(long[] ourTimeStamps) {
+      double[] result = new double[ourTimeStamps.length];
+//       System.out.println("DEBUG: producing " + result.length + " values");
+      if (result.length > 0) {
+        for (StatValue value : values) {
+          long[] valueTimeStamps = value.getRawAbsoluteTimeStamps();
+          double[] valueSnapshots = value.getRawSnapshots();
+          double currentValue = 0.0;
+          int curIdx = 0;
+          if (value.isTrimmedLeft() && valueSnapshots.length > 0) {
+            currentValue = valueSnapshots[0];
+          }
+//           System.out.println("DEBUG: inst#" + i + " has " + valueSnapshots.length + " values");
+          for (int j = 0; j < valueSnapshots.length; j++) {
+//             System.out.println("DEBUG: Doing v with"
+//                                + " vTs[" + j + "]=" + valueTimeStamps[j]
+//                                + " at mergeTs[" + curIdx + "]=" + ourTimeStamps[curIdx]);
+            while (!isClosest(valueTimeStamps[j], ourTimeStamps, curIdx)) {
+              if (descriptor.isCounter()) {
+                result[curIdx] += currentValue;
+              }
+//               System.out.println("DEBUG: skipping curIdx=" + curIdx
+//                                  + " valueTimeStamps[" + j + "]=" + valueTimeStamps[j]
+//                                  + " ourTimeStamps[" + curIdx + "]=" + ourTimeStamps[curIdx]
+//                                  + " ourTimeStamps[" + (curIdx+1) + "]=" + ourTimeStamps[curIdx+1]);
+
+              curIdx++;
+            }
+            if (curIdx >= result.length) {
+              // Add this to workaround bug 30288
+              int samplesSkipped = valueSnapshots.length - j;
+              StringBuilder msg = new StringBuilder(100);
+              msg.append("WARNING: dropping last ");
+              if (samplesSkipped == 1) {
+                msg.append("sample because it");
+              } else {
+                msg.append(samplesSkipped).append(" samples because they");
+              }
+              msg.append(" could not fit in the merged result.");
+              System.out.println(msg.toString());
+              break;
+            }
+            currentValue = valueSnapshots[j];
+            result[curIdx] += currentValue;
+            curIdx++;
+          }
+          if (descriptor.isCounter()) {
+            for (int j = curIdx; j < result.length; j++) {
+              result[j] += currentValue;
+            }
+          }
+        }
+      }
+      return result;
+    }
+
+    public double[] getSnapshots() {
+      double[] result;
+      if (filter != FILTER_NONE) {
+        long[] timestamps = getRawAbsoluteTimeStamps();
+        double[] snapshots = getRawSnapshots(timestamps);
+        if (snapshots.length <= 1) {
+          return new double[0];
+        }
+        result = new double[snapshots.length - 1];
+        for (int i = 0; i < result.length; i++) {
+          double valueDelta = snapshots[i + 1] - snapshots[i];
+          if (filter == FILTER_PERSEC) {
+            long timeDelta = timestamps[i + 1] - timestamps[i];
+            result[i] = valueDelta / (timeDelta / 1000.0);
+//             if (result[i] > valueDelta) {
+//               System.out.println("DEBUG:  timeDelta     was " + timeDelta + " ms.");
+//               System.out.println("DEBUG: valueDelta     was " + valueDelta);
+//               System.out.println("DEBUG: valueDelta/sec was " + result[i]);
+//             }
+          } else {
+            result[i] = valueDelta;
+          }
+        }
+      } else {
+        result = getRawSnapshots();
+      }
+      calcStats(result);
+      return result;
+    }
+  }
+
+  /**
+   * Provides the value series related to a single statistics.
+   */
+  private static class SimpleValue extends AbstractValue {
+    private final ResourceInst resource;
+
+    private boolean useNextBits = false;
+    private long nextBits;
+    private final BitSeries series;
+    private boolean valueChangeNoticed = false;
+
+
+    public StatValue createTrimmed(long startTime, long endTime) {
+      if (startTime == this.startTime && endTime == this.endTime) {
+        return this;
+      } else {
+        return new SimpleValue(this, startTime, endTime);
+      }
+    }
+
+    protected SimpleValue(ResourceInst resource, StatDescriptor sd) {
+      this.resource = resource;
+      if (sd.isCounter()) {
+        this.filter = FILTER_PERSEC;
+      } else {
+        this.filter = FILTER_NONE;
+      }
+      this.descriptor = sd;
+      this.series = new BitSeries();
+      this.statsValid = false;
+    }
+
+    private SimpleValue(SimpleValue in, long startTime, long endTime) {
+      this.startTime = startTime;
+      this.endTime = endTime;
+      this.useNextBits = in.useNextBits;
+      this.nextBits = in.nextBits;
+      this.resource = in.resource;
+      this.series = in.series;
+      this.descriptor = in.descriptor;
+      this.filter = in.filter;
+      this.statsValid = false;
+      this.valueChangeNoticed = true;
+    }
+
+    public ResourceType getType() {
+      return this.resource.getType();
+    }
+
+    public ResourceInst[] getResources() {
+      return new ResourceInst[]{this.resource};
+    }
+
+    public boolean isTrimmedLeft() {
+      return getStartIdx() != 0;
+    }
+
+    private int getStartIdx() {
+      int startIdx = 0;
+      if (startTime != -1) {
+        long startTimeStamp = startTime - resource.getTimeBase();
+        long[] timestamps = resource.getAllRawTimeStamps();
+        for (int i = resource.getFirstTimeStampIdx();
+             i < resource.getFirstTimeStampIdx() + series.getSize();
+             i++) {
+          if (timestamps[i] >= startTimeStamp) {
+            break;
+          }
+          startIdx++;
+        }
+      }
+      return startIdx;
+    }
+
+    private int getEndIdx(int startIdx) {
+      int endIdx = series.getSize() - 1;
+      if (endTime != -1) {
+        long endTimeStamp = endTime - resource.getTimeBase();
+        long[] timestamps = resource.getAllRawTimeStamps();
+        endIdx = startIdx - 1;
+        for (int i = resource.getFirstTimeStampIdx() + startIdx;
+             i < resource.getFirstTimeStampIdx() + series.getSize();
+             i++) {
+          if (timestamps[i] >= endTimeStamp) {
+            break;
+          }
+          endIdx++;
+        }
+        assert (endIdx == startIdx - 1 || timestamps[endIdx] < endTimeStamp);
+      }
+      return endIdx;
+    }
+
+    public double[] getSnapshots() {
+      double[] result;
+      int startIdx = getStartIdx();
+      int endIdx = getEndIdx(startIdx);
+      int resultSize = (endIdx - startIdx) + 1;
+
+      if (filter != FILTER_NONE && resultSize > 1) {
+        long[] timestamps = null;
+        if (filter == FILTER_PERSEC) {
+          timestamps = resource.getAllRawTimeStamps();
+        }
+        result = new double[resultSize - 1];
+        int tsIdx = resource.getFirstTimeStampIdx() + startIdx;
+        double[] values = series.getValuesEx(descriptor.getTypeCode(), startIdx,
+            resultSize);
+        for (int i = 0; i < result.length; i++) {
+          double valueDelta = values[i + 1] - values[i];
+          if (filter == FILTER_PERSEC) {
+            double timeDelta = (timestamps[tsIdx + i + 1] - timestamps[tsIdx + i]); // millis
+            valueDelta /= (timeDelta / 1000); // per second
+          }
+          result[i] = valueDelta;
+        }
+      } else {
+        result = series.getValuesEx(descriptor.getTypeCode(), startIdx,
+            resultSize);
+      }
+      calcStats(result);
+      return result;
+    }
+
+    public double[] getRawSnapshots() {
+      int startIdx = getStartIdx();
+      int endIdx = getEndIdx(startIdx);
+      int resultSize = (endIdx - startIdx) + 1;
+      return series.getValuesEx(descriptor.getTypeCode(), startIdx, resultSize);
+    }
+
+    public long[] getRawAbsoluteTimeStampsWithSecondRes() {
+      long[] result = getRawAbsoluteTimeStamps();
+      for (int i = 0; i < result.length; i++) {
+        result[i] += 500;
+        result[i] /= 1000;
+        result[i] *= 1000;
+      }
+      return result;
+    }
+
+    public long[] getRawAbsoluteTimeStamps() {
+      int startIdx = getStartIdx();
+      int endIdx = getEndIdx(startIdx);
+      int resultSize = (endIdx - startIdx) + 1;
+      if (resultSize <= 0) {
+        return new long[0];
+      } else {
+        long[] result = new long[resultSize];
+        long[] timestamps = resource.getAllRawTimeStamps();
+        int tsIdx = resource.getFirstTimeStampIdx() + startIdx;
+        long base = resource.getTimeBase();
+        for (int i = 0; i < resultSize; i++) {
+          result[i] = base + timestamps[tsIdx + i];
+        }
+        return result;
+      }
+    }
+
+    public boolean hasValueChanged() {
+      if (valueChangeNoticed) {
+        valueChangeNoticed = false;
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+    protected int getMemoryUsed() {
+      int result = 0;
+      if (series != null) {
+        result += series.getMemoryUsed();
+      }
+      return result;
+    }
+
+    protected void dump(PrintWriter stream) {
+      calcStats();
+      stream.print("  " + descriptor.getName() + "=");
+      stream.print("[size=" + getSnapshotsSize()
+          + " min=" + nf.format(min)
+          + " max=" + nf.format(max)
+          + " avg=" + nf.format(avg)
+          + " stddev=" + nf.format(stddev) + "]");
+      if (Boolean.getBoolean("StatArchiveReader.dumpall")) {
+        series.dump(stream);
+      } else {
+        stream.println();
+      }
+    }
+
+    protected void shrink() {
+      this.series.shrink();
+    }
+
+    protected void initialValue(long v) {
+      this.series.initialBits(v);
+    }
+
+    protected void prepareNextBits(long bits) {
+      useNextBits = true;
+      nextBits = bits;
+    }
+
+    protected void addSample() {
+      statsValid = false;
+      if (useNextBits) {
+        useNextBits = false;
+        series.addBits(nextBits);
+        valueChangeNoticed = true;
+      } else {
+        series.addBits(0);
+      }
+    }
+  }
+
+  private static abstract class BitInterval {
+    /**
+     * Returns number of items added to values
+     */
+    abstract int fill(double[] values, int valueOffset, int typeCode,
+        int skipCount);
+
+    abstract void dump(PrintWriter stream);
+
+    abstract boolean attemptAdd(long addBits, long addInterval, int addCount);
+
+    int getMemoryUsed() {
+      return 0;
+    }
+
+    protected int count;
+
+    public final int getSampleCount() {
+      return this.count;
+    }
+
+    static BitInterval create(long bits, long interval, int count) {
+      if (interval == 0) {
+        if (bits <= Integer.MAX_VALUE && bits >= Integer.MIN_VALUE) {
+          return new BitZeroIntInterval((int) bits, count);
+        } else {
+          return new BitZeroLongInterval(bits, count);
+        }
+      } else if (count <= 3) {
+        if (interval <= Byte.MAX_VALUE && interval >= Byte.MIN_VALUE) {
+          return new BitExplicitByteInterval(bits, interval, count);
+        } else if (interval <= Short.MAX_VALUE && interval >= Short.MIN_VALUE) {
+          return new BitExplicitShortInterval(bits, interval, count);
+        } else if (interval <= Integer.MAX_VALUE && interval >= Integer.MIN_VALUE) {
+          return new BitExplicitIntInterval(bits, interval, count);
+        } else {
+          return new BitExplicitLongInterval(bits, interval, count);
+        }
+      } else {
+        boolean smallBits = false;
+        boolean smallInterval = false;
+        if (bits <= Integer.MAX_VALUE && bits >= Integer.MIN_VALUE) {
+          smallBits = true;
+        }
+        if (interval <= Integer.MAX_VALUE && interval >= Integer.MIN_VALUE) {
+          smallInterval = true;
+        }
+        if (smallBits) {
+          if (smallInterval) {
+            return new BitNonZeroIntIntInterval((int) bits, (int) interval,
+                count);
+          } else {
+            return new BitNonZeroIntLongInterval((int) bits, interval, count);
+          }
+        } else {
+          if (smallInterval) {
+            return new BitNonZeroLongIntInterval(bits, (int) interval, count);
+          } else {
+            return new BitNonZeroLongLongInterval(bits, interval, count);
+          }
+        }
+      }
+    }
+  }
+
+  private static abstract class BitNonZeroInterval extends BitInterval {
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 4;
+    }
+
+    abstract long getBits();
+
+    abstract long getInterval();
+
+    @Override
+    int fill(double[] values, int valueOffset, int typeCode, int skipCount) {
+      int fillcount = values.length - valueOffset; // space left in values
+      int maxCount = count - skipCount; // maximum values this interval can produce
+      if (fillcount > maxCount) {
+        fillcount = maxCount;
+      }
+      long base = getBits();
+      long interval = getInterval();
+      base += skipCount * interval;
+      for (int i = 0; i < fillcount; i++) {
+        values[valueOffset + i] = bitsToDouble(typeCode, base);
+        base += interval;
+      }
+      return fillcount;
+    }
+
+    @Override
+    void dump(PrintWriter stream) {
+      stream.print(getBits());
+      if (count > 1) {
+        long interval = getInterval();
+        if (interval != 0) {
+          stream.print("+=" + interval);
+        }
+        stream.print("r" + count);
+      }
+    }
+
+    BitNonZeroInterval(int count) {
+      this.count = count;
+    }
+
+    @Override
+    boolean attemptAdd(long addBits, long addInterval, int addCount) {
+      // addCount >= 2; count >= 2
+      if (addInterval == getInterval()) {
+        if (addBits == (getBits() + (addInterval * (count - 1)))) {
+          count += addCount;
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  private static class BitNonZeroIntIntInterval extends BitNonZeroInterval {
+    int bits;
+    int interval;
+
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 8;
+    }
+
+    @Override
+    long getBits() {
+      return this.bits;
+    }
+
+    @Override
+    long getInterval() {
+      return this.interval;
+    }
+
+    BitNonZeroIntIntInterval(int bits, int interval, int count) {
+      super(count);
+      this.bits = bits;
+      this.interval = interval;
+    }
+  }
+
+  private static class BitNonZeroIntLongInterval extends BitNonZeroInterval {
+    int bits;
+    long interval;
+
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 12;
+    }
+
+    @Override
+    long getBits() {
+      return this.bits;
+    }
+
+    @Override
+    long getInterval() {
+      return this.interval;
+    }
+
+    BitNonZeroIntLongInterval(int bits, long interval, int count) {
+      super(count);
+      this.bits = bits;
+      this.interval = interval;
+    }
+  }
+
+  private static class BitNonZeroLongIntInterval extends BitNonZeroInterval {
+    long bits;
+    int interval;
+
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 12;
+    }
+
+    @Override
+    long getBits() {
+      return this.bits;
+    }
+
+    @Override
+    long getInterval() {
+      return this.interval;
+    }
+
+    BitNonZeroLongIntInterval(long bits, int interval, int count) {
+      super(count);
+      this.bits = bits;
+      this.interval = interval;
+    }
+  }
+
+  private static class BitNonZeroLongLongInterval extends BitNonZeroInterval {
+    long bits;
+    long interval;
+
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 16;
+    }
+
+    @Override
+    long getBits() {
+      return this.bits;
+    }
+
+    @Override
+    long getInterval() {
+      return this.interval;
+    }
+
+    BitNonZeroLongLongInterval(long bits, long interval, int count) {
+      super(count);
+      this.bits = bits;
+      this.interval = interval;
+    }
+  }
+
+  private static abstract class BitZeroInterval extends BitInterval {
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 4;
+    }
+
+    abstract long getBits();
+
+    @Override
+    int fill(double[] values, int valueOffset, int typeCode, int skipCount) {
+      int fillcount = values.length - valueOffset; // space left in values
+      int maxCount = count - skipCount; // maximum values this interval can produce
+      if (fillcount > maxCount) {
+        fillcount = maxCount;
+      }
+      double value = bitsToDouble(typeCode, getBits());
+      for (int i = 0; i < fillcount; i++) {
+        values[valueOffset + i] = value;
+      }
+      return fillcount;
+    }
+
+    @Override
+    void dump(PrintWriter stream) {
+      stream.print(getBits());
+      if (count > 1) {
+        stream.print("r" + count);
+      }
+    }
+
+    BitZeroInterval(int count) {
+      this.count = count;
+    }
+
+    @Override
+    boolean attemptAdd(long addBits, long addInterval, int addCount) {
+      // addCount >= 2; count >= 2
+      if (addInterval == 0 && addBits == getBits()) {
+        count += addCount;
+        return true;
+      }
+      return false;
+    }
+  }
+
+  private static class BitZeroIntInterval extends BitZeroInterval {
+    int bits;
+
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 4;
+    }
+
+    @Override
+    long getBits() {
+      return bits;
+    }
+
+    BitZeroIntInterval(int bits, int count) {
+      super(count);
+      this.bits = bits;
+    }
+  }
+
+  private static class BitZeroLongInterval extends BitZeroInterval {
+    long bits;
+
+    @Override
+    int getMemoryUsed() {
+      return super.getMemoryUsed() + 8;
+    }
+
+    @Override
+    long getBits() {
+      return bits;
+    }
+
+    BitZeroLongInterval(long bits, int count) {
+      super(count);
+      this.bits = bits;
+    }
+  }
+
+  private static class BitExplicitByteInterval extends BitInterval {
+    long firstValue;
+    long lastValue;
+    byte[] bitIntervals = null;
+
+    @Override
+    int getMemoryUsed() {
+      int result = super.getMemoryUsed() + 4 + 8 + 8 + 4;
+      if (bitIntervals != null) {
+        result += bitIntervals.length;
+      }
+      return result;
+    }
+
+    @Override
+    int fill(double[] values, int valueOffset, int typeCode, int skipCount) {
+      int fillcount = values.length - valueOffset; // space left in values
+      int maxCount = count - skipCount; // maximum values this interval can produce
+      if (fillcount > maxCount) {
+        fillcount = maxCount;
+      }
+      long bitValue = firstValue;
+      for (int i = 0; i < skipCount; i++) {
+        bitValue += bitIntervals[i];
+      }
+      for (int i = 0; i < fillcount; i++) {
+        bitValue += bitIntervals[skipCount + i];
+        values[valueOffset + i] = bitsToDouble(typeCode, bitValue);
+      }
+      return fillcount;
+    }
+
+    @Override
+    void dump(PrintWriter stream) {
+      stream.print("(byteIntervalCount=" + count + " start=" + firstValue);
+      for (int i = 0; i < count; i++) {
+        if (i != 0) {
+          stream.print(", ");
+        }
+        stream.print(bitIntervals[i]);
+      }
+      stream.print(")");
+    }
+
+    BitExplicitByteInterval(long bits, long interval, int addCount) {
+      count = addCount;
+      firstValue = bits;
+      lastValue = bits + (interval * (addCount - 1));
+      bitIntervals = new byte[count * 2];
+      bitIntervals[0] = 0;
+      for (int i = 1; i < count; i++) {
+        bitIntervals[i] = (byte) interval;
+      }
+    }
+
+    @Override
+    boolean attemptAdd(long addBits, long addInterval, int addCount) {
+      // addCount >= 2; count >= 2
+      if (addCount <= 11) {
+        if (addInterval <= Byte.MAX_VALUE && addInterval >= Byte.MIN_VALUE) {
+          long firstInterval = addBits - lastValue;
+          if (firstInterval <= Byte.MAX_VALUE && firstInterval >= Byte.MIN_VALUE) {
+            lastValue = addBits + (addInterval * (addCount - 1));
+            if ((count + addCount) >= bitIntervals.length) {
+              byte[] tmp = new byte[(count + addCount) * 2];
+              System.arraycopy(bitIntervals, 0, tmp, 0, bitIntervals.length);
+              bitIntervals = tmp;
+            }
+            bitIntervals[count++] = (byte) firstInterval;
+            for (int i = 1; i < addCount; i++) {
+              bitIntervals[count++] = (byte) addInterval;
+            }
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+  }
+
+  private static class BitExplicitShortInterval extends BitInterval {
+    long firstValue;
+    long lastValue;
+    short[] bitIntervals = null;
+
+    @Override
+    int getMemoryUsed() {
+      int result = super.getMemoryUsed() + 4 + 8 + 8 + 4;
+      if (bitIntervals != null) {
+        result += bitIntervals.length * 2;
+      }
+      return result;
+    }
+
+    @Override
+    int fill(double[] values, int valueOffset, int typeCode, int skipCount) {
+      int fillcount = values.length - valueOffset; // space left in values
+      int maxCount = count - skipCount; // maximum values this interval can produce
+      if (fillcount > maxCount) {
+        fillcount = maxCount;
+      }
+      long bitValue = firstValue;
+      for (int i = 0; i < skipCount; i++) {
+        bitValue += bitIntervals[i];
+      }
+      for (int i = 0; i < fillcount; i++) {
+        bitValue += bitIntervals[skipCount + i];
+        values[valueOffset + i] = bitsToDouble(typeCode, bitValue);
+      }
+      return fillcount;
+    }
+
+    @Override
+    void dump(PrintWriter stream) {
+      stream.print("(shortIntervalCount=" + count + " start=" + firstValue);
+      for (int i = 0; i < count; i++) {
+        if (i != 0) {
+          stream.print(", ");
+        }
+        stream.print(bitIntervals[i]);
+      }
+      stream.print(")");
+    }
+
+    BitExplicitShortInterval(long bits, long interval, int addCount) {
+      count = addCount;
+      firstValue = bits;
+      lastValue = bits + (interval * (addCount - 1));
+      bitIntervals = new short[count * 2];
+      bitIntervals[0] = 0;
+      for (int i = 1; i < count; i++) {
+        bitIntervals[i] = (short) interval;
+      }
+    }
+
+    @Override
+    boolean attemptAdd(long addBits, long addInterval, int addCount) {
+      // addCount >= 2; count >= 2
+      if (addCount <= 6) {
+        if (addInterval <= Short.MAX_VALUE && addInterval >= Short.MIN_VALUE) {
+          long firstInterval = addBits - lastValue;
+          if (firstInterval <= Short.MAX_VALUE && firstInterval >= Short.MIN_VALUE) {
+            lastValue = addBits + (addInterval * (addCount - 1));
+            if ((count + addCount) >= bitIntervals.length) {
+              short[] tmp = new short[(count + addCount) * 2];
+              System.arraycopy(bitIntervals, 0, tmp, 0, bitIntervals.length);
+              bitIntervals = tmp;
+            }
+            bitIntervals[count++] = (short) firstInterval;
+            for (int i = 1; i < addCount; i++) {
+              bitIntervals[count++] = (short) addInterval;
+            }
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+  }
+
+  private static class BitExplicitIntInterval extends BitInterval {
+    long firstValue;
+    long lastValue;
+    int[] bitIntervals = null;
+
+    @Override
+    int getMemoryUsed() {
+      int result = super.getMemoryUsed() + 4 + 8 + 8 + 4;
+      if (bitIntervals != null) {
+        result += bitIntervals.length * 4;
+      }
+      return result;
+    }
+
+    @Override
+    int fill(double[] values, int valueOffset, int typeCode, int skipCount) {
+      int fillcount = values.length - valueOffset; // space left in values
+      int maxCount = count - skipCount; // maximum values this interval can produce
+      if (fillcount > maxCount) {
+        fillcount = maxCount;
+      }
+      long bitValue = firstValue;
+      for (int i = 0; i < skipCount; i++) {
+        bitValue += bitIntervals[i];
+      }
+      for (int i = 0; i < fillcount; i++) {
+        bitValue += bitIntervals[skipCount + i];
+        values[valueOffset + i] = bitsToDouble(typeCode, bitValue);
+      }
+      return fillcount;
+    }
+
+    @Override
+    void dump(PrintWriter stream) {
+      stream.print("(intIntervalCount=" + count + " start=" + firstValue);
+      for (int i = 0; i < count; i++) {
+        if (i != 0) {
+          stream.print(", ");
+        }
+        stream.print(bitIntervals[i]);
+      }
+      stream.print(")");
+    }
+
+    BitExplicitIntInterval(long bits, long interval, int addCount) {
+      count = addCount;
+      firstValue = bits;
+      lastValue = bits + (interval * (addCount - 1));
+      bitIntervals = new int[count * 2];
+      bitIntervals[0] = 0;
+      for (int i = 1; i < count; i++) {
+        bitIntervals[i] = (int) interval;
+      }
+    }
+
+    @Override
+    boolean attemptAdd(long addBits, long addInterval, int addCount) {
+      // addCount >= 2; count >= 2
+      if (addCount <= 4) {
+        if (addInterval <= Integer.MAX_VALUE && addInterval >= Integer.MIN_VALUE) {
+          long firstInterval = addBits - lastValue;
+          if (firstInterval <= Integer.MAX_VALUE && firstInterval >= Integer.MIN_VALUE) {
+            lastValue = addBits + (addInterval * (addCount - 1));
+            if ((count + addCount) >= bitIntervals.length) {
+              int[] tmp = new int[(count + addCount) * 2];
+              System.arraycopy(bitIntervals, 0, tmp, 0, bitIntervals.length);
+              bitIntervals = tmp;
+            }
+            bitIntervals[count++] = (int) firstInterval;
+            for (int i = 1; i < addCount; i++) {
+              bitIntervals[count++] = (int) addInterval;
+            }
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+  }
+
+  private static class BitExplicitLongInterval extends BitInterval {
+    long[] bitArray = null;
+
+    @Override
+    int getMemoryUsed() {
+      int result = super.getMemoryUsed() + 4 + 4;
+      if (bitArray != null) {
+        result += bitArray.length * 8;
+      }
+      return result;
+    }
+
+    @Override
+    int fill(double[] values, int valueOffset, int typeCode, int skipCount) {
+      int fillcount = values.length - valueOffset; // space left in values
+      int maxCount = count - skipCount; // maximum values this interval can produce
+      if (fillcount > maxCount) {
+        fillcount = maxCount;
+      }
+      for (int i = 0; i < fillcount; i++) {
+        values[valueOffset + i] = bitsToDouble(typeCode,
+            bitArray[skipCount + i]);
+      }
+      return fillcount;
+    }
+
+    @Override
+    void dump(PrintWriter stream) {
+      stream.print("(count=" + count + " ");
+      for (int i = 0; i < count; i++) {
+        if (i != 0) {
+          stream.print(", ");
+        }
+        stream.print(bitArray[i]);
+      }
+      stream.print(")");
+    }
+
+    BitExplicitLongInterval(long bits, long interval, int addCount) {
+      count = addCount;
+      bitArray = new long[count * 2];
+      for (int i = 0; i < count; i++) {
+        bitArray[i] = bits;
+        bits += interval;
+      }
+    }
+
+    @Override
+    boolean attemptAdd(long addBits, long addInterval, int addCount) {
+      // addCount >= 2; count >= 2
+      if (addCount <= 3) {
+        if ((count + addCount) >= bitArray.length) {
+          long[] tmp = new long[(count + addCount) * 2];
+          System.arraycopy(bitArray, 0, tmp, 0, bitArray.length);
+          bitArray = tmp;
+        }
+        for (int i = 0; i < addCount; i++) {
+          bitArray[count++] = addBits;
+          addBits += addInterval;
+        }
+        return true;
+      }
+      return false;
+    }
+  }
+
+  private static class BitSeries {
+    int count; // number of items in this series
+    long currentStartBits;
+    long currentEndBits;
+    long currentInterval;
+    int currentCount;
+    int intervalIdx; // index of most recent BitInterval
+    BitInterval intervals[];
+
+    /**
+     * Returns the amount of memory used to implement this series.
+     */
+    protected int getMemoryUsed() {
+      int result = 4 + 8 + 8 + 8 + 4 + 4 + 4;
+      if (intervals != null) {
+        result += 4 * intervals.length;
+        for (int i = 0; i <= intervalIdx; i++) {
+          result += intervals[i].getMemoryUsed();
+        }
+      }
+      return result;
+    }
+
+    public double[] getValues(int typeCode) {
+      return getValuesEx(typeCode, 0, getSize());
+    }
+
+    /**
+     * Gets the first "resultSize" values of this series skipping over the first
+     * "samplesToSkip" ones. The first value in a series is at index 0. The
+     * maximum result size can be obtained by calling "getSize()".
+     */
+    public double[] getValuesEx(int typeCode, int samplesToSkip,
+        int resultSize) {
+      double[] result = new double[resultSize];
+      int firstInterval = 0;
+      int idx = 0;
+      while (samplesToSkip > 0
+          && firstInterval <= intervalIdx
+          && intervals[firstInterval].getSampleCount() <= samplesToSkip) {
+        samplesToSkip -= intervals[firstInterval].getSampleCount();
+        firstInterval++;
+      }
+      for (int i = firstInterval; i <= intervalIdx; i++) {
+        idx += intervals[i].fill(result, idx, typeCode, samplesToSkip);
+        samplesToSkip = 0;
+      }
+      if (currentCount != 0) {
+        idx += BitInterval.create(currentStartBits, currentInterval,
+            currentCount).fill(result, idx, typeCode, samplesToSkip);
+      }
+      // assert
+      if (idx != resultSize) {
+        throw new RuntimeException("GetValuesEx didn't fill the last "
+            + (resultSize - idx) + " entries of its result");
+      }
+      return result;
+    }
+
+    void dump(PrintWriter stream) {
+      stream.print("[size=" + count + " intervals=" + (intervalIdx + 1)
+          + " memused=" + getMemoryUsed() + " ");
+      for (int i = 0; i <= intervalIdx; i++) {
+        if (i != 0) {
+          stream.print(", ");
+        }
+        intervals[i].dump(stream);
+      }
+      if (currentCount != 0) {
+        if (intervalIdx != -1) {
+          stream.print(", ");
+        }
+        BitInterval.create(currentStartBits, currentInterval,
+            currentCount).dump(stream);
+      }
+      stream.println("]");
+    }
+
+    BitSeries() {
+      count = 0;
+      currentStartBits = 0;
+      currentEndBits = 0;
+      currentInterval = 0;
+      currentCount = 0;
+      intervalIdx = -1;
+      intervals = null;
+    }
+
+    void initialBits(long bits) {
+      this.currentEndBits = bits;
+    }
+
+    int getSize() {
+      return this.count;
+    }
+
+    void addBits(long deltaBits) {
+      long bits = currentEndBits + deltaBits;
+      if (currentCount == 0) {
+        currentStartBits = bits;
+        currentCount = 1;
+      } else if (currentCount == 1) {
+        currentInterval = deltaBits;
+        currentCount++;
+      } else if (deltaBits == currentInterval) {
+        currentCount++;
+      } else {
+        // we need to move currentBits into a BitInterval
+        if (intervalIdx == -1) {
+          intervals = new BitInterval[2];
+          intervalIdx = 0;
+          intervals[0] = BitInterval.create(currentStartBits, currentInterval,
+              currentCount);
+        } else {
+          if (!intervals[intervalIdx].attemptAdd(currentStartBits,
+              currentInterval, currentCount)) {
+            // wouldn't fit in current bit interval so add a new one
+            intervalIdx++;
+            if (intervalIdx >= intervals.length) {
+              BitInterval[] tmp = new BitInterval[intervals.length * 2];
+              System.arraycopy(intervals, 0, tmp, 0, intervals.length);
+              intervals = tmp;
+            }
+            intervals[intervalIdx] = BitInterval.create(currentStartBits,
+                currentInterval, currentCount);
+          }
+        }
+        // now start a new currentBits
+        currentStartBits = bits;
+        currentCount = 1;
+      }
+      currentEndBits = bits;
+      count++;
+    }
+
+    /**
+     * Free up any unused memory
+     */
+    void shrink() {
+      if (intervals != null) {
+        int currentSize = intervalIdx + 1;
+        if (currentSize < intervals.length) {
+          BitInterval[] tmp = new BitInterval[currentSize];
+          System.arraycopy(intervals, 0, tmp, 0, currentSize);
+          intervals = tmp;
+        }
+      }
+    }
+  }
+
+  private static class TimeStampSeries {
+    static private final int GROW_SIZE = 256;
+    int count; // number of items in this series
+    long base; // millis since midnight, Jan 1, 1970 UTC.
+    long[] timeStamps = new long[GROW_SIZE]; // elapsed millis from base
+
+    void dump(PrintWriter stream) {
+      stream.print("[size=" + count);
+      for (int i = 0; i < count; i++) {
+        if (i != 0) {
+          stream.print(", ");
+          stream.print(timeStamps[i] - timeStamps[i - 1]);
+        } else {
+          stream.print(" " + timeStamps[i]);
+        }
+      }
+      stream.println("]");
+    }
+
+    void shrink() {
+      if (count < timeStamps.length) {
+        long[] tmp = new long[count];
+        System.arr

<TRUNCATED>


Mime
View raw message