/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM 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
* any later version.
*
* PeerfactSim.KOM 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 PeerfactSim.KOM. If not, see .
*
*/
package de.tud.kom.p2psim.impl.churn;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import de.tud.kom.p2psim.api.churn.ChurnModel;
import de.tud.kom.p2psim.api.common.SimHost;
import de.tud.kom.p2psim.api.network.SimNetInterface;
import de.tud.kom.p2psim.impl.scenario.DefaultConfigurator;
import de.tud.kom.p2psim.impl.simengine.Simulator;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;
/**
* A simple churn model that reads a text file as input to set groups of nodes on- and offline.
* Doing so the path to the text file has to be given in the config.
*
* The file must have the following format:
*
* group_name, time when to go online, time when to go offline, duration
*
* The following example shows the format:
*
* main_noise_peers_8 , 30min, 15min, 45min
* main_noise_peers_8 , 1h, 2h, 1h
* ...
*
* @author Nils Richerzhagen
* @version 1.0, Feb 18, 2015
*/
public class CSVBasedChurnModel implements ChurnModel {
private String filename;
private final String SEP = ",";
private Map> churnInfos;
private Map usedForDowntime;
/**
*
* @param file
*/
@XMLConfigurableConstructor({ "file" })
public CSVBasedChurnModel(String file) {
this.filename = file;
this.churnInfos = new LinkedHashMap>();
this.usedForDowntime = new LinkedHashMap();
}
@Override
public long getNextUptime(SimHost host) {
String groupId = host.getProperties().getGroupID();
LinkedList hostChurnInfos = churnInfos.get(groupId);
if(usedForDowntime.containsKey(groupId) && usedForDowntime.get(groupId) == true){
hostChurnInfos.removeFirst();
usedForDowntime.put(groupId, false);
}
else
usedForDowntime.put(groupId, false);
if(hostChurnInfos.isEmpty())
throw new AssertionError("No more churn infos left, is tht possible? " + host.getProperties().getGroupID());
ChurnInfo churnInfo = hostChurnInfos.getFirst();
if(churnInfo == null){
throw new AssertionError("Seems to have not enough entries for group: " + host.getProperties().getGroupID());
}
// if(!notRemovedFirstThisRun){
// notRemovedFirstThisRun = true;
// }
return churnInfo.getStartTime() - Simulator.getCurrentTime();
// // the next interval should be an offline
// if(churnInfo.isOnline()){
// return churnInfo.getDuration();
// }
// else{
// try {
// hostChurnInfos.removeFirst();
// } catch (NoSuchElementException e) {
// throw new AssertionError("Seems to have not enough entries for group: " + host.getProperties().getGroupID());
// }
// churnInfo = hostChurnInfos.getFirst();
// if(churnInfo == null)
// {
// throw new AssertionError("Seems to have not enough entries for group: " + host.getProperties().getGroupID());
// }
// if(!churnInfo.isOnline()){
// throw new AssertionError("There should be an online interval after the offline one: " + host.getProperties().getGroupID());
// }
// return churnInfo.getDuration();
// }
}
@Override
public long getNextDowntime(SimHost host) {
String groupId = host.getProperties().getGroupID();
LinkedList hostChurnInfos = churnInfos.get(groupId);
usedForDowntime.put(groupId, true);
// if(usedForDowntime){
// hostChurnInfos.removeFirst();
// usedForDowntime = false;
// }
ChurnInfo churnInfo = hostChurnInfos.getFirst();
if(churnInfo == null){
throw new AssertionError("Seems to have not enough entries for group: " + host.getProperties().getGroupID());
}
// use churn info
return churnInfo.getEndTime() - Simulator.getCurrentTime();
// // the next interval should be an offline
// if(!churnInfo.isOnline()){
// return churnInfo.getDuration();
// }
// else{
// try {
// hostChurnInfos.removeFirst();
// } catch (NoSuchElementException e) {
// throw new AssertionError("Seems to have not enough entries for group: " + host.getProperties().getGroupID());
// }
// churnInfo = hostChurnInfos.getFirst();
// if(churnInfo == null){
// throw new AssertionError("Seems to have not enough entries for group: " + host.getProperties().getGroupID());
// }
// if(churnInfo.isOnline()){
// throw new AssertionError("There should be an offline interval after the previous online one: " + host.getProperties().getGroupID());
// }
// return churnInfo.getDuration();
// }
}
@Override
public void prepare(List churnHosts) {
for (SimHost host : churnHosts) {
for (SimNetInterface netI : host.getNetworkComponent()
.getSimNetworkInterfaces()) {
if (netI.isOnline()) {
netI.goOffline();
}
}
}
parseTrace(filename);
}
private void parseTrace(String filename) {
System.out.println("==============================");
System.out.println("Reading trace from " + filename);
/*
* This parser works for the following csv file structure.
*
* groupID, startTime, endTime, duration, boolean (true/false)
*
* groupID - describes the group id of the nodes that are affected
*
* startTime - the time when the event should occur
*
* endTime - Insanity Check
*
* duration - how long should the event take
*
* interChrunInterval - defines the time between each leave or join
* action in this group and this event. This is used as it makes the
* behaviour more realistic compared to a simple online/offline at the
* same time action.
*
* boolean - defines if the group should be online or offline during this event
*/
BufferedReader csv = null;
boolean entrySuccessfullyRead = false;
int lines = 0;
try {
csv = new BufferedReader(new FileReader(filename));
String previousGroupId = "";
boolean firstGroup = true;
long previousEndTime = 0;
while (csv.ready()) {
String line = csv.readLine();
lines++;
if (line.indexOf(SEP) > -1) {
String[] parts = line.split(SEP);
if (parts.length == 4) {
try {
String groupID = parts[0].replaceAll("\\s+","");
int a = DefaultConfigurator.parseNumber("20m", Integer.class);
long startTime = DefaultConfigurator.parseNumber(parts[1].replaceAll("\\s+",""), Long.class);
long endTime = DefaultConfigurator.parseNumber(parts[2].replaceAll("\\s+",""), Long.class);
long duration = DefaultConfigurator.parseNumber(parts[3].replaceAll("\\s+",""), Long.class);
// boolean online = Boolean.parseBoolean(parts[4].replaceAll("\\s+", ""));
// String onlineString = parts[5].replaceAll("\\s+","");
if(firstGroup){
previousGroupId = groupID;
// previousEndTime = startTime;
firstGroup = false;
}
// new group id
if(!previousGroupId.equals(groupID)){
previousGroupId = groupID;
// previousEndTime = startTime;
previousEndTime = 0;
}
// Insanity Checks
// if(startTime != previousEndTime){
if(startTime < previousEndTime){
throw new AssertionError("Wrong time in CSV for startTime as previousEndTime is larger.");
}
if(endTime - startTime != duration){
throw new AssertionError("Duration must be the same as endTime - startTime");
}
// if((onlineString.equals("true") && !online)|| (onlineString.equals("false") && online)){
// throw new AssertionError("Boolean should be the same!");
// }
LinkedList infoList = churnInfos.get(groupID);
if(infoList == null){
infoList = new LinkedList();
// infoList.add(new ChurnInfo(startTime, online, endTime));
infoList.add(new ChurnInfo(startTime, endTime));
churnInfos.put(groupID, infoList);
}
else{
// infoList.add(new ChurnInfo(startTime, online, endTime));
infoList.add(new ChurnInfo(startTime, endTime));
}
entrySuccessfullyRead = true;
} catch (NumberFormatException e) {
// Ignore leading comments
if (entrySuccessfullyRead) {
// System.err.println("CSV ParseError " +
// line);
}
}
} else {
throw new AssertionError("To many/few columns in CSV.");
}
}
}
}
catch (Exception e) {
System.err.println("Could not open " + filename);
throw new RuntimeException("Could not open " + filename);
}
finally {
if (csv != null) {
try {
csv.close();
} catch (IOException e) {
//
}
}
}
}
private class ChurnInfo {
long startTime;
long endTime;
// boolean online;
public ChurnInfo(long startTime, long endTime){
// public ChurnInfo(long startTime, boolean online, long endTime){
this.startTime = startTime;
// this.online = online;
this.endTime = endTime;
}
public long getEndTime(){
return endTime;
}
public long getDuration(){
// TODO introduce skew/offset
return (endTime - startTime);
}
public long getStartTime(){
return startTime;
}
//
// /**
// * Returns if this churn interval stands for an online interval
// * @return
// */
// public boolean isOnline(){
// return this.online;
// }
}
}