Commit 06c3b581 authored by James Long's avatar James Long Committed by Commit Bot

Set framework for visualizing Android dependencies

Rough code, mostly copied from wnwen's visualization, as a framework for
future visualization work. It is forecast that this code will be
restructured heavily in the future, hence the lack of more thorough documentation.

npm is used for this project, please read the README on how to run
locally. The dependencies so far are:
- ESLint
- d3 (however, as we continue to identify what is required, I plan to
break this up so we only import the v4 microlibraries we actually need)

The page is currently served the JSON file via a small Python script.
This is easy to use for development, but will hopefully be replaced with
either a user file upload or being served from somewhere else.

Bug: 1093962
Change-Id: I8d35ae2cde64581ff7d22fb06a29cd850e42cc86
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2242171
Commit-Queue: James Long <yjlong@google.com>
Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Reviewed-by: default avatarHenrique Nakashima <hnakashima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#777955}
parent 18759308
{
"env": {
"browser": true,
"es2020": true
},
"extends": [
"google"
],
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"require-jsdoc": ["warn", {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true,
"ArrowFunctionExpression": true,
"FunctionExpression": true
}
}]
}
}
# Chrome Android Dependency Analysis Visualization
## Development Setup
### Shell variables
This setup assumes Chromium is in a `cr` directory (`~/cr/src/...`). To make setup easier, you can modify and export the following variables:
```
export DEP_ANALYSIS_DIR=~/cr/src/tools/android/dependency_analysis
export DEP_ANALYSIS_JAR=~/cr/src/out/Default/obj/chrome/android/chrome_java__process_prebuilt.desugar.jar
```
### Generate JSON
See `../README.md` for instructions on using `generate_json_dependency_graph.py`, then generate a graph file in this directory (`js/json_graph.txt`) with that exact name:
```
cd $DEP_ANALYSIS_DIR
./generate_json_dependency_graph.py --target $DEP_ANALYSIS_JAR --output js/json_graph.txt
```
**The following instructions assume you are in the `dependency_analysis/js` = `$DEP_ANALYSIS_DIR/js` directory.**
### Install dependencies
You will need to install `npm` if it is not already installed (check with `npm -v`), either [from the site](https://www.npmjs.com/get-npm) or via [nvm](https://github.com/nvm-sh/nvm#about) (Node Version Manager).
To install dependencies:
```
npm install
```
### (TEMP) Run visualization
To run the (highly temporary) Python server to serve the JSON at `localhost:8888/json_graph.txt` :
```
python3 -m http.server 8888
```
The visualization will make requests to this server for the JSON graph on load.
**To view the visualization, open `localhost:8888/index.html`.**
### Miscellaneous
To run [ESLint](https://eslint.org/) on the JS (and fix fixable errors) using [npx](https://www.npmjs.com/package/npx) (bundled with npm):
```
npx eslint --fix *.js
```
/* Copyright 2020 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
.graph-edges line {
stroke: #999;
stroke-opacity: 0.6;
}
.graph-nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
.graph-labels text {
font-family: sans-serif;
font-size: 12px;
}
circle.dragging {
stroke: #000;
stroke-width: 3;
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="./index.css"></link>
<script type="text/javascript" src="./node_modules/d3/dist/d3.min.js"></script>
</head>
<body>
<svg width="960" height="600"></svg>
<script src="./index.js"></script>
</body>
</html>
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* TODO(yjlong): Compute the "shortest disambiguated name" in Python and include
* it in the JSON so we do not need to do this calculation.
* @param {number} name The full package name to shorten.
* @return {number} The shortened package name.
*/
const shortName = (name) => name.substring(name.lastIndexOf('.') + 1);
/*
* Transforms a graph JSON generated by Python scripts
* (generate_json_dependency_graph.py) into a working format for d3.
* TODO(yjlong): Figure out if we want to typecheck the graph.
*/
function parseNodesAndEdgesFromJson(graph) {
const nodes = graph.nodes.map((node) => ({
...node,
id: node.name,
short_name: shortName(node.name),
}));
const edges = graph.edges.map((edge) => ({
...edge,
id: `${edge.begin}>${edge.end}`,
// The names source/target are needed for d3-force links.
source: edge.begin,
target: edge.end,
}));
return [nodes, edges];
}
function renderJsonGraph(data) {
const [jsonNodes, jsonEdges] = parseNodesAndEdgesFromJson(data.package_graph);
const svg = d3.select('svg');
// TODO(yjlong): SVG should be resizable & these values updated.
const width = +svg.attr('width');
const height = +svg.attr('height');
const simulation = d3.forceSimulation()
.nodes(jsonNodes)
.alphaMin(0.1) // Stop the simulation faster than default (0.001).
.force('chargeForce', d3.forceManyBody().strength(-300))
.force('centerForce', d3.forceCenter(width / 2, height / 2))
.force('links', d3.forceLink(jsonEdges).id((d) => d.id));
const edgeGroup = svg.append('g')
.classed('graph-edges', true)
.attr('stroke-width', 1);
const edges = edgeGroup.selectAll('line')
.data(jsonEdges)
.join((enter) => enter.append('line'));
const dragStarted = (d, i, nodes) => {
d3.event.sourceEvent.stopPropagation();
d3.select(nodes[i]).classed('dragging', false);
d.fx = null;
d.fy = null;
};
const dragged = (d, i, nodes) => {
simulation.alpha(0.3).restart(); // Reheat the simulation on drag.
d3.select(nodes[i]).classed('dragging', true);
// Fix the node's position after it has been dragged.
d.fx = d3.event.x;
d.fy = d3.event.y;
};
const nodeGroup = svg.append('g')
.classed('graph-nodes', true)
.attr('fill', 'red');
const nodes = nodeGroup.selectAll('circle')
.data(jsonNodes)
.join((enter) => enter.append('circle'))
.attr('r', 5)
.call(d3.drag()
.on('start', dragStarted)
.on('drag', dragged));
const labelGroup = svg.append('g')
.classed('graph-labels', true);
const labels = labelGroup.selectAll('text')
.data(jsonNodes)
.join((enter) => enter.append('text'))
.attr('dx', 12)
.attr('dy', '.35em')
.text((d) => d.short_name);
// The simulation updates position variables in the JSON, it's up to us
// to update the visualization to match on each tick.
const tickActions = () => {
nodes
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y);
edges
.attr('x1', (d) => d.source.x)
.attr('y1', (d) => d.source.y)
.attr('x2', (d) => d.target.x)
.attr('y2', (d) => d.target.y);
labels
.attr('x', (d) => d.x)
.attr('y', (d) => d.y);
};
simulation.on('tick', tickActions);
}
// TODO(yjlong): Currently we take JSON served by a Python server running on
// the side. Replace this with a user upload or pull from some other source.
document.addEventListener('DOMContentLoaded', () => {
d3.json('http://localhost:8888/json_graph.txt')
.then((data) => renderJsonGraph(data));
});
{
"name": "dependency-analysis",
"version": "1.0.0",
"dependencies": {
"d3": "^5.16.0"
},
"devDependencies": {
"eslint": "^7.2.0",
"eslint-config-google": "^0.14.0"
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment