diff --git a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml
index 0992308..83f7d29 100644
--- a/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml
+++ b/docs/reference/intel-gpu-tools/intel-gpu-tools-docs.xml
@@ -19,6 +19,7 @@
     <xi:include href="xml/igt_core.xml"/>
     <xi:include href="xml/igt_types.xml"/>
     <xi:include href="xml/igt_stats.xml"/>
+    <xi:include href="xml/igt_plot.xml"/>
     <xi:include href="xml/igt_debugfs.xml"/>
     <xi:include href="xml/igt_draw.xml"/>
     <xi:include href="xml/igt_kms.xml"/>
diff --git a/lib/Makefile.sources b/lib/Makefile.sources
index 205a9aa..9fabd44 100644
--- a/lib/Makefile.sources
+++ b/lib/Makefile.sources
@@ -12,6 +12,8 @@ libintel_tools_la_SOURCES = 	\
 	igt_aux.h		\
 	igt_gt.c		\
 	igt_gt.h		\
+	igt_plot.c		\
+	igt_plot.h		\
 	igt_stats.c		\
 	igt_stats.h		\
 	igt_types.h		\
diff --git a/lib/igt_plot.c b/lib/igt_plot.c
new file mode 100644
index 0000000..f7187f6
--- /dev/null
+++ b/lib/igt_plot.c
@@ -0,0 +1,607 @@
+/*
+ * Copyright © 2015 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <float.h>
+#include <math.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "igt_plot.h"
+#include "igt_aux.h"	/* min() */
+
+/**
+ * SECTION:igt_plot
+ * @short_description: Draw various plots
+ * @title: Plots
+ * @include: igt_plots.h
+ *
+ * A drawing is better than a long speech. Plotting data can reveal surprises
+ * and the igt_plot_t object let you do just that.
+ */
+
+/* snap to pixel */
+#define SNAP(d)	((uint64_t)(d) + 0.5)
+
+static igt_vector_t *igt_vector_new_internal(unsigned int n)
+{
+	igt_vector_t *v;
+
+	/* single allocation for both the vector and data */
+	v = malloc(sizeof(igt_vector_t) + n * sizeof(double));
+	v->ref = 1;
+	v->n = n;
+
+	return v;
+}
+
+/**
+ * igt_vector_new:
+ * @n: Number of samples
+ *
+ * Creates a zeroed vector of size @n.
+ */
+igt_vector_t *igt_vector_new(unsigned int n)
+{
+	igt_vector_t *v;
+
+	v = igt_vector_new_internal(n);
+	memset(v->values, 0, n * sizeof(double));
+
+	return v;
+}
+
+/**
+ * igt_vector_new_from_array:
+ * @array: (array length=n): C array of doubles
+ * @n: Size of the array
+ *
+ * Creates a new vector from @array, copying the data.
+ */
+igt_vector_t *igt_vector_new_from_array(const double *array, unsigned int n)
+{
+	igt_vector_t *v;
+
+	v = igt_vector_new_internal(n);
+	memcpy(v->values, array, n * sizeof(double));
+
+	return v;
+}
+
+/**
+ * igt_vector_new_from_array_64:
+ * @array: (array length=n): C array of doubles
+ * @n: Size of the array
+ *
+ * Like igt_vector_new_from_array() but for uint64_t.
+ */
+igt_vector_t *igt_vector_new_from_array_u64(const uint64_t *array,
+					    unsigned int n)
+{
+	igt_vector_t *v;
+	unsigned int i;
+
+	v = igt_vector_new_internal(n);
+	for (i = 0; i < n; i++)
+		v->values[i] = (double)array[i];
+
+	return v;
+}
+
+/**
+ * igt_vector_ref:
+ * @v: An #igt_vector_t
+ *
+ * Takes a reference on @v.
+ */
+igt_vector_t *igt_vector_ref(igt_vector_t *v)
+{
+	v->ref++;
+	return v;
+}
+
+/**
+ * igt_vector_unref:
+ * @v: An #igt_vector_t
+ *
+ * Releases a reference on @v, freeing the object if the reference reaches 0.
+ */
+void igt_vector_unref(igt_vector_t *v)
+{
+	if (--v->ref == 0)
+		free(v);
+}
+
+/**
+ * igt_vector_get_min_max:
+ * @v: An #igt_vector_t
+ * @min: (out): The minimum value in @v
+ * @max: (out): The maximum value in @v
+ *
+ * Finds the mininum and maximum value in @v.
+ */
+void igt_vector_get_min_max(const igt_vector_t *v, double *min, double *max)
+{
+	unsigned int i;
+	double small, big;
+
+	/*
+	 * Make sure we deal with an even number of samples for the second step
+	 */
+	if (v->n % 2 == 1){
+		*min = v->values[0];
+		*max = v->values[0];
+		i = 1;
+	} else {
+		*min = DBL_MAX;
+		*max = -DBL_MAX;
+		i = 0;
+	}
+
+	for (; i < v->n; i += 2) {
+		if (v->values[i] < v->values[i + 1]) {
+			small = v->values[i];
+			big = v->values[i + 1];
+		} else {
+			small = v->values[i + 1];
+			big = v->values[i];
+		}
+
+		if (*min > small)
+			*min = small;
+		if (*max < big)
+			*max = big;
+	}
+}
+
+/**
+ * igt_vector_linear:
+ * @min: Lower bound
+ * @max: Upper bound
+ * @n: Number of samples to generate
+ *
+ * Creates a vector of @n values evenly spaced between @min and @max (both
+ * inclusive).
+ */
+igt_vector_t *igt_vector_linear(double min, double max, unsigned int n)
+{
+	igt_vector_t *v;
+	unsigned int i;
+
+	v = igt_vector_new_internal(n);
+	for (i = 0; i < n; i++)
+		v->values[i] = min + i * (max - min) / (n - 1);
+
+	return v;
+}
+
+static void igt_plot_axis_init(igt_plot_axis_t *axis,
+			       igt_orientation_t orientation)
+{
+	memset(axis, 0, sizeof(*axis));
+
+	axis->n_ticks = 5;
+	axis->orientation = orientation;
+	axis->min = DBL_MAX;
+	axis->max = -DBL_MAX;
+}
+
+static void igt_plot_axis_fini(igt_plot_axis_t *axis)
+{
+
+}
+
+static void igt_plot_axis_add_range(igt_plot_axis_t *axis,
+				    double min, double max)
+{
+	if (min < axis->min)
+		axis->min = min;
+	if (max > axis->max)
+		axis->max = max;
+}
+
+static void igt_plot_ctx_init(igt_plot_ctx_t *ctx)
+{
+	memset(ctx, 0, sizeof(*ctx));
+
+	ctx->line_width = 1.5;
+}
+
+static void igt_plot_ctx_fini(igt_plot_ctx_t *ctx)
+{
+	igt_vector_unref(ctx->x);
+	ctx->x = NULL;
+	igt_vector_unref(ctx->y);
+	ctx->y = NULL;
+}
+
+/**
+ * igt_plot_init:
+ * @plot: An #igt_plot_t instance
+ * @width: Width of the plot (in pixels)
+ * @height: Height of the plot (in pixels)
+ *
+ * Initializes an igt_plot_t object. Use igt_plot_fini() when finished with it.
+ */
+void igt_plot_init(igt_plot_t *plot, unsigned int width, unsigned int height)
+{
+	memset(plot, 0, sizeof(*plot));
+
+	plot->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+						   width, height);
+	plot->width = width;
+	plot->height = height;
+	plot->cr = cairo_create(plot->surface);
+
+	igt_plot_axis_init(&plot->x_axis, IGT_ORIENTATION_HORIZONTAL);
+	igt_plot_axis_init(&plot->y_axis, IGT_ORIENTATION_VERTICAL);
+
+	plot->ctx = &plot->contexts[0];
+	igt_plot_ctx_init(plot->ctx);
+}
+
+/**
+ * igt_plot_fini:
+ * @plot: An #igt_plot_t instance
+ *
+ * Frees resources allocated during the life time of @plot.
+ */
+void igt_plot_fini(igt_plot_t *plot)
+{
+	unsigned int i;
+
+	for (i = 0; i < plot->n_valid_contexts; i++)
+		igt_plot_ctx_fini(&plot->contexts[i]);
+
+	igt_plot_axis_fini(&plot->x_axis);
+	igt_plot_axis_fini(&plot->y_axis);
+
+	cairo_destroy(plot->cr);
+	cairo_surface_destroy(plot->surface);
+}
+
+/**
+ * igt_plot_set_line_width:
+ * @plot: An #igt_plot_t instance
+ * @width: The new line width to use
+ *
+ * Set the color to use when drawing plots.
+ */
+void igt_plot_set_line_width(igt_plot_t *plot, double width)
+{
+	igt_plot_ctx_t *ctx = plot->ctx;
+
+	ctx->line_width = width;
+}
+
+static void igt_plot_set_vectors(igt_plot_t *plot,
+				 igt_vector_t *x, igt_vector_t *y)
+{
+	igt_plot_ctx_t *ctx = plot->ctx;
+	double x_min, x_max, y_min, y_max;
+
+	ctx->x = igt_vector_ref(x);
+	ctx->y = igt_vector_ref(y);
+
+	/* X axis (sorted data) */
+	x_min = x->values[0];
+	x_max = x->values[x->n - 1];
+	ctx->x_range = x_max - x_min;
+	igt_plot_axis_add_range(&plot->x_axis, x_min, x_max);
+
+	/* Y axis */
+	igt_vector_get_min_max(y, &y_min, &y_max);
+	igt_plot_axis_add_range(&plot->y_axis, y_min, y_max);
+	ctx->y_range = y_max - y_min;
+}
+
+static void igt_plot_next_ctx(igt_plot_t *plot)
+{
+	unsigned int new_ctx_idx;
+	igt_plot_ctx_t *ctx;
+
+	new_ctx_idx = ++plot->n_valid_contexts;
+	assert(new_ctx_idx < IGT_PLOT_MAX_PLOTS);
+
+	/* keep most states for the next plot */
+	memcpy(&plot->contexts[new_ctx_idx], plot->ctx, sizeof(*plot->ctx));
+	ctx = plot->ctx = &plot->contexts[new_ctx_idx];
+
+	/* but reset the vector data */
+	ctx->x = ctx->y = NULL;
+	ctx->x_range = ctx->y_range = 0.0;
+}
+
+/**
+ * igt_plot_draw:
+ * @plot: An #igt_plot_t instance
+ * @x: X-axis data
+ * @y: Y-axis data
+ *
+ * Draw things on the @plot.
+ */
+void igt_plot_draw(igt_plot_t *plot, igt_vector_t *x, igt_vector_t *y)
+{
+	igt_plot_set_vectors(plot, x, y);
+	igt_plot_next_ctx(plot);
+}
+
+typedef struct {
+	char *text;
+	cairo_text_extents_t extents;
+	igt_align_t halign, valign;
+} igt_label_t;
+
+typedef struct {
+	double tick_label_padding;	/* padding between label and axis */
+	double tick_label_font_size;
+	igt_box_t plot_area;
+	igt_label_t *x_tick_labels;
+	igt_label_t *y_tick_labels;
+} flush_t;
+
+static double plot_length(igt_plot_t *plot, double percent)
+{
+	return round(percent * min(plot->width, plot->height));
+}
+
+static void
+igt_plot_draw_text(igt_plot_t *plot, double x, double y, igt_label_t *label)
+{
+	/* XXX: bearings? */
+
+	switch (label->halign) {
+	case IGT_ALIGN_LEFT:
+		break;
+	case IGT_ALIGN_RIGHT:
+		x -= label->extents.width + label->extents.x_bearing;
+		break;
+	case IGT_ALIGN_CENTER:
+	default:
+		x -= (label->extents.width + label->extents.x_bearing) / 2;
+		break;
+	}
+
+	switch (label->valign) {
+	case IGT_ALIGN_TOP:
+		y += label->extents.height;
+		break;
+	case IGT_ALIGN_BOTTOM:
+		break;
+	case IGT_ALIGN_CENTER:
+	default:
+		y += label->extents.height / 2;
+		break;
+	}
+
+	cairo_move_to(plot->cr, x, y);
+	cairo_show_text(plot->cr, label->text);
+}
+
+static double fit(double p, double start, double range, double scale)
+{
+	return start + (range / 2 + p) * scale;
+}
+
+static void
+igt_plot_draw_one(igt_plot_t *plot, igt_plot_ctx_t *ctx, flush_t *flush)
+{
+	igt_box_t *area = &flush->plot_area;
+	igt_vector_t *x = ctx->x;
+	igt_vector_t *y = ctx->y;
+	double x_min, y_min, x_range, y_range, x_scale, y_scale, area_width,
+	       area_height;
+	unsigned int i;
+
+	/*
+	 * We don't use cairo's CTM to fit the data into the drawing area
+	 * as we may want to draw screen-space things (ie. markers) at the
+	 * same time. Also it's a bit impractical to derive things like
+	 * line_width, which are screen-space dimensions, into the curve data
+	 * space.
+	 */
+	area_width = area->x2 - area->x1;
+	area_height = area->y2 - area->y1;
+	x_min = plot->x_axis.min;
+	y_min = plot->y_axis.min;
+	x_range = plot->x_axis.max - x_min;
+	y_range = plot->y_axis.max - y_min;
+	x_scale = area_width / x_range;
+	y_scale = area_height / y_range;
+
+	cairo_move_to(plot->cr,
+		      fit(x->values[0], area->x1, x_range, x_scale),
+		      fit(y->values[0], area->y2, y_range, -y_scale));
+	for (i = 1; i < x->n; i++)
+		cairo_line_to(plot->cr,
+			      fit(x->values[i], area->x1, x_range, x_scale),
+			      fit(y->values[i], area->y2, y_range, -y_scale));
+	cairo_set_line_cap(plot->cr, CAIRO_LINE_CAP_BUTT);
+	cairo_set_line_width(plot->cr, ctx->line_width);
+	cairo_stroke(plot->cr);
+
+}
+
+static void igt_plot_draw_ticks(igt_plot_t *plot, igt_plot_axis_t *axis,
+				double tick_length, flush_t *flush)
+{
+	unsigned int i;
+	igt_box_t *area = &flush->plot_area;
+	double area_width, area_height;
+
+	area_width = area->x2 - area->x1;
+	area_height = area->y2 - area->y1;
+
+	cairo_set_line_cap(plot->cr, CAIRO_LINE_CAP_SQUARE);
+	cairo_set_line_width(plot->cr, 1.0);
+
+	for (i = 0; i < axis->n_ticks; i++) {
+		double x, y;
+		igt_label_t *label;
+
+		if (axis->orientation == IGT_ORIENTATION_HORIZONTAL) {
+			x = area->x1 + i * area_width / (axis->n_ticks - 1);
+			y = area->y2;
+
+			cairo_move_to(plot->cr, SNAP(x), y);
+			cairo_line_to(plot->cr, SNAP(x), SNAP(y - tick_length));
+			cairo_stroke(plot->cr);
+
+			label = &flush->x_tick_labels[i];
+			y += flush->tick_label_padding;
+		} else {
+			x = area->x1;
+			y = area->y2 - i * area_height / (axis->n_ticks - 1);
+
+			cairo_move_to(plot->cr, x, SNAP(y));
+			cairo_line_to(plot->cr, SNAP(x + tick_length), SNAP(y));
+			cairo_stroke(plot->cr);
+
+			label = &flush->y_tick_labels[i];
+			x -= flush->tick_label_padding;
+		}
+
+		cairo_set_font_size(plot->cr, flush->tick_label_font_size);
+		igt_plot_draw_text(plot, x, y, label);
+	}
+}
+
+static void igt_plot_draw_axis(igt_plot_t *plot, flush_t *flush)
+{
+	igt_box_t *area = &flush->plot_area;
+	const double tick_length = plot_length(plot, 0.01);
+
+	/* X-axis */
+	cairo_move_to(plot->cr, area->x1, area->y2);
+	cairo_line_to(plot->cr, area->x2, area->y2);
+	igt_plot_draw_ticks(plot, &plot->x_axis, tick_length, flush);
+
+	/* Y-axis */
+	cairo_move_to(plot->cr, area->x1, area->y2);
+	cairo_line_to(plot->cr, area->x1, area->y1);
+	igt_plot_draw_ticks(plot, &plot->y_axis, tick_length, flush);
+
+}
+
+static void igt_plot_layout_tick_labels(igt_plot_t *plot,
+					igt_plot_axis_t *axis,
+					igt_label_t *labels,
+					double *max_size)
+{
+	unsigned int i;
+
+	*max_size = -DBL_MAX;
+
+	for (i = 0; i < axis->n_ticks; i++) {
+		igt_label_t *label = &labels[i];
+		double v = axis->min +
+			(axis->max - axis->min) * i / (axis->n_ticks - 1);
+
+		asprintf(&label->text, "%.02lf", v);
+		cairo_text_extents(plot->cr, label->text, &label->extents);
+
+		if (axis->orientation == IGT_ORIENTATION_HORIZONTAL) {
+			label->halign = IGT_ALIGN_CENTER;
+			label->valign = IGT_ALIGN_TOP;
+			if (label->extents.height > *max_size)
+				*max_size = label->extents.height;
+		} else {
+			label->halign = IGT_ALIGN_RIGHT;
+			label->valign = IGT_ALIGN_CENTER;
+			if (label->extents.width > *max_size)
+				*max_size = label->extents.width;
+		}
+	}
+}
+
+static void igt_plot_layout(igt_plot_t *plot, flush_t *flush)
+{
+	const double outer_padding = 0.10;
+	double max_width, max_height;
+
+	flush->tick_label_padding = plot_length(plot, 0.02);
+	flush->tick_label_font_size = round(0.015 * plot->width);
+
+	/* outer padding */
+	flush->plot_area.x1 = SNAP(plot->width * outer_padding);
+	flush->plot_area.y1 = SNAP(plot->height * outer_padding);
+	flush->plot_area.x2 = SNAP(plot->width * (1.0 - outer_padding));
+	flush->plot_area.y2 = SNAP(plot->height * (1.0 - outer_padding));
+
+	/* measure tick labels and adjust the plot area */
+	cairo_set_font_size(plot->cr, flush->tick_label_font_size);
+	igt_plot_layout_tick_labels(plot, &plot->x_axis, flush->x_tick_labels,
+				    &max_height);
+	flush->plot_area.y2 -= max_height - flush->tick_label_padding;
+	igt_plot_layout_tick_labels(plot, &plot->y_axis, flush->y_tick_labels,
+				    &max_width);
+	flush->plot_area.x1 += max_width + flush->tick_label_padding;
+}
+
+static void igt_plot_flush_init(igt_plot_t *plot, flush_t *flush)
+{
+	memset(flush, 0, sizeof(*flush));
+
+	flush->x_tick_labels = malloc(plot->x_axis.n_ticks *
+				 sizeof(*flush->x_tick_labels));
+	flush->y_tick_labels = malloc(plot->y_axis.n_ticks *
+				 sizeof(*flush->y_tick_labels));
+}
+
+static void igt_plot_flush_fini(igt_plot_t *plot, flush_t *flush)
+{
+	free(flush->x_tick_labels);
+	free(flush->y_tick_labels);
+}
+
+/**
+ * igt_plot_write:
+ * @plot: An #igt_plot_t instance
+ * @filename: File name
+ *
+ * Write @plot onto the disk in a file named @filename.
+ */
+void igt_plot_write(igt_plot_t *plot, const char *filename)
+{
+	flush_t flush;
+	unsigned int i;
+
+	igt_plot_flush_init(plot, &flush);
+
+	igt_plot_layout(plot, &flush);
+
+	igt_plot_draw_axis(plot, &flush);
+
+	for (i = 0; i < plot->n_valid_contexts; i++)
+		igt_plot_draw_one(plot, &plot->contexts[i], &flush);
+
+	cairo_surface_write_to_png(plot->surface, filename);
+
+	igt_plot_flush_fini(plot, &flush);
+}
diff --git a/lib/igt_plot.h b/lib/igt_plot.h
new file mode 100644
index 0000000..82ad10a
--- /dev/null
+++ b/lib/igt_plot.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2015 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#ifndef __IGT_PLOT_H__
+#define __IGT_PLOT_H__
+
+#include <stdint.h>
+
+#include <cairo.h>
+
+#include "igt_types.h"
+
+/**
+ * igt_vector_t:
+ * @values: Array of doubles
+ * @n: Length of @values
+ *
+ * A ref-counted, fixed-length, array of doubles
+ *
+ * A simple, fixed-sized, vector of doubles
+ */
+typedef struct {
+	/*< private >*/
+	int ref;
+	/*< public >*/
+	unsigned int n;
+	double values[];
+} igt_vector_t;
+
+igt_vector_t *igt_vector_new(unsigned int n);
+igt_vector_t *igt_vector_new_from_array(const double *array, unsigned int n);
+igt_vector_t *igt_vector_new_from_array_u64(const uint64_t *array,
+					    unsigned int n);
+igt_vector_t *igt_vector_linear(double min, double max, unsigned n);
+igt_vector_t *igt_vector_ref(igt_vector_t *v);
+void igt_vector_unref(igt_vector_t *v);
+void igt_vector_get_min_max(const igt_vector_t *v, double *min, double *max);
+
+/**
+ * igt_plot_axis_t:
+ *
+ * An axis.
+ */
+typedef struct {
+	/*< private >*/
+	igt_orientation_t orientation;
+	unsigned int n_ticks;
+	double min, max; /* range of the values on this axis */
+} igt_plot_axis_t;
+
+#define IGT_PLOT_MAX_PLOTS	32
+
+typedef struct {
+	/*< private >*/
+	igt_vector_t *x, *y;
+	double x_range, y_range;
+	double line_width;
+} igt_plot_ctx_t;
+
+/**
+ * igt_plot_t:
+ *
+ * Draw nice plots!
+ */
+typedef struct {
+	/*< private >*/
+
+	/* Cairo's corner */
+	cairo_surface_t *surface;
+	cairo_t *cr;
+
+	/* plot-wide states */
+	unsigned int width, height;
+	igt_trbl_t margin;
+	igt_plot_axis_t x_axis, y_axis;
+
+	/* per draw command contexts */
+	igt_plot_ctx_t contexts[IGT_PLOT_MAX_PLOTS + 1];
+	unsigned int n_valid_contexts;
+	igt_plot_ctx_t *ctx;
+} igt_plot_t;
+
+/**
+ * igt_plot_style_t:
+ * @IGT_PLOT_LINE: Draw lines between data points
+ * @IGT_PLOT_POINT: Draw points at each data point
+ *
+ * The different types of plots we can draw.
+ */
+typedef enum {
+	IGT_PLOT_LINE,
+	IGT_PLOT_POINT,
+} igt_plot_style_t;
+
+void igt_plot_init(igt_plot_t *plot, unsigned int width, unsigned int height);
+void igt_plot_fini(igt_plot_t *plot);
+void igt_plot_set_line_width(igt_plot_t *plot, double width);
+void igt_plot_draw(igt_plot_t *plot, igt_vector_t *x, igt_vector_t *y);
+void igt_plot_write(igt_plot_t *plot, const char *filename);
+
+#endif /* __IGT_PLOT_H__ */
diff --git a/lib/tests/.gitignore b/lib/tests/.gitignore
index 6519406..d7a9174 100644
--- a/lib/tests/.gitignore
+++ b/lib/tests/.gitignore
@@ -5,8 +5,12 @@ igt_list_only
 igt_no_exit
 igt_no_exit_list_only
 igt_no_subtest
