/**
* 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.destination.chronicletimeseries;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.apache.log4j.Logger;
import org.helios.apmrouter.catalog.EntryStatus;
import org.helios.apmrouter.catalog.EntryStatus.EntryStatusChange;
import org.helios.apmrouter.catalog.jdbc.h2.MetricTrigger;
import org.helios.apmrouter.catalog.jdbc.h2.NewElementTriggers;
import org.helios.apmrouter.jmx.JMXHelper;
import org.helios.apmrouter.metric.IMetric;
import org.helios.apmrouter.tsmodel.Tier;
import org.helios.apmrouter.util.SystemClock;
import vanilla.java.chronicle.Excerpt;
import vanilla.java.chronicle.impl.IndexedChronicle;
import vanilla.java.chronicle.impl.UnsafeExcerpt;
/**
* <p>Title: ChronicleTier</p>
* <p>Description: Represents one chronicle time-series tier.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.destination.chronicletimeseries.ChronicleTier</code></p>
*/
public class ChronicleTier implements ChronicleTierMXBean, NotificationListener {
/** The chronicle file name */
protected final String chronicleName;
/** The tier pattern */
protected final String pattern;
/** The chronicle parth */
protected final String chroniclePath;
/** The chronicle */
protected final IndexedChronicle chronicle;
/** The parent tier (if one exists) */
protected final ChronicleTier parent;
/** Instance logger */
protected final Logger log;
/** The chronicle time-series manager */
protected final ChronicleTSManager manager;
/** The number of periods in this tier */
protected final int periods;
/** The duration of each period in this tier in seconds */
protected final long periodDuration;
/** The duration of each period in this tier in ms */
protected final long periodDurationMs;
/** The size of each chronicle entry in this tier */
protected final int entrySize;
/** The JMX ObjectName for this tier */
protected final ObjectName objectName;
/** The earliest period end time in this tier */
protected final AtomicLong startPeriod = new AtomicLong(Long.MAX_VALUE);
/** The latest period end time in this tier */
protected final AtomicLong endPeriod = new AtomicLong(Long.MIN_VALUE);
/** The number of metric offline notifications received */
protected final AtomicLong offlineNotifications = new AtomicLong(0);
/** The number of values in each series entry */
protected static final int SERIES_SIZE_IN_LONGS = 5;
/** The header offset in each chronicle entry, ie. the length of the start time (long), end time (long), the size (int) and the status (byte) */
protected static final int HEADER_OFFSET = 8 + 8 + 4 + 1;
// /** The header offset in each chronicle entry, ie. the length of the start time (long), end time (long), the size (int) */
// protected static final int HEADER_OFFSET = 8 + 8 + 4;
/** The size of each series entry, ie. longs for TS, MIN, MAX, AVG and CNTS */
protected static final int SERIES_SIZE_IN_BYTES = SERIES_SIZE_IN_LONGS * 8;
/** The chronicle home directory. We're storing them in the same sub-dir as the H2 metric catalog */
public static final File CHRONICLE_HOME_DIR = new File(System.getProperty("user.home") + File.separator + ".apmrouter" + File.separator + "h2" + File.separator + "time-series");
/** The default chronicle databit size estimate */
public static final int CHRONICLE_SIZE_EST = 10;
// ===================================================
// Series entry index
// ===================================================
/** The array index for the period */
public static final int PERIOD = 0;
/** The array index for the min value */
public static final int MIN = 1;
/** The array index for the max value */
public static final int MAX = 2;
/** The array index for the avg value */
public static final int AVG = 3;
/** The array index for the count value */
public static final int CNT= 4;
// ===================================================
// Series header offsets
// ===================================================
/** The series offset for the header start time */
public static final int H_START = 0;
/** The series offset for the header end time */
public static final int H_END = H_START + 8; //8;
/** The series offset for the header entry count */
public static final int H_SIZE = H_END + 8; //16;
/** The series offset for the header entry status */
public static final int H_STATUS = H_SIZE + 4;
/** The JMX ObjectName's prefix to which the tier name is appended to create the full object name */
public static final String OBJECT_NAME_PREFIX = "org.helios.apmrouter.timeseries:type=chronicle,name=";
/** The JMX ObjectName for the live tier */
public static final ObjectName LIVE_TIER_OBJECT_NAME = JMXHelper.objectName("org.helios.apmrouter.timeseries:type=chronicle,name=live");
/** The JMX notification type for metric status updates */
public static final String ENTRY_STATUS_UPDATE_TYPE = "metric.event.statechange";
/** The JMX ObjectName for the MetricTrigger */
public static final ObjectName METRIC_TRIGGER_OBJECT_NAME = JMXHelper.objectName(NewElementTriggers.class.getPackage().getName(), "trigger", MetricTrigger.class.getSimpleName(), "type", "*UPDATE*");
/**
* Creates a new ChronicleTier and initializes the underlying chronicle
* @param tier The time-series model tier for this ChronicleTier
* @param parentTier The parent ChronicleTier for this tier or null if it has no parent
* @param tsManager The ChronicleTSManager
*/
ChronicleTier(Tier tier, ChronicleTier parentTier, ChronicleTSManager tsManager) {
chronicleName = tier.getName();
pattern = tier.getPattern();
log = Logger.getLogger(getClass().getName() + "." + chronicleName);
parent = parentTier;
manager = tsManager;
periods = (int) tier.getPeriodCount();
periodDuration = tier.getPeriodDuration().seconds;
periodDurationMs = TimeUnit.MILLISECONDS.convert(periodDuration, TimeUnit.SECONDS);
objectName = JMXHelper.objectName(new StringBuilder(OBJECT_NAME_PREFIX).append(chronicleName));
entrySize = HEADER_OFFSET + (SERIES_SIZE_IN_BYTES * periods);
if(!CHRONICLE_HOME_DIR.exists()) {
if(!CHRONICLE_HOME_DIR.mkdir()) {
throw new RuntimeException("Failed to create chronicle ts home directory [" + CHRONICLE_HOME_DIR + "]", new Throwable());
}
} else {
if(!CHRONICLE_HOME_DIR.isDirectory()) {
throw new RuntimeException("chronicle ts home directory [" + CHRONICLE_HOME_DIR + "] is a file not a directory", new Throwable());
}
}
chroniclePath = CHRONICLE_HOME_DIR + File.separator + chronicleName;
try {
chronicle = new IndexedChronicle(chroniclePath, CHRONICLE_SIZE_EST);
chronicle.useUnsafe(true);
initSeries();
JMXHelper.getHeliosMBeanServer().registerMBean(this, objectName);
} catch (Exception e) {
throw new RuntimeException("Failed to create chronicle on path [" + chroniclePath + "]", e);
}
// Register for notifications from the metric trigger
try {
for(ObjectName mtName: JMXHelper.getHeliosMBeanServer().queryNames(METRIC_TRIGGER_OBJECT_NAME, null)) {
JMXHelper.getHeliosMBeanServer().addNotificationListener(mtName, this, new NotificationFilter(){
/** */
private static final long serialVersionUID = -8194483721587020208L;
@Override
public boolean isNotificationEnabled(Notification notification) {
return ENTRY_STATUS_UPDATE_TYPE.equals(notification.getType());
}
}, null);
}
} catch (Exception e) {
throw new RuntimeException("Failed to register as MetricTrigger notification listener", e);
}
log.info("Initialized chronicle [" + chronicle.name() + "] on path [" + chroniclePath + "] with size [" + chronicle.size() + "]");
}
/**
* Initializes the tier headers
*/
protected void initSeries() {
if(chronicle.size()<1) return;
log.info("Initializing [" + chronicle.size() + "] entries");
for(long key = 0; key < chronicle.size(); key++) {
UnsafeExcerpt<IndexedChronicle> ex = createUnsafeExcerpt(key) ;
long startTime = ex.readLong();
long endTime = ex.readLong();
tickPeriods(startTime, endTime);
ex.finish();
}
log.info("Initialized tier [" + chronicleName + "] with [" + chronicle.size() + "] entries");
}
/**
* Fires a status change event to all registered listeners through the manager
* @param changeMap the map containing the status changes
*/
protected void fireEventStatusChangeEvent(final Map<EntryStatus, EntryStatusChange> changeMap) {
manager.fireEventStatusChangeEvent(changeMap);
}
/** Colon Splitter Pattern */
public static final Pattern COLON_SPLITTER = Pattern.compile(":");
/**
* <p>Listener for notifications from the H2 metric table trigger indicating a status change for a metric.</p>
* {@inheritDoc}
* @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
*/
@Override
public void handleNotification(Notification notification, Object handback) {
if(notification!=null && ENTRY_STATUS_UPDATE_TYPE.equals(notification.getType())) {
try {
String[] msg = COLON_SPLITTER.split(notification.getMessage());
long metricId = Long.parseLong(msg[0]);
byte status = Byte.parseByte(msg[1]);
EntryStatus es = EntryStatus.forByte(status);
triggeredStatusUpdate(metricId, es);
offlineNotifications.incrementAndGet();
} catch (Exception ex) {
log.error("Failed to process entry change notification", ex);
}
}
}
/**
* Returns the total number of metric offline notifications received.
* @return the total number of metric offline notifications received.
*/
public long getOffLineNotificationCount() {
return offlineNotifications.incrementAndGet();
}
/**
* Returns a list containing the period data arrays in this tier for the passed metric Id.
* @param metricId The metric Id
* @return A list of long arrays
*/
public List<long[]> getValues(long metricId) {
UnsafeExcerpt<IndexedChronicle> ex = createUnsafeExcerpt(metricId);
try {
int size = ex.readInt(H_SIZE);
List<long[]> results = new ArrayList<long[]>(size-1);
//StringBuilder b = new StringBuilder("Data series for [" + metricId + "]");
// int cnt = 0;
for(int i = size-1; i > 0; i--) {
long[] pData = ex.readLongArray(HEADER_OFFSET + (i*SERIES_SIZE_IN_BYTES) , SERIES_SIZE_IN_LONGS);
results.add(pData);
//b.append("\n\tRownum:").append(i).append(":").append(r(pData));
// cnt++;
}
// b.append("\n\tCount:").append(cnt);
// log.info(b);
return results;
} finally {
ex.finish();
}
}
/**
* Returns the entry status for the passed metric Id
* @param metricId The metric Id to get the status for
* @return the entry status
*/
public EntryStatus getEntryStatus(long metricId) {
UnsafeExcerpt<IndexedChronicle> ex = createUnsafeExcerpt(metricId);
try {
//return EntryStatus.ACTIVE;
return EntryStatus.forByte(ex.readByte(H_STATUS));
} finally {
ex.finish();
}
}
/**
* Returns the entry status name for the passed metric Id
* @param metricId The metric Id to get the status for
* @return the entry status name
*/
@Override
public String getEntryStatusName(long metricId) {
return getEntryStatus(metricId).name();
}
/**
* Adds a new value to the corresponding series in this tier
* @param metric The metric add values into the series from
* @return the rolled value or null if this operation updates the current period
*/
public long[] addValue(IMetric metric) {
try {
long metricId = metric.getToken(), timestamp = metric.getTime();
if(metricId<0) throw new IllegalArgumentException("The metric ID cannot be < 0", new Throwable());
UnsafeExcerpt<IndexedChronicle> ex = createUnsafeExcerpt(metricId);
final long period = SystemClock.period(periodDurationMs, timestamp);
ex.readLong(); ex.readLong();
int pCount = ex.readInt();
if(pCount>this.periods || pCount<0) {
SeriesEntry se = new SeriesEntry(createUnsafeExcerpt(), metric.getToken(), false);
Throwable t = new Throwable();
t.printStackTrace(System.err);
throw new RuntimeException("Read pCount was [" + pCount + "] but tier count is [" + this.periods + "] for metric [" + metric.getToken() + "/" + metric.getFQN() + "]\n\tSeries Dump:" + se , new Throwable());
}
if(pCount==0) {
ex.finish();
writeNewPeriod(period, metric);
tickPeriods(period, period);
return null;
}
int periodIndex = getPeriodIndex(metric.getToken(), period);
switch(periodIndex) {
case -1:
return null;
case 0:
ex.finish();
writeNewPeriod(period, metric);
break;
case 1:
updateCurrentPeriod(ex, metric, true);
break;
case 2:
// roll and update
return rollAndMerge(pCount, period, metric, ex);
//break;
default:
log.warn("Unexpected period index ["+ periodIndex + "]");
}
} catch (Throwable t) {
log.error("Add Value Error:", t);
}
return null;
}
/**
* Triggers a period roll, where the existing period data is rolled one period to the right and a new period is initialized in the current slot.
* @param currentSize The current number of periods in the series
* @param period The period of the incoming metric
* @param metric The metric to write
* @param ex The excerpt to use
* @return the values of the prior period that was rolled into the next slot
*/
protected long[] rollAndMerge(int currentSize, long period, IMetric metric, UnsafeExcerpt<IndexedChronicle> ex) {
if((currentSize)>this.periods || (currentSize)<1) {
Throwable t = new Throwable();
t.printStackTrace(System.err);
throw new RuntimeException("Attempted to set size to [" + (currentSize+1) + "] but tier count is [" + this.periods + "]", new Throwable());
}
final int rollSize;
final boolean incPos;
if(currentSize<this.periods) {
rollSize = currentSize;
incPos = true;
} else {
rollSize = currentSize-1;
incPos = false;
}
long[] retValues = ex.insertNewPeriod(incPos, SERIES_SIZE_IN_LONGS, rollSize, HEADER_OFFSET, new long[]{period, Long.MAX_VALUE,Long.MIN_VALUE,0,0});
ex.writeLongArray(H_START, new long[]{period, (period + this.periodDurationMs)});
tickPeriods(period, period + this.periodDurationMs);
updateCurrentPeriod(ex, metric, false);
if(incPos) ex.writeInt(H_SIZE, (currentSize+1));
ex.finish();
return retValues;
}
/**
* Creates and writes the passed metric into a new period
* @param period The period
* @param metric The metric to write
*/
protected void writeNewPeriod(long period, IMetric metric) {
UnsafeExcerpt<IndexedChronicle> ex = createUnsafeExcerpt(metric.getToken());
ex.writeLongArray(new long[]{period, period + this.periodDurationMs});
ex.writeInt(1);
byte priorStatus = ex.readByte(H_STATUS);
ex.writeByte(EntryStatus.ACTIVE.byteOrdinal());
long value = metric.getLongValue();
ex.writeLongArray(new long[]{period, value, value, value, 1});
ex.finish();
if(priorStatus!=EntryStatus.ACTIVE.byteOrdinal()) {
fireEventStatusChangeEvent(EntryStatus.EntryStatusChange.getChangeMap(SystemClock.time(), metric.getMetricId().getToken(), EntryStatus.ACTIVE));
}
}
/**
* Writes the passed metric into the current period
* @param ex The excerpt to use
* @param metric The metric to write
* @param finish If true, the write is committed, otherwise, the excerpt is left open
*/
protected void updateCurrentPeriod(UnsafeExcerpt<IndexedChronicle> ex, IMetric metric, boolean finish) {
long[] values = ex.readLongArray(HEADER_OFFSET, SERIES_SIZE_IN_LONGS);
long val = metric.getLongValue();
if(val < values[MIN]) values[MIN] = val;
if(val > values[MAX]) values[MAX] = val;
if(values[CNT]==0) {
values[AVG] = val;
} else {
long tmpTotal = values[AVG]+val;
values[AVG] = tmpTotal==0 ? 0 : tmpTotal/2;
}
values[CNT]++;
byte priorStatus = ex.readByte(H_STATUS);
ex.write(H_STATUS, EntryStatus.ACTIVE.byteOrdinal());
ex.writeLongArray(HEADER_OFFSET, values);
if(finish) {
ex.finish();
}
if(priorStatus!=EntryStatus.ACTIVE.byteOrdinal()) {
fireEventStatusChangeEvent(EntryStatus.EntryStatusChange.getChangeMap(SystemClock.time(), metric.getMetricId().getToken(), EntryStatus.ACTIVE));
}
}
/**
* Finds the starting and ending periods within the periods in the passed excerpt
* @param ex The excerpt which has already been set to appropriate index
* @return a long array with the starting date and ending date
*/
protected long[] getPeriodBoundaries(UnsafeExcerpt<IndexedChronicle> ex) {
int size = ex.readInt(H_SIZE);
int offset = (size-1) * SERIES_SIZE_IN_BYTES;
return new long[]{
ex.readLong(HEADER_OFFSET + offset),
ex.readLong(HEADER_OFFSET)
};
}
/**
* Returns the chronicle entry index that the passed period should be merged into, or -1 for a drop.
* Currently only returns an index if the merge is for the current period, or the next.
* At some point, may merge into earlier periods.
* @param metricId The metric Id to get the period index for
* @param period The period to get the index for
* @return the chronicle entry index or -1 for a drop
*/
protected int getPeriodIndex(long metricId, long period) {
if(chronicle.size()==0) return 0;
long firstIndex = createUnsafeExcerpt(metricId).readLong(HEADER_OFFSET);
if(period==firstIndex) return 1;
if(period>firstIndex) return 2;
return -1;
}
/**
* Ticks up the tier's earliest and latest period timestamp highwaters
* @param start The start time
* @param end The end time
*/
protected void tickPeriods(long start, long end) {
long endp = endPeriod.get();
if(end>endp) endPeriod.set(end);
long startp = startPeriod.get();
if(start<startp) startPeriod.set(start);
}
/**
* Returns the series for the passed metric ID
* @param metricId The metric ID to get the series for
* @return the series pojo
*/
@Override
public SeriesEntryMBean getSeries(long metricId) {
return new SeriesEntry(createUnsafeExcerpt(), metricId, true);
}
/**
* Performs a status check on this entry.
* If the state requires updating, the new state will be returned,
* otherwise null will be returned.
* @param metricId The metric id of the entry to check.
* @param currentTime The effective time of this check in ms.
* @param staleThreshold The maximum age of the entry in ms. before it is marked stale
* @param offLineThreshold The maximum age of the entry in ms. before it is marked offline
* @return the changed state or null if there was no change.
*/
public EntryStatus statusCheck(long metricId, long currentTime, long staleThreshold, long offLineThreshold) {
SeriesEntry se = new SeriesEntry(createUnsafeExcerpt(), metricId, false);
final long elapsed = currentTime-se.getStartPeriodTimestamp();
final EntryStatus status = se.getEntryStatus();
if(elapsed >= offLineThreshold) {
if(status!=EntryStatus.OFFLINE) {
se.updateStatus(EntryStatus.OFFLINE);
return EntryStatus.OFFLINE;
}
} else if(elapsed >= staleThreshold) {
if(status!=EntryStatus.STALE) {
se.updateStatus(EntryStatus.STALE);
return EntryStatus.STALE;
}
}
return null;
}
/**
* An update of a metric's entry status in the live tier triggered by the h2 metric table trigger.
* Since this is coming from the metric table, no event is needed.
* @param metricId The id of the metric to update
* @param status The status to update to
*/
public void triggeredStatusUpdate(long metricId, EntryStatus status) {
try {
SeriesEntry se = new SeriesEntry(createUnsafeExcerpt(), metricId, false);
se.updateStatus(status);
} catch (Exception ex) {
/* No Op */
}
}
/**
* Reads a series entry from the series for the passed index
* @param index The chronicle index to read the series for
* @param seriesIndex The index of the series entry to read
* @return the series entry
*/
protected long[] readSeriesEntry(long index, int seriesIndex) {
if(seriesIndex<0) throw new IllegalArgumentException("The index cannot be < 0", new Throwable());
if(index<0) throw new IllegalArgumentException("The metric ID cannot be < 0", new Throwable());
return createUnsafeExcerpt(index).readLongArray(HEADER_OFFSET + (seriesIndex * SERIES_SIZE_IN_BYTES), SERIES_SIZE_IN_LONGS);
}
/**
* Writes a series entry for the passed index
* @param index The chronicle index to write the series for
* @param seriesIndex The series index of the series entry to write
* @param values The values to write
*/
protected void writeSeriesEntry(long index, int seriesIndex, long[] values) {
if(seriesIndex<0) throw new IllegalArgumentException("The index cannot be < 0", new Throwable());
if(index<0) throw new IllegalArgumentException("The metric ID cannot be < 0", new Throwable());
UnsafeExcerpt<IndexedChronicle> ex = createUnsafeExcerpt(index);
EntryStatus status = EntryStatus.forByte(ex.readByte(H_STATUS));
ex.writeLongArray(HEADER_OFFSET + (seriesIndex * SERIES_SIZE_IN_BYTES), values);
if(status!=EntryStatus.ACTIVE) {
ex.write(H_STATUS, EntryStatus.ACTIVE.byteOrdinal());
fireEventStatusChangeEvent(EntryStatus.EntryStatusChange.getChangeMap(SystemClock.time(), index, EntryStatus.ACTIVE));
}
}
/**
* Reads the series start and end timestamps
* @param ex The currently placed excerpt
* @return the series start and end timestamps
*/
protected long[] getSeriesTimes(UnsafeExcerpt<IndexedChronicle> ex) {
return ex.readLongArray(H_START, 2);
}
/**
* Reads the series start and end timestamps for the passed chronicle index
* @param index the index of the chronicle to read from
* @return the series start and end timestamps
*/
protected long[] getSeriesTimes(long index) {
return getSeriesTimes(createUnsafeExcerpt(index));
}
/**
* Reads the series start timestamp
* @param ex The currently placed excerpt
* @return the series start timestamp
*/
protected long getSeriesStartTime(UnsafeExcerpt<IndexedChronicle> ex) {
return ex.readLong(H_START);
}
/**
* Reads the series start timestamp for the passed chronicle index
* @param index the index of the chronicle to read from
* @return the series start timestamp
*/
protected long getSeriesStartTime(long index) {
return getSeriesStartTime(createUnsafeExcerpt(index));
}
/**
* Reads the series end timestamp
* @param ex The currently placed excerpt
* @return the series end timestamp
*/
protected long getSeriesEndTime(UnsafeExcerpt<IndexedChronicle> ex) {
return ex.readLong(H_END);
}
/**
* Reads the series end timestamp for the passed chronicle index
* @param index the index of the chronicle to read from
* @return the series end timestamp
*/
protected long getSeriesEndTime(long index) {
return getSeriesEndTime(createUnsafeExcerpt(index));
}
/**
* Reads the series entry count
* @param ex The currently placed excerpt
* @return the series entry count
*/
protected long getSeriesEntryCount(UnsafeExcerpt<IndexedChronicle> ex) {
return ex.readInt(H_SIZE);
}
/**
* Reads the series entry count for the passed chronicle index
* @param index the index of the chronicle to read from
* @return the series entry count
*/
protected long getSeriesEntryCount(long index) {
return getSeriesEntryCount(createUnsafeExcerpt(index));
}
/**
* Creates a new metric chronicle entry and populates it
* @return the index of the new entry
*/
public synchronized long createNewMetric() {
try {
Excerpt<IndexedChronicle> ex = chronicle.createExcerpt();
ex.startExcerpt(entrySize);
long time = SystemClock.period(periodDuration);
ex.writeLong(time);
ex.writeLong(time);
ex.writeInt(0);
ex.writeByte(EntryStatus.ACTIVE.byteOrdinal());
int extendSize = periods * SERIES_SIZE_IN_LONGS;
for(int i = 0; i < extendSize; i++) {
ex.writeLong(-1L);
}
ex.finish();
return ex.index();
} catch (Exception ex) {
log.error("Failed to create new metric entry", ex);
throw new RuntimeException("Failed to create new metric entry", ex);
}
}
/**
* Dumps a formatted output of the excerpt at the passed index
* @param index The index to dump
* @param includePeriods Defines if the dump should include period data
* @return A formatted string
*/
@Override
public String dump(long index, boolean includePeriods) {
String s = new SeriesEntry(createUnsafeExcerpt(), index, includePeriods).toString();
return s;
}
/**
* Returns the name of this chronicle tier
* @return the name of this chronicle tier
*/
@Override
public String getName() {
return chronicle.name();
}
/**
* Returns the size of this chronicle tier
* @return the size of this chronicle tier
*/
@Override
public long getSize() {
return chronicle.size();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.destination.chronicletimeseries.ChronicleTierMXBean#getPerMetricDataSize()
*/
@Override
public long getPerMetricDataSize() {
return periods * SERIES_SIZE_IN_BYTES;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.destination.chronicletimeseries.ChronicleTierMXBean#getPerMetricSize()
*/
@Override
public long getPerMetricSize() {
return (periods * SERIES_SIZE_IN_BYTES) + HEADER_OFFSET;
}
/**
* Creates a new excerpt for this chronicle tier
* @return a new excerpt for this chronicle tier
*/
public Excerpt<IndexedChronicle> createExcerpt() {
return chronicle.createExcerpt();
}
/**
* Creates a new unsafe excerpt for this chronicle tier
* @return a new unsafe excerpt for this chronicle tier
* @see vanilla.java.chronicle.impl.IndexedChronicle#createUnsafeExcerpt()
*/
public UnsafeExcerpt<IndexedChronicle> createUnsafeExcerpt() {
return chronicle.createUnsafeExcerpt();
}
/**
* Creates a new unsafe excerpt for this chronicle tier and attempts to set the index on it.
* @param index The index to set the excerpt to
* @return a new unsafe excerpt for this chronicle tier with the index set
* @throws IllegalArgumentException thrown if the index does not exist in this chronicle
* @see vanilla.java.chronicle.impl.IndexedChronicle#createUnsafeExcerpt()
*/
public UnsafeExcerpt<IndexedChronicle> createUnsafeExcerpt(long index) {
UnsafeExcerpt<IndexedChronicle> ex = chronicle.createUnsafeExcerpt();
if(!ex.index(index)) throw new IllegalArgumentException("Failed to set the excerpt index to [" + index + "] in tier [" + chronicleName + "]", new Throwable());
return ex;
}
/**
* Returns the positional index after setting it to the passed index
* @param indexId The index to set
* @return The new positional index
* @see vanilla.java.chronicle.impl.IndexedChronicle#getIndexData(long)
*/
public long getIndexData(long indexId) {
return chronicle.getIndexData(indexId);
}
/**
* Returns the underlying byte buffer for the chronicle
* @param startPosition The position to set on the buffer
* @return The underlying byte buffer
* @see vanilla.java.chronicle.impl.IndexedChronicle#acquireDataBuffer(long)
*/
public ByteBuffer acquireDataBuffer(long startPosition) {
return chronicle.acquireDataBuffer(startPosition);
}
/**
* Sets the position in the buffer
* @param startPosition The position
* @return the new position
* @see vanilla.java.chronicle.impl.IndexedChronicle#positionInBuffer(long)
*/
public int positionInBuffer(long startPosition) {
return chronicle.positionInBuffer(startPosition);
}
/**
* Sets the index data
* @param indexId The index Id
* @param indexData The index data
* @see vanilla.java.chronicle.impl.IndexedChronicle#setIndexData(long, long)
*/
public void setIndexData(long indexId, long indexData) {
chronicle.setIndexData(indexId, indexData);
}
/**
* Starts a new excerpt
* @param capacity The capacity of the excerpt
* @return The index of the created excerpt
* @see vanilla.java.chronicle.impl.IndexedChronicle#startExcerpt(int)
*/
public long startExcerpt(int capacity) {
return chronicle.startExcerpt(capacity);
}
/**
* Increments the size of the chronicle
* @see vanilla.java.chronicle.impl.IndexedChronicle#incrSize()
*/
public void incrSize() {
chronicle.incrSize();
}
/**
* Clears the chronicle
*/
@Override
public void clear() {
chronicle.clear();
}
/**
* Closes the chronicle
*/
@Override
public void close() {
chronicle.close();
}
/**
* Returns the chronicle path
* @return the chroniclePath
*/
@Override
public String getChroniclePath() {
return chroniclePath;
}
/**
* Returns the earliest period end timestamp in this tier
* @return the earliest period end timestamp in this tier
*/
@Override
public Date getStartPeriod() {
long dt = startPeriod.get();
return dt==Long.MAX_VALUE ? null : new Date(dt);
}
/**
* Returns the latest period end timestamp in this tier
* @return the latest period end timestamp in this tier
*/
@Override
public Date getEndPeriod() {
long dt = endPeriod.get();
return dt==Long.MIN_VALUE ? null : new Date(dt);
}
/**
* Returns the size of the index data file
* @return the size of the index data file
*/
@Override
public long getIndexSize() {
return new File(chroniclePath + ".index").length();
}
/**
* Returns the size of the data file
* @return the size of the data file
*/
@Override
public long getDataSize() {
return new File(chroniclePath + ".data").length();
}
/**
* Returns the tier definition pattern
* @return the tier definition pattern
*/
@Override
public String getPattern() {
return pattern;
}
/**
* Returns the number of periods in this tier
* @return the number of periods in this tier
*/
@Override
public int getPeriodCount() {
return periods;
}
/**
* Returns the period duration in seconds for this tier
* @return the period duration in seconds for this tier
*/
@Override
public long getPeriodDuration() {
return periodDuration;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.destination.chronicletimeseries.ChronicleTierMXBean#getPeriodDurationMs()
*/
@Override
public long getPeriodDurationMs() {
return periodDurationMs;
}
/**
* Utility method to render a period value array to a readable string
* @param values The period values
* @return A formatted string
*/
protected static String r(long[] values) {
StringBuilder b = new StringBuilder("Period Values:");
if(values==null) {
b.append("null");
} else {
if(values.length!=SERIES_SIZE_IN_LONGS) {
b.append("Invalid Size:").append(Arrays.toString(values));
} else {
b.append(new Date(values[0])).append("[");
for(int i = 1; i < SERIES_SIZE_IN_LONGS; i++) {
b.append(values[i]).append(",");
}
b.deleteCharAt(b.length()-1);
b.append("]");
}
}
return b.toString();
}
}