Add a new backtrace-processing filter stack
authorVincent Tondellier <tonton+hg@team1664.org>
Fri, 08 Aug 2014 00:00:49 +0200
changeset 37 013953be0f3b
parent 36 703f1af889d1
child 38 6fa3cf9cf915
Add a new backtrace-processing filter stack Move many code from main to helpers or stack filters
CrashTest.pl
lib/CrashTest/Helpers/CrashTestHelpers.pm
lib/CrashTest/Models/Frame.pm
lib/CrashTest/Models/Thread.pm
lib/CrashTest/StackFilter.pm
lib/CrashTest/StackFilters/FileLink.pm
lib/CrashTest/StackFilters/HideArgs.pm
templates/report/backtrace.html.ep
templates/report/backtrace/frames.html.ep
templates/report/crash.html.ep
--- a/CrashTest.pl	Thu Aug 07 23:56:47 2014 +0200
+++ b/CrashTest.pl	Fri Aug 08 00:00:49 2014 +0200
@@ -15,15 +15,11 @@
 
 use Mojolicious::Lite;
 use UUID;
-use Mojo::JSON;
-use Mojo::ByteStream 'b';
-use Mojo::UserAgent;
 use lib 'lib';
 
-my @valid_params = qw/Add-ons Distributor ProductName ReleaseChannel StartupTime UserID Version BuildID CrashTime Comments/;
-my $config = plugin 'Config';
-
-plugin 'TagHelpers::BootstrapPagination';
+use CrashTest::Models::Frame;
+use CrashTest::Models::Thread;
+use CrashTest::StackFilter;
 
 app->attr(storage => sub {
     my $self = shift;
@@ -53,54 +49,6 @@
     );
 });
 
-helper scm_file_link => sub {
-    my ($self, $file, $line) = @_;
-
-    return "" unless(defined($file));
-
-    if($file =~ qr{([a-z]+):([a-z/.]+):(.+):(\d+)})
-    {
-        my ($scm, $repo, $scmpath, $rev) = ($1, $2, $3, $4);
-        my $filename = File::Spec->splitpath($scmpath);
-        my $scmrepo = "$scm:$repo";
-        if(exists($config->{ScmLinks}->{$scmrepo})) {
-            return b($self->link_to("$filename:$line" =>
-                    $self->render(inline => $config->{ScmLinks}->{$scmrepo},
-                        repo => $repo, scmpath => $scmpath, rev => $rev, line => $line, partial => 1)
-                ));
-        }
-        #return $file;
-    }
-    my $filebase = (File::Spec->splitpath($file))[-1];
-    if(defined($line) && $line ne "") {
-        return "$filebase:$line";
-    }
-    return $filebase;
-};
-
-helper shorten_signature => sub {
-    my ($self, $signature) = @_;
-
-    return "" if(!defined($signature) || $signature eq "");
-
-    my $short_signature = $signature;
-    if($signature =~ qr{([^<]+)<.+>::([^()]+)\(.*\)(.*)}) {
-        # c++ with template
-        $short_signature = "$1<>::$2()$3";
-    } elsif($signature =~ qr{([^()]+)\(.*\)(.*)}) {
-        # c/c++
-        $short_signature = "$1()$2";
-    }
-    return b($self->t(span => (title => $signature, class => "shortened-signature") => $short_signature));
-};
-
-helper xml_escape_block => sub {
-    my ($c, $block) = @_;
-    my $result = $block->();
-    $result = xml_escape $result;
-    return Mojo::ByteStream->new($result);
-};
-
 get '/' => sub {
     my $self = shift;
     my $page = 1;
@@ -116,13 +64,32 @@
 
 get '/report/:uuid' => [ uuid => qr/[0-9a-fA-F-]+/ ] => sub {
     my $self = shift;
-    $self->stash(processed_data => $self->app->storage->get_processed_data($self->param('uuid')));
+
+    my $data = $self->app->storage->get_processed_data($self->param('uuid'));
+    $self->stash(processed_data => $data);
+
+    my $stackfilter = CrashTest::StackFilter->new(config => $self->app->config, app => $self->app);
+
+    my $crashing_thread = CrashTest::Models::Thread->new($data->{crashing_thread});
+    $crashing_thread = $stackfilter->apply($crashing_thread);
+    $self->stash(crashing_thread => $crashing_thread);
+
+    my $threads = [];
+    foreach my $raw_thread(@{$data->{threads}}) {
+        my $thread = CrashTest::Models::Thread->new($raw_thread);
+        $thread = $stackfilter->apply($thread);
+        push $threads, $thread;
+    }
+    $self->stash(threads => $threads);
+
     $self->render('report/crash');
 } => 'report';
 
 post '/submit' => sub {
     my $self = shift;
 
+    #my @valid_params = qw/Add-ons Distributor ProductName ReleaseChannel StartupTime UserID Version BuildID CrashTime Comments/;
+
     # save the dump in a file
     my $file = $self->req->upload('upload_file_minidump');
     my %paramshash = map { $_ => $self->req->param($_) } $self->req->param;
@@ -139,12 +106,17 @@
             $self->render(text => $pjson->{status});
         }
     );
