/*
 * Copyright (C) 2008-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

/*
 * execute tests.
 */

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <errno.h>
#include <stdbool.h>
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <regex.h>
#include <stdarg.h>
#include <fcntl.h>

#define ARRAY_SIZE(t)	(sizeof t / sizeof(t[0]))
#define MIN(x, y)	(x < y ? x : y)

/* temporary files will be called like this: */
#define TMP_FILE_NAME 	"/tmp/faum-gen-results-XXXXXX"
/* maximum number of different experiments */
#define MAX_EXPERIMENTS	1024
/* maximum number of different test runs */
#define MAX_TESTRUNS	1024
/* maximum number of screenshots to pick up */
#define MAX_SCREENSHOTS	20
/* maximum number of lines to pick up for messages */
#define MAX_MATCH_LINES 3

/* template definitions for html */
#include "templates/history_header.def"
#include "templates/history_entry.def"
#include "templates/history_footer.def"
#include "templates/summary_header.def"
#include "templates/summary_entry.def"
#include "templates/summary_footer.def"

/** type: result of one test run */
struct exp_result_t {
	/** timestamp (YYYYMMDD-HHmm). also the directory name of the test 
	 *  run. */
	char timestamp[sizeof("YYYYMMDD-HHmm")];
	/** run time of the test run in seconds) */
	int runtime;
	/** did it succeed? */
	bool success;
	/** name of screenshots (or empty string if none) */
	char screenshot[MAX_SCREENSHOTS][NAME_MAX];
	/** is the screenshot a png file (corresponds to above array )*/
	bool is_png[MAX_SCREENSHOTS];
	/** name of log files */
	char logs[5][NAME_MAX];
	/** important messages */
	char messages[4096];
};

/** history information about one experiment */
struct exp_history_t {
	/** total number of test runs */
	unsigned int num_tests;
	/** total number of successful runs */
	unsigned int num_success;
	/** name of experiment */
	char exp_name[NAME_MAX];
	/* the following are only initialized, if num_tests > 0 */
	/** result of the current test run. */
	struct exp_result_t current_result;
	/** result of the last test run. */
	struct exp_result_t last_result;
	/** did the screenshots change? */
	bool shot_changed;
};

/** generic state */
struct cpssp {
	/** pid of the gnuplot process (0 for no subprocess active). */
	pid_t gnuplot_pid;
	/** file to send commands to gnuplot (write only) */
	FILE *gnuplot_out;
	/** temporary files, which need to be removed at the end */
	char tmp_files[MAX_EXPERIMENTS][sizeof(TMP_FILE_NAME)];
	/** number of tmp_files */
	unsigned int n_tmp_files;
	/** does the tmp file contain a dataset at index 0 matching a 
	 *  successful test run? */
	bool is_tmp_f_succ[MAX_EXPERIMENTS];
};

/* generic info */
static struct cpssp _cpssp = {
	.gnuplot_pid = 0,
	.gnuplot_out = NULL,
	.n_tmp_files = 0,
};


/** read file contents from file in path to a newly allocated buffer and
 *  set len appropriately. If the function returns false, *buf and len
 *  are undefined.
 *
 *  @param path full path to filename
 *  @param buf *buf will be allocated and points to the contents.
 *  @param len will be set to the length of the file.
 *  @return false on error, otherwise true. 
 */
static bool
read_file_to_mem(const char *path, void **_buf, size_t *len)
{
	int fd;
	off_t fs;
	int ret;
	size_t cnt;
	size_t nr;
	char *buf;

	assert(_buf != NULL);
	assert(len != NULL);
	assert(path != NULL);
	
	fd = open(path, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "%s: failed to open %s: %s\n", __FUNCTION__,
			path, strerror(errno));
		return false;
	}

	fs = lseek(fd, 0, SEEK_END);
	if (fs < 0) {
		fprintf(stderr, "%s: seeking to EOF of %s failed: %s\n",
			__FUNCTION__, path, strerror(errno));
		ret = close(fd);
		assert(ret == 0);
		return false;
	}

	*len = fs;
	fs = lseek(fd, 0, SEEK_SET);
	assert(fs >= 0);

	*_buf = malloc(*len);
	if (*_buf == NULL) {
		ret = close(fd);
		assert(ret == 0);
		return false;
	}
	buf = (char *)(*_buf);

	for (cnt = 0; cnt < *len; /* nothing */) {
		nr = read(fd, buf + cnt, *len - cnt);
		if (nr < 0) {
			switch (errno) {
			case EINTR:
				continue;

			case EAGAIN:
			case EBADF:
			case EISDIR:
				assert(false);

			default:
				fprintf(stderr, "Error from reading %s: %s\n",
					path, strerror(errno));
				ret = close(fd);
				assert(ret == 0);
				free(buf);

				return false;
			}
		}
		cnt += nr;
	}

	ret = close(fd);
	assert(ret == 0);

	return true;
}

/** perform a binary diff on two files.
 *  @param p1 path to first file.
 *  @param p2 path to second file.
 *  @return true if the files differ, false if they don't or an error occured.
 */
static bool
binary_diff(const char *p1, const char *p2)
{
	void *c1;
	void *c2;
	size_t len1;
	size_t len2;
	bool ret;
	int r;

	ret = read_file_to_mem(p1, &c1, &len1);
	if (! ret) {
		return false;
	}

	ret = read_file_to_mem(p2, &c2, &len2);
	if (! ret) {
		free(c1);
		return false;

	}

	if (len1 != len2) {
		free(c1);
		free(c2);
		return true;
	}

	r = memcmp(c1, c2, len1);
	free(c1);
	free(c2);

	return r == 0;
}


/** launch gnuplot and create an output pipe to it.
 *  @param pid will get set to the gnuplot's pid on successful invocation.
 *  @return FILE pointer to the output channel, or NULL if launching gnuploat
 *          failed.
 */
