Commit | Line | Data |
b2fc39a5 |
1 | #!/usr/bin/env perl |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
6 | use Mojolicious::Lite; |
d3b8a135 |
7 | use JSON::XS; |
e8f4c506 |
8 | use Getopt::Long; |
945e07d7 |
9 | use Storable qw(dclone); |
ee6e37bf |
10 | use Devel::Dwarn; |
e8f4c506 |
11 | |
d3b8a135 |
12 | =pod NOTE |
13 | |
14 | Needs to be run from the static/. directory. |
15 | For example: |
16 | |
17 | ./dmemview.pl daemon |
18 | |
7aa9284e |
19 | =pod TODO |
20 | |
d3b8a135 |
21 | Move all the static files into the DATA section of ths script so the script |
22 | is entirely self-contained and doesn't need any static files installed. |
23 | Or, work out how to install the static files and reference them from the script. |
24 | |
25 | Remove ORLite (for now) |
26 | |
27 | Make the treemap resize to fit the browser window (as NYTProf does). |
28 | |
7aa9284e |
29 | Protect against nodes with thousands of children |
d3b8a135 |
30 | perhaps replace all with single merged child that has no children itself |
31 | but just a warning as a title. |
7aa9284e |
32 | |
33 | =cut |
34 | |
e8f4c506 |
35 | GetOptions( |
d3b8a135 |
36 | 'db=s' => \(my $opt_db = '../dmemtree.db'), |
945e07d7 |
37 | 'debug!' => \my $opt_debug, |
e8f4c506 |
38 | ) or exit 1; |
b2fc39a5 |
39 | |
945e07d7 |
40 | #warn "Reading from $opt_db\n"; |
41 | |
d3b8a135 |
42 | # XXX currently uses ORLite but doesn't actually make use of it in any useful way |
43 | # should be removed and replaced with plain DBI till we have an obvious need for it |
b2fc39a5 |
44 | use ORLite { |
d3b8a135 |
45 | file => '../dmemtree.db', |
b2fc39a5 |
46 | package => "MemView", |
47 | #user_version => 1, |
48 | readonly => 1, |
49 | #unicode => 1, |
50 | }; |
51 | |
f60f09e5 |
52 | my $j = JSON::XS->new; |
53 | |
b2fc39a5 |
54 | # Documentation browser under "/perldoc" |
55 | plugin 'PODRenderer'; |
56 | |
57 | get '/' => sub { |
58 | my $self = shift; |
59 | $self->render('index'); |
60 | }; |
61 | |
d3b8a135 |
62 | |
63 | # /jit_tree are AJAX requests from the treemap visualization |
b2fc39a5 |
64 | get '/jit_tree/:id/:depth' => sub { |
65 | my $self = shift; |
e8f4c506 |
66 | |
b2fc39a5 |
67 | my $id = $self->stash('id'); |
68 | my $depth = $self->stash('depth'); |
ee6e37bf |
69 | |
d3b8a135 |
70 | # hack, would be best done on the client side |
ee6e37bf |
71 | my $logarea = (defined $self->param('logarea')) |
72 | ? $self->param('logarea') |
73 | : Mojo::URL->new($self->req->headers->referrer)->query->param('logarea'); |
74 | |
875c1073 |
75 | my $node_tree = _fetch_node_tree($id, $depth); |
76 | my $jit_tree = _transform_node_tree($node_tree, sub { |
77 | my ($node) = @_; |
78 | my $children = delete $node->{children}; # XXX edits the src tree |
e8f4c506 |
79 | my $area = $node->{self_size}+$node->{kids_size}; |
945e07d7 |
80 | $node->{'$area'} = ($logarea) ? log($area) : $area; # XXX move to jit js |
bb66f8a1 |
81 | my $jit_node = { |
82 | id => $node->{id}, |
e8f4c506 |
83 | name => $node->{title} || $node->{name}, |
bb66f8a1 |
84 | data => $node, |
85 | }; |
86 | $jit_node->{children} = $children if $children; |
87 | return $jit_node; |
b2fc39a5 |
88 | }); |
ee6e37bf |
89 | |
d3b8a135 |
90 | if(1){ # debug |
ee6e37bf |
91 | use Devel::Dwarn; |
92 | use Data::Dump qw(pp); |
93 | local $jit_tree->{children}; |
94 | pp(dclone($jit_tree)); # dclone to avoid stringification |
95 | } |
96 | |
b2fc39a5 |
97 | $self->render_json($jit_tree); |
98 | }; |
99 | |
d3b8a135 |
100 | |
875c1073 |
101 | sub _fetch_node_tree { |
102 | my ($id, $depth) = @_; |
d3b8a135 |
103 | |
875c1073 |
104 | my $node = MemView->selectrow_hashref("select * from node where id = ?", undef, $id) |
d3b8a135 |
105 | or die "Node '$id' not found"; # shouldn't die |
945e07d7 |
106 | $node->{$_} += 0 for (qw(child_count kids_node_count kids_size self_size)); |
e78b28ca |
107 | $node->{leaves} = $j->decode(delete $node->{leaves_json}); |
e8f4c506 |
108 | $node->{attr} = $j->decode(delete $node->{attr_json}); |
47a0d085 |
109 | $node->{name} .= "->" if $node->{type} == 2 && $node->{name}; |
e78b28ca |
110 | |
5a78486c |
111 | if ($node->{child_ids}) { |
112 | my @child_ids = split /,/, $node->{child_ids}; |
875c1073 |
113 | my $children; |
d3b8a135 |
114 | |
115 | # if this node has only one child then we merge that child into this node |
116 | # this makes the treemap more usable |
ee6e37bf |
117 | if (@child_ids == 1 |
d3b8a135 |
118 | && $node->{type} == 2 # currently we only collapse links XXX |
ee6e37bf |
119 | ) { |
5a78486c |
120 | my $child = _fetch_node_tree($child_ids[0], $depth); # same depth |
f9d8678b |
121 | # merge node into child |
5a78486c |
122 | # XXX id, depth, parent_id |
945e07d7 |
123 | warn "Merged $node->{name} #$node->{id} with only child $child->{name} #$child->{id}\n" |
124 | if $opt_debug; |
e74c1998 |
125 | $child->{name} = "$node->{name} $child->{name}"; |
f9d8678b |
126 | $child->{$_} += $node->{$_} for (qw(self_size)); |
5a78486c |
127 | $child->{$_} = $node->{$_} for (qw(parent_id)); |
e8f4c506 |
128 | |
e74c1998 |
129 | $child->{title} = join " ", grep { defined && length } $child->{title}, $node->{title}; |
945e07d7 |
130 | #warn "Titled $child->{title}" if $child->{title}; |
e8f4c506 |
131 | |
d3b8a135 |
132 | # somewhat hackish attribute merging |
e8f4c506 |
133 | for my $attr_type (keys %{ $node->{attr} }) { |
134 | my $src = $node->{attr}{$attr_type}; |
135 | if (ref $src eq 'HASH') { # eg NPattr_NAME: {attr}{1}{$name} = $value |
136 | my $dst = $child->{attr}{$attr_type} ||= {}; |
137 | for my $k (keys %$src) { |
138 | warn "Node $child->{id} attr $attr_type:$k=$dst->{$k} overwritten by $src->{$k}\n" |
139 | if defined $dst->{$k}; |
140 | $dst->{$k} = $src->{$k}; |
141 | } |
142 | } |
47a0d085 |
143 | elsif (ref $src eq 'ARRAY') { # eg NPattr_PADNAME: {attr}{2}[$val] = $name |
e8f4c506 |
144 | my $dst = $child->{attr}{$attr_type} ||= []; |
145 | my $idx = @$src; |
146 | while (--$idx >= 0) { |
147 | warn "Node $child->{id} attr $attr_type:$idx=$dst->[$idx] overwritten by $src->[$idx]\n" |
148 | if defined $dst->[$idx]; |
149 | $dst->[$idx] = $src->[$idx]; |
150 | } |
151 | } |
47a0d085 |
152 | else { # assume scalar |
153 | warn "Node $child->{id} attr $attr_type=$child->{attr}{$attr_type} overwritten by $src\n" |
154 | if exists $child->{attr}{$attr_type}; |
155 | $child->{attr}{$attr_type} = $src; |
156 | } |
e8f4c506 |
157 | } |
158 | |
e78b28ca |
159 | $child->{leaves}{$_} += $node->{leaves}{$_} |
160 | for keys %{ $node->{leaves} }; |
5a78486c |
161 | |
162 | $child->{_ids_merged} .= ",$node->{id}"; |
163 | my @child_ids = split /,/, $node->{child_ids}; |
164 | $child->{child_count} = @child_ids; |
165 | |
d3b8a135 |
166 | $node = $child; # use the merged child as this node |
875c1073 |
167 | } |
d3b8a135 |
168 | # XXX this elsif() should possibly be a plain if(), maybe with tweaks to the above |
169 | # because we want to allow the recursion |
170 | elsif ($depth) { # recurse to required depth |
5a78486c |
171 | $children = [ map { _fetch_node_tree($_, $depth-1) } @child_ids ]; |
f9d8678b |
172 | $node->{children} = $children; |
173 | $node->{child_count} = @$children; |
875c1073 |
174 | } |
b2fc39a5 |
175 | } |
b2fc39a5 |
176 | return $node; |
177 | } |
178 | |
d3b8a135 |
179 | |
f9d8678b |
180 | sub _transform_node_tree { # recurse depth first |
875c1073 |
181 | my ($node, $transform) = @_; |
182 | if (my $children = $node->{children}) { |
183 | $_ = _transform_node_tree($_, $transform) for @$children; |
184 | } |
185 | return $transform->($node); |
186 | } |
187 | |
b2fc39a5 |
188 | |
189 | app->start; |
d3b8a135 |
190 | |
b2fc39a5 |
191 | __DATA__ |
192 | @@ index.html.ep |
193 | % layout 'default'; |
194 | % title 'Welcome'; |
195 | Welcome to the Mojolicious real-time web framework! |
196 | |
197 | @@ layouts/default.html.ep |
198 | <!DOCTYPE html> |
199 | <head> |
200 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
bb66f8a1 |
201 | <title>Perl Memory Treemap</title> |
b2fc39a5 |
202 | |
203 | <!-- CSS Files --> |
204 | <link type="text/css" href="css/base.css" rel="stylesheet" /> |
205 | <link type="text/css" href="css/Treemap.css" rel="stylesheet" /> |
206 | |
207 | <!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]--> |
208 | |
209 | <!-- JIT Library File --> |
210 | <script language="javascript" type="text/javascript" src="jit.js"></script> |
2a627489 |
211 | <script language="javascript" type="text/javascript" src="jquery-1.8.1-min.js"></script> |
b2fc39a5 |
212 | |
bb66f8a1 |
213 | <script language="javascript" type="text/javascript" src="sprintf.js"></script> |
d3b8a135 |
214 | <script language="javascript" type="text/javascript" src="dmemtreemap.js"></script> |
b2fc39a5 |
215 | </head> |
216 | |
217 | <body onload="init();"> |
218 | <div id="container"> |
219 | |
220 | <div id="left-container"> |
221 | |
b2fc39a5 |
222 | <div class="text"> |
223 | <h4> |
bb66f8a1 |
224 | Perl Memory TreeMap |
b2fc39a5 |
225 | </h4> |
47a0d085 |
226 | Click on a node to zoom in.<br /><br /> |
b2fc39a5 |
227 | </div> |
228 | |
229 | <a id="back" href="#" class="theme button white">Go to Parent</a> |
e8f4c506 |
230 | |
231 | <br /> |
232 | <form name=params> |
233 | <label for="logarea"> Logarithmic scale |
234 | <input type=checkbox id="logarea" name="logarea"> |
235 | </form> |
236 | |
b2fc39a5 |
237 | </div> |
238 | |
239 | <div id="center-container"> |
240 | <div id="infovis"></div> |
241 | </div> |
242 | |
243 | <div id="right-container"> |
244 | |
245 | <div id="inner-details"></div> |
246 | |
247 | </div> |
248 | |
249 | <div id="log"></div> |
250 | </div> |
251 | </body> |
252 | </html> |