XAO-Web
view release on metacpan or search on metacpan
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
also more efficient because templates with known paths are cached in
parsed state the first time they are used while inlined templates are
parsed every time.
It is usually a good idea to make templates as simple as possible and move
most of the logic inside of objects. To comment what you're doing in
various parts of template you can use normal HTML-style comments. They
are removed from the output completely, so you can include any amount
of text inside of comments -- it won't impact the size of final HTML
file. Here is an example:
<!-- Header section -->
<%Header title="demosite.com"%>
<%Page path="/bits/menu"%>
<!-- Main part -->
<%Page path="/bits/body"%>
<!-- Footer -->
<%Footer%>
One exception is JavaScript code which is usually put into comments. The
parser will NOT remove comments if the opening comment is <!--//. Here is an
example of JavaScript code:
<script type="text/javascript"><!--//
function foo ()
{ alert("bar");
}
//-->
</script>
=head2 CACHING
Parsed templates are always cached either locally or using a configured
cache. The cache is keyed on 'path' or 'template' parameters value (two
identical 'template's will only parse once). Parse cache can be disabled
by giving a "xao.uncached" parameter. See parse() method description
for details.
The fully rendered content can also be cached if a couple of conditions
are met:
=over
=item *
/xao/page/render_cache_name in the config -- this should contain a name of
the cache to be used for rendered page components.
=item *
The page is configured to be cacheable with either an entry in
the configuration under '/xao/page/render_cache_allow' or with a
'xao.cacheable' parameter given (e.g. something like <%Page ...
xao.cacheable%>).
=item *
There is no "/xao/page/render_cache_update" in the clipboard. This can be used
to force cache reload by checking some environmental variable early in
the flow and setting the clipboard to disable all render caches for that
one render. Cached content is not used, but is updated -- so subsequent
cached calls with the same parameters will return new content.
=item *
There is no "/xao/page/render_cache_skip" in the clipboard. This can be used to
skip cache altogether if it is known that pages rendered in this session
are different from cached and the cache does not want to be contaminated
with them.
=back
Properly used render cache can speed up pages significantly, but if
used incorrectly it can also introduce very hard to find issues in the
rendered content.
Carefully consider what pages to tag with "cacheable" tag. Benchmarking
reports can be of great help for that.
Entries in the config /xao/page/render_cache_allow may include additional
specifications for what parameters are checked when rendered content is
cached. By default, if the value is '1' or 'on' all of Page template
parameters are checked, but none of CGI or cookies. Values for
parameters 'path' and 'template' are always checked, regardless of the
configuration.
The configuration can look like this:
xao => {
page => {
render_cache_name => 'xao_page_render',
render_cache_allow => {
'p:/bits/complex-template' => 1,
'p:/bits/complex-cgi' => {
param => [ '*' ],
cgi => [ 'cf*' ],
},
'p:/bits/complex-cookie' => {
param => [ '*', '!session*' ],
cookie => [ 'session' ],
},
},
},
}
=head2 BENCHMARKING
Benchmarking can be started and stopped by using benchmark_start()
and benchmark_stop() calls. The hash with current benchmarking data can
be retrieved with benchmark_stats() call.
When benchmarking is started all rendered paths (and optionally all
templates) are timed and are also analyzed for potential cacheability --
if rendered content is repeatedly the same for some set of parameters.
Custom execution paths spanning multiple templates can be tracked by
using benchmark_enter($tag) and benchmark_leave($tag) calls.
The data is "static", not specific to a particular Page object.
Benchmarking slows down processing. Do not use it in production.
For an easy way to control benchmarking from templates use <%Benchmark%>
object.
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
The output of the test above would be:
{}""
[]
""{}
In fact first two SetArg's would add two empty lines in front because
they have carriage returns after them, but this is only significant if
your HTML code is space-sensitive.
=head1 METHODS
Publicly accessible methods of Page (and therefor of all objects derived
from Page unless overwritten) are:
=over
=cut
###############################################################################
package XAO::DO::Web::Page;
use warnings;
use strict;
use utf8;
use Digest::SHA qw(sha1_hex);
use Encode;
use Time::HiRes qw(gettimeofday tv_interval);
use JSON qw(to_json);
use XAO::Cache;
use XAO::Objects;
use XAO::PageSupport;
use XAO::Projects qw(:all);
use XAO::Templates;
use XAO::Utils;
use Error qw(:try);
use base XAO::Objects->load(objname => 'Atom');
# Prototypes
#
sub cache ($%);
sub cgi ($);
sub check_db ($);
sub dbh ($);
sub display ($%);
sub expand ($%);
sub finaltextout ($%);
sub object ($%);
sub odb ($);
sub parse ($%);
sub siteconfig ($);
sub textout ($%);
sub benchmark_enabled ($);
sub benchmark_enter ($$;$$$);
sub benchmark_leave ($$;$$);
sub benchmark_reset ($);
sub benchmark_start ($;$);
sub benchmark_stats ($;$);
sub benchmark_stop ($);
sub page_clipboard ($);
sub _do_pass_args ($$$);
###############################################################################
sub params_digest ($$;$) {
my ($self,$args,$spec)=@_;
# Dropping non-scalar values from params. They get in by calling
# ::Action::data_... methods for example, and in other scenarios
# too.
#
my $params={ map { ref $args->{$_} ? () : ($_ => $args->{$_}) } keys %$args };
# Template and path are always passed along
#
my $path=delete $params->{'path'};
my $template=delete $params->{'template'};
# Checking what is considered important for the digest, getting a
# specification. It may come from outside in testing.
#
if(!$spec) {
$spec=$args->{'xao.cacheable'};
}
if(!$spec && !defined $args->{'template'} && (my $path=$args->{'path'})) {
my $cache_allow=$self->{'cache_allow'};
if($cache_allow) {
$spec=$cache_allow->{'p:'.$path};
}
}
# It may be a hash of instructions about what to keep and what to
# drop for the key:
#
# param => [ 'FOO*', '!FOO.BAR*' ],
# cgi => [ 'fn', 'fv' ],
# cookie => [ 'customer_id' ],
#
# Default is to ignore cookies and CGI and hash all scalar
# parameters.
#
my $cgis;
my $cookies;
my $protocol;
if($spec && ref($spec)) {
while(my ($spec_key,$spec_list)=each %$spec) {
my $hash;
my $target;
if($spec_key eq 'param') {
$hash=$params;
$target=\$params;
}
elsif($spec_key eq 'cgi') {
my $cgi=$self->cgi;
$hash={ map { $_ => [ $cgi->param($_) ] } $cgi->param };
$target=\$cgis;
}
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
$text=$args->{$objname};
# Executing object if not.
#
if(!defined $text) {
my $obj=$self->object(objname => $objname);
# Preparing arguments. If argument includes object references -
# they are expanded first.
#
my %objargs;
my $ia=$item->{'args'};
my $args_copy;
my $page_obj;
foreach my $a (keys %$ia) {
my $v=$ia->{$a};
if(ref($v)) {
if(@$v==1 && exists($v->[0]->{'text'})) {
$v=$v->[0]->{'text'};
}
else {
if(!$args_copy) {
$args_copy=merge_refs($args);
delete $args_copy->{'path'};
}
if(!$page_obj) {
$page_obj=$self->object(objname => 'Page');
}
$args_copy->{'template'}=$v;
$v=$page_obj->expand($args_copy);
}
}
# Decoding entities from arguments. Lt, gt, amp,
# quot and &#DEC; are supported.
#
$v=~s/</</sg;
$v=~s/>/>/sg;
$v=~s/"/"/sg;
$v=~s/&#(\d+);/chr($1)/sge;
$v=~s/&/&/sg;
$objargs{$a}=$v;
}
# Executing object. For speed optimisation we call object's
# display method directly if we're not going to do anything
# with the text anyway. This way we avoid push/pop and at
# least two extra memcpy's.
#
if($itemflag && $itemflag ne 't') {
$text=$obj->expand(\%objargs);
}
else {
$obj->display(\%objargs);
}
# Indicator that we do not need to parse or display anything
# after that point.
#
$stop_after=$self->clipboard->get('_no_more_output');
# Was it something like SetArg object? Merging changes in then.
#
if($self->{'merge_args'}) {
@{$args}{keys %{$self->{'merge_args'}}}=values %{$self->{'merge_args'}};
delete $self->{'merge_args'};
}
}
}
if(defined $text) {
# When the text is from an external argument like \xe9
# it might be stored in a platform encoding and not in
# Unicode. Upgrading it.
#
utf8::upgrade($text) if $self->_character_mode && !$item->{'binary'};
# Safety conversion - q for query, h - for html, s - for
# nbsp'ced html, f - for tag fields, u - for URLs, t - for text
# as is (default).
#
if($itemflag && $itemflag ne 't') {
if($itemflag eq 'h') {
$text=XAO::Utils::t2ht($text);
}
elsif($itemflag eq 's') {
$text=(defined $text && length($text)) ? XAO::Utils::t2ht($text) : " ";
}
elsif($itemflag eq 'q') {
$text=XAO::Utils::t2hq($text);
}
elsif($itemflag eq 'f') {
$text=XAO::Utils::t2hf($text);
}
elsif($itemflag eq 'u') {
$text=XAO::Utils::t2hq($text);
}
elsif($itemflag eq 'j') {
$text=XAO::Utils::t2hj($text);
}
else {
eprint "Unsupported translation flag '$itemflag', objname=",$item->{'objname'};
}
}
# Sending out the text
#
$self->textout($text);
}
# Checking if this object required to stop processing
#
last if $stop_after;
}
# We need to return the actual rendered content if this is called
# from cache render.
#
my $content=undef;
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
return $content;
}
###############################################################################
sub _character_mode ($) {
my $self=shift;
return $self->{'character_mode'} if exists $self->{'character_mode'};
my $character_mode=$self->siteconfig->get('/xao/page/character_mode') ? 1 : 0;
$self->{'character_mode'}=$character_mode;
return $character_mode;
}
###############################################################################
sub _render_cache ($) {
my $self=$_[0];
return $self->{'render_cache_obj'} if exists $self->{'render_cache_obj'};
my $cache_name=$self->siteconfig->get('/xao/page/render_cache_name') || '';
my $cache_obj;
if($cache_name) {
dprint "Using a cache '$cache_name' for rendered templates";
$cache_obj=$self->cache(
name => $cache_name,
coords => [ 'cache_key' ],
retrieve => \&_do_display,
);
}
$self->{'render_cache_obj'}=$cache_obj;
return $cache_obj;
}
###############################################################################
# In case of memcached this clears ALL caches, not just render!
sub render_cache_clear ($) {
my $self=$_[0];
my $cache=$self->_render_cache;
$cache->drop_all if $cache;
}
###############################################################################
sub can_cache_render ($$) {
my ($self,$args)=@_;
return 0 if $self->page_clipboard->{'render_cache_skip'};
return 1 if $args->{'xao.cacheable'};
my $path=!defined $args->{'template'} && $args->{'path'};
return 0 unless $path;
my $cache_key='p:' . $path;
my $cache_allow=$self->{'cache_allow'};
if(!$cache_allow) {
$cache_allow=$self->siteconfig->get('/xao/page/render_cache_allow');
if($cache_allow) {
$self->{'cache_allow'}=$cache_allow;
}
else {
$cache_allow=$self->{'cache_allow'}={ };
$self->siteconfig->put('/xao/page/render_cache_allow' => $cache_allow);
}
}
return $cache_allow->{$cache_key};
}
###############################################################################
=item display (%)
Displays given template to the current output buffer. The system uses
buffers to collect all text displayed by various objects in a rather
optimal way using XAO::PageSupport (see L<XAO::PageSupport>)
module. In XAO::Web handler the global buffer is initialized and after all
displayable objects have worked their way it retrieves whatever was
accumulated in that buffer and displays it.
This way you do not have to think about where your output goes as long
as you do not "print" anything by yourself - you should always call
either display() or textout() to print any piece of text.
Display() accepts the following arguments:
=over
=item pass
Passes arguments from calling context into the template.
The syntax allows to map parent arguments into new names,
and/or to limit what is passed. Multiple semi-colon separated rules are
allowed. Rules are processed from left to right.
NEWNAME=OLDNAME - pass the value of OLDNAME as NEWNAME
NEW*=OLD* - pass all old values starting with OLD as NEW*
VAR;VAR.* - pass VAR and VAR.* under their own names
*;!VAR* - pass everything except VAR*
The default, when the value of 'pass' is 'on' or '1', is the same as
passing '*' -- meaning that all parent arguments are passed literally
under their own names.
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
Gives Page a path to the template that should be processed and
displayed.
=item template => 'template text'
Provides Page with the actual template text.
=item unparsed => 1
If set it does not parse template, just displays it literally.
=back
Any other argument given is passed into template unmodified as a
variable. Remember that it is recommended to pass variables using
all-capital names for better visual recognition.
Example:
$obj->display(path => "/bits/left-menu", ITEM => "main");
For security reasons it is also recommended to put all sub-templates
into /bits/ directory under templates tree or into "bits" subdirectory
of some tree inside of templates (like /admin/bits/admin-menu). Such
templates cannot be displayed from XAO::Web handler by passing their
path in URL.
=cut
sub display ($%) {
my $self=shift;
my $args=$self->{'args'}=get_args(\@_);
# Merging parent's args in if requested.
#
if($args->{'pass'}) {
$args=$self->{'args'}=$self->pass_args($args->{'pass'},$args);
}
# Is this page cacheable? There is a distinction between page not
# being cached with '/xao/page/render_cache_skip' and page being flushed in
# cache with '/xao/page/render_cache_update'.
#
if($self->can_cache_render($args)) {
if(my $cache=$self->_render_cache()) {
# The key depends on all arguments.
#
my ($cache_key,$params_json)=$self->params_digest($args);
if($self->debug_check('render-cache-get')) {
dprint "RENDER_CACHE_GET: $cache_key / $params_json";
}
# Building the content. Real arguments for displaying are in
# $self->{'args'}.
#
my $content=$cache->get($self,{
cache_key => $cache_key,
force_update => ($self->page_clipboard->{'render_cache_update'} || $args->{'xao.uncached'}),
});
$self->textout($content);
return;
}
}
# We get here if the page cannot be cached
#
$self->_do_display();
}
###############################################################################
=item expand (%)
Returns a string corresponding to the expanded template. Accepts exactly
the same arguments as display(). Here is an example:
my $str=$obj->expand(template => '<%Date%>');
=cut
sub expand ($%) {
my $self=shift;
my $args=get_args(\@_);
# First it prepares a place in stack for new text (push) and after
# display it calls pop to get back whatever was written. The sole
# reason for all this is speed optimization - XAO::PageSupport is
# implemented in C in quite optimal way.
#
XAO::PageSupport::push();
# Not using Error's try{} -- it is too slow. Benchmarking showed
# about 7% slowdown.
#
### my $args=get_args(\@_);
### try {
### $self->display($args);
### }
### otherwise {
### my $e=shift;
###
### # Popping out the potential output of the failed
### # template. Otherwise we are going to break the stack order.
### #
### XAO::PageSupport::pop();
###
### $e->throw();
### };
# Eval is faster, almost indistinguishable from the bare call on
# benchmark results.
#
eval {
$self->display($args);
};
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
###############################################################################
=item textout ($)
Displays a piece of text literally, without any changes.
It used to be called as textout(text => "text") which is still
supported for compatibility, but is not recommended any more. Call it
with single argument -- text to be displayed.
Example:
$obj->textout("Text to be displayed");
This method is the only place where text is actually gets displayed. You
can override it if you really need some other output strategy for you
object. Although it is not recommended to do so.
=cut
sub textout ($%) {
my $self=shift;
return unless @_;
my $text;
if(@_ == 1) {
$text=$_[0];
}
else {
my %args=@_;
$text=$args{'text'} // '';
}
if(Encode::is_utf8($text)) {
XAO::PageSupport::addtext(Encode::encode_utf8($text));
}
else {
XAO::PageSupport::addtext($text);
}
}
###############################################################################
=item finaltextout ($)
Displays some text and stops processing templates on all levels. No more
objects should be called in this session and no more text should be
printed.
Used in Redirect object to break execution immediately for example.
Accepts the same arguments as textout() method.
=cut
sub finaltextout ($%) {
my $self=shift;
$self->textout(@_);
$self->clipboard->put(_no_more_output => 1);
}
###############################################################################
=item dbh ()
Returns current database handler or throws an error if it is not
available.
Example:
sub display ($%)
my $self=shift;
my $dbh=$self->dbh;
# if you got this far - you have valid DB handler on hands
}
=cut
sub dbh ($) {
my $self=shift;
return $self->{dbh} if $self->{'dbh'};
$self->{dbh}=$self->siteconfig->dbh;
return $self->{dbh} if $self->{dbh};
throw $self "- no database connection";
}
###############################################################################
=item odb ()
Returns current object database handler or throws an error if it is not
available.
Example:
sub display ($%) {
my $self=shift;
my $odb=$self->odb;
# ... if you got this far - you have valid DB handler on hands
}
=cut
sub odb ($) {
my $self=shift;
return $self->{odb} if $self->{odb};
$self->{odb}=$self->siteconfig->odb;
return $self->{odb} if $self->{odb};
throw $self "- requires object database connection";
}
###############################################################################
=item cache (%)
A shortcut that actually calls $self->siteconfig->cache. See the
description of cache() in L<XAO::DO::Web::Config> for more details.
=cut
sub cache ($%) {
my $self=shift;
my $args=get_args(\@_);
return $self->siteconfig->cache($args);
}
###############################################################################
=item cgi ()
Returns CGI object reference (see L<CGI>) or throws an error if it is
not available.
=cut
sub cgi ($) {
my $self=shift;
$self->siteconfig->cgi;
}
###############################################################################
=item clipboard ()
Returns clipboard object, which inherets XAO::SimpleHash methods. Use
this object to pass data between various objects that work together to
produce a page. Clipboard is cleaned before starting every new session.
=cut
sub clipboard ($) {
my $self=shift;
my $clipboard=$self->{'clipboard'};
if(!$clipboard) {
$clipboard=$self->{'clipboard'}=$self->siteconfig->clipboard;
}
return $clipboard;
}
###############################################################################
=item siteconfig ()
Returns site configuration reference. Be careful with your changes to
configuration, try not to change configuration -- use clipboard to pass
data between objects. See L<XAO::Projects> for more details.
=cut
sub siteconfig ($) {
my $self=shift;
my $siteconfig=$self->{'siteconfig'};
if(!$siteconfig) {
$siteconfig=$self->{'siteconfig'}=
$self->{'sitename'} ? get_project($self->{'sitename'})
: get_current_project();
}
return $siteconfig;
}
###############################################################################
=item base_url (%)
Returns base_url for secure or normal connection. Depends on parameter
"secure" if it is set, or current state if it is not.
If 'active' parameter is set then will return active URL, not the base
one. In most practical cases active URL is the same as base URL except
when your server is set up to answer for many domains. Base will stay
at what is set in the site configuration and active will be the one
taken from the Host: header.
Examples:
# Returns secure url in secure mode and normal
# url in normal mode.
#
my $url=$self->base_url;
# Return secure url no matter what
#
my $url=$self->base_url(secure => 1);
# Return normal url no matter what
#
my $url=$self->base_url(secure => 0);
# Return secure equivalent of the current active URL
#
my $url=$self->base_url(secure => 1, active => 1);
=cut
sub base_url ($;%) {
my $self=shift;
my $args=get_args(\@_);
my $secure=$args->{secure};
$secure=$self->is_secure unless defined $secure;
my $active=$args->{active};
my $url;
if($secure) {
$url=$active ? $self->clipboard->get('active_url_secure')
: $self->siteconfig->get('base_url_secure');
} else {
$url=$active ? $self->clipboard->get('active_url')
: $self->siteconfig->get('base_url');
}
return $url;
}
###############################################################################
=item is_secure ()
Returns 1 if the current the current connection is a secure one or
0 otherwise. If there is a defined cgi() value then the result is
defined by its https() method; otherwise the default is taken from
/xao/page/default_https configuration variable. The later is useful for
scripts that don't have a CGI environment.
=cut
sub is_secure ($) {
my $self=shift;
my $cgi=$self->cgi;
if($cgi) {
return $cgi->https() ? 1 : 0;
}
else {
return $self->siteconfig->get('/xao/page/default_https') ? 1 : 0;
}
}
###############################################################################
=item pageurl (%)
Returns full URL of current page without parameters. Accepts the same
arguments as base_url() method.
=cut
sub pageurl ($;%) {
my $self=shift;
my $pagedesc=$self->clipboard->get('pagedesc') ||
throw $self "- no Web context, needs clipboard->'pagedesc'";
my $url=$self->base_url(@_);
# This works in both CGI and PSGI environments, but simply
# requesting $cgi->url(-absolute => 1) does not work for PSGI
# because it sets PATH_INFO and REQUEST_URI to the same value,
# making them cancel each other.
#
my $uri=$self->cgi->request_uri();
$uri =~ s/\?.*$//s;
$uri = $self->cgi->unescape($uri);
return $url.$uri;
}
###############################################################################
sub _do_pass_args ($$$) {
my ($self,$pargs,$spec)=@_;
my $hash={ };
foreach my $rule (@$spec) {
$rule=~s/^\s*(.*?)\s*$/$1/;
### dprint "...rule='$rule'";
if($rule eq '*') {
$hash=merge_refs($pargs,$hash);
}
elsif($rule =~ /^([\w\.]+)\s*=\s*([\w\.]+)$/) { # VAR=FOO
$hash->{$1}=$pargs->{$2};
}
elsif($rule =~ /^([\w\.]*)\*([\w\.]*)\s*=\s*([\w\.]*)\*([\w\.]*)$/) {# VAR*=FOO* or *VAR=*FOO or V*R=T*Z or *=X*Z
my ($prnew,$sufnew,$prold,$sufold)=($1,$2,$3,$4);
my $re=qr/^\Q$prold\E(.*)\Q$sufold\E$/;
foreach my $k (keys %$pargs) {
next unless $k =~ $re;
$hash->{$prnew.$1.$sufnew}=$pargs->{$k};
}
}
elsif($rule =~ /^([\w\.]+)$/) { # VAR
$hash->{$1}=$pargs->{$1};
}
elsif($rule =~ /^([\w\.]*)\*([\w\.]*)$/) { # VAR* or *VAR or VAR*FOO
my ($pr,$suf)=($1,$2);
my $re=qr/^\Q$pr\E(.*)\Q$suf\E$/;
foreach my $k (keys %$pargs) {
next unless $k =~ $re;
$hash->{$k}=$pargs->{$k};
}
}
elsif($rule =~ /^!([\w\.]+)$/) { # !VAR
delete $hash->{$1};
}
elsif($rule =~ /^!([\w\.]*)\*([\w\.]*)$/) { # !VAR* or !*VAR or !VAR*FOO
my ($pr,$suf)=($1,$2);
my $re=qr/^\Q$pr\E(.*)\Q$suf\E$/;
my @todel;
foreach my $k (keys %$hash) {
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
});
If "pass" argument is not defined it will just return the original args,
otherwise the following rules are supported:
"on" or "1" - pass all arguments from parent object
"VAR=FOO" - pass FOO from parent as VAR
"VAR*=FOO*" - pass FOO* from parent renaming as VAR*
"*=FOO*" - pass FOO* from parent stripping FOO
"VAR" - pass only VAR from parent
"VAR*" - pass only VAR* from parent
Multiple pass specifications can be given with semi-colon delimiter.
Several special tags are deleted from parent arguments: pass, path,
template, and objname.
=cut
sub pass_args ($$;$) {
my ($self,$pass,$args)=@_;
$args||={ };
# The first argument is the content of 'pass', if it's not defined
# we return unadulteraded arguments.
#
return $args unless $pass;
# If we don't have parent arguments then there is nothing to do.
#
my $pargs;
if(!$self->{'parent'} || !($pargs=$self->{'parent'}->{'args'})) {
return $args;
}
# Simplified (old) way of calling with just <%Page pass
# template='xxx'%> would result in pass being 'on'.
#
if($pass eq 'on' || $pass eq '1') {
$pass='*';
}
# Building inherited hash.
#
my $hash=$self->_do_pass_args($pargs,[split(/;/,$pass)]);
# Always deleting pass, path and template
#
delete @{$hash}{'pass','objname','path','template'};
# This is it, merging with the arguments given to us and returning
#
return merge_refs($hash,$args);
}
###############################################################################
sub benchmark_enabled ($) {
my $self=shift;
$self->clipboard->get('_page_benchmark_enabled') || 0;
}
###############################################################################
sub _benchmark_hash ($) {
my $self=shift;
my $stats=$self->{'benchmark_stats'};
if(!$stats) {
$stats=$self->siteconfig->get('_page_benchmark_stats');
if($stats) {
$self->{'benchmark_stats'}=$stats;
}
else {
$stats=$self->{'benchmark_stats'}={ };
$self->siteconfig->put('_page_benchmark_stats' => $stats);
}
}
return $stats;
}
###############################################################################
sub benchmark_tag_data ($$) {
my ($self,$tag,$key)=@_;
$tag || throw $self "- no 'tag'";
$key||='-';
ref $tag && throw $self "- tag '$tag' is not a scalar";
my $stats=$self->_benchmark_hash();
my $tagdata=$stats->{$tag};
if(!$tagdata) {
$tagdata=$stats->{$tag}={
count => 0,
total => 0,
last => [ ],
runs => { },
};
}
my $rundata=$tagdata->{'runs'};
$rundata->{$key}||={ };
$rundata=$rundata->{$key};
return wantarray ? ($tagdata,$rundata,$key) : $tagdata;
}
###############################################################################
=item benchmark_enter($;$$$)
Start tracking the given tag execution time until benchmark_leave() is
called on the same tag.
lib/XAO/DO/Web/Page.pm view on Meta::CPAN
### dprint to_json($tagdata);
my $started=$rundata->{'started'};
if(!$started) {
eprint "Benchmark for '$tag' (key '$key') was not started";
return;
}
my $taken=tv_interval($started);
# For median calculation
#
my $last=$tagdata->{'last'};
push(@$last,$taken);
shift(@$last) if scalar(@$last) > 50;
++$tagdata->{'count'};
++$rundata->{'count'};
$tagdata->{'total'}+=$taken;
$rundata->{'total'}+=$taken;
# Remembering the content for cacheability analysis.
#
$content_digest||='-';
++$rundata->{'content'}->{$content_digest};
# Resetting for the next run
#
$rundata->{'started'}=undef;
}
###############################################################################
=item benchmark_reset()
Clear all benchmarking statistics accumulated so far.
=cut
sub benchmark_reset ($) {
my $self=shift;
%{$self->_benchmark_hash()}=();
}
###############################################################################
=item benchmark_start(;$)
Start automatic system-wide page rendering benchmarking.
By default only 'path' based rendering is benchmarked. If an optional
single argument is set to '2' then templates are also benchmarked (this
may demand a lot of extra memory!).
=cut
sub benchmark_start ($;$) {
my ($self,$level)=@_;
$self->clipboard->put('_page_benchmark_enabled' => ($level || 1));
}
###############################################################################
=item benchmark_stop()
Stop automatic system-wide rendering benchmarking.
=cut
sub benchmark_stop ($) {
my $self=shift;
$self->clipboard->put('_page_benchmark_enabled' => 0);
}
###############################################################################
=item benchmark_stats
Return a hash with accumulated benchmark statistics.
=cut
sub benchmark_stats ($;$) {
my ($self,$desired_tag)=@_;
my $stats=$self->_benchmark_hash();
my %analyzed;
foreach my $tag (keys %$stats) {
my $d=$stats->{$tag};
next unless $d->{'count'};
next if $desired_tag && $tag ne $desired_tag;
$d->{'average'}=$d->{'total'} / $d->{'count'};
$d->{'median'}=$d->{'last'}->[scalar(@{$d->{'last'}})/2];
# The page is cacheable if the content only depends on
# parameters and not on clipboard, cookies, CGI, time, or other
# environment.
#
$d->{'cacheable'}=scalar(grep {
scalar(keys %{$d->{'runs'}->{$_}->{'content'}}) != 1
} keys %{$d->{'runs'}}) ? 0 : 1;
# Current cacheable flag, if it's shared across all runs
#
$d->{'cache_flag'}=scalar(grep {
! $d->{'runs'}->{$_}->{'cache_flag'}
} keys %{$d->{'runs'}}) ? 0 : 1;
$analyzed{$tag}=$d;
}
### dprint to_json(\%analyzed,{ utf8 => 1, canonical => 1, pretty => 1 });
return \%analyzed;
}
###############################################################################
sub cache_show_size ($$) {
my ($self,$path)=@_;
eval {
require Devel::Size;
};
if($@) {
eprint "Devel::Size not available, disabling debug 'page-cache-size'";
$self->debug_set('cache-size' => 0);
return;
}
my $size=Devel::Size::total_size(\%parsed_cache);
eprint "Web::Page cache size ".sprintf('%.3f',$size/1024.0)." KB - ",$path;
}
###############################################################################
sub debug_check ($$) {
my ($self,$type)=@_;
# This is a speed up (makes the parsing more than twice faster when a
# local parsing cache is also used).
#
# 8 wallclock secs ( 8.78 usr + 0.01 sys = 8.79 CPU) @ 113765.64/s (n=1000000)
# 19 wallclock secs (18.97 usr + 0.00 sys = 18.97 CPU) @ 52714.81/s (n=1000000)
#
### return $self->clipboard->get("debug/Web/Page/$type");
my $debug_hash=$self->{'debug_hash'};
if(!$debug_hash) {
$debug_hash=$self->clipboard->get('/debug/Web/Page');
if($debug_hash) {
$self->{'debug_hash'}=$debug_hash;
}
else {
$self->{'debug_hash'}=$debug_hash={ };
$self->clipboard->put('/debug/Web/Page' => $debug_hash);
}
}
return $debug_hash->{$type};
}
###############################################################################
sub debug_set ($%) {
my $self=shift;
my $args=get_args(\@_);
foreach my $type (keys %$args) {
$self->clipboard->put("/debug/Web/Page/$type",$args->{$type} ? 1 : 0);
}
}
###############################################################################
sub page_clipboard ($) {
my $self=shift;
my $cb_hash=$self->{'page_clipboard'};
if(!$cb_hash) {
$cb_hash=$self->clipboard->get('/xao/page');
if($cb_hash) {
$self->{'page_clipboard'}=$cb_hash;
}
else {
$self->{'page_clipboard'}=$cb_hash={ };
$self->clipboard->put('/xao/page' => $cb_hash);
}
}
return $cb_hash;
}
###############################################################################
1;
__END__
=back
=head1 EXPORTS
Nothing.
=head1 AUTHOR
Copyright (c) 2005 Andrew Maltsev
Copyright (c) 2001-2004 Andrew Maltsev, XAO Inc.
<am@ejelta.com> -- http://ejelta.com/xao/
=head1 SEE ALSO
Recommended reading:
L<XAO::Web>,
L<XAO::Objects>,
L<XAO::Projects>,
L<XAO::Templates>.
L<XAO::DO::Web::Benchmark>.
=cut
( run in 0.775 second using v1.01-cache-2.11-cpan-81fc1098f69 )