geode-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kl...@apache.org
Subject [12/24] incubator-geode git commit: GEODE-1781: refactor internal statistics classes
Date Thu, 18 Aug 2016 16:30:46 GMT
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/750996a0/geode-core/src/main/java/com/gemstone/gemfire/internal/statistics/StatArchiveReader.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/com/gemstone/gemfire/internal/statistics/StatArchiveReader.java b/geode-core/src/main/java/com/gemstone/gemfire/internal/statistics/StatArchiveReader.java
new file mode 100644
index 0000000..12637bc
--- /dev/null
+++ b/geode-core/src/main/java/com/gemstone/gemfire/internal/statistics/StatArchiveReader.java
@@ -0,0 +1,3339 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gemstone.gemfire.internal.statistics;
+
+import com.gemstone.gemfire.GemFireIOException;
+import com.gemstone.gemfire.InternalGemFireException;
+import com.gemstone.gemfire.internal.Assert;
+import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
+import com.gemstone.gemfire.internal.logging.DateFormatter;
+
+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.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+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;
+
+/**
+ * StatArchiveReader provides APIs to read statistic snapshots from an archive
+ * file.
+ */
+public class StatArchiveReader implements StatArchiveFormat {
+  
+  protected static final NumberFormat nf = NumberFormat.getNumberInstance();
+  static {
+    nf.setMaximumFractionDigits(2);
+    nf.setGroupingUsed(false);
+  }
+
+  private final StatArchiveFile[] archives;
+  private boolean dump;
+  private boolean closed = false;
+
+  /**
+   * 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 IOException if <code>archiveName</code> could not be opened
+   * read, or closed.
+   */
+  public StatArchiveReader(File[] 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, archiveNames[i], dump, filters);
+    }
+
+    update(false, autoClose);
+    
+    if (this.dump || Boolean.getBoolean("StatArchiveReader.dump")) {
+      this.dump(new PrintWriter(System.out));
+    }
+  }
+
+  /**
+   * Creates a StatArchiveReader that will read the named archive file.
+   * @throws IOException if <code>archiveName</code> could not be opened
+   * read, or closed.
+   */
+  public StatArchiveReader(String archiveName) throws IOException {
+    this(new File[]{new File(archiveName)}, null, false);
+  }
+
+  /**
+   * 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();
+      StatArchiveReader.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 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;
+    StatArchiveReader.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;
+  }
+  
+  /**
+   * Closes all archives.
+   */
+  public void close() throws IOException {
+    if (!this.closed) {
+      StatArchiveReader.StatArchiveFile[] archives = getArchives();
+      for (int i=0; i < archives.length; i++) {
+        StatArchiveFile f = archives[i];
+        f.close();
+      }
+      this.closed = true;
+    }
+  }
+
+  private int getMemoryUsed()  {
+    int result = 0;
+    StatArchiveReader.StatArchiveFile[] archives = getArchives();
+    for (int i=0; i < archives.length; i++) {
+      StatArchiveFile f = archives[i];
+      result += f.getMemoryUsed();
+    }
+    return result;
+  }
+
+  private void dump(PrintWriter stream) {
+    StatArchiveReader.StatArchiveFile[] archives = getArchives();
+    for (int i=0; i < archives.length; i++) {
+      StatArchiveFile f = archives[i];
+      f.dump(stream);
+    }
+  }
+
+  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 InternalGemFireException(LocalizedStrings.StatArchiveReader_UNEXPECTED_TYPECODE_0.toLocalizedString(Integer.valueOf(type)));
+    }
+  }
+  
+  /**
+   * Simple utility to read and dump statistic archive.
+   */
+  public static void main(String args[]) throws IOException {
+    String archiveName = null;
+    if (args.length > 1) {
+      System.err.println("Usage: [archiveName]");
+      System.exit(1);
+    } else if (args.length == 1) {
+      archiveName = args[0];
+    } else {
+      archiveName = "statArchive.gfs";
+    }
+    StatArchiveReader reader = new StatArchiveReader(archiveName);
+    System.out.println("DEBUG: memory used = " + reader.getMemoryUsed());
+    reader.close();
+  }
+
+  /**
+   * 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 Object get(int idx) {
+      int archiveIdx = 0;
+      StatArchiveReader.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;
+      StatArchiveReader.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.
+     * It will be one of the following values:
+     * <ul>
+     * <li> {@link #BOOLEAN_CODE}
+     * <li> {@link #WCHAR_CODE}
+     * <li> {@link #CHAR_CODE}
+     * <li> {@link #BYTE_CODE}
+     * <li> {@link #SHORT_CODE}
+     * <li> {@link #INT_CODE}
+     * <li> {@link #LONG_CODE}
+     * <li> {@link #FLOAT_CODE}
+     * <li> {@link #DOUBLE_CODE}
+     * </ul>
+     */
+    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 StatArchiveReader.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 StatArchiveReader.StatDescriptor#isCounter}.  */
+    public static final int FILTER_NONE = 0;
+    /**
+     * {@link StatArchiveReader.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 StatArchiveReader.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
+     * StatArchiveReader.ResourceInst#getFirstTimeMillis}.
+     * <p>This is the default filter for counter statistics.
+     * To determine if a statistic is a counter use {@link StatArchiveReader.StatDescriptor#isCounter}.  */
+    public static final int FILTER_PERSEC = 1;
+    /**
+     * {@link StatArchiveReader.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 StatArchiveReader.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
+     * StatArchiveReader.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 StatArchiveReader.ResourceType type} of the
+     * resources that this value belongs to.
+     */
+    public ResourceType getType();
+    /**
+     * Gets the {@link StatArchiveReader.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 StatArchiveReader.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(LocalizedStrings.StatArchiveReader_FILTER_VALUE_0_MUST_BE_1_2_OR_3
+              .toLocalizedString(
+              new Object[] {
+                  Integer.valueOf(filter),
+                  Integer.valueOf(FILTER_NONE),
+                  Integer.valueOf(FILTER_PERSEC),
+                  Integer.valueOf(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();
+      StringBuffer result = new StringBuffer();
+      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(LocalizedStrings.StatArchiveReader_CANT_COMBINE_VALUES_WITH_DIFFERENT_FILTERS.toLocalizedString());
+        }
+        if (!typeName.equals(this.values[i].getType().getName())) {
+          throw new IllegalArgumentException(LocalizedStrings.StatArchiveReader_CANT_COMBINE_VALUES_WITH_DIFFERENT_TYPES.toLocalizedString());
+        }
+        if (!statName.equals(this.values[i].getDescriptor().getName())) {
+          throw new IllegalArgumentException(LocalizedStrings.StatArchiveReader_CANT_COMBINE_DIFFERENT_STATS.toLocalizedString());
+        }
+        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 (int i=0; i < values.length; i++) {
+        set.addAll(Arrays.asList(values[i].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) {
+//           for (int i=0; i < tsCount; i++) {
+//             if (ourTimeStamps[i] >= startTime) {
+//               break;
+//             }
+//             startIdx++;
+//           }
+          Assert.assertTrue(ourTimeStamps[startIdx] >= startTime);
+        }
+        if (endTime != -1) {
+//           endIdx = startIdx-1;
+//           for (int i=startIdx; i < tsCount; i++) {
+//             if (ourTimeStamps[i] >= endTime) {
+//               break;
+//             }
+//             endIdx++;
+//           }
+          Assert.assertTrue(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 (int i=0; i < this.values.length; i++) {
+        if (this.values[i].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 (int i=0; i < values.length; i++) {
+          long[] valueTimeStamps = values[i].getRawAbsoluteTimeStamps();
+          double[] valueSnapshots = values[i].getRawSnapshots();
+          double currentValue = 0.0;
+          int curIdx = 0;
+          if (values[i].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;
+              StringBuffer msg = new StringBuffer(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.assertTrue(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 InternalGemFireException(LocalizedStrings.StatArchiveReader_GETVALUESEX_DIDNT_FILL_THE_LAST_0_ENTRIES_OF_ITS_RESULT.toLocalizedString(Integer.valueOf(resultSize-idx)));
+      }
+      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.arraycopy(timeStamps, 0, tmp, 0, count);
+        timeStamps = tmp;
+      }
+    }
+    
+    TimeStampSeries() {
+      count = 0;
+      base = 0;
+    }
+    
+    void setBase(long base) {
+      this.base = base;
+    }
+
+    int getSize() {
+      return this.count;
+    }
+    
+    void addTimeStamp(int ts) {
+      if (count >= timeStamps.length) {
+        long[] tmp = new long[timeStamps.length + GROW_SIZE];
+        System.arraycopy(timeStamps, 0, tmp, 0, timeStamps.length);
+        timeStamps = tmp;
+      }
+      if (count != 0) {
+        timeStamps[count] = timeStamps[count-1] + ts;
+      } else {
+        timeStamps[count] = ts;
+      }
+      count++;
+    }
+    
+    long getBase() {
+      return this.base;
+    }
+    /** Provides direct access to underlying data.
+     * Do not modify contents and use getSize() to keep from reading
+     * past end of array.
+     */
+    long[] getRawTimeStamps() {
+      return this.timeStamps;
+    }
+    long getMilliTimeStamp(int idx) {
+      return this.base + this.timeStamps[idx];
+    }
+    /**
+     * Returns an array of time stamp values the first of which
+     * has the specified index.
+     * Each returned time stamp is the number of millis since
+     * midnight, Jan 1, 1970 UTC.
+     */
+    double[] getTimeValuesSinceIdx(int idx) {
+      int resultSize = this.count - idx;
+      double[] result = new double[resultSize];
+      for (int i=0; i < resultSize; i++) {
+        result[i] = getMilliTimeStamp(idx+i);
+      }
+      return result;
+    }
+  }
+
+  /**
+   * Defines a statistic resource type. Each resource instance must be
+   * of a single type. The type defines what statistics each instance
+   * of it will support. The type also has a description of itself.
+   */
+  public static class ResourceType {
+    private boolean loaded;
+//    private final int id;
+    private final String name;
+    private String desc;
+    private final StatDescriptor[] stats;
+    private Map descriptorMap;
+
+    public void dump(PrintWriter stream) {
+      if (loaded) {
+        stream.println(name + ": " + desc);
+        for (int i=0; i < stats.length; i++) {
+          stats[i].dump(stream);
+        }
+      }
+    }
+    
+    protected ResourceType(int id, String name, int statCount) {
+      this.loaded = false;
+//      this.id = id;
+      this.name = name;
+      this.desc = null;
+      this.stats = new StatDescriptor[statCount];
+      this.descriptorMap = null;
+    }
+
+    protected ResourceType(int id, String name, String desc, int statCount) {
+      this.loaded = true;
+//      this.id = id;
+      this.name = name;
+      this.desc = desc;
+      this.stats = new StatDescriptor[statCount];
+      this.descriptorMap = new HashMap();
+    }
+    public boolean isLoaded() {
+      return this.loaded;
+    }
+    /**
+     * Frees up any resources no longer needed after the archive file is closed.
+     * Returns true if this guy is no longer needed.
+     */
+    protected boolean close() {
+      if (isLoaded()) {
+        for (int i=0; i < stats.length; i++) {
+          if (stats[i] != null) {
+            if (!stats[i].isLoaded()) {
+              stats[i] = null;
+            }
+          }
+        }
+        return false;
+      } else {
+        return true;
+      }
+    }
+    
+    void unload() {
+      this.loaded = false;
+      this.desc = null;
+      for (int i=0; i < this.stats.length; i++) {
+        this.stats[i].unload();
+      }
+      this.descriptorMap.clear();
+      this.descriptorMap = null;
+    }
+
+    protected void addStatDescriptor(StatArchiveFile archive, int offset, String name, boolean isCounter,
+                                   boolean largerBetter,
+                                   byte typeCode, String units, String desc) {
+      StatDescriptor descriptor = new StatDescriptor(name, offset, isCounter, largerBetter, typeCode, units, desc);
+      this.stats[offset] = descriptor;
+      if (archive.loadStatDescriptor(descriptor, this)) {
+        descriptorMap.put(name, descriptor);
+      }
+    }
+
+//    private int getId() {
+//      return this.id;
+//    }
+    /**
+     * Returns the name of this resource type.
+     */
+    public String getName() {
+      return this.name;
+    }
+    /**
+     * Returns an array of descriptors for each statistic this resource
+     * type supports.
+     */
+    public StatDescriptor[] getStats() {
+      return this.stats;
+    }
+    /**
+     * Gets a stat descriptor contained in this type given the stats name.
+     * @param name the name of the stat to find in the current type
+     * @return the descriptor that matches the name or null if the type
+     * does not have a stat of the given name
+     */
+    public StatDescriptor getStat(String name) {
+      return (StatDescriptor)descriptorMap.get(name);
+    }
+    /**
+     * Returns a description of this resource type.
+     */
+    public String getDescription() {
+      return this.desc;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj)
+        return true;
+      if (obj == null)
+        return false;
+      if (getClass() != obj.getClass())
+        return false;
+      ResourceType other = (ResourceType) obj;
+      if (name == null) {
+        if (other.name != null)
+          return false;
+      } else if (!name.equals(other.name))
+        return false;
+      return true;
+    }
+  }
+
+  /**
+   * Describes some global information about the archive.
+   */
+  public static class ArchiveInfo {
+    private final StatArchiveFile archive;
+    private final byte archiveVersion;
+    private final long startTimeStamp; // in milliseconds
+    private final long systemStartTimeStamp; // in milliseconds
+    private final int timeZoneOffset;
+    private final String timeZoneName;
+    private final String systemDirectory;
+    private final long systemId;
+    private final String productVersion;
+    private final String os;
+    private final String machine;
+
+    public ArchiveInfo(StatArchiveFile archive, byte archiveVersion,
+                       long startTimeStamp, long systemStartTimeStamp,
+                       int timeZoneOffset,String timeZoneName,
+                       String systemDirectory, long systemId,
+                       String productVersion, String os, String machine) {
+      this.archive = archive;
+      this.archiveVersion = archiveVersion;
+      this.startTimeStamp = startTimeStamp;
+      this.systemStartTimeStamp = systemStartTimeStamp;
+      this.timeZoneOffset = timeZoneOffset;
+      this.timeZoneName = timeZoneName;
+      this.systemDirectory = systemDirectory;
+      this.systemId = systemId;
+      this.productVersion = productVersion;
+      this.os = os;
+      this.machine = machine;
+      archive.setTimeZone(getTimeZone());
+    }
+
+    /**
+     * Returns the difference, measured in milliseconds, between the time
+     * the archive file was create and midnight, January 1, 1970 UTC.
+     */
+    public long getStartTimeMillis() {
+      return this.startTimeStamp;
+    }
+    /**
+     * Returns the difference, measured in milliseconds, between the time
+     * the archived system was started and midnight, January 1, 1970 UTC.
+     */
+    public long getSystemStartTimeMillis() {
+      return this.systemStartTimeStamp;
+    }
+    /**
+     * Returns a numeric id of the archived system.  It can be used in
+     * conjunction with the {@link #getSystemStartTimeMillis} to
+     * uniquely identify an archived system.
+     */
+    public long getSystemId() {
+      return this.systemId;
+    }
+    /**
+     * Returns a string describing the operating system the archive was
+     * written on.
+     */
+    public String getOs() {
+      return this.os;
+    }
+    /**
+     * Returns a string describing the machine the archive was
+     * written on.
+     */
+    public String getMachine() {
+      return this.machine;
+    }
+    /**
+     * Returns  the time zone used when the archive was created.
+     * This can be used to print timestamps in the same time zone
+     * that was in effect when the archive was created.
+     */
+    public TimeZone getTimeZone() {
+      TimeZone result = TimeZone.getTimeZone(this.timeZoneName);
+      if (result.getRawOffset() != this.timeZoneOffset) {
+        result = new SimpleTimeZone(this.timeZoneOffset, this.timeZoneName);
+      }
+      return result;
+    }
+    /**
+     * Returns a string containing the version of the product that wrote
+     * this archive.
+     */
+    public String getProductVersion() {
+      return this.productVersion;
+    }
+    /**
+     * Returns a numeric code that represents the format version used to
+     * encode the archive as a stream of bytes.
+     */
+    public int getArchiveFormatVersion() {
+      return this.archiveVersion;
+    }
+    /**
+     * Returns a string describing the system that this archive recorded.
+     */
+    public String getSystem() {
+      return this.systemDirectory;
+    }
+    /**
+     * Return the name of the file this archive was stored in or
+     * an empty string if the archive was not stored in a file.
+     */
+    public String getArchiveFileName() {
+      if (this.archive != null) {
+        return this.archive.getFile().getPath();
+      } else {
+        return "";
+      }
+    }
+
+    /**
+     * Returns a string representation of this object.
+     */
+    @Override
+    public String toString() {
+      StringWriter sw = new StringWriter();
+      this.dump(new PrintWriter(sw));
+      return sw.toString();
+    }
+    
+    protected void dump(PrintWriter stream) {
+      if (archive != null) {
+        stream.println("archive=" + archive.getFile());
+      }
+      stream.println("archiveVersion=" + archiveVersion);
+      if (archive != null) {
+        stream.println("startDate=" + archive.formatTimeMillis(startTimeStamp));
+      }
+      // stream.println("startTimeStamp=" + startTimeStamp +" tz=" + timeZoneName + " tzOffset=" + timeZoneOffset);
+      // stream.println("timeZone=" + getTimeZone().getDisplayName());
+      stream.println("systemDirectory=" + systemDirectory);
+      if (archive != null) {
+        stream.println("systemStartDate=" + archive.formatTimeMillis(systemStartTimeStamp));
+      }
+      stream.println("systemId=" + systemId);
+      stream.println("productVersion=" + productVersion);
+      stream.println("osInfo=" + os);
+      stream.println("machineInfo=" + machine);
+    }
+  }
+
+  /**
+   * Defines a single instance of a resource type.
+   */
+  public static class ResourceInst {
+    private final boolean loaded;
+    private final StatArchiveFile archive;
+//    private final int uniqueId;
+    private final ResourceType type;
+    private final String name;
+    private final long id;
+    private boolean active = true;
+    private final SimpleValue[] values;
+    private int firstTSidx = -1;
+    private int lastTSidx = -1;
+
+    /**
+     * Returns the approximate amount of memory used to implement this object.
+     */
+    protected int getMemoryUsed() {
+      int result = 0;
+      if (values != null) {
+        for (int i=0; i < values.length; i++) {
+          result += this.values[i].getMemoryUsed();
+        }
+      }
+      return result;
+    }
+
+    public StatArchiveReader getReader() {
+      return archive.getReader();
+    }
+    /**
+     * Returns a string representation of this object.
+     */
+    @Override
+    public String toString() {
+      StringBuffer result = new StringBuffer();
+      result.append(name)
+        .append(", ")
+        .append(id)
+        .append(", ")
+        .append(type.getName())
+        .append(": \"")
+        .append(archive.formatTimeMillis(getFirstTimeMillis()))
+        .append('\"');
+      if (!active) {
+        result.append(" inactive");
+      }
+      result.append(" samples="+ getSampleCount());
+      return result.toString();
+    }
+    /**
+     * Returns the number of times this resource instance has been sampled.
+     */
+    public int getSampleCount() {
+      if (active) {
+        return archive.getTimeStamps().getSize() - firstTSidx;
+      } else {
+        return (lastTSidx+1) - firstTSidx;
+      }
+    }
+
+    public StatArchiveFile getArchive() {
+      return this.archive;
+    }
+    
+    protected void dump(PrintWriter stream) {
+      stream.println(name + ":"
+                     + " file=" + getArchive().getFile()
+                     + " id=" + id
+                     + (active? "" : " deleted")
+                     + " start=" + archive.formatTimeMillis(getFirstTimeMillis()));
+      for (int i=0; i < values.length; i++) {
+        values[i].dump(stream);
+      }
+    }
+
+    protected ResourceInst(StatArchiveFile archive, int uniqueId, String name, long id, ResourceType type, boolean loaded) {
+      this.loaded = loaded;
+      this.archive = archive;
+//      this.uniqueId = uniqueId;
+      this.name = name;
+      this.id = id;
+      Assert.assertTrue(type != null);
+      this.type = type;
+      if (loaded) {
+        StatDescriptor[] stats = type.getStats();
+        this.values = new SimpleValue[stats.length];
+        for (int i=0; i < stats.length; i++) {
+          if (archive.loadStat(stats[i], this)) {
+            this.values[i] = new SimpleValue(this, stats[i]);
+          } else {
+            this.values[i] = null;
+          }
+        }
+      } else {
+        this.values = null;
+      }
+    }
+
+    void matchSpec(StatSpec spec, List matchedValues) {
+      if (spec.typeMatches(this.type.getName())) {
+        if (spec.instanceMatches(this.getName(), this.getId())) {
+          for (int statIdx=0; statIdx < values.length; statIdx++) {
+            if (values[statIdx] != null) {
+              if (spec.statMatches(values[statIdx].getDescriptor().getName())) {
+                matchedValues.add(values[statIdx]);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    protected void initialValue(int statOffset, long v) {
+      if (this.values != null && this.values[statOffset] != null) {
+        this.values[statOffset].initialValue(v);
+      }
+    }
+    /**
+     * Returns true if sample was added.
+     */
+    protected boolean addValueSample(int statOffset, long statDeltaBits) {
+      if (this.values != null && this.values[statOffset] != null) {
+        this.values[statOffset].prepareNextBits(statDeltaBits);
+        return true;
+      } else {
+        return false;
+      }
+    }
+    
+    public boolean isLoaded() {
+      return this.loaded;
+    }
+
+    /**
+     * Frees up any resources no longer needed after the archive file is closed.
+     * Returns true if this guy is no longer needed.
+     */
+    protected boolean close() {
+      if (isLoaded()) {
+        for (int i=0; i < values.length; i++) {
+          if (values[i] != null) {
+            values[i].shrink();
+          }
+        }
+        return false;
+      } else {
+        return true;
+      }
+    }
+
+    protected int getFirstTimeStampIdx() {
+      return this.firstTSidx;
+    }
+    protected long[] getAllRawTimeStamps() {
+      return archive.getTimeStamps().getRawTimeStamps();
+    }
+    protected long getTimeBase() {
+      return archive.getTimeStamps().getBase();
+    }
+    /**
+     * Returns an array of doubles containing the timestamps at which
+     * this instances samples where taken. Each of these timestamps
+     * is the difference, measured in milliseconds, between the sample
+     * time and midnight, January 1, 1970 UTC.
+     * Although these values are double they can safely be converted
+     * to <code>long</code> with no loss of information.
+     */
+    public double[] getSnapshotTimesMillis() {
+      return archive.getTimeStamps().getTimeValuesSinceIdx(firstTSidx);
+    }
+    /**
+     * Returns an array of statistic value descriptors. Each element
+     * of the array describes the corresponding statistic this instance
+     * supports. The <code>StatValue</code> instances can be used to
+     * obtain the actual sampled values of the instances statistics.
+     */
+    public StatValue[] getStatValues() {
+      return this.values;
+    }
+    /**
+     * Gets the value of the stat in the current instance given the stat name.
+     * @param name the name of the stat to find in the current instance
+     * @return the value that matches the name or null if the instance
+     * does not have a stat of the given name
+     *
+     */
+    public StatValue getStatValue(String name) {
+      StatValue result = null;
+      StatDescriptor desc = getType().getStat(name);
+      if (desc != null) {
+        result = values[desc.getOffset()];
+      }
+      return result;
+    }
+
+    /**
+     * Returns the name of this instance.
+     */
+    public String getName() {
+      return this.name;
+    }
+    /**
+     * Returns the id of this instance.
+     */
+    public long getId() {
+      return this.id;
+    }
+    /**
+     * Returns the difference, measured in milliseconds, between the time
+     * of the instance's first sample and midnight, January 1, 1970 UTC.
+     */
+    public long getFirstTimeMillis() {
+      return archive.getTimeStamps().getMilliTimeStamp(firstTSidx);
+    }
+    /**
+     * Returns resource type of this instance.
+     */
+    public ResourceType getType() {
+      return this.type;
+    }
+    protected void makeInactive() {
+      this.active = false;
+      lastTSidx = archive.getTimeStamps().getSize() - 1;
+      close(); // this frees up unused memory now that no more samples
+    }
+    /**
+     * Returns true if archive might still have future samples for this instance.
+     */
+    public boolean isActive() {
+      return this.active;
+    }
+    protected void addTimeStamp() {
+      if (this.loaded) {
+        if (firstTSidx == -1) {
+          firstTSidx = archive.getTimeStamps().getSize() - 1;
+        }
+        for (int i=0; i < values.length; i++) {
+          if (values[i] != null) {
+            values[i].addSample();
+          }
+        }
+      }
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + (int) (id ^ (id >>> 32));
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((type == null) ? 0 : type.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj)
+     

<TRUNCATED>


Mime
View raw message