sirius.kernel.async.Operation Maven / Gradle / Ivy
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.kernel.async;
import com.google.common.collect.Lists;
import sirius.kernel.commons.Watch;
import sirius.kernel.di.std.Register;
import sirius.kernel.health.metrics.MetricProvider;
import sirius.kernel.health.metrics.MetricsCollector;
import sirius.kernel.nls.NLS;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* Tracks the execution of a blocking operation.
*
* The operations framework is used to track blocking operations which might possibly hang. As a blocking operation
* cannot be checked by the calling thread itself, the classical alternative would be to fork a thread which
* monitors the operation. As this approach does not scale very well, the operations framework creates a
* lighweight Operation object around a potentially blocking operation using a try-with-resources block.
*
* A metrics provider will check for all operations and use its limits (set by component-kernel.conf,
* to warn if too many operations are active (or are probably hanging).
*
* Other frameworks can provider further help: SIRIUS-WEB e.g. provides a list of all operations
* using the async command in the system console.
*/
public class Operation implements AutoCloseable {
private static final Set ops =
Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
private Supplier nameProvider;
private Duration timeout;
private String name;
private Watch w = Watch.start();
/**
* Creates a new operation.
*
* @param name the supplier used to compute a user readable name if the operation is rendered somewhere
* @param timeout the timeout. If the duration is longer than the given timeout,
* this operation is considered "hanging"
*/
public Operation(Supplier name, Duration timeout) {
this.nameProvider = name;
this.timeout = timeout;
ops.add(this);
}
@Override
public void close() {
ops.remove(this);
if (isOvertime()) {
Tasks.LOG.WARN(toString());
}
}
/**
* Determines if the duration of the operation is longer than its timeout
*
* @return true if the duration of the operation is longer than its timeout, false otherwise
*/
public boolean isOvertime() {
return w.elapsed(TimeUnit.SECONDS, false) > timeout.getSeconds();
}
@Override
public String toString() {
if (name == null) {
name = nameProvider.get();
}
String result = name + " (" + w.duration() + "/" + NLS.convertDuration(timeout.getSeconds(), true, false) + ")";
if (isOvertime()) {
result += " OVERTIME!";
}
return result;
}
/**
* Returns a list of all currently active operations
*
* @return a list of all known operations
*/
public static List getActiveOperations() {
return Lists.newArrayList(ops);
}
/**
* Provides metrics of the operation monitoring.
*
* The provided metrics are active-operations, which contains the number of active operations and
* hanging-operations, which contains the number of operations that take longer than expected
* (and therefore might hang).
*/
@Register
public static class OperationMetrics implements MetricProvider {
@Override
public void gather(MetricsCollector collector) {
collector.metric("kernel_active_operations", "active-operations", "Active-Operations", ops.size(), null);
collector.metric("kernel_hanging_operations",
"hanging-operations",
"Hanging-Operations",
getActiveOperations().stream().filter(Operation::isOvertime).count(),
null);
}
}
}