blob: 403bee01c3533c76e9c9316534b01cdc532f9dd7 [file] [log] [blame]
package org.apache.cassandra.stress.util;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
// relatively simple timing class for getting a uniform sample of latencies, and saving other metrics
// ensures accuracy of timing by having single threaded timers that are check-pointed by the snapping thread,
// which waits for them to report back. They report back the data up to the last event prior to the check-point.
// if the threads are blocked/paused this may mean a period of time longer than the checkpoint elapses, but that all
// metrics calculated over the interval are accurate
public class Timing
{
// concurrency: this should be ok as the consumers are created serially by StressAction.run / warmup
// Probably the CopyOnWriteArrayList could be changed to an ordinary list as well.
private final Map<String, List<Timer>> timers = new TreeMap<>();
private volatile TimingIntervals history;
private final int historySampleCount;
private final int reportSampleCount;
private boolean done;
public Timing(int historySampleCount, int reportSampleCount)
{
this.historySampleCount = historySampleCount;
this.reportSampleCount = reportSampleCount;
}
// TIMING
public static class TimingResult<E>
{
public final E extra;
public final TimingIntervals intervals;
public TimingResult(E extra, TimingIntervals intervals)
{
this.extra = extra;
this.intervals = intervals;
}
}
public <E> TimingResult<E> snap(Callable<E> call) throws InterruptedException
{
// Count up total # of timers
int timerCount = 0;
for (List<Timer> timersForOperation : timers.values())
{
timerCount += timersForOperation.size();
}
final CountDownLatch ready = new CountDownLatch(timerCount);
// request reports
for (List <Timer> timersForOperation : timers.values())
{
for(Timer timer : timersForOperation)
{
timer.requestReport(ready);
}
}
E extra;
try
{
extra = call.call();
}
catch (Exception e)
{
if (e instanceof InterruptedException)
throw (InterruptedException) e;
throw new RuntimeException(e);
}
// TODO fail gracefully after timeout if a thread is stuck
if (!ready.await(5L, TimeUnit.MINUTES))
{
throw new RuntimeException("Timed out waiting for a timer thread - seems one got stuck. Check GC/Heap size");
}
boolean done = true;
// reports have been filled in by timer threadCount, so merge
Map<String, TimingInterval> intervals = new TreeMap<>();
for (Map.Entry<String, List<Timer>> entry : timers.entrySet())
{
List<TimingInterval> operationIntervals = new ArrayList<>();
for (Timer timer : entry.getValue())
{
operationIntervals.add(timer.report);
done &= !timer.running();
}
intervals.put(entry.getKey(), TimingInterval.merge(operationIntervals, reportSampleCount,
history.get(entry.getKey()).endNanos()));
}
TimingIntervals result = new TimingIntervals(intervals);
this.done = done;
history = history.merge(result, historySampleCount, history.startNanos());
return new TimingResult<>(extra, result);
}
// build a new timer and add it to the set of running timers.
public Timer newTimer(String opType, int sampleCount)
{
final Timer timer = new Timer(sampleCount);
if (!timers.containsKey(opType))
timers.put(opType, new ArrayList<Timer>());
timers.get(opType).add(timer);
return timer;
}
public void start()
{
history = new TimingIntervals(timers.keySet());
}
public boolean done()
{
return done;
}
public TimingIntervals getHistory()
{
return history;
}
}