/**************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package com.espertech.esper.epl.metric;
import com.espertech.esper.client.metric.StatementMetric;
import com.espertech.esper.util.ManagedReadWriteLock;
import java.util.HashSet;
import java.util.Set;
/**
* Holder for statement group's statement metrics.
* <p>
* Changes to StatementMetric instances must be done in a read-lock:
<pre>
getRwLock.readLock.lock()
metric = getAddMetric(index)
metric.accountFor(cpu, wall, etc)
getRwLock.readLock.unlock()
</pre>
* <p>
* All other changes are done under write lock for this class.
* <p>
* This is a collection backed by an array that grows by 50% each time expanded, maintains a free/busy list of statement names,
* maintains an element number of last used element.
* <p>
* The flush operaton copies the complete array, thereby keeping array size. Statement names are only removed on the next flush.
*/
public class StatementMetricArray
{
private final String engineURI;
// Lock
// Read lock applies to each current transaction on a StatementMetric instance
// Write lock applies to flush and to add a new statement
private final ManagedReadWriteLock rwLock;
private final boolean isReportInactive;
// Active statements
private String[] statementNames;
// Count of active statements
private int currentLastElement;
// Flushed metric per statement
private volatile StatementMetric[] metrics;
// Statements ids to remove with the next flush
private Set<String> removedStatementNames;
/**
* Ctor.
* @param engineURI engine URI
* @param name name of statement group
* @param initialSize initial size of array
* @param isReportInactive true to indicate to report on inactive statements
*/
public StatementMetricArray(String engineURI, String name, int initialSize, boolean isReportInactive)
{
this.engineURI = engineURI;
this.isReportInactive = isReportInactive;
metrics = new StatementMetric[initialSize];
statementNames = new String[initialSize];
currentLastElement = -1;
rwLock = new ManagedReadWriteLock("StatementMetricArray-" + name, true);
removedStatementNames = new HashSet<String>();
}
/**
* Remove a statement.
* <p>
* Next flush actually frees the slot that this statement occupied.
* @param statementName to remove
*/
public void removeStatement(String statementName)
{
rwLock.acquireWriteLock();
try
{
removedStatementNames.add(statementName);
if (removedStatementNames.size() > 1000) {
for (int i = 0; i <= currentLastElement; i++)
{
if (removedStatementNames.contains(statementNames[i]))
{
statementNames[i] = null;
}
}
removedStatementNames.clear();
}
}
finally
{
rwLock.releaseWriteLock();
}
}
/**
* Adds a statement and returns the index added at.
* <p>
* May reuse an empty slot, grow the underlying array, or append to the end.
* @param statementName to add
* @return index added to
*/
public int addStatementGetIndex(String statementName)
{
rwLock.acquireWriteLock();
try
{
// see if there is room
if ((currentLastElement + 1) < metrics.length)
{
currentLastElement++;
statementNames[currentLastElement] = statementName;
return currentLastElement;
}
// no room, try to use an existing slot of a removed statement
for (int i = 0; i < statementNames.length; i++)
{
if (statementNames[i] == null)
{
statementNames[i] = statementName;
if ((i + 1) > currentLastElement)
{
currentLastElement = i;
}
return i;
}
}
// still no room, expand storage by 50%
int newSize = (int) (metrics.length * 1.5);
String[] newStatementNames = new String[newSize];
StatementMetric[] newMetrics = new StatementMetric[newSize];
System.arraycopy(statementNames, 0, newStatementNames, 0, statementNames.length);
System.arraycopy(metrics, 0, newMetrics, 0, metrics.length);
statementNames = newStatementNames;
metrics = newMetrics;
currentLastElement++;
statementNames[currentLastElement] = statementName;
return currentLastElement;
}
finally
{
rwLock.releaseWriteLock();
}
}
/**
* Flushes the existing metrics via array copy and swap.
* <p>
* May report all statements (empty and non-empty slots) and thereby null values.
* <p>
* Returns null to indicate no reports to do.
* @return metrics
*/
public StatementMetric[] flushMetrics()
{
rwLock.acquireWriteLock();
try
{
boolean isEmpty = false;
if (currentLastElement == -1)
{
isEmpty = true;
}
// first fill in the blanks if there are no reports and we report inactive statements
if (isReportInactive)
{
for (int i = 0; i <= currentLastElement; i++)
{
if (statementNames[i] != null)
{
metrics[i] = new StatementMetric(engineURI, statementNames[i]);
}
}
}
// remove statement ids that disappeared during the interval
if ((currentLastElement > -1) && (!removedStatementNames.isEmpty()))
{
for (int i = 0; i <= currentLastElement; i++)
{
if (removedStatementNames.contains(statementNames[i]))
{
statementNames[i] = null;
}
}
}
// adjust last used element
while ((currentLastElement != -1) && (statementNames[currentLastElement] == null))
{
currentLastElement--;
}
if (isEmpty)
{
return null; // no copies made if empty collection
}
// perform flush
StatementMetric[] newMetrics = new StatementMetric[metrics.length];
StatementMetric[] oldMetrics = metrics;
metrics = newMetrics;
return oldMetrics;
}
finally
{
rwLock.releaseWriteLock();
}
}
/**
* Returns the read-write lock, for read-lock when modifications are made.
* @return lock
*/
public ManagedReadWriteLock getRwLock()
{
return rwLock;
}
/**
* Returns an existing or creates a new statement metric for the index.
* @param index of statement
* @return metric to modify under read lock
*/
public StatementMetric getAddMetric(int index)
{
StatementMetric metric = metrics[index];
if (metric == null)
{
metric = new StatementMetric(engineURI, statementNames[index]);
metrics[index] = metric;
}
return metric;
}
/**
* Returns maximum collection size (last used element), which may not truely reflect the number
* of actual statements held as some slots may empty up when statements are removed.
* @return known maximum size
*/
public int sizeLastElement()
{
return currentLastElement + 1;
}
}