# HG changeset patch # User Vincent Tondellier # Date 1446655380 -3600 # Node ID 0ebef32c34af92517576f80894c1687a84b9f825 # Parent e408da1419cda639e256b8ac5a8f28d3199285e5 Refactor everything - change db access method, use Mojo::{Pg,SQLite,...} instead of DBIx::Class - use Minion as job queue - refactor into a non-Lite Mojolicious app diff -r e408da1419cd -r 0ebef32c34af CrashTest.conf --- a/CrashTest.conf Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -{ - Dumper => { - JSONStackwalker => './stackwalker', - SymbolsPath => 'breakpad-debug-symbols/*', - }, -# Storage => { -# Type => "CrashTest::Storage::FileSystem", -# DataDir => 'data/', -# }, - Storage => { - Type => "CrashTest::Storage::Sql", - DSN => "dbi:Pg:dbname=crashreports", - DataDir => 'data/', - }, - DecodeQueue => { - Type => "CrashTest::Decode::Queue::NoQueue" - }, -# DecodeQueue => { -# Type => "CrashTest::Decode::Queue::Gearman", -# GearmanServers => [ 'localhost:4730' ], -# }, - WebInterface => { - ScmLinks => { - "svn:svn.example.org/testproject" => 'https://redmine.example.org/projects/testproject/repository/entry/<%= $scmpath =%>?rev=<%= $rev =%>#L<%= $line =%>', - }, - ExtraColumns => { - Index => [ - { id => 'os', db_column => "crash_user.os", name => 'Operating System' }, - ], - }, - }, -}; -# vim:ft=perl: diff -r e408da1419cd -r 0ebef32c34af CrashTest.pl --- a/CrashTest.pl Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -#!/usr/bin/env perl - -# 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 . - -# ABSTRACT: Web interface for breakpad - -use Mojolicious::Lite; -use Mojo::Loader qw/load_class/; -use UUID; -use lib 'lib'; - -use CrashTest::Models::Frame; -use CrashTest::Models::Thread; -use CrashTest::StackFilter; - -app->attr(storage => sub { - my $self = shift; - - my $storage_class = $self->app->config->{Storage}->{Type}; - if (my $e = load_class($storage_class)) { - die ref $e ? "Exception: $e" : 'Not found!'; - } - - state $storage = $storage_class->new( - config => $self->app->config->{Storage}, - extra_columns => $self->app->config->{WebInterface}->{ExtraColumns}, - ); - return $storage; -}); - -app->attr(decode_queue => sub { - my $self = shift; - - my $decode_class = $self->app->config->{DecodeQueue}->{Type}; - if (my $e = load_class($decode_class)) { - die ref $e ? "Exception: $e" : 'Not found!'; - } - - state $decode = $decode_class->new( - config => $self->app->config->{DecodeQueue}, - dumper_config => $self->app->config->{Dumper}, - storage => $self->app->storage - ); - return $decode; -}); - -app->attr(stackfilter => sub { - my $self = shift; - - state $stackfilter = CrashTest::StackFilter->new( - config => $self->app->config, - app => $self->app - ); - return $stackfilter; -}); - -get '/' => sub { - my $self = shift; - my $page = 1; - my $crashs_per_page = 25; - - if($self->req->url =~ qr{.*\.atom$}) { - $crashs_per_page = 100; - } - - $self->validation->required('page')->like(qr/^[0-9]+$/); - $page = scalar $self->validation->param("page") if $self->validation->is_valid('page'); - - my $result = $self->app->storage->index($page, $crashs_per_page, $self->req->param('search')); - - $self->stash(files => $result->{crashs}); - $self->stash(pager => $result->{pager}); - $self->stash(extra_columns => $self->app->config->{WebInterface}->{ExtraColumns}->{Index}); - $self->render('index'); -} => 'index'; - -get '/report/:uuid' => [ uuid => qr/[0-9a-fA-F-]+/ ] => sub { - my $self = shift; - - my $data = $self->app->storage->get_processed_data($self->param('uuid')); - $self->stash(processed_data => $data); - - my $crashing_thread = CrashTest::Models::Thread->new($data->{crashing_thread}); - $crashing_thread = $self->app->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 = $self->app->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'); - - # TODO check for authorised values ... - my %paramshash = map { $_ => $self->req->param($_) } $self->req->param; - - my ($uuid, $uuidstr); - UUID::generate($uuid); - UUID::unparse($uuid, $uuidstr); - - $self->render_later(); - - $self->app->decode_queue->decode($file, \%paramshash, $uuidstr, sub { - my $pjson = shift; - # reply - $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 'bootstrap_pagination'; -plugin 'CrashTestHelpers'; - -app->start; diff -r e408da1419cd -r 0ebef32c34af bin/fs_to_db.pl --- a/bin/fs_to_db.pl Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -#!/usr/bin/env perl - -# 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 . - -use Mojo::Base -strict; -use lib 'lib'; - -use Mojo::JSON qw/decode_json/; -use Mojo::Util qw/decode slurp/; -use Mojo::Loader qw/load_class/; -use File::Basename; -use Mojolicious; -use Mojo::Home; - -if(scalar @ARGV == 0) { - say "Usage: $0 "; - exit 1; -} - -sub load_config { - my $app = Mojolicious->new; - $app->home(Mojo::Home->new("$FindBin::Bin/../")); - $app->moniker("CrashTest"); - return $app->plugin('Config'); -} - -my $config = load_config(); - -my $storage_class = $config->{Storage}->{Type}; -if (my $e = load_class($storage_class)) { - die ref $e ? "Exception: $e" : 'Not found!'; -} - -my $storage = $storage_class->new(config => $config->{Storage}); - -foreach my $arg (@ARGV) { - eval { - my $pjson = decode_json(slurp $arg); - - my($filename, $dirs, $suffix) = fileparse($arg, qr/\.[^.]*/); - - my $uuid = $filename; - $storage->_db_insert_processed_data($uuid, $pjson); - }; - warn $@ if $@; -} diff -r e408da1419cd -r 0ebef32c34af crash_test.conf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/crash_test.conf Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,30 @@ +{ + Processor => { + Common => { + JobQueue => { + Backend => { + Minion => { Pg => "postgresql:///crashtest" }, + }, + }, + }, + Breakpad => { + JSONStackwalker => 'stackwalker', + SymbolsPath => 'data/breakpad-debug-symbols/*', + }, + }, + Storage => [ + { Type => "Sql", db => { Pg => "postgresql:///crashtest" } }, + { Type => "File", DataDir => 'data/crashs/' }, + ], + WebInterface => { + ScmLinks => { + "svn:svn.example.org/testproject" => 'https://redmine.example.org/projects/testproject/repository/entry/<%= $scmpath =%>?rev=<%= $rev =%>#L<%= $line =%>', + }, + ExtraColumns => { + Index => [ + { id => 'os', db_column => "crash_user.os", name => 'Operating System' }, + ], + }, + }, +}; +# vim:ft=perl: diff -r e408da1419cd -r 0ebef32c34af dbicdh/PostgreSQL/deploy/1/001-auto-__VERSION.sql --- a/dbicdh/PostgreSQL/deploy/1/001-auto-__VERSION.sql Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ --- --- Created by SQL::Translator::Producer::PostgreSQL --- Created on Sun Feb 1 19:58:35 2015 --- -; --- --- Table: dbix_class_deploymenthandler_versions. --- -CREATE TABLE "dbix_class_deploymenthandler_versions" ( - "id" serial NOT NULL, - "version" character varying(50) NOT NULL, - "ddl" text, - "upgrade_sql" text, - PRIMARY KEY ("id"), - CONSTRAINT "dbix_class_deploymenthandler_versions_version" UNIQUE ("version") -); - -; diff -r e408da1419cd -r 0ebef32c34af dbicdh/PostgreSQL/deploy/1/001-auto.sql --- a/dbicdh/PostgreSQL/deploy/1/001-auto.sql Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ --- --- Created by SQL::Translator::Producer::PostgreSQL --- Created on Sun Feb 1 19:58:35 2015 --- -; --- --- Table: crash_users. --- -CREATE TABLE "crash_users" ( - "id" serial NOT NULL, - "user_id" character varying(40) NOT NULL, - "os" character varying(40), - "cpu_arch" character varying(10), - "cpu_count" integer, - "extra_info" text, - PRIMARY KEY ("id") -); - -; --- --- Table: modules. --- -CREATE TABLE "modules" ( - "id" serial NOT NULL, - "debug_id" character varying(33) NOT NULL, - "filename" character varying(128) NOT NULL, - "version" character varying(64), - PRIMARY KEY ("id"), - CONSTRAINT "module_id" UNIQUE ("debug_id", "filename") -); - -; --- --- Table: products. --- -CREATE TABLE "products" ( - "id" serial NOT NULL, - "distributor" character varying(40), - "name" character varying(40), - "version" character varying(16), - "release_channel" character varying, - PRIMARY KEY ("id") -); - -; --- --- Table: crash_reports. --- -CREATE TABLE "crash_reports" ( - "id" serial NOT NULL, - "start_time" timestamp, - "crash_time" timestamp, - "uuid" character varying(36) NOT NULL, - "bug_reference" character varying(20), - "crash_user_id" integer NOT NULL, - "product_id" integer NOT NULL, - PRIMARY KEY ("id") -); -CREATE INDEX "crash_reports_idx_crash_user_id" on "crash_reports" ("crash_user_id"); -CREATE INDEX "crash_reports_idx_product_id" on "crash_reports" ("product_id"); - -; --- --- Table: crash_threads. --- -CREATE TABLE "crash_threads" ( - "id" serial NOT NULL, - "number" integer NOT NULL, - "crashed" bool NOT NULL, - "crash_report_id" integer NOT NULL, - PRIMARY KEY ("id") -); -CREATE INDEX "crash_threads_idx_crash_report_id" on "crash_threads" ("crash_report_id"); - -; --- --- Table: crash_frames. --- -CREATE TABLE "crash_frames" ( - "id" serial NOT NULL, - "number" integer NOT NULL, - "function" character varying(128), - "source_file" character varying(128), - "source_line" integer, - "stack_walk_mode" character varying(10), - "crash_thread_id" integer NOT NULL, - "module_id" integer NOT NULL, - PRIMARY KEY ("id") -); -CREATE INDEX "crash_frames_idx_crash_thread_id" on "crash_frames" ("crash_thread_id"); -CREATE INDEX "crash_frames_idx_module_id" on "crash_frames" ("module_id"); - -; --- --- Foreign Key Definitions --- - -; -ALTER TABLE "crash_reports" ADD CONSTRAINT "crash_reports_fk_crash_user_id" FOREIGN KEY ("crash_user_id") - REFERENCES "crash_users" ("id") DEFERRABLE; - -; -ALTER TABLE "crash_reports" ADD CONSTRAINT "crash_reports_fk_product_id" FOREIGN KEY ("product_id") - REFERENCES "products" ("id") DEFERRABLE; - -; -ALTER TABLE "crash_threads" ADD CONSTRAINT "crash_threads_fk_crash_report_id" FOREIGN KEY ("crash_report_id") - REFERENCES "crash_reports" ("id") DEFERRABLE; - -; -ALTER TABLE "crash_frames" ADD CONSTRAINT "crash_frames_fk_crash_thread_id" FOREIGN KEY ("crash_thread_id") - REFERENCES "crash_threads" ("id") DEFERRABLE; - -; -ALTER TABLE "crash_frames" ADD CONSTRAINT "crash_frames_fk_module_id" FOREIGN KEY ("module_id") - REFERENCES "modules" ("id") DEFERRABLE; - -; diff -r e408da1419cd -r 0ebef32c34af dbicdh/PostgreSQL/deploy/2/002-extract_crashing_functions.sql --- a/dbicdh/PostgreSQL/deploy/2/002-extract_crashing_functions.sql Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - -CREATE OR REPLACE FUNCTION extract_crashing_functions (processed_json jsonb) RETURNS text AS -$$ - -- extract threads[crashing_idx]->frames[*]->function - SELECT string_agg(functions, E'\n') - FROM ( - SELECT jsonb_array_elements( - (($1 #> ARRAY['threads', $1->'crash_info'->>'crashing_thread'])->'frames') - )->>'function' AS functions - ) AS frames -$$ -LANGUAGE sql IMMUTABLE; - --- This extension is the contrib modules -CREATE EXTENSION IF NOT EXISTS pg_trgm; - --- Used for searching function in the crashing thread backtrace --- Search will be really slow if this index is not present -CREATE INDEX crash_report_datas_idx_extract_crashing_functions ON crash_report_datas USING gist ( - extract_crashing_functions(processed) gist_trgm_ops -); - diff -r e408da1419cd -r 0ebef32c34af dbicdh/SQLite/deploy/1/001-auto-__VERSION.sql --- a/dbicdh/SQLite/deploy/1/001-auto-__VERSION.sql Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ --- --- Created by SQL::Translator::Producer::SQLite --- Created on Sun Feb 1 19:58:35 2015 --- - -; -BEGIN TRANSACTION; --- --- Table: dbix_class_deploymenthandler_versions --- -CREATE TABLE dbix_class_deploymenthandler_versions ( - id INTEGER PRIMARY KEY NOT NULL, - version varchar(50) NOT NULL, - ddl text, - upgrade_sql text -); -CREATE UNIQUE INDEX dbix_class_deploymenthandler_versions_version ON dbix_class_deploymenthandler_versions (version); -COMMIT; diff -r e408da1419cd -r 0ebef32c34af dbicdh/SQLite/deploy/1/001-auto.sql --- a/dbicdh/SQLite/deploy/1/001-auto.sql Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ --- --- Created by SQL::Translator::Producer::SQLite --- Created on Sun Feb 1 19:58:35 2015 --- - -; -BEGIN TRANSACTION; --- --- Table: crash_users --- -CREATE TABLE crash_users ( - id INTEGER PRIMARY KEY NOT NULL, - user_id varchar(40) NOT NULL, - os varchar(40), - cpu_arch varchar(10), - cpu_count int, - extra_info text -); --- --- Table: modules --- -CREATE TABLE modules ( - id INTEGER PRIMARY KEY NOT NULL, - debug_id varchar(33) NOT NULL, - filename varchar(128) NOT NULL, - version varchar(64) -); -CREATE UNIQUE INDEX module_id ON modules (debug_id, filename); --- --- Table: products --- -CREATE TABLE products ( - id INTEGER PRIMARY KEY NOT NULL, - distributor varchar(40), - name varchar(40), - version varchar(16), - release_channel varchar -); --- --- Table: crash_reports --- -CREATE TABLE crash_reports ( - id INTEGER PRIMARY KEY NOT NULL, - start_time timestamp, - crash_time timestamp, - uuid varchar(36) NOT NULL, - bug_reference varchar(20), - crash_user_id int NOT NULL, - product_id int NOT NULL, - FOREIGN KEY (crash_user_id) REFERENCES crash_users(id), - FOREIGN KEY (product_id) REFERENCES products(id) -); -CREATE INDEX crash_reports_idx_crash_user_id ON crash_reports (crash_user_id); -CREATE INDEX crash_reports_idx_product_id ON crash_reports (product_id); --- --- Table: crash_threads --- -CREATE TABLE crash_threads ( - id INTEGER PRIMARY KEY NOT NULL, - number int NOT NULL, - crashed bool NOT NULL, - crash_report_id int NOT NULL, - FOREIGN KEY (crash_report_id) REFERENCES crash_reports(id) -); -CREATE INDEX crash_threads_idx_crash_report_id ON crash_threads (crash_report_id); --- --- Table: crash_frames --- -CREATE TABLE crash_frames ( - id INTEGER PRIMARY KEY NOT NULL, - number int NOT NULL, - function varchar(128), - source_file varchar(128), - source_line int, - stack_walk_mode varchar(10), - crash_thread_id int NOT NULL, - module_id int NOT NULL, - FOREIGN KEY (crash_thread_id) REFERENCES crash_threads(id), - FOREIGN KEY (module_id) REFERENCES modules(id) -); -CREATE INDEX crash_frames_idx_crash_thread_id ON crash_frames (crash_thread_id); -CREATE INDEX crash_frames_idx_module_id ON crash_frames (module_id); -COMMIT; diff -r e408da1419cd -r 0ebef32c34af dbicdh/_source/deploy/1/001-auto-__VERSION.yml --- a/dbicdh/_source/deploy/1/001-auto-__VERSION.yml Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ ---- -schema: - procedures: {} - tables: - dbix_class_deploymenthandler_versions: - constraints: - - deferrable: 1 - expression: '' - fields: - - id - match_type: '' - name: '' - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: PRIMARY KEY - - deferrable: 1 - expression: '' - fields: - - version - match_type: '' - name: dbix_class_deploymenthandler_versions_version - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: UNIQUE - fields: - ddl: - data_type: text - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: ddl - order: 3 - size: - - 0 - id: - data_type: int - default_value: ~ - is_auto_increment: 1 - is_nullable: 0 - is_primary_key: 1 - is_unique: 0 - name: id - order: 1 - size: - - 0 - upgrade_sql: - data_type: text - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: upgrade_sql - order: 4 - size: - - 0 - version: - data_type: varchar - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 1 - name: version - order: 2 - size: - - 50 - indices: [] - name: dbix_class_deploymenthandler_versions - options: [] - order: 1 - triggers: {} - views: {} -translator: - add_drop_table: 0 - filename: ~ - no_comments: 0 - parser_args: - sources: - - __VERSION - parser_type: SQL::Translator::Parser::DBIx::Class - producer_args: {} - producer_type: SQL::Translator::Producer::YAML - show_warnings: 0 - trace: 0 - version: 0.11020 diff -r e408da1419cd -r 0ebef32c34af dbicdh/_source/deploy/1/001-auto.yml --- a/dbicdh/_source/deploy/1/001-auto.yml Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,587 +0,0 @@ ---- -schema: - procedures: {} - tables: - crash_frames: - constraints: - - deferrable: 1 - expression: '' - fields: - - id - match_type: '' - name: '' - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: PRIMARY KEY - - deferrable: 1 - expression: '' - fields: - - crash_thread_id - match_type: '' - name: crash_frames_fk_crash_thread_id - on_delete: '' - on_update: '' - options: [] - reference_fields: - - id - reference_table: crash_threads - type: FOREIGN KEY - - deferrable: 1 - expression: '' - fields: - - module_id - match_type: '' - name: crash_frames_fk_module_id - on_delete: '' - on_update: '' - options: [] - reference_fields: - - id - reference_table: modules - type: FOREIGN KEY - fields: - crash_thread_id: - data_type: int - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: crash_thread_id - order: 7 - size: - - 0 - function: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: function - order: 3 - size: - - 128 - id: - data_type: int - default_value: ~ - is_auto_increment: 1 - is_nullable: 0 - is_primary_key: 1 - is_unique: 0 - name: id - order: 1 - size: - - 0 - module_id: - data_type: int - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: module_id - order: 8 - size: - - 0 - number: - data_type: int - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: number - order: 2 - size: - - 0 - source_file: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: source_file - order: 4 - size: - - 128 - source_line: - data_type: int - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: source_line - order: 5 - size: - - 0 - stack_walk_mode: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: stack_walk_mode - order: 6 - size: - - 10 - indices: - - fields: - - crash_thread_id - name: crash_frames_idx_crash_thread_id - options: [] - type: NORMAL - - fields: - - module_id - name: crash_frames_idx_module_id - options: [] - type: NORMAL - name: crash_frames - options: [] - order: 6 - crash_reports: - constraints: - - deferrable: 1 - expression: '' - fields: - - id - match_type: '' - name: '' - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: PRIMARY KEY - - deferrable: 1 - expression: '' - fields: - - crash_user_id - match_type: '' - name: crash_reports_fk_crash_user_id - on_delete: '' - on_update: '' - options: [] - reference_fields: - - id - reference_table: crash_users - type: FOREIGN KEY - - deferrable: 1 - expression: '' - fields: - - product_id - match_type: '' - name: crash_reports_fk_product_id - on_delete: '' - on_update: '' - options: [] - reference_fields: - - id - reference_table: products - type: FOREIGN KEY - fields: - bug_reference: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: bug_reference - order: 5 - size: - - 20 - crash_time: - data_type: timestamp - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: crash_time - order: 3 - size: - - 0 - crash_user_id: - data_type: int - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: crash_user_id - order: 6 - size: - - 0 - id: - data_type: int - default_value: ~ - is_auto_increment: 1 - is_nullable: 0 - is_primary_key: 1 - is_unique: 0 - name: id - order: 1 - size: - - 0 - product_id: - data_type: int - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: product_id - order: 7 - size: - - 0 - start_time: - data_type: timestamp - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: start_time - order: 2 - size: - - 0 - uuid: - data_type: varchar - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: uuid - order: 4 - size: - - 36 - indices: - - fields: - - crash_user_id - name: crash_reports_idx_crash_user_id - options: [] - type: NORMAL - - fields: - - product_id - name: crash_reports_idx_product_id - options: [] - type: NORMAL - name: crash_reports - options: [] - order: 4 - crash_threads: - constraints: - - deferrable: 1 - expression: '' - fields: - - id - match_type: '' - name: '' - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: PRIMARY KEY - - deferrable: 1 - expression: '' - fields: - - crash_report_id - match_type: '' - name: crash_threads_fk_crash_report_id - on_delete: '' - on_update: '' - options: [] - reference_fields: - - id - reference_table: crash_reports - type: FOREIGN KEY - fields: - crash_report_id: - data_type: int - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: crash_report_id - order: 4 - size: - - 0 - crashed: - data_type: bool - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: crashed - order: 3 - size: - - 0 - id: - data_type: int - default_value: ~ - is_auto_increment: 1 - is_nullable: 0 - is_primary_key: 1 - is_unique: 0 - name: id - order: 1 - size: - - 0 - number: - data_type: int - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: number - order: 2 - size: - - 0 - indices: - - fields: - - crash_report_id - name: crash_threads_idx_crash_report_id - options: [] - type: NORMAL - name: crash_threads - options: [] - order: 5 - crash_users: - constraints: - - deferrable: 1 - expression: '' - fields: - - id - match_type: '' - name: '' - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: PRIMARY KEY - fields: - cpu_arch: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: cpu_arch - order: 4 - size: - - 10 - cpu_count: - data_type: int - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: cpu_count - order: 5 - size: - - 0 - extra_info: - data_type: text - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: extra_info - order: 6 - size: - - 0 - id: - data_type: int - default_value: ~ - is_auto_increment: 1 - is_nullable: 0 - is_primary_key: 1 - is_unique: 0 - name: id - order: 1 - size: - - 0 - os: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: os - order: 3 - size: - - 40 - user_id: - data_type: varchar - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 0 - name: user_id - order: 2 - size: - - 40 - indices: [] - name: crash_users - options: [] - order: 1 - modules: - constraints: - - deferrable: 1 - expression: '' - fields: - - id - match_type: '' - name: '' - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: PRIMARY KEY - - deferrable: 1 - expression: '' - fields: - - debug_id - - filename - match_type: '' - name: module_id - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: UNIQUE - fields: - debug_id: - data_type: varchar - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 1 - name: debug_id - order: 2 - size: - - 33 - filename: - data_type: varchar - default_value: ~ - is_nullable: 0 - is_primary_key: 0 - is_unique: 1 - name: filename - order: 3 - size: - - 128 - id: - data_type: int - default_value: ~ - is_auto_increment: 1 - is_nullable: 0 - is_primary_key: 1 - is_unique: 0 - name: id - order: 1 - size: - - 0 - version: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: version - order: 4 - size: - - 64 - indices: [] - name: modules - options: [] - order: 2 - products: - constraints: - - deferrable: 1 - expression: '' - fields: - - id - match_type: '' - name: '' - on_delete: '' - on_update: '' - options: [] - reference_fields: [] - reference_table: '' - type: PRIMARY KEY - fields: - distributor: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: distributor - order: 2 - size: - - 40 - id: - data_type: int - default_value: ~ - is_auto_increment: 1 - is_nullable: 0 - is_primary_key: 1 - is_unique: 0 - name: id - order: 1 - size: - - 0 - name: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: name - order: 3 - size: - - 40 - release_channel: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: release_channel - order: 5 - size: - - 0 - version: - data_type: varchar - default_value: ~ - is_nullable: 1 - is_primary_key: 0 - is_unique: 0 - name: version - order: 4 - size: - - 16 - indices: [] - name: products - options: [] - order: 3 - triggers: {} - views: {} -translator: - add_drop_table: 0 - filename: ~ - no_comments: 0 - parser_args: - sources: - - CrashFrame - - CrashReport - - CrashThread - - CrashUser - - Module - - Product - parser_type: SQL::Translator::Parser::DBIx::Class - producer_args: {} - producer_type: SQL::Translator::Producer::YAML - show_warnings: 0 - trace: 0 - version: 0.11020 diff -r e408da1419cd -r 0ebef32c34af dist.ini --- a/dist.ini Sun Sep 27 22:45:40 2015 +0200 +++ b/dist.ini Wed Nov 04 17:43:00 2015 +0100 @@ -1,10 +1,10 @@ name = CrashTest -version = 0.1.0 +version = 0.2.0 author = Vincent Tondellier license = GPL_3 copyright_holder = Vincent Tondellier -main_module = CrashTest.pl +main_module = script/crash_test.pl [@Basic] [Mercurial::Check] @@ -25,23 +25,24 @@ Mojolicious::Plugin::BootstrapPagination = 0.12 UUID = 0.0.3 Text::Balanced = 0 +Data::Page = 0 +Minion = 2.00 + [Prereqs / RuntimeRecommends] -; Gearman Queue (also contains the Worker class) -Gearman::Client = 1.10 -; Database Storage (minimal tested versions, may work with older releases) -DBIx::Class::DeploymentHandler = 0.002000 -DBIx::Class = 0.08196 -DBIx::Class::Candy = 0.002100 ; For search field (only for SQL) -Search::Query = 0.300 -Search::Query::Dialect::DBIxClass = 0.005 -; PostgreSQL +Search::QueryParser = 0.90 +Search::QueryParser::Sql = 0.10 +; PostgreSQL (recommended) DateTime::Format::Pg = 0 DBD::Pg = 0 -; SQLite +Mojo::Pg = 2.10 +; SQLite (untested) DateTime::Format::SQLite = 0 DBD::SQLite = 0 -; MySQL +Mojo::SQLite = 0.010 +Minion::Backend::SQLite = 0 +; MySQL (untested) DateTime::Format::MySQL = 0 DBD::mysql = 0 - +Mojo::mysql = 0 +Minion::Backend::mysql = 0 diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,53 @@ +package CrashTest; +use Mojo::Base 'Mojolicious'; + +use CrashTest::Model::Storage; +use CrashTest::Model::StackFilter; +use CrashTest::Model::CrashReport; +use CrashTest::Model::CrashProcessor; + +# This method will run once at server start +sub startup { + my $self = shift; + + $self->secrets([ + 'My secret passphrase here' + ]); + + # External plugins + $self->plugin('Config'); + $self->plugin('bootstrap_pagination'); + # Documentation browser under "/perldoc" + #$self->plugin('PODRenderer'); + + # Commands + push @{$self->commands->namespaces}, 'CrashTest::Command'; + + # Helpers + $self->plugin("CrashTest::Helper::DateTime"); + $self->plugin("CrashTest::Helper::Backtrace"); + $self->plugin("CrashTest::Helper::XmlEscape"); + + + $self->helper(crash_reports => sub { state $crash_reports = CrashTest::Model::CrashReport->new (app => $self); }); + + $self->helper(crash_processor => sub { state $crash_processor = CrashTest::Model::CrashProcessor->new (app => $self, config => $self->config); }); + $self->helper(stackfilter => sub { state $crash_reports = CrashTest::Model::StackFilter->new (app => $self, config => $self->config); }); + $self->helper(storage => sub { state $storage = CrashTest::Model::Storage->new (app => $self, config => $self->config); }); + + $self->plugin('Minion', $self->config->{Processor}->{Common}->{JobQueue}->{Backend}->{Minion}); + + $self->storage->load_plugins(); + $self->crash_processor->load_plugins(); + + # Router + my $r = $self->routes; + + # Normal route to controller + $r->get('/')->to('crash_reports#index')->name('index'); + $r->get('/report/:uuid' => [ uuid => qr/[0-9a-fA-F-]+/ ])->to('crash_reports#report')->name('report'); + + $r->post('/submit')->to('crash_inserter#insert'); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Command/insert.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Command/insert.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,55 @@ +# 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 . + +package CrashTest::Command::insert; +use Mojo::Base 'Mojolicious::Command'; +use Mojo::JSON qw/decode_json/; +use Mojo::Util qw/slurp/; +use File::Spec::Functions qw/catdir catfile/; +use File::Basename; + +# Short description +has description => 'Insert crash'; + +# Short usage message +has usage => <usage; + exit 0; + } + + foreach my $jsonfile(@args) { + my ($uuid,$path,$suffix) = fileparse($jsonfile, qw/.json/); + + my $dmp = undef; + if(-e catfile($path, "$uuid.dmp")) { + $dmp = slurp(catfile($path, "$uuid.dmp")); + } + + eval { + my $pjson = decode_json(slurp(catfile($path, "$uuid.json"))); + $self->app->crash_reports->create($uuid, $pjson, $pjson->{client_info}, $dmp); + }; + warn $@ if $@; + + } + +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Commands/db.pm --- a/lib/CrashTest/Commands/db.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -# 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 . - -package CrashTest::Commands::db; -use Mojo::Base 'Mojolicious::Command'; -use v5.12; - -use DBIx::Class::DeploymentHandler; -use CrashTest::Storage::Sql::Schema; - -# Short description -has description => 'Setup database.'; - -# Short usage message -has usage => <connect($self->app->config->{Storage}->{DSN}); - - my $dhargs = { - schema => $schema, - script_directory => "$FindBin::Bin/dbicdh", - sql_translator_args => { add_drop_table => 0 }, - force_overwrite => 0, - }; - - if($args[0] eq "prepare") { - $dhargs->{force_overwrite} = 1; - my $dh = DBIx::Class::DeploymentHandler->new($dhargs); - $dh->prepare_install; -# $dh->prepare_upgrade; - } elsif($args[0] eq "create") { - my $dh = DBIx::Class::DeploymentHandler->new($dhargs); - $dh->install; -# } elsif($args[0] eq "upgrade") { -# my $dh = DBIx::Class::DeploymentHandler->new($dhargs); -# $dh->upgrade; - } else { - say "Invalid arguments"; - exit 1; - } - -} - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Controller/CrashInserter.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Controller/CrashInserter.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,28 @@ +# 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 . + +package CrashTest::Controller::CrashInserter; +use Mojo::Base 'Mojolicious::Controller'; + +sub insert { + my $self = shift; + + my $processor_task = $self->app->crash_processor->find_processor($self->req); + return $self->render(text => "No processor found", status => 421) if !$processor_task; + + $self->app->crash_processor->decode($processor_task, $self->req); + + return $self->render(text => "ok"); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Controller/CrashReports.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Controller/CrashReports.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,69 @@ +# 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 . + +package CrashTest::Controller::CrashReports; +use Mojo::Base 'Mojolicious::Controller'; +use Mojo::Util qw/dumper/; + +use CrashTest::Model::Thread; + +sub index { + my $self = shift; + + my $page = 1; + my $crashs_per_page = 25; + + if($self->req->url =~ qr{.*\.atom$}) { + $crashs_per_page = 100; + } + + $self->validation->required('page')->like(qr/^[0-9]+$/); + $page = scalar $self->validation->param("page") if $self->validation->is_valid('page'); + + my ($results, $pager) = $self->crash_reports->index($page, $crashs_per_page, $self->req->param('search')); + + #$self->app->log->debug(dumper $results); + + $self->stash(crashs => $results); + $self->stash(pager => $pager); + $self->stash(extra_columns => []); + + $self->render("index"); +} + +sub report { + my ($self, $uuid) = @_; + + my $data = $self->app->crash_reports->get_processed_data($self->param('uuid')); + $self->stash(processed_data => $data); + + my $crashing_thread = CrashTest::Model::Thread->new($data->{crashing_thread}); + $crashing_thread = $self->app->stackfilter->apply($crashing_thread); + $self->stash(crashing_thread => $crashing_thread); + + my @threads = (); + foreach my $raw_thread(@{$data->{threads}}) { + my $thread = CrashTest::Model::Thread->new($raw_thread); + $thread = $self->app->stackfilter->apply($thread); + push @threads, $thread; + } + $self->stash(threads => \@threads); + + #my $similar = $self->app->storage->get_similar_crashs($self->param('uuid')); + #$self->stash(similar => $similar->{crashs}); + $self->stash(extra_columns => $self->app->config->{WebInterface}->{ExtraColumns}->{Index}); + + $self->render("report/crash"); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Helper/Backtrace.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Helper/Backtrace.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,44 @@ +# 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 . + +package CrashTest::Helper::Backtrace; +use Mojo::Base 'Mojolicious::Plugin'; + +sub register { + my ($self, $app, $conf) = @_; + + $app->helper(module_warnings => sub { $self->_bt_warning("module_warnings", @_) } ); + $app->helper(frame_warnings => sub { $self->_bt_warning("frame_warnings", @_) } ); +} + +sub _bt_warning { + my ($self, $type, $c, $frame) = @_; + + my $warnings; + if($type eq "frame_warnings") { + $warnings = $frame->frame_warnings; + } elsif($type eq "module_warnings") { + $warnings = $frame->module_warnings; + } + + return "" if(!$warnings); + + my @ret; + foreach my $warning (@{$warnings}) { + push @ret, scalar $c->render_to_string('report/backtrace/warning', tooltip_text => $warning); + } + + return Mojo::ByteStream->new(join "", @ret); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Helper/DateTime.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Helper/DateTime.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,46 @@ +# 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 . + +package CrashTest::Helper::DateTime; +use Mojo::Base 'Mojolicious::Plugin'; + +use DateTime::Format::Pg; + +sub register { + my ($self, $app, $conf) = @_; + + $app->helper(date_from_db_utc => sub { + my $d = $self->_date_from_db_utc(@_); + return "" unless $d; + $d->set_time_zone('local')->strftime("%F %T"); + }); + + $app->helper(date_with_tz_from_db_utc => sub { + my $d = $self->_date_from_db_utc(@_); + return "" unless $d; + $d->strftime("%FT%TZ"); + }); +} + +sub _date_from_db_utc { + my ($self, $c, $dbdate) = @_; + my $utcdate; + eval { + $utcdate = DateTime::Format::Pg->parse_datetime($dbdate)->set_time_zone('UTC'); + }; + $c->app->log->warn($@) if $@; + + return $utcdate; +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Helper/XmlEscape.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Helper/XmlEscape.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,31 @@ +# 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 . + +package CrashTest::Helper::XmlEscape; +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; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Helpers/CrashTestHelpers.pm --- a/lib/CrashTest/Helpers/CrashTestHelpers.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# 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 . - -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; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Model/CrashProcessor.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Model/CrashProcessor.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,78 @@ +# 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 . + +package CrashTest::Model::CrashProcessor; +use Mojo::Base -base; +use File::Temp; +use UUID; + +has [ qw/app config/ ]; +has processors => sub { return [] }; + +sub _register_instance { + my ($self, $processor_instance) = @_; + + push @{$self->processors}, $processor_instance; +} + +sub load_plugins { + my ($self) = @_; + + my @conf_processors = grep(!/^Common$/, keys %{$self->config->{Processor}}); + for my $module (@conf_processors) { + $self->app->plugin('CrashTest::Plugin::CrashProcessor::' . $module, { + config => $self->config, + cb => sub { my $i = shift; $self->_register_instance($i); } + }); + } +} + +sub find_processor { + my ($self, $req) = @_; + + # find the first processor that accepts this kind of crash + foreach my $processor(@{$self->processors}) { + my $validator = $processor->validate($req); + if(!$validator->has_error) { + return $processor->task_name; + } + } + return undef; +} + +sub decode { + my ($self, $task_name, $req) = @_; + + my $tmpdir = $self->config->{Processor}->{Common}->{TmpDir}; + + my $files; + foreach my $up_name(map { $_->name } @{$req->uploads}) { + foreach my $up(@{$req->every_upload($up_name)}) { + my $fh = File::Temp->new(DIR => $tmpdir, SUFFIX => '.dat'); + $fh->unlink_on_destroy(0); + + #print $fh $up->slurp; + $up->move_to($fh->filename); + + $files->{$up_name} ||= []; + push @{$files->{$up_name}}, $fh->filename; + } + } + + my ($uuid, $uuidstr); + UUID::generate($uuid); + UUID::unparse($uuid, $uuidstr); + my $id = $self->app->minion->enqueue($task_name => [ $uuidstr, $req->params->to_hash, $files ]); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Model/CrashReport.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Model/CrashReport.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,37 @@ +# 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 . + +package CrashTest::Model::CrashReport; +use Mojo::Base -base; + +has [ qw/app config/ ]; + +sub index { + my ($self, $page, $nperpage, $search_str) = @_; + + return $self->app->storage->first("index", $page, $nperpage, $search_str); +} + +sub create { + my ($self, $uuid, $pjson, $dump) = @_; + + return $self->app->storage->each("create", $uuid, $pjson, $dump); +} + +sub get_processed_data { + my ($self, $uuid) = @_; + + return $self->app->storage->first("get_processed_data", $uuid); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Model/Frame.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Model/Frame.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,49 @@ +# 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 . + +package CrashTest::Model::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 frame_number => undef; +has module_name => ""; +has function_name => ""; +has file_link => ""; +has frame_warnings => sub { return []; }; +has module_warnings => sub { return []; }; +#has "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); + } + + return $self; +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Model/StackFilter.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Model/StackFilter.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,80 @@ +# 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 . + +package CrashTest::Model::StackFilter; +use Mojo::Base -base; +use Mojo::Loader qw/load_class find_modules/; + +has [ qw/config app filters/ ]; + +sub new { + my $self = shift->SUPER::new(@_); + + if(defined($self->config->{StackFilters})) { + $self->load_plugins($self->config->{StackFilters}); + } else { + $self->find_stackfilters_plugins(); + } + + return $self; +} + +sub apply { + my ($self, $thread) = @_; + + foreach my $filter(@{$self->filters}) { + #say "apply filter $filter"; + $thread = $filter->apply($thread); + } + + return $thread; +} + +sub load_plugins { + my ($self, $modules) = @_; + + my @filters = (); + for my $module (@$modules) { + my $e = load_class($module); + die qq{Loading "$module" failed: $e} if ref $e; + + #say "loading $module"; + push @filters, $module->new(config => $self->config, app => $self->app); + } + + $self->filters(\@filters); +} + +sub find_stackfilters_plugins { + my ($self) = @_; + + my @modules = (); + + # Find modules in a namespace + for my $module (find_modules('CrashTest::Plugin::StackFilter')) { + my $e = load_class($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; + + # instanciate + my @filters = map { $_->[0]->new(config => $self->config, app => $self->app) } @modules; + + $self->filters(\@filters); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Model/Storage.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Model/Storage.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,70 @@ +# 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 . + +package CrashTest::Model::Storage; +use Mojo::Base -base; + +has [ qw/app config/ ]; +has instances => sub { return [] }; + +sub register { + my ($self, $storage_instance) = @_; + + push @{$self->instances}, $storage_instance; + + #$self->app->log->debug("Loaded $storage_instance"); +} + +sub load_plugins { + my $self = shift; + + for my $storage(@{$self->config->{Storage}}) { + $self->app->plugin("CrashTest::Plugin::Storage::" . $storage->{Type}, $storage); + } +} + + +sub each { + my ($self, $proc, @args) = @_; + + my $result = 1; + foreach my $storage(@{$self->instances}) { + if(defined(my $model = $storage->models->{CrashReport})) { + if($model->can($proc)) { + $result = $result && $model->$proc(@args); + } + } + } + return $result; +} + +sub first { + my ($self, $proc, @args) = @_; + + my @result; + foreach my $storage(@{$self->instances}) { + + if(defined(my $model = $storage->models->{CrashReport})) { + if($model->can($proc)) { + @result = $model->$proc(@args); + if(@result) { + last; + } + } + } + } + + return wantarray ? @result : shift @result; +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Model/Thread.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Model/Thread.pm Wed Nov 04 17:43:00 2015 +0100 @@ -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 . + +package CrashTest::Model::Thread; +use Mojo::Base -base; +use Mojo::ByteStream 'b'; +use Mojolicious::Plugin::TagHelpers; +use CrashTest::Model::Frame; + +has [ qw/frame_count crashing_thread frames/ ]; + +sub new { + my $self = shift->SUPER::new(@_); + + foreach my $frame(@{$self->frames}) { + $frame = CrashTest::Model::Frame->new($frame); + } + + return $self; +} + +sub each_frame { + my ($self, $block) = @_; + + foreach my $frame(@{$self->frames}) { + $block->($frame); + } +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Models/Frame.pm --- a/lib/CrashTest/Models/Frame.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# 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 . - -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; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Models/Thread.pm --- a/lib/CrashTest/Models/Thread.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# 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 . - -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; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/CrashProcessor/Breakpad.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/CrashProcessor/Breakpad.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,87 @@ +# 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 . + +package CrashTest::Plugin::CrashProcessor::Breakpad; +use Mojo::Base 'Mojolicious::Plugin'; +use Mojo::JSON qw/decode_json/; +use Mojo::Util qw/dumper/; +use Mojolicious::Validator; + +has [ qw/app config dumper_config/ ]; +has task_name => "breakpad_decode_task"; + +sub register { + my ($self, $app, $args) = @_; + + $self->app($app); + $self->config($args->{config}); + $self->dumper_config($self->config->{Processor}->{Breakpad}); + + $app->minion->add_task($self->task_name => + sub { + my ($job, $uuid, $params, $files) = @_; + #$job->app->log->debug(dumper $params); + + $job->on(failed => sub { $self->cleanup($files); }); + #$job->on(finished => sub { $self->cleanup($files); }); + + $self->decode($uuid, $params, $files); + $self->cleanup($files); + } + ); + + $app->log->debug("Registered Breakpad"); + $args->{cb}->($self); +} + +sub validate { + my ($self, $req) = @_; + + my $hash = $req->params->to_hash; + $hash->{$_} = $req->every_upload($_) for map { $_->name } @{$req->uploads}; + + my $validation = Mojolicious::Validator->new->validation->input($hash); + + $validation->required('UserID'); + #$validation->required('Version'); + $validation->required('ProductName'); + + $validation->required('upload_file_minidump')->upload->size(1024, 4 * 1024 * 1024); + + return $validation; +} + +sub decode { + my ($self, $uuidstr, $client_info, $files) = @_; + + my $dmp_file = $files->{upload_file_minidump}->[0]; + + my $cmd = $self->dumper_config->{JSONStackwalker} . " $dmp_file " . $self->dumper_config->{SymbolsPath}; + my $out = qx($cmd 2>/dev/null) or die $!; + + my $pjson = decode_json($out); + + $self->app->crash_reports->create($uuidstr, $pjson, $client_info, $dmp_file); +} + +sub cleanup { + my ($self, $files) = @_; + #$self->app->log->debug("cleanup " . dumper $files); + foreach my $values(values %$files) { + foreach my $f(@$values) { + unlink $f or $self->app->log->warn("Failed to unlink $f: $!"); + } + } +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/StackFilter/FileLink.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/StackFilter/FileLink.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,104 @@ +# 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 . + +package CrashTest::Plugin::StackFilter::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 _link_template { + my ($self, $scm, $repo) = @_; + + my $linkconf = "$scm:$repo"; + if(defined($self->{_template_cache}->{$linkconf})) { + return $self->{_template_cache}->{$linkconf}; + } + my $template = $self->config->{WebInterface}->{ScmLinks}->{$linkconf}; + + # try the generic repository type + if(!defined($template)) { + $linkconf = $scm; + if(defined($self->{_template_cache}->{$linkconf})) { + return $self->{_template_cache}->{$linkconf}; + } + $template = $self->config->{WebInterface}->{ScmLinks}->{$linkconf}; + } + + return undef if !defined($template); + + $self->app->log->debug("Building template for $linkconf"); + + my $mt = Mojo::Template->new + ->prepend('my ($repo, $scmpath, $rev, $line) = @_;') + ->auto_escape(1) + ->escape(sub { + my $str = shift; + return Mojo::ByteStream::b($str)->url_escape; + }) + ->parse($template) + ->build; + my $e = $mt->compile; + if($e) { + $self->app->log->error($e); + return undef; + } + + $self->{_template_cache}->{$linkconf} = $mt; + return $mt; +} + +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 =~ /([^:]+):([^:]+):([^:]+):(.+)/) { + my ($scm, $repo, $scmpath, $rev) = ($1, $2, $3, $4); + my $filename = File::Spec->splitpath($scmpath); + + # and we have a link template configured for this specific repository or repository type + my $template = $self->_link_template($scm, $repo); + if(defined($template)) { + # build url using the configured template + my $url = $template->interpret($repo, $scmpath, $rev, $line); + # and 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; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/StackFilter/FrameTrust.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/StackFilter/FrameTrust.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,61 @@ +# 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 . + +package CrashTest::Plugin::StackFilter::FrameTrust; +use Mojo::Base -base; + +sub priority { return 10; } + +has [ qw/app/ ]; + +sub new { + my $self = shift->SUPER::new(@_); + + return $self; +} + +sub apply { + my ($self, $thread) = @_; + + $thread->each_frame(sub { + my $frame = shift; + + $self->set_trust($frame); + } + ); + + return $thread; +} + +sub set_trust { + my ($self, $frame) = @_; + + if(!defined($frame->module) && $frame->offset ne "0x0") { + push @{$frame->module_warnings}, "No module loaded at this address"; + } + + if($frame->missing_symbols) { + push @{$frame->module_warnings}, "Missing symbols"; + } + + if($frame->corrupt_symbols) { + push @{$frame->module_warnings}, "Corrupt symbols"; + } + + if(defined($frame->trust) && $frame->trust eq "scan") { + push @{$frame->frame_warnings}, "Imprecise stackwalking"; + } + +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/StackFilter/HideArgs.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/StackFilter/HideArgs.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,56 @@ +# 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 . + +package CrashTest::Plugin::StackFilter::HideArgs; +use Mojo::Base -base; +use Text::Balanced qw/extract_bracketed/; + +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 = ""; + my $text = $signature; + do { + my ($str, $next, $prefix) = extract_bracketed($text, '<(', '[^<(]*'); + if($str) { + $short_signature .= $prefix . substr($str, 0, 1) . substr($str, -1, 1); + $text = $next; + } else { + $short_signature .= $next; + $text = undef; + } + } while($text); + + return $self->app->t(code => (title => $signature, class => "shortened-signature prettyprint lang-cpp") => $short_signature); +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/Storage/Base.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/Storage/Base.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,48 @@ +# 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 . + +package CrashTest::Plugin::Storage::Base; +use Mojo::Base -base; +use Scalar::Util qw/weaken/; + +use Mojo::Loader qw/find_packages find_modules load_class/; +has [ qw/app config/ ]; + +sub _load_models { + my ($self, $model_ns) = @_; + + my %models; + + my @pkgs = find_packages $model_ns; + push @pkgs, find_modules $model_ns; + foreach my $pkg(@pkgs) { + my @pkg_comp = split(/::/, $pkg); + my $pkg_name = $pkg_comp[-1]; + + my $e = load_class $pkg; + warn qq{Loading "$pkg" failed: $e} and next if ref $e; + + my $instance = "$pkg"->new(instance => $self, app => $self->app, config => $self->config); + weaken $instance->{instance}; + weaken $instance->{app}; + weaken $instance->{config}; + $models{$pkg_name} = $instance; + } + + weaken $self->{app}; + weaken $self->{config}; + + return \%models; +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/Storage/File.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/Storage/File.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,98 @@ +# 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 . + +package CrashTest::Plugin::Storage::File; +use Mojo::Base 'Mojolicious::Plugin'; + +sub register { + my ($self, $app, $conf) = @_; + + $app->storage->register(CrashTest::Plugin::Storage::File::Instance->new(app => $app, config => $conf)); +} + +1; + +package CrashTest::Plugin::Storage::File::Instance; +use Mojo::Base "CrashTest::Plugin::Storage::Base"; + +has models => sub { + my $self = shift; + + state $models = $self->_load_models("CrashTest::Plugin::Storage::File::Model"); +}; + +1; + +package CrashTest::Plugin::Storage::File::Model::CrashReport; +use Mojo::Base -base; +use Mojo::Util qw/slurp spurt/; +use Mojo::JSON qw/j decode_json/; +use File::Spec; + +has [ qw/instance app config extra_columns data_path/ ]; + +sub new { + my $self = shift->SUPER::new(@_); + + $self->data_path($self->config->{DataDir}); + + return $self; +} + +sub create { + my ($self, $uuid, $pjson, $client_info, $dump) = @_; + + $self->_store_dump($uuid, $dump); + $self->_store_processed_data($uuid, $pjson, $client_info); +} + +sub get_processed_data { + my ($self, $uuid) = @_; + + my $jsonfilename = File::Spec->catfile($self->data_path, "$uuid.json"); + my $json_content = slurp($jsonfilename) or die $!; + + my $processed_data = decode_json($json_content); + + return $processed_data; +} + +sub _store_processed_data { + my ($self, $uuid, $pjson, $client_info) = @_; + + # Create json for the params + $pjson->{client_info} = $client_info; + + my $jsonfilename = File::Spec->catfile($self->data_path, "$uuid.json"); + my $dmpfilename = File::Spec->catfile($self->data_path, "$uuid.dmp"); + + spurt(j($pjson), $jsonfilename) or die $!; + + # Set time of the .dmp to the CrashTime + my $crashtime = $pjson->{client_info}->{CrashTime}; + if(defined($crashtime)) { + utime $crashtime, $crashtime, $dmpfilename; + } +} + +sub _store_dump { + my ($self, $uuid, $dmp_content) = @_; + + my $dmp_file = File::Spec->catfile($self->data_path, "$uuid.dmp"); + my $fh = IO::File->new($dmp_file, "w") or die($!); + $fh->binmode; + print $fh $dmp_content; + undef $fh; +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/Storage/Sql.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/Storage/Sql.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,50 @@ +# 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 . + +package CrashTest::Plugin::Storage::Sql; +use Mojo::Base 'Mojolicious::Plugin'; + +sub register { + my ($self, $app, $conf) = @_; + + push @{$app->commands->namespaces}, 'CrashTest::Plugin::Storage::Sql::Command'; + $app->storage->register(CrashTest::Plugin::Storage::Sql::Instance->new(app => $app, config => $conf)); +} + +1; + +package CrashTest::Plugin::Storage::Sql::Instance; +use Mojo::Base "CrashTest::Plugin::Storage::Base"; +use Mojo::Loader qw/load_class/; + +has dbtype => sub { + my $self = shift; + my @dbtypes = keys %{$self->config->{db}}; + state $dbtype = $dbtypes[0]; +}; + +has dbh => sub { + my $self = shift; + + my $type = $self->dbtype; + load_class "Mojo::$type"; + state $dbh = "Mojo::$type"->new($self->config->{db}->{$type}); +}; + +has models => sub { + my $self = shift; + + state $models = $self->_load_models("CrashTest::Plugin::Storage::Sql::Model"); +}; + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/Storage/Sql/Command/db.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/Storage/Sql/Command/db.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,65 @@ +# 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 . + +package CrashTest::Plugin::Storage::Sql::Command::db; +use Mojo::Base 'Mojolicious::Command'; +use File::Spec::Functions 'catdir'; +use File::Basename; + +# Short description +has description => 'Setup database'; + +# Short usage message +has usage => <usage; + exit 0; + } + + my $path = dirname(__FILE__); + + foreach my $storage_instance(@{$self->app->storage->instances}) { + if($storage_instance->isa("CrashTest::Plugin::Storage::Sql::Instance")) { + + my $migrations = $storage_instance->dbh->migrations; + $migrations->name("crashtest"); + $migrations->from_file(catdir($path, '..', 'migrations_pg.sql')); + + if($args[0] eq "create") { + $migrations->migrate; + } elsif($args[0] eq "reset") { + $migrations->migrate(0)->migrate; + } elsif($args[0] eq "upgrade") { + $migrations->migrate; + } elsif($args[0] eq "downgrade") { + } else { + say "Invalid arguments"; + exit 1; + } + + } + } +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/Storage/Sql/Model/CrashReport.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/Storage/Sql/Model/CrashReport.pm Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,213 @@ +# 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 . + +package CrashTest::Plugin::Storage::Sql::Model::CrashReport; +use Mojo::Base -base; +use Mojo::JSON qw/j decode_json/; +use DateTime; +use Data::Page; +#use DBI::Log; +#$DBI::Log::trace = 0; +use Search::QueryParser::SQL; + +has [ qw/instance app config/ ]; + +has qw/db/; + +sub new { + my $self = shift->SUPER::new(@_); + + $self->db($self->instance->dbh->db); + + return $self; +} + +sub _build_query_from_search_string { + my ($self, $search) = @_; + + my @values; + + # define a callback to collect values safely (without magic markers) + my $cb = sub { + my ($column, $this_op, $value) = @_; + say $column; + if($column->type eq "fuzzy") { + $this_op = " ILIKE "; + $value = "%$value%"; + } + push @values, $value; + return join('', $column->stringify, $this_op, '?'); + }; + + my $search_fields = { + user_id => { callback => $cb, name => 'u.user_id' }, + product => { callback => $cb, name => 'p.name' }, + version => { callback => $cb, name => 'p.version' }, + channel => { callback => $cb, name => 'p.release_channel' }, + function => { callback => $cb, name => 'extract_crashing_functions(d.processed)', type => "fuzzy" }, + }; + + my $parser = Search::QueryParser::SQL->new( + columns => $search_fields, + default_column => "function", + strict => 1, + ); + + my $query = $parser->parse($search, 1) + or die "Error in query: " . $parser->err; + + # reset before calling dbi + @values = (); + + return [ $query->dbi->[0], \@values ]; +} + +sub index { + my ($self, $pagen, $nperpage, $search_str) = @_; + + my $where = ""; + my @values = (); + if(defined($search_str) && $search_str ne "") { + my $q = $self->_build_query_from_search_string($search_str); + $where = "WHERE " . $q->[0]; + @values = @{$q->[1]}; + } + + my $count = $self->db->query(" + SELECT count(crash_reports.id) AS total FROM crash_reports + JOIN crash_users AS u ON crash_reports.crash_user_id = u.id + JOIN products AS p ON crash_reports.product_id = p.id + JOIN crash_report_datas AS d ON crash_reports.id = d.crash_report_id + $where + ", @values)->hash; + + my $pager = Data::Page->new(); + $pager->total_entries($count->{total}); + $pager->entries_per_page($nperpage); + $pager->current_page($pagen); + + my $results = $self->db->query(" + SELECT crash_reports.*, + p.distributor AS p_distributor, p.name AS p_name, p.version AS p_version, p.release_channel AS p_release_channel, + u.os AS u_os, u.cpu_arch AS u_cpu_arch, u.cpu_count AS u_cpu_count, u.extra_info AS u_extra_info + FROM crash_reports + JOIN crash_users AS u ON crash_reports.crash_user_id = u.id + JOIN products AS p ON crash_reports.product_id = p.id + JOIN crash_report_datas AS d ON crash_reports.id = d.crash_report_id + $where + ORDER BY crash_time DESC + OFFSET (?) ROWS + FETCH NEXT (?) ROWS ONLY + ", + @values, + $pager->skipped, $pager->entries_per_page + )->hashes; + + return ($results, $pager); +} + +sub get_processed_data { + my ($self, $uuid) = @_; + + my $dbdata = $self->db->query("SELECT processed FROM crash_report_datas WHERE crash_report_id = (SELECT id FROM crash_reports WHERE uuid = ?)", $uuid)->hash; + + my $processed_data = decode_json($dbdata->{processed}); + + return $processed_data; +} + +sub create { + my ($self, $uuid, $pjson, $client_info, $dmp_content) = @_; + + if(!defined($client_info->{UserID})) { + $self->app->log->info("Invalid crash $uuid: no UserID"); + return; + } + + my $tx = $self->db->begin; + + my ($start_time, $crash_time); + if($client_info->{StartupTime}) { + $start_time = DateTime->from_epoch(epoch => $client_info->{StartupTime}); + } + if($client_info->{CrashTime}) { + $crash_time = DateTime->from_epoch(epoch => $client_info->{CrashTime}); + } + + my @product_values = ( + $client_info->{Distributor}, + $client_info->{ProductName}, + $client_info->{Version}, + ); + + my $dbproduct = $self->db->query("SELECT * FROM products WHERE distributor = ? AND name = ? AND version = ?", @product_values)->hash; + if(!$dbproduct) { + push @product_values, $client_info->{ReleaseChannel}; + $dbproduct = $self->db->query( + "INSERT INTO products (distributor, name, version, release_channel) VALUES (?, ?, ?, ?) RETURNING *", + @product_values + )->hash; + } + + my @user_values = ( + $client_info->{UserID}, + ); + + my $dbuser = $self->db->query("SELECT * FROM crash_users WHERE user_id = ?", @user_values)->hash; + if(!$dbuser) { + push @user_values, $pjson->{system_info}->{cpu_arch}; + push @user_values, $pjson->{system_info}->{cpu_count}; + push @user_values, $pjson->{system_info}->{os}; + push @user_values, j($client_info); + + $dbuser = $self->db->query( + "INSERT INTO crash_users (user_id, cpu_arch, cpu_count, os, extra_info) VALUES (?, ?, ?, ?, ?) RETURNING *", + @user_values + )->hash; + } + + my $main_module; + { + my $i = $pjson->{main_module}; + $main_module = $pjson->{modules}->[$i]->{filename}; + } + + my $dbcrash = $self->db->query( + "INSERT INTO crash_reports (start_time, crash_time, uuid, main_module, product_id, crash_user_id) VALUES (?, ?, ?, ?, ?, ?) RETURNING id", + $start_time, $crash_time, $uuid, $main_module, $dbproduct->{id}, $dbuser->{id} + )->hash; + + $self->db->query( + "INSERT INTO crash_report_datas (crash_report_id, processed) VALUES (?, ?) RETURNING id", + $dbcrash->{id}, j($pjson) + ); + + $tx->commit; + + return $dbcrash->{id}; +} + +sub update { + my ($self, $uuid, $pjson, $dmp_content) = @_; + + my $tx = $self->db->begin; + + my $dbcrash = $self->db->query("SELECT id FROM crash_reports WHERE uuid = ?", $uuid)->hash; + $self->db->query("UPDATE crash_report_datas SET processed = ? WHERE crash_report_id = ?", $pjson, $dbcrash->{id}); + + $tx->commit; + + return 1; +} + +1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Plugin/Storage/Sql/migrations_pg.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/CrashTest/Plugin/Storage/Sql/migrations_pg.sql Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,79 @@ +-- ########################################################################### +-- 1 up +-- ########################################################################### +CREATE TABLE "crash_users" ( + "id" serial NOT NULL, + "user_id" character varying(40) NOT NULL, + "os" character varying(40), + "cpu_arch" character varying(10), + "cpu_count" integer, + "extra_info" jsonb, + PRIMARY KEY ("id") +); +CREATE TABLE "products" ( + "id" serial NOT NULL, + "distributor" character varying(40), + "name" character varying(40), + "version" character varying(40), + "release_channel" character varying, + PRIMARY KEY ("id") +); +CREATE TABLE "crash_reports" ( + "id" serial NOT NULL, + "start_time" timestamp, + "crash_time" timestamp NOT NULL, + "uuid" uuid NOT NULL, + "main_module" character varying(40), + "crash_user_id" integer NOT NULL, + "product_id" integer NOT NULL, + CONSTRAINT "crash_reports_uuid_idx" UNIQUE ("uuid"), + PRIMARY KEY ("id") +); +CREATE INDEX "crash_reports_idx_crash_user_id" on "crash_reports" ("crash_user_id"); +CREATE INDEX "crash_reports_idx_product_id" on "crash_reports" ("product_id"); +CREATE INDEX "crash_reports_crash_time_idx" on "crash_reports" ("crash_time"); +CREATE TABLE "crash_report_datas" ( + "id" serial NOT NULL, + "processed" jsonb NOT NULL, + "crash_report_id" integer NOT NULL, + CONSTRAINT "crash_report_datas_idx_crash_report_id" UNIQUE ("crash_report_id"), + PRIMARY KEY ("id") +); +-- CREATE INDEX "crash_report_datas_idx_crash_report_id" on "crash_report_datas" ("crash_report_id"); +ALTER TABLE "crash_reports" ADD CONSTRAINT "crash_reports_fk_crash_user_id" FOREIGN KEY ("crash_user_id") + REFERENCES "crash_users" ("id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; +ALTER TABLE "crash_reports" ADD CONSTRAINT "crash_reports_fk_product_id" FOREIGN KEY ("product_id") + REFERENCES "products" ("id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; +ALTER TABLE "crash_report_datas" ADD CONSTRAINT "crash_report_datas_fk_crash_report_id" FOREIGN KEY ("crash_report_id") + REFERENCES "crash_reports" ("id") ON DELETE CASCADE DEFERRABLE; + +CREATE OR REPLACE FUNCTION extract_crashing_functions (processed_json jsonb) RETURNS text AS +$$ + -- extract threads[crashing_idx]->frames[*]->function + SELECT string_agg(functions, E'\n') + FROM ( + SELECT jsonb_array_elements( + (($1 #> ARRAY['threads', $1->'crash_info'->>'crashing_thread'])->'frames') + )->>'function' AS functions + ) AS frames +$$ +LANGUAGE sql IMMUTABLE; + +-- This extension is the contrib modules +CREATE EXTENSION IF NOT EXISTS pg_trgm; + +CREATE INDEX crash_report_datas_idx_extract_crashing_functions ON crash_report_datas USING gist ( + extract_crashing_functions(processed) gist_trgm_ops +); + +-- ########################################################################### +-- 1 down +-- ########################################################################### +DROP TABLE crash_report_datas CASCADE; +DROP TABLE crash_reports CASCADE; +DROP TABLE products CASCADE; +DROP TABLE crash_users CASCADE; +DROP FUNCTION extract_crashing_functions (processed_json jsonb); + + +-- vim:ft=pgsql: diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/StackFilter.pm --- a/lib/CrashTest/StackFilter.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -# 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 . - -package CrashTest::StackFilter; -use Mojo::Base -base; -use Mojo::Loader qw/load_class find_modules/; - -has [ qw/config app filters/ ]; - -sub new { - my $self = shift->SUPER::new(@_); - - if(defined($self->config->{StackFilters})) { - $self->load_plugins($self->config->{StackFilters}); - } else { - $self->find_stackfilters_plugins(); - } - - return $self; -} - -sub apply { - my ($self, $thread) = @_; - - foreach my $filter(@{$self->filters}) { - #say "apply filter $filter"; - $thread = $filter->apply($thread); - } - - return $thread; -} - -sub load_plugins { - my ($self, $modules) = @_; - - my @filters = (); - for my $module (@$modules) { - my $e = load_class($module); - die qq{Loading "$module" failed: $e} if ref $e; - - #say "loading $module"; - push @filters, $module->new(config => $self->config, app => $self->app); - } - - $self->filters(\@filters); -} - -sub find_stackfilters_plugins { - my ($self) = @_; - - my @modules = (); - - # Find modules in a namespace - for my $module (find_modules('CrashTest::StackFilters')) { - my $e = load_class($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; - - # instanciate - my @filters = map { $_->[0]->new(config => $self->config, app => $self->app) } @modules; - - $self->filters(\@filters); -} - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/StackFilters/FileLink.pm --- a/lib/CrashTest/StackFilters/FileLink.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -# 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 . - -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 _link_template { - my ($self, $scm, $repo) = @_; - - my $linkconf = "$scm:$repo"; - if(defined($self->{_template_cache}->{$linkconf})) { - return $self->{_template_cache}->{$linkconf}; - } - my $template = $self->config->{WebInterface}->{ScmLinks}->{$linkconf}; - - # try the generic repository type - if(!defined($template)) { - $linkconf = $scm; - if(defined($self->{_template_cache}->{$linkconf})) { - return $self->{_template_cache}->{$linkconf}; - } - $template = $self->config->{WebInterface}->{ScmLinks}->{$linkconf}; - } - - return undef if !defined($template); - - $self->app->log->debug("Building template for $linkconf"); - - my $mt = Mojo::Template->new - ->prepend('my ($repo, $scmpath, $rev, $line) = @_;') - ->auto_escape(1) - ->escape(sub { - my $str = shift; - return Mojo::ByteStream::b($str)->url_escape; - }) - ->parse($template) - ->build; - my $e = $mt->compile; - if($e) { - $self->app->log->error($e); - return undef; - } - - $self->{_template_cache}->{$linkconf} = $mt; - return $mt; -} - -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 =~ /([^:]+):([^:]+):([^:]+):(.+)/) { - my ($scm, $repo, $scmpath, $rev) = ($1, $2, $3, $4); - my $filename = File::Spec->splitpath($scmpath); - - # and we have a link template configured for this specific repository or repository type - my $template = $self->_link_template($scm, $repo); - if(defined($template)) { - # build url using the configured template - my $url = $template->interpret($repo, $scmpath, $rev, $line); - # and 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; - diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/StackFilters/FrameTrust.pm --- a/lib/CrashTest/StackFilters/FrameTrust.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -# 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 . - -package CrashTest::StackFilters::FrameTrust; -use Mojo::Base -base; - -sub priority { return 10; } - -has [ qw/app/ ]; - -sub new { - my $self = shift->SUPER::new(@_); - - # Search for inline templates in this class too - push @{$self->app->renderer->classes}, __PACKAGE__; - - # bug in Mojolicious ? - # force refresh of the template cache - $self->app->renderer->_warmup; - - return $self; -} - -sub apply { - my ($self, $thread) = @_; - - # create a pseudo controller to render templates - my $c = $self->app->controller_class->new(app => $self->app); - - $thread->each_frame(sub { - my $frame = shift; - - $self->set_trust($c, $frame); - } - ); - - return $thread; -} - -sub set_trust { - my ($self, $c, $frame) = @_; - - if(!defined($frame->module) && $frame->offset ne "0x0") { - $frame->module_name( - $c->render_to_string( - template => "stackfilters/frame_trust/warning", partial => 1, - content_text => $frame->module_name, popup_text => "No module loaded at this address" - ) - ); - } - - if($frame->missing_symbols) { - $frame->module_name( - $c->render_to_string( - template => "stackfilters/frame_trust/warning", partial => 1, - content_text => $frame->module_name, popup_text => "Missing symbols" - ) - ); - } - - if(defined($frame->trust) && $frame->trust eq "scan") { - $frame->function_name( - $c->render_to_string( - template => "stackfilters/frame_trust/warning", partial => 1, - content_text => $frame->function_name, popup_text => "Imprecise stackwalking" - ) - ); - } - -} - -1; - -__DATA__ - -@@ stackfilters/frame_trust/warning.html.ep -%= tag a => (href => "#", class => "text-danger", "data-toggle" => "tooltip", "data-original-title" => $popup_text) => begin - %= tag span => (class => "glyphicon glyphicon-warning-sign") => "" -% end -%= $content_text - diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/StackFilters/HideArgs.pm --- a/lib/CrashTest/StackFilters/HideArgs.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -# 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 . - -package CrashTest::StackFilters::HideArgs; -use Mojo::Base -base; -use Text::Balanced qw/extract_bracketed/; - -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 = ""; - my $text = $signature; - do { - my ($str, $next, $prefix) = extract_bracketed($text, '<(', '[^<(]*'); - if($str) { - $short_signature .= $prefix . substr($str, 0, 1) . substr($str, -1, 1); - $text = $next; - } else { - $short_signature .= $next; - $text = undef; - } - } while($text); - - return $self->app->t(code => (title => $signature, class => "shortened-signature prettyprint lang-cpp") => sub { return $short_signature }); -} - -1; - diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/FileSystem.pm --- a/lib/CrashTest/Storage/FileSystem.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,120 +0,0 @@ -# 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 . - -package CrashTest::Storage::FileSystem; -use Mojo::Base -base; -use Mojo::Util qw/slurp spurt/; -use Mojo::JSON qw/j decode_json/; -use DateTime; -use Data::Page; - -has [ qw/config data_path/ ]; - -sub new { - my $self = shift->SUPER::new(@_); - - $self->data_path($self->config->{DataDir}); - - return $self; -} - -sub index { - my ($self, $page, $nperpage) = @_; - - my @files; - my $dh; - opendir($dh, $self->data_path) or die $!; - my @allfiles = readdir $dh; - foreach(@allfiles) { - if($_ =~ /(.*)\.json$/) { - my $filename = File::Spec->catfile($self->data_path, $_); - if(-f $filename) { - push @files, { - file => $filename, - uuid => $1, - product => "", - version => "", - user => "", - date => DateTime->from_epoch(epoch => ((stat $filename)[9])), - }; - } - } - } - closedir $dh; - - my $by_date = sub { $b->{date} <=> $a->{date} }; - my @sorted_files = sort $by_date @files; - - my $pager = Data::Page->new(); - $pager->total_entries(scalar @files); - $pager->entries_per_page($nperpage); - $pager->current_page($page); - - if($page <= $pager->last_page) { - @sorted_files = @sorted_files[($pager->first - 1)..($pager->last - 1)]; - } else { - @sorted_files = (); - } - - foreach(@sorted_files) - { - $_->{product} = $self->get_processed_data($_->{uuid})->{client_info}->{ProductName}; - $_->{version} = $self->get_processed_data($_->{uuid})->{client_info}->{Version}; - $_->{user} = $self->get_processed_data($_->{uuid})->{client_info}->{UserID}; - } - - my $results = { - pager => $pager, - crashs => \@sorted_files, - }; - - return $results; -} - -sub get_processed_data { - my ($self, $uuid) = @_; - - my $jsonfilename = File::Spec->catfile($self->data_path, "$uuid.json"); - my $json_content = slurp($jsonfilename) or die $!; - - my $processed_data = decode_json($json_content); - - return $processed_data; -} - -sub store_processed_data { - my ($self, $uuid, $pjson) = @_; - - my $jsonfilename = File::Spec->catfile($self->data_path, "$uuid.json"); - my $dmpfilename = File::Spec->catfile($self->data_path, "$uuid.dmp"); - - spurt(j($pjson), $jsonfilename) or die $!; - - # Set time of the .dmp to the CrashTime - my $crashtime = $pjson->{client_info}->{CrashTime}; - if(defined($crashtime)) { - utime $crashtime, $crashtime, $dmpfilename; - } -} - -sub store_dump { - my ($self, $uuid, $file) = @_; - - my $dmp_file = File::Spec->catfile($self->data_path, "$uuid.dmp"); - my $fh = IO::File->new($dmp_file, "w") or die($!); - $fh->binmode; - print $fh $file; - undef $fh; -} - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql.pm --- a/lib/CrashTest/Storage/Sql.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -# 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 . - -package CrashTest::Storage::Sql; -use Mojo::Base "CrashTest::Storage::FileSystem"; -use Mojo::JSON qw/j decode_json/; -use DateTime; -use CrashTest::Storage::Sql::Schema; -use Search::Query; - -has [ qw/extra_columns/ ]; - -sub new { - my $self = shift->SUPER::new(@_); - - $self->{schema} = CrashTest::Storage::Sql::Schema->connect($self->config->{DSN}); - - return $self; -} - -sub index { - my ($self, $page, $nperpage, $search_str) = @_; - - my $dbic_query = {}; - - if(defined($search_str) && $search_str ne "") { - my $search_fields = { - user_id => { alias_for => 'crash_user.user_id' }, - product => { alias_for => 'product.name' }, - version => { alias_for => 'product.version' }, - channel => { alias_for => 'product.release_channel' }, - function => { alias_for => 'extract_crashing_functions(processed)' }, - }; - my $query = Search::Query->parser( - dialect => 'DBIxClass', - fields => $search_fields, - default_field => [ 'extract_crashing_functions(processed)' ], - )->parse($search_str); - - if(defined($query)) { - $dbic_query = $query->as_dbic_query; - } - } - - my @select = (); - my @sel_as = (); - my @extra_ids = (); - - if($self->extra_columns) { - foreach my $extra_col(@{$self->extra_columns->{Index}}) { - push @select, $extra_col->{db_column}; - push @sel_as, $extra_col->{id}; - push @extra_ids, $extra_col->{id}; - } - } - - my $dbcrashs = $self->{schema}->resultset('CrashReport')->search_rs( - $dbic_query, - { - prefetch => 'product', - join => [ qw/crash_user crash_report_data/ ], - '+select' => \@select, - '+as' => \@sel_as, - order_by => { -desc => 'crash_time' }, - page => $page, - rows => $nperpage, - }, - ); - - my $results = { - pager => $dbcrashs->pager, - crashs => [], - }; - - for my $crash($dbcrashs->all) { - my $filename = File::Spec->catfile($self->{data_path}, $crash->uuid); - - my $result = { - file => $filename, - uuid => $crash->uuid, - product => $crash->product->name, - version => $crash->product->version, - date => $crash->crash_time, - }; - - foreach (@extra_ids) { - $result->{$_} = $crash->get_column($_); - } - - push @{$results->{crashs}}, $result; - } - - return $results; -} - -sub _db_insert_processed_data { - my ($self, $uuid, $pjson) = @_; - - $self->{schema}->txn_do(sub { - - my $crash = $self->{schema}->resultset('CrashReport')->new({ uuid => $uuid }); - - if($pjson->{client_info}->{StartupTime}) { - $crash->start_time(DateTime->from_epoch(epoch => $pjson->{client_info}->{StartupTime})); - } - if($pjson->{client_info}->{CrashTime}) { - $crash->crash_time(DateTime->from_epoch(epoch => $pjson->{client_info}->{CrashTime})); - } - - my $product = { - distributor => $pjson->{client_info}->{Distributor}, - name => $pjson->{client_info}->{ProductName}, - version => $pjson->{client_info}->{Version}, - }; - - my $dbproduct = $self->{schema}->resultset('Product')->search($product)->first(); - if($dbproduct) { - $crash->product($dbproduct); - } else { - $product->{release_channel} = $pjson->{client_info}->{ReleaseChannel}; - $crash->product($self->{schema}->resultset('Product')->new($product)); - } - - my $user = { - user_id => $pjson->{client_info}->{UserID}, - }; - - my $dbuser = $self->{schema}->resultset('CrashUser')->search($user)->first(); - if($dbuser) { - $crash->crash_user($dbuser); - } else { - $user->{cpu_arch} = $pjson->{system_info}->{cpu_arch}; - $user->{cpu_count} = $pjson->{system_info}->{cpu_count}; - $user->{os} = $pjson->{system_info}->{os}; - $user->{extra_info} = j($pjson->{client_info}); - $crash->crash_user($self->{schema}->resultset('CrashUser')->new($user)); - } - - my $txt_json = j($pjson); - $crash->create_related('crash_report_data', { - processed => $txt_json, - }); - - $crash->insert; - }); -} - -sub store_processed_data { - my ($self, $uuid, $pjson) = @_; - - $self->_db_insert_processed_data($uuid, $pjson); -} - -sub get_processed_data { - my ($self, $uuid) = @_; - - my $crash = $self->{schema}->resultset('CrashReport')->search( - { uuid => $uuid }, - { prefetch => 'crash_report_data' } - )->first(); - - return decode_json($crash->crash_report_data->processed); -} - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql/Schema.pm --- a/lib/CrashTest/Storage/Sql/Schema.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# 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 . - -use utf8; -package CrashTest::Storage::Sql::Schema; -use base qw/DBIx::Class::Schema/; - -our $VERSION = 2; - -__PACKAGE__->load_namespaces(); - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql/Schema/Candy.pm --- a/lib/CrashTest/Storage/Sql/Schema/Candy.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# 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 . - -package CrashTest::Storage::Sql::Schema::Candy; - -use Mojo::Base -strict; -use base 'DBIx::Class::Candy'; - -sub perl_version { 12 } -sub autotable { 1 } - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql/Schema/Result/CrashReport.pm --- a/lib/CrashTest/Storage/Sql/Schema/Result/CrashReport.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -# 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 . - -package CrashTest::Storage::Sql::Schema::Result::CrashReport; - -use CrashTest::Storage::Sql::Schema::Candy; -__PACKAGE__->load_components(qw/InflateColumn::DateTime Core/); - -primary_column id => { - data_type => 'int', - is_auto_increment => 1, -}; - -column start_time => { - data_type => 'timestamp', - inflate_datetime => 1, - is_nullable => 1, -}; - -column crash_time => { - data_type => 'timestamp', - inflate_datetime => 1, - is_nullable => 1, -}; - -column uuid => { - data_type => 'uuid', -}; - -column bug_reference => { - data_type => 'varchar', - size => 20, - is_nullable => 1, -}; - -has_one crash_report_data => 'CrashTest::Storage::Sql::Schema::Result::CrashReportData', 'crash_report_id'; - -column crash_user_id => { data_type => 'int' }; -belongs_to crash_user => 'CrashTest::Storage::Sql::Schema::Result::CrashUser', 'crash_user_id'; - -column product_id => { data_type => 'int' }; -belongs_to product => 'CrashTest::Storage::Sql::Schema::Result::Product', 'product_id'; - -sub sqlt_deploy_hook { - my ($self, $sqlt_table) = @_; - $sqlt_table->add_index( - name => 'crash_reports_uuid_idx', - fields => [ 'uuid' ], - type => 'unique', - ); - $sqlt_table->add_index( - name => 'crash_reports_crash_time_idx', - fields => [ 'crash_time' ] - ); -} - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql/Schema/Result/CrashReportData.pm --- a/lib/CrashTest/Storage/Sql/Schema/Result/CrashReportData.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# 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 . - -package CrashTest::Storage::Sql::Schema::Result::CrashReportData; - -use CrashTest::Storage::Sql::Schema::Candy; - -primary_column id => { - data_type => 'int', - is_auto_increment => 1, -}; - -column processed => { - data_type => 'jsonb', -}; - -column crash_report_id => { data_type => 'int' }; -belongs_to crash_report => 'CrashTest::Storage::Sql::Schema::Result::CrashReport', 'crash_report_id'; - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql/Schema/Result/CrashUser.pm --- a/lib/CrashTest/Storage/Sql/Schema/Result/CrashUser.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -# 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 . - -package CrashTest::Storage::Sql::Schema::Result::CrashUser; - -use CrashTest::Storage::Sql::Schema::Candy; - -primary_column id => { - data_type => 'int', - is_auto_increment => 1, -}; - -column user_id => { - data_type => 'varchar', - size => 40, -}; - -column os => { - data_type => 'varchar', - size => 40, - is_nullable => 1, -}; - -column cpu_arch => { - data_type => 'varchar', - size => 10, - is_nullable => 1, -}; - -column cpu_count => { - data_type => 'int', - is_nullable => 1, -}; - -column extra_info => { - data_type => 'text', - is_nullable => 1, -}; - -has_many crash_report => 'CrashTest::Storage::Sql::Schema::Result::CrashReport', 'crash_user_id'; - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql/Schema/Result/Module.pm --- a/lib/CrashTest/Storage/Sql/Schema/Result/Module.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -# 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 . - -package CrashTest::Storage::Sql::Schema::Result::Module; - -use CrashTest::Storage::Sql::Schema::Candy; - -primary_column id => { - data_type => 'int', - is_auto_increment => 1, -}; - -column debug_id => { - data_type => 'varchar', - size => 33, -}; - -column filename => { - data_type => 'varchar', - size => 128, -}; - -column version => { - data_type => 'varchar', - size => 64, - is_nullable => 1, -}; - -unique_constraint module_id => ['debug_id', 'filename']; - -1; diff -r e408da1419cd -r 0ebef32c34af lib/CrashTest/Storage/Sql/Schema/Result/Product.pm --- a/lib/CrashTest/Storage/Sql/Schema/Result/Product.pm Sun Sep 27 22:45:40 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -# 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 . - -package CrashTest::Storage::Sql::Schema::Result::Product; - -use CrashTest::Storage::Sql::Schema::Candy; - -primary_column id => { - data_type => 'int', - is_auto_increment => 1, -}; - -column distributor => { - data_type => 'varchar', - size => 40, - is_nullable => 1, -}; - -column name => { - data_type => 'varchar', - size => 40, - is_nullable => 1, -}; - -column version => { - data_type => 'varchar', - size => 40, - is_nullable => 1, -}; - -column release_channel => { - data_type => 'varchar', - is_nullable => 16, -}; - -has_many crash_report => 'CrashTest::Storage::Sql::Schema::Result::CrashReport', 'product_id'; - -1; diff -r e408da1419cd -r 0ebef32c34af script/CrashTest --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/script/CrashTest Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,25 @@ +#!/usr/bin/env perl + +# 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 . + +# ABSTRACT: Web interface for breakpad + +use strict; +use warnings; + +use lib 'lib'; + +# Start command line interface for application +require Mojolicious::Commands; +Mojolicious::Commands->start_app('CrashTest'); diff -r e408da1419cd -r 0ebef32c34af templates/index.atom.ep --- a/templates/index.atom.ep Sun Sep 27 22:45:40 2015 +0200 +++ b/templates/index.atom.ep Wed Nov 04 17:43:00 2015 +0100 @@ -4,22 +4,22 @@ Recent Crashs - <%= @$files[0]->{date}->strftime("%FT%TZ") =%> + <%= date_with_tz_from_db_utc(@$crashs[0]->{crash_time}) =%> -% foreach my $file(@$files) { +% foreach my $crash(@$crashs) { - tag:crash,2014:uuid::<%= $file->{uuid} =%> - - <%= $file->{product} =%>: <%= $file->{signature} =%> - <%= $file->{date}->strftime("%FT%TZ") =%> - <%= $file->{date}->strftime("%FT%TZ") =%> + tag:crash,2014:uuid::<%= $crash->{uuid} =%> + + <%= $crash->{p_name} =%> <%= $crash->{p_version} =%>: <%= $crash->{uuid} =%> + <%= date_with_tz_from_db_utc($crash->{crash_time}) =%> + <%= date_with_tz_from_db_utc($crash->{crash_time}) =%> %= xml_escape_block begin - %= t h3 => $file->{date}->strftime("%F %T") - %= t h3 => $file->{product} . " version " . $file->{version} + %= t h3 => date_from_db_utc($crash->{crash_time}) + %= t h3 => $crash->{p_name} . " version " . $crash->{p_version} % end diff -r e408da1419cd -r 0ebef32c34af templates/index.html.ep --- a/templates/index.html.ep Sun Sep 27 22:45:40 2015 +0200 +++ b/templates/index.html.ep Wed Nov 04 17:43:00 2015 +0100 @@ -7,25 +7,26 @@ Version UUID % foreach my $extra_col(@$extra_columns) { - %= t th => $extra_col->{name} + %= t th => $extra_col->{name} % } Date -% foreach my $crash(@$files) { +% foreach my $crash(@$crashs) { %= t tr => begin - %= t td => $crash->{product} - %= t td => $crash->{version} + %= t td => $crash->{p_name} + %= t td => ($crash->{p_version} or "") %= t td => (style => "font-family:monospace;") => begin %= link_to $crash->{uuid} => url_for('report', uuid => $crash->{uuid}) % end % foreach my $extra_col(@$extra_columns) { - %= t td => $crash->{$extra_col->{id}} + %= t td => $crash->{$extra_col->{id}} % } - %= t td => $crash->{date}->set_time_zone('UTC')->set_time_zone('local')->strftime("%F %T") + %= t td => date_from_db_utc($crash->{crash_time}) % end % } % end + % if($pager->first_page != $pager->last_page) { %= bootstrap_pagination($pager->current_page, $pager->last_page); % } diff -r e408da1419cd -r 0ebef32c34af templates/report/backtrace.html.ep --- a/templates/report/backtrace.html.ep Sun Sep 27 22:45:40 2015 +0200 +++ b/templates/report/backtrace.html.ep Wed Nov 04 17:43:00 2015 +0100 @@ -15,4 +15,3 @@ % end % } - diff -r e408da1419cd -r 0ebef32c34af templates/report/backtrace/frames.html.ep --- a/templates/report/backtrace/frames.html.ep Sun Sep 27 22:45:40 2015 +0200 +++ b/templates/report/backtrace/frames.html.ep Wed Nov 04 17:43:00 2015 +0100 @@ -13,9 +13,11 @@ %= $frame->frame_number % end %= t td => (class => 'col-md-2') => begin + %= module_warnings($frame) %= $frame->module_name % end %= t td => (class => 'col-md-5') => begin + %= frame_warnings($frame) %= $frame->function_name % end %= t td => (class => 'col-md-4') => begin diff -r e408da1419cd -r 0ebef32c34af templates/report/backtrace/warning.html.ep --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/report/backtrace/warning.html.ep Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,3 @@ +%= tag a => (href => "#", class => "text-danger", "data-toggle" => "tooltip", "data-original-title" => $tooltip_text) => begin + %= tag span => (class => "glyphicon glyphicon-warning-sign") => "" +% end diff -r e408da1419cd -r 0ebef32c34af templates/report/client_info.html.ep --- a/templates/report/client_info.html.ep Sun Sep 27 22:45:40 2015 +0200 +++ b/templates/report/client_info.html.ep Wed Nov 04 17:43:00 2015 +0100 @@ -1,3 +1,11 @@ + + %= t td => 'Start Time' + %= t td => $client_info->{StartupTime} + + + %= t td => 'Crash Time' + %= t td => $client_info->{CrashTime} + %= t td => 'Product' %= t td => $client_info->{ProductName} @@ -16,9 +24,13 @@ %= t td => 'Release Channel' - %= t td => $client_info->{ReleaseChannel} + %= t td => $client_info->{ReleaseChannel} || "" %= t td => 'UUID' %= t td => $self->param('uuid'); + + %= t td => 'User ID' + %= t td => $client_info->{UserID}; + diff -r e408da1419cd -r 0ebef32c34af templates/report/crash.html.ep --- a/templates/report/crash.html.ep Sun Sep 27 22:45:40 2015 +0200 +++ b/templates/report/crash.html.ep Wed Nov 04 17:43:00 2015 +0100 @@ -20,6 +20,7 @@ % end %= t div => (class => 'tab-pane', id => 'details') => begin %= t table => (class => 'table table-striped table-hover table-bordered table-condensed') => begin + %= include('report/crash_info', crash_info => $processed_data->{crash_info}); %= include('report/client_info', client_info => $processed_data->{client_info}); %= include('report/system_info', system_info => $processed_data->{system_info}); % end diff -r e408da1419cd -r 0ebef32c34af templates/report/crash_info.html.ep --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/report/crash_info.html.ep Wed Nov 04 17:43:00 2015 +0100 @@ -0,0 +1,4 @@ + + %= t td => 'Crash Type' + %= t td => $crash_info->{type} +