/**
* 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.tsmodel;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
/**
* <p>Title: Tier</p>
* <p>Description: Model and parser for one timeseries tier that is a rollup of the timeseries tier below it. The bottom timeseries tier is <b><code>live</code></b>.</p>
* <p>Shot codes for tier attributes<ul>
* <li><b>p</b>: Period</li>
* <li><b>d</b>: Duration</li>
* <li><b>c</b>: Period Count</li>
* <li><b>n</b>: Name</li>
* </ul></p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.redis.ts.cor e.Tier</code></p>
*/
public class Tier implements TierMBean {
/** The number of periods in this timeseries tier */
protected long periodCount;
/** The duration of one period in the timeseries tier */
protected Duration periodDuration;
/** The duration of one full tier rotation */
protected Duration tierDuration;
/** The name of this tier */
protected String name;
/** The level of the tier */
protected int level = -1;
/** The tier pattern */
protected final String pattern;
/** Instance logger */
protected final Logger log = Logger.getLogger(getClass());
/** The field codes for a tier definition */
public static enum FieldCode {
/** The period duration */
p,
/** The tier duration */
t,
/** The period count */
c,
/** The tier name */
n;
}
/** The regex to parse a tier expression */
public static final Pattern TIER_EXPR_REGEX = Pattern.compile("([p|t|c|n])=(?:(\\d+)([s|m|h|d|w])|(.*))", Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);
/** The regex to split a group of tier expressions */
public static final Pattern TIER_GRP_REGEX = Pattern.compile(",");
/** The live ts tier name */
public static final String LIVE_TIER = "live";
/** The default non-live tier prefix */
public static final String TIER_PREFIX = "t";
/**
* Creates a new Tier
* @param tierDef The tier definition expressions
* @param level The tier level
* @return a new Tier
*/
public static Tier newTier(String tierDef, int level) {
return new Tier(tierDef, level);
}
Tier(String tierDef, int level) {
if(tierDef==null) throw new InvalidTierDefinitionException("The passed tier definition was null");
tierDef = tierDef.trim().replace(" ", "").toLowerCase();
pattern = tierDef;
if(tierDef.isEmpty()) throw new InvalidTierDefinitionException("The passed tier definition was empty");
if(level<0) throw new InvalidTierDefinitionException("The passed tier level [" + level + "] is invalid (<0)");
if(level==0) {
name = LIVE_TIER;
}
this.level = level;
String[] expressions = TIER_GRP_REGEX.split(tierDef);
Map<String, Triplet> triplets = new LinkedHashMap<String, Triplet>(3); // we need 2 out of a possible 3 attributes to complete the tier definition
Set<FieldCode> pending = EnumSet.of(FieldCode.p, FieldCode.t, FieldCode.c);
for(String expression: expressions) {
Matcher matcher = TIER_EXPR_REGEX.matcher(expression);
if(!matcher.matches()) {
throw new InvalidTierDefinitionException("The passed tier definition contained an invalid expression [" + expression + "]");
}
long size = -1L;
String unit = null;
String name = null;
String attr = matcher.group(1);
pending.remove(FieldCode.valueOf(attr));
if("n".equals(attr)) {
name = matcher.group(4);
if(name.isEmpty()) name = TIER_PREFIX + level;
} else {
if("c".equals(attr)) {
size = Long.parseLong(matcher.group(4));
if(size<1) throw new InvalidTierDefinitionException("The passed tier definition specified an invalid size (<1) [" + size + "]");
} else {
size = Long.parseLong(matcher.group(2));
if(size<1) throw new InvalidTierDefinitionException("The passed tier definition specified an invalid size (<1) [" + size + "]");
unit = matcher.group(3);
}
if(triplets.put(attr, new Triplet(attr, unit, size))!=null) {
throw new InvalidTierDefinitionException("The passed tier definition contained duplicate attributes [" + tierDef + "]");
}
}
if(triplets.size()==2) {
twoTripletsValidate(triplets, pending.iterator().next());
}
} // End of triplet processing loop
if(name==null) {
name = TIER_PREFIX + level;
}
if(triplets.size()<2) {
throw new InvalidTierDefinitionException("The passed tier definition contained an insufficient number of attributes [" + tierDef + "]");
}
pending = EnumSet.of(FieldCode.p, FieldCode.t, FieldCode.c);
// This is so we validate in the order set, so that if the 3rd value is derived, that should be the first to be checked.
LinkedList<FieldCode> validationOrder = new LinkedList<FieldCode>();
for(Triplet triplet: triplets.values()) {
switch (triplet.fc) {
case p:
periodDuration = new Duration(triplet.size, triplet.unit).refine();
if(log.isDebugEnabled()) log.debug("Set p [" + periodDuration + "]");
pending.remove(FieldCode.p);
validationOrder.add(FieldCode.p);
break;
case t:
tierDuration = new Duration(triplet.size, triplet.unit).refine();
if(log.isDebugEnabled()) log.debug("Set t [" + tierDuration + "]");
pending.remove(FieldCode.t);
validationOrder.add(FieldCode.t);
break;
case c:
periodCount = triplet.size;
if(log.isDebugEnabled()) log.debug("Set c [" + periodCount + "]");
pending.remove(FieldCode.c);
validationOrder.add(FieldCode.c);
}
}
// If we only got 2 triplets, we need to calculate the third
if(!pending.isEmpty()) {
switch(pending.iterator().next()) {
case p:
periodDuration = new Duration(tierDuration.renderIn(TSUnit.SECONDS).size/periodCount, TSUnit.SECONDS).refine();
if(log.isDebugEnabled()) log.debug("Calced p [" + periodDuration + "]");
validationOrder.add(FieldCode.p);
break;
case t:
tierDuration = new Duration(periodDuration.renderIn(TSUnit.SECONDS).size*periodCount, TSUnit.SECONDS).refine();
if(log.isDebugEnabled()) log.debug("Calced t [" + tierDuration + "]");
validationOrder.add(FieldCode.t);
break;
case c:
periodCount = tierDuration.renderIn(TSUnit.SECONDS).size / periodDuration.renderIn(TSUnit.SECONDS).size;
if(log.isDebugEnabled()) log.debug("Calced c [" + periodCount + "]");
validationOrder.add(FieldCode.c);
}
}
validate(validationOrder);
}
/**
* Validates two triplets
* @param triplets The map containing the two triplets to validate
* @param thirdTriplet The third triplet which identifies the two triplets being validated
*/
protected void twoTripletsValidate(Map<String, Triplet> triplets, FieldCode thirdTriplet) {
switch(thirdTriplet) {
case p:
if(log.isDebugEnabled()) log.debug("2Trip Validating t/c");
long tierSize = triplets.get("t").size;
long periodCount = triplets.get("c").size;
if(periodCount%tierSize!=0) {
throw new InvalidTierTripletPairException("The period count [" + periodCount + "] is not an integral multiple of the tier duration [" + tierSize + "]");
}
break;
case t:
if(log.isDebugEnabled()) log.debug("2Trip Validating p/c");
long periodSize = triplets.get("p").size;
periodCount = triplets.get("c").size;
if(periodCount%periodSize!=0) {
throw new InvalidTierTripletPairException("The period count [" + periodCount + "] is not an integral multiple of the period duration [" + periodSize + "]");
}
break;
case c:
if(log.isDebugEnabled()) log.debug("2Trip Validating p/t");
Duration p = new Duration(triplets.get("p").size, triplets.get("p").unit);
Duration t = new Duration(triplets.get("t").size, triplets.get("t").unit);
if(t.seconds%p.seconds!=0) {
throw new InvalidTierTripletPairException("The tier duration [" + t + "] is not an integral multiple of the period duration [" + p + "]");
}
}
}
/**
* Validates the calculated values for this tier.
* @param validationOrder A list of field codes to supply the order to validate in
*/
protected void validate(LinkedList<FieldCode> validationOrder) {
for(Iterator<FieldCode> iter = validationOrder.descendingIterator(); iter.hasNext();) {
FieldCode fc = iter.next();
switch(fc) {
case p:
if(log.isDebugEnabled()) log.debug("Validating p");
Duration pDur = new Duration(tierDuration.seconds/periodCount, TSUnit.SECONDS).refine();
if(!pDur.equals(periodDuration)) {
throw new IllegalTierStateException("Invalid Period Duration [" + periodDuration + "] for Tier Duration [" + tierDuration + "] and Period Count [" + periodCount + "]. Should be [" + pDur + "]");
}
break;
case t:
if(log.isDebugEnabled()) log.debug("Validating t");
Duration tDur = new Duration(periodDuration.seconds*periodCount, TSUnit.SECONDS).refine();
if(!tDur.equals(tierDuration)) {
throw new IllegalTierStateException("Invalid Tier Duration [" + tierDuration + "] for Period Duration [" + periodDuration + "] and Period Count [" + periodCount + "]. Should be [" + tDur + "]");
}
break;
case c:
if(log.isDebugEnabled()) log.debug("Validating c");
long pCount = tierDuration.renderIn(TSUnit.SECONDS).size / periodDuration.renderIn(TSUnit.SECONDS).size;
if(periodCount!=pCount) {
throw new IllegalTierStateException("Invalid Period Count [" + periodCount + "] for Period Duration [" + periodDuration + "] and Tier Duration [" + tierDuration + "]. Should be [" + pCount + "]");
}
}
}
}
/**
* Constructs a <code>String</code> with all attributes in <code>name:value</code> format.
* @return a <code>String</code> representation of this object.
*/
@Override
public String toString() {
final String C = ",";
StringBuilder retValue = new StringBuilder();
retValue.append(FieldCode.n).append("=").append(this.name).append(C);
retValue.append(FieldCode.p).append("=").append(this.periodDuration).append(C);
retValue.append(FieldCode.t).append("=").append(this.tierDuration).append(C);
retValue.append(FieldCode.c).append("=").append(this.periodCount);
return retValue.toString();
}
public static void main(String[] args) {
Logger.getLogger(Tier.class).setLevel(Level.DEBUG);
log("Tier Test");
//log(new Tier("p=60s,t=23d", 1));
//log(new Tier("p=60s,c=33120", 1));
log(new Tier("t=23d,c=33120", 1));
}
public static void log(Object msg) {
System.out.println(msg);
}
private static class Triplet {
protected final FieldCode fc;
protected final TSUnit unit;
protected final long size;
/**
* Creates a new Triplet
* @param fc
* @param unit
* @param size
*/
public Triplet(String attr, String unit, long size) {
this.fc = FieldCode.valueOf(attr);
this.size = size;
this.unit = unit==null ? null : TSUnit.forCode(unit);
}
}
/**
* Returns the number of periods in the tier
* @return the number of periods in the tier
*/
public long getPeriodCount() {
return periodCount;
}
/**
* Returns the duration of one period in the tier
* @return the duration of one period in the tier
*/
public Duration getPeriodDuration() {
return periodDuration;
}
/**
* Returns the duration of the full tier
* @return the duration of the full tier
*/
public Duration getTierDuration() {
return tierDuration;
}
/**
* Returns the name of the tier
* @return the name of the tier
*/
public String getName() {
return name;
}
/**
* Returns the level of tier within the time series model
* @return the level of tier within the time series model
*/
public int getLevel() {
return level;
}
/**
* {@inheritDoc}
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + level;
result = prime * result
+ ((periodDuration == null) ? 0 : periodDuration.hashCode());
result = prime * result
+ ((tierDuration == null) ? 0 : tierDuration.hashCode());
return result;
}
/**
* {@inheritDoc}
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Tier other = (Tier) obj;
if (level != other.level)
return false;
if (periodDuration == null) {
if (other.periodDuration != null)
return false;
} else if (!periodDuration.equals(other.periodDuration))
return false;
if (tierDuration == null) {
if (other.tierDuration != null)
return false;
} else if (!tierDuration.equals(other.tierDuration))
return false;
return true;
}
/**
* Returns the tier definition pattern
* @return the tier definition pattern
*/
public String getPattern() {
return pattern;
}
}