From ed32a1c27b153e87ddddb3663fe0f4c8b68a75d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kv=C3=A1=C4=8Dek?= Date: Sat, 23 Sep 2017 00:46:53 +0200 Subject: [PATCH] INIT: initial commit --- Config/config.php | 12 ++ ..._02_22_014824_create_queries_log_table.php | 33 ++++++ ...017_02_22_021253_create_http_log_table.php | 35 ++++++ ...02_114429_update_http_log_add_load_avg.php | 35 ++++++ ...115241_update_query_log_add_request_id.php | 43 +++++++ Database/Seeders/.gitkeep | 0 Database/Seeders/ProfilerDatabaseSeeder.php | 20 ++++ Entities/HttpLog.php | 22 ++++ Entities/QueryLog.php | 23 ++++ Handlers/ProfileHandler.php | 25 ++++ Http/Controllers/ProfilerController.php | 67 +++++++++++ Http/Controllers/QueryController.php | 13 ++ Http/Controllers/RequestController.php | 37 ++++++ Http/Middleware/.gitkeep | 0 Http/Requests/.gitkeep | 0 Http/routes.php | 23 ++++ Middleware/Profiler.php | 64 ++++++++++ Providers/ProfilerEventServiceProvider.php | 8 ++ Providers/ProfilerServiceProvider.php | 49 ++++++++ Repositories/.gitkeep | 0 Resources/lang/cs/module.php | 44 +++++++ .../_partials/groupedRequestList.blade.php | 31 +++++ .../views/_partials/requestList.blade.php | 60 ++++++++++ Resources/views/index.blade.php | 44 +++++++ Resources/views/queries/index.blade.php | 41 +++++++ Resources/views/request/detail.blade.php | 111 ++++++++++++++++++ Resources/views/settings/profiler.blade.php | 65 ++++++++++ composer.json | 15 +++ start.php | 15 +++ 29 files changed, 935 insertions(+) create mode 100644 Config/config.php create mode 100644 Database/Migrations/2017_02_22_014824_create_queries_log_table.php create mode 100644 Database/Migrations/2017_02_22_021253_create_http_log_table.php create mode 100644 Database/Migrations/2017_03_02_114429_update_http_log_add_load_avg.php create mode 100644 Database/Migrations/2017_03_02_115241_update_query_log_add_request_id.php create mode 100644 Database/Seeders/.gitkeep create mode 100644 Database/Seeders/ProfilerDatabaseSeeder.php create mode 100644 Entities/HttpLog.php create mode 100644 Entities/QueryLog.php create mode 100644 Handlers/ProfileHandler.php create mode 100644 Http/Controllers/ProfilerController.php create mode 100644 Http/Controllers/QueryController.php create mode 100644 Http/Controllers/RequestController.php create mode 100644 Http/Middleware/.gitkeep create mode 100644 Http/Requests/.gitkeep create mode 100644 Http/routes.php create mode 100644 Middleware/Profiler.php create mode 100644 Providers/ProfilerEventServiceProvider.php create mode 100644 Providers/ProfilerServiceProvider.php create mode 100644 Repositories/.gitkeep create mode 100644 Resources/lang/cs/module.php create mode 100644 Resources/views/_partials/groupedRequestList.blade.php create mode 100644 Resources/views/_partials/requestList.blade.php create mode 100644 Resources/views/index.blade.php create mode 100644 Resources/views/queries/index.blade.php create mode 100644 Resources/views/request/detail.blade.php create mode 100644 Resources/views/settings/profiler.blade.php create mode 100644 composer.json create mode 100644 start.php diff --git a/Config/config.php b/Config/config.php new file mode 100644 index 0000000..6d01f49 --- /dev/null +++ b/Config/config.php @@ -0,0 +1,12 @@ + 'Profiler', + 'translatedName' => 'profiler::module.name', + 'translatedDescription' => 'profiler::module.description', + 'alias' => 'profiler', + 'handler' => \Modules\Profiler\Handlers\ProfileHandler::class, + 'providers' => [\Modules\Profiler\Providers\ProfilerServiceProvider::class], + 'files' => ['start.php'], + 'order' => -100, +]; \ No newline at end of file diff --git a/Database/Migrations/2017_02_22_014824_create_queries_log_table.php b/Database/Migrations/2017_02_22_014824_create_queries_log_table.php new file mode 100644 index 0000000..754e186 --- /dev/null +++ b/Database/Migrations/2017_02_22_014824_create_queries_log_table.php @@ -0,0 +1,33 @@ +increments('id'); + $table->timestamps(); + $table->text('query'); + $table->text('bindings'); + $table->double('time'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('queries_log'); + } +} diff --git a/Database/Migrations/2017_02_22_021253_create_http_log_table.php b/Database/Migrations/2017_02_22_021253_create_http_log_table.php new file mode 100644 index 0000000..77b44cc --- /dev/null +++ b/Database/Migrations/2017_02_22_021253_create_http_log_table.php @@ -0,0 +1,35 @@ +increments('id'); + $table->timestamps(); + $table->string('method'); + $table->text('url'); + $table->double('time'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('http_log'); + } + +} diff --git a/Database/Migrations/2017_03_02_114429_update_http_log_add_load_avg.php b/Database/Migrations/2017_03_02_114429_update_http_log_add_load_avg.php new file mode 100644 index 0000000..086f727 --- /dev/null +++ b/Database/Migrations/2017_03_02_114429_update_http_log_add_load_avg.php @@ -0,0 +1,35 @@ +double('load_1')->nullable(); + $table->double('load_5')->nullable(); + $table->double('load_15')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('http_log', function (Blueprint $table) { + $table->dropColumn('load_1'); + $table->dropColumn('load_5'); + $table->dropColumn('load_15'); + }); + } +} diff --git a/Database/Migrations/2017_03_02_115241_update_query_log_add_request_id.php b/Database/Migrations/2017_03_02_115241_update_query_log_add_request_id.php new file mode 100644 index 0000000..b7ef557 --- /dev/null +++ b/Database/Migrations/2017_03_02_115241_update_query_log_add_request_id.php @@ -0,0 +1,43 @@ +integer('http_log_id') + ->nullable() + ->unsigned() + ->after('id') + ->comment('FK to http_log'); + + $table->foreign('http_log_id') + ->references('id') + ->on('http_log') + ->onDelete('cascade') + ->onUpdate('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('queries_log', function (Blueprint $table) { + + // $table->dropForeign(''); + $table->dropColumn('http_log_id'); + }); + } +} diff --git a/Database/Seeders/.gitkeep b/Database/Seeders/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Database/Seeders/ProfilerDatabaseSeeder.php b/Database/Seeders/ProfilerDatabaseSeeder.php new file mode 100644 index 0000000..b618393 --- /dev/null +++ b/Database/Seeders/ProfilerDatabaseSeeder.php @@ -0,0 +1,20 @@ +call("OthersTableSeeder"); + } + +} \ No newline at end of file diff --git a/Entities/HttpLog.php b/Entities/HttpLog.php new file mode 100644 index 0000000..cb6f360 --- /dev/null +++ b/Entities/HttpLog.php @@ -0,0 +1,22 @@ +hasMany(QueryLog::class); + } +} \ No newline at end of file diff --git a/Entities/QueryLog.php b/Entities/QueryLog.php new file mode 100644 index 0000000..e458bcd --- /dev/null +++ b/Entities/QueryLog.php @@ -0,0 +1,23 @@ + 'array' + ]; + + public function httpLog() + { + return $this->belongsTo(HttpLog::class); + } +} \ No newline at end of file diff --git a/Handlers/ProfileHandler.php b/Handlers/ProfileHandler.php new file mode 100644 index 0000000..0fec0ab --- /dev/null +++ b/Handlers/ProfileHandler.php @@ -0,0 +1,25 @@ +whereTitle(trans('admin/mainMenu.system')); + $submenu->route('profiler.index', trans('profiler::module.name'), [], 10, ['icon' => 'fa fa-dashboard']); + } +} \ No newline at end of file diff --git a/Http/Controllers/ProfilerController.php b/Http/Controllers/ProfilerController.php new file mode 100644 index 0000000..8aca7be --- /dev/null +++ b/Http/Controllers/ProfilerController.php @@ -0,0 +1,67 @@ += 0) { + $httpLog = $httpLog->where('created_at', '>=', Carbon::today() + ->subDays($pastDays)); + } + + session(['profilerPastDays' => $pastDays]); + + if (Input::get('method')) { + $filters[] = 'method'; + $httpLog = $httpLog->where('method', Input::get('method')); + } + + $displayGrouped = false; + + if (Input::get('url')) { + $httpLog = $httpLog->where('url', Input::get('url')) + ->orderBy('created_at', 'DESC') + ->with('queries'); + } else { + $displayGrouped = true; + $httpLog = $httpLog->groupBy('url', 'method') + ->select(\DB::raw('*'), \DB::raw('COUNT(url) as total'), \DB::raw('AVG(time) as time_avg')) + ->orderBy('total', 'DESC'); + } + + $httpLog = $httpLog->paginate(25); + + $filter = Input::all(); + return view('profiler::index', compact('httpLog', 'displayGrouped', 'filter', 'pastDays')); + } + + public function destroy() + { + $older = Input::get('olderThan'); + + if (is_null($older)) { + \DB::statement("SET foreign_key_checks=0"); + QueryLog::truncate(); + HttpLog::truncate(); + \DB::statement("SET foreign_key_checks=1"); + } else { + HttpLog::where('created_at', '<=', Carbon::now() + ->subDays($older)) + ->delete(); + } + + return redirect() + ->back() + ->with('success', trans('profiler::module.succesfullyDeleted')); + } +} \ No newline at end of file diff --git a/Http/Controllers/QueryController.php b/Http/Controllers/QueryController.php new file mode 100644 index 0000000..b19af8f --- /dev/null +++ b/Http/Controllers/QueryController.php @@ -0,0 +1,13 @@ +queries; + + return view('profiler::queries.index', compact('queries')); + } +} \ No newline at end of file diff --git a/Http/Controllers/RequestController.php b/Http/Controllers/RequestController.php new file mode 100644 index 0000000..56c734d --- /dev/null +++ b/Http/Controllers/RequestController.php @@ -0,0 +1,37 @@ +url) + ->where('method', $httpLog->method); + + $pastDays = Input::get('pastDays', session('profilerPastDays', 0)); + if (is_numeric($pastDays) && $pastDays >= 0) { + $sameRequests = $sameRequests->where('created_at', '>=', Carbon::today() + ->subDays($pastDays)); + } + + session(['profilerPastDays' => $pastDays]); + + $sameRequests = $sameRequests->get(); + + $times = [ + 'avg' => $sameRequests->avg('time'), + 'min' => $sameRequests->min('time'), + 'max' => $sameRequests->max('time') + ]; + + $filter = Input::all(); + + return view('profiler::request.detail', compact('httpLog', 'sameRequests', 'times', 'pastDays', 'filter')); + } +} \ No newline at end of file diff --git a/Http/Middleware/.gitkeep b/Http/Middleware/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Http/Requests/.gitkeep b/Http/Requests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Http/routes.php b/Http/routes.php new file mode 100644 index 0000000..b64505b --- /dev/null +++ b/Http/routes.php @@ -0,0 +1,23 @@ + LaravelLocalization::setLocale(), + 'namespace' => 'Modules\Profiler\Http\Controllers' + ], function () { + Route::group([ + 'middleware' => ['admin'], + 'prefix' => 'admin/profiler' + ], function () { + + Route::get('/', 'ProfilerController@index') + ->name('profiler.index'); + + Route::get('{httpLog}', 'RequestController@detail') + ->name('request.detail'); + + Route::get('{httpLog}/queries', 'QueryController@index') + ->name('queries.index'); + + Route::get('delete/all', 'ProfilerController@destroy') + ->name('profiler.deleteAll'); + }); +}); \ No newline at end of file diff --git a/Middleware/Profiler.php b/Middleware/Profiler.php new file mode 100644 index 0000000..0126802 --- /dev/null +++ b/Middleware/Profiler.php @@ -0,0 +1,64 @@ +queries = \DB::getQueryLog(); + + $requestId = $this->logRequest($request); + $this->logQueries($requestId); + } + + /** + * + * + * @param Request $request + * + * @return mixed + */ + protected function logRequest(Request $request) + { + $url = $request->fullUrl(); + $method = $request->getMethod(); + $time = (microtime(true) - LARAVEL_START) * 1000; + + $data = ['url' => $url, 'method' => $method, 'time' => $time]; + + if (function_exists('sys_getloadavg')) { + $load = sys_getloadavg(); + $data['load_1'] = $load[0]; + $data['load_5'] = $load[1]; + $data['load_15'] = $load[2]; + } + + $http = HttpLog::create($data); + + return $http->id; + } + + protected function logQueries($httpLogId = null) + { + foreach ($this->queries as $query) { + QueryLog::create([ + 'query' => $query['query'], + 'bindings' => $query['bindings'], + 'time' => $query['time'], + 'http_log_id' => $httpLogId + ]); + } + } +} \ No newline at end of file diff --git a/Providers/ProfilerEventServiceProvider.php b/Providers/ProfilerEventServiceProvider.php new file mode 100644 index 0000000..707b75c --- /dev/null +++ b/Providers/ProfilerEventServiceProvider.php @@ -0,0 +1,8 @@ +alias = 'profiler'; + } + + /** + * Boot the application events. + * + * @return void + */ + public function boot() + { + parent::boot(); + + $this->app->register(ProfilerEventServiceProvider::class); + } + + public function register() + { + parent::register(); + + // register settings form + $registrator = $this->app->make('registerSettingsExtension'); + $registrator->register('profiler', 'profiler::module.name', 'profiler::settings.profiler'); + + // turn on profiler for visitor area (middleware group "web") + if (setting('useProfilerInVisitorArea', true)) { + $this->app['router']->prependMiddlewareToGroup('web', Profiler::class); + } + + // use profiler also in admin? + if (setting('useProfilerInAdmin', true)) { + $this->app['router']->prependMiddlewareToGroup('admin', Profiler::class); + } + } + + protected function setModulePath() + { + $this->path = __DIR__; + } +} \ No newline at end of file diff --git a/Repositories/.gitkeep b/Repositories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Resources/lang/cs/module.php b/Resources/lang/cs/module.php new file mode 100644 index 0000000..c3b3453 --- /dev/null +++ b/Resources/lang/cs/module.php @@ -0,0 +1,44 @@ + 'Profiler', + 'index' => [ + 'createdAt' => 'Datum', + 'httpMethod' => 'HTTP metoda', + 'url' => 'URL', + 'time' => 'Čas', + 'timeAvg' => 'Průměrný čas', + 'timeMin' => 'Minimální čas', + 'timeMax' => 'Maximální čas', + 'systemLoad' => 'Load serveru', + 'calledTotal' => 'Volání', + 'pastDays' => [ + 'today' => 'Dnes', + 'overall' => 'Vše', + 'days' => '1 den|:count dny|:count dní' + ] + ], + 'settings' => [ + 'useProfilerInVisitorArea' => 'Zapnout měření ve veřejné části?', + 'useProfilerInAdmin' => 'Zapnout měření v administraci?' + ], + 'request' => [ + 'info' => 'Detaily requestu', + 'sql' => 'SQL dotazy', + 'detail' => 'Detail požadavku', + 'sameRequests' => 'Ostatní volání' + ], + 'graphs' => [ + 'responseTime' => 'Doba odezvy' + ], + 'queries' => [ + 'sql' => 'SQL dotaz', + 'time' => 'Čas dotazu' + ], + 'listDescription' => 'Seznam HTTP požadavků na tuto aplikaci.', + 'groupedListDescription' => 'Seznam HTTP požadavků na tuto aplikaci.', + 'requestDetail' => 'Detail HTTP požadavku', + 'deleteAllData' => 'Smazat všechna data', + 'deleteOld' => 'Smazat data starší než 30 dní', + 'succesfullyDeleted' => 'Úspěšně vymazána data z profileru.', + +]; \ No newline at end of file diff --git a/Resources/views/_partials/groupedRequestList.blade.php b/Resources/views/_partials/groupedRequestList.blade.php new file mode 100644 index 0000000..90cadf3 --- /dev/null +++ b/Resources/views/_partials/groupedRequestList.blade.php @@ -0,0 +1,31 @@ +@if(count($requests) > 0) + + + + + + + + + + + + @foreach($requests as $request) + + + + + + + + @endforeach + +
@lang('profiler::module.index.calledTotal')@lang('profiler::module.index.httpMethod')@lang('profiler::module.index.url')@lang('profiler::module.index.timeAvg')
{{$request->total}}{{$request->method}}{{$request->url}}{{number_format($request->time_avg, 2)}} ms + + + +
+@else + @lang('admin/common.empty') +@endif \ No newline at end of file diff --git a/Resources/views/_partials/requestList.blade.php b/Resources/views/_partials/requestList.blade.php new file mode 100644 index 0000000..f578b00 --- /dev/null +++ b/Resources/views/_partials/requestList.blade.php @@ -0,0 +1,60 @@ +@if(count($requests) > 0) + + + + + + + + + + + + + + + @foreach($requests as $request) + + + + + + + + + @endforeach + +
@lang('profiler::module.index.createdAt')@lang('profiler::module.index.httpMethod')@lang('profiler::module.index.url')@lang('profiler::module.index.time')@lang('profiler::module.index.systemLoad')
{{$request->created_at->format('d.m.Y G:i')}}{{$request->method}}{{$request->url}}{{number_format($request->time, 3)}} ms{{$request->load_1}}, {{$request->load_5}}, {{$request->load_15}} + +
+ + + + + + + + +
+ +
+
+ + +
+ +@else + @lang('admin/common.empty') +@endif \ No newline at end of file diff --git a/Resources/views/index.blade.php b/Resources/views/index.blade.php new file mode 100644 index 0000000..f59f90a --- /dev/null +++ b/Resources/views/index.blade.php @@ -0,0 +1,44 @@ +@extends('admin.layoutWithNavigation') + +@section('pageHeading', trans('profiler::module.name')) + +@section('headerButtons') + @if($displayGrouped == false) + + + + @endif + +
+ @foreach(['0' => 'today', '7' => 'days', '30' => 'days', 'inf' => 'overall'] as $pastDay => $name) + @php $filter['pastDays'] = $pastDay; @endphp + + {{trans_choice('profiler::module.index.pastDays.'.$name, $pastDay)}} + @endforeach +
+ +@endsection + +@if($displayGrouped == false) + @section('pageDescription', trans('profiler::module.listDescription')) +@else + @section('pageDescription', trans('profiler::module.groupedListDescription')) +@endif + +@section('content') + +
+
+ + @if ($displayGrouped == true) + @include('profiler::_partials.groupedRequestList', ['requests' => $httpLog]) + @else + @include('profiler::_partials.requestList', ['requests' => $httpLog]) + @endif +
+ +
+@endsection \ No newline at end of file diff --git a/Resources/views/queries/index.blade.php b/Resources/views/queries/index.blade.php new file mode 100644 index 0000000..2da5e5e --- /dev/null +++ b/Resources/views/queries/index.blade.php @@ -0,0 +1,41 @@ +@extends('admin.layoutWithNavigation') + +@section('pageHeading', trans('profiler::module.name')) + +@section('content') + +
+
+ + + + + + + + + + + + @foreach($queries as $query) + + + + + @endforeach + +
@lang('profiler::module.queries.sql')@lang('profiler::module.queries.time')
+ @php $sql = $query->query; @endphp + @foreach($query->bindings as $value) + @php + if (is_array($value) && isset($value['date'])) + {$value = \Carbon\Carbon::parse($value['date'])->toDateTimeString();} + $sql = preg_replace('/\?/', $value, $sql, 1); + @endphp + @endforeach + + {{$sql}} + {{number_format($query->time, 3)}} ms
+
+
+@endsection \ No newline at end of file diff --git a/Resources/views/request/detail.blade.php b/Resources/views/request/detail.blade.php new file mode 100644 index 0000000..850194b --- /dev/null +++ b/Resources/views/request/detail.blade.php @@ -0,0 +1,111 @@ +@extends('admin.layoutWithNavigation') + +@section('pageHeading', trans('profiler::module.requestDetail')) + +@section('headerButtons') + +
+ @foreach(['0' => 'today', '7' => 'days', '30' => 'days', 'inf' => 'overall'] as $pastDay => $name) + @php $filter['httpLog'] = $httpLog ;$filter['pastDays'] = $pastDay; @endphp + + {{trans_choice('profiler::module.index.pastDays.'.$name, $pastDay)}} + @endforeach +
+ +@endsection + + +@section('content') + +
+
+
+
+
+

@lang('profiler::module.request.detail')

+
+
+ +
+
+
    +
  • @lang('profiler::module.index.httpMethod'): {{$httpLog->method}}
  • +
  • @lang('profiler::module.index.url'): {{$httpLog->url}} +
  • +
  • @lang('profiler::module.index.time'): {{number_format($httpLog->time, 2)}} ms
  • +
  • @lang('profiler::module.index.systemLoad'): {{$httpLog->load_1}}, {{$httpLog->load_5}} + , {{$httpLog->load_15}}
  • +
+
+ +
+
+ {{count($sameRequests)}} ×
@lang('profiler::module.index.calledTotal')
+
{{number_format($times['min'], 2)}} + ms
@lang('profiler::module.index.timeMin')
+
{{number_format($times['max'], 2)}} + ms
@lang('profiler::module.index.timeMax')
+
{{number_format($times['avg'], 2)}} + ms
@lang('profiler::module.index.timeAvg')
+
+
+
+
+ +
+
+
+
+

@lang('profiler::module.graphs.responseTime')

+
+
+
+ +
+
+
+
+ +
+
+
+

@lang('profiler::module.request.sameRequests')

+
+
+ +
+ @include('profiler::_partials.requestList', ['requests' => $sameRequests]) +
+
+ + +@endsection + +@section('javascripts') + +@endsection \ No newline at end of file diff --git a/Resources/views/settings/profiler.blade.php b/Resources/views/settings/profiler.blade.php new file mode 100644 index 0000000..8cf845e --- /dev/null +++ b/Resources/views/settings/profiler.blade.php @@ -0,0 +1,65 @@ +
+
+ + @php $profileVisitorArea = old('useProfilerInVisitorArea', isset($settings->useProfilerInVisitorArea) ? $settings->useProfilerInVisitorArea['val'] : null); @endphp + + @lang('profiler::module.settings.useProfilerInVisitorArea') + + + + +
+
+ @include('admin.settings.snippets.commonSettings', ['field' => 'useProfilerInVisitorArea']) +
+
+ +
+
+ @php $profileAdmin = old('useProfilerInAdmin', isset($settings->useProfilerInAdmin) ? $settings->useProfilerInAdmin['val'] : false); @endphp + @lang('profiler::module.settings.useProfilerInAdmin') + + + + +
+
+ @include('admin.settings.snippets.commonSettings', ['field' => 'useProfilerInAdmin']) +
+
+ +
+ + @lang('profiler::module.deleteAllData') + + + + @lang('profiler::module.deleteOld') + +
+ + + + + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d191be6 --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name": "pingpong-modules/profiler", + "description": "", + "authors": [ + { + "name": "Pingpong Labs", + "email": "pingpong.labs@gmail.com" + } + ], + "autoload": { + "psr-4": { + "Modules\\Profiler\\": "" + } + } +} \ No newline at end of file diff --git a/start.php b/start.php new file mode 100644 index 0000000..7be329c --- /dev/null +++ b/start.php @@ -0,0 +1,15 @@ +