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