geode-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dschnei...@apache.org
Subject [2/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:44 GMT
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/jvsd/stats/StatFileParser.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/jvsd/stats/StatFileParser.java b/jvsdfx-mm/src/main/java/com/pivotal/jvsd/stats/StatFileParser.java
new file mode 100644
index 0000000..6d95ff5
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/jvsd/stats/StatFileParser.java
@@ -0,0 +1,3591 @@
+package com.pivotal.jvsd.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.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.SimpleTimeZone;
+import java.util.TimeZone;
+import java.util.zip.GZIPInputStream;
+
+
+/**
+ * Copies StatArchiveFileReader behavior and then adds to it to support usage
+ * within a VSD like tool
+ */
+
+/**
+ *
+ * @author Vince Ford
+ */
+public class StatFileParser implements StatArchiveFormat {
+
+	private final StatArchiveFile[] archives;
+	// private boolean dump;
+	private boolean closed = false;
+	/**
+	 *
+	 */
+	public final static String FORMAT = "yyyy/MM/dd HH:mm:ss.SSS z";
+
+	/**
+	 *
+	 * @param archiveNames
+	 * @param filters
+	 * @param autoClose
+	 * @throws IOException
+	 */
+	public StatFileParser(File[] archiveNames, ValueFilter[] filters, boolean autoClose)
+					throws IOException {
+		//System.out.println("StatFileParser 1");
+		this.archives = new StatArchiveFile[archiveNames.length];
+		//this.dump = Boolean.getBoolean("StatFileParser.dumpall");
+		for (int i = 0; i < archiveNames.length; i++) {
+			this.archives[i] = new StatArchiveFile(this, archiveNames[i], false, filters);
+		}
+
+		update(false, autoClose);
+
+//        if (this.dump || Boolean.getBoolean("StatFileParser.dump")) {
+//            this.dump(new PrintWriter(System.out));
+//        }
+	}
+
+	/**
+	 *
+	 * @param archiveName
+	 * @throws IOException
+	 */
+	public StatFileParser(String archiveName) throws IOException {
+
+		this(new File[]{new File(archiveName)}, null, false);
+		//System.out.println("StatFileParser 2");
+	}
+
+//    private void dump(PrintWriter printWriter) {
+//        System.out.println("StatFileParser Dump 1");
+//        throw new UnsupportedOperationException("Not yet implemented");
+//    }
+	/**
+	 *
+	 */
+	public interface ValueFilter {
+
+		/**
+		 * Returns true if the specified archive file matches this spec. Any
+		 * archives whose name does not match this spec will not be selected for
+		 * loading by this spec.
+		 *
+		 * @param archive
+		 * @return
+		 */
+		public boolean archiveMatches(File archive);
+
+		/**
+		 * Returns true if the specified type name matches this spec. Any types
+		 * whose name does not match this spec will not be selected for loading by
+		 * this spec.
+		 *
+		 * @param typeName
+		 * @return
+		 */
+		public boolean typeMatches(String typeName);
+
+		/**
+		 * Returns true if the specified statistic name matches this spec. Any stats
+		 * whose name does not match this spec will not be selected for loading by
+		 * this spec.
+		 *
+		 * @param statName
+		 * @return
+		 */
+		public boolean statMatches(String statName);
+
+		/**
+		 * Returns true if the specified instance matches this spec. Any instance
+		 * whose text id and numeric id do not match this spec will not be selected
+		 * for loading by this spec.
+		 *
+		 * @param textId
+		 * @param numericId
+		 * @return
+		 */
+		public boolean instanceMatches(String textId, long numericId);
+	}
+
+	/**
+	 *
+	 */
+	static public class StatArchiveFile {
+
+		private final StatFileParser reader;
+		private InputStream is;
+		private DataInputStream dataIn;
+		private ValueFilter[] filters;
+		private final File archiveName;
+		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(FORMAT);
+		private final static int BUFFER_SIZE = 1024 * 1024;
+		private final ArrayList fileComboValues = new ArrayList();
+
+		/**
+		 *
+		 * @param reader
+		 * @param archiveName
+		 * @param dump
+		 * @param filters
+		 * @throws IOException
+		 */
+		public StatArchiveFile(StatFileParser reader,
+						File archiveName,
+						boolean dump,
+						ValueFilter[] filters)
+						throws IOException {
+			// System.out.println("StatArchiveFile 1");
+			this.reader = reader;
+			this.archiveName = archiveName;
+			this.dump = dump;
+			this.compressed = archiveName.getPath().endsWith(".gz");
+			this.is = new FileInputStream(this.archiveName);
+			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);
+		}
+
+		private ValueFilter[] createFilters(ValueFilter[] allFilters) {
+			// System.out.println("StatArchiveFile createFilters 1");
+			if (allFilters == null) {
+				return new ValueFilter[0];
+			}
+			ArrayList l = new ArrayList();
+			for (int i = 0; i < allFilters.length; i++) {
+				if (allFilters[i].archiveMatches(this.getFile())) {
+					l.add(allFilters[i]);
+				}
+			}
+			if (l.size() == allFilters.length) {
+				return allFilters;
+			} else {
+				ValueFilter[] result = new ValueFilter[l.size()];
+				return (ValueFilter[]) l.toArray(result);
+			}
+		}
+
+		StatFileParser getReader() {
+			// System.out.println("StatArchiveFile getReader");
+			return this.reader;
+		}
+
+		void matchSpec(StatSpec spec, List matchedValues) {
+			// System.out.println("StatArchiveFile matchSpec");
+			if (spec.getCombineType() == StatSpec.FILE) {
+				// search for previous ComboValue
+				Iterator it = this.fileComboValues.iterator();
+				while (it.hasNext()) {
+					ComboValue v = (ComboValue) it.next();
+					if (!spec.statMatches(v.getDescriptor().getName())) {
+						continue;
+					}
+					if (!spec.typeMatches(v.getType().getName())) {
+						continue;
+					}
+					ResourceInst[] resources = v.getResources();
+					for (int i = 0; i < resources.length; i++) {
+						if (!spec.instanceMatches(resources[i].getName(), resources[i].
+										getId())) {
+							continue;
+						}
+						// 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.
+		 * @return
+		 */
+		public String formatTimeMillis(long ts) {
+			// System.out.println("StatArchiveFile formatTimeMillis");
+			synchronized (timeFormatter) {
+				return timeFormatter.format(new Date(ts));
+			}
+		}
+
+		/**
+		 * sets the time zone this archive was written in.
+		 */
+		void setTimeZone(TimeZone z) {
+			//System.out.println("StatArchiveFile setTimeZone");
+			timeFormatter.setTimeZone(z);
+		}
+
+		/**
+		 * Returns the time series for this archive.
+		 */
+		TimeStampSeries getTimeStamps() {
+			// System.out.println("StatArchiveFile 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.
+		 *
+		 * @param doReset
+		 * @return true if update read some new data.
+		 * @throws IOException if <code>archiveName</code> could not be opened read,
+		 * or closed.
+		 */
+		public boolean update(boolean doReset) throws IOException {
+			// System.out.println("StatArchiveFile update");
+			if (this.closed) {
+				return false;
+			}
+			if (!this.updateOK) {
+				throw new IOException("UPDATE_OF_THIS_TYPE_OF_FILE_IS_NOT_SUPPORTED");
+			}
+
+			if (doReset) {
+				this.dataIn.reset();
+			}
+
+			int updateTokenCount = 0;
+			while (this.readToken()) {
+				updateTokenCount++;
+			}
+			return updateTokenCount != 0;
+		}
+
+		/**
+		 *
+		 * @param stream
+		 */
+		public void dump(PrintWriter stream) {
+			// System.out.println("StatArchiveFile dump");
+			stream.print("archive=" + archiveName);
+			if (info != null) {
+				info.dump(stream);
+			}
+			for (int i = 0; i < resourceTypeTable.length; i++) {
+				if (resourceTypeTable[i] != null) {
+					resourceTypeTable[i].dump(stream);
+				}
+			}
+			stream.print("time=");
+			timeSeries.dump(stream);
+			for (int i = 0; i < resourceInstTable.length; i++) {
+				if (resourceInstTable[i] != null) {
+					//resourceInstTable[i].dump(stream);
+				}
+			}
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		public File getFile() {
+			// System.out.println("StatArchiveFile getFile");
+			return this.archiveName;
+		}
+
+		/**
+		 * Closes the archive.
+		 *
+		 * @throws IOException
+		 */
+		public void close() throws IOException {
+			// System.out.println("StatArchiveFile close");
+			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 (int i = 0; i < this.resourceTypeTable.length; i++) {
+						if (this.resourceTypeTable[i] != null) {
+							newTypeTable[typeCount] = this.resourceTypeTable[i];
+							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 (int i = 0; i < this.resourceInstTable.length; i++) {
+						if (this.resourceInstTable[i] != null) {
+							newInstTable[instCount] = this.resourceInstTable[i];
+							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.
+		 *
+		 * @return
+		 */
+		public ArchiveInfo getArchiveInfo() {
+			// System.out.println("StatArchiveFile getArchiveInfo");
+			return this.info;
+		}
+
+		private void readHeaderToken() throws IOException {
+			// System.out.println("StatArchiveFile readHeaderToken");
+			byte archiveVersionData = 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 (archiveVersionData <= 1) {
+				throw new IOException("ARCHIVE_VERSION_0_IS_NO_LONGER_SUPPORTED");
+			}
+			if (archiveVersionData > ARCHIVE_VERSION) {
+				throw new IOException("UNSUPPORTED_ARCHIVE_VERSION_0_THE_SUPPORTED_VERSION");
+			}
+			this.archiveVersion = archiveVersionData;
+			this.info = new ArchiveInfo(this, archiveVersionData,
+							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) {
+           // System.out.println("StatArchiveFile loadType");
+			// note we don't have instance data or descriptor data yet
+			if (filters == null || filters.length == 0) {
+				return true;
+			} else {
+				for (int i = 0; i < filters.length; i++) {
+					if (filters[i].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
+			// System.out.println("StatArchiveFile loadStatDescriptor");
+			if (!type.isLoaded()) {
+				return false;
+			}
+			if (filters == null || filters.length == 0) {
+				return true;
+			} else {
+				for (int i = 0; i < filters.length; i++) {
+					if (filters[i].statMatches(stat.getName())
+									&& filters[i].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) {
+			//System.out.println("StatArchiveFile loadInstance");
+			if (!type.isLoaded()) {
+				return false;
+			}
+			if (filters == null || filters.length == 0) {
+				return true;
+			} else {
+				for (int i = 0; i < filters.length; i++) {
+					if (filters[i].typeMatches(type.getName())) {
+						if (filters[i].instanceMatches(textId, numericId)) {
+							StatDescriptor[] stats = type.getStats();
+							for (int j = 0; j < stats.length; j++) {
+								if (stats[j].isLoaded()) {
+									if (filters[i].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) {
+			// System.out.println("StatArchiveFile loadStat");
+			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 (int i = 0; i < filters.length; i++) {
+					if (filters[i].statMatches(stat.getName())
+									&& filters[i].typeMatches(type.getName())
+									&& filters[i].instanceMatches(textId, numericId)) {
+						return true;
+					}
+				}
+				return false;
+			}
+		}
+
+		private void readResourceTypeToken() throws IOException {
+			// System.out.println("StatArchiveFile readResourceTypeToken");
+			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(resourceTypeId,
+								resourceTypeName,
+								resourceTypeDesc,
+								statCount);
+				if (dump) {
+                    //System.out.println("ResourceType id=" + resourceTypeId
+					//     + " name=" + resourceTypeName
+					//      + " statCount=" + statCount
+					//       + " desc=" + resourceTypeDesc);
+				}
+			} else {
+				rt = new ResourceType(resourceTypeId, 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, resourceInstId, 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_0");
+					}
+					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("UNEXPECTED_TYPECODE_VALUE_0");
+					}
+					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 (int i = 0; i < resourceInstTable.length; i++) {
+				ResourceInst inst = resourceInstTable[i];
+				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_0");
+				}
+				return true;
+			} catch (EOFException ignore) {
+				return false;
+			}
+		}
+
+		/**
+		 * Returns the approximate amount of memory used to implement this object.
+		 *
+		 * @return
+		 */
+//        protected int getMemoryUsed() {
+//            int result = 0;
+//            for (int i = 0; i < resourceInstTable.length; i++) {
+//                if (resourceInstTable[i] != null) {
+//                    result += resourceInstTable[i].getMemoryUsed();
+//                }
+//            }
+//            return result;
+//        }
+	}
+
+	/**
+	 *
+	 */
+	static protected 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;
+
+		/**
+		 *
+		 */
+		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 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_0_MUST_BE_1_2_OR_3");
+				}
+				this.filter = filter;
+				this.statsValid = false;
+			}
+		}
+
+		/**
+		 * Calculates each stat given the result of calling getSnapshots
+		 *
+		 * @param values
+		 */
+		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;
+			} else {
+				min = values[0];
+				max = values[0];
+				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));
+			return result.toString();
+		}
+	}
+
+	/**
+	 *
+	 */
+	static public 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;
+
+		/**
+		 *
+		 * @param archive
+		 * @param archiveVersion
+		 * @param startTimeStamp
+		 * @param systemStartTimeStamp
+		 * @param timeZoneOffset
+		 * @param timeZoneName
+		 * @param systemDirectory
+		 * @param systemId
+		 * @param productVersion
+		 * @param os
+		 * @param 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.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public long getSystemId() {
+			return this.systemId;
+		}
+
+		/**
+		 * Returns a string describing the operating system the archive was written
+		 * on.
+		 *
+		 * @return
+		 */
+		public String getOs() {
+			return this.os;
+		}
+
+		/**
+		 * Returns a string describing the machine the archive was written on.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public int getArchiveFormatVersion() {
+			return this.archiveVersion;
+		}
+
+		/**
+		 * Returns a string describing the system that this archive recorded.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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();
+		}
+
+		/**
+		 *
+		 * @param stream
+		 */
+		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);
+			stream.println("systemStartDate=" + archive.
+							formatTimeMillis(systemStartTimeStamp));
+			stream.println("systemId=" + systemId);
+			stream.println("productVersion=" + productVersion);
+			stream.println("osInfo=" + os);
+			stream.println("machineInfo=" + machine);
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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_DIDNT_FILL_THE_LAST_0_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;
+				}
+			}
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	/**
+	 *
+	 */
+	static public class ResourceType {
+
+		private boolean loaded;
+//    private final int id;
+		private final String name;
+		private String desc;
+		private final StatDescriptor[] stats;
+		private Map descriptorMap;
+
+		/**
+		 *
+		 * @param stream
+		 */
+		public void dump(PrintWriter stream) {
+			if (loaded) {
+				stream.println(name + ": " + desc);
+				for (int i = 0; i < stats.length; i++) {
+					stats[i].dump(stream);
+				}
+			}
+		}
+
+		/**
+		 *
+		 * @param id
+		 * @param name
+		 * @param statCount
+		 */
+		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;
+		}
+
+		/**
+		 *
+		 * @param id
+		 * @param name
+		 * @param desc
+		 * @param statCount
+		 */
+		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();
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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;
+		}
+
+		/**
+		 *
+		 * @param archive
+		 * @param offset
+		 * @param name
+		 * @param isCounter
+		 * @param largerBetter
+		 * @param typeCode
+		 * @param units
+		 * @param desc
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public String getName() {
+			return this.name;
+		}
+
+		/**
+		 * Returns an array of descriptors for each statistic this resource type
+		 * supports.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public String getDescription() {
+			return this.desc;
+		}
+	}
+
+	/**
+	 *
+	 */
+	static public 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; //PID of process??
+		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.
+		 *
+		 * @return
+		 */
+//        protected int getMemoryUsed() {
+//            int result = 0;
+//            if (values != null) {
+//                for (int i = 0; i < values.length; i++) {
+//                    result += this.values[i].getMemoryUsed();
+//                }
+//            }
+//            return result;
+//        }
+		/**
+		 *
+		 * @return
+		 */
+		public StatFileParser 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" + 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.
+		 *
+		 * @return
+		 */
+		public int getSampleCount() {
+			if (active) {
+				return archive.getTimeStamps().getSize() - firstTSidx;
+			} else {
+				return (lastTSidx + 1) - firstTSidx;
+			}
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		public StatArchiveFile getArchive() {
+			return this.archive;
+		}
+
+		/**
+		 *
+		 * @param stream
+		 */
+//        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);
+//            }
+//        }
+		/**
+		 *
+		 * @param archive
+		 * @param uniqueId
+		 * @param name
+		 * @param id
+		 * @param type
+		 * @param loaded
+		 */
+		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 (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]);
+							}
+						}
+					}
+				}
+			}
+		}
+
+		/**
+		 *
+		 * @param statOffset
+		 * @param v
+		 */
+		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.
+		 *
+		 * @param statOffset
+		 * @param statDeltaBits
+		 * @return
+		 */
+		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;
+			}
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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;
+			}
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		protected int getFirstTimeStampIdx() {
+			return this.firstTSidx;
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		protected long[] getAllRawTimeStamps() {
+			return archive.getTimeStamps().getRawTimeStamps();
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public String getName() {
+			return this.name;
+		}
+
+		/**
+		 * Returns the id of this instance.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public long getFirstTimeMillis() {
+			return archive.getTimeStamps().getMilliTimeStamp(firstTSidx);
+		}
+
+		/**
+		 * Returns resource type of this instance.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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();
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 *
+	 */
+	public interface StatSpec extends ValueFilter {
+
+		/**
+		 * Causes all stats that matches this spec, in all archive files, to be
+		 * combined into a single global stat value.
+		 */
+		public static final int GLOBAL = 2;
+		/**
+		 * Causes all stats that matches this spec, in each archive file, to be
+		 * combined into a single stat value for each file.
+		 */
+		public static final int FILE = 1;
+		/**
+		 * No combination is done.
+		 */
+		public final int NONE = 0;
+
+		/**
+		 * Returns one of the following values:
+		 * {@link #GLOBAL}, {@link #FILE}, {@link #NONE}.
+		 *
+		 * @return
+		 */
+		public int getCombineType();
+	}
+
+	static private 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("CANT_COMBINE_VALUES_WITH_DIFFERENT_FILTERS");
+				}
+				if (!typeName.equals(this.values[i].getType().getName())) {
+					throw new IllegalArgumentException("CANT_COMBINE_VALUES_WITH_DIFFERENT_TYPES");
+				}
+				if (!statName.equals(this.values[i].getDescriptor().getName())) {
+					throw new IllegalArgumentException("CANT_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() {
+			HashSet 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;
+					}
+
+				}
+			}
+
+			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 (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()) {
+						currentValue = valueSnapshots[0];
+					}
+					for (int j = 0; j < valueSnapshots.length; j++) {
+						while (!isClosest(valueTimeStamps[j], ourTimeStamps, curIdx)) {
+							if (descriptor.isCounter()) {
+								result[curIdx] += currentValue;
+							}
+							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);
+
+					} else {
+						result[i] = valueDelta;
+					}
+				}
+			} else {
+				result = getRawSnapshots();
+			}
+			calcStats(result);
+			return result;
+		}
+	}
+
+	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);
+		}
+	}
+
+	/**
+	 *
+	 */
+	static public 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;
+
+		/**
+		 *
+		 * @param stream
+		 */
+		protected void dump(PrintWriter stream) {
+			stream.println("  " + name + ": type=" + typeCode + " offset=" + offset
+							+ (isCounter ? " counter" : "")
+							+ " units=" + units
+							+ " largerBetter=" + largerBetter
+							+ " desc=" + desc);
+		}
+
+		/**
+		 *
+		 * @param name
+		 * @param offset
+		 * @param isCounter
+		 * @param largerBetter
+		 * @param typeCode
+		 * @param units
+		 * @param 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;
+		}
+
+		/**
+		 *
+		 * @return
+		 */
+		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>
+		 *
+		 * @return
+		 */
+		public byte getTypeCode() {
+			return this.typeCode;
+		}
+
+		/**
+		 * Returns the name of this statistic.
+		 *
+		 * @return
+		 */
+		public String getName() {
+			return this.name;
+		}
+
+		/**
+		 * Returns true if this statistic's value will always increase.
+		 *
+		 * @return
+		 */
+		public boolean isCounter() {
+			return this.isCounter;
+		}
+
+		/**
+		 * Returns true if larger values indicate better performance.
+		 *
+		 * @return
+		 */
+		public boolean isLargerBetter() {
+			return this.largerBetter;
+		}
+
+		/**
+		 * Returns a string that describes the units this statistic measures.
+		 *
+		 * @return
+		 */
+		public String getUnits() {
+			return this.units;
+		}
+
+		/**
+		 * Returns a textual description of this statistic.
+		 *
+		 * @return
+		 */
+		public String getDescription() {
+			return this.desc;
+		}
+
+		/**
+		 * Returns the offset of this stat in its type.
+		 *
+		 * @return
+		 */
+		public int getOffset() {
+			return this.offset;
+		}
+	}
+
+	/**
+	 *
+	 */
+	static public 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.
+		 *
+		 * @param startTime
+		 * @param endTime
+		 * @return
+		 */
+		public StatValue createTrimmed(long startTime, long endTime);
+
+		/**
+		 * Returns true if value has data that has been trimmed off it by a start
+		 * timestamp.
+		 *
+		 * @return
+		 */
+		public boolean isTrimmedLeft();
+
+		/**
+		 * Gets the {@link StatArchiveReader.ResourceType type} of the resources
+		 * that this value belongs to.
+		 *
+		 * @return
+		 */
+		public ResourceType getType();
+
+		/**
+		 * Gets the {@link StatArchiveReader.ResourceInst resources} that this value
+		 * belongs to.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public long[] getRawAbsoluteTimeStampsWithSecondRes();
+
+		/**
+		 * Returns an array of doubles containing the unfiltered value of this
+		 * statistic for each point in time that it was sampled.
+		 *
+		 * @return
+		 */
+		public double[] getRawSnapshots();
+
+		/**
+		 * Returns an array of doubles containing the filtered value of this
+		 * statistic for each point in time that it was sampled.
+		 *
+		 * @return
+		 */
+		public double[] getSnapshots();
+
+		/**
+		 * Returns the number of samples taken of this statistic's value.
+		 *
+		 * @return
+		 */
+		public int getSnapshotsSize();
+
+		/**
+		 * Returns the smallest of all the samples taken of this statistic's value.
+		 *
+		 * @return
+		 */
+		public double getSnapshotsMinimum();
+
+		/**
+		 * Returns the largest of all the samples taken of this statistic's value.
+		 *
+		 * @return
+		 */
+		public double getSnapshotsMaximum();
+
+		/**
+		 * Returns the average of all the samples taken of this statistic's value.
+		 *
+		 * @return
+		 */
+		public double getSnapshotsAverage();
+
+		/**
+		 * Returns the standard deviation of all the samples taken of this
+		 * statistic's value.
+		 *
+		 * @return
+		 */
+		public double getSnapshotsStandardDeviation();
+
+		/**
+		 * 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.
+		 *
+		 * @return
+		 */
+		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>
+		 *
+		 * @return
+		 */
+		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.
+		 *
+		 * @return
+		 */
+		public StatDescriptor getDescriptor();
+	}
+
+	/**
+	 *
+	 */
+	static protected 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;
+
+		/**
+		 *
+		 * @return
+		 */
+		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);
+					}
+				}
+			}
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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;
+		}
+	}
+
+	static private 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 inter

<TRUNCATED>


Mime
View raw message