/******************************************************************************* * gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/ * Copyright (C) 2014 SVS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package staticContent.evaluation.loadGenerator.dynamicSchedule; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import staticContent.evaluation.loadGenerator.LoadGenerator; import staticContent.evaluation.loadGenerator.applicationLevelTraffic.requestReply.ALRR_ClientWrapper; import staticContent.evaluation.loadGenerator.applicationLevelTraffic.requestReply.ApplicationLevelMessage; import staticContent.evaluation.loadGenerator.applicationLevelTraffic.requestReply.EndOfFileReachedException; import staticContent.evaluation.loadGenerator.scheduler.InOrderYieldWaitScheduler; import staticContent.evaluation.loadGenerator.scheduler.ScheduleTarget; import staticContent.evaluation.loadGenerator.scheduler.Scheduler; import staticContent.evaluation.loadGenerator.scheduler.ThreadPoolScheduler; import staticContent.framework.AnonNode; import staticContent.framework.config.Paths; import staticContent.framework.config.Settings; import staticContent.framework.launcher.ToolName; import staticContent.framework.routing.RoutingMode; import staticContent.framework.socket.socketInterfaces.AnonSocketOptions.CommunicationDirection; import staticContent.framework.util.IOTester; import staticContent.framework.util.Util; public class ALM_DS_Tracefile { private Settings settings; private HashMap<Integer, ALRR_ClientWrapper> clientReferences; private ALRR_ClientWrapper[] clientsArray; private long experimentStart; // in nanosec private AnonNode client; private BufferedReader traceReader; private int lineCounter = 0; private Scheduler<ALRR_ClientWrapper> scheduler; private boolean readAheadRequired = true; private boolean stopExecution = false; private volatile boolean traceFileReaderActive = false; // used for weak overload detection private boolean hasMore = true; private int READ_AHEAD; private TraceFileReaderThread traceFileReaderThread; private ReplyReceiverThread replyReceiverThread; private RequestSender requestSender; public ALM_DS_Tracefile(LoadGenerator owner) { this.settings = owner.settings; boolean useYieldWaitScheduler = !settings.isPropertyPresent("USE_SLOW_BUT_ACCURATE_SCHEDULER") ? false : settings.getPropertyAsBoolean("USE_SLOW_BUT_ACCURATE_SCHEDULER"); if (useYieldWaitScheduler) this.scheduler = new InOrderYieldWaitScheduler<ALRR_ClientWrapper>(settings); else this.scheduler = new ThreadPoolScheduler<ALRR_ClientWrapper>(settings); this.traceFileReaderThread = new TraceFileReaderThread(); this.replyReceiverThread = new ReplyReceiverThread(); this.requestSender = new RequestSender(); this.experimentStart = scheduler.now() + TimeUnit.SECONDS.toNanos(2); this.READ_AHEAD = settings.getPropertyAsInt("AL-TRACE_FILE-READ_AHEAD"); System.out.println("LOAD_GENERATOR: start at " +experimentStart); // try to load trace-file: System.out.println("TRACE_READER: loading trace file"); String traceFilePath = Paths.getProperty("LG_TRACE_FILE_PATH") + settings.getProperty("AL-TRACE_FILE-NAME"); resetReader(traceFilePath); // create client owner.commandLineParameters.gMixTool = ToolName.CLIENT; this.client = new AnonNode(owner.commandLineParameters); // determine number of clients and lines; create ClientWrapper objects: this.clientReferences = new HashMap<Integer, ALRR_ClientWrapper>(1000); // TODO: dynamic String line; int ctr = 0; try { while ((line = traceReader.readLine()) != null) { ctr++; Integer id = Integer.parseInt(line.split("(,|;)")[1]); ALRR_ClientWrapper cw = clientReferences.get(id); if (cw == null) { cw = new ALRR_ClientWrapper(id); clientReferences.put(id, cw); } cw.TOTAL_TRANSACTIONS++; } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("TRACE_READER: error traversing trace file"); } System.out.println("TRACE_READER: found traces for " +clientReferences.size() +" clients in the trace file (" +ctr +" trace entries)"); resetReader(traceFilePath); // create array etc.: this.clientsArray = new ALRR_ClientWrapper[clientReferences.size()]; int i = 0; for (ALRR_ClientWrapper cw: clientReferences.values()) { clientsArray[i++] = cw; cw.traceEntries = new ConcurrentLinkedQueue<ApplicationLevelMessage>(); } // generate and connect sockets for (ALRR_ClientWrapper cw: clientsArray) // generate sockets cw.socket = client.createStreamSocket(CommunicationDirection.DUPLEX, client.ROUTING_MODE != RoutingMode.GLOBAL_ROUTING); // connect sockets: int port = settings.getPropertyAsInt("SERVICE_PORT1"); System.out.println("LOAD_GENERATOR: connecting clients..."); for (ALRR_ClientWrapper cw: clientsArray) try { cw.socket.connect(port); cw.outputStream = new BufferedOutputStream(cw.socket.getOutputStream()); cw.inputStream = cw.socket.getInputStream(); } catch (IOException e) { e.printStackTrace(); } // read first trace entries this.traceFileReaderThread.readAhead(); // let this thread read first entries to assure they are available in the next loop this.traceFileReaderThread.start(); // let TraceFileReaderThread read further entries (in the future) // schedule first request for each client long now = scheduler.now(); for (ALRR_ClientWrapper cw: clientsArray) { cw.currentTraceEntry = cw.traceEntries.poll(); cw.transactionCounter++; scheduler.executeIn( (now - experimentStart) + cw.currentTraceEntry.getSendDelayInNanoSec(), requestSender, cw ); } // start reply receiver: this.replyReceiverThread.start(); } private class TraceFileReaderThread extends Thread { @Override public void run() { while (hasMore) { synchronized (traceFileReaderThread) { while (!readAheadRequired) { try { traceFileReaderThread.wait(); } catch (InterruptedException e) { continue; } } readAheadRequired = false; } if (!stopExecution) readAhead(); } } private void readAhead() { synchronized (traceReader) { traceFileReaderActive = true; System.out.println("start reading from logfile (" +(READ_AHEAD/2) +" lines)"); long start = System.currentTimeMillis(); ApplicationLevelMessage currentEntry = null; for (int i=0; i<(READ_AHEAD/2); i++) { try { currentEntry = new ApplicationLevelMessage(traceReader, ++lineCounter); } catch (EndOfFileReachedException e) { lineCounter--; hasMore = false; } ALRR_ClientWrapper cw = clientReferences.get(currentEntry.getClientId()); cw.traceEntries.add(currentEntry); } currentEntry.isNotifyEvent = true; System.out.println("finished reading from logfile (" +(READ_AHEAD/2) +" lines, " +(System.currentTimeMillis()-start) +" ms)"); traceFileReaderActive = false; } } // read till a line for the client with id "clientId" is found (and read some more lines) // called when RequestSender runs out of messages public void readAhead(int clientId) { synchronized (traceReader) { int ctr = 0; traceFileReaderActive = true; System.out.println("start reading from logfile"); long start = System.currentTimeMillis(); ApplicationLevelMessage currentEntry = null; while (true) { try { currentEntry = new ApplicationLevelMessage(traceReader, ++lineCounter); } catch (EndOfFileReachedException e) { lineCounter--; hasMore = false; } ctr++; if (!hasMore) throw new RuntimeException("no record for client " +clientId); ALRR_ClientWrapper cw = clientReferences.get(currentEntry.getClientId()); cw.traceEntries.add(currentEntry); if (cw.identifier == clientId) break; } if (hasMore) { for (int i=0; i<(READ_AHEAD/2); i++) { // read further lines try { currentEntry = new ApplicationLevelMessage(traceReader, ++lineCounter); } catch (EndOfFileReachedException e) { lineCounter--; hasMore = false; } ctr++; ALRR_ClientWrapper cw = clientReferences.get(currentEntry.getClientId()); cw.traceEntries.add(currentEntry); } currentEntry.isNotifyEvent = true; } System.out.println("finished reading from logfile (" +ctr +" lines, " +(System.currentTimeMillis()-start) +" ms)"); traceFileReaderActive = false; } } public void wakeUp() { if (traceFileReaderActive) System.err.println("TRACE_READER: warning: it seems the " +"trace file can't be read fast enough. simulation may " +"be inaccurate!"); synchronized (traceFileReaderThread) { readAheadRequired = true; traceFileReaderThread.notifyAll(); } } } private class ReplyReceiverThread extends Thread { @Override public void run() { while (true) { // read replies... int ctr = 0; for (ALRR_ClientWrapper cw: clientsArray) { // ...for each client int available; try { available = cw.inputStream.available(); } catch (IOException e) { e.printStackTrace(); continue; } if (available > 0) { // if data available synchronized (cw) { try { ctr++; // read available data: byte[] arrivedData = new byte[available]; int read = cw.inputStream.read(arrivedData); assert read == arrivedData.length: ""+read; // assured by mix io-streams if (LoadGenerator.VALIDATE_IO) IOTester.findInstance("reply-"+cw.identifier).addReceiveRecord(arrivedData); ApplicationLevelMessage reply = cw.currentTraceEntry; reply.addReplyChunk(arrivedData); if (!reply.needMoreReplyChunks()) { // reply now received completely // display stats: long delay = System.currentTimeMillis() - reply.getAbsoluteSendTime(); System.out.println( "LOAD_GENERATOR: received reply (" + "transactionId:" +reply.getTransactionId() +"; replySize: " +reply.getReplySize() +"bytes" +"; delay: " +delay +"ms" +")" ); // schedule next request cw.currentTraceEntry = cw.traceEntries.poll(); cw.transactionCounter++; if (cw.currentTraceEntry == null) { if (cw.transactionCounter == cw.TOTAL_TRANSACTIONS) { continue; } else { System.err.println("warning AL-TRACE_FILE-READ_AHEAD set to a too low value; the messages of client " +cw.identifier +" may be deffered"); traceFileReaderThread.readAhead(cw.identifier); cw.currentTraceEntry = cw.traceEntries.poll(); } } if (cw.currentTraceEntry.isNotifyEvent) traceFileReaderThread.wakeUp(); scheduler.executeIn( cw.currentTraceEntry.getSendDelayInNanoSec(), requestSender, cw ); } } catch (IOException e) { e.printStackTrace(); continue; } } } } if (ctr == 0) // TODO wait-notify? try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} } } } private class RequestSender implements ScheduleTarget<ALRR_ClientWrapper> { @Override public void execute(ALRR_ClientWrapper cw) { synchronized (cw) { ApplicationLevelMessage message = cw.currentTraceEntry; try { message.setAbsoluteSendTime(System.currentTimeMillis()); byte[] payload = message.createPayloadForRequest(); String stats = "LOAD_GENERATOR: sending request (" +"client:" +message.getClientId() +"; transactionId:" +message.getTransactionId() +"; requestSize: " +message.getRequestSize() +"bytes"; if (message.getReplySize() != Util.NOT_SET) stats += "; replySize: " +message.getReplySize() +"bytes"; stats += ")"; System.out.println(stats); if (LoadGenerator.VALIDATE_IO) IOTester.findInstance(""+message.getClientId()).addSendRecord(payload); cw.outputStream.write(payload); cw.outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } } } private void resetReader(String traceFilePath) { try { this.traceReader = new BufferedReader(new FileReader(traceFilePath)); } catch (FileNotFoundException e) { System.err.println("TRACE_READER: could not find trace file: " + traceFilePath); } } public static ALM_DS_Tracefile createInstance(LoadGenerator loadGenerator) { return new ALM_DS_Tracefile(loadGenerator); } }