blob: 25670c3d89de416268ca2791efd0daa06a75cfc8 [file] [log] [blame]
/*
* 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 org.apache.geode.internal.statistics;
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;
import org.apache.geode.GemFireIOException;
import org.apache.geode.InternalGemFireException;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.ExitCode;
import org.apache.geode.internal.logging.DateFormatter;
/**
* StatArchiveReader provides APIs to read statistic snapshots from an archive file.
*/
public class StatArchiveReader implements StatArchiveFormat, AutoCloseable {
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.
*/
@Override
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(String.format("Unexpected typecode %s",
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]");
ExitCode.FATAL.doSystemExit();
} 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;
}
@Override
public int getCombineType() {
return StatSpec.NONE;
}
@Override
public boolean typeMatches(String typeName) {
return spec.typeMatches(typeName);
}
@Override
public boolean statMatches(String statName) {
return spec.statMatches(statName);
}
@Override
public boolean instanceMatches(String textId, long numericId) {
return spec.instanceMatches(textId, numericId);
}
@Override
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 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}.
*/
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}.
*/
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}.
*/
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.
*/
StatValue createTrimmed(long startTime, long endTime);
/**
* Returns true if value has data that has been trimmed off it by a start timestamp.
*/
boolean isTrimmedLeft();
/**
* Gets the {@link StatArchiveReader.ResourceType type} of the resources that this value belongs
* to.
*/
ResourceType getType();
/**
* Gets the {@link StatArchiveReader.ResourceInst resources} that this value belongs to.
*/
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.
*/
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.
*/
long[] getRawAbsoluteTimeStampsWithSecondRes();
/**
* Returns an array of doubles containing the unfiltered value of this statistic for each point
* in time that it was sampled.
*/
double[] getRawSnapshots();
/**
* Returns an array of doubles containing the filtered value of this statistic for each point in
* time that it was sampled.
*/
double[] getSnapshots();
/**
* Returns the number of samples taken of this statistic's value.
*/
int getSnapshotsSize();
/**
* Returns the smallest of all the samples taken of this statistic's value.
*/
double getSnapshotsMinimum();
/**
* Returns the largest of all the samples taken of this statistic's value.
*/
double getSnapshotsMaximum();
/**
* Returns the average of all the samples taken of this statistic's value.
*/
double getSnapshotsAverage();
/**
* Returns the standard deviation of all the samples taken of this statistic's value.
*/
double getSnapshotsStandardDeviation();
/**
* Returns the most recent value of all the samples taken of this statistic's value.
*/
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.
*/
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>
*/
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.
*/
void setFilter(int filter);
/**
* Returns a description of this statistic.
*/
StatDescriptor getDescriptor();
}
protected abstract static 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();
}
}
@Override
public int getSnapshotsSize() {
calcStats();
return this.size;
}
@Override
public double getSnapshotsMinimum() {
calcStats();
return this.min;
}
@Override
public double getSnapshotsMaximum() {
calcStats();
return this.max;
}
@Override
public double getSnapshotsAverage() {
calcStats();
return this.avg;
}
@Override
public double getSnapshotsStandardDeviation() {
calcStats();
return this.stddev;
}
@Override
public double getSnapshotsMostRecent() {
calcStats();
return this.mostRecent;
}
@Override
public StatDescriptor getDescriptor() {
return this.descriptor;
}
@Override
public int getFilter() {
return this.filter;
}
@Override
public void setFilter(int filter) {
if (filter != this.filter) {
if (filter != FILTER_NONE && filter != FILTER_PERSEC && filter != FILTER_PERSAMPLE) {
throw new IllegalArgumentException(
String.format("Filter value %s must be %s, %s, or %s.",
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("\"");
}
NumberFormat nf = getNumberFormat();
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[0]));
}
/**
* 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(
"Cannot combine values with different filters.");
}
if (!typeName.equals(this.values[i].getType().getName())) {
throw new IllegalArgumentException(
"Cannot combine values with different types.");
}
if (!statName.equals(this.values[i].getDescriptor().getName())) {
throw new IllegalArgumentException(
"Cannot combine different stats.");
}
if (this.values[i].getDescriptor().isCounter()) {
// it is a counter which is not the default
if (!this.values[i].getDescriptor().isLargerBetter()) {
// this value has non-defaults for both, use it
bestTypeIdx = i;
} else if (this.values[bestTypeIdx].getDescriptor()
.isCounter() == this.values[bestTypeIdx].getDescriptor().isLargerBetter()) {
// as long as we haven't already found a value with defaults
// make this value 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 value with defaults
if (this.values[bestTypeIdx].getDescriptor().isCounter() == this.values[bestTypeIdx]
.getDescriptor().isLargerBetter()) {
// make this value 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);
}
}
@Override
public StatValue createTrimmed(long startTime, long endTime) {
if (startTime == this.startTime && endTime == this.endTime) {
return this;
} else {
return new ComboValue(this, startTime, endTime);
}
}
@Override
public ResourceType getType() {
return this.type;
}
@Override
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);
}
@Override
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);
}
@Override
public long[] getRawAbsoluteTimeStampsWithSecondRes() {
return getRawAbsoluteTimeStamps();
}
@Override
public long[] getRawAbsoluteTimeStamps() {
if (values.length == 0) {
return new long[0];
}
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])) {
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;
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.assertTrue(ourTimeStamps[startIdx] >= startTime);
}
if (endTime != -1) {
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;
}
@Override
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]);
}
@Override
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];
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];
}
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;
}
@Override
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;
}
}
/**
* 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;
@Override
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;
}
@Override
public ResourceType getType() {
return this.resource.getType();
}
@Override
public ResourceInst[] getResources() {
return new ResourceInst[] {this.resource};
}
@Override
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;
}
@Override
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;
}
@Override
public double[] getRawSnapshots() {
int startIdx = getStartIdx();
int endIdx = getEndIdx(startIdx);
int resultSize = (endIdx - startIdx) + 1;
return series.getValuesEx(descriptor.getTypeCode(), startIdx, resultSize);
}
@Override
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;
}
@Override
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;
}
}
@Override
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() + "=");
NumberFormat nf = getNumberFormat();
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 abstract static 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 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 abstract static 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 abstract static 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(
String.format("getValuesEx did not fill the last %s entries of its result.",
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 {
private static 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 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.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.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 resource 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);
}
}
/**
* 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 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.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
* these stats are 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)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ResourceInst other = (ResourceInst) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
if (this.firstTSidx != other.firstTSidx) {
return false;
}
return true;
}
}
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.
*/
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.
*/
int FILE = 1;
/**
* No combination is done.
*/
int NONE = 0;
/**
* Returns one of the following values: {@link #GLOBAL}, {@link #FILE}, {@link #NONE}.
*/
int getCombineType();
}
/**
* Specifies what data from a statistic archive will be of interest to the reader. This is used
* when loading a statistic archive file to reduce the memory footprint. Only statistic data that
* matches all four will be selected for loading.
*/
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.
*/
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.
*/
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.
*/
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.
*/
boolean instanceMatches(String textId, long numericId);
}
public static class StatArchiveFile {
private final StatArchiveReader 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(DateFormatter.FORMAT_STRING);
private static final int BUFFER_SIZE = 1024 * 1024;
private final ArrayList fileComboValues = new ArrayList();
public StatArchiveFile(StatArchiveReader reader, File archiveName, boolean dump,
ValueFilter[] filters) throws IOException {
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) {
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);
}
}
StatArchiveReader getReader() {
return this.reader;
}
void matchSpec(StatSpec spec, List matchedValues) {
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.
*/
public String formatTimeMillis(long ts) {
synchronized (timeFormatter) {
return timeFormatter.format(new Date(ts));
}
}
/**
* sets the time zone this archive was written in.
*/
void setTimeZone(TimeZone z) {
timeFormatter.setTimeZone(z);
}
/**
* Returns the time series for this archive.
*/
TimeStampSeries getTimeStamps() {
return timeSeries;
}
/**
* Checks to see if the archive has changed since the StatArchiverReader instance was created or
* last updated. If the archive has additional samples then those are read the resource
* instances maintained by the reader are updated.
* <p>
* Once closed a reader can no longer be updated.
*
* @return true if update read some new data.
* @throws IOException if <code>archiveName</code> could not be opened read, or closed.
*/
public boolean update(boolean doReset) throws IOException {
if (this.closed) {
return false;
}
if (!this.updateOK) {
throw new InternalGemFireException(
"update of this type of file is not supported.");
}
if (doReset) {
this.dataIn.reset();
}
int updateTokenCount = 0;
while (this.readToken()) {
updateTokenCount++;
}
return updateTokenCount != 0;
}
public void dump(PrintWriter stream) {
stream.print("archive=" + 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);
}
}
}
public File getFile() {
return this.archiveName;
}
/**
* Closes the archive.
*/
public void close() throws IOException {
if (!this.closed) {
this.closed = true;
this.is.close();
this.dataIn.close();
this.is = null;
this.dataIn = null;
int typeCount = 0;
if (this.resourceTypeTable != null) { // fix for bug 32320
for (int i = 0; i < this.resourceTypeTable.length; i++) {
if (this.resourceTypeTable[i] != null) {
if (this.resourceTypeTable[i].close()) {
this.resourceTypeTable[i] = null;
} else {
typeCount++;
}
}
}
ResourceType[] newTypeTable = new ResourceType[typeCount];
typeCount = 0;
for (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.
*/
public ArchiveInfo getArchiveInfo() {
return this.info;
}
private void readHeaderToken() throws IOException {
byte archiveVersion = dataIn.readByte();
long startTimeStamp = dataIn.readLong();
long systemId = dataIn.readLong();
long systemStartTimeStamp = dataIn.readLong();
int timeZoneOffset = dataIn.readInt();
String timeZoneName = dataIn.readUTF();
String systemDirectory = dataIn.readUTF();
String productVersion = dataIn.readUTF();
String os = dataIn.readUTF();
String machine = dataIn.readUTF();
if (archiveVersion <= 1) {
throw new GemFireIOException(
String.format("Archive version: %s is no longer supported.",
Byte.valueOf(archiveVersion)),
null);
}
if (archiveVersion > ARCHIVE_VERSION) {
throw new GemFireIOException(
String.format("Unsupported archive version: %s . The supported version is: %s .",
new Object[] {Byte.valueOf(archiveVersion), Byte.valueOf(ARCHIVE_VERSION)}),
null);
}
this.archiveVersion = archiveVersion;
this.info = new ArchiveInfo(this, archiveVersion, startTimeStamp, systemStartTimeStamp,
timeZoneOffset, timeZoneName, systemDirectory, systemId, productVersion, os, machine);
// Clear all previously read types and instances
this.resourceInstSize = 0;
this.resourceInstTable = new ResourceInst[1024];
this.resourceTypeTable = new ResourceType[256];
timeSeries.setBase(startTimeStamp);
if (dump) {
info.dump(new PrintWriter(System.out));
}
}
boolean loadType(String typeName) {
// note we don't have instance data or descriptor data yet
if (filters == null || filters.length == 0) {
return true;
} else {
for (int i = 0; i < filters.length; i++) {
if (filters[i].typeMatches(typeName)) {
return true;
}
}
return false;
}
}
boolean loadStatDescriptor(StatDescriptor stat, ResourceType type) {
// note we don't have instance data yet
if (!type.isLoaded()) {
return false;
}
if (filters == null || filters.length == 0) {
return true;
} else {
for (int i = 0; i < filters.length; i++) {
if (filters[i].statMatches(stat.getName()) && filters[i].typeMatches(type.getName())) {
return true;
}
}
stat.unload();
return false;
}
}
boolean loadInstance(String textId, long numericId, ResourceType type) {
if (!type.isLoaded()) {
return false;
}
if (filters == null || filters.length == 0) {
return true;
} else {
for (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;
}
}
}
}
}
}
return false;
}
}
boolean loadStat(StatDescriptor stat, ResourceInst resource) {
ResourceType type = resource.getType();
if (!resource.isLoaded() || !type.isLoaded() || !stat.isLoaded()) {
return false;
}
if (filters == null || filters.length == 0) {
return true;
} else {
String textId = resource.getName();
long numericId = resource.getId();
for (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 {
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.assertTrue(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.assertTrue(resourceInstTable[resourceInstId] == null);
if ((resourceInstId + 1) > this.resourceInstSize) {
this.resourceInstSize = resourceInstId + 1;
}
ResourceType type = resourceTypeTable[resourceTypeId];
if (type == null) {
throw new IllegalStateException("ResourceType is missing for resourceTypeId "
+ resourceTypeId + ", resourceName " + name);
}
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(String.format("unexpected typeCode value %s",
Byte.valueOf(stats[i].getTypeCode())));
}
resourceInstTable[resourceInstId].initialValue(i, v);
}
}
}
private void readResourceInstanceDeleteToken() throws IOException {
int resourceInstId = dataIn.readInt();
Assert.assertTrue(resourceInstTable[resourceInstId] != null);
resourceInstTable[resourceInstId].makeInactive();
if (dump) {
System.out.println("Delete resource instance " + resourceInstId);
}
}
private int readResourceInstId() throws IOException {
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 {
return StatArchiveWriter.readCompactValue(this.dataIn);
}
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(String.format("unexpected typeCode value %s",
Byte.valueOf(stats[statOffset].getTypeCode())));
}
if (resourceInstTable[resourceInstId].addValueSample(statOffset, statDeltaBits)) {
if (dump) {
System.out.print(" [" + statOffset + "]=" + statDeltaBits);
}
}
statOffset = dataIn.readUnsignedByte();
}
if (dump) {
System.out.println();
}
resourceInstId = readResourceInstId();
}
timeSeries.addTimeStamp(millisSinceLastSample);
for (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(String.format("Unexpected token byte value: %s",
Byte.valueOf(token)));
}
return true;
} catch (EOFException ignore) {
return false;
}
}
/**
* Returns the approximate amount of memory used to implement this object.
*/
protected int getMemoryUsed() {
int result = 0;
for (int i = 0; i < resourceInstTable.length; i++) {
if (resourceInstTable[i] != null) {
result += resourceInstTable[i].getMemoryUsed();
}
}
return result;
}
}
private static NumberFormat getNumberFormat() {
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
nf.setGroupingUsed(false);
return nf;
}
}