tkyprof 0.0.5.2 → 0.0.6
raw patch · 13 files changed
+421/−149 lines, 13 files
Files
- Handler/Home.hs +1/−1
- Handler/Reports.hs +41/−21
- Handler/Reports/Helpers.hs +4/−7
- TKYProf.hs +5/−4
- config/routes +8/−6
- hamlet/home.hamlet +17/−19
- hamlet/reports-id.hamlet +3/−5
- hamlet/reports.hamlet +8/−9
- julius/home.julius +3/−2
- julius/reports-id.julius +234/−62
- lucius/reports-id.lucius +4/−10
- static/js/tkyprof.js +87/−0
- tkyprof.cabal +6/−3
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