diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2011, Jasper Van der Jeugt
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Jasper Van der Jeugt nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/criterion-to-html.cabal b/criterion-to-html.cabal
new file mode 100644
--- /dev/null
+++ b/criterion-to-html.cabal
@@ -0,0 +1,50 @@
+Name:          criterion-to-html
+Version:       0.0.0.1
+Synopsis:      Convert criterion output to HTML reports
+License:       BSD3
+License-file:  LICENSE
+Author:        Jasper Van der Jeugt
+Maintainer:    m@jaspervdj.be
+Category:      Development
+Build-type:    Simple
+Homepage:      http://github.com/jaspervdj/criterion-to-html
+Bug-Reports:   http://github.com/jaspervdj/criterion-to-html/issues
+Cabal-version: >= 1.6
+
+Description:
+  A program to convert criterion output (a CSV file) to an HTML which has some
+  quick and dirty plots to compare the results. To generate the CSV file, run
+  your criterion benchmark using the @-u@ flag:
+
+  .
+
+  > some-criterion-benchmark -u results.csv
+
+  .
+
+  You can then convert the CSV results to some simple HTML plots using this
+  program:
+
+  .
+
+  > criterion-to-html results.csv
+
+  .
+
+
+Source-Repository head
+  Type:     git
+  Location: git://github.com/jaspervdj/criterion-to-html.git
+
+Executable criterion-to-html
+  Hs-source-dirs: src/
+  Main-is:        Criterion/ToHtml.hs
+  Build-depends:
+    base       >= 4   && < 5,
+    blaze-html >= 0.4 && < 0.5,
+    bytestring >= 0.9 && < 0.10,
+    filepath   >= 1.1 && < 1.3
+  Other-modules:
+    Criterion.ToHtml.Csv,
+    Criterion.ToHtml.Html,
+    Criterion.ToHtml.Result
diff --git a/src/Criterion/ToHtml.hs b/src/Criterion/ToHtml.hs
new file mode 100644
--- /dev/null
+++ b/src/Criterion/ToHtml.hs
@@ -0,0 +1,32 @@
+-- | Main module
+--
+module Main
+    ( main
+    ) where
+
+import Control.Applicative ((<$>))
+import System.Environment (getArgs, getProgName)
+import System.FilePath (replaceExtension)
+
+import Text.Blaze.Renderer.Utf8 (renderHtml)
+import qualified Data.ByteString.Lazy as BL
+
+import Criterion.ToHtml.Html
+import Criterion.ToHtml.Result
+
+toHtml' :: FilePath -> FilePath -> IO ()
+toHtml' csv html = do
+    putStrLn $ "Parsing " ++ csv
+    csv' <- parseCriterionCsv <$> readFile csv
+    BL.writeFile html $ renderHtml $ table csv'
+    putStrLn $ "Wrote " ++ html
+
+main :: IO ()
+main = do
+    args <- getArgs
+    progName <- getProgName
+    case args of
+        [csv, html] -> toHtml' csv html
+        [csv]       -> toHtml' csv (replaceExtension csv "html")
+        _           -> putStrLn $
+            "Usage: " ++ progName ++ " <csv-file> [out-file]"
diff --git a/src/Criterion/ToHtml/Csv.hs b/src/Criterion/ToHtml/Csv.hs
new file mode 100644
--- /dev/null
+++ b/src/Criterion/ToHtml/Csv.hs
@@ -0,0 +1,36 @@
+-- | Simple CSV parser
+--
+module Criterion.ToHtml.Csv
+    ( Field
+    , Record
+    , Csv
+    , parseCsv
+    ) where
+
+type Field = String
+type Record = [Field]
+type Csv = [Record]
+
+-- | Parse a CSV file
+--
+parseCsv :: String -> Csv
+parseCsv = map parseRecord . lines
+
+-- | Parse a CSV record
+--
+parseRecord :: String -> Record
+parseRecord rec = case parseField rec of
+    (x, ',' : y) -> x : parseRecord y
+    (x, _)       -> [x]
+
+-- | Parse a CSV field
+--
+parseField :: String -> (Field, String)
+parseField xs = case xs of
+    ('"' : xs') -> parse' "" xs'
+    _           -> break (== ',') xs
+  where
+    parse' y ('"' : '"' : ys) = parse' ('"' : y) ys
+    parse' y ('"' : ys)       = (reverse y, ys)
+    parse' y (y'  : ys)       = parse' (y' : y) ys
+    parse' y []               = (reverse y, "")
diff --git a/src/Criterion/ToHtml/Html.hs b/src/Criterion/ToHtml/Html.hs
new file mode 100644
--- /dev/null
+++ b/src/Criterion/ToHtml/Html.hs
@@ -0,0 +1,38 @@
+-- | Output the results to HTML
+--
+{-# LANGUAGE OverloadedStrings #-}
+module Criterion.ToHtml.Html
+    ( table
+    ) where
+
+import Data.Monoid (mappend)
+import Text.Printf (printf)
+
+import Text.Blaze (Html, toHtml, toValue, preEscapedString, (!))
+import qualified Text.Blaze.Html5 as H
+import qualified Text.Blaze.Html5.Attributes as A
+
+import Criterion.ToHtml.Result
+
+table :: [Result] -> Html
+table results = H.docTypeHtml $ do
+    H.head $ H.title "Criterion results"
+    H.body $ do 
+        H.table ! A.style "width: 100%;" $ do
+            H.tr $ do
+                H.th "Name"
+                H.th "Mean"
+            mapM_ row $ normalizeMeans results
+
+row :: (Result, Double) -> Html
+row (Result name _, n) = H.tr $ do
+    H.td $ toHtml name
+    H.td ! A.style "width: 100%" $
+        H.div ! A.style (backgroundColor `mappend` "; " `mappend` width)
+              $ preEscapedString "&nbsp;"
+  where
+    width = toValue $ "width: " ++ show (round (n * 100) :: Int) ++ "%"
+    backgroundColor = toValue
+        (printf "background-color: rgb(%d, %d, %d)" r g b :: String)
+    r, g, b :: Int
+    (r, g, b) = (round (150 * n), round (150 * (1 - n)), 0)
diff --git a/src/Criterion/ToHtml/Result.hs b/src/Criterion/ToHtml/Result.hs
new file mode 100644
--- /dev/null
+++ b/src/Criterion/ToHtml/Result.hs
@@ -0,0 +1,35 @@
+-- | Parse Criterion results
+--
+module Criterion.ToHtml.Result
+    ( Result (..)
+    , parseCriterionCsv
+    , normalizeMeans
+    ) where
+
+import Criterion.ToHtml.Csv
+
+-- | A criterion result
+--
+data Result = Result
+    { resultName :: String
+    , resultMean :: Double
+    }
+
+-- | Parse a Criterion CSV file
+--
+parseCriterionCsv :: String -> [Result]
+parseCriterionCsv = map parseCriterionResult . drop 1 . parseCsv
+
+-- | Parse a single result
+--
+parseCriterionResult :: Record -> Result
+parseCriterionResult (name : mean : _) = Result name (read mean)
+parseCriterionResult _                 = error
+    "Criterion.ToHtml.Parse.parseCriterionResult: invalid CSV file"
+
+normalizeMeans :: [Result] -> [(Result, Double)]
+normalizeMeans []      = []
+normalizeMeans results = map normalize results
+  where
+    normalize result = (result, resultMean result / max')
+    max' = maximum $ map resultMean results
