packages feed

tkyprof 0.0.5.2 → 0.0.6

raw patch · 13 files changed

+421/−149 lines, 13 files

Files

Handler/Home.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE TemplateHaskell, QuasiQuotes, OverloadedStrings #-} module Handler.Home where-import TKYProf+import TKYProf hiding (reports) import Yesod.Form (Enctype(Multipart)) import Data.Maybe (listToMaybe) import Handler.Reports.Helpers (getAllReports)
Handler/Reports.hs view
@@ -3,18 +3,20 @@   ( getReportsR   , postReportsR   , getReportsIdR+  , getReportsIdTimeR+  , getReportsIdAllocR   ) where -import TKYProf-import ProfilingReport-import Control.Applicative-import Yesod.Request import Data.Maybe (listToMaybe)+import Data.Text (Text) import Handler.Reports.Helpers (getAllReports, getProfilingReport, postProfilingReport)+import ProfilingReport+import TKYProf+import Yesod.Request+import qualified Data.Aeson as A (encode) import qualified Data.Attoparsec as A import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L-import qualified Data.Aeson as A (encode) import qualified Data.Text.Lazy.Encoding as T (decodeUtf8)  getReportsR :: Handler RepHtml@@ -26,30 +28,48 @@  postReportsR :: Handler () postReportsR = do-  FileInfo {fileContent} <- getPostedReport-  prof <- parseFileContent fileContent-  postProfilingReport prof+  files <- getPostedReports+  case files of+    [fileInfo] -> do reportId <- postFileInfo fileInfo+                     sendResponseCreated $ ReportsIdR reportId+    _          -> do mapM_ postFileInfo files+                     sendResponseCreated ReportsR  getReportsIdR :: ReportID -> Handler RepHtml-getReportsIdR reportId = do-  report@ProfilingReport {..} <- getProfilingReport reportId-  let json = T.decodeUtf8 $ A.encode reportCostCentres-  defaultLayout $ do-    setTitle $ "TKYProf Reports"-    addScript $ StaticR js_d3_min_js-    addScript $ StaticR js_d3_layout_min_js-    addWidget $(widgetFile "reports-id")+getReportsIdR reportId = redirect RedirectSeeOther (ReportsIdTimeR reportId []) +getReportsIdTimeR :: ReportID -> [a] -> Handler RepHtml+getReportsIdTimeR reportId _ = getReportsIdCommon reportId "time"++getReportsIdAllocR :: ReportID -> [a] -> Handler RepHtml+getReportsIdAllocR reportId _ = getReportsIdCommon reportId "alloc"+ -- Helper functions-getPostedReport :: Handler FileInfo-getPostedReport = do+getPostedReports :: Handler [FileInfo]+getPostedReports = do   (_, files) <- runRequestBody-  case lookup "reports" files of-    Nothing   -> invalidArgs ["Missing files"]-    Just file -> return file+  case [ file | (header, file) <- files, header == "reports" ] of+    []    -> invalidArgs ["Missing files"]+    found -> return found +postFileInfo :: FileInfo -> Handler ReportID+postFileInfo FileInfo {fileContent} = do+  prof <- parseFileContent fileContent+  postProfilingReport prof+ parseFileContent :: L.ByteString -> Handler ProfilingReport parseFileContent content =   case A.parseOnly profilingReport (S.concat $ L.toChunks content) of     Left err   -> invalidArgs ["Invalid format", toMessage err]     Right tree -> return tree++getReportsIdCommon :: ReportID -> Text -> Handler RepHtml+getReportsIdCommon reportId profilingType = do+  report@ProfilingReport {..} <- getProfilingReport reportId+  let json = T.decodeUtf8 $ A.encode reportCostCentres+  defaultLayout $ do+    setTitle $ "TKYProf Reports"+    addScript $ StaticR js_tkyprof_js+    addScript $ StaticR js_d3_min_js+    addScript $ StaticR js_d3_layout_min_js+    addWidget $(widgetFile "reports-id")
Handler/Reports/Helpers.hs view
@@ -12,8 +12,8 @@ import Control.Monad.Trans (liftIO) import Model (Reports(..), ReportID, allReports, lookupReport, insertReport) import ProfilingReport (ProfilingReport)-import TKYProf (Handler, TKYProf(getReports), TKYProfRoute(..))-import Yesod.Core (getYesod, sendResponseCreated)+import TKYProf (Handler, TKYProf(getReports))+import Yesod.Core (getYesod) import Yesod.Handler (notFound)  runReports :: STM a -> Handler a@@ -38,10 +38,7 @@     Just r  -> return r     Nothing -> notFound -postProfilingReport :: ProfilingReport -> Handler ()+postProfilingReport :: ProfilingReport -> Handler ReportID postProfilingReport prof = do   rs <- getReports'-  reportId <- runReports $ insertReport prof rs-  sendResponseCreated (ReportsIdR reportId)--+  runReports $ insertReport prof rs
TKYProf.hs view
@@ -102,7 +102,8 @@     return $ Just $ Right (StaticR $ StaticRoute ["tmp", T.pack fn] [], [])  instance YesodBreadcrumbs TKYProf where-  breadcrumb HomeR            = return ("Home", Nothing)-  breadcrumb ReportsR         = return ("Reports", Just HomeR)-  breadcrumb (ReportsIdR rid) = return ("Report #" `T.append` T.pack (show rid), Just ReportsR)-  breadcrumb _                = return ("Not found", Just HomeR)+  breadcrumb HomeR                   = return ("Home", Nothing)+  breadcrumb ReportsR                = return ("Reports", Just HomeR)+  breadcrumb (ReportsIdTimeR rid _)  = return ("Report #" `T.append` T.pack (show rid), Just ReportsR)+  breadcrumb (ReportsIdAllocR rid _) = return ("Report #" `T.append` T.pack (show rid), Just ReportsR)+  breadcrumb _                       = return ("Not found", Just HomeR)
config/routes view
@@ -1,6 +1,8 @@-/                          HomeR              GET-/reports                   ReportsR           GET POST-/reports/#Integer          ReportsIdR         GET-/static                    StaticR            Static getStatic-/favicon.png               FaviconR           GET-/robots.txt                RobotsR            GET+/                              HomeR           GET+/reports                       ReportsR        GET POST+/reports/#Integer              ReportsIdR      GET+/reports/#Integer/time/*Texts  ReportsIdTimeR  GET+/reports/#Integer/alloc/*Texts ReportsIdAllocR GET+/static                        StaticR         Static getStatic+/favicon.png                   FaviconR        GET+/robots.txt                    RobotsR         GET
hamlet/home.hamlet view
@@ -1,19 +1,17 @@-<section>-  <h1>Instructions-  <h2>1. Select your profiling reports-  <form #uploader action=@{ReportsR} method=post enctype=#{Multipart}>-    <div #box-wrapper>-      <div #uploader-dropbox .box>-        <p>Drop files here!-      <div #uploader-filedialog .box>-        <p>Click to select files.-        <input type=file>-  <h2>-    <a href=@{ReportsR}>2. View reports-  $maybe _ <- listToMaybe reports-    <ul>-      $forall report <- reports-        <li>-          <a href=@{ReportsIdR (fst report)}>#{reportCommandLine (snd report)}-  $nothing-    <p>There are currently no reports available. First of all, upload your profiling reports please.+<h2>1. Select your profiling reports+<form #uploader action=@{ReportsR} method=post enctype=#{Multipart}>+  <div #box-wrapper>+    <div #uploader-dropbox .box>+      <p>Drop files here!+    <div #uploader-filedialog .box>+      <p>Click to select files.+      <input type=file>+<h2>+  <a href=@{ReportsR}>2. View reports+$maybe _ <- listToMaybe reports+  <ul>+    $forall report <- reports+      <li>+        <a href=@{ReportsIdR (fst report)}>#{reportCommandLine (snd report)}+$nothing+  <p>There are currently no reports available. First of all, upload your profiling reports please.
hamlet/reports-id.hamlet view
@@ -1,10 +1,8 @@-<h1>Time and Allocation Profiling Report ##{reportId} <section #cost-centres>   <header>-    <h1>Cost centres:     <div .button-group>-      <div #time .button .active>Time profiling-      <div #alloc .button>Alloc profiling+      <h2 #time .button>Time profiling+      <h2 #alloc .button>Alloc profiling   <div #chart-container>     <figure #chart>     <div #sidebox>@@ -31,7 +29,7 @@         <h2>Children         <ol> <section #summary>-  <h1>Summary+  <h2>Summary   <div #report-summary .box>     <dl>       <dt>Timestamp
hamlet/reports.hamlet view
@@ -1,9 +1,8 @@-<section>-  <h1>All reports-  $maybe _ <- listToMaybe reports-    <ul>-      $forall report <- reports-        <li>-          <a href=@{ReportsIdR (fst report)}>#{reportCommandLine (snd report)}-  $nothing-    <p>There are currently no reports available. First of all, upload your profiling reports please.+<h2>All reports+$maybe _ <- listToMaybe reports+  <ul>+    $forall report <- reports+      <li>+        <a href=@{ReportsIdR (fst report)}>#{reportCommandLine (snd report)}+$nothing+  <p>There are currently no reports available. First of all, upload your profiling reports please.
julius/home.julius view
@@ -6,7 +6,8 @@   });    uploader.fileupload({-    url: '/reports',-    paramName: "reports"+    url:               "/reports",+    paramName:         "reports",+    singleFileUploads: false   }); });
julius/reports-id.julius view
@@ -1,22 +1,157 @@ $(function() {-  var report = #{json};+  var data = #{json};+  var parmalink = costCentreParmalink();+  var chart = costCentreChart({+    data: data,+    parmalink: parmalink+  });+  var button = profilingModeButton({+    chart: chart,+    parmalink: parmalink+  }); -  var w = $("#chart").width(),-      h = $("#chart").height(),+  $(window).bind('popstate', function(event) {+    console.log('popstate');+    if (!window.history.ready && !event.originalEvent.state) {+      return; // workaround for popstate on load+    }+    parmalink.popstate(location.pathname);+    button.mode(parmalink.mode());+    chart.mode(parmalink.mode());+    chart.transposeById(parmalink.id());+  });+});++function costCentreParmalink(spec) {+  var that = {};+  var parsed = parse(location.pathname),+      prefix = parsed[1],+      mode   = parsed[2],+      suffix = parsed[3];++  function pathname() {+    return prefix + mode + suffix;+  };++  function parse(path) {+    var regex = /^(\/reports\/[0-9]+\/)(time|alloc)(.*)$/;+    return regex.exec(path);+  }++  that.mode = function(newMode) {+    if (!newMode)+      return mode; // getter++    mode = newMode;+    window.history.ready = true; // workaround for popstate on load+    history.pushState(null, null, pathname()); // HTML 5 History API+    return mode;+  };++  that.transpose = function(node) {+    var cursor = node, path = [];+    for (cursor = node; cursor.parent; cursor = cursor.parent) {+      path.unshift(cursor.no);+    }+    if (path.length > 0) {+      suffix = "/" + path.join("/");+    } else {+      suffix = "";+    }+  };++  that.popstate = function(url) {+    var parsed = parse(url);+    mode   = parsed[2];+    suffix = parsed[3];+  };++  that.id = function() {+    var regex = /^.*\/([0-9]*)$/;+    var matched = regex.exec(suffix);+    return matched ? matched[1] : null;+  };++  that.pathname = pathname;++  return that;+}++function profilingModeButton(spec) {+  var that = {};+  var chart = spec.chart, parmalink = spec.parmalink;+  var timeButton  = $("#time"),+      allocButton = $("#alloc"),+      _mode;++  console.log(timeButton);+  console.log(allocButton);++  // methods+  var mode = function(newMode) {+    if (!newMode)+      return _mode; // getter++    console.log("button.mode: " + newMode);++    _mode = newMode;+    // buttons+    if (_mode == "time") {+      timeButton.addClass("active");+      allocButton.removeClass("active");+    } else {+      timeButton.removeClass("active");+      allocButton.addClass("active");+    }+    // chart+    chart.mode(_mode);+    return _mode;+  };++  // setup initial states and event handlers+  mode(parmalink.mode());+  timeButton.click(function(event) {+    parmalink.mode("time");+    mode(parmalink.mode());+  });+  allocButton.click(function(event) {+    parmalink.mode("alloc");+    mode(parmalink.mode());+  });++  // public slots+  that.mode = mode;++  return that;+}++function costCentreChart(spec) {+  var that = {};++  var cursor = spec.data,+      selector = spec.selector,+      parmalink = spec.parmalink;+  var context = costCentreContext({cursor: cursor});++  // chart+  var chart = $("#chart"),+      w = spec.w || chart.width(),+      h = spec.h || chart.height(),       r = Math.min(w, h) / 2,       x = d3.scale.linear().range([0, 2*Math.PI]),       y = d3.scale.sqrt().range([0, r]),-      color = d3.scale.category20c();+      color = spec.color || d3.scale.category20c(); -  var vis = d3.select("#chart").append("svg:svg")+  var vis = d3.select("#chart")+    .append("svg:svg")     .attr("width", w)     .attr("height", h)     .append("svg:g")     .attr("transform", "translate(" + w/2 + "," + h/2 + ")");    var partition = d3.layout.partition()-    .value(function(d) { return d.individualTime; })-    .children(function(d) { return d.subForest; });+    .children(function(d) { return d.subForest; })+    .value(value(parmalink.mode()));    var arc = d3.svg.arc()     .startAngle(function(d) { return Math.max(0, Math.min(2*Math.PI, x(d.x))); })@@ -24,67 +159,41 @@     .innerRadius(function(d) { return Math.max(0, y(d.y)); })     .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); }); -  var path = vis.data([report]).selectAll("path")+  var path = vis.data([cursor]).selectAll("path")     .data(partition.nodes)     .enter().append("svg:path")-    .attr("id", function(d) { return "cc" + d.no + (d.isParent ? "p" : ""); })-    .attr("class", function(d) { return d.children || d.isParent ? "branch" : "leaf"; })     .attr("d", arc)+    .attr("id", function(d) { return "cc" + d.no + (d.isParent ? "p" : ""); })     .style("fill", function(d) { return color((d.children ? d : d.parent).name); })     .on("click", click)     .on("mouseover", mouseover)-    .each(stash);--  d3.select("#time").on("click", function() {-    path.data(partition.value(function(d) { return d.individualTime; }))-      .transition()-      .duration(500)-      .attrTween("d", arcTween);--    d3.select("#time").classed("active", true);-    d3.select("#alloc").classed("active", false);-  });--  d3.select("#alloc").on("click", function() {-    path.data(partition.value(function(d) { return d.individualAlloc; }))-      .transition()-      .duration(500)-      .attrTween("d", arcTween);--    d3.select("#time").classed("active", false);-    d3.select("#alloc").classed("active", true);-  });+    .each(function(d) { d.x0 = d.x; d.dx0 = d.dx; }); // stash the old values for transition +  // event handlers   function click(d) {-    showCostCentre(d);-    showChildren(d);-    path.transition()-      .duration(500)-      .attrTween("d", arcTweenZoom(d));+    transpose(d);+    parmalink.transpose(d);+    console.log(parmalink.pathname());+    window.history.ready = true; // workaround for popstate on load+    history.pushState(null, null, parmalink.pathname()); // HTML 5 History API   }    function mouseover(d) {--  }--  // Stash the old values for transition.-  function stash(d) {-    d.x0 = d.x;-    d.dx0 = d.dx;+    // XXX   } -  // Interpolate the arcs in data space.+  // tweeners   function arcTween(a) {     var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);     return function(t) {       var b = i(t);-      a.x0 = b.x;+      a.x0  = b.x;       a.dx0 = b.dx;       return arc(b);     };   } -  function arcTweenZoom(d) {+  function zoomTween(d) {     var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),         yd = d3.interpolate(y.domain(), [d.y, 1]),         yr = d3.interpolate(y.range(), [d.y ? 20 : 0, r]);@@ -95,27 +204,90 @@     };   } -  function showCostCentre(node) {+  // helper functions+  function value(mode) {+    if (mode === "time")+      return function(d) { return d.individualTime; };+    else+      return function(d) { return d.individualAlloc; };+  }++  function nodeFromId(id) {+    if (!id)+      return null;+    console.log(id);+    var elem = d3.select("#cc" + id);+    console.log(elem);+    return elem.node().__data__; // XXX: __data__ is not a public API+  }++  var mode = function(mode) {+    path.data(partition.value(value(mode)))+      .transition()+      .duration(500)+      .attrTween("d", arcTween);+  };++  var transpose = function(node) {+    cursor = node;+    context.transpose(cursor);+    path.transition()+      .duration(500)+      .attrTween("d", zoomTween(cursor));+  };++  // setup initial states+  mode(parmalink.mode());+  transpose(nodeFromId(parmalink.id()) || spec.data);++  // public slots+  that.mode = mode;+  that.transpose = transpose;+  that.transposeById = function(id) {+    transpose(nodeFromId(id) || spec.data);+  };++  return that;+}++function costCentreContext(spec) {+  var that = {};+  var cursor = spec.cursor;++  var showCostCentre = function(node) {     var currentNode = $("#current-node dl");-    $("#current-node-module", currentNode).text(node.module);-    $("#current-node-name", currentNode).text(node.name);-    $("#current-node-number", currentNode).text(node.no);-    $("#current-node-entries", currentNode).text(node.entries);-    $("#current-node-ind-time", currentNode).text(node.individualTime);+    $("#current-node-module",    currentNode).text(node.module);+    $("#current-node-name",      currentNode).text(node.name);+    $("#current-node-number",    currentNode).text(node.no);+    $("#current-node-entries",   currentNode).text(node.entries);+    $("#current-node-ind-time",  currentNode).text(node.individualTime);     $("#current-node-ind-alloc", currentNode).text(node.individualAlloc);-    $("#current-node-inh-time", currentNode).text(node.inheritedTime);+    $("#current-node-inh-time",  currentNode).text(node.inheritedTime);     $("#current-node-inh-alloc", currentNode).text(node.inheritedAlloc);-  }+  }; -  function showChildren(node) {+  var showChildren = function(node) {     $("#children ol li").remove();     var children = $("#children ol");     var cs = node.children;-        n  = cs.length < 10 ? cs.length : 10;-    for (var i = 0; i < n; i++) {-      children.append("<li>" + cs[i].name + "</li>");+    if (cs) {+      var n = cs.length < 10 ? cs.length : 10;+      for (var i = 0; i < n; i++) {+        children.append("<li>" + cs[i].name + "</li>");+      }     }-  }-  showCostCentre(report);-  showChildren(report);-});+  };++  // setup+  showCostCentre(cursor);+  showChildren(cursor);++  // public slots+  that.transpose = function(node) {+    cursor = node;+    showCostCentre(cursor);+    showChildren(cursor);+  };++  return that;+}
lucius/reports-id.lucius view
@@ -1,16 +1,10 @@ /* title and buttons */-#cost-centres h1 {-  display: inline;-  margin-right: 15px;-  font-size: 1.5em;+#cost-centres > header {+  margin: 1em; }--#cost-centres .button-group,-#cost-centres .button-group div {+#cost-centres h2 {   display: inline;-  font-family: 'Amaranth', sans-serif;-  font-weight: 700;-  font-size: 1.0em;+  font-size: 1.5em; }  #cost-centres .button {
+ static/js/tkyprof.js view
@@ -0,0 +1,87 @@+// Quoted by "JavsScrpt: The Good Parts"++Function.prototype.method = function(name, func) {+  if (!this.prototype[name]) {+    this.prototype[name] = func;+    return this;+  }+}++Function.method('curry', function() {+  var slice = Array.prototype.slice,+      args = slice.apply(arguments),+      that = this;+  return function() {+    return that.apply(null, args.concat(slice.apply(arguments)));+  };+});++Array.method('shift', function() {+  return this.splice(0, 1)[0];+});++Array.method('splice', function(start, deleteCount) {+  var max = Math.max,+      min = Math.min,+      delta,+      element,+      insertCount = max(arguments.length - 2, 0),+      k = 0,+      len = this.length,+      newLen,+      result = [],+      shiftCount;++  start = start || 0;+  if (start < 0) {+    start += len;+  }+  start = max(min(start, len), 0);+  deleteCount = max(+    min(+      typeof deleteCount === 'number'+        ? deleteCount+        : len,+      len - start+    ),+    0+  );+  delta = insertCount - deleteCount;+  newLen = len + delta;+  while (k < deleteCount) {+    element = this[start + k];+    if (element !== undefined) {+      result[k] = element;+    }+    k += 1;+  }+  shiftCount = len - start - deleteCount;+  if (delta < 0) {+    k = start + insertCount;+    while (shiftCount) {+      this[k] = this[k - delta];+      k += 1;+      shiftCount -= 1;+    }+    this.length = newLen;+  } else if (delta > 0) {+    k = 1;+    while (shiftCount) {+      this[newLen - k] = this[len - k];+      k += 1;+      shiftCount -= 1;+    }+  }+  for (k = 0; k < insertCount; k += 1) {+    this[start + k] = arguments[k + 2];+  }+  return result;+});++Array.method('unshift', function() {+  this.splice.apply(+    this,+    [0, 0].concat(Array.prototype.slice.apply(arguments))+  );+  return this.length;+});
tkyprof.cabal view
@@ -1,17 +1,19 @@ name:                 tkyprof-version:              0.0.5.2+version:              0.0.6 license:              BSD3 license-file:         LICENSE author:               Mitsutoshi Aoe maintainer:           Mitsutoshi Aoe <maoe@foldr.in>-synopsis:             A visualizer for GHC Profiling Reports-description:          A visualizer for GHC Profiling Reports+copyright:            Copyright (C) 2011 Mitsutoshi Aoe+synopsis:             A web-based visualizer for GHC Profiling Reports+description:          A web-based visualizer for GHC Profiling Reports category:             Development stability:            Experimental cabal-version:        >= 1.6 build-type:           Simple homepage:             https://github.com/maoe/tkyprof bug-reports:          https://github.com/maoe/tkyprof/issues+tested-with:          GHC == 7.0.2  data-files:           static/js/d3.layout.min.js                       static/js/d3.min.js@@ -19,6 +21,7 @@                       static/js/jquery.iframe-transport.js                       static/js/jquery.pjax.js                       static/js/jquery.ui.widget.js+                      static/js/tkyprof.js                       static/images/*.png                       config/favicon.png