PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Devel/TrackObjects.pm

http://github.com/noxxi/p5-devel-trackobjects
Perl | 428 lines | 321 code | 61 blank | 46 comment | 46 complexity | ae32e130f388722e9d606f651011f552 MD5 | raw file
  1. package Devel::TrackObjects;
  2. use strict;
  3. use warnings;
  4. use Scalar::Util 'weaken';
  5. use overload;
  6. our $VERSION = '0.601';
  7. my @weak_objects; # List of weak objects incl file + line
  8. my @conditions; # which objects to track, set by import
  9. my $is_redefined; # flag if already redefined
  10. my $old_bless; # bless sub before redefining
  11. my $debug; # enable internal debugging
  12. my $verbose; # detailed output instead of compact
  13. my $with_tstamp; # prefix output with timestamp
  14. my $with_size; # with size of objects
  15. my $with_sizediff; # track changes in size
  16. my $no_end; # no show tracked at END
  17. my $outfunc = sub {
  18. my ($prefix,$out) = @_;
  19. if (ref($out)) {
  20. # details - can be multiple lines
  21. if (@$out) {
  22. print STDERR "LEAK$prefix " .
  23. ($with_tstamp ? localtime().' ' :'' ) . " >> \n",
  24. @$out,
  25. " --\n";
  26. } else {
  27. print STDERR "LEAK$prefix " .
  28. ($with_tstamp ? localtime().' ' :'' ) . " >> empty --\n";
  29. }
  30. } else {
  31. # no details - single line
  32. $out ||= 'empty ';
  33. print STDERR "LEAK$prefix >> $out--\n";
  34. }
  35. };
  36. ############################################################################
  37. # redefined CORE::GLOBAL::bless if restrictions are given
  38. # which classes should get tracked
  39. ############################################################################
  40. sub import {
  41. shift;
  42. while (@_) {
  43. local $_ = shift;
  44. if ( ! ref && m{^-(\w+)$} ) {
  45. if ($1 eq 'debug') {
  46. $debug = 1;
  47. } elsif ($1 eq 'verbose') {
  48. $verbose = 1;
  49. } elsif ($1 eq 'timestamp') {
  50. $with_tstamp = 1;
  51. } elsif ($1 eq 'noend') {
  52. $no_end = 1;
  53. } elsif ($1 eq 'size') {
  54. # need Devel::Size;
  55. $with_size = eval { require Devel::Size }
  56. or die "need Devel::Size installed for '-size' option"
  57. } elsif ($1 eq 'sizediff') {
  58. $with_sizediff = 1;
  59. unshift @_, 'size' if ! $with_size;
  60. } elsif ($1 eq 'out') {
  61. $outfunc = shift @_;
  62. ref($outfunc) eq 'CODE'
  63. or die "outfunc needs to be function reference";
  64. } else {
  65. die "unknown option $_";
  66. }
  67. } elsif ( $_ eq 'track_object' ) {
  68. # export function
  69. my ($pkg) = caller();
  70. no strict 'refs';
  71. *{"${pkg}::track_object"} = \&track_object;
  72. } elsif ( ! ref && m{^/} ) {
  73. # assume uncompiled regex
  74. my $rx = eval "qr$_";
  75. die $@ if $@;
  76. push @conditions,$rx;
  77. } else {
  78. push @conditions,$_
  79. }
  80. }
  81. _redefine_bless() if @conditions;
  82. }
  83. ############################################################################
  84. # show everything tracked at the end
  85. ############################################################################
  86. sub END {
  87. $no_end && return;
  88. __PACKAGE__->show_tracked() if $is_redefined;
  89. 1;
  90. }
  91. ############################################################################
  92. # depending on $verbose show detailed or compact version
  93. ############################################################################
  94. sub show_tracked {
  95. return $verbose
  96. ? show_tracked_detailed(@_)
  97. : show_tracked_compact(@_);
  98. }
  99. ############################################################################
  100. # show what's still used. If I want something back give reference to
  101. # \@weak_objects, else print myself to STDERR
  102. ############################################################################
  103. sub show_tracked_detailed {
  104. shift;
  105. my $prefix = shift || '';
  106. _remove_destroyed();
  107. return \@weak_objects if defined wantarray;
  108. my @out;
  109. if ( @weak_objects ) {
  110. my (%s,%l);
  111. for my $o ( sort {
  112. overload::StrVal($a->[0]) cmp overload::StrVal($b->[0])
  113. } @weak_objects ) {
  114. my $line = '-- ';
  115. if ( $with_size ) {
  116. my $size = Devel::Size::size($o->[0]);
  117. my $total_size = Devel::Size::total_size($o->[0]);
  118. if ( $with_sizediff ) {
  119. $line .= sprintf("size=%d/%+d/%+d ",$size,
  120. $size-($o->[6]||0),$size-($o->[4]||0));
  121. $line .= sprintf("%d/%+d/%+d ", $total_size,
  122. $total_size-($o->[7]||0),$total_size-($o->[5]||0));
  123. $o->[4] = $size if ! defined $o->[4];
  124. $o->[5] = $total_size if ! defined $o->[5];
  125. $o->[6] = $size;
  126. $o->[7] = $total_size;
  127. } else {
  128. $line .= "size=$size total=$total_size ";
  129. }
  130. }
  131. $line .= sprintf "%s | %s:%s%s\n",
  132. overload::StrVal($o->[0]),$o->[1],$o->[2],
  133. defined($o->[3]) ? " $o->[3]":'';
  134. push @out, $line;
  135. }
  136. }
  137. return $outfunc->($prefix,\@out);
  138. }
  139. ############################################################################
  140. # show tracked objects in compact form, e.g. only counter for each class
  141. ############################################################################
  142. sub show_tracked_compact {
  143. shift;
  144. my $prefix = shift || '';
  145. _remove_destroyed();
  146. my %count4class;
  147. foreach my $o (@weak_objects) {
  148. ( $count4class{ ref($o->[0]) } ||= 0 )++;
  149. }
  150. if ( defined wantarray ) {
  151. return %count4class ? \%count4class : undef
  152. }
  153. my $msg;
  154. if ( %count4class ) {
  155. foreach ( sort keys %count4class ) {
  156. $msg .= $_.'='.$count4class{$_}.' ';
  157. }
  158. }
  159. return $outfunc->($prefix,$msg);
  160. }
  161. ############################################################################
  162. # bless object and track it, if it matches @condition
  163. ############################################################################
  164. sub _bless_and_track($;$) {
  165. my ($pkg,$filename,$line) = caller();
  166. my $class = $_[1] || $pkg;
  167. if (ref($_[0])) {
  168. # unregister
  169. @weak_objects = grep { $_->[0] && $_->[0] != $_[0] } @weak_objects;
  170. }
  171. my $object = $old_bless
  172. ? $old_bless->( $_[0],$class)
  173. : CORE::bless( $_[0],$class );
  174. my $track = 0;
  175. if ( @conditions ) {
  176. foreach my $c ( @conditions ) {
  177. if ( ! ref($c) ) {
  178. $track = 1,last if $c eq $pkg or $c eq $class;
  179. } elsif ( UNIVERSAL::isa($c,'Regexp' )) {
  180. $track = 1,last if $pkg =~m{$c} or $class =~m{$c};
  181. } elsif ( UNIVERSAL::isa($c,'CODE' )) {
  182. $track = 1,last if $c->($pkg) or $c->($class);
  183. }
  184. }
  185. } else {
  186. $track = 1;
  187. }
  188. _register( $object,$filename,$line ) if $track;
  189. return $object;
  190. }
  191. ############################################################################
  192. sub track_object {
  193. my ($object,$info) = @_;
  194. my (undef,$filename,$line) = caller();
  195. _register( $object,$filename,$line,$info );
  196. }
  197. ############################################################################
  198. # redefine bless unless it's already redefined
  199. ############################################################################
  200. sub _redefine_bless {
  201. return if $is_redefined;
  202. # take redefined variant if exists
  203. $old_bless = \&CORE::CLOBAL::bless;
  204. eval { $old_bless->( {}, __PACKAGE__ ) };
  205. $old_bless = undef if $@;
  206. # redefine 'bless'
  207. no warnings 'once';
  208. *CORE::GLOBAL::bless = \&_bless_and_track;
  209. $is_redefined = 1;
  210. }
  211. ############################################################################
  212. # register object, called from _bless_and_track
  213. ############################################################################
  214. sub _register {
  215. my ($ref,$fname,$line,$info) = @_;
  216. warn "TrackObjects: register ".overload::StrVal($ref).
  217. " $fname:$line ".(defined($info) ? $info:'' )."\n"
  218. if $debug;
  219. #0: referenz
  220. #1: file name
  221. #2: line in file
  222. #3: info message
  223. #4: initial size
  224. #5: initial total_size
  225. #6: last size
  226. #7: last total_size
  227. push @weak_objects, [ $ref,$fname,$line,$info ];
  228. weaken( $weak_objects[-1][0] );
  229. }
  230. ############################################################################
  231. # eliminate destroyed objects, eg where the weak ref is undef
  232. ############################################################################
  233. sub _remove_destroyed {
  234. @weak_objects = grep { defined( $_->[0] ) } @weak_objects;
  235. }
  236. 1;
  237. __END__
  238. =head1 NAME
  239. Devel::TrackObjects - Track use of objects
  240. =head1 SYNOPSIS
  241. =over 4
  242. =item cmdline
  243. perl -MDevel::TrackObjects=/^IO::/ server.pl
  244. =item inside
  245. use Devel::TrackObjects qr/^IO::/;
  246. use Devel::TrackObjects '-verbose','track_object';
  247. use Devel::TrackObjects '-size','-sizediff','-timestamp';
  248. use IO::Socket;
  249. ...
  250. my $sock = IO::Socket::INET->new...
  251. ...
  252. my $foreign = get_some_object_from_xs();
  253. track_object( $foreign, "This was created in XS" );
  254. ...
  255. Devel::TrackObjects->show_tracked;
  256. =back
  257. =head1 DESCRIPTION
  258. Devel::TrackObjects redefines C<bless> and thus tracks
  259. the creation of objects by putting weak references to the
  260. object into a list. It can be specified which classes
  261. to track.
  262. At the end of the program it will print out infos about the
  263. still existing objects (probably leaking). The same info
  264. can be print out during the run using L<show_tracked>.
  265. =head1 IMPORTANT
  266. The Module must be loaded as early as possible, because it
  267. cannot redefine B<bless> in already loaded modules. See L<import>
  268. how to load it so that it redefines B<bless>.
  269. =head1 METHODS
  270. The following class methods are defined.
  271. =over 4
  272. =item import ( COND|OPTIONS )
  273. Called from B<use>.
  274. COND is a list of conditions. A condition is either a regex used
  275. as a match for a classname, a string used to match the class with
  276. exactly this name or a reference to a subroutine, which gets called
  277. to decide if the class should get tracked (must return TRUE).
  278. Special is if the condition is C</regex/>. In this case it will
  279. be compiled as a regex. This is used, because on the perl cmdline
  280. one cannot enter compiled regex.
  281. If the item is a string starting with "-" it will be interpreted
  282. as an option. Valid options are:
  283. =over 8
  284. =item -verbose
  285. Output from L<show_tracked> will be more verbose, e.g it will use
  286. L<show_tracked_detailed> instead of L<show_tracked_compact>.
  287. =item -timestamp
  288. Includes timestamp in messages.
  289. =item -size
  290. Includes size of objects in detailed output.
  291. Needs Devel::Size installed.
  292. =item -sizediff
  293. Includes size and difference in size to last output and first output.
  294. =item -noend
  295. Don't show remaining tracked objects at B<END>.
  296. =item -out \&func
  297. Use given function (code reference) for output instead of printing to STDERR.
  298. This function is called as C<$func->($prefix,$line)> for simple output and
  299. C<$func->($prefix,\@lines)> for detailled output. It is expected to add time
  300. stamps when needed, i.e. option C<-timestamp> is ignored.
  301. =item -debug
  302. Will switch an internal debugging.
  303. =back
  304. If conditions are given it will redefine C<CORE::GLOBAL::bless>
  305. unless it was already redefined by this module.
  306. That means you do not pay a performance penalty if you just
  307. include the module, only if conditions are given it will redefine
  308. B<bless>.
  309. =item track_object( OBJECT, [ INFO ] )
  310. This tracks the given OBJECT manually.
  311. This can be used in cases, where one only wants to track single objects
  312. and not all objects for a given class or if the object was created outside
  313. of perl and thus could not be tracked automatically.
  314. If an additional INFO string is given it will be saved and shown from
  315. B<show_tracked>.
  316. =item show_tracked ( [ PREFIX ] )
  317. If B<-verbose> was set in L<import> it will call L<show_tracked_detailed>,
  318. otherwise L<show_tracked_compact>.
  319. This method will be called at B<END> unless B<-noend> was specified
  320. in L<import>.
  321. =item show_tracked_compact ( [ PREFIX ] )
  322. Will create a hash containing all tracked classes and
  323. the current object count for the class.
  324. If the caller wants to get something in return it will
  325. return a reference to this hash, otherwise it will print
  326. out the information in a single line to STDERR starting
  327. with C<"LEAK$PREFIX">.
  328. =item show_tracked_detailed ( [ PREFIX ] )
  329. If the caller wants something in return it will give it a reference to an
  330. array containing array-refs with C<< [ REF,FILE,LINE ] >>, where REF is the
  331. weak reference to the object, FILE and LINE the file name and line number,
  332. where the object was blessed.
  333. If the calling context is void it will print these information to STDERR.
  334. The first line will start with C<"LEAK$PREFIX"> and the last one ends with
  335. C<"LEAK$PREFIX">.
  336. Each line in between has the information about one object, including the
  337. stringification of REF, FILE and LINE of creation.
  338. If option C<-size> was given it will include the size and total_size of the
  339. object (see L<Devel::Size> for meaning of C<size> and C<total_size>).
  340. If option C<-sizediff> was given it will also add the difference of size
  341. between the last call and the first call.
  342. =back
  343. =head1 COPYRIGHT
  344. Steffen Ullrich Steffen_Ullrich-at-genua-dot-de