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>
