| B-Size documentation | Contained in the B-Size distribution. |
B::TerseSize - Printing info about ops and their (estimated) size
perl -MO=TerseSize[,OPTIONS] foo.pl
The B::Size and B::TerseSize modules attempt to measure the size of Perl op codes. The output of B::TerseSize is similar to that of B::Terse, but includes the size of each OP in the tree and the PADLIST (subroutine lexical variables). The module can be run just as other compiler backends or used via Apache::Status (version 2.02 and higher).
If the Apache::Status StatusTerseSize option is enabled, there will be a main menu item added, "Memory Usage". Clicking on this link will cause B::TerseSize to produce a summary of package memory usage. This summary can take quite a while to produce, as each package subroutine syntax tree will be walked, adding up the information. This information will be cached, so running httpd in -X (non-forking mode) is a good choice.
When browsing the Apache::Status "Symbol Table Dump", a "Memory Usage" link will be at the bottom of each page. These summaries also include measurements of package global variables.
The Apache::Status symbol table browser will also provide an option to dump a subroutine tree along with the other subroutine options.
The memory measurements are only an estimate. But, chances are, if a measurement is not accurate, it is smaller than the actual size.
The "execution order" option under Apache::Status can only be run once unless you are using Perl 5.6.0+ or apply the patches/b_clearsym_60.pat to older Perls.
B(3), B::Size(3), B::LexInfo(3), Apache::Status(3)
Currently Maintained by Philip M. Gollucci Previously Developed by Doug MacEachern based in part on B::Terse by Malcolm Beattie
| B-Size documentation | Contained in the B-Size distribution. |
# B::TerseSize.pm # Copyright (c) 1999 Doug MacEachern. All rights reserved. # This module is free software; you can redistribute and/or modify # it under the same terms as Perl itself. # portions of this module are based on B::Terse, by Malcolm Beattie package B::TerseSize; use strict; use constant IS_MODPERL => $ENV{MOD_PERL}; use constant MP2 => $ENV{MOD_PERL_API_VERSION} == 2 ? 1 : 0; use B (); use B::Asmdata qw(@specialsv_name); use B::Size (); { no strict; $VERSION = '0.09'; } my $opcount; my $opsize; my $copsize; my $curcop; sub UNIVERSAL::op_size { $opcount++; my $size = shift->size; $opsize += $size; $copsize += $size; } my $mouse_attr = qq( onclick='javascript: return false') . qq( onmouseout='window.status=""; return true'); sub op_html_name { my($op, $sname) = @_; $sname =~ s/(\s+)$//; my $pad = $1 || ""; my $desc = sprintf qq(onmouseover='window.status="%s"; return true'), B::OP::op_desc($op->type) || "unknown"; my $href = $curcop ? $curcop->line : ""; return qq(<a $desc $mouse_attr href="#$href">$sname</a>$pad); } sub peekop { my $op = shift; my $size = $op->size; $opcount++; $opsize += $size; $copsize += $size; my $name; my $sname = sprintf "%-13s", $op->name; if (IS_MODPERL) { $name = op_html_name($op, $sname); } else { $name = $sname; } my $addr = sprintf "0x%lx", $$op; $addr = qq(<a name="$addr">$addr</a>) if IS_MODPERL; return sprintf qq(%-6s $name $addr {%d bytes}), B::class($op), $size; } my $hr = "=" x 60; my %filelex = (); sub package_size { my($package) = @_; #local *UNIVERSAL::op_size = \&universal_op_size; my %retval = (); my $total_opsize = 0; my $total_opcount = 0; my $stash; { no strict; $stash = \%{"$package\::"}; } for (keys %$stash) { my $name = $package . "::$_"; my $has_code = 0; { no strict; $has_code = *{$name}{CODE}; #defined() expects CvROOT || CvXSUB } unless ($has_code) { #CV_walk will measure $total_opsize += B::Sizeof::GV + B::Sizeof::XPVGV + B::Sizeof::GP; } #measure global variables for my $type (qw(ARRAY HASH SCALAR)) { no strict; next if $name =~ /::$/; #stash next unless /^[\w_]/; next if /^_</; my $ref = *{$name}{$type}; next unless $ref; my $obj = B::svref_2object($ref); next if ref($obj) eq 'B::NULL'; my $tsize = $obj->size; $total_opsize += $tsize; $retval{"*${_}{$type}"} = {'size' => $tsize}; } next unless defined $has_code; CV_walk('slow', $name, 'op_size'); for (keys %{ $filelex{$package} }) { my $fsize = $filelex{$package}->{$_}; $total_opsize += $opsize; $retval{"my ${_} = ...;"} = {'size' => $fsize}; } %filelex = (); $total_opsize += $opsize; $total_opcount += $opcount; $retval{$_} = {'count' => $opcount, 'size' => $opsize}; } return (\%retval, $total_opcount, $total_opsize); } my $b_objsym = \&B::objsym; sub objsym { my $obj = shift; my $value = $b_objsym->($obj); return unless $value; sprintf qq(<a href="#0x%lx">$value</a>), $$obj; } sub CV_walk { my($order, $objname, $meth) = @_; $meth ||= 'terse_size'; my $cvref = \&{$objname}; my $cv = B::svref_2object($cvref); my($package, $func) = ($objname =~ /(.*)::([^:]+)$/); $opsize = B::Sizeof::GV + B::Sizeof::XPVGV + B::Sizeof::GP; $opcount = 0; $curcop = ""; my $gv = $cv->GV; $opsize += length $gv->NAME; if (my $stash = $cv->is_alias($package)) { return; } $opsize += B::Sizeof::XPVCV; $opsize += B::Sizeof::SV; if ($cv->FLAGS & B::SVf_POK) { $opsize += B::Sizeof::XPV + length $cv->PV; } else { $opsize += B::Sizeof::XPVIV; #IVX == -1 for no prototype } init_curpad_names($cvref); no strict; local *B::objesym = \&objsym if IS_MODPERL; if ($order eq 'exec') { B::walkoptree_exec($cv->START, $meth); } else { B::walkoptree_slow($cv->ROOT, $meth); } curcop_info() if $curcop; my($padsize, $padsummary) = PADLIST_size($cv); $opsize += $padsize; $padsummary; } sub terse_size { my($order, $objname) = @_; my $padsummary = CV_walk($order, $objname); print "\n$hr\nTotals: $opsize bytes | $opcount OPs\n$hr\n"; if ($padsummary) { print "\nPADLIST summary:\n"; print @$padsummary; } } my @curpad_names = (); sub init_curpad_names { my $cv = B::svref_2object(shift); my $padlist = $cv->PADLIST; return if ref($padlist) eq 'B::SPECIAL'; @curpad_names = ($padlist->ARRAY)[0]->ARRAY; } sub compile { my $order = shift; my @options = @_; B::clearsym() if defined &B::clearsym; if (@options) { return sub { my $objname; foreach $objname (@options) { $objname = "main::$objname" unless $objname =~ /::/; terse_size($order, $objname); } } } else { if ($order eq "exec") { return sub { B::walkoptree_exec(B::main_start, "terse_size"); curcop_info() if $curcop} } else { return sub { B::walkoptree_slow(B::main_root, "terse_size"); curcop_info() if $curcop} } } } sub indent { my $level = shift; return " " x $level; } #thanks B::Deparse sub padname { my $obj = shift; return '?' unless ref $obj; my $str = $obj->PV; my $ix = index($str, "\0"); $str = substr($str, 0, $ix) if $ix != -1; return $str; } sub B::OP::terse_size { my ($op, $level) = @_; my $t = $op->targ; my $targ = ""; if ($t > 0) { my $name = B::OP::op_name($op->targ); my $desc = B::OP::op_desc($op->targ); if ($op->type == 0) { #OP_NULL $targ = $name eq $desc ? " [$name]" : sprintf " [%s - %s]", $name, $desc; } else { $targ = sprintf " [targ %d - %s]", $t, padname($curpad_names[$t]); } } print indent($level), peekop($op), $targ, "\n"; } sub B::SVOP::terse_size { my ($op, $level) = @_; print indent($level), peekop($op), " "; $op->sv->terse_size(0); } sub B::GVOP::terse_size { my ($op, $level) = @_; print indent($level), peekop($op), " "; $op->gv->terse_size(0); } sub B::PMOP::terse_size { my ($op, $level) = @_; my $precomp = $op->precomp; print indent($level), peekop($op), (defined($precomp) ? " /$precomp/\n" : " (regexp not compiled)\n"); } sub B::PVOP::terse_size { my ($op, $level) = @_; print indent($level), peekop($op), " ", B::cstring($op->pv), "\n"; } my $hr2 = "-" x 60; *cop_file = B::COP->can('file') || sub { shift->filegv->SV->PV; }; sub curcop_info { my $line = $curcop->line; my $linestr = "line $line"; if ($line > 0 && IS_MODPERL) { my $anchor = ""; if ($line > 10) { $anchor = "#" . ($line - 10); } my $window = sprintf "offset=%d&len=%d", $line - 100, $line + 100; my $args = sprintf "noh_fileline=1&filename=%s&line=%d&$window", cop_file($curcop), $line; my $uri = MP2 ? Apache2::RequestUtil->request()->location() : Apache->request->location; $linestr = qq(<a name="$line" target=top href="$uri?$args$anchor">$linestr</a>); } print "\n[$linestr size: $copsize bytes]\n"; } sub B::COP::terse_size { my ($op, $level) = @_; my $label = $op->label || ""; if ($label) { $label = " label ".B::cstring($label); } curcop_info() if $curcop; $copsize = 0; $curcop = $op; print "\n$hr2\n", indent($level), peekop($op), "$label\n"; } sub B::PV::terse_size { my ($sv, $level) = @_; print indent($level); my $pv = B::cstring($sv->PV); B::Size::escape_html(\$pv) if IS_MODPERL; printf "%s %s\n", B::class($sv), $pv; } sub B::AV::terse_size { my ($sv, $level) = @_; print indent($level); printf "%s FILL %d\n", B::class($sv), $sv->FILL; } sub B::GV::terse_size { my ($gv, $level) = @_; my $stash = $gv->STASH->NAME; if ($stash eq "main") { $stash = ""; } else { $stash = $stash . "::"; } print indent($level); printf "%s *%s%s\n", B::class($gv), $stash, $gv->NAME; } sub B::IV::terse_size { my ($sv, $level) = @_; print indent($level); printf "%s %d\n", B::class($sv), $sv->IV; } sub B::NV::terse_size { my ($sv, $level) = @_; print indent($level); printf "%s %s\n", B::class($sv), $sv->NV; } sub B::RV::terse_size { my ($sv, $level) = @_; print indent($level); printf "%s \n", B::class($sv); } sub B::NULL::terse_size { my ($sv, $level) = @_; print indent($level); printf "%s \n", B::class($sv); } sub B::SPECIAL::terse_size { my ($sv, $level) = @_; print indent($level); printf "%s #%d %s\n", B::class($sv), $$sv, $specialsv_name[$$sv]; } my $padname_max = 0; sub PADLIST_size { my $cv = shift; my $obj = UNIVERSAL::isa($cv, "B::CV") ? $cv : B::svref_2object($cv); my $size = (B::Sizeof::AV + B::Sizeof::XPVAV) * 3; #padlist, names, values if ($obj->PADLIST->isa('B::SPECIAL')) { return B::Sizeof::AV; #XXX??? } my($padnames, $padvals) = $obj->PADLIST->ARRAY; my @names = $padnames->ARRAY; $padname_max = 0; my @names_pv = map { my $pv = padname($_); $padname_max = length($pv) > $padname_max ? length($pv) : $padname_max; $pv; } @names; my @vals = $padvals->ARRAY; my $fill = $padnames->FILL; my $fill_len = length $fill; my @retval = (); my $wantarray = wantarray; for (my $i = 0; $i <= $fill; $i++) { my $entsize = $names[$i]->size; my $is_fake = $names[$i]->FLAGS & B::SVf_FAKE; if ($is_fake) { $entsize += B::Sizeof::SV; # just a reference to outside scope if (B::class($obj->OUTSIDE->GV) eq 'SPECIAL') { $filelex{ $obj->GV->STASH->NAME }->{ $names_pv[$i] } = $vals[$i]->size; } else { #XXX nested/anonsubs } } else { $entsize += $vals[$i]->size; } $size += $entsize; next unless $wantarray; my $class = B::class($vals[$i]); my $byteinfo = sprintf "[%-4s %3d bytes]", $class, $entsize; no warnings; push @retval, sprintf "%${fill_len}d: %${padname_max}s %s %s\n", $i, $names_pv[$i], $byteinfo, $is_fake ? '__SvFAKE__' : $vals[$i]->sizeval; } return $wantarray ? ($size, \@retval) : $size; } #hmm, I wonder if B::Deparse could be used instead sub Apache::Status::noh_fileline { my $r = shift; my %args = $r->args; my $offset = $args{offset} || 0; my $len = $args{len} || 0; local *FH; my $filename = $args{filename}; $r->send_http_header('text/html'); unless (Apache::Status::status_config($r, "StatusTerseSize")) { print "sorry, StatusTerseSize not enabled\n"; return 0; } unless (exists $main::{"_<$filename"}) { #useithreads doesnt gv_fetchfile() my $in_inc = 0; for (keys %INC) { if ($INC{$_} eq $filename) { $in_inc = 1; $main::{"_<$filename"} = $_; last; } } unless ($in_inc) { print "sorry, `$filename' is not a file used by Perl\n"; return 0; } } my $i = 0; $r->print('<pre>'); if ($offset > 0) { printf "%4d..%d [...]\n", 1, $offset-1; } open FH, $filename or die $!; while (<FH>) { $i++; next if $len > 0 and $i > $len; next if $offset > 0 and $i < $offset; chomp; s/^\t/ /; #indent proper my $lineno = sprintf "%4d", $i; B::Size::escape_html(\$_); my $line = ($i == $args{line}) ? \qq(<font color="#FF0000">$_</font>) : \$_; print qq($lineno: <a name=$i>$$line</a>\n); } if ($len > 0 and $i > $len) { printf "%4d..%d [...]\n", $len+1, $i; } close FH; 0; } sub Apache2::Status::noh_fileline { my $r = shift; my $args = $r->args; require CGI; my $CGI = CGI->new($args); my %params = map { $_ => $CGI->param($_) } $CGI->param(); my $offset = $params{offset} || 0; my $len = $params{len} || 0; local *FH; my $filename = $params{filename}; $r->content_type('text/html'); unless (Apache2::Status::status_config($r, "StatusTerseSize")) { print "sorry, StatusTerseSize not enabled\n"; return 0; } unless (exists $main::{"_<$filename"}) { #useithreads doesnt gv_fetchfile() my $in_inc = 0; for (keys %INC) { if ($INC{$_} eq $filename) { $in_inc = 1; $main::{"_<$filename"} = $_; last; } } unless ($in_inc) { print "sorry, '$filename' is not a file used by Perl\n"; return 0; } } my $i = 0; $r->print('<pre>'); if ($offset > 0) { printf "%4d..%d [...]\n", 1, $offset-1; } open FH, $filename or die $!; while (<FH>) { $i++; next if $len > 0 and $i > $len; next if $offset > 0 and $i < $offset; chomp; s/^\t/ /; #indent proper my $lineno = sprintf "%4d", $i; B::Size::escape_html(\$_); my $line = ($i == $params{line}) ? \qq(<font color="#FF0000">$_</font>) : \$_; print qq($lineno: <a name=$i>$$line</a>\n); } if ($len > 0 and $i > $len) { printf "%4d..%d [...]\n", $len+1, $i; } close FH; 0; } sub max { my($cur, $maybe) = @_; $maybe > $cur ? $maybe : $cur; } my %summary_cache = (); sub apache_package_size { my $package = shift; my($subs, $opcount, $opsize); my $keys = 0; my $cache = {}; { no strict 'refs'; $keys = keys %{"$package\::"}; } if ($cache = $summary_cache{$package}) { if ($cache->{'keys'} == $keys) { return @{ $cache->{'data'} } if $cache->{'data'}; } } $cache->{'keys'} = $keys; $summary_cache{$package} = $cache; @{ $cache->{'data'} } = B::TerseSize::package_size($package); } sub status_memory_usage { my($r, $q) = @_; if (MP2) { unless (Apache2::Status::status_config($r, "StatusTerseSize")) { return ["StatusTerseSize is not enabled"]; } } else { unless (Apache::Status::status_config($r, "StatusTerseSize")) { return ["StatusTerseSize is not enabled"]; } } unless ($r->dir_config("StatusTerseSizeMainSummary")) { return ["StatusTerseSizeMainSummary is not enabled"]; } my $script = MP2 ? $r->uri : $q->script_name; my $stab = Devel::Symdump->rnew('main'); my %total; my @retval = ('<pre>'); my($clen, $slen, $nlen); for my $package ('main', $stab->packages) { my($subs, $opcount, $opsize) = apache_package_size($package); $total{$package} = {'count' => $opcount, 'size' => $opsize}; $nlen = max($nlen, length $package); $slen = max($slen, length $opsize); $clen = max($clen, length $opcount); } for (sort { $total{$b}->{size} <=> $total{$a}->{size} } keys %total) { my $link = qq(<a href="$script/$_?noh_b_package_size">); push @retval, sprintf "$link%-${nlen}s</a> %${slen}d bytes | %${clen}d OPs\n", $_, $total{$_}->{size}, $total{$_}->{count}; } \@retval; } if (MP2) { Apache2::Status->menu_item( 'status_memory_usage' => "Memory Usage", \&status_memory_usage, ); } elsif (IS_MODPERL and Apache->module("Apache::Status")) { Apache::Status->menu_item( 'status_memory_usage' => "Memory Usage", \&status_memory_usage, ); } 1; __END__