-
-};
+} => 'submit';
 
 app->secrets([
     'My secret passphrase here'
 ]);
 
 push @{app->commands->namespaces}, 'CrashTest::Commands';
+push @{app->plugins->namespaces}, 'CrashTest::Helpers';
+
+plugin 'Config';
+plugin 'TagHelpers::BootstrapPagination';
+plugin 'CrashTestHelpers';
+
 app->start;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/Helpers/CrashTestHelpers.pm	Fri Aug 08 00:00:49 2014 +0200
@@ -0,0 +1,35 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::Helpers::CrashTestHelpers;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::ByteStream qw/b/;
+use Mojo::Util qw/xml_escape/;
+
+sub register {
+  my ($self, $mojo, $params) = @_;
+
+  $mojo->helper(
+      xml_escape_block => sub {
+          return $self->xml_escape_block(@_);
+      }
+  );
+}
+
+sub xml_escape_block {
+    my ($self, $c, $block) = @_;
+    my $result = $block->();
+    return b(xml_escape($result));
+}
+
+1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/Models/Frame.pm	Fri Aug 08 00:00:49 2014 +0200
@@ -0,0 +1,53 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::Models::Frame;
+use Mojo::Base -base;
+use Mojo::ByteStream 'b';
+use Mojolicious::Plugin::TagHelpers;
+use File::Basename;
+
+# from json
+has [ qw/frame module function file line trust/ ];
+has [ qw/function_offset module_offset offset/ ];
+has [ qw/missing_symbols corrupt_symbols/ ];
+
+# added
+has [ qw/module_name function_name file_link frame_number/ ];
+has [ qw/warnings infos/ ];
+
+sub new {
+    my $self = shift->SUPER::new(@_);
+
+    # defaults
+    $self->frame_number($self->frame);
+    $self->function_name($self->function);
+    $self->module_name($self->module);
+    if(defined($self->file)) {
+        my $filename = fileparse($self->file);
+        $self->file_link($filename . ":" . $self->line);
+    }
+    #$self->frame_number($self->frame);
+    $self->warnings([]);
+    $self->add_warning("hello world!");
+
+    return $self;
+}
+
+sub add_warning {
+    my ($self, $args) = @_;
+
+    push @{$self->warnings}, $args;
+}
+
+1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/Models/Thread.pm	Fri Aug 08 00:00:49 2014 +0200
@@ -0,0 +1,40 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::Models::Thread;
+use Mojo::Base -base;
+use Mojo::ByteStream 'b';
+use Mojolicious::Plugin::TagHelpers;
+use CrashTest::Models::Frame;
+
+has [ qw/frame_count crashing_thread frames/ ];
+
+sub new {
+    my $self = shift->SUPER::new(@_);
+
+    foreach my $frame(@{$self->frames}) {
+        $frame = CrashTest::Models::Frame->new($frame);
+    }
+
+    return $self;
+}
+
+sub each_frame {
+    my ($self, $block) = @_;
+
+    foreach my $frame(@{$self->frames}) {
+        $block->($frame);
+    }
+}
+
+1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/StackFilter.pm	Fri Aug 08 00:00:49 2014 +0200
@@ -0,0 +1,86 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::StackFilter;
+use Mojo::Base -base;
+use Mojo::Loader;
+use Data::Dumper;
+
+has [ qw/config app filters/ ];
+
+sub new {
+    my $self = shift->SUPER::new(@_);
+
+    if(defined($self->config->{StackFilters})) {
+        $self->load_modules($self->config->{StackFilters});
+    } else {
+        $self->find_modules();
+    }
+
+    return $self;
+}
+
+sub apply {
+    my ($self, $thread) = @_;
+
+    foreach my $filter(@{$self->filters}) {
+        say "apply filter $filter";
+        $thread = $filter->apply($thread);
+    }
+
+    return $thread;
+}
+
+sub load_modules {
+    my ($self, $modules) = @_;
+
+    my @filters = ();
+    my $loader = Mojo::Loader->new;
+    for my $module (@$modules) {
+        my $e = $loader->load($module);
+        die qq{Loading "$module" failed: $e} and next if ref $e;
+
+#        say "loading $module";
+        push @filters, $module->new(config => $self->config, app => $self->app);
+    }
+
+    $self->filters(\@filters);
+}
+
+sub find_modules {
+    my ($self) = @_;
+
+    my @modules = ();
+    my $loader = Mojo::Loader->new;
+
+    # Find modules in a namespace
+    for my $module (@{$loader->search('CrashTest::StackFilters')}) {
+        my $e = $loader->load($module);
+        warn qq{Loading "$module" failed: $e} and next if ref $e;
+
+        push @modules, [ $module, $module->priority ];
+    }
+
+    # sort by prio
+    @modules = sort { $b->[1] <=> $a->[1] } @modules;
+
+    say Dumper(@modules);
+    # instanciate
+    my @filters = map { $_->[0]->new(config => $self->config, app => $self->app) } @modules;
+
+    $self->filters(\@filters);
+}
+
+1;
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/StackFilters/FileLink.pm	Fri Aug 08 00:00:49 2014 +0200
@@ -0,0 +1,66 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::StackFilters::FileLink;
+use Mojo::Base -base;
+
+sub priority { return 50; }
+
+has [ qw/config app/ ];
+
+sub apply {
+    my ($self, $thread) = @_;
+
+    $thread->each_frame(sub {
+            my $frame = shift;
+
+            $frame->file_link($self->scm_file_link($frame->file, $frame->line));
+        }
+    );
+
+    return $thread;
+}
+
+sub scm_file_link {
+    my ($self, $file, $line) = @_;
+
+    return "" unless(defined($file));
+
+    # if the file section looks like vcs:vcs_root_url:vcs_path:revision
+    if($file =~ qr{([a-z]+):([a-z/.]+):(.+):([a-zA-Z0-9]+)}) {
+        my ($scm, $repo, $scmpath, $rev) = ($1, $2, $3, $4);
+        my $filename = File::Spec->splitpath($scmpath);
+        my $scmrepo = "$scm:$repo";
+        my $mt = Mojo::Template->new;
+        my $config = $self->config->{ScmLinks};
+
+        # and we have a browser configured for this repository
+        if(exists($config->{$scmrepo})) {
+            my $template = '% my ($repo, $scmpath, $rev, $line) = @_; ' . "\n" . $config->{$scmrepo};
+            my $url = $mt->render($template, $repo, $scmpath, $rev, $line);
+            # then create a link to it
+            return $self->app->link_to("$filename:$line" => $url);
+        }
+    }
+
+    # else display only the filename
+    my $filebase = (File::Spec->splitpath($file))[-1];
+    if(defined($line) && $line ne "") {
+        return "$filebase:$line";
+    }
+
+    return $filebase;
+}
+
+1;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/StackFilters/HideArgs.pm	Fri Aug 08 00:00:49 2014 +0200
@@ -0,0 +1,52 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::StackFilters::HideArgs;
+use Mojo::Base -base;
+
+sub priority { return 50; }
+
+has [ qw/app/ ];
+
+sub apply {
+    my ($self, $thread) = @_;
+
+    $thread->each_frame(sub {
+            my $frame = shift;
+
+            $frame->function_name($self->shorten_signature($frame->function_name));
+        }
+    );
+
+    return $thread;
+}
+
+sub shorten_signature {
+    my ($self, $signature) = @_;
+
+    return "" if(!defined($signature) || $signature eq "");
+
+    my $short_signature = $signature;
+    if($signature =~ qr{([^<]+)<.+>::([^()]+)\(.*\)(.*)}) {
+        # c++ with template
+        $short_signature = "$1<>::$2()$3";
+    } elsif($signature =~ qr{([^()]+)\(.*\)(.*)}) {
+        # c/c++
+        $short_signature = "$1()$2";
+    }
+
+    return $self->app->t(span => (title => $signature, class => "shortened-signature") => sub { return $short_signature });
+}
+
+1;
+
--- a/templates/report/backtrace.html.ep	Thu Aug 07 23:56:47 2014 +0200
+++ b/templates/report/backtrace.html.ep	Fri Aug 08 00:00:49 2014 +0200
@@ -1,6 +1,6 @@
 <a data-toggle="collapse" href="#backtrace-crashing-thread"><h3>Crashing thread top frames</h3></a>
 <div id="backtrace-crashing-thread" class="collapse in">
