/*
* Copyright 2015 Ben Manes. All Rights Reserved.
*
* Licensed 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.
*/
package com.github.benmanes.caffeine.cache.simulator;
import static com.github.benmanes.caffeine.cache.simulator.Simulator.Message.ERROR;
import static com.github.benmanes.caffeine.cache.simulator.Simulator.Message.FINISH;
import static com.github.benmanes.caffeine.cache.simulator.Simulator.Message.START;
import static java.util.stream.Collectors.toList;
import java.io.IOException;
import java.util.List;
import java.util.PrimitiveIterator;
import java.util.stream.LongStream;
import com.github.benmanes.caffeine.cache.simulator.parser.TraceFormat;
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyActor;
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.github.benmanes.caffeine.cache.simulator.policy.Registry;
import com.github.benmanes.caffeine.cache.simulator.report.Reporter;
import com.google.common.base.Stopwatch;
import com.typesafe.config.Config;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.routing.ActorRefRoutee;
import akka.routing.BroadcastRoutingLogic;
import akka.routing.Routee;
import akka.routing.Router;
import it.unimi.dsi.fastutil.longs.LongArrayList;
/**
* A simulator that broadcasts the recorded cache events to each policy and generates an aggregated
* report. See <tt>reference.conf</tt> for details on the configuration.
* <p>
* The simulator reports the hit rate of each of the policy being evaluated. A miss may occur
* due to,
* <ul>
* <li>Conflict: multiple entries are mapped to the same location
* <li>Compulsory: the first reference misses and the entry must be loaded
* <li>Capacity: the cache is not large enough to contain the needed entries
* <li>Coherence: an invalidation is issued by another process in the system
* </ul>
* <p>
* It is recommended that multiple access traces are used during evaluation to see how the policies
* handle different workload patterns. When choosing a policy some metrics that are not reported
* may be relevant, such as the cost of maintaining the policy's internal structures.
*
* @author ben.manes@gmail.com (Ben Manes)
*/
public final class Simulator extends AbstractActor {
public enum Message { START, FINISH, ERROR }
private final BasicSettings settings;
private final Stopwatch stopwatch;
private final Reporter reporter;
private final Router router;
private final int batchSize;
private int remaining;
public Simulator() {
Config config = context().system().settings().config().getConfig("caffeine.simulator");
settings = new BasicSettings(config);
List<Routee> routes = makeRoutes();
router = new Router(new BroadcastRoutingLogic(), routes);
remaining = routes.size();
batchSize = settings.batchSize();
stopwatch = Stopwatch.createStarted();
reporter = settings.report().format().create(config);
}
@Override
public void preStart() {
self().tell(START, self());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals(START, msg -> broadcast())
.matchEquals(ERROR, msg -> context().stop(self()))
.match(PolicyStats.class, this::reportStats)
.build();
}
/** Broadcast the trace events to all of the policy actors. */
private void broadcast() {
try (LongStream events = eventStream()) {
LongArrayList batch = new LongArrayList(batchSize);
for (PrimitiveIterator.OfLong i = events.iterator(); i.hasNext();) {
batch.add(i.nextLong());
if (batch.size() == batchSize) {
router.route(batch, self());
batch = new LongArrayList(batchSize);
}
}
router.route(batch, self());
router.route(FINISH, self());
} catch (Exception e) {
context().system().log().error(e, "");
context().stop(self());
}
}
/** Returns a stream of trace events. */
private LongStream eventStream() throws IOException {
if (settings.isSynthetic()) {
return Synthetic.generate(settings);
}
List<String> filePaths = settings.traceFiles().paths();
TraceFormat format = settings.traceFiles().format();
return format.readFiles(filePaths).events();
}
/** Returns the actors to broadcast trace events to. */
private List<Routee> makeRoutes() {
return Registry.policies(settings).stream().map(policy -> {
ActorRef actorRef = context().actorOf(Props.create(PolicyActor.class, policy));
context().watch(actorRef);
return new ActorRefRoutee(actorRef);
}).collect(toList());
}
/** Add the stats to the reporter, print if completed, and stop the simulator. */
private void reportStats(PolicyStats stats) throws IOException {
reporter.add(stats);
if (--remaining == 0) {
reporter.print();
context().stop(self());
System.out.println("Executed in " + stopwatch);
}
}
public static void main(String[] args) {
akka.Main.main(new String[] { Simulator.class.getName() } );
}
}