DashProfiler is a lightweight code instrumentation tool that allows developers to measure execution times of specific code blocks with minimal overhead. It works by aggregating timing samples into a tree structure, profiling data at different levels of granularity. Samples are automatically flushed to disk periodically. DashProfiler requires only one line of code per sample and has a very low time cost per sample of around 0.000021 seconds. It can group samples into logical periods and measure exclusive execution times. The profiling data it collects can be used to analyze performance and identify bottlenecks.
2. A Problem
A web application ~100K lines of code
Using many external services
If response time goes up... what’s causing it?
Continuous monitoring in production
Must have very low CPU and I/O cost
Minimal code changes
3. A Typical Approach
package MyNetIO;
sub send_request {
my ($hostname, $request) = @_
...send to $hostname...
}
How much time was spent sending the request?
4. A Typical Approach
package MyNetIO;
use Time::Hires qw(time);
sub send_request {
my ($hostname, $request) = @_
my $start = time();
...send to $hostname...
$durations->{MyNetIO}{$hostname} = time() - $start;
}
• Doesn’t record count so can’t produce averages.
• Two lines of code. Worse if multiple return statements.
• Doesn’t record time if function exits via an exception.
Still need to write code to flush
6. DashProfiler
• Can group samples into granular time units
• Can measure exclusive time in a period
• Can flush to disk at intervals
• Just needs one line of code per sample
7. DashProfiler Internals
Built on DBI::Profile, part of the DBI
Aggregates measurements into a data tree
Two-level tree by default:
$root->{ $key1 }->{ $key2 }->[ ...leaf node... ]
$root->{ ‘MyNetIO’ }->{ $hostname }->[ ...leaf node... ]
8. DashProfiler Data
Each leaf node in the tree is a reference to an array:
$root->{ $key1 }->{ $key2 } = [
106, # 0: count of samples at this node
0.0312958955764771, # 1: total duration
0.000490069389343262, # 2: first duration
0.000176072120666504, # 3: shortest duration
0.00140702724456787, # 4: longest duration
1023115819.83019, # 5: time of first sample
1023115819.86576, # 6: time of last sample
]
First sample populates all
Later samples always update 0, 1, and 6
and may update 3 or 4
9. DashProfiler By-Time
Optional extra time level in the data tree
$time = int(time() / $granularity) * $granularity;
$root->{ $time }->{ ‘MyNetIO’ }->{ $hostname }->[ ... ]
So a new sub-tree is grown each granularity seconds
10. DashProfiler Config
use DashProfiler;
DashProfiler->add_profile( foo => { } );
DashProfiler->add_profile( foo => {
granularity => 10,
flush_interval => 600,
flush_hook => sub { ... },
sample_class => ‘DashProfiler::Sample’,
dbi_profile_class => ‘DBI::Profile’,
period_exclusive => ...,
period_summary => ...,
...
});
Create named profiles
Lots of features
11. Without DashProfiler
package MyNetIO;
use Time::Hires qw(time);
sub send_request {
my ($hostname, $request) = @_
my $start = time();
...send to $hostname...
$durations->{MyNetIO}{$hostname} = time() - $start;
}
12. Without DashProfiler
package MyNetIO;
use DashProfiler::Import foo_profiler => [ ‘MyNetIO’ ];
sub send_request {
my ($hostname, $request) = @_
my $sample = foo_profiler( $hostname );
...send to $hostname...
}
- imports a pre-curried profiler code ref
DashProfiler::Import
- Profilers return bless object containing timestamp
- Object destruction triggers accumulation of sample
13. With DashProfiler
Name of profile created with add_profile()
Value to use for ‘key1’
package MyNetIO;
use DashProfiler::Import foo_profiler => [ ‘MyNetIO’ ];
sub send_request {
Value to use for ‘key2’
my ($hostname, $request) = @_
my $sample = foo_profiler( $hostname );
...send to $hostname...
}
Duration is measured when
$sample goes out of scope
14. With DashProfiler
package MyNetIO;
use DashProfiler::Import foo_profiler => [ ‘MyNetIO’ ];
sub send_request {
my ($hostname, $request) = @_
my $sample = foo_profiler( $hostname )
if foo_profiler_enabled();
...send to $hostname...
Automatically imported
compile-time constant
} reduces cost to zero
if profile is disabled
15. DashProfiler Flush
Data is written to STDERR on exit, by default
Regular flushing is enabled by specifying a flush_interval
The dbi_profile_class handles the flush. Choices include:
DBI::Profile
DBI::ProfileData
DBI::ProfileData::Apache
DashProfiler->add_profile( foo => {
...,
flush_interval => 600,
dbi_profile_class => ‘DBI::ProfileData’,
flush_hook => sub { ... },
...
});
16. DashProfiler Periods
• Group samples into periods
- e.g. http request to response
- start_sample_period() and end_sample_period()
- counted, to enable averages and totals per period
- can output period counts instead of sample counts
• Measure ‘exclusive’ time
- time from period start to end that’s not been
accounted for by other samples
- enabled via period_exclusive option
17. Example Data
Average response times over 24 hours
DashProfiler doesn’t generate graphs itself, but the
data can be used to create graphs like these
19. DashProfiler Perspectives
• Each DashProfiler can have multiple DBI
Profile objects attached
• Samples accumulate in all attached profiles
• Each profile can have a different Path
• giving different ‘perspectives’ or level of detail
- key1 + key2
- key1 + country + browser type
- key2 + browser type
- ... etc.
20. DashProfiler Per-Period
• Optional extra ‘per-period’ DBI profile
• Enabled via period_summary option
• Automatically attached and reset by
start_sample_period()
• Gives current totals for this period
• Great for ‘debug footers’ on web page showing
how much time was spent generating this page
21. DashProfiler Cost
Time cost of taking a sample:
0.000021s
- Time to create sample object, destroy it, accumulate the counts
- In hot code can be 0.000015s
- (Timings made on a 2GHz MacBook Pro Intel Core Duo)
- (Could be made much faster by porting sampler class to C)