File Coverage

blib/lib/POE/Devel/Top.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package POE::Devel::Top;
2              
3 1     1   1004 use strict;
  1         3  
  1         52  
4 1     1   8 use warnings;
  1         3  
  1         40  
5              
6 1     1   20 use Carp;
  1         3  
  1         101  
7 1     1   1579 use POE qw< API::Peek Session >;
  1         126725  
  1         8  
8             use Term::ANSIColor qw< :constants >;
9              
10              
11             our $VERSION = "0.100";
12              
13              
14             #
15             # import()
16             # ------
17             sub import {
18             my ($class, @args) = @_;
19              
20             # if caller line is zero, it means the module was loaded from the
21             # command line, in which case we automatically spawn the session
22             my ($package, undef, $line) = caller;
23             $class->spawn(render => "console", @args)
24             if $line == 0 or $package eq __PACKAGE__;
25             }
26              
27              
28             #
29             # spawn()
30             # -----
31             sub spawn {
32             my ($class, @args) = @_;
33              
34             croak "Odd number of argument" if @args % 2 == 1;
35              
36             POE::Session->create(
37             heap => {
38             interval => 2,
39             @args
40             },
41              
42             inline_states => {
43             _start => sub {
44             $_[KERNEL]->alias_set("[$class]");
45             $_[KERNEL]->delay(poe_devel_top_collect => $_[HEAP]->{interval});
46             },
47             poe_devel_top_collect => \&collect,
48             poe_devel_top_render => \&render,
49             poe_devel_top_store => \&store,
50             },
51             );
52             }
53              
54              
55             #
56             # collect()
57             # -------
58             sub collect {
59             my ($kernel, $heap) = @_[ KERNEL, HEAP ];
60             my $poe_api = POE::API::Peek->new;
61             my $now = time;
62              
63             # collect general data about the current process
64             my @times = times;
65             my @pwent = getpwuid(int $>);
66             my $egid = (split / /, $))[0];
67             my @grent = getgrgid(int $egid);
68              
69             my %general = (
70             process => {
71             pid => $$,
72             uid => $>,
73             gid => $egid,
74             user => $pwent[0],
75             group => $grent[0],
76             },
77             resource => {
78             utime_self => $times[0],
79             utime_chld => $times[2],
80             stime_self => $times[1],
81             stime_chld => $times[3],
82             },
83             poe => {
84             sessions => $poe_api->session_count,
85             handles => $poe_api->handle_count,
86             loop => $poe_api->which_loop,
87             },
88             );
89              
90             # collect information about the sessions
91             my $kernel_id = $kernel->ID;
92             my @sessions;
93              
94             for my $session ($poe_api->session_list) {
95             push @sessions, {
96             $session->ID eq $kernel_id ? (
97             id => 0,
98             aliases => "[POE::Kernel] id=".$session->ID,
99             ) : (
100             id => $session->ID,
101             aliases => join(",", $poe_api->session_alias_list($session)),
102             ),
103             memory_size => $poe_api->session_memory_size($session),
104             refcount => $poe_api->get_session_refcount($session),
105             events_to => $poe_api->event_count_to($session),
106             events_from => $poe_api->event_count_from($session),
107             };
108             }
109              
110             @sessions = sort { $a->{id} <=> $b->{id} } @sessions;
111              
112             # collect information about the events
113             my @events;
114              
115             for my $event ($poe_api->event_queue_dump) {
116             push @events, {
117             id => $event->{ID},
118             name => $event->{event},
119             type => $event->{type},
120             priority => $event->{priority} > $now ?
121             $event->{priority} - $now : $event->{priority},
122             source => $event->{source}->ID,
123             destination => $event->{destination}->ID,
124             }
125             }
126              
127             # create the final hash
128             my %stats = (
129             general => \%general,
130             sessions => \@sessions,
131             events => \@events,
132             );
133              
134             # call myself
135             $kernel->delay(poe_devel_top_collect => $heap->{interval});
136              
137             # call the dumper event
138             $kernel->yield(poe_devel_top_store => \%stats)
139             if $heap->{dump_as} and $heap->{dump_as} ne "none";
140              
141             # call the renderer event
142             $kernel->yield(poe_devel_top_render => \%stats)
143             if $heap->{render} eq "console";
144              
145             return \%stats
146             }
147              
148              
149             #
150             # render()
151             # ------
152             sub render {
153             my ($kernel, $stats) = @_[ KERNEL, ARG0 ];
154             my $proc = $stats->{general}{process};
155             my $rsrc = $stats->{general}{resource};
156              
157             local $Term::ANSIColor::AUTORESET = 1;
158              
159             my $session_head = REVERSE(BOLD "%5s %6s %8s %6s %8s %-40s").$/;
160             my $session_row = "%5d %6s %8d %6d %8d %-40s\n";
161             my @session_cols = qw< ID Memory Refcount EvtsTo EvtsFrom Aliases >;
162              
163             my $event_head = REVERSE(BOLD "%5s %-17s %4s %5s %5s %-40s").$/;
164             my $event_row = "%5d %-17s %4d %5d %5d %-40s\n";
165             my @event_cols = qw< ID Type Pri Src Dest Name >;
166              
167             print "\e[2J\e[f",
168             "Process ID: $proc->{pid}, ",
169             "UID: $proc->{uid} ($proc->{user}), ",
170             "GID: $proc->{gid} ($proc->{group})\n",
171             "Resource usage: ",
172             "user: $rsrc->{utime_self} sec (+$rsrc->{utime_chld} sec), ",
173             "system: $rsrc->{stime_self} sec (+$rsrc->{stime_chld} sec)\n",
174             "Sessions: $stats->{general}{poe}{sessions} total, ",
175             "Handles: $stats->{general}{poe}{handles} total, ",
176             "Loop: $stats->{general}{poe}{loop}\n\n";
177              
178             print BOLD " Sessions", $/;
179             printf $session_head, @session_cols;
180             printf $session_row,
181             $_->{id}, human_size( $_->{memory_size} ), $_->{refcount},
182             $_->{events_to}, $_->{events_from}, $_->{aliases}
183             for @{$stats->{sessions}};
184              
185             print $/;
186              
187             print BOLD " Events", $/;
188             printf $event_head, @event_cols;
189             printf $event_row,
190             $_->{id}, $_->{type}, $_->{priority},
191             $_->{source}, $_->{destination}, $_->{name}
192             for @{$stats->{events}};
193              
194             print $/;
195             }
196              
197              
198             #
199             # human_size()
200             # ----------
201             sub human_size {
202             my ($size) = @_;
203              
204             return $size if $size < 100_000;
205              
206             my $unit;
207             for (qw< K M G >) {
208             $size = int($size / 1024);
209             $unit = $_;
210             last if $size < 1024;
211             }
212              
213             return $size.$unit;
214             }
215              
216              
217             #
218             # store()
219             # -----
220             sub store {
221             my ($kernel, $heap, $stats) = @_[ KERNEL, HEAP, ARG0 ];
222              
223             if ($heap->{dump_as} eq "yaml") {
224             if (eval "require YAML; 1") {
225             YAML::DumpFile($heap->{dump_to}, $stats);
226             return
227             }
228             else {
229             $heap->{dump_as} = "native";
230             $heap->{dump_to} =~ s/\.ya?ml$/.dmp/;
231             carp "warning: YAML not available. Defaulting to native format."
232             }
233             }
234              
235             if ($heap->{dump_as} eq "native") {
236             if (eval "require Storable; 1") {
237             Storable::nstore($stats, $heap->{dump_to});
238             return
239             }
240             else {
241             croak "fatal: Can't load Storable: $@"
242             }
243             }
244             }
245              
246              
247             __PACKAGE__
248              
249             __END__