Chris R
Chris R

Reputation: 17896

How to track task execution statistics using an ExecutorService?

I'm firing off tasks using an ExecutorService, dispatching tasks that need to be grouped by task-specific criteria:

Task[type=a]
Task[type=b]
Task[type=a]
...

Periodically I want to output the average length of time that each task took (grouped by type) along with statistical information such as mean/median and standard deviation.

This needs to be pretty fast, of course, and ideally should not cause the various threads to synchronize when they report statistics. What's a good architecture for doing this?

Upvotes: 10

Views: 9573

Answers (5)

Victor Choy
Victor Choy

Reputation: 4246

I agree with @Robert Munteanu. The beforeExecute in threadpool really worthing nothing even though the docs said it can be used to statistic. But in fact, we cannot check the runnable's identity in the our situation.

I think a wrapper can arrive this.

public interface ICallableHook<V> {
    void beforeExecute(Thread t, Callable<V> callable);
    void afterExecute(Callable<V> callable, V result, Throwable e);
}


private class CallableWrapper<V> implements Callable<V> {
        private ICallableHook hooker;
        private Callable<V> callable;

        CallableWrapper(Callable callable, ICallableHook hooker) {
            this.callable = callable;
            this.hooker = hooker;
        }




    @Override
    public V call() throws Exception {
        if (hooker != null) {
            hooker.beforeExecute(Thread.currentThread(), callable);
        }

        V result = null;
        Exception exception = null;
        try {
            result = callable.call();
        } catch (Exception e) {
            exception = e;
            throw e;
        } finally {
            if (hooker != null) {
                hooker.afterExecute(callable, result, exception);
            }
        }
        return result;
    }
}

Usage like this,

  for (Callable<XXX> callable : callableList) {
        CallableWrapper<XXX> callableWrapper = new CallableWrapper<>(callable, hooker);
        Future task = completionService.submit(callableWrapper);

    }

Upvotes: 0

leef
leef

Reputation: 593

Another way is to use wrapper/decorator pattern.

public class Job implements Runnable {
private Runnable _task;
private Statistics _statistics;

public Job(Runnable task, Statistics statistics) {
    this._task = task;
}

public void run() {
    long s = System.currentTimeMillis();
    _task.run();
    long e = System.currentTimeMillis();

    long executionTime = e - s;
    _statistics.updateStatistics(executionTime);
}
}

Upvotes: 2

Gandalf
Gandalf

Reputation: 9845

I believe the two other answers are correct, but maybe a bit too complicated (although my answer, while simple, is probably not quite as performant as theirs.

Why not just use Atomic variables to keep track of your stats? Such as number of tasks run, total execution time (divided by total number, you get avg execution time). Pass these variables into your Runnable for each task. Unless your tasks as extremely short lived I do not think the overhead of locking an Atomic variable will impact you.

Upvotes: 1

Adam Jaskiewicz
Adam Jaskiewicz

Reputation: 10996

ThreadPoolExecutor provides beforeExecute and afterExecute methods that you can override. You could use those to record your statistics in a single (member variable of your ExecutorService) ConcurrentHashMap keyed on some unique identifier for your tasks, and storing the type, start time, and end time.

Calculate the statistics from the ConcurrentHashMap when you are ready to look at them.

Upvotes: 11

Robert Munteanu
Robert Munteanu

Reputation: 68268

Subclass Thread Pool Executor and track the execution events:

It's worth noting that the methods are invoked by the worker thread which executes the task, so you need to insure thread safety for the execution tracking code.

Also, the Runnables you will receive will most likely not be your Runnables, but wrapped in FutureTasks.

Upvotes: 4

Related Questions