static FILE *
launch_gnuplot(pid_t *pid)
{
	int ret;
	int fd[2];
	FILE *f;

	ret = pipe(fd);
	if (ret < 0) {
		fprintf(stderr, "%s: failed to create a pipe: %s\n",
			__FUNCTION__, strerror(errno));
		return NULL;
	}

	*pid = fork();
	switch (*pid) {
	case 0: 
		/* child process */
		ret = close(0);
		assert(ret == 0);
		ret = dup(fd[0]);
		assert(ret == 0);

		ret = close(1);
		assert(ret == 0);
		ret = open("/dev/null", O_WRONLY);
		assert(ret == 1);

		ret = close(2);
		assert(ret == 0);
		ret = open("/dev/null", O_WRONLY);
		assert(ret == 2);

		ret = close(fd[1]);
		assert(ret == 0);

		execlp("gnuplot", "gnuplot", NULL);
		/* only reached on error */
		exit(EXIT_FAILURE);
		break;

	case -1:
		/* error */
		ret = close(fd[0]);
		assert(ret == 0);
		ret = close(fd[1]);
		assert(ret == 0);
		return NULL;
	
	default:
		/* parent */
		ret = close(fd[0]);
		assert(ret == 0);

		f = fdopen(fd[1], "w");
		if (f == NULL) {
			fprintf(stderr, "%s: failed to fdopen: %s\n",
				__FUNCTION__, strerror(errno));
			ret = close(fd[1]);
			assert(ret == 0);
		}

		return f;
	}
}

/** stop gnuplot (if launched) and close the associated pipe.
 *  @param pid pid of gnuplot (or 0 if not launched).
 *  @param f associated pipe.
 */
static void
stop_gnuplot(pid_t pid, FILE *f)
{
	pid_t p1;
	int status;

	if (f != NULL) {
		int ret;
		ret = fprintf(f, "quit;\n");
		/* don't care if writing works or not. */

		ret = fclose(f);
		assert(ret == 0);
	}

	if (pid > 0) {
		do {
			p1 = waitpid(pid, &status, 0);
		} while (! WIFEXITED(status));
	}
}

/** start a subprocess performing the actual conversion.
 *  @param path path in which the screenshots reside.
 *  @param src existing source image (filename w.o. directory).
 *  @param target desired target image (filename w.o. directory).
 */
static void
launch_convert(const char *path, const char *src, const char *target)
{
	char buf[3][PATH_MAX];
	char *argv[4];
	pid_t pid;
	int status;
	int ret;
	
	strncpy(buf[0], "/usr/bin/convert", sizeof(buf[0]));
	ret = snprintf(buf[1], sizeof(buf[1]), "%s/%s", path, src);
	assert(ret < sizeof(buf[1]));
	ret = snprintf(buf[2], sizeof(buf[2]), "%s/%s", path, target);
	assert(ret < sizeof(buf[2]));

	argv[0] = buf[0];
	argv[1] = buf[1];
	argv[2] = buf[2];
	argv[3] = NULL;

	switch (pid = fork()) {
	case -1:
		fprintf(stderr, "%s: %s.\n", argv[0], strerror(errno));
		break;
	case 0:
		/* Child */
		execv(argv[0], argv);
		fprintf(stderr, "%s: %s.\n", argv[0], strerror(errno));
		_exit(1);
	default:
		/* Parent */
		ret = waitpid(pid, &status, 0);
		assert(ret == pid);
		break;
	}
}

/** convert a screenshot from ppm to png using the same basename.
 *  @param path relative base path to screenshot.
 *  @param shot name of the screenshot (w.o. directory part).
 *  @return name of resulting screenshot, static return.
 */
static const char *
convert_screenshot(const char *path, const char *shot)
{
	static char result[NAME_MAX];
	char *dot;
	int sz;

	dot = rindex(shot, '.');

	assert(dot != NULL);
	assert(dot - shot + 1 < sizeof(result));

	sz = snprintf(result, dot - shot + 1, "%s", shot);
	assert(0 < sz);
	strncat(result, ".png", sizeof(result) - 1);

	launch_convert(path, shot, result);

	return result;
}

/** return a result representation of a history entry.
 *  @param entry history entry.
 *  @return string representation for html.
 */
static const char *
str_make_result(const struct exp_history_t *entry)
{
	if (entry->current_result.success) {
		return "<td><font color=\"lime\">success</font></td>";
	}

	/* not successful */
	if (entry->num_success == 0) {
		return "<td><font color=\"red\">failed</font></td>";
	}

	/* earlier, this test succeeded at least once -> warning */
	return "<td bgcolor=\"orange\">warning</td>";
}

/** return a color code string for a history entry.
 *  @param entry history entry.
 *  @return string representation for html.
 */
static const char *
str_make_color_code(const struct exp_history_t *entry)
{
	int delta;
	int rel_delta;

	if (entry->num_tests < 2) {
		/* new test, no color */
		return "<td></td>";
	}

	/* highest priority: success -> failed and failed -> success. */
	if (entry->current_result.success != entry->last_result.success) {
		if (entry->current_result.success) {
			/* failed -> success */
			return "<td bgcolor=\"lime\"></td>";
		}
		/* success -> failed */
		return "<td bgcolor=\"red\"></td>";
	}

	/* check if screenshots changed. */
	if (entry->shot_changed) {
		return "<td bgcolor=\"aqua\"></td>";
	}

	/* can we do a check on runtime changes in the first place? */
	if (   (entry->current_result.runtime <= 0) 
	    || (entry->last_result.runtime <= 0)) {

	    return "<td></td>";
	}

	/* FIXME eventually: the old version seems to only care for 
	 *       successful experiments. enable the next bit to make
	 *       this behave the same way.
	 */
#if 0 /* see above */
	if (! (entry->current_result.success && entry->last_result.success)) {
		return "<td></td>";
	}
#endif
	/* check, if runtime changed? */
	delta = entry->current_result.runtime - entry->last_result.runtime;
	rel_delta = (delta * 100) / entry->current_result.runtime;

	if (rel_delta >= 10) {
		/* at least 10% worse than last run */
		return "<td bgcolor=\"maroon\"></td>";
	}

	if (rel_delta <= -10) {
		/* at least 10% faster than last run */
		return "<td bgcolor=\"green\"></td>";
	}

	return "<td></td>";
}

/** format a YYYYMMDD-HHMM to a human readable date.
 *  @param timeformat time in aforementioned format.
 *  @return human readable format.
 */
static const char *
str_make_datetime(const char *timeformat)
{
	static char buf[5][sizeof("dd.mm.yyyy, hh:mm")];
	static unsigned int bi = 0;

	int ret;
	int year;
	int month;
	int day;
	int hour;
	int minute;

	if (bi >= ARRAY_SIZE(buf)) {
		bi = 0;
	}

	assert(strlen(timeformat) == strlen("yyyymmdd-hhmm"));
	ret = sscanf(timeformat, "%04d%02d%02d-%02d%02d", &year, &month, &day, 
		&hour, &minute);
	assert(ret == 5);
	ret = snprintf(buf[bi], sizeof(buf[bi]), "%02d.%02d.%04d, %02d:%02d",
		day, month, year, hour, minute);
	assert(ret < sizeof(buf[bi]));

	bi++;
	return buf[bi - 1];
}

