Removed Mojolicious::Lite as a prerequisite as it was stoping people installing on 5.8
[p5sagit/Devel-Size.git] / bin / sizeme_graph.pl
CommitLineData
b2fc39a5 1#!/usr/bin/env perl
2
3d2b08ed 3BEGIN {
4 die qq{$0 requires Mojolicious::Lite, which isn't installed.
5
6 Currently requires Mojolicious::Lite which isn't available for perl 5.8.
7 If this affects you you can run Devel::SizeMe with your normal perl and
8 run sizeme_graph.pl with a different perl, perhaps on a different machine.
9 \n}
10 unless eval "require Mojolicious::Lite";
11}
12
591aba53 13=head1 NAME
b2fc39a5 14
591aba53 15sizeme_graph.pl - web server providing an interactive treemap of Devel::SizeMe data
e8f4c506 16
591aba53 17=head1 SYNOPSIS
18
19 sizeme_graph.pl --db sizeme.db daemon
20
21 sizeme_graph.pl daemon # same as above
22
23Then open a web browser on http://127.0.0.1:3000
24
25=head1 DESCRIPTION
26
27Reads a database created by sizeme_store.pl and provides a web interface with
28an interactive treemap of the data.
72eb1fbb 29
3d2b08ed 30Currently requires Mojolicious::Lite which isn't available for perl 5.8.
31If this affects you you can run Devel::SizeMe with your normal perl and
32run sizeme_graph.pl with a different perl, perhaps on a different machine.
33
591aba53 34=head2 TODO
d3b8a135 35
591aba53 36Current implementation is all very alpha and rather hackish.
d3b8a135 37
591aba53 38Split out the db and tree code into a separate module.
d3b8a135 39
591aba53 40Use a history management library so the back button works and we can have
41links to specific nodes.
7aa9284e 42
591aba53 43Better tool-tip and/or add a scrollable information area below the treemap
44that could contain details and links.
d3b8a135 45
591aba53 46Make the treemap resize to fit the browser window (as NYTProf does).
d3b8a135 47
591aba53 48Protect against nodes with thousands of children
49 perhaps replace all with single merged child that has no children itself
50 but just a warning as a title.
51
52Implement other visualizations, such as a space-tree
53http://thejit.org/static/v20/Jit/Examples/Spacetree/example2.html
7aa9284e 54
55=cut
56
591aba53 57use strict;
58use warnings;
59
60use Mojolicious::Lite;
61use JSON::XS;
62use Getopt::Long;
63use Storable qw(dclone);
64use Devel::Dwarn;
65use Devel::SizeMe::Graph;
66
67require ORLite;
68
72eb1fbb 69my $j = JSON::XS->new;
70
e8f4c506 71GetOptions(
72eb1fbb 72 'db=s' => \(my $opt_db = 'sizeme.db'),
0a00007b 73 'showid!' => \my $opt_showid,
945e07d7 74 'debug!' => \my $opt_debug,
e8f4c506 75) or exit 1;
b2fc39a5 76
72eb1fbb 77die "Can't open $opt_db: $!\n" unless -r $opt_db;
78warn "Reading $opt_db\n";
945e07d7 79
d3b8a135 80# XXX currently uses ORLite but doesn't actually make use of it in any useful way
72eb1fbb 81ORLite->import({
82 file => $opt_db,
0a00007b 83 package => "SizeMe",
b2fc39a5 84 readonly => 1,
72eb1fbb 85 #user_version => 1, # XXX
86 #unicode => 1, # XXX
87});
b2fc39a5 88
e610a166 89my $static_dir = $INC{'Devel/SizeMe/Graph.pm'} or die 'panic';
90$static_dir =~ s:\.pm$:/static:;
91die "panic $static_dir" unless -d $static_dir;
92push @{ app->static->paths}, $static_dir;
93
f60f09e5 94
b2fc39a5 95# Documentation browser under "/perldoc"
96plugin 'PODRenderer';
97
98get '/' => sub {
99 my $self = shift;
100 $self->render('index');
101};
102
d3b8a135 103
104# /jit_tree are AJAX requests from the treemap visualization
b2fc39a5 105get '/jit_tree/:id/:depth' => sub {
106 my $self = shift;
e8f4c506 107
b2fc39a5 108 my $id = $self->stash('id');
109 my $depth = $self->stash('depth');
ee6e37bf 110
d3b8a135 111 # hack, would be best done on the client side
ee6e37bf 112 my $logarea = (defined $self->param('logarea'))
113 ? $self->param('logarea')
114 : Mojo::URL->new($self->req->headers->referrer)->query->param('logarea');
115
875c1073 116 my $node_tree = _fetch_node_tree($id, $depth);
117 my $jit_tree = _transform_node_tree($node_tree, sub {
118 my ($node) = @_;
119 my $children = delete $node->{children}; # XXX edits the src tree
e8f4c506 120 my $area = $node->{self_size}+$node->{kids_size};
945e07d7 121 $node->{'$area'} = ($logarea) ? log($area) : $area; # XXX move to jit js
bb66f8a1 122 my $jit_node = {
123 id => $node->{id},
0a00007b 124 name => ($node->{title} || $node->{name}).($opt_showid ? " #$node->{id}" : ""),
bb66f8a1 125 data => $node,
126 };
127 $jit_node->{children} = $children if $children;
128 return $jit_node;
b2fc39a5 129 });
ee6e37bf 130
d3b8a135 131 if(1){ # debug
ee6e37bf 132 use Devel::Dwarn;
133 use Data::Dump qw(pp);
134 local $jit_tree->{children};
135 pp(dclone($jit_tree)); # dclone to avoid stringification
136 }
137
b2fc39a5 138 $self->render_json($jit_tree);
139};
140
d3b8a135 141
875c1073 142sub _fetch_node_tree {
143 my ($id, $depth) = @_;
d3b8a135 144
0a00007b 145 warn "#$id fetching\n"
146 if $opt_debug;
147 my $node = SizeMe->selectrow_hashref("select * from node where id = ?", undef, $id)
d3b8a135 148 or die "Node '$id' not found"; # shouldn't die
945e07d7 149 $node->{$_} += 0 for (qw(child_count kids_node_count kids_size self_size));
e78b28ca 150 $node->{leaves} = $j->decode(delete $node->{leaves_json});
e8f4c506 151 $node->{attr} = $j->decode(delete $node->{attr_json});
47a0d085 152 $node->{name} .= "->" if $node->{type} == 2 && $node->{name};
e78b28ca 153
5a78486c 154 if ($node->{child_ids}) {
155 my @child_ids = split /,/, $node->{child_ids};
875c1073 156 my $children;
d3b8a135 157
158 # if this node has only one child then we merge that child into this node
159 # this makes the treemap more usable
ee6e37bf 160 if (@child_ids == 1
0a00007b 161 # && $node->{type} == 2 # only collapse links XXX
ee6e37bf 162 ) {
0a00007b 163 warn "#$id fetch only child $child_ids[0]\n"
164 if $opt_debug;
5a78486c 165 my $child = _fetch_node_tree($child_ids[0], $depth); # same depth
f9d8678b 166 # merge node into child
5a78486c 167 # XXX id, depth, parent_id
0a00007b 168 warn "Merged $node->{name} (#$node->{id} d$node->{depth}) with only child $child->{name} #$child->{id}\n"
945e07d7 169 if $opt_debug;
e74c1998 170 $child->{name} = "$node->{name} $child->{name}";
f9d8678b 171 $child->{$_} += $node->{$_} for (qw(self_size));
5a78486c 172 $child->{$_} = $node->{$_} for (qw(parent_id));
e8f4c506 173
e74c1998 174 $child->{title} = join " ", grep { defined && length } $child->{title}, $node->{title};
945e07d7 175 #warn "Titled $child->{title}" if $child->{title};
e8f4c506 176
d3b8a135 177 # somewhat hackish attribute merging
e8f4c506 178 for my $attr_type (keys %{ $node->{attr} }) {
179 my $src = $node->{attr}{$attr_type};
180 if (ref $src eq 'HASH') { # eg NPattr_NAME: {attr}{1}{$name} = $value
181 my $dst = $child->{attr}{$attr_type} ||= {};
182 for my $k (keys %$src) {
183 warn "Node $child->{id} attr $attr_type:$k=$dst->{$k} overwritten by $src->{$k}\n"
184 if defined $dst->{$k};
185 $dst->{$k} = $src->{$k};
186 }
187 }
47a0d085 188 elsif (ref $src eq 'ARRAY') { # eg NPattr_PADNAME: {attr}{2}[$val] = $name
e8f4c506 189 my $dst = $child->{attr}{$attr_type} ||= [];
190 my $idx = @$src;
191 while (--$idx >= 0) {
192 warn "Node $child->{id} attr $attr_type:$idx=$dst->[$idx] overwritten by $src->[$idx]\n"
193 if defined $dst->[$idx];
194 $dst->[$idx] = $src->[$idx];
195 }
196 }
47a0d085 197 else { # assume scalar
198 warn "Node $child->{id} attr $attr_type=$child->{attr}{$attr_type} overwritten by $src\n"
199 if exists $child->{attr}{$attr_type};
200 $child->{attr}{$attr_type} = $src;
201 }
e8f4c506 202 }
203
e78b28ca 204 $child->{leaves}{$_} += $node->{leaves}{$_}
205 for keys %{ $node->{leaves} };
5a78486c 206
207 $child->{_ids_merged} .= ",$node->{id}";
208 my @child_ids = split /,/, $node->{child_ids};
209 $child->{child_count} = @child_ids;
210
d3b8a135 211 $node = $child; # use the merged child as this node
875c1073 212 }
0a00007b 213 if ($depth) { # recurse to required depth
5a78486c 214 $children = [ map { _fetch_node_tree($_, $depth-1) } @child_ids ];
f9d8678b 215 $node->{children} = $children;
216 $node->{child_count} = @$children;
875c1073 217 }
b2fc39a5 218 }
b2fc39a5 219 return $node;
220}
221
d3b8a135 222
f9d8678b 223sub _transform_node_tree { # recurse depth first
875c1073 224 my ($node, $transform) = @_;
225 if (my $children = $node->{children}) {
226 $_ = _transform_node_tree($_, $transform) for @$children;
227 }
228 return $transform->($node);
229}
230
b2fc39a5 231
232app->start;
d3b8a135 233
3d2b08ed 234{ # just to reserve the namespace for future use
235 package Devel::SizeMe::Graph;
236 1;
237}
238
b2fc39a5 239__DATA__
240@@ index.html.ep
241% layout 'default';
242% title 'Welcome';
243Welcome to the Mojolicious real-time web framework!
244
245@@ layouts/default.html.ep
246<!DOCTYPE html>
247<head>
248<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
bb66f8a1 249<title>Perl Memory Treemap</title>
b2fc39a5 250
251<!-- CSS Files -->
252<link type="text/css" href="css/base.css" rel="stylesheet" />
253<link type="text/css" href="css/Treemap.css" rel="stylesheet" />
254
255<!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
256
257<!-- JIT Library File -->
21058011 258<script language="javascript" type="text/javascript" src="jit-yc.js"></script>
2a627489 259<script language="javascript" type="text/javascript" src="jquery-1.8.1-min.js"></script>
bb66f8a1 260<script language="javascript" type="text/javascript" src="sprintf.js"></script>
21058011 261<script language="javascript" type="text/javascript" src="treemap.js"></script>
b2fc39a5 262</head>
263
264<body onload="init();">
72eb1fbb 265
b2fc39a5 266<div id="container">
267
268<div id="left-container">
269
b2fc39a5 270<div class="text">
72eb1fbb 271<h4> Perl Memory TreeMap </h4>
47a0d085 272 Click on a node to zoom in.<br /><br />
b2fc39a5 273</div>
274
275<a id="back" href="#" class="theme button white">Go to Parent</a>
e8f4c506 276<br />
277<form name=params>
278<label for="logarea">&nbsp;Logarithmic scale
279<input type=checkbox id="logarea" name="logarea">
280</form>
281
b2fc39a5 282</div>
283
284<div id="center-container">
72eb1fbb 285 <div id="infovis"></div>
b2fc39a5 286</div>
287
288<div id="right-container">
72eb1fbb 289 <div id="inner-details"></div>
b2fc39a5 290</div>
291
292<div id="log"></div>
293</div>
294</body>
295</html>