mirror of
https://github.com/codedownio/time-ghc-modules.git
synced 2024-10-26 07:18:58 +03:00
Merge branch 'treemap'
This commit is contained in:
commit
02e62d08d9
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
@ -0,0 +1,13 @@
|
||||
# ChangeLog
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Added TreeMap visualizations.
|
||||
|
||||
## 1.0.1
|
||||
|
||||
* Be able to control dependency paths with environment variables so it's easier to package on Nixpkgs.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Initial release.
|
14
README.md
14
README.md
@ -58,7 +58,7 @@ stack build --ghc-options "-ddump-to-file -ddump-timings"
|
||||
|
||||
# Tips
|
||||
|
||||
The script will output its log messages to `stderr` and print the final report path to `stdout` (assuming it didn't exit with a failure). This makes it easy to use the output in scripts. For example:
|
||||
* The script will output its log messages to `stderr` and print the final report path to `stdout` (assuming it didn't exit with a failure). This makes it easy to use the output in scripts. For example:
|
||||
|
||||
``` shell
|
||||
# Build the report and open it in your browser
|
||||
@ -70,6 +70,18 @@ The script will output its log messages to `stderr` and print the final report p
|
||||
> cp $(/path/to/time-ghc-modules/time-ghc-modules) $MY_CI_ARTIFACTS_DIR/
|
||||
```
|
||||
|
||||
* You can also look at the timing of individual components, but doing e.g. `stack build some-component:lib`. But, make sure to clean up any old `.dump-timings` files from previous runs:
|
||||
|
||||
``` shell
|
||||
find . -name "*.dump-timings" | xargs rm
|
||||
```
|
||||
|
||||
* GHC's `-dumpdir` option can be used to consolidate the `.dump-timings` files, so they aren't left all over your source tree. For example:
|
||||
|
||||
``` shell
|
||||
stack build --ghc-options "-ddump-to-file -ddump-timings -dumpdir .ghcdump"
|
||||
```
|
||||
|
||||
# Compatibility
|
||||
|
||||
The flag `-ddump-timings` is available for `GHC >= 8.4.1`.
|
||||
|
22
dist/index.html
vendored
22
dist/index.html
vendored
File diff suppressed because one or more lines are too long
14386
package-lock.json
generated
14386
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -20,20 +20,35 @@
|
||||
},
|
||||
"homepage": "https://github.com/codedownio/time-ghc-modules#readme",
|
||||
"devDependencies": {
|
||||
"@types/d3-hierarchy": "^3.1.6",
|
||||
"@types/d3-interpolate": "^3.0.4",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"@types/d3-scale-chromatic": "^3.0.3",
|
||||
"@types/d3-selection": "^3.0.10",
|
||||
"@types/lodash": "^4.14.171",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"process": "^0.11.10",
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codedown/react-d3-treemap": "^1.0.31",
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"apexcharts": "^3.27.3",
|
||||
"d3-hierarchy": "^3.1.2",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-scale-chromatic": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"parcel": "^2.0.0-beta.2",
|
||||
"parcel": "^2.11.0",
|
||||
"react": "^17.0.2",
|
||||
"react-apexcharts": "^1.3.9",
|
||||
"react-dom": "^17.0.2",
|
||||
"tachyons": "^4.12.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"@parcel/transformer-css": {
|
||||
"errorRecovery": true
|
||||
}
|
||||
}
|
||||
|
27
src/TreeMap.css
Normal file
27
src/TreeMap.css
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
.TreeMap__breadcrumb {
|
||||
padding-bottom: 0.5em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.TreeMap__breadcrumb > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.TreeMap__breadcrumbItem {
|
||||
font-size: 1em !important;
|
||||
}
|
||||
|
||||
/* a.TreeMap__breadcrumbItem[style*="cursor:pointer"] { */
|
||||
.TreeMap__breadcrumb div:not(:last-child) a.TreeMap__breadcrumbItem {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.AppTreeMap__node {
|
||||
rx: 0;
|
||||
}
|
||||
|
||||
.AppTreeMap__svg {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
130
src/TreeMap.tsx
130
src/TreeMap.tsx
@ -1,10 +1,15 @@
|
||||
|
||||
import {size} from "lodash";
|
||||
import {Treemap as RVTreeMap} from "react-vis";
|
||||
import { scaleSequential } from "d3-scale";
|
||||
import * as chromatic from "d3-scale-chromatic";
|
||||
import { debounce } from "lodash";
|
||||
import { CSSProperties, useEffect, useMemo, useRef, useState} from "react";
|
||||
|
||||
import "react-vis/dist/style.css";
|
||||
import buildNestedData, {removeEmptyNodes} from "./TreeMap/BuildData";
|
||||
import {formatBytes, formatTime} from "./Util";
|
||||
|
||||
// import {formatBytes, formatTime} from "./Util";
|
||||
// import SimpleD3TreeMap from "./D3TreeMap";
|
||||
|
||||
import D3TreeMap, { ColorModel, NumberOfChildrenPlacement } from "@codedown/react-d3-treemap";
|
||||
|
||||
|
||||
interface Props {
|
||||
@ -12,53 +17,88 @@ interface Props {
|
||||
data: ModuleData;
|
||||
}
|
||||
|
||||
const wrapperStyle: CSSProperties = {
|
||||
width: "100%",
|
||||
height: "80vh",
|
||||
};
|
||||
|
||||
const paddingPx = 0;
|
||||
|
||||
const svgStyle: CSSProperties = {
|
||||
marginLeft: "-" + paddingPx + "px",
|
||||
}
|
||||
|
||||
export default function TreeMap({aggregate, data}: Props) {
|
||||
const modulesList = aggregate === "time" ? data.modulesByTime : data.modulesByAlloc;
|
||||
const numModules = size(modulesList);
|
||||
|
||||
console.log("modulesList", modulesList);
|
||||
const nestedData: Tree<TreeNode> = useMemo(() => {
|
||||
const tree = buildNestedData(aggregate, modulesList);
|
||||
return removeEmptyNodes(tree);
|
||||
}, [aggregate, modulesList]);
|
||||
|
||||
const myData = {
|
||||
"title": "analytics",
|
||||
"color": "#12939A",
|
||||
"children": [
|
||||
{
|
||||
"title": "cluster",
|
||||
"children": [
|
||||
{"title": "AgglomerativeCluster", "color": "#12939A", "size": 3938},
|
||||
{"title": "CommunityStructure", "color": "#12939A", "size": 3812},
|
||||
{"title": "HierarchicalCluster", "color": "#12939A", "size": 6714},
|
||||
{"title": "MergeEdge", "color": "#12939A", "size": 743}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "graph",
|
||||
"children": [
|
||||
{"title": "BetweennessCentrality", "color": "#12939A", "size": 3534},
|
||||
{"title": "LinkDistance", "color": "#12939A", "size": 5731},
|
||||
{"title": "MaxFlowMinCut", "color": "#12939A", "size": 7840},
|
||||
{"title": "ShortestPaths", "color": "#12939A", "size": 5914},
|
||||
{"title": "SpanningTree", "color": "#12939A", "size": 3416}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "optimization",
|
||||
"children": [
|
||||
{"title": "AspectRatioBanker", "color": "#12939A", "size": 7074}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
const ref = useRef(null);
|
||||
const [dimensions, setDimensions] = useState<{ height: number, width: number } | null>(null);
|
||||
const elementObserver = useMemo(() => {
|
||||
return new ResizeObserver(() => {
|
||||
debounce(() => {
|
||||
if (!ref.current) return;
|
||||
setDimensions({
|
||||
height: ref.current.clientHeight,
|
||||
width: ref.current.clientWidth
|
||||
});
|
||||
}, 10)();
|
||||
});
|
||||
}, [ref.current]);
|
||||
useEffect(() => {
|
||||
if (!ref) return;
|
||||
const element = ref.current;
|
||||
|
||||
// Modes: squarify, resquarify, slice, dice, slicedice, binary, circlePack, partition, partition-pivot
|
||||
elementObserver.observe(element);
|
||||
return () => {
|
||||
elementObserver.unobserve(element);
|
||||
};
|
||||
}, [ref.current, elementObserver]);
|
||||
|
||||
return (
|
||||
<RVTreeMap
|
||||
title={'My New Treemap'}
|
||||
width={300}
|
||||
height={300}
|
||||
data={myData}
|
||||
mode="binary"
|
||||
/>
|
||||
<div ref={ref}
|
||||
style={wrapperStyle}>
|
||||
|
||||
{dimensions &&
|
||||
<D3TreeMap<Tree<TreeNode>>
|
||||
key={aggregate}
|
||||
id="myTreeMap"
|
||||
width={dimensions.width + paddingPx}
|
||||
svgStyle={svgStyle}
|
||||
height={dimensions.height}
|
||||
data={nestedData}
|
||||
valueUnit=""
|
||||
levelsToDisplay={2}
|
||||
paddingInner={paddingPx}
|
||||
paddingOuter={constZero}
|
||||
paddingTop={constZero}
|
||||
nodeClassName="AppTreeMap__node"
|
||||
nodeStyle={{
|
||||
fontSize: 12,
|
||||
paddingTop: 2,
|
||||
paddingLeft: 5,
|
||||
paddingRight: 5,
|
||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||
}}
|
||||
hideNumberOfChildren={true}
|
||||
customD3ColorScale={scaleSequential(
|
||||
chromatic.interpolateSpectral
|
||||
)}
|
||||
colorModel={ColorModel.OneEachChildren}
|
||||
darkNodeBorderColor="silver"
|
||||
darkNodeTextColor="white"
|
||||
lightNodeBorderColor="brown"
|
||||
lightNodeTextColor="brown"
|
||||
valueFn={aggregate === "time" ? formatTime : formatBytes}
|
||||
breadCrumbClassName="ba b--silver pa1 mv1"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const constZero = () => 0;
|
||||
|
69
src/TreeMap/BuildData.ts
Normal file
69
src/TreeMap/BuildData.ts
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
import {find, forEach} from "lodash";
|
||||
|
||||
|
||||
type ModuleItem = {module: string, alloc: number} | {module: string, time: number};
|
||||
|
||||
export default function buildNestedData(
|
||||
aggregate: Aggregate,
|
||||
modulesList: Array<ModuleItem>
|
||||
) {
|
||||
const ret: Tree<TreeNode> = {
|
||||
name: "Top-level",
|
||||
value: 0,
|
||||
part: "",
|
||||
children: [],
|
||||
};
|
||||
|
||||
for (const m of modulesList) {
|
||||
addModuleToTree(aggregate, m, ret);
|
||||
}
|
||||
forEach(ret.children, (x) => {
|
||||
ret.value += x.value;
|
||||
});
|
||||
|
||||
console.log("Built nestedData", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
function addModuleToTree(aggregate: Aggregate, m: ModuleItem, ret: Tree<TreeNode>) {
|
||||
let current = ret;
|
||||
let soFar = "";
|
||||
const parts = m.module.split(".");
|
||||
|
||||
for (let i = 0; i < parts.length; i += 1) {
|
||||
const part = parts[i];
|
||||
soFar += (i === 0 ? part : "." + part);
|
||||
|
||||
let item = find(current.children, (x) => x.part === part);
|
||||
if (!item) {
|
||||
item = {
|
||||
name: soFar,
|
||||
part,
|
||||
value: 0,
|
||||
children: [],
|
||||
};
|
||||
current.children.push(item);
|
||||
}
|
||||
current = item;
|
||||
|
||||
// Add the value to the bottommost node
|
||||
if (i === parts.length - 1) {
|
||||
current.value += (aggregate === "time" ? m["time"]: m["alloc"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function removeEmptyNodes(tree: Tree<TreeNode>): Tree<TreeNode> {
|
||||
for (let i = 0; i < tree.children.length; i += 1) {
|
||||
tree.children[i] = removeEmptyNodes(tree.children[i]);
|
||||
}
|
||||
|
||||
if (tree.value === 0 && tree.children.length === 1) {
|
||||
return tree.children[0];
|
||||
} else {
|
||||
return tree;
|
||||
}
|
||||
}
|
@ -11,14 +11,16 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "tachyons";
|
||||
@import "../node_modules/tachyons/css/tachyons.css";
|
||||
@import "../node_modules/@codedown/react-d3-treemap/dist/react.d3.treemap.css";
|
||||
@import "./TreeMap.css";
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
<script type="module">
|
||||
import "./index.tsx";
|
||||
</script>
|
||||
</body>
|
||||
|
2475
src/index_test.html
Normal file
2475
src/index_test.html
Normal file
File diff suppressed because it is too large
Load Diff
12
src/types.d.ts
vendored
12
src/types.d.ts
vendored
@ -17,3 +17,15 @@ interface ModuleData {
|
||||
modules: Array<{module: string;}>;
|
||||
data: Array<Event>;
|
||||
}
|
||||
|
||||
type Tree<D> = D & {
|
||||
children?: Tree<D>[];
|
||||
id?: string;
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
name: string;
|
||||
part: string;
|
||||
color?: string;
|
||||
value: number;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
|
Loading…
Reference in New Issue
Block a user