/** return a string representation of the runtime.
 *  @param runtime runtime in seconds.
 *  @return string representation.
 */
static const char *
str_runtime(int runtime)
{
	static char ret[20];

	if (runtime < 0) {
		return "n.a.";
	}

	snprintf(ret, sizeof(ret), "%dh %dm %ds", runtime / 3600, 
		(runtime / 60) % 60, runtime % 60);

	return ret;
}

/** make a link from a path with target as link name.
 *  Must not be called more than 5 times concurrently, otherwise
 *  a previous result may get overwritten.
 *  @param link_name name of the link, or '\0' to return the empty string.
 *  @param fmt format sting of the link, followed by needed format params.
 *         fmt should result in the path component.
 *  @return html link to file, static return.
 */
static const char *
str_make_link(const char *link_name, const char *fmt, ...)
{
	static char ret[5][PATH_MAX + 1024];
	static char buf[PATH_MAX];
	static unsigned int dim = 0;
	va_list args;
	int sz;

	if (dim > 4) {
		dim = 0;
	}

	if (*link_name == '\0') {
		ret[dim][0] = '\0';
		dim++;
		return ret[dim - 1];
	}

	va_start(args, fmt);
	sz = vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);
	assert(sz < sizeof(buf));

	sz = snprintf(ret[dim], sizeof(ret[dim]), 
		"<a href=\"%s\">%s</a>", buf, link_name);
	assert(sz < sizeof(ret[dim]));
	dim++;
	return ret[dim - 1];
}


/** make links for an array of strings.
 *  @param prefix relative path prefix to prepend to each string.
 *  @param t_arr array of strings, which represent both the file target
 *         and the name of the link. empty strings will be skipped.
 *  @param t_arr_sz array size of t_arr.
 *  @param t_arr_elem_sz element size of array element.
 *  @param dest destination buffer.
 *  @param sz size of destination buffer.
 *  @return destination buffer dest.
 */
static const char *
str_make_file_links(
	const char *prefix,
	const char *t_arr,
	size_t t_arr_sz,
	size_t t_arr_elem_sz,
	char *dest,
	int sz
)
{
	char *d = dest;
	bool have_entry = false;
	int r;
	int i;

	for (i = 0; i < t_arr_sz; i++, t_arr += t_arr_elem_sz) {
		if (*t_arr == '\0') {
			continue;
		}

		if (have_entry) {
			r = snprintf(d, sz, "\n<br>\n");
			sz -= r;
			d += r;
			assert(sz > 0);
		}

		have_entry = true;
		r = snprintf(d, sz, "<a href=\"%s/%s\">%s</a>", prefix,
			t_arr, t_arr);
		sz -= r;
		d += r;
		assert(sz > 0);
	}

	if (! have_entry) {
		dest[0] = '\0';
	}

	return dest;
}

/** return a string representation of success/failed value.
 *  @param success did the test run succeed?
 *  @return string representation.
 */
static const char *
str_success(bool success)
{
	if (success) {
		return "<font color=\"lime\">success</font>";
	}

	return "<font color=\"red\">failed</font>";
}

/** generate html file for history.
 *  @param path path to html output file.
 *  @param experiment name of executed experiment.
 *  @param num_results number of results.
 *  @param results array containing the results.
 */
static void
html_gen_history(
	const char *path,
	const char *experiment,
	unsigned int num_results, 
	const struct exp_result_t *results
)
{
	FILE *f;
	int ret;
	unsigned int i;
	char buf1[PATH_MAX + 1024];
	char buf2[PATH_MAX + 1024];

	f = fopen(path, "w");
	if (f == NULL) {
		fprintf(stderr, "Could not open %s for writing: %s\n",
			path, strerror(errno));
		return;
	}

	ret = fprintf(f, FORMAT_history_header, experiment, experiment);
	if (ret < 0) {
		fprintf(stderr, "Could not write to %s: %s\n", path,
			strerror(errno));
		ret = fclose(f);
		assert(ret == 0);
		return;
	}

	for (i = 0; i < num_results; i++, results++) {
		ret = fprintf(f, FORMAT_history_entry, 
			str_make_datetime(results->timestamp),
			str_runtime(results->runtime),
			str_success(results->success),
			results->messages,
			str_make_file_links(
				results->timestamp, 
				results->logs[0],
				ARRAY_SIZE(results->logs),
				sizeof(results->logs[0]),
				buf1, 
				sizeof(buf1)),
			str_make_file_links(
				results->timestamp,
				results->screenshot[0],
				ARRAY_SIZE(results->screenshot),
				sizeof(results->screenshot[0]),
				buf2,
				sizeof(buf2))
			);
		if (ret < 0) {
			fprintf(stderr, "Could not write to %s: %s\n",
				path, strerror(errno));
			ret = fclose(f);
			assert(ret == 0);
			return;
		}
	}

	ret = fprintf(f, FORMAT_history_footer);
	if (ret < 0) {
		fprintf(stderr, "Could not write to %s: %s\n",
			path, strerror(errno));
	}

	ret = fclose(f);
	assert(ret == 0);
}


/** generate one entry of the summary html file.
 *  @param f write to this file.
 *  @param entry generate html for this entry.
 *  @return result of fprintf call, < 0 on error.
 */
static int
html_gen_summary_entry(
	FILE *f, 
	const struct exp_history_t *entry
)
{
	int ret;
	const char *messages;
	const char *timestamp;
	int runtime = -1;
	char prefix[PATH_MAX];
	char buf1[PATH_MAX + 1024];
	char buf2[PATH_MAX + 1024];

	assert(entry->num_tests > 0);
	runtime = entry->current_result.runtime;
	messages = entry->current_result.messages;
	timestamp = entry->current_result.timestamp;

	ret = snprintf(prefix, sizeof(prefix), "%s/%s", entry->exp_name,
			entry->current_result.timestamp);
	assert(ret < sizeof(prefix));

	ret = fprintf(f, FORMAT_summary_entry,
		str_make_link(entry->exp_name, "%s/history.html",
				entry->exp_name),
		str_make_color_code(entry),
		str_make_datetime(timestamp),
		str_runtime(runtime),
		str_make_result(entry), 
		messages,
		str_make_file_links(
			prefix,
			entry->current_result.logs[0],
			ARRAY_SIZE(entry->current_result.logs),
			sizeof(entry->current_result.logs[0]),
			buf1,
			sizeof(buf1)),
		str_make_file_links(
			prefix,
			entry->current_result.screenshot[0],
			ARRAY_SIZE(entry->current_result.screenshot),
			sizeof(entry->current_result.screenshot[0]),
			buf2,
			sizeof(buf2))
		);

	return ret;
}

