/** * 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.catalog.jdbc.h2; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.management.MBeanNotificationInfo; import javax.management.Notification; import javax.sql.DataSource; import org.helios.apmrouter.catalog.EntryStatus; import org.helios.apmrouter.destination.chronicletimeseries.ChronicleTier; import org.helios.apmrouter.metric.MetricType; import org.helios.apmrouter.util.SystemClock; /** * <p>Title: MetricTrigger</p> * <p>Description: H2 new metric trigger</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.catalog.jdbc.h2.MetricTrigger</code></p> */ public class MetricTrigger extends AsynchAbstractTrigger implements MetricTriggerMBean { /** The metric type ordinal for the incrementor metric type */ protected static final int INCR_ID = MetricType.INCREMENTOR.ordinal(); /** The metric type ordinal for the interval incrementor metric type */ protected static final int INT_INCR_ID = MetricType.INTERVAL_INCREMENTOR.ordinal(); /** The ID of the column containing the metric id for a metric */ public static final int METRIC_COLUMN_ID = 0; /** The ID of the column containing the agent id for a metric */ public static final int AGENT_COLUMN_ID = 1; /** The ID of the column containing the metric type id for a metric */ public static final int TYPE_COLUMN_ID = 2; /** The ID of the column containing the metric namespace for a metric */ public static final int NAMESPACE_COLUMN_ID = 3; /** The ID of the column containing the metric namespace array for a metric */ public static final int NARR_COLUMN_ID = 4; /** The ID of the column containing the metric level for a metric */ public static final int LEVEL_COLUMN_ID = 5; /** The ID of the column containing the metric name for a metric */ public static final int NAME_COLUMN_ID = 6; /** The ID of the column containing the metric first seen timestamp for a metric */ public static final int FIRST_SEEN_COLUMN_ID = 7; /** The ID of the column containing the state for a metric */ public static final int STATE_COLUMN_ID = 8; /** The ID of the column containing the metric last seen timestamp for a metric */ public static final int LAST_SEEN_COLUMN_ID = 9; /** The SQL to pre-populate the cache on trigger init */ public static final String PREPOP_SQL = "SELECT A.AGENT_ID, H.DOMAIN || '/' || H.NAME || '/' || A.NAME FROM HOST H, AGENT A WHERE A.HOST_ID = H.HOST_ID"; /** The SQL to get the cache entry on a cache miss */ public static final String CACHE_MISS_SQL = "SELECT H.DOMAIN || '/' || H.NAME || '/' || A.NAME FROM HOST H, AGENT A WHERE A.HOST_ID = H.HOST_ID AND A.AGENT_ID = ?"; /** A cache of <b><code>Domain/Host/Agent<code></b> prefixes for each agent Id */ protected static final Map<Integer, String> metricFqnPrefixCache = new ConcurrentHashMap<Integer, String>(128, 0.75f, 16); /** Thread local string builder for fast string concat. (This is a very performance sensitive class) */ protected static ThreadLocal<StringBuilder> stringBuilder = new ThreadLocal<StringBuilder>() { @Override protected StringBuilder initialValue() { return new StringBuilder(); } }; /** * <p>Overriden to populate the {@link #metricFqnPrefixCache} cache</p> * {@inheritDoc} * @see org.h2.api.Trigger#init(java.sql.Connection, java.lang.String, java.lang.String, java.lang.String, boolean, int) */ @Override public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException { super.init(conn, schemaName, triggerName, tableName, before, type); PreparedStatement ps = null; ResultSet rset = null; try { ps = conn.prepareStatement(PREPOP_SQL); rset = ps.executeQuery(); while(rset.next()) { metricFqnPrefixCache.put(rset.getInt(1), rset.getString(2)); } log.info("Populated AgentPrefix Cache with [" + metricFqnPrefixCache + "] Entries"); } finally { if(rset!=null) try { rset.close(); } catch (Exception e) {/* No Op */} if(ps!=null) try { ps.close(); } catch (Exception e) {/* No Op */} } } /** * Retrieves the agent prefix for the passed agentId * @param agentId The agentId * @param dataSource The datasource in case the cache misses and the prefix needs to bbe looked up * @return the agent prefix */ public String getAgentPrefix(int agentId, DataSource dataSource) { String prefix = metricFqnPrefixCache.get(agentId); if(prefix==null) { synchronized(metricFqnPrefixCache) { prefix = metricFqnPrefixCache.get(agentId); if(prefix==null) { Connection conn = null; PreparedStatement ps = null; ResultSet rset = null; try { conn = dataSource.getConnection(); ps = conn.prepareStatement(CACHE_MISS_SQL); ps.setInt(agentId, 1); rset = ps.executeQuery(); if(rset.next()) { prefix = rset.getString(1); metricFqnPrefixCache.put(agentId, prefix); return prefix; } return null; } catch (Exception ex) { return null; } finally { if(rset!=null) try { rset.close(); } catch (Exception e) {/* No Op */} if(ps!=null) try { ps.close(); } catch (Exception e) {/* No Op */} if(conn!=null) try { conn.close(); } catch (Exception e) {/* No Op */} } } } } return prefix; } /** * {@inheritDoc} * @see org.helios.apmrouter.catalog.jdbc.h2.MetricTriggerMBean#getAgentPrefixCacheSize() */ @Override public int getAgentPrefixCacheSize() { return metricFqnPrefixCache.size(); } /** * {@inheritDoc} * @see org.helios.apmrouter.catalog.jdbc.h2.MetricTriggerMBean#flushAgentPrefixCache() */ @Override public void flushAgentPrefixCache() { metricFqnPrefixCache.clear(); } /** The event status message format */ public static final String EVENT_STATUS_MESSAGE = "%s:%s"; /** * Creates a new MetricTrigger */ public MetricTrigger() { super(new MBeanNotificationInfo(new String[]{NEW_METRIC}, Notification.class.getName(), "A new metric registration event")); } /** The JMX notification type for new metric events */ public static final String NEW_METRIC_EVENT = "metric.event.new"; /** The JMX notification type for new metric events */ public static final String STATE_CHANGE_METRIC_EVENT = "metric.event.statechange"; /** The JMX notification type for new metric events */ public static final String DATA_METRIC_EVENT = "metric.event.data"; /** * {@inheritDoc} * @see org.helios.apmrouter.catalog.jdbc.h2.AsynchAbstractTrigger#doFire(javax.sql.DataSource, java.lang.Object[], java.lang.Object[]) */ @Override protected void doFire(DataSource dataSource, Object[] oldRow, Object[] newRow) { if(newRow==null) return; if(TriggerOp.INSERT.isEnabled(type)) { short typeId = (Short)newRow[2]; if(INCR_ID==typeId || INT_INCR_ID==typeId) { addIncr(dataSource, (Long)newRow[0], typeId); } NewElementTriggers.newMetricQueue.offer(newRow); } else if(TriggerOp.UPDATE.isEnabled(type)) { if(oldRow!=null && newRow[STATE_COLUMN_ID] != oldRow[STATE_COLUMN_ID]) { NewElementTriggers.metricStateChangeQueue.offer(newRow); } } callCount.incrementAndGet(); } // STATE_CHANGE_METRIC_EVENT, newRow[METRIC_COLUMN_ID], newRow[STATE_COLUMN_ID] // NEW_METRIC_EVENT, newRow[METRIC_COLUMN_ID] // org.helios.apmrouter.catalog.jdbc.h2.MetricTrigger /** * Adds the incrementor for the passed metric ID * @param dataSource The conn source for H2 connections * @param metricId The new metric Id * @param typeId The {@link MetricType} ordinal * FIXME: If metric is new, value is 1, otherwise it is +1 */ protected void addIncr(DataSource dataSource, long metricId, int typeId) { Connection conn = null; PreparedStatement ps = null; try { conn = dataSource.getConnection(); ps = conn.prepareStatement(typeId==INCR_ID ? "INSERT INTO INCREMENTOR (METRIC_ID, INC_VALUE, LAST_INC) VALUES (?, 1, CURRENT_TIMESTAMP)" : "INSERT INTO INTERVAL_INCREMENTOR (METRIC_ID, INC_VALUE, LAST_INC) VALUES (?, 1, CURRENT_TIMESTAMP)" ); ps.setLong(1, metricId); ps.executeUpdate(); } catch (SQLException ex) { throw new RuntimeException("Failed to addIncr for metricId [" + metricId + "]", ex); } finally { if(ps!=null) try { ps.close(); } catch (Exception ex) {/* No Op */} if(conn!=null) try { conn.close(); } catch (Exception ex) {/* No Op */} } } }