Apache-Voodoo/lib/Apache/Voodoo/Debug/Handler.pm
################################################################################
#
# Apache::Voodoo::Debug::Handler
#
# Handles servicing debugging information requests
#
################################################################################
package Apache::Voodoo::Debug::Handler;
$VERSION = "3.0200";
use strict;
use warnings;
use DBI;
use Time::HiRes;
use JSON::DWIW;
use Apache::Voodoo::MP;
use Apache::Voodoo::Constants;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
$self->{mp} = Apache::Voodoo::MP->new();
$self->{constants} = Apache::Voodoo::Constants->new();
$self->{debug_root} = $self->{constants}->debug_path();
warn "Voodoo Debugging Handler Starting...\n";
$self->{template_dir} = $INC{"Apache/Voodoo/Debug/Handler.pm"};
$self->{template_dir} =~ s/Handler.pm$/html/;
$self->{handlers} = {
map { $_ => 'handle_'.$_ }
('profile','debug','return_data','session','template_conf','parameters','request')
};
$self->{static_files} = {
"debug.css" => "text/css",
"debug.js" => "application/x-javascript",
"debug.png" => "image/png",
"error.png" => "image/png",
"exception.png" => "image/png",
"info.png" => "image/png",
"minus.png" => "image/png",
"plus.png" => "image/png",
"spinner.gif" => "image/gif",
"table.png" => "image/png",
"trace.png" => "image/png",
"warn.png" => "image/png"
};
$self->{json} = JSON::DWIW->new({bad_char_policy => 'convert', pretty => 1});;
return $self;
}
sub handler {
my $self = shift;
my $r = shift;
$self->{mp}->set_request($r);
# holds all vars associated with this page processing request
my $uri = $self->{mp}->uri();
$uri =~ s/^$self->{debug_root}//;
$uri =~ s/^\///;
if (defined($self->{static_files}->{$uri})) {
# request for one of the static files.
my $file = File::Spec->catfile($self->{template_dir},$uri);
my $mtime = (stat($file))[9];
# Handle "if not modified since" requests.
$r->update_mtime($mtime);
$r->set_last_modified;
$r->meets_conditions;
my $rc = $self->{mp}->if_modified_since($mtime);
return $rc unless $rc == $self->{mp}->ok;
# set the content type
$self->{mp}->content_type($self->{static_files}->{$uri});
# tell apache to send the underlying file
$r->sendfile($file);
return $self->{mp}->ok;
}
elsif (defined($self->{handlers}->{$uri})) {
# request for an operation
my $method = $self->{handlers}->{$uri};
# parse the params
my $params = $self->{mp}->parse_params(1);
unless (ref($params)) {
# something went boom
return $self->display_host_error($params);
}
# connect to the debugging database
my $dbh = DBI->connect_cached(@{$self->{constants}->debug_dbd()});
unless ($dbh) {
return $self->display_host_error("Can't connect to debugging database: ".DBI->errstr);
}
my $return;
eval {
$return = $self->$method($dbh,$params);
};
use Data::Dumper;
warn Dumper $@;
if ($@) {
return $self->display_host_error("$@");
}
if (ref($return) eq "HASH") {
$self->{mp}->content_type("application/json");
$self->{mp}->print($self->{json}->to_json($return));
}
else {
$self->{mp}->content_type("text/plain");
$self->{mp}->print($return);
}
$self->{mp}->flush();
return $self->{mp}->ok;
}
# not a request we handle
return $self->{mp}->declined;
}
sub display_host_error {
my $self = shift;
my $error = shift;
$self->{'mp'}->content_type("text/html");
$self->{'mp'}->print("<h2>The following error was encountered while processing this request:</h2>");
$self->{'mp'}->print("<pre>$error</pre>");
$self->{'mp'}->flush();
return $self->{mp}->ok;
}
sub json_data {
my $self = shift;
my $type = shift;
my $data = shift;
if (ref($data)) {
$data = $self->{json}->to_json($data);
}
elsif ($data !~ /^\s*[\[\{\"]/) {
$data = '"'.$data.'"';
}
return '{"key":"'.$type.'","value":'.$data.'}';
}
sub json_error {
my $self = shift;
my $errors = shift;
my $return = {
'success' => 'false',
'errors' => []
};
if (ref($errors) eq "HASH") {
foreach my $key (keys %{$errors}) {
push(@{$return->{errors}},{id => $key, msg => $errors->{$key}});
}
}
else {
push(@{$return->{errors}},{id => 'error', msg => $errors});
}
return $return;
}
sub json_true { return $JSON::DWIW->true; }
sub json_false { return $JSON::DWIW->false; }
sub get_request_id {
my $self = shift;
my $dbh = shift;
my $id = shift;
unless ($id->{request_id} =~ /^\d+(\.\d*)?$/) {
return "invalid request id";
}
unless ($id->{app_id} =~ /^[a-z]\w*$/i) {
return "invalid application id";
}
unless ($id->{session_id} =~ /^[0-9a-z]+$/i) {
return "invalid session id";
}
my $res = $dbh->selectcol_arrayref("
SELECT id
FROM request
WHERE
request_timestamp = ? AND
application = ? AND
session_id = ?",undef,
$id->{request_id},
$id->{app_id},
$id->{session_id});
unless ($res->[0] > 0) {
return "no such id";
}
return $res->[0];
}
sub select_data_by_id {
my $self = shift;
my $dbh = shift;
my $table = shift;
my $id = shift;
my $res = $dbh->selectall_arrayref("
SELECT
data
FROM
$table
WHERE
request_id = ?",undef,
$id);
return $res->[0]->[0];
}
sub simple_data {
my $self = shift;
my $dbh = shift;
my $params = shift;
my $key = shift;
my $table = shift;
my $id = $self->get_request_id($dbh,$params);
unless ($id =~ /^\d+$/) {
return $self->json_error($id);
}
return $self->json_data(
$key,
$self->select_data_by_id($dbh,$table,$id)
);
}
sub handle_template_conf {
my $self = shift;
my $dbh = shift;
my $params = shift;
return $self->simple_data($dbh,$params,'vd_template_conf','template_conf');
}
sub handle_parameters {
my $self = shift;
my $dbh = shift;
my $params = shift;
return $self->simple_data($dbh,$params,'vd_parameters','params');
}
sub handle_session {
my $self = shift;
my $dbh = shift;
my $params = shift;
return $self->simple_data($dbh,$params,'vd_session','session');
}
sub handle_request {
my $self = shift;
my $dbh = shift;
my $params = shift;
my $app_id = $params->{'app_id'};
my $session_id = $params->{'session_id'};
my $request_id = $params->{'request_id'};
my $return = [];
if ($app_id =~ /^[a-z]\w+/i &&
$session_id =~ /^[a-f0-9]+$/i &&
$request_id =~ /^\d+\.\d+$/) {
$return = $dbh->selectall_arrayref("
SELECT
request_timestamp AS request_id,
url
FROM
request
WHERE
application = ? AND
session_id = ? AND
request_timestamp >= ?
ORDER BY
id",{Slice => {}},
$app_id,
$session_id,
$request_id);
}
return $self->json_data('vd_request',$return);
}
sub handle_return_data {
my $self = shift;
my $dbh = shift;
my $params = shift;
my $id = $self->get_request_id($dbh,$params);
unless ($id =~ /^\d+$/) {
return $self->json_error($id);
}
my $res = $dbh->selectall_arrayref("
SELECT
handler,
method,
data
FROM
return_data
WHERE
request_id = ?
ORDER BY
seq",undef,
$id);
my $d = '[';
foreach (@{$res}) {
$d .= '["'.$_->[0].'->'.$_->[1].'",'.$_->[2].'],';
}
$d =~ s/,$//;
$d .= ']';
return $self->json_data('vd_return_data',$d);
}
sub handle_debug {
my $self = shift;
my $dbh = shift;
my $params = shift;
my $id = $self->get_request_id($dbh,$params);
unless ($id =~ /^\d+$/) {
return $self->json_error($id);
}
my @levels;
foreach (qw(debug info warn error exception table trace)) {
if ($params->{$_} eq "1") {
push(@levels,$_);
}
}
my $query = "
SELECT
level,
stack,
data
FROM
debug
WHERE
request_id = ?";
if (scalar(@levels)) {
$query .= ' AND level IN (' . join(',',map { '?'} @levels) . ') ';
}
$query .= "
ORDER BY
seq";
my $res = $dbh->selectall_arrayref($query,undef,$id,@levels);
return $self->json_data('vd_debug',$self->_process_debug($params->{app_id},$res));
}
sub _process_debug {
my $self = shift;
my $app_id = shift;
my $data = shift;
my $debug = '[';
foreach my $row (@{$data}) {
$debug .= '{"level":"'.$row->[0].'"';
$debug .= ',"stack":' .$row->[1];
$debug .= ',"data":';
if ($row->[2] =~ /^[\[\{\"]/) {
$debug .= $row->[2];
}
else {
$debug .= '"'.$row->[2].'"';
}
$debug .= '},';
}
$debug =~ s/,$//;
$debug .= ']';
return $debug;
}
sub handle_profile {
my $self = shift;
my $dbh = shift;
my $params = shift;
my $id = $self->get_request_id($dbh,$params);
unless ($id =~ /^\d+$/) {
return $self->json_error($id);
}
my $res = $dbh->selectall_arrayref("
SELECT
timestamp,
data
FROM
profile
WHERE
request_id = ?
ORDER BY
timestamp",undef,
$id);
my $return;
$return->{'key'} = 'vd_profile';
my $last = $#{$res};
if ($last > 0) {
my $total_time = $res->[$last]->[0] - $res->[0]->[0];
$return->{'value'} = [
map {
[
sprintf("%.5f", $res->[$_]->[0] - $res->[$_-1]->[0]),
sprintf("%5.2f%%",($res->[$_]->[0] - $res->[$_-1]->[0])/$total_time*100),
$res->[$_]->[1]
]
} (1 .. $last)
];
unshift(@{$return->{value}}, [
sprintf("%.5f",$total_time),
'percent',
'message'
]);
}
return $return;
}
1;
################################################################################
# Copyright (c) 2005-2010 Steven Edwards (maverick@smurfbane.org).
# All rights reserved.
#
# You may use and distribute Apache::Voodoo under the terms described in the
# LICENSE file include in this package. The summary is it's a legalese version
# of the Artistic License :)
#
################################################################################