/** generate html file for summary.
 *  @param path path to html output file.
 *  @param num_hist number of history entries.
 *  @param history array of history entries.
 */
static void
html_gen_summary(
	const char *path,
	unsigned int num_hist,
	const struct exp_history_t *history
)
{
	FILE *f;
	int ret;
	unsigned int i;

	f = fopen(path, "w");
	if (f == NULL) {
		fprintf(stderr, "Could not open %s for writing: %s\n",
			path, strerror(errno));
		return;
	}

	ret = fprintf(f, FORMAT_summary_header);
	if (ret < 0) {
		fprintf(stderr, "Could not write to %s: %s\n", path,
			strerror(errno));
		ret = fclose(f);
		assert(ret == 0);
		return;
	}

	for (i = 0; i < num_hist; i++, history++) {
		ret = html_gen_summary_entry(f, history);
		if (ret < 0) {
			fprintf(stderr, "Could not write to %s: %s\n",
				path, strerror(errno));
			ret = fclose(f);
			assert(ret == 0);
			return;
		}
	}

	ret = fprintf(f, FORMAT_summary_footer);
	if (ret < 0) {
		fprintf(stderr, "Could not write to %s: %s\n",
			path, strerror(errno));
	}

	ret = fclose(f);
	assert(ret == 0);

}

/** write all tests to the FILE output filtering on successful/unsuccessful
 *  tests.
 *  @param num_results total number of results.
 *  @param results array of results.
 *  @param output write to this file.
 *  @param success filter on success/non-success.
 *  @return number of entries written.
 */
static unsigned int
plot_write_tests(
	unsigned int num_results, 
	const struct exp_result_t *results,
	FILE *output,
	bool success
)
{
	unsigned int ret = 0;
	unsigned int i;

	for (i = 0; i < num_results; i++) {
		if (results[i].success != success) {
			continue;
		}
	
		/* skip tests with less than 5 seconds of runtime. */
		if (results[i].runtime >= 5) {
			fprintf(output, "%s %f\n", results[i].timestamp,
				results[i].runtime / 60.0);
			ret++;
		}
	}

	return ret;
}

/** actually invoke gnuplot.
 *  @param out file name of destination file.
 *  @param title title of plot
 *  @param data full path to file containing plot data.
 *  @param have_data1 have a first data set (draw color: green) in data.
 *  @param have_data2 have a second data set (draw color: red) in data.
 */
static void
plot_invoke_gnuplot(
	const char *out,
	const char *title,
	const char *data,
	bool have_data1,
	bool have_data2
)
{
	FILE *gnu_plot;
	int ret;

	if ((! have_data1) && (! have_data2)) {
		/* nothing to plot. */
		return;
	}

	gnu_plot = _cpssp.gnuplot_out;

	/* generic info: title, date format, etc. */
	ret = fprintf(gnu_plot, "set title \"Experiment %s\";\n", title);
	ret = fprintf(gnu_plot, "set xlabel \"date\";\n");
	ret = fprintf(gnu_plot, "set ylabel \"runtime (minutes)\";\n");
	ret = fprintf(gnu_plot, "set xdata time;\n");
	ret = fprintf(gnu_plot, "set timefmt \"%%Y%%m%%d-%%H%%M\";\n");
	ret = fprintf(gnu_plot, "set terminal png;\n");
	ret = fprintf(gnu_plot, "set output \"%s\";\n", out);

	/* issue the actual plot command */
	if (have_data1 && have_data2) {
		/* both datasets available */
		ret = fprintf(gnu_plot, "plot \"%s\" index 0 using 1:2 with "
			"lines ti \"success\" lt 2, \"%s\" index 1 using 1:2 "
			"with lines ti \"failed\" lt 1;\n", data, data);
	} else if (have_data1) {
		ret = fprintf(gnu_plot, "plot \"%s\" index 0 using 1:2 with "
			"lines ti \"success\" lt 2;\n", data);
	} else {
		assert(have_data2);
		/* only dataset 2 at index 0 */
		ret = fprintf(gnu_plot, "plot \"%s\" index 0 using 1:2 with "
			"lines ti \"failed\" lt 1;\n", data);
	}
	ret = fflush(gnu_plot);
	assert(ret == 0);
}

/** add a temporary file that was created to the cleanup list.
 *  @param cpssp cpssp instance.
 *  @param tmp full path to temp file.
 *  @param is_succ does the tmp file match a successful test run?
 */
static void
add_tmp_file(struct cpssp *cpssp, const char *tmp, bool is_succ)
{
	assert(strlen(tmp) < sizeof(cpssp->tmp_files[0]));
	assert(cpssp->n_tmp_files < ARRAY_SIZE(cpssp->tmp_files));

	strcpy(cpssp->tmp_files[cpssp->n_tmp_files], tmp);
	cpssp->is_tmp_f_succ[cpssp->n_tmp_files] = is_succ;
	cpssp->n_tmp_files++;
}

/** plot a history file.
 *  @param path path to plot output file.
 *  @param experiment name of executed experiment.
 *  @param num_results number of results.
 *  @param results array containing the results.
 */
static void
plot_history(
	const char *path,
	const char *experiment,
	unsigned int num_results,
	const struct exp_result_t *results
)
{
	char template_name[] = TMP_FILE_NAME;
	int fd;
	FILE *tmp;
	int ret;
	unsigned int num;
	bool have_successful = false;
	bool have_failed = false;

	fd = mkstemp(template_name);
	if (fd < 0) {
		fprintf(stderr, "Could not create temporary file %s: %s\n",
			template_name, strerror(errno));
		return;
	}

	tmp = fdopen(fd, "w");
	if (tmp == NULL) {
		fprintf(stderr, "fdopen failed: %s\n",
			strerror(errno));
		return;
	}

	/* write all successful tests */
	num = plot_write_tests(num_results, results, tmp, true);
	if (num > 0) {
		have_successful = true;
		fprintf(tmp, "\n\n");
	}

	num = plot_write_tests(num_results, results, tmp, false);
	if (num > 0) {
		have_failed = true;
	}

	if (ferror(tmp)) {
		fprintf(stderr, "Error writing to %s: %s\n", template_name,
			strerror(errno));
		clearerr(tmp);
		fclose(tmp);
		return;
	}

	ret = fclose(tmp);
	if (ret != 0) {
		fprintf(stderr, "Close of file %s failed: %s\n",
			template_name, strerror(errno));
		return;
	}

	plot_invoke_gnuplot(path, experiment, template_name, have_successful,
			have_failed);

	add_tmp_file(&_cpssp, template_name, have_successful);
}

