/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.util.tap;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Tap implementation that aggregates information on a per-thread basis. An
* instance of this class maintains a collection of subordinate Taps, one
* per thread. These are created on demand as new threads invoke
* {@link #in()} or {@link #out()}.
* <p/>
* By default each subordinate Tap is a {@link TimeAndCount}, but the
* two-argument constructor provides a way to override that default.
*/
abstract class PerThread extends Tap
{
// Object interface
@Override
public String toString()
{
return "PerThread(" + name + ")";
}
// Tap interface
@Override
public void in()
{
threadTap().in();
}
@Override
public void out()
{
threadTap().out();
}
@Override
public long getDuration()
{
return threadTap().getDuration();
}
@Override
public void reset()
{
for (Tap tap : threadMap.values()) {
tap.reset();
}
enabled = true;
}
@Override
public void disable()
{
for (Tap tap : threadMap.values()) {
tap.disable();
}
enabled = false;
}
@Override
public void appendReport(String unused, StringBuilder buffer)
{
Map<Thread, Tap> threadMap = new TreeMap<>(THREAD_COMPARATOR);
threadMap.putAll(this.threadMap);
// TODO - fix this: thread names not necessarily unique. putAll could lose threads!
assert this.threadMap.size() == threadMap.size();
for (Map.Entry<Thread, Tap> entry : threadMap.entrySet()) {
String label = String.format("%s==%s==%s", NEW_LINE, entry.getKey().getName(), NEW_LINE);
entry.getValue().appendReport(label, buffer);
}
}
@Override
public TapReport[] getReports()
{
Map<String, TapReport> cumulativeReports = new HashMap<>();
for (Tap tap : threadMap.values()) {
TapReport[] tapReports = tap.getReports();
assert tapReports != null; // because tap is not a Dispatch or Null.
for (TapReport tapReport : tapReports) {
String tapName = tapReport.getName();
TapReport cumulativeReport = cumulativeReports.get(tapName);
if (cumulativeReport == null) {
cumulativeReport = new TapReport(tapName);
cumulativeReports.put(tapName, cumulativeReport);
}
cumulativeReport.inCount += tapReport.getInCount();
cumulativeReport.outCount += tapReport.getOutCount();
cumulativeReport.cumulativeTime += tapReport.getCumulativeTime();
}
}
if (cumulativeReports.isEmpty()) {
cumulativeReports.put(name, new TapReport(name));
}
TapReport[] reports = new TapReport[cumulativeReports.size()];
cumulativeReports.values().toArray(reports);
return reports;
}
// PerThread interface
abstract Tap threadTap();
/**
* Create a PerThread instance the adds a new instance of the supplied
* subclass of {@link com.foundationdb.util.tap.Tap} for each Thread. Note: the classes
* {@link com.foundationdb.util.tap.PerThread} (including subclasses)
* may not be added.
*
* @param name Name of the Tap
* @param tapClass Class from which new Tap instances are created.
*/
public static PerThread createPerThread(String name, Class<? extends Tap> tapClass)
{
if (PerThread.class.isAssignableFrom(tapClass)) {
throw new IllegalArgumentException();
}
return new Ordinary(name, tapClass);
}
public static PerThread createRecursivePerThread(String name, PerThread outermostRecursivePerThread)
{
return new Recursive(name, outermostRecursivePerThread);
}
// For use by subclasses
protected PerThread(String name)
{
super(name);
}
// Class state
private static final Comparator<Thread> THREAD_COMPARATOR = new Comparator<Thread>()
{
@Override
public int compare(Thread x, Thread y)
{
return x.getName().compareTo(y.getName());
}
};
// Object state
final Map<Thread, Tap> threadMap = new ConcurrentHashMap<>();
boolean enabled = false;
// Inner classes
private static class Ordinary extends PerThread
{
// PerThread interface
Tap threadTap()
{
return threadLocal.get();
}
// Ordinary interface
Ordinary(String name, Class<? extends Tap> tapClass)
{
super(name);
this.tapClass = tapClass;
}
private final Class<? extends Tap> tapClass;
private final ThreadLocal<Tap> threadLocal = new ThreadLocal<Tap>()
{
@Override
protected Tap initialValue()
{
Tap tap;
try {
tap = tapClass.getConstructor(String.class).newInstance(name);
tap.reset();
if (!enabled) {
tap.disable();
}
threadMap.put(Thread.currentThread(), tap);
} catch (Exception e) {
tap = new Null(name);
LOG.warn("Unable to create tap of class " + tapClass.getSimpleName(), e);
}
return tap;
}
};
}
private static class Recursive extends PerThread
{
// Tap interface
@Override
public boolean isSubsidiary()
{
return true;
}
// PerThread interface
Tap threadTap()
{
return threadLocal.get();
}
// Recursive interface
Recursive(String name, PerThread outermostRecursivePerThread)
{
super(name);
this.outermostRecursivePerThread = outermostRecursivePerThread;
}
private final PerThread outermostRecursivePerThread;
private final ThreadLocal<Tap> threadLocal = new ThreadLocal<Tap>()
{
@Override
protected Tap initialValue()
{
Tap x = outermostRecursivePerThread.threadTap();
RecursiveTap.Outermost outermost =
(RecursiveTap.Outermost) x;
RecursiveTap tap = outermost.createSubsidiaryTimer(name);
tap.reset();
threadMap.put(Thread.currentThread(), tap);
return tap;
}
};
}
}