/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.metric.catalog.direct;
import org.helios.apmrouter.metric.IMetric;
import org.helios.apmrouter.metric.MetricType;
import org.helios.apmrouter.metric.catalog.IDelegateMetric;
import org.helios.apmrouter.metric.catalog.direct.chronicle.ChronicleController;
import org.helios.apmrouter.util.StringHelper;
import org.helios.apmrouter.util.SystemClock;
import org.helios.apmrouter.util.SystemClock.ElapsedTime;
import vanilla.java.chronicle.Excerpt;
import vanilla.java.chronicle.impl.IndexedChronicle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.helios.apmrouter.util.Methods.nvl;
/**
* <p>Title: ChronicleICEMetric</p>
* <p>Description: An {@link IDelegateMetric} implementation that reads its values from a java-chronicle</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.metric.catalog.direct.ChronicleICEMetric</code></p>
*/
public class ChronicleICEMetric implements IDelegateMetric {
/** The excerpt to read fields from */
protected final Excerpt<IndexedChronicle> excerpt;
/** The offsets of the host, agent and metric names */
protected final int[] nameOffsets;
/** The offsets of the namespaces */
protected final int[] namespaceOffsets;
/** The chronicle index */
protected final long index;
/** The index of the offset of the flat indicator */
public static final int FLAT_POS = 0;
/** The index of the offset of the hostname */
public static final int HOST_POS = 1;
/** The index of the offset of the agent */
public static final int AGENT_POS = 2;
/** The index of the offset of the metric name */
public static final int NAME_POS = 3;
/** The index of the offset of the unmapped delegate metric */
public static final int UNMAPPED_POS = 4;
/** An empty string array */
private static final String[] EMPTY_STR_ARR = {};
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (index ^ (index >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ChronicleICEMetric other = (ChronicleICEMetric) obj;
if (index != other.index)
return false;
return true;
}
/**
* Creates a new ChronicleICEMetric
* @param index The chronicle index for this metric
* @param excerpt The excerpt to read fields from
* @param nameOffsets The offsets of the host, agent and metric names
* @param namespaceOffsets The offsets of the namespaces
*/
public ChronicleICEMetric(long index, Excerpt<IndexedChronicle> excerpt, int[] nameOffsets, int[] namespaceOffsets) {
this.index = index;
this.excerpt = excerpt;
this.nameOffsets = nameOffsets;
this.namespaceOffsets = namespaceOffsets;
}
/**
* Creates a new ChronicleICEMetric
* @param index The chronicle index for this metric
*/
public ChronicleICEMetric(long index) {
this(ChronicleController.getInstance().createExcerpt(), index);
}
/**
* Creates a new ChronicleICEMetric
* @param ex The excerpt to use
* @param index The chronicle index for this metric
*/
public ChronicleICEMetric(Excerpt<IndexedChronicle> ex, long index) {
this.index = index;
excerpt = ex;
if(!excerpt.index(index)) throw new IllegalStateException("No metric for index [" + index + "]", new Throwable());
nameOffsets = new int[5];
excerpt.position(0);
excerpt.readLong(); // the token
excerpt.readInt(); // the type
namespaceOffsets = new int[excerpt.readInt()]; // offset is the token (8) and the type (4)
nameOffsets[FLAT_POS] = excerpt.position();
excerpt.readByte();
//excerpt.skipBytes(9); // skip two ints and a byte
nameOffsets[HOST_POS] = excerpt.position();
excerpt.skipBytes(excerpt.readInt());
nameOffsets[AGENT_POS] = excerpt.position();
excerpt.skipBytes(excerpt.readInt());
nameOffsets[NAME_POS] = excerpt.position();
excerpt.skipBytes(excerpt.readInt());
for(int i = 0; i < namespaceOffsets.length; i++) {
namespaceOffsets[i] = excerpt.readInt();
excerpt.skipBytes(namespaceOffsets[i]);
}
nameOffsets[UNMAPPED_POS] = excerpt.position();
}
/**
* Writes a new metric to the chronicle. The chronicle entry format is: <ol>
* <li>An <b>int</b> for the MetricType<li>
* <li>An <b>int</b> for the number of namespace entries<li>
* <li>A <b>byte</b> indicating if the namespace is:<ol>
* <li><b>flat</b>:1</li>
* <li><b>mapped</b>:0</li>
* </ol><li>
* <li>The host name as a <b>UTF String</b><li>
* <li>The agent name as a <b>UTF String</b><li>
* <li>The metric name as a <b>UTF String</b><li>
* <li>The namespaces as <b>UTF String</b>s<li>
* </ol>
* @param host The metric host name
* @param agent The metric agent
* @param name The metric name
* @param type The metric type
* @param namespace The metric namespace
* @return the ChronicleICEMetric that reads its values from the chronicle
* FIXME: the record structure above is WAY out of date
*/
public static ChronicleICEMetric newInstance(String host, String agent, CharSequence name, MetricType type, CharSequence...namespace) {
try {
nvl(host, "Host Name");
nvl(agent, "Agent Name");
nvl(agent, "Metric Name");
int exSize = 40 + 4 + 4 + 8; // the size of the unmapped long reference, the ordinal int, the ns size int and 8 ints used to track the name lengths & offsets
int[] nameOffsets = new int[4];
int[] nameLengths = new int[]{0, host.trim().getBytes().length, agent.trim().getBytes().length, name.toString().trim().getBytes().length};
int[] namespaceOffsets;
int[] namespaceLengths;
byte flat = 1;
List<String> ns = new ArrayList<String>(namespace==null ? 0 : namespace.length);
int offind = 0;
if(namespace!=null) {
for(CharSequence cs: namespace) {
if(cs==null) continue;
String s = cs.toString();
if(s.trim().isEmpty()) continue;
ns.add(s.trim());
if(s.trim().indexOf('=')!=-1) flat = 0;
}
}
exSize += (ns.size()*8); // Adding space for an additional 2 ints per namespace
namespaceOffsets = new int[ns.size()];
namespaceLengths = new int[ns.size()];
for(int i = 0; i < ns.size(); i++) {
namespaceLengths[i] = ns.get(i).getBytes().length;
exSize += namespaceLengths[i];
}
// Calculate the total size of the excerpt to be written
// DataOutputStream daos = new DataOutputStream(new OutputStream(){
// @Override
// public void write(int b) throws IOException {}
// });
// daos.writeUTF(host);
// daos.writeUTF(agent);
// daos.writeUTF(name);
// for(String s: ns) {
// daos.writeUTF(s);
// if(s.indexOf('=')!=-1) flat = 0;
// }
Excerpt<IndexedChronicle> ex = ChronicleController.getInstance().createExcerpt();
ex.startExcerpt(exSize+20);
ex.position(0);
ex.writeLong(-1L); // the initial token
ex.writeInt(type.ordinal());
ex.writeInt(ns.size());
nameOffsets[FLAT_POS] = ex.position();
ex.write(flat);
nameOffsets[HOST_POS] = ex.position();
ex.writeInt(nameLengths[HOST_POS]);
ex.write(host.getBytes());
nameOffsets[AGENT_POS] = ex.position();
ex.writeInt(nameLengths[AGENT_POS]);
ex.write(agent.getBytes());
nameOffsets[NAME_POS] = ex.position();
ex.writeInt(nameLengths[NAME_POS]);
ex.write(name.toString().trim().getBytes());
offind = 0;
for(String s: ns) {
namespaceOffsets[offind] = ex.position();
ex.writeInt(namespaceLengths[offind]);
ex.write(s.getBytes());
offind++;
}
ex.writeLong(-1L); // until an unmapped is requested
ex.finish();
return new ChronicleICEMetric(ex.index(), ex, nameOffsets, namespaceOffsets);
} catch (Exception e) {
throw new RuntimeException("Failed to create new ChronicleICEMetric", e);
}
}
/**
* Reads a string from the excerpt at the specified offset
* @param offset The offset to read the string from
* @return the read string
*/
protected String readString(int offset) {
byte[] bytes = new byte[excerpt.readInt(offset)];
excerpt.position(offset+4);
excerpt.readFully(bytes);
return new String(bytes);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getHost()
*/
@Override
public String getHost() {
return readString(nameOffsets[HOST_POS]);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getAgent()
*/
@Override
public String getAgent() {
return readString(nameOffsets[AGENT_POS]);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getName()
*/
@Override
public String getName() {
return readString(nameOffsets[NAME_POS]);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getType()
*/
@Override
public MetricType getType() {
return MetricType.valueOf(excerpt.readInt(8));
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getNamespace()
*/
@Override
public String[] getNamespace() {
if(namespaceOffsets.length==0) return EMPTY_STR_ARR;
String[] ns = new String[namespaceOffsets.length];
for(int i = 0; i < namespaceOffsets.length; i++) {
ns[i] = readString(namespaceOffsets[i]);
}
return ns;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getNamespaceF()
*/
public String getNamespaceF() {
String[] ns = getNamespace();
if(ns.length==0) return "";
return StringHelper.fastConcatAndDelim(IMetric.NSDELIM, ns);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getFQN()
*/
public String getFQN() {
return String.format(FQN_FORMAT, getHost(), getAgent(), getNamespaceF(), getName());
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#isFlat()
*/
@Override
public boolean isFlat() {
return excerpt.readByte(nameOffsets[FLAT_POS])==1;
}
/**
* Returns the serialization token for this IMetric
* @return the serialization token for this IMetric or -1 if one has not been assigned
*/
@Override
public long getToken() {
return excerpt.readLong(0);
}
/**
* Returns the ID of the unmapped version of this metric.
* If this metric is not mapped, this will be -1
* @return the ID of the unmapped version of this metric.
*/
protected long getUnmappedId() {
return excerpt.readLong(nameOffsets[UNMAPPED_POS]);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#unmap()
*/
@Override
public IDelegateMetric unmap() {
if(!isMapped()) return this;
long mappedId = getUnmappedId();
ChronicleICEMetric unmapped = null;
if(mappedId==-1) {
synchronized(this) {
mappedId = getUnmappedId();
if(mappedId==-1) {
String[] namespace = getNamespace();
String[] unmappedNamespace = new String[namespace.length];
for(int i = 0; i < namespace.length; i++) {
int _index = namespace[i].indexOf("=");
unmappedNamespace[i] = _index==-1 ? namespace[i] : namespace[i].substring(_index+1);
}
unmapped = newInstance(getHost(), getAgent(), getName(), getType(), unmappedNamespace);
excerpt.writeLong(nameOffsets[UNMAPPED_POS], unmapped.index);
}
}
}
if(unmapped == null) {
return new ChronicleICEMetric(mappedId);
}
return unmapped;
}
/**
* Sets the serialization token for this IMetric
* @param token the serialization token for this IMetric
*/
@Override
public void setToken(long token) {
if(!excerpt.index(index)) {
throw new IllegalStateException("Failed to set index to [" + index + "]", new Throwable());
}
excerpt.writeLong(0, token);
// excerpt.finish();
// Excerpt<IndexedChronicle> ex = excerpt.chronicle().createExcerpt();
// if(!ex.index(index)) {
// throw new IllegalStateException("Failed to set index to [" + index + "] AFTER token write", new Throwable());
// }
//
// long readToken = ex.readLong(0);
// if(readToken!=token) {
// throw new RuntimeException("Failed to validate token update. Set [" + token + "] but read back [" + readToken + "]", new Throwable());
// }
//excerpt.finish();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#getSerSize()
*/
@Override
public int getSerSize() {
if(getToken()!=-1) return 8;
// bigger than it needs to be, but fast
return excerpt.length();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.metric.catalog.IDelegateMetric#isMapped()
*/
@Override
public boolean isMapped() {
return excerpt.readByte(nameOffsets[FLAT_POS])==0;
}
public static void main(String[] args) {
log("DirectMetric Test");
System.setProperty("apmrouter.chronicle.retain", "true");
int loopCount = 1000000;
// int loopCount = 100;
for(int i = 0; i < loopCount; i++) {
ChronicleICEMetric.newInstance("MyHost" + i, "MyAgent", "metric" + i, MetricType.LONG_GAUGE, (i%2!=0) ? ("ns" + i) : ("ns" + i + "=foobar" + i));
}
ChronicleController.getInstance().clear();
ChronicleController.getInstance().useUnsafe(true);
log("Create Warmup complete");
SystemClock.startTimer();
for(int i = 0; i < loopCount; i++) {
ChronicleICEMetric.newInstance("MyHost" + i, "MyAgent", "metric" + i, MetricType.LONG_GAUGE, (i%2!=0) ? ("ns" + i) : ("ns" + i + "=foobar" + i));
}
ElapsedTime et = SystemClock.endTimer();
log("Create Test complete in " + et);
log("Create Average Per:" + et.avgNs(loopCount) + " ns.");
for(int i = 0; i < loopCount; i++) {
IDelegateMetric dim = new ChronicleICEMetric(i);
if(!("MyHost" + i).equals(dim.getHost())) {
throw new RuntimeException("Invalid Metric. Got [" + dim.getHost() + "] expected [" + ("MyHost" + i) + "]", new Throwable());
}
}
log("Lookup Warmup complete");
SystemClock.startTimer();
for(int i = 0; i < loopCount; i++) {
IDelegateMetric dim = new ChronicleICEMetric(i);
dim.setToken(i+1);
}
et = SystemClock.endTimer();
log("Set Token Test complete in " + et);
log("Set Token Average Per:" + et.avgNs(loopCount) + " ns.");
log("Closing Chronicle .....");
ChronicleController.getInstance().close();
log("Closed Chronicle");
long size = ChronicleController.getInstance().size();
log("Re-opened chronicle with size:" + size);
ChronicleController.getInstance().useUnsafe(true);
SystemClock.startTimer();
for(int i = 0; i < loopCount; i++) {
IDelegateMetric dim = new ChronicleICEMetric(i);
if(!("MyHost" + i).equals(dim.getHost())) {
throw new RuntimeException("Invalid Metric. Got [" + dim.getHost() + "] expected [" + ("MyHost" + i) + "]", new Throwable());
}
if((i+1)!=dim.getToken()) {
throw new RuntimeException("Invalid Token. Got [" + dim.getToken() + "] expected [" + (i+1) + "]", new Throwable());
}
}
et = SystemClock.endTimer();
log("Lookup Test complete in " + et);
log("Lookup Average Per:" + et.avgNs(loopCount) + " ns.");
// log("Done Creating");
// for(ChronicleICEMetric dim: metrics) {
// log(dim);
// }
// log("Done");
}
public static void log(Object msg) {
System.out.println(msg);
}
/**
* {@inheritDoc}
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ChronicleICEMetric (");
builder.append(", host=");
builder.append(getHost());
builder.append(", agent=");
builder.append(getAgent());
builder.append(", name=");
builder.append(getName());
builder.append(", namespace=");
builder.append(Arrays.toString(getNamespace()));
builder.append(")");
return builder.toString();
}
/**
* Renders the metric with some additional internal data for diagnostics
* @return a debug string render of the metric
*/
public String toDebugString() {
StringBuilder builder = new StringBuilder();
builder.append("ChronicleICEMetric [");
builder.append("offsets=");
builder.append(Arrays.toString(nameOffsets));
builder.append(", nsoffsets=");
builder.append(Arrays.toString(namespaceOffsets));
builder.append(", flat=");
builder.append(isFlat());
builder.append(", exSize=");
builder.append(excerpt.length());
builder.append(", exIndex=");
builder.append(index);
builder.append(", host=");
builder.append(getHost());
builder.append(", agent=");
builder.append(getAgent());
builder.append(", name=");
builder.append(getName());
builder.append(", namespace=");
builder.append(Arrays.toString(getNamespace()));
builder.append(", unmappedId=");
builder.append(getUnmappedId());
builder.append("]");
return builder.toString();
}
}