/** determine messages from log.faum-node-pc 
 *  @param path path to log.faum-node-pc
 *  @param dest store messages there.
 *  @param sz size of dest buffer.
 */
static void
determine_messages(const char *path, char *dest, size_t sz)
{
	FILE *f;
	int ret;
	unsigned int lines = 0;
	regex_t preg;
	char buf[2048];
	char match_buf[MAX_MATCH_LINES][2048];
	unsigned int i;
	unsigned int rel_index;
	char *s;
	const char *regex = 
		"(WARNING:)|(FATAL:)|(\\.c:)|(Exception)";

	*dest = '\0';

	f = fopen(path, "r");
	if (f == NULL) {
		if (errno == ENOENT) {
			/* file does not exist, skip */
			*dest = '\0';
			return;
		}

		fprintf(stderr, "Could not open file %s: %s\n", path,
			strerror(errno));
		return;
	}

	/* set up regular expressions */
	ret = regcomp(&preg, regex, REG_EXTENDED | REG_NOSUB);
	assert(ret == 0);

	/* advance to last 16K as heuristic */
	ret = fseek(f, -16384, SEEK_END);
	/* This will either work, or not, and leave us then at pos 0. (which
	 *  is equally correct). */
	
	while (! feof(f)) {
		s = fgets(buf, sizeof(buf), f);
		if (s == NULL) {
			break;
		}
		
		ret = regexec(&preg, buf, 0, NULL, 0);
		if (ret == REG_NOMATCH) {
			continue;
		}

		/* match... copy to match_buf ringbuffer */
		strncpy(match_buf[lines % MAX_MATCH_LINES], buf, 
			sizeof(match_buf[0]));
		lines++;
	}


	regfree(&preg);
	ret = fclose(f);
	assert(ret >= 0);

	/* copy to dest from ringbuffer */
	for (i = 0; i < MIN(MAX_MATCH_LINES, lines); i++) {
		if (lines <= MAX_MATCH_LINES) {
			rel_index = i;
		} else {
			rel_index = (lines + i) % MAX_MATCH_LINES;
		}

		strncpy(dest, match_buf[rel_index], sz);
		dest[sz - 1] = '\0';
		sz -= strlen(dest);
		dest += strlen(dest);

		if (sz < 6) {
			return;
		}

		if (i < MAX_MATCH_LINES - 1) {
			ret = snprintf(dest, sz, "<br>");
			assert(ret < sz);
			dest += ret;
			sz -= ret;
		}
	}
}

/** callback for qsort, to sort on the timestamp of a result type.
 *  @param _s1 first result entry (exp_result_t)
 *  @param _s2 second result entry (exp_result_t)
 *  @return numeric comparison result.
 */
static int
compare_timestamp(const void *_r1, const void *_r2)
{
	const struct exp_result_t *r1 = (const struct exp_result_t*)_r1;
	const struct exp_result_t *r2 = (const struct exp_result_t*)_r2;

	return strncmp(r2->timestamp, r1->timestamp, sizeof(r2->timestamp));
}

/** callback for qsort, to sort the history by experiment name.
 *  @param _h1 first history entry (exp_history_t)
 *  @parma _h2 second history entry (exp_history_t)
 *  @return numeric comparison result.
 */
static int
compare_experiment_name(const void *_h1, const void *_h2)
{
	const struct exp_history_t *h1 = (const struct exp_history_t*)_h1;
	const struct exp_history_t *h2 = (const struct exp_history_t*)_h2;

	return strncmp(h1->exp_name, h2->exp_name, sizeof(h1->exp_name));
}

/* TODO: after all setups have been converted, much logic can be removed
 *       from this function. The only part that shall remain is to walk
 *       through the directory, and pick up the last png file.
 */
/** check if filename refers to a screenshot, and if so set the 
 *  corresponding members of dest.
 *  @param prefix path prefix to the top directory of the test results.
 *  @param experiment directory of the experiment.
 *  @param test_run directory of the test run.
 *  @param filename filename in the test run directory.
 *  @param dest members in this structure will get updated if appropriate.
 */
static void
set_screenshot(
	const char *prefix,
	const char *experiment,
	const char *test_run,
	const char *filename,
	struct exp_result_t *dest
)
{
	int i;
	char num[4];
	const char *extension;
	bool ext_valid = false;

	if (strncmp(filename, "screenshot-", strlen("screenshot-")) != 0) {
		return;
	}

	if (strlen(filename) != strlen("screenshot-###.png")) {
		return;
	}

	extension = filename + strlen("screenshot-###.");
	if (strcasecmp(extension, "png") == 0) {
		ext_valid = true;
	}
	if (strcasecmp(extension, "ppm") == 0) {
		ext_valid = true;
	}	
	if (! ext_valid) {
		return;
	}

	strncpy(num, filename + strlen("screenshot-"), sizeof(num));

	i = atoi(num);
	assert(i < ARRAY_SIZE(dest->screenshot));
	assert(i < ARRAY_SIZE(dest->is_png));

	if (dest->is_png[i]) {
		/* png file already registered, do nothing. */
		return;
	}

	strncpy(dest->screenshot[i], filename, sizeof(dest->screenshot[i]));

	if (strncmp(filename + strlen("screenshot-###."), "png", 
		strlen("png")) == 0) {

		dest->is_png[i] = true;
	} else {
		dest->is_png[i] = false;
	}
}


/** check, if filename refers to a log file, and update dest->logs if
 *  appropriate.
 *  @param prefix path prefix to the top directory of the test results.
 *  @param experiment directory of the experiment.
 *  @param test_run directory of the test run.
 *  @param filename filename in the test run directory.
 *  @param dest members in this structure will get updated if appropriate.
 */
static void
set_log(
	const char *prefix,
	const char *experiment,
	const char *test_run,
	const char *filename,
	struct exp_result_t *dest
)
{
	size_t i;
	bool found = false;

	if (strlen(filename) < strlen("log.")) {
		return;
	}

	if (strncmp(filename, "log.", strlen("log.")) != 0) {
		return;
	}

	for (i = 0; i < ARRAY_SIZE(dest->logs); i++) {
		if (dest->logs[i][0] == '\0') {
			found = true;
			break;
		}
	}

	if (! found) {
		fprintf(stderr, "%s: WARNING discarding log %s/%s/%s/%s "
			"(no space in array left).\n", __FUNCTION__,
			prefix, experiment, test_run, filename);
		return;
	}

	strncpy(dest->logs[i], filename, sizeof(dest->logs[i]));
}

