/*******************************************************************************
* Copyright (c) 2006-2010 Vienna University of Technology,
* Department of Software Technology and Interactive Systems
*
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Apache License, Version 2.0 which accompanies
* this distribution, and is available at
* http://www.apache.org/licenses/LICENSE-2.0
*******************************************************************************/
package at.tuwien.minimee.migration.engines;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import at.tuwien.minimee.migration.runners.DefaultRunner;
import at.tuwien.minimee.migration.runners.IRunner;
import at.tuwien.minimee.migration.runners.RunInfo;
import at.tuwien.minimee.model.Machine;
import at.tuwien.minimee.model.ToolConfig;
import at.tuwien.minimee.registry.ToolRegistry;
import eu.planets_project.pp.plato.model.DigitalObject;
import eu.planets_project.pp.plato.model.FormatInfo;
import eu.planets_project.pp.plato.model.measurement.MeasurableProperty;
import eu.planets_project.pp.plato.model.measurement.Measurement;
import eu.planets_project.pp.plato.model.values.FreeStringValue;
import eu.planets_project.pp.plato.model.values.PositiveFloatValue;
import eu.planets_project.pp.plato.services.action.MigrationResult;
import eu.planets_project.pp.plato.util.FileUtils;
import eu.planets_project.pp.plato.util.OS;
import eu.planets_project.pp.plato.util.PlatoLogger;
public class MiniMeeDefaultMigrationEngine implements IMigrationEngine {
private Log log = PlatoLogger.getLogger(this.getClass());
protected DecimalFormat df = new DecimalFormat("#####.###");
/**
* temporary directory to be used for storing files
*/
private String tempDir;
private String machine;
private String configParam;
public String getConfigParam() {
return configParam;
}
public void setConfigParam(String configParam) {
this.configParam = configParam;
}
public String getMachine() {
return machine;
}
public void setMachine(String machine) {
this.machine = machine;
}
public void addProperty(MeasurableProperty p) {
measurableProperties.add(p);
}
private List<MeasurableProperty> measurableProperties = new ArrayList<MeasurableProperty>();
protected String name;
/**
* initialises the engine
* @see #initTempDir()
*/
public MiniMeeDefaultMigrationEngine() {
initTempDir();
}
/**
* We have to make sure here that the temporary directory is initialized correctly.
* Depending on the configuration and on the operating system, getProperty("java.io.tmpdir")
* comes with or without an ending slash. We rely on the fact that it is
*/
private void initTempDir() {
tempDir = OS.getTmpPath();
}
public String getTempDir() {
if (tempDir == "") {
initTempDir();
}
return tempDir;
}
public String getName() {
return name;
}
public List<MeasurableProperty> getMeasurableProperties() {
return measurableProperties;
}
public void setMeasurableProperties(
List<MeasurableProperty> measurableProperties) {
this.measurableProperties = measurableProperties;
}
/**
* @see IMigrationEngine#migrate(byte[], String, String)
* This measures <b>elapsed time</b> and relative file size only.
*/
public MigrationResult migrate(byte[] data, String toolID, String params) {
MigrationResult result = new MigrationResult();
String key = "minimee/";
String toolIdentifier = toolID.substring(toolID.indexOf(key)
+ key.length());
ToolConfig config = ToolRegistry.getInstance().getToolConfig(toolIdentifier);
migrate(data, config, params, result);
normaliseMeasurements(result, toolIdentifier);
result.getMeasurements().put("input:filesize",new Measurement("input:filesize",data.length));
return result;
}
/**
* Currently does not do anything.
* @param result
* @param toolIdentifier
*/
protected void normaliseMeasurements(MigrationResult result, String toolIdentifier) {
/*
for (MeasurableProperty p : measurableProperties) {
if (p.isNumeric()) {
//Accumulate average experience
Measurement measured = result.getMeasurements().get(p.getName());
Measurement m = ToolRegistry.getInstance().addExperience(toolIdentifier, measured);
result.getMeasurements().put(m.getProperty().getName(), m);
// Add normalised value
if (p.getName().startsWith("performance:time") && (!p.getName().contains("normalised"))) {
Double d = ((INumericValue) measured.getValue()).value();
Measurement normalised = new Measurement();
MeasurableProperty p2 = new MeasurableProperty();
p2.setName(p.getName()+":normalised");
p2.setScale(p.getScale());
normalised.setProperty(p2);
PositiveFloatValue v = (PositiveFloatValue) p2.getScale().createValue();
v.setValue(d/ToolRegistry.getInstance().getBenchmarkScore()); // <<<< NORMALISED
normalised.setValue(v);
result.getMeasurements().put(normalised.getProperty().getName(), normalised);
}
}
}
*/
}
/**
* migrates a bytestream using the provided toolconfig and params,
* and measures elapsed time.
*
*/
public boolean migrate(byte[] data, ToolConfig config, String params,
MigrationResult result) {
System.gc();
boolean success = false;
/**
* we use this variable - time - for uniquely identifying all
* files. This is handed down the working methods and appended
* to temp files. Using nanoTime is sufficiently safe to avoid duplicates.
*/
long time = System.nanoTime();
String inputFile = prepareInputFile(data, config, time);
String outputFile = prepareOutputFile(config, time);
try {
String command = prepareCommand(config, params, inputFile, outputFile, time);
IRunner runner = makeRunner(command, config);
RunInfo r = runner.run();
result.setSuccessful(r.isSuccess());
result.setReport(r.getReport());
byte[] migratedFile = new byte[]{};
try {
migratedFile = FileUtils.getBytesFromFile(new File(outputFile));
DigitalObject u = new DigitalObject();
u.getData().setData(migratedFile);
FormatInfo tFormat = new FormatInfo();
tFormat.setDefaultExtension(config.getOutEnding());
result.setTargetFormat(tFormat);
result.setMigratedObject(u);
} catch (Exception e) {
log.error("Could not get outputfile "+outputFile);
result.setSuccessful(false);
log.error(e);
}
collectData(config, time, result);
double length = migratedFile.length;
double elapsed = r.getElapsedTimeMS();
double elapsedPerMB = ((double)elapsed)/(getMByte(data));
Measurement me = new Measurement(MigrationResult.MIGRES_ELAPSED_TIME,elapsed);
result.getMeasurements().put(MigrationResult.MIGRES_ELAPSED_TIME, me);
for (MeasurableProperty property: getMeasurableProperties()) {
if (!property.getName().startsWith("machine:")) {
Measurement m = new Measurement();
m.setProperty(property);
PositiveFloatValue v = (PositiveFloatValue) property.getScale().createValue();
if (property.getName().equals(MigrationResult.MIGRES_ELAPSED_TIME)) {
v.setValue(elapsed);
m.setValue(v);
result.getMeasurements().put(property.getName(), m);
} else if (property.getName().equals(MigrationResult.MIGRES_ELAPSED_TIME_PER_MB)) {
v.setValue(elapsedPerMB);
m.setValue(v);
result.getMeasurements().put(property.getName(), m);
} else if (property.getName().equals(MigrationResult.MIGRES_RELATIVE_FILESIZE)) {
v.setValue(((double)length)/data.length * 100);
m.setValue(v);
result.getMeasurements().put(property.getName(), m);
} else if (property.getName().equals(MigrationResult.MIGRES_RESULT_FILESIZE)) {
v.setValue((double)length);
m.setValue(v);
result.getMeasurements().put(property.getName(), m);
}
}
}
success = r.isSuccess();
} catch (Exception e) {
log.error(e.getMessage(),e);
return false;
} finally {
cleanup(time,inputFile,outputFile);
}
return success;
}
/**
* utility method to get the size in megabyte from a bytestream.
* @param data
* @return
*/
protected double getMByte(byte[] data) {
return ((double)data.length)/1048576;
}
/**
* deletes input and outputfile
* @param time not used at the moment
* @param inputFile absolute path of the input file
* @param outputFile absolute path of the input file
*/
protected void cleanup(long time, String inputFile, String outputFile) {
new File(inputFile).delete();
new File(outputFile).delete();
}
/**
* collects all available measurements and puts them into the provided
* MigrationResult
* @param config {@link ToolConfig} to use
* @param time
* @param result here the measurements are deposited
*/
protected void collectData(ToolConfig config, long time, MigrationResult result){
// overwrite this to collect additional performance data and
// add it to the measurements of MigrationResult
Machine m = ToolRegistry.getInstance().getMachine(machine);
for (MeasurableProperty property: getMeasurableProperties()) {
if (property.getName().startsWith("machine:")) {
Measurement measurement = new Measurement();
measurement.setProperty(property);
FreeStringValue v =(FreeStringValue) property.getScale().createValue();
if (property.getName().equals(Machine.MACHINE_NAME)) {
v.setValue(m.getId());
} else if (property.getName().equals(Machine.MACHINE_OS)) {
v.setValue(m.getOperatingSystem());
} else if (property.getName().equals(Machine.MACHINE_CPUS)) {
v.setValue(m.getCpus());
} else if (property.getName().equals(Machine.MACHINE_CPUCLOCK)) {
v.setValue(m.getCpuClock());
} else if (property.getName().equals(Machine.MACHINE_CPUTYPE)) {
v.setValue(m.getCpuType());
} else if (property.getName().equals(Machine.MACHINE_MEMORY)) {
v.setValue(m.getMemory());
}
measurement.setValue(v);
result.getMeasurements().put(property.getName(), measurement);
}
}
}
/**
* creates an {@link IRunner} instance to be doing the job
* @param command to be executed
* @param config {@link ToolConfig} in use
* @return an {@link IRunner}
*/
protected IRunner makeRunner(String command, ToolConfig config) {
// TODO sometimes it may be necessary to use a SINGLETON RUNNER (sync on tool config)
// ToolConfig is not used for DefaultRunner but may be used by others
DefaultRunner r = new DefaultRunner();
r.setCommand(command);
r.setWorkingDir(getTempDir());
return r;
}
/**
* constructs the command string to be executed
* @param config {@link ToolConfig} to be used
* @param inputFile absolute path of the input file
* @param outputFile absolute path of the output file
* @return the command string to be executed
*/
protected String prepareCommand(ToolConfig config, String params,
String inputFile, String outputFile, long time) throws Exception {
StringBuffer cmd = new StringBuffer();
cmd.append(config.getTool().getExecutablePath());
cmd.append(" ");
if (params != null && !"".equals(params)) {
cmd.append(params).append(" "); // add additional params BEFORE the others
}
cmd.append(config.getParams()).append(" ");
cmd.append(inputFile);
if (!config.isNoOutFile()) {
cmd.append(" ").append(outputFile);
}
String command = cmd.toString();
command = command.replaceAll("%OUTFILE%",outputFile);
return command;
}
/**
* this GENERATES the file name for the outputfile
* @param config {@link ToolConfig} to be used
* @param time
* @return
*/
protected String makeOutputFilename(ToolConfig config, long time) {
String outputFile = tempDir + "out" + time;
if (config.getOutEnding() != null && !"".equals(config.getOutEnding())) {
outputFile = tempDir + "in" + time+"."+config.getOutEnding();
}
return outputFile;
}
/**
* This might actually DO something for preparing the outfile, but in the default implementation
* it just delegates to the file name generating method
* @param config
* @param time
* @return
*/
protected String prepareOutputFile(ToolConfig config, long time) {
return makeOutputFilename(config, time);
}
/**
* writes the input bytestream to a file
* @param data to be migrated
* @param config {@link ToolConfig} for this migration run
* @param time the identifier for this migration run
* @return absolute path of the file that the bytestream has been written to
* @throws FileNotFoundException if the constructed filename can't be found
* @throws IOException if anything else goes wrong with writing the file
*/
protected String prepareInputFile(byte[] data, ToolConfig config, long time) {
String inputFile = makeInputFilename(config, time);
OutputStream in;
try {
in = new BufferedOutputStream (new FileOutputStream(inputFile));
in.write(data);
in.close();
} catch (FileNotFoundException e) {
log.error(e.getMessage(),e);
} catch (IOException e) {
log.error(e.getMessage(),e);
}
return inputFile;
}
/**
* @param config
* @param time
* @return absolute path for the input file
*/
protected String makeInputFilename(ToolConfig config, long time) {
String inputFile = tempDir + "in" + time;
// special treatment of input file configuration specialties of some migration tools
if (config.getInEnding() != null && !"".equals(config.getInEnding())) {
inputFile = inputFile+"."+config.getInEnding();
}
return inputFile;
}
/**
* creates a temporary working directory
* @param time to be used for naming the directory
* @return the absolute path of the newly created working directory
* @throws Exception if something goes wrong
*/
protected String prepareWorkingDirectory(long time) throws Exception {
File wDir = new File(getTempDir()+time);
if (wDir.mkdir()) {
return wDir.getAbsolutePath();
} else {
return "<failed to create working dir>";
}
}
public void setName(String name) {
this.name = name;
}
}