/** * 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.collector.jdbc.mapping; import org.apache.log4j.Logger; import org.helios.apmrouter.jmx.XMLHelper; import org.helios.apmrouter.metric.MetricType; import org.helios.apmrouter.trace.ITracer; import org.helios.collector.jdbc.binding.provider.BindVariableProviderFactory; import org.helios.collector.jdbc.binding.provider.IBindVariableProvider; import org.helios.collector.jdbc.extract.IReadOnlyProcessedResultSet; import org.w3c.dom.Node; import javax.management.MBeanServer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.helios.apmrouter.util.StringHelper; //import net.sf.ehcache.Ehcache; //- import org.helios.ot.deltas.DeltaManager; //- import org.helios.ot.trace.Trace.Builder; /** * <p>Title: MetricMap</p> * <p>Description: Configured instances of this class acept the results of a SQLMap's query and generate OpenTrace metrics from each row returned. </p> * <p>Company: Helios Development Group</p> * @author Whitehead (whitehead.nicholas@gmail.com) * @version $LastChangedRevision$ * $HeadURL$ * $Id$ */ public class MetricMap { /** The column from which the value should be extracted */ protected String valueCol = null; /** The metric segment expression */ protected String segment = null; /** The metric name expression */ protected String name = null; /** The metric type expression */ protected String type = null; /** The optional scope value expression */ protected String scope = null; /** flag to indicate if metric should be temporal */ protected boolean temporal = false; /** flag to indicate that columns from multiple rows should be flatened out into one string */ protected boolean flatten = false; /** The collector provided metric name root prefix */ protected String[] prefix = {}; /** The collector provided tracer */ protected ITracer tracer = null; /** Instance logger */ protected final Logger log = Logger.getLogger(getClass()); /** The default expression delimeter */ public static final String DEFAULT_DELIMETER = ","; /** The expression delimeter */ public String delimeter = DEFAULT_DELIMETER ; /** The metric name formatter */ protected Formatter nameFormatter = null; /** The metric value formatter */ protected Formatter valueFormatter = null; /** The metric segment formatter */ protected Formatter segmentFormatter = null; /** The metric type formatter */ protected Formatter metricTypeFormatter = null; /** The scope formatter */ protected volatile Formatter scopeFormatter = null; /** The bind formatter */ protected volatile Formatter bindFormatter = null; /** Flag to indicate if tracing is enabled (if not it is probably a pre) */ protected boolean tracerEnabled = true; /** A reference to the MBeanServer where the collector is registered */ protected MBeanServer server = null; // /** A reference to the collector's collector cache */ // protected Ehcache collectorCache = null; /** Result binding expression */ protected String resultBind = null; /** Result binding binder expression */ protected String binder = null; /** the provider for the result binding */ protected IBindVariableProvider provider = null; /** flag indicating if a binder is enabled */ protected boolean binderEnabled = false; /** The scope map */ protected Map<String, ScopeState> scopeMap = new HashMap<String, ScopeState>(); /** * Parameterless constructor. */ public MetricMap() { } /** * XML node based constructor. * @param node The root configuration XML node for this mapping. */ public MetricMap(Node node) throws InvalidMetricMappingException { valueCol = XMLHelper.getAttributeValueByName(node, "value"); segment = XMLHelper.getAttributeValueByName(node, "segment"); name = XMLHelper.getAttributeValueByName(node, "name"); type = XMLHelper.getAttributeValueByName(node, "type"); scope = XMLHelper.getAttributeValueByName(node, "scope"); String temporalStr = XMLHelper.getAttributeValueByName(node, "temporal"); temporal = "true".equalsIgnoreCase(temporalStr); String flattenStr = XMLHelper.getAttributeValueByName(node, "flatten"); flatten = "true".equalsIgnoreCase(flattenStr); resultBind = XMLHelper.getAttributeValueByName(node, "bindresult"); binder = XMLHelper.getAttributeValueByName(node, "provider"); } /** * Initializes the metric map. * @param prefix The collector provided metric name prefix. * @param tracer The collector provided HOT tracer * @param server The collector provided MBeanServer. * @throws InvalidMetricMappingException */ public void init(String[] prefix, ITracer tracer, /*Ehcache collectorCache,*/ MBeanServer server) throws InvalidMetricMappingException { this.prefix = prefix; this.tracer = tracer; this.server = server; //this.collectorCache = collectorCache; if(name==null||valueCol==null|segment==null||type==null) { } try { nameFormatter = new Formatter(delimeter, name); } catch (Exception e) {throw new InvalidMetricMappingException("Failed to initialize MetricMapping [Name]", e); } try { valueFormatter = new Formatter(delimeter, valueCol); } catch (Exception e) {throw new InvalidMetricMappingException("Failed to initialize MetricMapping [Value]", e); } try { segmentFormatter = new Formatter(delimeter, segment); } catch (Exception e) {throw new InvalidMetricMappingException("Failed to initialize MetricMapping [Segment]", e); } try { metricTypeFormatter = new Formatter(delimeter, type); } catch (Exception e) {throw new InvalidMetricMappingException("Failed to initialize MetricMapping [MetricType]", e); } if(scope!=null) { try { scopeFormatter = new Formatter(delimeter, scope); } catch (Exception e) {throw new InvalidMetricMappingException("Failed to initialize MetricMapping [Scope]", e); } } if(resultBind != null) { try { bindFormatter = new Formatter(delimeter, resultBind); } catch (Exception e) { log.warn("Failed to acquire result formatter for [" + resultBind + "]",e); resultBind = null; } } if(binder != null) { try { provider = BindVariableProviderFactory.getInstance().getProvider(binder); } catch (Exception e) { log.warn("Failed to acquire bind variable provider for [" + binder + "]",e); binder = null; } } this.binderEnabled = (bindFormatter!=null && provider!=null); } /** * Extracts data from the passed result set and connection meta-data and generates HOT traces. Invoked by the SQLMapper after the query completes. * @param prs The retrieved result set. * @param connMetaData The connection meta-data. */ //- public void traceMetrics(IReadOnlyProcessedResultSet prs, Map<String, Object> connMetaData) { if(!tracerEnabled) return; //tracer.traceGauge(Long.parseLong(valueFormatter.getValue(prs, connMetaData)), nameFormatter.getValue(prs, connMetaData), prefix, segmentFormatter.getValues(prs, connMetaData))); MetricType metricType = MetricType.valueOf(metricTypeFormatter.getValue(prs, connMetaData)); if(metricType.isDelta()) { tracer.traceDeltaGauge(Long.parseLong(valueFormatter.getValue(prs, connMetaData)), nameFormatter.getValue(prs, connMetaData), StringHelper.append(prefix,segmentFormatter.getValues(prs, connMetaData))); } else { tracer.traceGauge(Long.parseLong(valueFormatter.getValue(prs, connMetaData)), nameFormatter.getValue(prs, connMetaData), StringHelper.append(prefix,segmentFormatter.getValues(prs, connMetaData))); } } /** * Executes the result bind directive, if it is defined. * @param prs The retrieved result set. * @param connMetaData The connection meta-data. */ public void executeBinds(IReadOnlyProcessedResultSet prs, Map<String, Object> connMetaData) { if(!binderEnabled) return; provider.setValue(bindFormatter.getValue(prs, connMetaData)); } /** * Callback from SQLMapping to reset scope before tracing starts. */ public void resetScope() { if(!tracerEnabled) return; if(scope!=null) { for(String name: scopeMap.keySet()) { scopeMap.get(name).setAccounted(false); } } } /** * Traces scope values for any metrics that failed scoping. * Once the scoped value has been traced, the metric name is removed from scope.!{bean:SchemaNameProvider} */ //- public void traceScopeFailures() { if(!tracerEnabled) return; /*Set<String> removes = new HashSet<String>(); for(Map.Entry<String, ScopeState> scopes: scopeMap.entrySet()) { ScopeState state = scopes.getValue(); if(!state.isAccounted()) { Builder builder = tracer.trace( state.getName(), scope, state.getType()) .segment(prefix) .segment(state.getSegment()) .temporal(temporal) .deltaReset(); Trace trace = tracer.traceTrace(builder.trace()); if(trace!=null && state.getType().isDelta()) { DeltaManager.getInstance().reset(trace.getFQN()); } removes.add(scopes.getKey()); } } for(String name: removes) { scopeMap.remove(name); }*/ } /** * Creates a new MetricMap * @param valueCol * @param segment * @param name * @param type * @param scope * @param temporal * @param flatten */ public MetricMap(String valueCol, String segment, String name, String type, String scope, boolean temporal, boolean flatten, String resultBind, String binder) { this.valueCol = valueCol; this.segment = segment; this.name = name; this.type = type; this.scope = scope; this.temporal = temporal; this.flatten = flatten; this.resultBind = resultBind; this.binder = binder; } /** * @return the valueCol */ public String getValueCol() { return valueCol; } /** * @param valueCol the valueCol to set */ public void setValueCol(String valueCol) { this.valueCol = valueCol; } /** * @return the segment */ public String getSegment() { return segment; } /** * @param segment the segment to set */ public void setSegment(String segment) { this.segment = segment; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the type */ public String getType() { return type; } /** * @param type the type to set */ public void setType(String type) { this.type = type; } /** * @return the scope */ public String getScope() { return scope; } /** * @param scope the scope to set */ public void setScope(String scope) { this.scope = scope; } /** * @return the temporal */ public boolean isTemporal() { return temporal; } /** * @param temporal the temporal to set */ public void setTemporal(boolean temporal) { this.temporal = temporal; } /** * @return the flatten */ public boolean isFlatten() { return flatten; } /** * @param flatten the flatten to set */ public void setFlatten(boolean flatten) { this.flatten = flatten; } /** * @param prefix the prefix to set */ public void setPrefix(String[] prefix) { this.prefix = prefix; } /** * @param tracer the tracerFactory to set */ public void setTracer(ITracer tracer) { this.tracer = tracer; } /** * @return the delimeter */ public String getDelimeter() { return delimeter; } /** * @param delimeter the delimeter to set */ public void setDelimeter(String delimeter) { this.delimeter = delimeter; } /** * <p>Title: ValueReader</p> * <p>Description: Reads a column value from a result set, result set header or DB Conn Meta Data</p> */ public static class ValueReader { /** The value identifier */ protected int colNum = -1; /** The value name */ protected String colName = null; /** The value type */ protected String colType = null; /** * Creates a new ValueReader * Valid types are:<ul> * <li><b>c</b>: The column name</li> * <li><b>v</b>: The column value for the current row</li> * <li><b>m</b>: Database meta-data entry</li> * <li><b>t</b>: The JDBC Type Code</li> * <li><b>n</b>: The JDBC Type Name</li> * <li><b>j</b>: The JDBC Java Class Name</li> * <li><b>s</b>: The column database specific type name</li> * <li><b>q</b>: The query name</li> * </ul> * @param type The value reader type * @param value The value key */ ValueReader(String type, String value) { if(value==null || value.length()<1) throw new RuntimeException("Invalid value specification"); if(type==null || type.length()<1 || !validateType(type)) throw new RuntimeException("Invalid type specification"); colType = type; if(colType.equalsIgnoreCase("m")) { // keep value as String colName = value; } else { // might be a column number or name try { colNum = Integer.parseInt(value); colName = null; } catch (Exception e) { colName = value; colNum = -1; } } } /** * Retrieves the configured/requested value from the result set or the metadata * @param prs The processed result set to extract from * @param connMetaData The connection meta-data set to extract from * @return The extracted value. */ String getValue(IReadOnlyProcessedResultSet prs, Map<String, Object> connMetaData) { Object result = null; switch ((int)colType.toCharArray()[0]) { case (int)'q': result = prs.getQueryName(); break; case (int)'m': result = connMetaData.get(colName); break; case (int)'c': result = colName==null ? prs.getColumnName(colNum) : prs.getColumnName(colName); break; case (int)'v': result = colName==null ? prs.get(colNum) : prs.get(colName); break; case (int)'n': result = colName==null ? prs.getColumnTypeName(colNum) : prs.getColumnTypeName(colName); break; case (int)'t': result = colName==null ? prs.getColumnType(colNum) : prs.getColumnType(colName); break; case (int)'j': result = colName==null ? prs.getColumnClassName(colNum) : prs.getColumnClassName(colName); break; case (int)'s': result = colName==null ? prs.getDbTypeName(colNum) : prs.getDbTypeName(colName); break; default: throw new RuntimeException("Unlikely but unrecognized token encountered in ValueReader [" + colType + "]"); } return result==null ? "" : result.toString(); } /** * Validates the the type string is valid * Valid values are:<ul> * <li><b>c</b>: The column name</li> * <li><b>v</b>: The column value for the current row</li> * <li><b>m</b>: Database meta-data entry</li> * <li><b>t</b>: The JDBC Type Code</li> * <li><b>n</b>: The JDBC Type Name</li> * <li><b>j</b>: The JDBC Java Class Name</li> * <li><b>s</b>: The column database specific type name</li> * <li><b>q</b>: The query name</li> * </ul> * @param s The value type code. (c: column, v: value, m: meta-data) * @return true if the type is valid. */ boolean validateType(String s) { return s.equalsIgnoreCase("c") || s.equalsIgnoreCase("v") || s.equalsIgnoreCase("m") || s.equalsIgnoreCase("t") || s.equalsIgnoreCase("n") || s.equalsIgnoreCase("j") || s.equalsIgnoreCase("s") || s.equalsIgnoreCase("q"); } } /** * <p>Title: Formatter</p> * <p>Description: Utility class to create specifically formated <code>ValueReader</code> instances and render a full value from them at collect time. * Directives are:<ul> * <li><b>c</b>: The column name</li> * <li><b>v</b>: The column value for the current row</li> * <li><b>m</b>: Database meta-data entry</li> * <li><b>t</b>: The JDBC Type Code</li> * <li><b>n</b>: The JDBC Type Name</li> * <li><b>j</b>: The JDBC Java Class Name</li> * <li><b>s</b>: The column database specific type name</li> * <li><b>q</b>: The query name</li> * </ul> * </p> */ public static class Formatter { /** The value reader token parser */ static final Pattern SEG_PATTERN = Pattern.compile("(\\{([v|c|m|t|n|j|s|q]):(\\d+|\\w+)\\})", Pattern.CASE_INSENSITIVE); /** An empty segment default value */ static final String[] emptySegment = {}; /** The configured delimeter */ protected String delim = null; /** The configured raw value */ protected String rawValue = null; /** ValueReader map keyed by token */ protected Map<String, ValueReader> valueReaders = new HashMap<String, ValueReader>(); /** flag indicating an empty segment */ protected boolean empty = false; /** * Creates a new Formatter instance. * @param delim The configured delimeter for multi values * @param config The full raw configuration string. */ Formatter(String delim, String config) { this.delim = delim; this.rawValue = config; if(config==null||config.length()<1) { empty = true; } else { Matcher m = SEG_PATTERN.matcher(config); while(m.find()) { String token = m.group(1); if(!valueReaders.containsKey(token)) { valueReaders.put(token, new ValueReader(m.group(2), m.group(3))); } } } } /** * Returns a multi valued formatted value * @param prs The processed result set * @param connMetaData The connection meta-data map * @return An array of formatted strings. */ String[] getValues(IReadOnlyProcessedResultSet prs, Map<String, Object> connMetaData) { if(empty) return emptySegment; return formatValue(prs, connMetaData).split(Pattern.quote(delim)); } /** * Returns a single valued formatted value * @param prs The processed result set * @param connMetaData The connection meta-data map * @return A formatted string. */ String getValue(IReadOnlyProcessedResultSet prs, Map<String, Object> connMetaData) { if(empty) return ""; return formatValue(prs, connMetaData); } /** * Iterates through all registered value readers and replaces value tokens with the actual data. * @param prs The processed result set * @param connMetaData The connection meta-data map * @return The unsplit formatted string */ private String formatValue(IReadOnlyProcessedResultSet prs, Map<String, Object> connMetaData) { String raw = new String(rawValue); for(Map.Entry<String, ValueReader> vr: valueReaders.entrySet()) { raw = raw.replace(vr.getKey(), clean(vr.getValue().getValue(prs, connMetaData))); } return raw; } public static String clean(CharSequence s) { if(s==null) return null; return s.toString().replaceAll("/", "\\\\"); } } public static class ScopeState { protected MetricType type = null; protected boolean accounted = true; protected String name = null; protected String[] segment = null; /** * @param type * @param accounted * @param name * @param segment */ public ScopeState(MetricType type, boolean accounted, String name, String[] segment) { this.type = type; this.accounted = accounted; this.name = name; this.segment = segment; } /** * @return the type */ public MetricType getType() { return type; } /** * @param type the type to set */ public void setType(MetricType type) { this.type = type; } /** * @return the accounted */ public boolean isAccounted() { return accounted; } /** * @param accounted the accounted to set */ public void setAccounted(boolean accounted) { this.accounted = accounted; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the segment */ public String[] getSegment() { return segment; } /** * @param segment the segment to set */ public void setSegment(String[] segment) { this.segment = segment; } } /** * @return * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((segment == null) ? 0 : segment.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } /** * @param obj * @return * @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; MetricMap other = (MetricMap) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (segment == null) { if (other.segment != null) return false; } else if (!segment.equals(other.segment)) return false; if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; return true; } /** * Constructs a <code>String</code> with all attributes in name = value format. * @return a <code>String</code> representation of this object. */ public String toString() { final String TAB = "\n\t"; StringBuilder retValue = new StringBuilder("MetricMap ["); retValue.append(TAB).append("tracerEnabled=").append(this.tracerEnabled); if(tracerEnabled) { retValue.append(TAB).append("name=").append(this.name); retValue.append(TAB).append("segment=").append(this.segment); retValue.append(TAB).append("type=").append(this.type); retValue.append(TAB).append("valueCol=").append(this.valueCol); } retValue.append(TAB).append("scope=").append(this.scope); retValue.append(TAB).append("resultBind=").append(this.resultBind); retValue.append(TAB).append("binder=").append(this.binder); retValue.append(TAB).append("temporal=").append(this.temporal); retValue.append(TAB).append("flatten=").append(this.flatten); retValue.append(TAB).append("prefix=").append(this.prefix==null ? "" : Arrays.toString(prefix)); retValue.append(TAB).append("delimeter=").append(this.delimeter); retValue.append("\n]"); return retValue.toString(); } /** * @return the resultBind */ public String getResultBind() { return resultBind; } /** * @param resultBind the resultBind to set */ public void setResultBind(String resultBind) { this.resultBind = resultBind; } /** * @param binder */ public void setBinder(String binder) { this.binder = binder; } /** * @return */ public String getBinder() { return binder; } /** * @return the binderEnabled */ public boolean isBinderEnabled() { return binderEnabled; } /** * @param binderEnabled the binderEnabled to set */ public void setBinderEnabled(boolean binderEnabled) { this.binderEnabled = binderEnabled; } /** * @return the tracerEnabled */ public boolean isTracerEnabled() { return tracerEnabled; } /** * @param tracerEnabled the tracerEnabled to set */ public void setTracerEnabled(boolean tracerEnabled) { this.tracerEnabled = tracerEnabled; } }