/** check, if filename is a test success stamp and set dest->success.
 *  @param prefix path prefix to the top directory of the test results.
 *  @param experiment directory of the experiment.
 *  @param test_run directory of the test run.
 *  @param filename filename in the test run directory.
 *  @param dest members in this structure will get updated if appropriate.
 */
static void
set_success(
	const char *prefix,
	const char *experiment,
	const char *test_run,
	const char *filename,
	struct exp_result_t *dest
)
{
	if (strcmp(filename, "test.success") == 0) {
		dest->success = true;
	}
}

/** check, if filename refers to a runtime entry and set runtime.
 *  @param prefix path prefix to the top directory of the test results.
 *  @param experiment directory of the experiment.
 *  @param test_run directory of the test run.
 *  @param filename filename in the test run directory.
 *  @param dest members in this structure will get updated if appropriate.
 */
static void
set_runtime(
	const char *prefix,
	const char *experiment,
	const char *test_run,
	const char *filename,
	struct exp_result_t *dest
)
{
	char s[PATH_MAX];
	char buffer[15];
	int sz;
	FILE *f;
	int ret;
	char *t;

	if (strcmp(filename, "runtime") != 0) {
		return;
	}


	/* check for existance of "%path%/runtime" */
	sz = snprintf(s, sizeof(s), "%s/%s/%s/runtime", prefix, 
			experiment, test_run);
	assert(sz < sizeof(s));

	f = fopen(s, "r");
	if (f == NULL) {
		fprintf(stderr, "%s: WARNING, cannot read runtime for %s.\n",
			__FUNCTION__, s);
		return;
	}

	t = fgets(buffer, sizeof(buffer), f);
	assert(t != NULL);
	ret = fclose(f);
	assert(ret == 0);

	dest->runtime = atoi(buffer);
}

/** check, if filename refers to log.faum-node-pc and if so extract 
 *  messages and set dest->messages to them.
 *  @param prefix path prefix to the top directory of the test results.
 *  @param experiment directory of the experiment.
 *  @param test_run directory of the test run.
 *  @param filename filename in the test run directory.
 *  @param dest members in this structure will get updated if appropriate.
 */
static void
set_messages(
	const char *prefix,
	const char *experiment,
	const char *test_run,
	const char *filename,
	struct exp_result_t *dest
)
{
	char path[PATH_MAX];
	int sz;

	if (strcmp(filename, "log.faum-node-pc") != 0) {
		return;
	}

	sz = snprintf(path, sizeof(path), "%s/%s/%s/%s", prefix, experiment,
			test_run, filename);
	assert(sz < sizeof(path));

	determine_messages(path, dest->messages, sizeof(dest->messages));
}


/** convert any .ppm screenshots to .png updating dest->screenshot.
 *  @param prefix path prefix to the top directory of the test results.
 *  @param experiment directory of the experiment.
 *  @param test_run directory of the test run.
 *  @param dest members in this structure will get updated if appropriate.
 */
static void
fixup_screenshots(
	const char *prefix,
	const char *experiment,
	const char *test_run,
	struct exp_result_t *dest
)
{
	int i;
	int sz;
	char buf[PATH_MAX];
	const char *s;

	assert(ARRAY_SIZE(dest->is_png) == ARRAY_SIZE(dest->screenshot));

	for (i = 0; i < ARRAY_SIZE(dest->is_png); i++) {
		if (dest->is_png[i]) {
			continue;
		}

		if (dest->screenshot[i][0] == '\0') {
			continue;
		}

		sz = snprintf(buf, sizeof(buf), "%s/%s/%s", prefix, 
			experiment, test_run);
		assert(sz < sizeof(buf));

		s = convert_screenshot(buf, dest->screenshot[i]);
		assert(s != NULL);
		strncpy(dest->screenshot[i], s, sizeof(dest->screenshot[i]));
	}
}

/** initialize a result structure with sane values.
 *  @param r structure to initialize.
 */
static void
exp_result_t_init(struct exp_result_t *r)
{
	size_t i;
	r->timestamp[0] = '\0';
	r->runtime = -1;
	r->success = false;
	r->messages[0] = '\0';

	for (i = 0; i < ARRAY_SIZE(r->screenshot); i++) {
		r->screenshot[i][0] = '\0';
	}

	for (i = 0; i < ARRAY_SIZE(r->is_png); i++) {
		r->is_png[i] = false;
	}

	for (i = 0; i < ARRAY_SIZE(r->logs); i++) {
		r->logs[i][0] = '\0';
	}
}

/** is prefix/entry->d_name a regular file?
 *  @param prefix: full path prefix to entry
 *  @param entry: corresponding directory entry
 *  @return true if it is a regular file, false otherwise
 */
static bool
is_regular_file(const char *prefix, const struct dirent *entry)
{
	int ret;
	struct stat sb;
	char buf[PATH_MAX];

	/* not every FS supports entry->d_type */
	if (entry->d_type != DT_UNKNOWN) {
		return (entry->d_type & DT_REG) == DT_REG;
	}

	/* need to stat */
	ret = snprintf(buf, sizeof(buf), "%s/%s", prefix, entry->d_name);
	assert(ret < sizeof(buf));

	ret = stat(buf, &sb);
	if (ret < 0) {
		fprintf(stderr, "error for %s: %s\n", buf, strerror(errno));
		return false;
	}

	return S_ISREG(sb.st_mode);
}

/** evaluate one test run.
 *  @param prefix relative path prefix so that "prefix/experiment/timestamp/"
 *         is the directory of the test run to evaluate.
 *  @param experiment directory name of the experiment.
 *  @param timestamp timestamp/directory name of test run.
 *  @param ret fill in this entry.
 *  @return filled ret or NULL if invalid.
 */
