__PACKAGE__->mk_classdata($_)
for qw/components arguments dispatcher engine log dispatcher_class
- engine_class context_class request_class response_class setup_finished/;
+ engine_class context_class request_class response_class stats_class
+ setup_finished/;
__PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
__PACKAGE__->engine_class('Catalyst::Engine::CGI');
__PACKAGE__->request_class('Catalyst::Request');
__PACKAGE__->response_class('Catalyst::Response');
+__PACKAGE__->stats_class('Catalyst::Stats');
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.7008';
+our $VERSION = '5.7013';
sub import {
my ( $class, @arguments ) = @_;
### in lib/MyApp.pm
use Catalyst qw/-Debug/; # include plugins here as well
- ### In lib/MyApp/Controller/Root.pm (autocreated)
+ ### In lib/MyApp/Controller/Root.pm (autocreated)
sub foo : Global { # called for /foo, /foo/1, /foo/1/2, etc.
my ( $self, $c, @args ) = @_; # args are qw/1 2/ for /foo/1/2
$c->stash->{template} = 'foo.tt'; # set the template
Specifies log level.
+=head2 -Stats
+
+Enables statistics collection and reporting. You can also force this setting
+from the system environment with CATALYST_STATS or <MYAPP>_STATS. The
+environment settings override the application, with <MYAPP>_STATS having the
+highest priority.
+
+e.g.
+
+ use Catalyst qw/-Stats=1/
+
=head1 METHODS
=head2 INFORMATION ABOUT THE CURRENT REQUEST
my $c = shift;
if (@_) {
my $stash = @_ > 1 ? {@_} : $_[0];
- croak('stash takes a hash or hashref') unless ref $stash;
+ croak('stash takes a hash or hashref') unless ref $stash;
foreach my $key ( keys %$stash ) {
$c->{stash}->{$key} = $stash->{$key};
}
$class->setup_plugins( delete $flags->{plugins} );
$class->setup_dispatcher( delete $flags->{dispatcher} );
$class->setup_engine( delete $flags->{engine} );
+ $class->setup_stats( delete $flags->{stats} );
for my $flag ( sort keys %{$flags} ) {
contain GET parameter key/value pairs, which will be appended to the URI
in standard fashion.
+Note that uri_for is destructive to the passed hashref. Subsequent calls
+with the same hashref may have unintended results.
+
Instead of C<$path>, you can also optionally pass a C<$action> object
which will be resolved to a path using
C<< $c->dispatcher->uri_for_action >>; if the first element of
$val = '' unless defined $val;
(map {
$_ = "$_";
- utf8::encode( $_ );
+ utf8::encode( $_ ) if utf8::is_utf8($_);
# using the URI::Escape pattern here so utf8 chars survive
s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
s/ /+/g;
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
- <meta http-equiv="Content-Language" content="en" />
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>$name on Catalyst $VERSION</title>
<style type="text/css">
body {
<p>That really depends on what <b>you</b> want to do.
We do, however, provide you with a few starting points.</p>
<p>If you want to jump right into web development with Catalyst
- you might want want to start with a tutorial.</p>
+ you might want to start with a tutorial.</p>
<pre>perldoc <a href="http://cpansearch.perl.org/dist/Catalyst-Manual/lib/Catalyst/Manual/Tutorial.pod">Catalyst::Manual::Tutorial</a></code>
</pre>
<p>Afterwards you can go on to check out a more complete look at our features.</p>
return $c->state;
}
- my $stats_info = $c->_stats_start_execute( $code ) if $c->debug;
+ my $stats_info = $c->_stats_start_execute( $code ) if $c->use_stats;
push( @{ $c->stack }, $code );
eval { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) };
- $c->_stats_finish_execute( $stats_info ) if $c->debug and $stats_info;
+ $c->_stats_finish_execute( $stats_info ) if $c->use_stats and $stats_info;
my $last = pop( @{ $c->stack } );
}
}
- my $node = Tree::Simple->new(
- {
- action => $action,
- elapsed => undef, # to be filled in later
- comment => "",
- }
- );
- $node->setUID( "$code" . $c->counter->{"$code"} );
+ my $uid = "$code" . $c->counter->{"$code"};
# is this a root-level call or a forwarded call?
if ( $callsub =~ /forward$/ ) {
# forward, locate the caller
if ( my $parent = $c->stack->[-1] ) {
- my $visitor = Tree::Simple::Visitor::FindByUID->new;
- $visitor->searchForUID(
- "$parent" . $c->counter->{"$parent"} );
- $c->stats->accept($visitor);
- if ( my $result = $visitor->getResult ) {
- $result->addChild($node);
- }
+ $c->stats->profile(
+ begin => $action,
+ parent => "$parent" . $c->counter->{"$parent"},
+ uid => $uid,
+ );
}
else {
# forward with no caller may come from a plugin
- $c->stats->addChild($node);
+ $c->stats->profile(
+ begin => $action,
+ uid => $uid,
+ );
}
}
else {
-
+
# root-level call
- $c->stats->addChild($node);
+ $c->stats->profile(
+ begin => $action,
+ uid => $uid,
+ );
}
+ return $action;
- return {
- start => [gettimeofday],
- node => $node,
- };
}
sub _stats_finish_execute {
my ( $c, $info ) = @_;
- my $elapsed = tv_interval $info->{start};
- my $value = $info->{node}->getNodeValue;
- $value->{elapsed} = sprintf( '%fs', $elapsed );
+ $c->stats->profile( end => $info );
}
=head2 $c->_localize_fields( sub { }, \%keys );
$c->finalize_body;
}
- if ($c->debug) {
- my $elapsed = sprintf '%f', tv_interval($c->stats->getNodeValue);
+ if ($c->use_stats) {
+ my $elapsed = sprintf '%f', $c->stats->elapsed;
my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed;
-
- my $t = Text::SimpleTable->new( [ 62, 'Action' ], [ 9, 'Time' ] );
- $c->stats->traverse(
- sub {
- my $action = shift;
- my $stat = $action->getNodeValue;
- $t->row( ( q{ } x $action->getDepth ) . $stat->{action} . $stat->{comment},
- $stat->{elapsed} || '??' );
- }
- );
-
$c->log->info(
- "Request took ${elapsed}s ($av/s)\n" . $t->draw . "\n" );
+ "Request took ${elapsed}s ($av/s)\n" . $c->stats->report . "\n" );
}
return $c->response->status;
}
}
else {
+ # everything should be bytes at this point, but just in case
$c->response->content_length( bytes::length( $c->response->body ) );
}
}
}
);
+ $c->stats($class->stats_class->new)->enable($c->use_stats);
if ( $c->debug ) {
- $c->stats(Tree::Simple->new([gettimeofday]));
$c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
}
$c->prepare_cookies;
$c->prepare_path;
- # On-demand parsing
- $c->prepare_body unless $c->config->{parse_on_demand};
+ # Prepare the body for reading, either by prepare_body
+ # or the user, if they are using $c->read
+ $c->prepare_read;
+
+ # Parse the body unless the user wants it on-demand
+ unless ( $c->config->{parse_on_demand} ) {
+ $c->prepare_body;
+ }
}
my $method = $c->req->method || '';
- my $path = $c->req->path || '/';
+ my $path = $c->req->path;
+ $path = '/' unless length $path;
my $address = $c->req->address || '';
$c->log->debug(qq/"$method" request for "$path" from "$address"/)
You have to set C<< MyApp->config->{parse_on_demand} >> to use this
directly.
+Warning: If you use read(), Catalyst will not process the body,
+so you will not be able to access POST parameters or file uploads via
+$c->request. You must handle all body parsing yourself.
+
=cut
sub read { my $c = shift; return $c->engine->read( $c, @_ ) }
my %comps = map { $_ => 1 } @comps;
for my $component ( @comps ) {
+
+ # We pass ignore_loaded here so that overlay files for (e.g.)
+ # Model::DBI::Schema sub-classes are loaded - if it's in @comps
+ # we know M::P::O found a file on disk so this is safe
+
Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
my $module = $class->setup_component( $component );
=cut
+=head2 $c->setup_stats
+
+Sets up timing statistics class.
+
+=cut
+
+sub setup_stats {
+ my ( $class, $stats ) = @_;
+
+ Catalyst::Utils::ensure_class_loaded($class->stats_class);
+
+ my $env = Catalyst::Utils::env_value( $class, 'STATS' );
+ if ( defined($env) ? $env : ($stats || $class->debug ) ) {
+ no strict 'refs';
+ *{"$class\::use_stats"} = sub { 1 };
+ $class->log->debug('Statistics enabled');
+ }
+}
+
+
=head2 $c->registered_plugins
Returns a sorted list of the plugins which have either been stated in the
my ( $proto, $plugin, $instant ) = @_;
my $class = ref $proto || $proto;
- Catalyst::Utils::ensure_class_loaded( $plugin, { ignore_loaded => 1 } );
+ # no ignore_loaded here, the plugin may already have been
+ # defined in memory and we don't want to error on "no file" if so
+
+ Catalyst::Utils::ensure_class_loaded( $plugin );
$proto->_plugins->{$plugin} = 1;
unless ($instant) {
Returns an arrayref of the internal execution stack (actions that are
currently executing).
+=head2 $c->stats_class
+
+Returns or sets the stats (timing statistics) class.
+
+=head2 $c->use_stats
+
+Returns 1 when stats collection is enabled. Stats collection is enabled
+when the -Stats options is set, debug is on or when the <MYAPP>_STATS
+environment variable is set.
+
+Note that this is a static method, not an accessor and should be overloaded
+by declaring "sub use_stats { 1 }" in your MyApp.pm, not by calling $c->use_stats(1).
+
+=cut
+
+sub use_stats { 0 }
+
+
=head2 $c->write( $data )
Writes $data to the output stream. When using this method directly, you
=head1 ON-DEMAND PARSER
The request body is usually parsed at the beginning of a request,
-but if you want to handle input yourself or speed things up a bit,
-you can enable on-demand parsing with a config parameter.
+but if you want to handle input yourself, you can enable on-demand
+parsing with a config parameter.
MyApp->config->{parse_on_demand} = 1;
Sascha Kiefer
+Sebastian Willert
+
Tatsuhiko Miyagawa
Ulf Edvinsson