+igt_plot
 igt_segfault
 igt_simple_test_subtests
 igt_simulation
 igt_stats
 igt_timeout
+
+# files generated by igt_plot
+test_*.png
diff --git a/lib/tests/Makefile.sources b/lib/tests/Makefile.sources
index 58ae36b..9c5cf3f 100644
--- a/lib/tests/Makefile.sources
+++ b/lib/tests/Makefile.sources
@@ -4,6 +4,7 @@ check_PROGRAMS = \
 	igt_fork_helper \
 	igt_list_only \
 	igt_no_subtest \
+	igt_plot \
 	igt_simulation \
 	igt_simple_test_subtests \
 	igt_stats \
diff --git a/lib/tests/igt_plot.c b/lib/tests/igt_plot.c
new file mode 100644
index 0000000..be0e132
--- /dev/null
+++ b/lib/tests/igt_plot.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright © 2015 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include <math.h>
+#include <stdint.h>
+
+#include "igt_core.h"
+#include "igt_plot.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
+
+#define SNAP_TO_PIXEL(d)	((uint64_t)(d) + 0.5)
+
+static void test_snap_to_pixel(void)
+{
+	static const struct { double input; double expected; } test_data[] = {
+		{ 1.0, 1.5 }, { 1.1, 1.5 }, { 1.2, 1.5 }, { 1.3, 1.5 },
+		{ 1.4, 1.5 }, { 1.5, 1.5 }, { 1.6, 1.5 }, { 1.7, 1.5 },
+		{ 1.8, 1.5 }, { 1.9, 1.5 }, { 2.0, 2.5 }, { 2.1, 2.5 },
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(test_data); i++)
+		igt_assert_eq_double(SNAP_TO_PIXEL(test_data[i].input),
+				     test_data[i].expected);
+}
+
+static void test_min_max(void)
+{
+	static const uint64_t s1[] =
+		{ 47, 49, 6, 7, 15, 36, 39, 40, 41, 42, 43 };
+	static const uint64_t s2[] = { 40, 41, 7, 15, 36, 39 };
+	igt_vector_t *v1, *v2;
+	double min, max;
+
+	v1 = igt_vector_new_from_array_u64(s1, ARRAY_SIZE(s1));
+	v2 = igt_vector_new_from_array_u64(s2, ARRAY_SIZE(s2));
+
+	igt_vector_get_min_max(v1, &min, &max);
+	igt_assert_eq_double(min, 6);
+	igt_assert_eq_double(max, 49);
+
+	igt_vector_get_min_max(v2, &min, &max);
+	igt_assert_eq_double(min, 7);
+	igt_assert_eq_double(max, 41);
+
+	igt_vector_unref(v1);
+	igt_vector_unref(v2);
+}
+
+static void test_simple_plot(void)
+{
+	igt_vector_t *x, *y;
+	unsigned int i;
+	igt_plot_t plot;
+
+	x = igt_vector_linear(-1.0, 1.0, 200);
+
+	y = igt_vector_new(200);
+	for (i = 0; i < y->n; i++)
+		y->values[i] = sin(2 * M_PI * x->values[i]);
+
+	igt_plot_init(&plot, 800, 600);
+	igt_plot_draw(&plot, x, y);
+	igt_plot_write(&plot, "test_simple_plot.png");
+
+	igt_plot_fini(&plot);
+	igt_vector_unref(x);
+	igt_vector_unref(y);
+}
+
+igt_simple_main
+{
+	test_snap_to_pixel();
+	test_min_max();
+	test_simple_plot();
+}