static struct exp_result_t *
eval_testrun(
	const char *prefix,
	const char *experiment,
	const char *timestamp,
	struct exp_result_t *ret
)
{
	size_t i;
	int sz;
	char c;
	DIR *d;
	struct dirent *entry;
	char path[PATH_MAX];

	/* should have a timestamp component */
	sz = strlen(timestamp);
	assert(sz == sizeof(ret->timestamp) - 1);

	for (i = 0; i < strlen(timestamp); i++) {
		c = timestamp[i];
		/* make sure it is a timestamp */
		if (! (((c >= '0') && (c <= '9')) || c == '-')) {
			return NULL;
		}
	}

	exp_result_t_init(ret);
	strncpy(ret->timestamp, timestamp, sizeof(ret->timestamp));

	/* iterate over directory entries, setting appropriate members */
	sz = snprintf(path, sizeof(path), "%s/%s/%s", prefix, experiment, 
		timestamp);
	assert(sz < sizeof(path));

	d = opendir(path);
	if (d == NULL) {
		fprintf(stderr, "%s: Could not open directory %s: %s\n",
			__FUNCTION__, path, strerror(errno));
		fprintf(stderr, "prefix=%s, experiment=%s, timestamp=%s\n",
			prefix, experiment, timestamp);
		return NULL;
	}

	errno = 0;
	for (entry = readdir(d); entry != NULL; entry = readdir(d)) {
		if (! is_regular_file(path, entry)) {
			continue;
		}

		set_screenshot(prefix, experiment, timestamp, entry->d_name, 
				ret);
		set_log(prefix, experiment, timestamp, entry->d_name, ret);
		set_success(prefix, experiment, timestamp, entry->d_name, ret);
		set_runtime(prefix, experiment, timestamp, entry->d_name, ret);
		set_messages(prefix, experiment, timestamp, entry->d_name, 
			ret);
		errno = 0;
	};

	if (errno != 0) {
		fprintf(stderr, "Error reading directory %s: %s\n", path,
			strerror(errno));
	}

	sz = closedir(d);
	assert(sz >= 0);

	/* finally fix up screenshots */
	fixup_screenshots(prefix, experiment, timestamp, ret);

	return ret;
}

/** is prefix/entry->d_name a directory?
 *  @param prefix: full path prefix to entry
 *  @param entry: corresponding directory entry
 *  @return true if it is a directory, false otherwise
 */
static bool
is_directory(const char *prefix, const struct dirent *entry)
{
	int ret;
	struct stat sb;
	char buf[PATH_MAX];

	/* not every FS supports entry->d_type */
	if (entry->d_type != DT_UNKNOWN) {
		return (entry->d_type & DT_DIR) == DT_DIR;
	}

	/* need to stat */
	ret = snprintf(buf, sizeof(buf), "%s/%s", prefix, entry->d_name);
	assert(ret < sizeof(buf));

	ret = stat(buf, &sb);
	if (ret < 0) {
		fprintf(stderr, "error for %s: %s\n", buf, strerror(errno));
		return false;
	}

	return S_ISDIR(sb.st_mode);
}

/** heuristic to check, if the dirent entry is a test run directory.
 *  @param prefix prefix of directory entry.
 *  @param entry dirent entry to check.
 *  @return true, if entry is a test run directory, false otherwise.
 */
static bool
is_test_run_dir(const char *prefix, const struct dirent *entry)
{
	size_t len;
	int i;
	const char *c;

	if (! is_directory(prefix, entry)){
		return false;
	}

	/* check if name matches "yyyymmdd-hhmm" */
	len = strlen(entry->d_name);
	if (len != strlen("yyyymmdd-hhmm")) {
		return false;
	}

	c = entry->d_name;
	for (i = 0; i < strlen("yyyymmdd"); i++, c++) {
		if ((*c < '0') || (*c > '9')) {
			/* invalid character */
			return false;
		}
	}

	if (*c != '-') {
		/* missing hyphen */
		return false;
	}
	c++;

	for (i = 0; i < strlen("hhmm"); i++, c++) {
		if ((*c < '0') || (*c > '9')) {
			/* invalid character */
			return false;
		}
	}

	return true;
}

/** check if the screenshots have changed an update entry->shot_changed.
 *  @param prefix prefix directory of experiments.
 *  @param exp_name directory name of experiment.
 *  @param entry update this entry. current_result and last_result members
 *         must be valid.
 */
static void
check_shot_changed(
	const char *prefix, 
	const char *exp_name, 
	struct exp_history_t *entry
)
{
	unsigned int i;
	char p1[PATH_MAX];
	char p2[PATH_MAX];
	int ret;

	for (i = 0; i < ARRAY_SIZE(entry->current_result.screenshot); i++) {
		if (   (entry->current_result.screenshot[i][0] == '\0') 
		    && (entry->last_result.screenshot[i][0] == '\0')) {
			continue;
		}

		if (entry->current_result.screenshot[i][0] == '\0') {
			entry->shot_changed = true;
			return;
		}

		if (entry->last_result.screenshot[i][0] == '\0') {
			entry->shot_changed = true;
			return;
		}


		ret = snprintf(p1, sizeof(p1), "%s/%s/%s/%s", prefix,
				exp_name, entry->current_result.timestamp,
				entry->current_result.screenshot[i]);
		assert(ret < sizeof(p1));

		ret = snprintf(p2, sizeof(p2), "%s/%s/%s/%s", prefix,
				exp_name, entry->last_result.timestamp,
				entry->last_result.screenshot[i]);
		assert(ret < sizeof(p2));

		/* both screenshots present, compare these */
		entry->shot_changed = binary_diff(p1, p2);
		if (entry->shot_changed) {
			/* there was a change. bail out */
			return;			
		}
	}
}

/** generate history information and html for one experiment
 *  @param path path to directory containing experiment results.
 *  @param exp_name name of experiment.
 *  @param result store information in this entry.
 *  @return history entry of experiments (history param) or NULL on error.
 */
