gipeda 0.3.2.2 → 0.3.3.0
raw patch · 4 files changed
+173/−90 lines, 4 filesdep ~aeson
Dependency ranges changed: aeson
Files
- gipeda.cabal +2/−2
- site/js/gipeda.js +81/−41
- src/GraphReport.hs +38/−11
- src/ReportTypes.hs +52/−36
gipeda.cabal view
@@ -1,5 +1,5 @@ name: gipeda-version: 0.3.2.2+version: 0.3.3.0 category: Development synopsis: Git Performance Dashboard description:@@ -76,7 +76,7 @@ vector >= 0.10 && <0.12, cassava >= 0.4 && <0.5, yaml >= 0.8 && <0.9,- aeson >= 0.7 && <1.1,+ aeson >= 0.7 && <1.2, scientific >= 0.3 && <0.4, gitlib >= 3.1.0.2 && <3.2, gitlib-libgit2,
site/js/gipeda.js view
@@ -47,7 +47,15 @@ { regex: /^revision\/([a-f0-9]+)$/, viewData: function (match) { return { hash: match[1] }; }, download: function () {- return ['out/benchNames.json','out/reports/' + viewData.hash + '.json'];+ todo = ['out/benchNames.json','out/reports/' + viewData.hash + '.json'];+ if (data && data.revisions) {+ var rev = data.revisions[viewData.hash];+ if (rev && rev.summary && rev.summary.parents) {+ var parentHash = rev.summary.parents[0];+ todo.push('out/reports/' + parentHash + '.json');+ }+ }+ return todo; }, url: function (hash) { return "revision/" + hash; }, },@@ -105,13 +113,15 @@ viewChanged.dispatch(); } -viewChanged.add(function () {+function load_missing_data() { if (routes[view].download) { d = routes[view].download;- if ($.isFunction(d)) {d = d()}- d.forEach(function (url) { getJSON(url) });+ if ($.isFunction(d)) {d = d(); }+ d.forEach(function (url) { getJSON(url); }); }-});+}+viewChanged.add(load_missing_data);+dataChanged.add(load_missing_data); function handleHashChange(newHash) { parseRoute(newHash);@@ -343,7 +353,7 @@ if (benchmark_name_matches(s.match, name)) { $.extend(benchSettings, s); }- })+ }); return benchSettings; }@@ -351,12 +361,12 @@ // The following logic should be kept in sync with toResult in ReportTypes.hs function compareResults (res1, res2) {- if (!res1 && !res2) { return };+ if (!res1 && !res2) { return; } - name = res1? res1.name : res2.name;- s = setting_for(name);+ var name = res1? res1.name : res2.name;+ var s = setting_for(name); - res = {+ var res = { name: name, previous: res1 ? res1.value : null, value: res2 ? res2.value : null,@@ -368,9 +378,9 @@ if (res1 && res2){ if (s.type == "integral" || s.type == "float"){- if (res1.value == 0 && res2.value == 0) {+ if (res1.value === 0 && res2.value === 0) { res.change = "=";- } else if (res1.value == 0) {+ } else if (res1.value === 0) { res.change = "+ ∞"; res.changeType = "Improvement"; } else {@@ -418,26 +428,73 @@ return res; } +// A variant of compareResults, for when there is nothing to compare.+function singleResult (res) {+ if (!res) { return; }+ var name = res.name;+ var s = setting_for(name);++ return {+ name: name,+ previous: null,+ value: res.value,+ unit: s.unit,+ important: s.important,+ changeType: "Boring",+ change: "",+ };+}+ // Some views require the data to be prepared in ways that are // too complicated for the template, so lets do it here.-dataViewPrepare = {- 'revision': function (data, viewData) {- if (!data.benchGroups || !data.revisions) return {};- var hash = viewData.hash;- var rev = data.revisions[hash];- if (!rev) return {};- if (!rev.benchResults) return {}; - var groups = data.benchGroups.map(function (group) {+// revision and compare actually share a lot of logic, the difference is only+// where the starting commit is from+function calculate_groups(rev1, rev2) {+ if (!rev2) return {};+ if (!rev2.benchResults) return {};++ return data.benchGroups.map(function (group) { var benchmarks = group.groupMembers.map(function (bn) {- return rev.benchResults[bn]- }).filter(function (br) {return br});+ var r2 = rev2.benchResults[bn];+ if (rev1 && rev1.benchResults) {+ var r1 = rev1.benchResults[bn];+ return compareResults(r1,r2);+ } else {+ return singleResult(r2);+ }+ }).filter(function (br) {return br;}); return { groupName: group.groupName || "Benchmarks", benchResults: benchmarks, groupStats: groupStats(benchmarks), }; });+}++dataViewPrepare = {+ 'revision': function (data, viewData) {+ if (!data.benchGroups || !data.revisions) return {};+ var hash = viewData.hash;+ var rev = data.revisions[hash];++ if (!rev) return {};+ if (!rev.benchResults) return {};++ var groups;+ if (rev.summary.parents) {+ var parentHash = rev.summary.parents[0];+ var parentRev = data.revisions[parentHash];++ if (parentRev) {+ groups = calculate_groups(parentRev, rev);+ }+ }+ // Fallback (no parent, or parent not loaded yet)+ if (!groups) {+ groups = calculate_groups(null, rev);+ }+ return { rev : rev, groups : groups,@@ -449,30 +506,13 @@ var hash2 = viewData.hash2; var rev1 = data.revisions[hash1]; var rev2 = data.revisions[hash2];- if (!rev1) return {};- if (!rev1.benchResults) return {};- if (!rev2) return {};- if (!rev2.benchResults) return {};-- var groups = data.benchGroups.map(function (group) {- var benchmarks = group.groupMembers.map(function (bn) {- var r1 = rev1.benchResults[bn];- var r2 = rev2.benchResults[bn];- return compareResults(r1,r2);- }).filter(function (br) {return br});- return {- groupName: group.groupName || "Benchmarks",- benchResults: benchmarks,- groupStats: groupStats(benchmarks),- };- }); return { rev1 : rev1, rev2 : rev2,- groups : groups,+ groups : calculate_groups(rev1, rev2), }; },-}+}; function remember_from_to() { settings.compare.from = $('#compare-from').data('rev');
src/GraphReport.hs view
@@ -18,24 +18,51 @@ graphReportMain :: [String] -> IO () graphReportMain (bench:revs) = do settings <- S.readSettings "gipeda.yaml"+ let s = S.benchSettings settings bench - g <- forM revs $ \rev -> do- json <- BS.readFile (reportOf rev)- rep <- case eitherDecode json of- Left e -> fail e- Right rep -> return rep- case M.lookup bench (benchResults (rep !!! "revisions" !!! rev)) of+ g <- forM (withPrev revs) $ \(rev, prevM) -> do+ report <- readReport rev++ case M.lookup bench (benchResults report) of Nothing -> return Nothing- Just result -> return $ Just $ T.pack rev .= object+ Just result -> do+ changeType <- case prevM of+ Nothing -> return Boring+ Just prev -> do+ prevReport <- readReport prev+ case M.lookup bench (benchResults prevReport) of+ Nothing -> return Boring+ Just prevResult ->+ let comp = makeComparison s bench (value result) (Just (value prevResult))+ in return $ changeType comp++ return $ Just $ T.pack rev .= object [ "benchResults" .= object- [ T.pack bench .= benchResultToGraphPoint result ]+ [ T.pack bench .= GraphPoint+ { gpValue = value result+ , gpChangeType = changeType+ }+ ] ] let doc = object [ "revisions" .= object (catMaybes g) , "benchmarkSettings" .= object- [ T.pack bench .= toJSON (S.benchSettings settings bench) ]+ [ T.pack bench .= toJSON s ] ] BS.putStr (encode doc)- where- m !!! k = m M.! T.pack k++readReport :: Hash -> IO RevReport+readReport hash = do+ json <- BS.readFile (reportOf hash)+ case eitherDecode json of+ Left e -> fail e+ Right rep -> return $ rep !!! "revisions" !!! hash++(!!!) :: M.Map T.Text a -> String -> a+m !!! k = m M.! T.pack k++withPrev :: [a] -> [(a, Maybe a)]+withPrev (x:y:ys) = (x, Just y) : withPrev (y:ys)+withPrev [x] = [(x, Nothing)]+withPrev [] = []
src/ReportTypes.hs view
@@ -63,12 +63,18 @@ SummaryStats (a + a') (b + b') (c + c') -} +data Status = Built | Failed | Waiting+ deriving (Show, Generic)+instance ToJSON Status+instance FromJSON Status+ data Summary = Summary { hash :: Hash , gitDate :: Integer , gitSubject :: String , stats :: SummaryStats- , parents :: [String]+ , parents :: [Hash]+-- , status :: Status } deriving (Generic) instance ToJSON Summary@@ -111,9 +117,6 @@ data BenchResult = BenchResult { name :: String , value :: BenchValue- , previous :: Maybe BenchValue- , change :: String- , changeType :: ChangeType , unit :: String , important :: Bool }@@ -126,7 +129,17 @@ instance FromJSON BenchResult where parseJSON = genericParseJSON defaultOptions +data BenchComparison = BenchComparison+ { changeName :: String+ , change :: String+ , changeType :: ChangeType+ , changeImportant :: Bool+ }+ -- A smaller BenchResult+-- (This is a hack: BenchResult no longer carries a changeType. But it is convenient to+-- have that in the graph report, for the tallying for the graph summary. But all not very+-- satisfactory.) data GraphPoint = GraphPoint { gpValue :: BenchValue , gpChangeType :: ChangeType@@ -143,11 +156,6 @@ graphPointOptions = defaultOptions { fieldLabelModifier = fixup } where fixup ('g':'p':c:cs) = toLower c : cs -benchResultToGraphPoint (BenchResult {..}) = GraphPoint- { gpValue = value- , gpChangeType = changeType- }- invertChangeType :: ChangeType -> ChangeType invertChangeType Improvement = Regression invertChangeType Boring = Boring@@ -200,17 +208,22 @@ -- even if the user did not set the numberType correctly: explain s v1 v2 = explainFloat s (toFloat v1) (toFloat v2) -toResult :: S.BenchSettings -> String -> BenchValue -> Maybe BenchValue -> BenchResult-toResult s name value prev = BenchResult- { name = name- , value = value- , previous = prev- , change = change- , changeType = changeType- , unit = S.unit s+toResult :: S.BenchSettings -> String -> BenchValue -> BenchResult+toResult s name value = BenchResult+ { name = name+ , value = value+ , unit = S.unit s , important = S.important s }- where ++makeComparison :: S.BenchSettings -> String -> BenchValue -> Maybe BenchValue -> BenchComparison+makeComparison s name value prev = BenchComparison+ { changeName = name+ , change = change+ , changeType = changeType+ , changeImportant = S.important s+ }+ where (change, changeType') = case prev of Just p -> explain s p value@@ -218,22 +231,20 @@ changeType | S.smallerIsBetter s = invertChangeType changeType' | otherwise = changeType' -toSummaryStats :: [BenchResult] -> SummaryStats-toSummaryStats res = SummaryStats- { totalCount = length res+toSummaryStats :: [BenchComparison] -> SummaryStats+toSummaryStats comps = SummaryStats+ { totalCount = length comps , improvementCount = length- [ ()- | BenchResult { changeType = Improvement, important = True } <- res- ]+ [ () | comp <- importantComps , changeType comp == Improvement ] , regressionCount = length- [ ()- | BenchResult { changeType = Regression, important = True } <- res- ]+ [ () | comp <- importantComps , changeType comp == Regression ] , summaryDesc = andMore 5- [ name r ++ ": " ++ change r- | r <- res, important r, changeType r `elem` [Improvement, Regression]+ [ changeName comp ++ ": " ++ change comp+ | comp <- importantComps+ , changeType comp `elem` [Improvement, Regression] ] }+ where importantComps = filter changeImportant comps andMore :: Int -> [String] -> String andMore _ [] = "–"@@ -262,12 +273,12 @@ createBranchReport settings this other thisM otherM commitCount = BranchReport { branchHash = this , mergeBaseHash = other- , branchStats = toSummaryStats $ M.elems results+ , branchStats = toSummaryStats comparisons , commitCount = commitCount } where- results = M.fromList- [ (name, toResult s name value (M.lookup name otherM))+ comparisons =+ [ makeComparison s name value (M.lookup name otherM) | (name, value) <- M.toAscList thisM , let s = S.benchSettings settings name ]@@ -277,11 +288,11 @@ ResultMap -> ResultMap -> String -> String -> Integer -> RevReport-createReport settings this parents thisM parentM log subject date = RevReport +createReport settings this parents thisM parentM log subject date = RevReport { summary = Summary { hash = this , parents = parents- , stats = toSummaryStats $ M.elems results+ , stats = toSummaryStats comparisons , gitSubject = subject , gitDate = date }@@ -291,10 +302,15 @@ } where results = M.fromList- [ (name, toResult s name value (M.lookup name parentM))+ [ (name, toResult s name value) | (name, value) <- M.toAscList thisM , let s = S.benchSettings settings name ]+ comparisons =+ [ makeComparison s name value (M.lookup name parentM)+ | (name, value) <- M.toAscList thisM+ , let s = S.benchSettings settings name+ ] summarize :: RevReport -> Summary-summarize (RevReport {..}) = summary +summarize (RevReport {..}) = summary