home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / Profile.pm < prev    next >
Encoding:
Perl POD Document  |  2003-08-19  |  21.1 KB  |  659 lines

  1. package DBI::Profile;
  2.  
  3. =head1 NAME
  4.  
  5. DBI::Profile - Performance profiling and benchmarking for the DBI
  6.  
  7. =head1 SYNOPSIS
  8.  
  9. The easiest way to enable DBI profiling is to set the DBI_PROFILE
  10. environment variable to 2 and then run your code as usual:
  11.  
  12.   DBI_PROFILE=2 prog.pl
  13.  
  14. This will profile your program and then output a textual summary
  15. grouped by query.  You can also enable profiling by setting the
  16. Profile attribute of any DBI handle:
  17.  
  18.   $dbh->{Profile} = 2;
  19.  
  20. Other values are possible - see L<"ENABLING A PROFILE"> below.
  21.  
  22. =head1 DESCRIPTION
  23.  
  24. DBI::Profile is new and experimental and subject to change.
  25.  
  26. The DBI::Profile module provides a simple interface to collect and
  27. report performance and benchmarking data from the DBI.
  28.  
  29. For a more elaborate interface, suitable for larger programs, see
  30. L<DBI::ProfileDumper|DBI::ProfileDumper> and L<dbiprof|dbiprof>.
  31. For Apache/mod_perl applications see
  32. L<DBI::ProfileDumper::Apache|DBI::ProfileDumper::Apache>.
  33.  
  34. =head1 OVERVIEW
  35.  
  36. Performance data collection for the DBI is built around several
  37. concepts which are important to understand clearly.
  38.  
  39. =over 4
  40.  
  41. =item Method Dispatch
  42.  
  43. Every method call on a DBI handle passes through a single 'dispatch'
  44. function which manages all the common aspects of DBI method calls,
  45. such as handling the RaiseError attribute.
  46.  
  47. =item Data Collection
  48.  
  49. If profiling is enabled for a handle then the dispatch code takes
  50. a high-resolution timestamp soon after it is entered. Then, after
  51. calling the appropriate method and just before returning, it takes
  52. another high-resolution timestamp and calls a function to record
  53. the information.  That function is passed the two timestamps
  54. plus the DBI handle and the name of the method that was called.
  55. That information about a single DBI method call is called the
  56. I<profile sample> data.
  57.  
  58. =item Data Filtering
  59.  
  60. If the method call was invoked by the DBI or by a driver then the
  61. call is currently ignored for profiling because the time spent will
  62. be accounted for by the original 'outermost' call.
  63.  
  64. For example, the calls that the selectrow_arrayref() method makes
  65. to prepare() and execute() etc. are not counted individually
  66. because the time spent in those methods is going to be allocated
  67. to the selectrow_arrayref() method when it returns. If this was not
  68. done then it would be very easy to double count time spent inside
  69. the DBI.
  70.  
  71. In future releases it may be possible to alter this behaviour.
  72.  
  73. =item Data Storage Tree
  74.  
  75. The profile data is stored as 'leaves on a tree'. The 'path' through
  76. the branches of the tree to the particular leaf that will store the
  77. profile sample data for a profiled call is determined dynamically.
  78. This is a powerful feature.
  79.  
  80. For example, if the Path is
  81.  
  82.   [ 'foo', 'bar', 'baz' ]
  83.  
  84. then the new profile sample data will be I<merged> into the tree at
  85.  
  86.   $h->{Profile}->{Data}->{foo}->{bar}->{baz}
  87.  
  88. It wouldn't be very useful to merge all the call data into one leaf
  89. node (except to get an overall 'time spent inside the DBI' total).
  90. It's more common to want the Path to include the current statement
  91. text and/or the name of the method called to show what the time
  92. spent inside the DBI was for.
  93.  
  94. The Path can contain some 'magic cookie' values that are automatically
  95. replaced by corresponding dynamic values when they're used.
  96. For example DBIprofile_Statement (exported by DBI::profile) is
  97. automatically replaced by value of the C<Statement> attribute of
  98. the handle. For example, is the Path was:
  99.  
  100.   [ 'foo', DBIprofile_Statement, 'bar' ]
  101.  
  102. and the value of $h->{Statement} was:
  103.  
  104.   SELECT * FROM tablename
  105.  
  106. then the profile data will be merged into the tree at:
  107.  
  108.   $h->{Profile}->{Data}->{foo}->{SELECT * FROM tablename}->{bar}
  109.  
  110. The default Path is just C<[ DBIprofile_Statement ]> and so by
  111. default the profile data is aggregated per distinct Statement string.
  112.  
  113. For statement handles this is always simply the string that was
  114. given to prepare() when the handle was created.  For database handles
  115. this is the statement that was last prepared or executed on that
  116. database handle. That can lead to a little 'fuzzyness' because, for
  117. example, calls to the quote() method to build a new statement will
  118. typically be associated with the previous statement. In practice
  119. this isn't a significant issue and the dynamic Path mechanism can
  120. be used to setup your own rules.
  121.  
  122. =item Profile Data
  123.  
  124. Profile data is stored at the 'leaves' of the tree as references
  125. to an array of numeric values. For example:
  126.  
  127.     [
  128.       106,                    # count
  129.       0.0312958955764771,     # total duration
  130.       0.000490069389343262,   # first duration
  131.       0.000176072120666504,   # shortest duration
  132.       0.00140702724456787,    # longest duration
  133.       1023115819.83019,       # time of first event
  134.       1023115819.86576,       # time of last event
  135.     ]
  136.  
  137. =back
  138.  
  139. =head1 ENABLING A PROFILE
  140.  
  141. Profiling is enabled for a handle by assigning to the Profile
  142. attribute. For example:
  143.  
  144.   $h->{Profile} = DBI::Profile->new();
  145.  
  146. The Profile attribute holds a blessed reference to a hash object
  147. that contains the profile data and attributes relating to it.
  148. The class the Profile object is blessed into is expected to
  149. provide at least a DESTROY method which will dump the profile data
  150. to the DBI trace file handle (STDERR by default).
  151.  
  152. All these examples have the same effect as the first:
  153.  
  154.   $h->{Profile} = {};
  155.   $h->{Profile} = "DBI::Profile";
  156.   $h->{Profile} = "2/DBI::Profile";
  157.   $h->{Profile} = 2;
  158.  
  159. If a non-blessed hash reference is given then the DBI::Profile
  160. module is automatically C<require>'d and the reference is blessed
  161. into that class.
  162.  
  163. If a string is given then it is split on 'C</>' characters and the
  164. first value is used to select the Path to be used (see below).
  165. The second value, if present, is used as the name of a module which
  166. will be loaded and it's C<new> method called. If not present it
  167. defaults to DBI::Profile. Any other values are passed as arguments
  168. to the C<new> method. For example: "C<2/DBIx::OtherProfile/Foo/42>".
  169.  
  170. Various common sequences for Path can be selected by simply assigning
  171. an integer value to Profile. The simplest way to explain how the
  172. values are interpreted is to show the code:
  173.  
  174.     push @Path, "DBI"                       if $path & 0x01;
  175.     push @Path, DBIprofile_Statement        if $path & 0x02;
  176.     push @Path, DBIprofile_MethodName       if $path & 0x04;
  177.     push @Path, DBIprofile_MethodClass      if $path & 0x08;
  178.  
  179. So using the value "C<1>" causes all profile data to be merged into
  180. a single leaf of the tree. That's useful when you just want a total.
  181.  
  182. Using "C<2>" causes profile sample data to be merged grouped by
  183. the corresponding Statement text. This is the most frequently used.
  184.  
  185. Using "C<4>" causes profile sample data to be merged grouped by
  186. the method name ('FETCH', 'prepare' etc.). Using "C<8>" is similar
  187. but gives the fully qualified 'glob name' of the method called. For
  188. example: '*DBD::Driver::db::prepare', '*DBD::_::st::fetchrow_hashref'.
  189.  
  190. The values can be added together to create deeper paths. The most
  191. useful being 6 (statement then method name) or 10 (statement then
  192. method name with class).  Using a negative number will reverse the
  193. path. Thus -6 will group by method name then statement.
  194.  
  195. The spliting and parsing of string values assigned to the Profile
  196. attribute may seem a little odd, but there's a good reason for it.
  197. Remember that attributes can be embedded in the Data Source Name
  198. string which can be passed in to a script as a parameter. For
  199. example:
  200.  
  201.     dbi:DriverName(RaiseError=>1,Profile=>2):dbname
  202.  
  203. And also, if the C<DBI_PROFILE> environment variable is set then
  204. The DBI arranges for every driver handle to share the same profile
  205. object. When perl exits a single profile summary will be generated
  206. that reflects (as nearly as practical) the total use of the DBI by
  207. the application.
  208.  
  209.  
  210. =head1 THE PROFILE OBJECT
  211.  
  212. The DBI core expects the Profile attribute value to be a hash
  213. reference and if the following values don't exist it will create
  214. them as needed:
  215.  
  216. =head2 Data
  217.  
  218. A reference to a hash containing the collected profile data.
  219.  
  220. =head2 Path
  221.  
  222. The Path value is used to control where the profile for a method
  223. call will be merged into the collected profile data.  Whenever
  224. profile data is to be stored the current value for Path is used.
  225.  
  226. The value can be one of:
  227.  
  228. =over 4
  229.  
  230. =item Array Reference
  231.  
  232. Each element of the array defines an element of the path to use to
  233. store the profile data into the C<Data> hash.
  234.  
  235. =item Undefined value (the default)
  236.  
  237. Treated the same as C<[ $DBI::Profile::DBIprofile_Statement ]>.
  238.  
  239. =item Subroutine Reference B<NOT YET IMPLEMENTED>
  240.  
  241. The subroutine is passed the DBI method name and the handle it was
  242. called on.  It should return a list of values to uses as the path.
  243. If it returns an empty list then the method call is not profiled.
  244.  
  245. =back
  246.  
  247. The following 'magic cookie' values can be included in the Path and will be
  248.  
  249. =over 4
  250.  
  251. =item DBIprofile_Statement
  252.  
  253. Replaced with the current value of the Statement attribute for the
  254. handle the method was called with. If that value is undefined then
  255. an empty string is used.
  256.  
  257. =item DBIprofile_MethodName
  258.  
  259. Replaced with the name of the DBI method that the profile sample
  260. relates to.
  261.  
  262. =item DBIprofile_MethodClass
  263.  
  264. Replaced with the fully qualified name of the DBI method, including
  265. the package, that the profile sample relates to. This shows you
  266. where the method was implemented. For example:
  267.  
  268.   'DBD::_::db::selectrow_arrayref' =>
  269.       0.022902s
  270.   'DBD::mysql::db::selectrow_arrayref' =>
  271.       2.244521s / 99 = 0.022445s avg (first 0.022813s, min 0.022051s, max 0.028932s)
  272.  
  273. The "DBD::_::db::selectrow_arrayref" shows that the driver has
  274. inherited the selectrow_arrayref method provided by the DBI.
  275.  
  276. But you'll note that there is only one call to
  277. DBD::_::db::selectrow_arrayref but another 99 to
  278. DBD::mysql::db::selectrow_arrayref. That's because after the first
  279. call Perl has cached the method to speed up method calls.
  280. You may also see some names begin with an asterix ('C<*>').
  281. Both of these effects are subject to change in later releases.
  282.  
  283.  
  284. =back
  285.  
  286. Other magic cookie values may be added in the future.
  287.  
  288.  
  289. =head1 REPORTING
  290.  
  291. =head2 Report Format
  292.  
  293. The current profile data can be formatted and output using
  294.  
  295.     print $h->{Profile}->format;
  296.  
  297. To discard the profile data and start collecting fresh data
  298. you can do:
  299.  
  300.     $h->{Profile}->{Data} = undef;
  301.  
  302.  
  303. The default results format looks like this:
  304.  
  305.   DBI::Profile: 0.001015 seconds (5 method calls) programname
  306.   '' =>
  307.       0.000024s / 2 = 0.000012s avg (first 0.000015s, min 0.000009s, max 0.000015s)
  308.   'SELECT mode,size,name FROM table' =>
  309.       0.000991s / 3 = 0.000330s avg (first 0.000678s, min 0.000009s, max 0.000678s)
  310.  
  311. Which shows the total time spent inside the DBI, with a count of
  312. the total number of method calls and the name of the script being
  313. run, then a formated version of the profile data tree.
  314.  
  315. If the results are being formated when the perl process is exiting
  316. (which is usually the case when the DBI_PROFILE environment variable
  317. is used) then the percentage of time the process spent inside the
  318. DBI is also shown.
  319.  
  320. In the example above the paths in the tree are only one level deep and
  321. use the Statement text as the value (that's the default behaviour).
  322.  
  323. The merged profile data at the 'leaves' of the tree are presented
  324. as total time spent, count, average time spent (which is simply total
  325. time divided by the count), then the time spent on the first call,
  326. the time spent on the fastest call, and finally the time spent on
  327. the slowest call.
  328.  
  329. The 'avg', 'first', 'min' and 'max' times are not particularly
  330. useful when the profile data path only contains the statement text.
  331. Here's an extract of a more detailed example using both statement
  332. text and method name in the path:
  333.  
  334.   'SELECT mode,size,name FROM table' =>
  335.       'FETCH' =>
  336.           0.000076s
  337.       'fetchrow_hashref' =>
  338.           0.036203s / 108 = 0.000335s avg (first 0.000490s, min 0.000152s, max 0.002786s)
  339.  
  340. Here you can see the 'avg', 'first', 'min' and 'max' for the
  341. 108 calls to fetchrow_hashref() become rather more interesting.
  342. Also the data for FETCH just shows a time value because it was only
  343. called once.
  344.  
  345. Currently the profile data is output sorted by branch names. That
  346. may change in a later version so the leaf nodes are sorted by total
  347. time per leaf node.
  348.  
  349.  
  350. =head2 Report Destination
  351.  
  352. The default method of reporting is for the DESTROY method of the
  353. Profile object to format the results and write them using:
  354.  
  355.     DBI->trace_msg($results, 0);  # see $ON_DESTROY_DUMP below
  356.  
  357. to write them to the DBI trace() filehandle (which defaults to
  358. STDERR). To direct the DBI trace filehandle to write to a file
  359. without enabling tracing the trace() method can be called with a
  360. trace level of 0. For example:
  361.  
  362.     DBI->trace(0, $filename);
  363.  
  364. The same effect can be achieved without changing the code by
  365. setting the C<DBI_TRACE> environment variable to C<0=filename>.
  366.  
  367. The $DBI::Profile::ON_DESTROY_DUMP variable holds a code ref
  368. that's called to perform the output of the formatted results.
  369. The default value is:
  370.  
  371.   $ON_DESTROY_DUMP = sub { DBI->trace_msg($results, 0) };
  372.  
  373. Apart from making it easy to send the dump elsewhere, it can also
  374. be useful as a simple way to disable dumping results.
  375.  
  376. =head1 CHILD HANDLES
  377.  
  378. Child handles inherit a reference to the Profile attribute value
  379. of their parent.  So if profiling is enabled for a database handle
  380. then by default the statement handles created from it all contribute
  381. to the same merged profile data tree.
  382.  
  383.  
  384. =head1 CUSTOM DATA COLLECTION
  385.  
  386. =head2 Using The Path Attribute
  387.  
  388.   XXX example to be added later using a selectall_arrayref call
  389.   XXX nested inside a fetch loop where the first column of the
  390.   XXX outer loop is bound to the profile Path using
  391.   XXX bind_column(1, \${ $dbh->{Profile}->{Path}->[0] })
  392.   XXX so you end up with separate profiles for each loop
  393.   XXX (patches welcome to add this to the docs :)
  394.  
  395. =head2 Adding Your Own Samples
  396.  
  397. The dbi_profile() function can be used to add extra sample data
  398. into the profile data tree. For example:
  399.  
  400.     use DBI;
  401.     use DBI::Profile (dbi_profile dbi_time);
  402.  
  403.     my $t1 = dbi_time(); # floating point high-resolution time
  404.  
  405.     ... execute code you want to profile here ...
  406.  
  407.     my $t2 = dbi_time();
  408.     dbi_profile($h, $statement, $method, $t1, $t2);
  409.  
  410. The $h parameter is the handle the extra profile sample should be
  411. associated with. The $statement parameter is the string to use where
  412. the Path specifies DBIprofile_Statement. If $statement is undef
  413. then $h->{Statement} will be used. Similarly $method is the string
  414. to use if the Path specifies DBIprofile_MethodName. There is no
  415. default value for $method.
  416.  
  417. The $h->{Profile}{Path} attribute is processed by dbi_profile() in
  418. the usual way.
  419.  
  420. It is recommended that you keep these extra data samples separate
  421. from the DBI profile data samples by using values for $statement
  422. and $method that are distinct from any that are likely to appear
  423. in the profile data normally.
  424.  
  425.  
  426. =head1 SUBCLASSING
  427.  
  428. Alternate profile modules must subclass DBI::Profile to help ensure
  429. they work with future versions of the DBI.
  430.  
  431.  
  432. =head1 CAVEATS
  433.  
  434. Applications which generate many different statement strings
  435. (typically because they don't use placeholders) and profile with
  436. DBIprofile_Statement in the Path (the default) will consume memory
  437. in the Profile Data structure for each statement.
  438.  
  439. If a method throws an exception itself (not via RaiseError) then
  440. it won't be counted in the profile.
  441.  
  442. If a HandleError subroutine throws an exception (rather than returning
  443. 0 and letting RaiseError do it) then the method call won't be counted
  444. in the profile.
  445.  
  446. Time spent in DESTROY is added to the profile of the parent handle.
  447.  
  448. Time spent in DBI->*() methods is not counted. The time spent in
  449. the driver connect method, $drh->connect(), when it's called by
  450. DBI->connect is counted if the DBI_PROFILE environment variable is set.
  451.  
  452. Time spent fetching tied variables, $DBI::errstr, is counted.
  453.  
  454. DBI::PurePerl does not support profiling (though it could in theory).
  455.  
  456. A few platforms don't support the gettimeofday() high resolution
  457. time function used by the DBI (and available via the dbi_time() function).
  458. In which case you'll get integer resolution time which is mostly useless.
  459.  
  460. On Windows platforms the dbi_time() function is limited to millisecond
  461. resolution. Which isn't sufficiently fine for our needs, but still
  462. much better than integer resolution. This limited resolution means
  463. that fast method calls will often register as taking 0 time. And
  464. timings in general will have much more 'jitter' depending on where
  465. within the 'current millisecond' the start and and timing was taken.
  466.  
  467. This documentation could be more clear. Probably needs to be reordered
  468. to start with several examples and build from there.  Trying to
  469. explain the concepts first seems painful and to lead to just as
  470. many forward references.  (Patches welcome!)
  471.  
  472. =cut
  473.  
  474.  
  475. use strict;
  476. use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION $ON_DESTROY_DUMP);
  477. use Exporter ();
  478. use UNIVERSAL ();
  479. use Carp;
  480.  
  481. use DBI qw(dbi_time dbi_profile dbi_profile_merge);
  482.  
  483. $VERSION = sprintf "%d.%02d", '$Revision: 1.7 $ ' =~ /(\d+)\.(\d+)/;
  484.  
  485. @ISA = qw(Exporter);
  486. @EXPORT = qw(
  487.     DBIprofile_Statement
  488.     DBIprofile_MethodName
  489.     DBIprofile_MethodClass
  490.     dbi_profile
  491.     dbi_profile_merge
  492.     dbi_time
  493. );
  494. @EXPORT_OK = qw(
  495.     format_profile_thingy
  496. );
  497.  
  498. use constant DBIprofile_Statement    => -2100000001;
  499. use constant DBIprofile_MethodName    => -2100000002;
  500. use constant DBIprofile_MethodClass    => -2100000003;
  501.  
  502. $ON_DESTROY_DUMP = sub { DBI->trace_msg(shift, 0) };
  503.  
  504. sub new {
  505.     my $class = shift;
  506.     my $profile = { @_ };
  507.     return bless $profile => $class;
  508. }
  509.  
  510.  
  511. sub _auto_new {
  512.     my $class = shift;
  513.     my ($arg) = @_;
  514.  
  515.     # This sub is called by DBI internals when a non-hash-ref is
  516.     # assigned to the Profile attribute. For example
  517.     #    dbi:mysql(RaiseError=>1,Profile=>4/DBIx::MyProfile):dbname
  518.     # This sub works out what to do and returns a suitable hash ref.
  519.     
  520.     my ($path, $module, @args);
  521.  
  522.     # parse args
  523.     if ($arg =~ m!/!) {
  524.         # it's a path/module/arg/arg/arg list
  525.         ($path, $module, @args) = split /\s*\/\s*/, $arg, -1;
  526.     } elsif ($arg =~ /^\d+$/) {
  527.         # it's a numeric path selector
  528.         $path = $arg;
  529.     } else {
  530.         # it's a module name
  531.         $module = $arg;
  532.     }
  533.  
  534.     my @Path;
  535.     if ($path) {
  536.     my $reverse = ($path < 0) ? ($path=-$path, 1) : 0;
  537.     push @Path, "DBI"            if $path & 0x01;
  538.     push @Path, DBIprofile_Statement    if $path & 0x02;
  539.     push @Path, DBIprofile_MethodName    if $path & 0x04;
  540.     push @Path, DBIprofile_MethodClass    if $path & 0x08;
  541.     @Path = reverse @Path if $reverse;
  542.     } else {
  543.         # default Path
  544.         push @Path, DBIprofile_Statement;
  545.     }
  546.  
  547.     if ($module) {
  548.     if (eval "require $module") {
  549.       $class = $module;
  550.     }
  551.     else {
  552.         carp "Can't use $module for DBI profile: $@";
  553.     }
  554.     }
  555.  
  556.     return $class->new(Path => \@Path, @args);
  557. }
  558.  
  559.  
  560. sub format {
  561.     my $self = shift;
  562.     my $class = ref($self) || $self;
  563.     
  564.     my $prologue = "$class: ";
  565.     my $detail = $self->format_profile_thingy(
  566.     $self->{Data}, 0, "    ",
  567.     my $path = [],
  568.     my $leaves = [],
  569.     )."\n";
  570.  
  571.     if (@$leaves) {
  572.     dbi_profile_merge(my $totals=[], @$leaves);
  573.     my ($count, $dbi_time) = @$totals;
  574.     (my $progname = $0) =~ s:.*/::;
  575.     if ($count) {
  576.         $prologue .= sprintf "%f seconds ", $dbi_time;
  577.         my $perl_time = dbi_time() - $^T;
  578.         $prologue .= sprintf "%.2f%% ", $dbi_time/$perl_time*100
  579.         if $DBI::PERL_ENDING && $perl_time;
  580.         $prologue .= sprintf "(%d method calls) $progname\n", $count;
  581.     }
  582.  
  583.     if (@$leaves == 1 && $self->{Data}->{DBI}) {
  584.         $detail = "";    # hide it
  585.     }
  586.     }
  587.     return ($prologue, $detail) if wantarray;
  588.     return $prologue.$detail;
  589. }
  590.  
  591.  
  592. sub format_profile_leaf {
  593.     my ($self, $thingy, $depth, $pad, $path, $leaves) = @_;
  594.     croak "format_profile_leaf called on non-leaf ($thingy)"
  595.     unless UNIVERSAL::isa($thingy,'ARRAY');
  596.  
  597.     push @$leaves, $thingy if $leaves;
  598.     if (0) {
  599.     use Data::Dumper;
  600.     return Dumper($thingy);
  601.     }
  602.     my ($count, $total_time, $first_time, $min, $max, $first_called, $last_called) = @$thingy;
  603.     return sprintf "%s%fs\n", ($pad x $depth), $total_time
  604.     if $count <= 1;
  605.     return sprintf "%s%fs / %d = %fs avg (first %fs, min %fs, max %fs)\n",
  606.     ($pad x $depth), $total_time, $count, $count ? $total_time/$count : 0,
  607.     $first_time, $min, $max;
  608. }
  609.  
  610.  
  611. sub format_profile_branch {
  612.     my ($self, $thingy, $depth, $pad, $path, $leaves) = @_;
  613.     croak "format_profile_branch called on non-branch ($thingy)"
  614.     unless UNIVERSAL::isa($thingy,'HASH');
  615.     my @chunk;
  616.     my @keys = sort keys %$thingy;
  617.     while ( @keys ) {
  618.     my $k = shift @keys;
  619.     my $v = $thingy->{$k};
  620.     push @$path, $k;
  621.     push @chunk, sprintf "%s'%s' =>\n%s",
  622.         ($pad x $depth), $k,
  623.         $self->format_profile_thingy($v, $depth+1, $pad, $path, $leaves);
  624.     pop @$path;
  625.     }
  626.     return join "", @chunk;
  627. }
  628.  
  629.  
  630. sub format_profile_thingy {
  631.     my ($self, $thingy, $depth, $pad, $path, $leaves) = @_;
  632.     return $self->format_profile_leaf(  $thingy, $depth, $pad, $path, $leaves)
  633.     if UNIVERSAL::isa($thingy,'ARRAY');
  634.     return $self->format_profile_branch($thingy, $depth, $pad, $path, $leaves)
  635.     if UNIVERSAL::isa($thingy,'HASH');
  636.     return "$thingy\n";
  637. }
  638.  
  639.  
  640. sub on_destroy {
  641.     my $self = shift;
  642.     return unless $ON_DESTROY_DUMP;
  643.     return unless $self->{Data};
  644.     my $detail = $self->format();
  645.     $ON_DESTROY_DUMP->($detail) if $detail;
  646. }
  647.  
  648. sub DESTROY {
  649.     my $self = shift;
  650.     eval { $self->on_destroy };
  651.     if ($@) {
  652.         my $class = ref($self) || $self;
  653.         DBI->trace_msg("$class on_destroy failed: $@", 0);
  654.     }
  655. }
  656.  
  657. 1;
  658.  
  659.