static struct exp_history_t *
gen_history(
	const char *prefix, 
	const char *exp_name,
	struct exp_history_t *result
)
{
	DIR *d;
	struct dirent *entry;
	char run_dir[PATH_MAX];
	char test_run_dir[PATH_MAX];
	int sz;
	static struct exp_result_t history[MAX_TESTRUNS];
	struct exp_result_t *det;

	result->num_tests = 0;
	result->num_success = 0;
	result->shot_changed = false;

	sz = snprintf(run_dir, sizeof(run_dir), "%s/%s", prefix, exp_name);
	assert(sz < sizeof(run_dir));

	d = opendir(run_dir);
	if (d == NULL) {
		fprintf(stderr, "%s: Could not open directory %s: %s\n",
			__FUNCTION__, run_dir, strerror(errno));
			return NULL;
	}

	/* evaluate all test runs, and store these in history */
	for (entry = readdir(d); entry != NULL; entry = readdir(d)) {
		if (! is_test_run_dir(run_dir, entry)) {
			continue;
		}

		strncpy(test_run_dir, entry->d_name, sizeof(test_run_dir));
		det = eval_testrun(prefix, exp_name, test_run_dir,
				&history[result->num_tests]);

		if (det == NULL) {
			fprintf(stderr, "%s/%s: shrug\n", run_dir, entry->d_name);
			continue;
		}

		if (history[result->num_tests].success) {
			result->num_success++;
		}
		
		result->num_tests++;
		assert(result->num_tests < ARRAY_SIZE(history));
	};
	sz = closedir(d);
	assert(sz >= 0);


	strncpy(result->exp_name, exp_name, sizeof(result->exp_name));
	
	if (result->num_tests == 0) {
		/* no test, do not list */
		return NULL;
	}

	/* sort history by timestamp */
	qsort(history, result->num_tests, sizeof(history[0]), 
		compare_timestamp);

	result->current_result = history[0];
	if (result->num_tests > 1) {
		result->last_result = history[1];
	}

	/* generate html history */
	sz = snprintf(test_run_dir, sizeof(test_run_dir), 
			"%s/%s/history.html",
			prefix, exp_name);
	assert(sz < sizeof(test_run_dir));

	html_gen_history(test_run_dir, exp_name, result->num_tests, history);

	/* plot a history graph */
	if (result->num_tests > 1) {
		sz = snprintf(test_run_dir, 
				sizeof(test_run_dir), 
				"%s/%s/history.png",
				prefix, exp_name);
		assert(sz < sizeof(test_run_dir));
		plot_history(test_run_dir, exp_name, result->num_tests, history);
	}

	/* check if screenshots changed */
	check_shot_changed(prefix, exp_name, result);

	return result;
}

/** is this the directory of an experiment?
 *  @param prefix directory prefix to the dirent entry.
 *  @param entry dirent entry to check.
 *  @return true, if it corresponds to an experiment result directory,
 *          false otherwise.
 */
static bool
is_experiment_dir(const char *prefix, const struct dirent *entry)
{
	const char *at_char;

	if (! is_directory(prefix, entry)) {
		return false;
	}

	/* heuristic: experiment directories always contain one '@' */
	at_char = index(entry->d_name, '@');
	if (at_char == NULL) {
		return false;
	}
	
	return true;
}

/** generate summary
 *  @param results path name of results directory
 */
static void
generate_summary(const char *results)
{
	DIR *d;
	struct dirent *entry;
	char exp_dir[PATH_MAX];
	int ret;
	static struct exp_history_t summary[MAX_EXPERIMENTS];
	unsigned int num = 0;
	struct exp_history_t *res;

	d = opendir(results);
	if (d == NULL) {
		fprintf(stderr, "Could not open results directory %s: %s\n",
			results, strerror(errno));
		return;	
	}

	for (entry = readdir(d); entry != NULL; entry = readdir(d)) {
		if (! is_experiment_dir(results, entry)) {
			continue;
		}

		ret = snprintf(exp_dir, sizeof(exp_dir), "%s", entry->d_name);
		assert(ret < sizeof(exp_dir));
		res = gen_history(results, exp_dir, &summary[num]);

		if (res) {
			num++;
			assert(num < ARRAY_SIZE(summary));
		}
	}

	ret = closedir(d);
	assert(ret >= 0);
	if (num == 0) {
		/* no results */
		fprintf(stderr, "Warning: no results present!\n");
		return;
	}

	qsort(summary, num, sizeof(summary[0]), compare_experiment_name);
	ret = snprintf(exp_dir, sizeof(exp_dir), "%s/summary.html", 
			results);
	assert(ret < sizeof(exp_dir));
	html_gen_summary(exp_dir, num, summary);
}

/** remove all created temporary files again */
static void
cleanup_tmp_files(struct cpssp *cpssp)
{
	unsigned int i;
	int ret;

	for (i = 0; i < cpssp->n_tmp_files; i++) {
		ret = unlink(cpssp->tmp_files[i]);
		assert(ret == 0);
	}

	cpssp->n_tmp_files = 0;
}

/** dirty hack to pick up all temporary .dat files and generate a 
 *  summary plot from index 0.
 *  @param prefix directory prefix to filename
 *  @param filename name of output file.
 *  @param cpssp cpssp instance with temporary files in it.
 */
static void
plot_all_succ(
	const char *prefix, 
	const char *filename, 
	const struct cpssp *cpssp
)
{
	int ret;
	int i;
	FILE *gnu_plot = cpssp->gnuplot_out;
	bool first = true;
	bool do_print = false;

	/* check if there's anything to plot at all. */
	for (i = 0; i < cpssp->n_tmp_files; i++) {
		if (cpssp->is_tmp_f_succ) {
			do_print = true;
			break;
		}
	}

	if (! do_print) {
		return;
	}

	/* generic info: title, date format, etc. */
	ret = fprintf(gnu_plot, "set title \"All successful tests\";\n");
	ret = fprintf(gnu_plot, "set xlabel \"date\";\n");
	ret = fprintf(gnu_plot, "set ylabel \"runtime (minutes)\";\n");
	ret = fprintf(gnu_plot, "set xdata time;\n");
	ret = fprintf(gnu_plot, "set timefmt \"%%Y%%m%%d-%%H%%M\";\n");
	ret = fprintf(gnu_plot, "set terminal png;\n");
	ret = fprintf(gnu_plot, "set output \"%s/%s\";\n", prefix, filename);

	for (i = 0; i < cpssp->n_tmp_files; i++) {
		if (! cpssp->is_tmp_f_succ[i]) {
			continue;
		}

		if (first) {
			ret = fprintf(gnu_plot, "plot \"%s\" index 0 using "
					"1:2 with lines ti \"\"", 
					cpssp->tmp_files[i]);
			first = false;
			continue;
		} 

		ret = fprintf(gnu_plot, ", \"%s\" index 0 using "
					"1:2 with lines ti \"\"", 
					cpssp->tmp_files[i]);
	}
	ret = fprintf(gnu_plot, ";\n");
}

int
main(int argc, char **argv)
{
	const char *sdir;
	const char *prog_name = *argv;

	if (argc >= 2) {
		argv++;
		sdir = *argv;
	} else {
		fprintf(stderr, "Usage: %s <result directory>.\n", prog_name);
		exit(EXIT_FAILURE);
	}
	/* ignore param 2: dir to test run and
	 *        param 3: name of experiment
	 */

	_cpssp.gnuplot_out = launch_gnuplot(&_cpssp.gnuplot_pid);
	generate_summary(sdir);
	plot_all_succ(sdir, "summary_all.png", &_cpssp);
	stop_gnuplot(_cpssp.gnuplot_pid, _cpssp.gnuplot_out);
	cleanup_tmp_files(&_cpssp);
	
	return 0;
}
