diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for monad-timing
+
+## 0.1.0.0  -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2016 Jude Taylor
+
+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 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.
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/monad-timing.cabal b/monad-timing.cabal
new file mode 100644
--- /dev/null
+++ b/monad-timing.cabal
@@ -0,0 +1,41 @@
+-- Initial monad-timing.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                monad-timing
+version:             0.1.0.0
+synopsis:            Monad transformer for recording timing events
+description:         Monad transformer for recording timing events
+homepage:            https://github.com/pikajude/monad-timing
+license:             MIT
+license-file:        LICENSE
+author:              Jude Taylor
+maintainer:          me@jude.bio
+category:            Control
+build-type:          Simple
+extra-source-files:  ChangeLog.md
+cabal-version:       >=1.10
+
+source-repository head
+  type: git
+  location: https://github.com/pikajude/monad-timing
+
+library
+  exposed-modules:     Control.Monad.Timing
+  build-depends:       base ==4.*
+                     , containers
+                     , exceptions
+                     , mtl
+                     , monad-control
+                     , time
+                     , transformers
+                     , transformers-base
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+test-suite hlint
+  type:                exitcode-stdio-1.0
+  main-is:             hlint.hs
+  build-depends:       base, hlint
+  hs-source-dirs:      test
+  default-language:    Haskell2010
+  ghc-options:         -w -threaded -rtsopts -with-rtsopts=-N
diff --git a/src/Control/Monad/Timing.hs b/src/Control/Monad/Timing.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Monad/Timing.hs
@@ -0,0 +1,192 @@
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE NoMonomorphismRestriction  #-}
+{-# LANGUAGE TypeFamilies               #-}
+{-# LANGUAGE UndecidableInstances       #-}
+
+module Control.Monad.Timing
+    ( -- * Introduction
+      -- $intro
+
+      -- * @MonadTiming@
+      MonadTiming(..), TimingTree, Tag,
+
+      -- * @TimingT@, general timing handler
+      TimingT(..), runTimingT, execTimingT,
+
+      -- * @NoTimingT@, a no-timing handler
+      NoTimingT (..)
+    ) where
+
+import Control.Applicative         (Alternative (..), liftA2)
+import Control.Arrow               (first, second)
+import Control.Monad.Base          (MonadBase (..))
+import Control.Monad.Catch         (MonadCatch (..), MonadMask (..),
+                                    MonadThrow (..))
+import Control.Monad.Cont
+import Control.Monad.Except        (MonadError (..))
+import Control.Monad.IO.Class      (MonadIO (..))
+import Control.Monad.RWS
+import Control.Monad.Trans.Control (MonadBaseControl (..))
+import Data.Function
+import Data.List
+import Data.Time
+import Data.Tree
+
+-- | Monads that support timing subcomputations.
+class MonadTiming m where
+    -- | Time the given computation with the given tag.
+    timeGroup :: Tag -> m a -> m a
+
+-- | A tree of timing events.
+type TimingTree = Tree (Tag, NominalDiffTime)
+
+-- | Tags are strings.
+type Tag = String
+
+-- | A monad transformer that records timing events.
+--
+-- Note that this module exports both '_runTimingT' and 'runTimingT'.
+--
+-- * '_runTimingT' returns the timing events verbatim without sanitization.
+-- * 'runTimingT' combines trees with the same 'Tag' and tags the duration
+-- of the entire computation with the special tag @\<all\>@.
+newtype TimingT m a = TimingT { _runTimingT :: m (a, [TimingTree]) }
+
+evalTimingT :: Functor f => TimingT f b -> f b
+evalTimingT = fmap fst . _runTimingT
+
+-- | Run a @TimingT@ computation, discarding the result.
+execTimingT :: MonadIO m => TimingT m b -> m TimingTree
+execTimingT = fmap snd . runTimingT
+
+-- | Run the giving @TimingT@ computation, recording timing events.
+runTimingT :: MonadIO m => TimingT m a -> m (a, TimingTree)
+runTimingT = fmap (second (head . condenseTree)) . _runTimingT . timeGroup "<all>"
+
+condenseTree :: [TimingTree] -> [TimingTree]
+condenseTree = map (foldl collapseNodes emptyNode)
+             . groupBy ((==) `on` (fst . rootLabel)) where
+    collapseNodes (Node (_, x) sub) (Node (t, x1) sub2)
+        = Node (t, x + x1) (condenseTree $ sub ++ sub2)
+    emptyNode = Node ("", 0) []
+
+liftTimingT :: Functor m => m a -> TimingT m a
+liftTimingT = TimingT . fmap (\ x -> (x, []))
+
+instance Functor m => Functor (TimingT m) where
+    fmap f (TimingT q) = TimingT (first f <$> q)
+
+instance Applicative m => Applicative (TimingT m) where
+    pure x = TimingT (pure (x, []))
+    TimingT f <*> TimingT a' = TimingT $
+        liftA2 (\ (a,b) (c,d) -> (a c, b ++ d)) f a'
+
+instance Alternative m => Alternative (TimingT m) where
+    empty = liftTimingT empty
+    a <|> b = TimingT $ _runTimingT a <|> _runTimingT b
+
+instance Monad m => Monad (TimingT m) where
+    TimingT a >>= f = TimingT $ do
+        (thing1, b) <- a
+        (thing2, c) <- _runTimingT $ f thing1
+        return (thing2, b ++ c)
+
+instance MonadTrans TimingT where
+    lift = liftTimingT
+
+instance MonadFix m => MonadFix (TimingT m) where
+    mfix f = lift $ mfix $ \ a -> evalTimingT (f a)
+
+instance MonadPlus m => MonadPlus (TimingT m) where
+
+instance MonadReader r m => MonadReader r (TimingT m) where
+    local f m = TimingT $ local f $ _runTimingT m
+    ask = lift ask
+
+instance MonadWriter w m => MonadWriter w (TimingT m) where
+    tell = lift . tell
+    listen m = TimingT $ do
+        ~((a, b), c) <- listen (_runTimingT m)
+        return ((a, c), b)
+    pass m = TimingT $ pass $ do
+        ~((a, b), c) <- _runTimingT m
+        return ((a, c), b)
+
+instance MonadState s m => MonadState s (TimingT m) where
+    get = lift get
+    put = lift . put
+
+instance MonadRWS r w s m => MonadRWS r w s (TimingT m)
+
+instance MonadCont m => MonadCont (TimingT m) where
+    callCC f = lift $ callCC $ \ c -> evalTimingT (f (TimingT . c))
+
+instance MonadThrow m => MonadThrow (TimingT m) where
+    throwM e = TimingT $ throwM e
+
+instance MonadCatch m => MonadCatch (TimingT m) where
+    TimingT a `catch` f = TimingT $ a `catch` (_runTimingT . f)
+
+instance MonadMask m => MonadMask (TimingT m) where
+    mask a = TimingT $ mask $ \ u -> _runTimingT (a $ q u) where
+        q u (TimingT m) = TimingT (u m)
+    uninterruptibleMask a = TimingT $ uninterruptibleMask $ \ u -> _runTimingT (a $ q u) where
+        q u (TimingT m) = TimingT (u m)
+
+instance MonadError e m => MonadError e (TimingT m) where
+    throwError = TimingT . throwError
+    TimingT a `catchError` f = TimingT $ a `catchError` (_runTimingT . f)
+
+instance MonadBase b m => MonadBase b (TimingT m) where
+    liftBase = lift . liftBase
+
+instance MonadBaseControl b m => MonadBaseControl b (TimingT m) where
+    type StM (TimingT m) a = StM m a
+
+    liftBaseWith runInBase = lift $
+        liftBaseWith (\ runInTiming -> runInBase (\ (TimingT f) -> runInTiming $ fmap fst f))
+
+    restoreM = lift . restoreM
+
+instance MonadIO m => MonadIO (TimingT m) where
+    liftIO = lift . liftIO
+
+instance MonadIO m => MonadTiming (TimingT m) where
+    timeGroup s (TimingT m) = TimingT $ do
+        t1 <- liftIO getCurrentTime
+        (res, ps) <- m
+        t2 <- liftIO getCurrentTime
+        return (res, [Node (s, diffUTCTime t2 t1) ps])
+
+-- | Run a computation without recording any timing data.
+newtype NoTimingT m a = NoTimingT { runNoTimingT :: m a }
+    deriving ( Functor, Applicative, Monad, MonadIO, Alternative, MonadFix, MonadPlus
+             , MonadReader r, MonadState s, MonadWriter w, MonadRWS r w s
+             , MonadError e, MonadCatch, MonadThrow, MonadMask
+             )
+
+instance MonadTrans NoTimingT where
+    lift = NoTimingT
+
+instance MonadBase b m => MonadBase b (NoTimingT m) where
+    liftBase = lift . liftBase
+
+instance MonadBaseControl b m => MonadBaseControl b (NoTimingT m) where
+    type StM (NoTimingT m) a = StM m a
+
+    liftBaseWith runInBase = lift $
+        liftBaseWith (\ runInTiming -> runInBase (\ (NoTimingT f) -> runInTiming f))
+
+    restoreM = lift . restoreM
+
+instance MonadCont m => MonadCont (NoTimingT m) where
+    callCC f = lift $ callCC $ \ c -> runNoTimingT (f (NoTimingT . c))
+
+instance MonadTiming (NoTimingT m) where timeGroup _ = id
+
+{- $intro
+This package provides an mtl-like interface for timing subcomponents of an
+arbitrary computation.
+-}
diff --git a/test/hlint.hs b/test/hlint.hs
new file mode 100644
--- /dev/null
+++ b/test/hlint.hs
@@ -0,0 +1,12 @@
+module Main where
+
+import Control.Monad
+import Language.Haskell.HLint
+import System.Environment
+import System.Exit
+
+main :: IO ()
+main = do
+    args <- getArgs
+    hints <- hlint ["src"]
+    unless (null hints) exitFailure