-  %= include 'report/backtrace/frames', frames => $crashing_thread->{frames}
+  %= include 'report/backtrace/frames', thread => $crashing_thread
 </div>
 <a data-toggle="collapse" href="#backtrace-all-threads"><h3>All threads</h3></a>
 <div id="backtrace-all-threads" class="collapse in">
@@ -10,7 +10,7 @@
       %= t h4 => "Thread $thread_id"
     % end
     %= t div => ( id => "backtrace-thread-$thread_id", class => "collapse in" ) => begin
-      %= include 'report/backtrace/frames', frames => $thread->{frames}
+      %= include 'report/backtrace/frames', thread => $thread
       % $thread_id = $thread_id + 1;
     % end
   % }
--- a/templates/report/backtrace/frames.html.ep	Thu Aug 07 23:56:47 2014 +0200
+++ b/templates/report/backtrace/frames.html.ep	Fri Aug 08 00:00:49 2014 +0200
@@ -1,18 +1,26 @@
-%= t table => (class => 'table table-striped table-bordered table-condensed') => begin
-<thead>
-  <tr>
-    <th>Frame</th>
-    <th>Module</th>
-    <th class="signature-column">Signature</th>
-    <th>Source</th>
-  </tr>
-</thead>
-% foreach my $frame(@$frames) {
-<tr>
-  %= t td => $frame->{frame}
-  %= t td => $frame->{module}
-  <td><%= shorten_signature $frame->{function} =%></td>
-  <td><%= scm_file_link($frame->{file}, $frame->{line}) =%></td>
-</tr>
-% }
+%= t table => (class => 'table table-striped table-hover table-bordered table-condensed') => begin
+  %= t thead => begin
+    %= t tr => begin
+      %= t th => "Frame"
+      %= t th => "Module"
+      %= t th => (class => "signature-column") => "Signature"
+      %= t th => "Source"
+    %= end
+  %= end
+  % $thread->each_frame(sub { my $frame = shift;
+  %= t tr => begin
+    %= t td => begin
+      %= $frame->frame_number
+    %  end
+    %= t td => begin
+      %= $frame->module_name
+    % end
+    %= t td => begin
+      %= $frame->function_name
+    % end
+    %= t td => begin
+      %= $frame->file_link
+    % end
+  % end
+  % });
 % end
--- a/templates/report/crash.html.ep	Thu Aug 07 23:56:47 2014 +0200
+++ b/templates/report/crash.html.ep	Fri Aug 08 00:00:49 2014 +0200
@@ -16,10 +16,10 @@
 
   %= t div => (class => 'tab-content') => begin
     %= t div => (class => 'tab-pane active', id => 'backtrace') => begin
-    %= include('report/backtrace', crashing_thread => $processed_data->{crashing_thread}, threads => $processed_data->{threads});
+    %= include('report/backtrace', crashing_thread => $crashing_thread, threads => $threads);
     % end
     %= t div => (class => 'tab-pane', id => 'details') => begin
-     %= t table => (class => 'table table-striped table-bordered table-condensed') => begin
+     %= t table => (class => 'table table-striped table-hover table-bordered table-condensed') => begin
       %= include('report/client_info', client_info => $processed_data->{client_info});
       %= include('report/system_info', system_info => $processed_data->{system_info});
      % end