a few more base modules

This commit is contained in:
Bachir Soussi Chiadmi
2016-09-06 16:05:07 +02:00
parent c6cff46234
commit 027aa99b32
455 changed files with 43606 additions and 0 deletions
@@ -0,0 +1,28 @@
!! README.md is a work in progress !!
#Dependencies:
- d3.js: Webprofiler module requires D3 library 3.x (not 4.x) to render data.
Download https://github.com/mbostock/d3 into /libraries/d3/d3.min.js
- highlight.js: Webprofiler module requires highlight library to syntax highlight collected queries.
Download http://highlightjs.org into /libraries/highlight
#IDE link:
Every class name discovered while profiling (controller class, event class) are linked to an url for directly open in
an IDE, you can configure the url of those link based on the IDE you are using:
- Sublime text (2 and 3): see https://github.com/dhoulb/subl for Mac OS X
- Textmate: should be supported by default, use txmt://open?url=file://@file&line=@line as link
- PhpStorm 8+: use phpstorm://open?file=@file&line=@line as link
#Timeline:
Now it is possible to also collect the time needed to instantiate every single service used in a request, to make it
work you need to add these two lines to settings.php (or, event better, to settings.local.php):
```
$class_loader->addPsr4('Drupal\\webprofiler\\', [ __DIR__ . '/../../modules/contrib/devel/webprofiler/src']);
$settings['container_base_class'] = '\Drupal\webprofiler\DependencyInjection\TraceableContainer';
```
@@ -0,0 +1,18 @@
{
"name": "drupal/webprofiler",
"description": "Drupal Web Profiler.",
"type": "drupal-module",
"license": "GPL-2.0+",
"require": {
"oomphinc/composer-installers-extender" : "v1.1.*",
"mbostock/d3": "v3.5.*",
"components/highlightjs": "v9.5.*",
"symfony/stopwatch": "2.4.*"
},
"extra": {
"installer-types": ["library", "component"],
"installer-paths": {
"libraries/{$name}": ["mbostock/d3", "components/highlightjs"]
}
}
}
@@ -0,0 +1,28 @@
purge_on_cache_clear: true
storage: profiler.database_storage
exclude: "/contextual/*\r\n/toolbar/*\r\n/edit/*\r\n*.js\r\n*.css"
ide_link: "txmt://open?url=file://@file&line=@line"
ide_link_remote: ""
ide_link_local: ""
active_toolbar_items:
devel: devel
assets: assets
blocks: blocks
cache: cache
database: database
drupal_extension: drupal_extension
forms: forms
performance_timing: performance_timing
php_config: php_config
request: request
time: time
user: user
views: views
config: '0'
events: '0'
http: '0'
routing: '0'
services: '0'
state: '0'
query_sort: source
query_highlight: 5
@@ -0,0 +1,35 @@
# Schema for the configuration files of the Webprofiler module.
webprofiler.config:
type: config_object
label: 'Webprofiler configuration'
mapping:
purge_on_cache_clear:
type: boolean
label: 'Purge profiles on cache clear'
storage:
type: string
label: 'Storage implementation'
exclude:
type: string
label: 'Paths to exclude'
active_toolbar_items:
type: sequence
label: 'Active toolbar items'
sequence:
- type: string
label: 'Toolbar item'
ide_link:
type: string
label: 'IDE link'
ide_link_remote:
type: string
label: 'IDE link remote path'
ide_link_local:
type: string
label: 'IDE link local path'
query_sort:
type: string
label: 'Sort query log'
query_highlight:
type: integer
label: 'Slow query highlighting'
@@ -0,0 +1,54 @@
commands:
webprofiler:
export:
description: Exports Webprofiler profile/s to file.
arguments:
id: Profile id.
options:
directory: Destination directory to store exported file/s.
messages:
success: Succesfully exported to %s
exported_count: <info>Exported %s profiles</info>
error_writing: Error writing file %s
error_no_profile: No profile with id %s
progress:
exporting: Exporting profiles...
archive: Create archive...
delete_tmp: Delete temp files...
done: Done.
list:
description: Lists Webprofiler profiles.
options:
ip: Filter by IP.
url: Filter by URL.
method: Filter by HTTP method.
limit: Limit printed profiles.
rows:
time: D, m/d/Y - H:i:s
header:
token: Token
ip: IP
method: Method
url: URL
time: Time
benchmark:
description: Benchmark an url.
arguments:
url: Url to benchmark.
options:
runs: Number of runs.
file: Save results as file.
cache-rebuild: Rebuild cache before start benchmark.
messages:
not_git: Not in a git repository.
error_login: Impossibile to login in the user.
progress:
cache_rebuild: Rebuilding cache...
login: Login user...
get: Http request...
compute_avg: Compute average...
compute_median: Compute median...
compute_95percentile: Compute 95 percentile...
git_hash: Compute GIT hash...
yaml: Generate output...
done: Done.
@@ -0,0 +1,54 @@
commands:
webprofiler:
export:
description: Exportar Webprofiler perfil(es) a un archivo.
arguments:
id: Profile id.
options:
directory: Directorio de destiono para almacenar archivo(s) exportados.
messages:
success: Satisfactoriamente exportado en %s
exported_count: <info>Exportados %s perfiles</info>
error_writing: Error escribiendo el archivo %s
error_no_profile: No fue encontrado un perfil con id %s
progress:
exporting: Exportando perfiles...
archive: Creando archivos...
delete_tmp: Borrando archivos temporales...
done: Terminado.
list:
description: Listar perfiles Webprofiler.
options:
ip: Filtrar por IP.
url: Filtrar por URL.
method: Filtrar por método HTTP.
limit: Limitar perfiles a mostrar.
rows:
time: D, d/m/Y - H:i:s
header:
token: Token
ip: IP
method: Método
url: URL
time: Fecha
benchmark:
description: Benchmark una URL.
arguments:
url: Url para efectuar un benchmark.
options:
runs: Número de ejecuciones.
file: Guardar los resultados en un archivo.
cache-rebuild: Reconstruir cache antes de iniciar el benchmark.
messages:
not_git: No en un repositorio git.
error_login: Error ingresando al sistema.
progress:
cache_rebuild: Reconstruyendo cache...
login: Ingresando al sistema...
get: Solicitud Http...
compute_avg: Calculando promedio...
compute_median: Calculando media...
compute_95percentile: Calculando percentile 95...
git_hash: Calculando GIT hash...
yaml: Generando salida...
done: Terminado.
@@ -0,0 +1,582 @@
/* base themes */
#webrofiler {
overflow: hidden;
font-size: 1em;
line-height: 1.2;
color: rgba(0, 0, 0, 0.7);
background-color: #ffffff; }
#webrofiler * {
box-sizing: border-box; }
#webprofiler {
/*
overview example
<ul class="overview__list">
<li>
<a class="overview__link">
<img class="overview__icon" src="data:">
<span class="overview__title">
<span class="overview__subtitle">
*/
/*
general element template
will be implemented in the next dashboard relase
this HTML will contain the preview of any single card present inside the dashboard
<a class="ui--[red||pink||purple||deepPurple||indigo||blue||lightBlue||cyan||teal||green||lightGreen||lime||yellow||amber||orange||deepOrange||brown||gray||blueGray] app l-card">
<div class="app__bar">
<h3 class="app__title">
<img class="app__icon" src="data:">
<div class="app__container">
<div class="app__main-data"> text
<div class="app__secondary-data"> text
*/ }
#webprofiler .tabs:after, #webprofiler .tabs__tabs:after, #webprofiler .tabs__panels:after, #webprofiler .tabs__panel:after, #webprofiler .resume:after, #webprofiler .collectors:after, #webprofiler .panel:after, #webprofiler .panel__toolbar:after, #webprofiler .panel__container:after, #webprofiler .panel__expand-header:after, #webprofiler .panel__expand-content:after, #webprofiler .app__bar:after, #webprofiler .app__container:after, #webprofiler .modal__bar:after, #webprofiler .modal__content:after {
content: ' ';
display: block;
clear: both; }
#webprofiler .panel__container, #webprofiler .ui--red, #webprofiler .ui--pink, #webprofiler .ui--purple, #webprofiler .ui--deepPurple, #webprofiler .ui--indigo, #webprofiler .ui--blue, #webprofiler .ui--lightBlue, #webprofiler .ui--cyan, #webprofiler .ui--teal, #webprofiler .ui--green, #webprofiler .ui--lightGreen, #webprofiler .ui--lime, #webprofiler .ui--yellow, #webprofiler .ui--amber, #webprofiler .ui--orange, #webprofiler .ui--deepOrange, #webprofiler .ui--brown, #webprofiler .ui--gray, #webprofiler .ui--blueGray, #webprofiler .modal__container {
box-shadow: 0 2px 4px -2px #000000;
margin-bottom: 40px;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
overflow: hidden; }
#webprofiler .ui--red .app__title, #webprofiler .ui--pink .app__title, #webprofiler .ui--purple .app__title, #webprofiler .ui--deepPurple .app__title, #webprofiler .ui--blue .app__title, #webprofiler .ui--lightBlue .app__title, #webprofiler .ui--cyan .app__title, #webprofiler .ui--green .app__title, #webprofiler .ui--lightGreen .app__title, #webprofiler .ui--lime .app__title, #webprofiler .ui--yellow .app__title, #webprofiler .ui--amber .app__title, #webprofiler .ui--orange .app__title, #webprofiler .ui--deepOrange .app__title, #webprofiler .ui--brown .app__title {
color: white; }
#webprofiler .ui--indigo .app__title, #webprofiler .ui--teal .app__title, #webprofiler .ui--gray .app__title, #webprofiler .ui--blueGray .app__title, #webprofiler .modal__title {
color: rgba(0, 0, 0, 0.87); }
#webprofiler .ui--red .app__main-data, #webprofiler .ui--pink .app__main-data, #webprofiler .ui--purple .app__main-data, #webprofiler .ui--deepPurple .app__main-data, #webprofiler .ui--indigo .app__main-data, #webprofiler .ui--blue .app__main-data, #webprofiler .ui--lightBlue .app__main-data, #webprofiler .ui--cyan .app__main-data, #webprofiler .ui--teal .app__main-data, #webprofiler .ui--green .app__main-data, #webprofiler .ui--lightGreen .app__main-data, #webprofiler .ui--lime .app__main-data, #webprofiler .ui--yellow .app__main-data, #webprofiler .ui--amber .app__main-data, #webprofiler .ui--orange .app__main-data, #webprofiler .ui--deepOrange .app__main-data, #webprofiler .ui--brown .app__main-data, #webprofiler .ui--gray .app__main-data, #webprofiler .ui--blueGray .app__main-data, #webprofiler .modal__content {
color: rgba(0, 0, 0, 0.7); }
#webprofiler .ui--red .app__secondary-data, #webprofiler .ui--pink .app__secondary-data, #webprofiler .ui--purple .app__secondary-data, #webprofiler .ui--deepPurple .app__secondary-data, #webprofiler .ui--indigo .app__secondary-data, #webprofiler .ui--blue .app__secondary-data, #webprofiler .ui--lightBlue .app__secondary-data, #webprofiler .ui--cyan .app__secondary-data, #webprofiler .ui--teal .app__secondary-data, #webprofiler .ui--green .app__secondary-data, #webprofiler .ui--lightGreen .app__secondary-data, #webprofiler .ui--lime .app__secondary-data, #webprofiler .ui--yellow .app__secondary-data, #webprofiler .ui--amber .app__secondary-data, #webprofiler .ui--orange .app__secondary-data, #webprofiler .ui--deepOrange .app__secondary-data, #webprofiler .ui--brown .app__secondary-data, #webprofiler .ui--gray .app__secondary-data, #webprofiler .ui--blueGray .app__secondary-data {
color: rgba(0, 0, 0, 0.3); }
#webprofiler a {
color: #2979ff; }
#webprofiler a:hover {
color: #2962ff;
text-decoration: none; }
#webprofiler .list--unstyled {
margin: 0;
list-style-type: none;
padding: 0; }
#webprofiler .list--unstyled > li {
display: block;
padding: 0 8px; }
#webprofiler .list--inline {
margin: 0;
list-style-type: none;
padding: 0; }
#webprofiler .list--inline > li {
display: inline-block;
padding: 8px; }
#webprofiler .list--level-0 li {
padding: 0 0 0 0; }
#webprofiler .list--level-1 li {
padding: 0 0 0 8px; }
#webprofiler .list--level-3 li {
padding: 0 0 0 16px; }
#webprofiler pre {
padding: 16px; }
#webprofiler pre, #webprofiler code {
white-space: normal;
word-break: break-all;
word-break: break-word;
font-size: 14px;
background-color: #f5f5f5; }
#webprofiler .list-item--bold {
font-weight: bold;
}
#webprofiler code.code--json {
white-space: pre-wrap; }
#webprofiler thead {
white-space: nowrap; }
#webprofiler thead th {
background-color: transparent; }
#webprofiler th {
font-size: 12px;
color: rgba(0, 0, 0, 0.54);
border: none; }
#webprofiler td {
word-break: break-all;
font-size: 13px;
color: rgba(0, 0, 0, 0.87); }
#webprofiler tr {
border-bottom: 1px solid rgba(0, 0, 0, 0.18); }
#webprofiler tr:hover {
background-color: #eeeeee; }
#webprofiler td, #webprofiler th {
padding: 16px 24px;
min-height: 46px;
text-transform: none;
vertical-align: top; }
#webprofiler .table--compact {
table-layout: fixed; }
#webprofiler .table--compact td, #webprofiler .table--compact th {
word-spacing: 99em; }
#webprofiler .table--fixed {
table-layout: fixed; }
#webprofiler .table--duo th:first-child,
#webprofiler .table--duo td:first-child {
width: 35%; }
#webprofiler .table--trio th:first-child,
#webprofiler .table--trio td:first-child {
width: 80%; }
#webprofiler .table--trio th:nth-child(2),
#webprofiler .table--trio td:nth-child(2),
#webprofiler .table--trio th:nth-child(3),
#webprofiler .table--trio td:nth-child(3) {
width: 10%; }
#webprofiler [class*="button"] {
display: block;
color: #2979ff;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
border: none;
text-transform: uppercase;
font-size: 16px;
text-align: center;
padding: 16px;
margin: 8px;
cursor: pointer;
min-width: 42px; }
#webprofiler .button--flat {
background-color: transparent; }
#webprofiler .button--flat:hover {
background-color: #eeeeee; }
#webprofiler .button--flat:active, #webprofiler .button--flat:focus {
background-color: #bdbdbd; }
#webprofiler .tabs {
position: relative;
margin: -16px 0 16px 0; }
#webprofiler .tabs a {
display: inline; }
#webprofiler .tabs__radio {
display: none; }
#webprofiler .tabs__tabs {
margin: 0 -16px 16px -16px;
text-transform: uppercase;
height: 48px;
background-color: #f5f5f5; }
#webprofiler .tabs__tabs > li {
padding: 0; }
#webprofiler .tabs__label {
border-bottom: 5px solid transparent;
text-align: center;
line-height: 48px;
min-width: 160px;
display: inline-block;
margin: -5px 0 0 0; }
#webprofiler .tabs__panel {
display: none;
width: 100%; }
#webprofiler .tabs__radio:checked:nth-child(1) ~ .tabs__panels .tabs__panel:nth-child(1) {
display: block; }
#webprofiler .tabs__radio:checked:nth-child(2) ~ .tabs__panels .tabs__panel:nth-child(2) {
display: block; }
#webprofiler .tabs__radio:checked:nth-child(3) ~ .tabs__panels .tabs__panel:nth-child(3) {
display: block; }
#webprofiler .tabs__radio:checked:nth-child(1) ~ .tabs__tabs li:nth-child(1) .tabs__label {
border-bottom-color: #2962ff; }
#webprofiler .tabs__radio:checked:nth-child(2) ~ .tabs__tabs li:nth-child(2) .tabs__label {
border-bottom-color: #2962ff; }
#webprofiler .tabs__radio:checked:nth-child(3) ~ .tabs__tabs li:nth-child(3) .tabs__label {
border-bottom-color: #2962ff; }
#webprofiler .overview {
display: table-cell;
width: 20%;
vertical-align: top;
font-size: 1.2em;
line-height: 2em;
background: #ffffff; }
#webprofiler .overview__list {
padding: 0;
margin: -1px -1px 0 0;
list-style-type: none;
border-top: 1px solid rgba(0, 0, 0, 0.18); }
#webprofiler .overview__link {
position: relative;
display: block;
padding: 0 0 0 72px;
border-right: 5px solid transparent; }
#webprofiler .overview__link:after {
content: '';
display: block;
position: absolute;
bottom: 0;
right: -5px;
left: 72px;
border-bottom: 1px solid rgba(0, 0, 0, 0.18); }
#webprofiler .overview__icon {
position: absolute;
top: 16px;
left: 16px; }
#webprofiler .overview__title {
display: block;
font-size: 16px;
line-height: 22px;
padding-top: 16px;
padding-bottom: 16px;
padding-right: 11px; }
#webprofiler .overview__subtitle {
display: block;
font-size: 14px;
line-height: 20px;
padding-bottom: 16px;
padding-right: 11px;
margin-top: -16px; }
#webprofiler .is--selected .overview__link {
color: #000000;
background-color: #f5f5f5;
border-right-color: #2962ff; }
#webprofiler .overview__link:hover {
color: #000000;
background-color: #82b1ff; }
#webprofiler .resume {
position: relative;
background-color: #e0e0e0;
border-bottom: 1px solid rgba(0, 0, 0, 0.18);
border-left: 1px solid rgba(0, 0, 0, 0.18);
padding: 16px 16px 0 16px;
margin-left: 20%; }
#webprofiler .resume__title {
display: block;
font-size: 24px;
line-height: 32px; }
#webprofiler .resume__subtitle,
#webprofiler .resume__time {
display: block;
font-size: 16px;
line-height: 24px; }
#webprofiler .resume__time {
margin-bottom: 52px; }
#webprofiler .resume__button {
position: absolute;
bottom: 0;
right: 16px; }
#webprofiler .collectors {
display: table;
width: 100%; }
#webprofiler .details {
display: table-cell;
overflow: hidden;
padding: 16px;
border-left: 1px solid rgba(0, 0, 0, 0.18); }
#webprofiler .details__title {
border-bottom: 1px solid #ccc;
padding-bottom: 5px; }
#webprofiler .details svg {
max-width: 100%; }
#webprofiler [class*='panel__filter--'] {
display: inline-block;
padding: 32px 16px 0 16px;
position: relative; }
#webprofiler .panel {
margin: 0 0 20px;
padding: 0;
background: #fafafa;
border: none; }
#webprofiler .panel select,
#webprofiler .panel input[type="text"] {
min-width: 165px;
font-size: 16px;
background-color: #fafafa;
border-color: transparent;
border-width: 0;
border-bottom: 2px solid #ffffff;
padding: 8px 0;
margin-bottom: 8px; }
#webprofiler .panel select:focus,
#webprofiler .panel input[type="text"]:focus {
outline: none;
border-bottom-color: #2979ff; }
#webprofiler .panel select:focus + label,
#webprofiler .panel input[type="text"]:focus + label {
color: #2979ff; }
#webprofiler .panel select {
min-width: 165px;
padding-right: 24px;
border-radius: 0;
background: url(../../images/caret-down.svg) no-repeat 99% 63%;
-moz-appearance: none; }
#webprofiler .panel select:hover {
box-shadow: none; }
#webprofiler .panel__title {
font-size: 24px;
line-height: 1;
background-color: #f5f5f5;
text-transform: none;
padding: 16px;
margin: -16px -16px 16px -16px; }
#webprofiler .panel__toolbar {
background-color: #fafafa;
margin: -16px -16px 16px -16px; }
#webprofiler .panel__filter-action {
display: inline-block; }
#webprofiler .panel__filter-label {
display: block;
position: absolute;
top: 0;
margin-top: 16px; }
#webprofiler .panel__container {
background-color: #ffffff;
position: relative;
padding: 16px; }
#webprofiler .panel__expand-content {
display: none; }
#webprofiler .is--open .panel__expand-content {
display: block; }
#webprofiler .is--hightlighted {
border-right: 10px solid #aa00ff;
padding-right: 6px; }
#webprofiler .is--hidden {
display: none; }
#webprofiler .app {
overflow: hidden;
position: relative;
display: block;
cursor: pointer; }
#webprofiler .app__bar {
padding: 16px; }
#webprofiler .app__title {
margin: 8px 32px 0 0;
font-weight: normal; }
#webprofiler .app__icon {
float: right;
height: 32px;
width: 32px; }
#webprofiler .app__container {
padding: 16px; }
#webprofiler .app__main-data {
font-size: 24px;
padding-bottom: 16px; }
#webprofiler .app__secondary-data {
font-size: 14px;
padding-bottom: 16px;
margin-top: -8px; }
#webprofiler .ui--red .app__bar {
background-color: #ff1744; }
#webprofiler .ui--red .app__container {
background-color: #ff8a80; }
#webprofiler .ui--pink .app__bar {
background-color: #f50057; }
#webprofiler .ui--pink .app__container {
background-color: #ff80ab; }
#webprofiler .ui--purple .app__bar {
background-color: #d500f9; }
#webprofiler .ui--purple .app__container {
background-color: #ea80fc; }
#webprofiler .ui--deepPurple .app__bar {
background-color: #651fff; }
#webprofiler .ui--deepPurple .app__container {
background-color: #b388ff; }
#webprofiler .ui--indigo .app__bar {
background-color: #3d5afe; }
#webprofiler .ui--indigo .app__container {
background-color: #8c9eff; }
#webprofiler .ui--blue .app__bar {
background-color: #2979ff; }
#webprofiler .ui--blue .app__container {
background-color: #82b1ff; }
#webprofiler .ui--lightBlue .app__bar {
background-color: #00b0ff; }
#webprofiler .ui--lightBlue .app__container {
background-color: #80d8ff; }
#webprofiler .ui--cyan .app__bar {
background-color: #00e5ff; }
#webprofiler .ui--cyan .app__container {
background-color: #84ffff; }
#webprofiler .ui--teal .app__bar {
background-color: #ade9b6; }
#webprofiler .ui--teal .app__container {
background-color: #a7ffeb; }
#webprofiler .ui--green .app__bar {
background-color: #00e676; }
#webprofiler .ui--green .app__container {
background-color: #b9f6ca; }
#webprofiler .ui--lightGreen .app__bar {
background-color: #76ff03; }
#webprofiler .ui--lightGreen .app__container {
background-color: #ccff90; }
#webprofiler .ui--lime .app__bar {
background-color: #c6ff00; }
#webprofiler .ui--lime .app__container {
background-color: #f4ff81; }
#webprofiler .ui--yellow .app__bar {
background-color: #ffea00; }
#webprofiler .ui--yellow .app__container {
background-color: #ffff8d; }
#webprofiler .ui--amber .app__bar {
background-color: #ffc400; }
#webprofiler .ui--amber .app__container {
background-color: #ffe57f; }
#webprofiler .ui--orange .app__bar {
background-color: #ff9100; }
#webprofiler .ui--orange .app__container {
background-color: #ffd180; }
#webprofiler .ui--deepOrange .app__bar {
background-color: #ff3d00; }
#webprofiler .ui--deepOrange .app__container {
background-color: #ff9e80; }
#webprofiler .ui--brown .app__bar {
background-color: #8d6e63; }
#webprofiler .ui--brown .app__container {
background-color: #d7ccc8; }
#webprofiler .ui--gray .app__bar {
background-color: #bdbdbd; }
#webprofiler .ui--gray .app__container {
background-color: #f5f5f5; }
#webprofiler .ui--blueGray .app__bar {
background-color: #bdbdbd; }
#webprofiler .ui--blueGray .app__container {
background-color: #f5f5f5; }
#webprofiler .loader--fixed {
position: fixed;
right: 0;
top: 50%;
width: 80%;
height: 100px;
zoom: 1.7;
text-align: center; }
#webprofiler .loader__circle {
animation: rotate 2s linear infinite;
height: 100px;
position: relative;
width: 100px; }
#webprofiler .loader__path {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite;
stroke-linecap: round; }
@keyframes rotate {
100% {
transform: rotate(360deg); } }
@keyframes dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0; }
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35; }
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -124; } }
@keyframes color {
100%, 0% {
stroke: #ff1744; }
40% {
stroke: #2979ff; }
66% {
stroke: #00e676; }
80%, 90% {
stroke: #ffea00; } }
#webprofiler .loader--linear {
height: 3px;
background-color: #ffea00;
position: absolute;
top: 0;
left: 0;
right: 0; }
#webprofiler .loader__bar {
content: "";
display: inline;
position: absolute;
width: 0;
height: 100%;
left: 50%;
text-align: center; }
#webprofiler .loader__bar:nth-child(1) {
background-color: #ff1744;
animation: loading 3s linear infinite; }
#webprofiler .loader__bar:nth-child(2) {
background-color: #2979ff;
animation: loading 3s linear 1s infinite; }
#webprofiler .loader__bar:nth-child(3) {
background-color: #00e676;
animation: loading 3s linear 2s infinite; }
@keyframes loading {
from {
left: 50%;
width: 0;
z-index: 100; }
33.3333% {
left: 0;
width: 100%;
z-index: 10; }
to {
left: 0;
width: 100%; } }
#webprofiler .modal {
background-color: rgba(0, 0, 0, 0.87);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0; }
#webprofiler .modal__container {
border-top-right-radius: 2px;
border-top-left-radius: 2px;
box-shadow: 0 2px 4px -2px #000000;
background-color: #ffffff;
position: relative;
width: 33%;
min-width: 350px;
margin: 0 auto;
margin-top: 15%; }
#webprofiler .modal__bar {
padding: 16px; }
#webprofiler .modal__content {
padding: 16px;
font-size: 14px; }
#webprofiler .modal__title {
margin: 8px 32px 0 0;
font-weight: normal;
font-size: 24px; }
#webprofiler .modal__main-data {
font-size: 24px;
padding-bottom: 16px; }
#webprofiler .modal textarea {
white-space: normal;
word-break: break-all;
word-break: break-word;
font-size: 14px;
background-color: #f5f5f5;
max-width: 100%;
height: 320px;
width: 100%;
padding: 16px;
box-sizing: border-box;
border: none; }
#webprofiler .t--heading, #webprofiler .tabs__radio:checked:nth-child(1) ~ .tabs__tabs li:nth-child(1) .tabs__label, #webprofiler .tabs__radio:checked:nth-child(2) ~ .tabs__tabs li:nth-child(2) .tabs__label, #webprofiler .tabs__radio:checked:nth-child(3) ~ .tabs__tabs li:nth-child(3) .tabs__label, #webprofiler .overview__title, #webprofiler .resume__title, #webprofiler .panel select,
#webprofiler .panel input[type="text"], #webprofiler .panel__title {
color: black; }
#webprofiler .t--notice, #webprofiler .tabs__label, #webprofiler .overview__subtitle, #webprofiler .resume__subtitle,
#webprofiler .resume__time, #webprofiler .panel__filter-label {
color: rgba(0, 0, 0, 0.3); }
#webprofiler .l-card {
width: 32%;
margin-left: 1%;
margin-right: 1%;
float: left; }
#webprofiler .l-card:nth-child(3n + 1) {
margin-left: 0; }
#webprofiler .l-card:nth-child(3n) {
margin-right: 0; }
#webprofiler .l-left {
float: left; }
#webprofiler .l-right {
float: right; }
#webprofiler .h--word-broken {
word-break: break-all; }
#webprofiler .h--word-intact {
word-break: normal; }
/*# sourceMappingURL=dashboard.css.map */
@@ -0,0 +1,106 @@
/**
* Timeline
*/
.timeline__legends {
font-size: 12px;
line-height: 1.5em;
}
.timeline__legends span {
border-left-width: 10px;
border-left-style: solid;
padding: 0 10px 0 5px;
}
.timeline__legends--default {
border-left-color: #E91E63;
}
.timeline__legends--section {
border-left-color: #3F51B5;
}
.timeline__legends--event_listener {
border-left-color: #00BCD4;
}
.timeline__legends--event_listener_loading {
border-left-color: #8BC34A;
}
.timeline__legends--template {
border-left-color: #FFC107;
}
.timeline__legends--service {
border-left-color: #795548;
}
text {
font-size: 12px;
line-height: 20px;
height: 22px;
fill: rgba(0, 0, 0, 0.87);
}
#timeline {
background: white;
margin: 10px 0;
width: 100%;
position: relative;
padding: 0 0 40px 0;
}
.timeline__row,
.timeline__scale--x,
.timeline__label rect {
stroke: rgba(0, 0, 0, 0.18);
}
.timeline__row{
fill: transparent;
}
.timeline__label rect {
fill: white;
}
.timeline__period--default {
fill: #E91E63;
}
.timeline__period--service {
fill: #795548;
}
.timeline__period--section {
fill: #3F51B5;
}
.timeline__period--event_listener {
fill: #00BCD4;
}
.timeline__period--event_listener_loading {
fill: #8BC34A;
}
.timeline__period--template {
fill: #FFC107;
}
.timeline__period-trigger{
fill: transparent;
}
.tooltip{
position: absolute;
padding: 8px;
background: rgb(0,0,0);
background: rgba(0, 0, 0, 0.87);
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
width: 175px;
text-align: center;
display: none;
color: white;
}
.tooltip__title{
display: block;
}
.tooltip__content{
display: block;
}
.axis{
stroke-width: 2px;
fill: none;
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#333333" d="M3.8 5.4c-.165-.22-.075-.4.2-.4h8.002c.275 0 .365.18.199.4l-3.898 5.2c-.166.221-.436.221-.6 0l-3.903-5.2z"/></svg>

After

Width:  |  Height:  |  Size: 202 B

@@ -0,0 +1,35 @@
(function ($, Drupal, drupalSettings, Backbone) {
"use strict";
Drupal.webprofiler.collectors.Collectors = Backbone.Collection.extend({
model: Drupal.webprofiler.models.Collector,
url: Drupal.url('admin/reports/profiler/view/' + drupalSettings.webprofiler.token + '/collectors'),
/**
* Unselect all models.
*/
resetSelected: function () {
this.each(function (model) {
model.set({"selected": false});
});
},
/**
* Select a specific model from the collection.
*
* @param id
* @returns {*}
*/
selectByID: function (id) {
this.resetSelected();
var collector = this.get(id);
collector.set({"selected": true});
return collector.id;
}
});
}(jQuery, Drupal, drupalSettings, Backbone));
@@ -0,0 +1,104 @@
(function (drupalSettings) {
Drupal.webprofiler.helpers = (function () {
"use strict";
var escapeRx = function escapeRegExp(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
},
repl = function replaceAll(string, find, replace) {
return string.replace(new RegExp(escapeRx(find), 'g'), replace);
},
shortLink = function (clazz) {
if (!clazz) {
return null;
}
clazz = repl(clazz, '/', '\\');
var parts = clazz.split("\\"), result = [], size = (parts.length - 1);
_.each(parts, function (item, key) {
if (key < size) {
result.push(item.substring(0, 1));
} else {
result.push(item);
}
});
return result.join("\\");
},
abbr = function (clazz) {
if (!clazz) {
return null;
}
return '<abbr title="' + clazz + '">' + shortLink(clazz) + '</abbr>';
},
ideLink = function (file, line) {
if (!file) {
return null;
}
line = line || 0;
file = file.replace(drupalSettings.webprofiler.ide_link_remote, drupalSettings.webprofiler.ide_link_local);
return drupalSettings.webprofiler.ide_link.replace("@file", file).replace("@line", line);
},
classLink = function (data) {
var link = ideLink(data['file'], data['line']), clazz = abbr(data['class']), method = data['method'], output = '';
output = clazz;
if (method) {
output += '::' + method;
}
if (link) {
output = '<a href="' + link + '">' + output + '</a>';
}
return output;
},
printTime = function (data, unit) {
unit = unit || 'ms';
data = Math.round((data + 0.00001) * 100) / 100;
return data + ' ' + unit;
},
frm = function (obj, level) {
level = level || 0;
var str = '<ul class="list--unstyled list--level-' + level + ' list--flat">', prop;
if (typeof obj != 'object') {
return obj;
}
for (prop in obj) {
if (isInt(prop)) {
str += '<li>' + frm(obj[prop], level + 1) + '</li>';
} else {
str += '<li><span class="list-item--bold">' + prop + '</span>: ' + frm(obj[prop], level + 1) + '</li>';
}
}
return str + '</ul>';
},
isInt = function (value) {
var x;
return isNaN(value) ? !1 : (x = parseFloat(value), (0 | x) === x);
};
return {
frm: frm,
ideLink: ideLink,
shortLink: shortLink,
classLink: classLink,
printTime: printTime
}
})();
}(drupalSettings));
@@ -0,0 +1,105 @@
(function ($, Drupal, Backbone) {
"use strict";
/**
* Define namespaces.
*/
Drupal.webprofiler = {
views: {},
models: {},
collectors: {},
routers: {}
};
Drupal.behaviors.webprofiler = {
attach: function (context) {
var el,
elz,
key,
sel,
value,
select,
selector,
unselected,
filter = [],
livefilter = function (e) {
el = $(e).attr('id').replace('edit-', '');
value = $(e).val();
filter[el] = value.replace('/', '\/');
selector = [];
unselected = [];
for (key in filter) {
if (filter[key].length > 0 && filter[key] != ' ') {
select = filter[key].split(' ').filter(Boolean);
for (sel in select) {
selector.push('[data-wp-' + key + ' *= ' + select[sel].toLowerCase() + ']');
unselected.push('[data-wp-' + key + ']:not([data-wp-' + key + ' *= ' + select[sel].toLowerCase() + '])');
}
}
else {
selector.push('[data-wp-' + key + ']');
}
}
for (elz in unselected) {
$(unselected[elz]).addClass('is--hidden');
}
$(selector.join('')).removeClass('is--hidden');
},
modalFill = function(t,c){
$('.modal__title').html(t);
$('.modal__main-data').html(c);
},
clipboard = function (e, t) {
var clip = e.parent().find(t).get(0),
title = 'Original Code',
content = '<textarea readonly >' +
clip.textContent +
'</textarea>';
modalFill(title,content);
$('.modal').show();
};
$(context).find('#collectors').once('webprofiler').each(function () {
new Drupal.webprofiler.routers.CollectorsRouter({el: $('#collectors')});
Backbone.history.start({
pushState: false
});
});
$(context).find('.js--modal-close').each(function () {
$(this).on('click', function () {
$('.js--modal').hide();
});
});
$(context).find('.js--live-filter').each(function () {
$(this).on('keyup', function () {
livefilter($(this));
});
$(this).on('change', function () {
livefilter($(this));
});
});
$(context).find('.js--panel-toggle').once('js--panel-toggle').each(function () {
$(this).on('click', function () {
$(this).parent().parent().toggleClass('is--open');
});
});
$(context).find('.js--clipboard-trigger').once('js--clipboard-trigger').each(function () {
$(this).on('click', function () {
clipboard($(this), '.js--clipboard-target')
}
);
});
}
};
}(jQuery, Drupal, Backbone));
@@ -0,0 +1,15 @@
(function ($, Drupal, drupalSettings, Backbone) {
"use strict";
Drupal.webprofiler.models.Collector = Backbone.Model.extend({
idAttribute: 'name',
urlRoot: Drupal.url('admin/reports/profiler/view/' + drupalSettings.webprofiler.token + '/collectors'),
defaults: {
name: "default",
data: [],
selected: false
}
});
}(jQuery, Drupal, drupalSettings, Backbone));
@@ -0,0 +1,48 @@
(function ($, Drupal, drupalSettings, Backbone) {
"use strict";
var collectors = new Drupal.webprofiler.collectors.Collectors(drupalSettings.webprofiler.collectors);
Drupal.webprofiler.routers.CollectorsRouter = Backbone.Router.extend({
routes: {
':id': 'selectCollector'
},
/**
*
* @param id
*/
selectCollector: function (id) {
var collectors = this.collectors, layout = this.layout;
//collectors.resetSelected();
collectors.selectByID(id);
var collector = collectors.get(id);
if (collector.get('data').length != 0) {
layout.setDetails(collector);
} else {
var deferred = collectors.get(id).fetch();
deferred.done(function () {
layout.setDetails(collector);
});
}
},
/**
*
* @param options
*/
initialize: function (options) {
this.collectors = collectors;
this.layout = Drupal.webprofiler.views.Layout.getInstance({
el: options.el,
router: this
});
this.layout.render();
}
});
}(jQuery, Drupal, drupalSettings, Backbone));
@@ -0,0 +1,29 @@
(function ($, Drupal, Backbone) {
"use strict";
Drupal.webprofiler.views.CollectorView = Backbone.View.extend({
tagName: 'li',
template: _.template($("script#collector").html()),
/**
*
*/
initialize: function () {
_.bindAll(this, "render");
this.listenTo(this.model, 'change:selected', this.render);
},
/**
*
* @returns {Drupal.webprofiler.views.CollectorView}
*/
render: function () {
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass('is--selected', this.model.get('selected'));
return this;
}
});
}(jQuery, Drupal, Backbone));
@@ -0,0 +1,21 @@
(function ($, Drupal, Backbone) {
"use strict";
Drupal.webprofiler.views.CollectorsList = Backbone.View.extend({
tagName: 'section',
/**
*
* @returns {Drupal.webprofiler.views.CollectorsList}
*/
render: function () {
var collectorsView = this.collection.map(function (collector) {
return (new Drupal.webprofiler.views.CollectorView({model: collector})).render().el;
});
this.$el.html(collectorsView);
return this;
}
});
}(jQuery, Drupal, Backbone));
@@ -0,0 +1,20 @@
(function ($, Drupal, Backbone) {
"use strict";
Drupal.webprofiler.views.DetailsView = Backbone.View.extend({
el: '#details',
/**
*
* @returns {Drupal.webprofiler.views.DetailsView}
*/
render: function () {
var template = _.template($("script#" + this.model.get('name')).html());
this.$el.html(template(this.model.toJSON()));
return this;
}
});
}(jQuery, Drupal, Backbone));
@@ -0,0 +1,83 @@
(function ($, Drupal, drupalSettings, Backbone) {
"use strict";
Drupal.webprofiler.views.Layout = Backbone.View.extend({
template: _.template(
'<div id="overview" class="overview">' +
'<ul class="overview__list"></ul>' +
'</div>' +
'<div id="details" class="details">' +
'Choose a collector.' +
'</div>'
),
/**
*
* @returns {Drupal.webprofiler.views.Layout}
*/
render: function () {
this.$el.html(this.template());
if(this.currentDetails) {
this.currentDetails.setElement(this.$('#details')).render();
}
this.overview.setElement(this.$('#overview ul')).render();
return this;
},
/**
*
* @param options
*/
initialize: function (options) {
options.router.collectors.on('request', this.beginSync);
options.router.collectors.on('sync', this.finishSync);
this.overview = new Drupal.webprofiler.views.CollectorsList({
collection: options.router.collectors,
router: options.router
});
},
/**
*
* @param collector
*/
setDetails: function (collector) {
if (this.currentDetails) this.currentDetails.remove();
this.currentDetails = new Drupal.webprofiler.views.DetailsView({model: collector});
this.render();
Drupal.attachBehaviors(/*this.$el, drupalSettings*/);
},
/**
*
*/
beginSync: function () {
$('.loader--fixed').fadeIn({duration: 100});
},
/**
*
*/
finishSync: function () {
$('.loader--fixed').fadeOut({duration: 100});
}
});
var instance;
Drupal.webprofiler.views.Layout.getInstance = function (options) {
if (!instance) {
instance = new Drupal.webprofiler.views.Layout({
el: options.el,
router: options.router,
collection: options.router.collectors
});
}
return instance;
}
}(jQuery, Drupal, drupalSettings, Backbone));
@@ -0,0 +1,68 @@
/**
* @file
* Database panel app.
*/
(function ($, Drupal, drupalSettings) {
"use strict";
Drupal.behaviors.webprofiler_database = {
attach: function (context) {
$(context).find('.js--explain-trigger').once('js--explain-trigger').each(function () {
$(this).on('click', function () {
var position = $(this).attr('data-wp-queryPosition'),
wrapper = $(this).parent().parent().find('.js--explain-target'),
loader = $(this).parent().parent().find('.js--loader');
if (wrapper.html().length === 0) {
var url = Drupal.url('admin/reports/profiler/database_explain/' + drupalSettings.webprofiler.token + '/' + position);
loader.show();
$.getJSON(url, function (data) {
_.templateSettings.variable = 'rc';
var template = _.template(
$("#wp-query-explain-template").html()
);
wrapper.html(template(data));
loader.hide();
delete _.templateSettings.variable;
});
}
wrapper.toggle();
});
});
$(context).find('.js--code-toggle').once('js--code-toggle').each(function () {
$(this).on('click', function () {
$(this).parent().find('.js--code-target').find('code').toggleClass('is--hidden');
});
});
$(context).find('.js--code-toggle--global').once('js--code-toggle--global').each(function () {
$(this).on('click', function () {
if($(this).hasClass('js--placeholder-visible')){
$('.js--placeholder-query').addClass('is--hidden');
$('.js--original-query').removeClass('is--hidden');
}else{
$('.js--placeholder-query').removeClass('is--hidden');
$('.js--original-query').addClass('is--hidden');
}
$(this).toggleClass('js--placeholder-visible');
});
});
if (typeof hljs != "undefined") {
$('code.sql').each(function (i, block) {
hljs.highlightBlock(block);
});
}
}
}
})
(jQuery, Drupal, drupalSettings);
@@ -0,0 +1,223 @@
/**
* @file
* Timeline panel app.
*/
(function ($, Drupal, drupalSettings, d3) {
'use strict';
Drupal.behaviors.webprofiler_timeline = {
attach: function (context) {
if (typeof d3 != 'undefined') {
// data
var data = drupalSettings.webprofiler.time.events;
var parts = [];
var dataL = data.length;
var perL;
var labelW = [];
var rowW;
var scalePadding;
var endTime = parseInt(data[(dataL - 1)].endtime);
var roundTime = Math.ceil(endTime / 1000) * 1000;
var endScale;
for (var j = 0; j < dataL; j++) {
perL = data[j].periods.length;
for (var k = 0; k < perL; k++) {
parts.push({
lane: j,
category: data[j].category,
memory: data[j].memory,
name: data[j].name,
start: data[j].periods[k].start,
end: data[j].periods[k].end
});
}
}
var tooltipCtrl = function (d, i) {
tooltip.html('<span class="tooltip__content">memory usage: ' + d.memory + '</span>' +
'<span class="tooltip__content">' + parseInt(d.start) + 'ms ~ ' + parseInt(d.end) + 'ms</span>');
tooltip
.style('display', 'block')
.style('left', (d3.event.layerX - 87) + 'px')
.style('top', ((d.lane + 1) * 22) + 'px')
.style('opacity', .9);
};
var xscale = d3.scale.linear().domain([0, roundTime]).range([0, 1000]);
d3.select('#timeline').append('svg').attr('height', (dataL + 1) * 22 + 'px').attr('width', '100%').attr('class', 'timeline__canvas');
// tooltips
var tooltip = d3.select('#timeline')
.append('div')
.attr('class', 'tooltip');
// Add a rectangle for every data element.
d3.select('.timeline__canvas')
.append('g')
.attr('class', 'timeline__rows')
.attr('x', 0)
.attr('y', 0)
.selectAll('g')
.data(data)
.enter()
.append('rect')
.attr('class', 'timeline__row')
.attr('x', 0)
.attr('y', function (d, i) {
return (i * 22);
})
.attr('height', 22)
.attr('width', '100%')
.each(function () {
rowW = this.getBoundingClientRect().width;
});
// scale
var scale = d3.select('.timeline__canvas')
.append('g')
.attr('class', 'timeline__scale')
.attr('id', 'timeline__scale')
.attr('x', 0)
.attr('y', 0)
.selectAll('g')
.data(data)
.enter()
.append('a')
.attr('xlink:href', function (d) {
return Drupal.webprofiler.helpers.ideLink(d.link);
})
.attr('class', function (d) {
return 'timeline__label ' + d.category;
})
.attr('x', xscale(5))
.attr('y', function (d, i) {
return (((i + 1) * 22) - 5);
});
scale.append('title')
.text(function (d) {
return d.name;
});
scale.append('text')
.attr('x', xscale(5))
.attr('y', function (d, i) {
return (((i + 1) * 22) - 5);
})
.text(function (d) {
return Drupal.webprofiler.helpers.shortLink(d.name);
})
.each(function (d) {
labelW.push(this.getBoundingClientRect().width);
});
scalePadding = Math.max.apply(null, labelW) + 10;
scale.insert('rect', 'title')
.attr('x', 0)
.attr('y', function (d, i) {
return (i * 22);
})
.attr('height', 22)
.attr('stroke', 'transparent')
.attr('strokw-width', 1)
.attr('width', scalePadding);
// times
var events = d3.select('.timeline__canvas')
.insert('g', '.timeline__scale')
.attr('class', 'timeline__parts')
.attr('x', 0)
.attr('y', 0)
.selectAll('g')
.data(parts)
.enter();
events.append('rect').attr('class', function (d) {
return 'timeline__period--' + d.category;
})
.attr('x', function (d) {
return xscale(parseInt(d.start)) + scalePadding;
})
.attr('y', function (d) {
return d.lane * 22;
})
.attr('height', 22)
.attr('width', function (d) {
return xscale(Math.max(parseInt(d.end - d.start), 1));
});
events.append('rect')
.attr('class', function (d) {
return 'timeline__period-trigger';
})
.attr('x', function (d) {
return xscale(parseInt(d.start)) + scalePadding - 5;
})
.attr('y', function (d) {
return d.lane * 22;
})
.attr('height', 22)
.attr('width', function (d) {
return xscale(Math.max(parseInt(d.end - d.start), 1)) + 11;
})
.on('mouseover', function (d, i) {
tooltipCtrl(d, i);
})
.on('mouseout', function (d) {
tooltip
.style('display', 'none');
});
// Draw X-axis grid lines
d3.select('.timeline__parts').insert('g', '.timeline__parts')
.selectAll('line')
.data(xscale.ticks(10))
.enter()
.append('line')
.attr('class', 'timeline__scale--x')
.attr('x1', xscale)
.attr('x2', xscale)
.attr('y1', 0)
.attr('y2', data.length * 22)
.attr('transform', 'translate( ' + scalePadding + ' , 0)');
var xAxis = d3.svg.axis().scale(xscale).ticks(10).orient('bottom').tickFormat(function (d) {
return d + ' ms';
});
d3.select('.timeline__parts').insert('g', '.timeline__parts')
.attr('class', 'axis')
.attr('transform', 'translate(' + scalePadding + ', ' + dataL * 22 + ')')
.call(xAxis);
endScale = xscale(endTime) - rowW - parseInt(scalePadding);
if (parseInt(xscale(endTime)) > (rowW - parseInt(scalePadding))) {
d3.select('.timeline__canvas')
.call(
d3.behavior.zoom()
.scaleExtent([1, 1])
.x(xscale)
.on('zoom', function () {
var t = d3.event.translate;
var tx = t[0];
tx = tx > 0 ? 0 : tx;
tx = tx < endScale ? endScale : tx;
d3.select('.timeline__parts').attr('transform', 'translate( ' + tx + ' , 0)');
}));
}
}
}
};
})(jQuery, Drupal, drupalSettings, d3);
@@ -0,0 +1,83 @@
<?php
namespace Drupal\webprofiler\Access;
use Drupal\Core\Access\AccessException;
use Drupal\Core\Access\AccessManager;
use Drupal\Component\Utility\ArgumentsResolverInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\webprofiler\DataCollector\RequestDataCollector;
use Symfony\Component\HttpFoundation\Request;
/**
* Attaches access check services to routes and runs them on request.
*
* @see \Drupal\Tests\Core\Access\AccessManagerTest
*/
class AccessManagerWrapper extends AccessManager {
/**
* @var \Drupal\webprofiler\DataCollector\RequestDataCollector
*/
private $dataCollector;
/**
* {@inheritdoc}
*/
public function check(RouteMatchInterface $route_match, AccountInterface $account = NULL, Request $request = NULL, $return_as_object = FALSE) {
if (!isset($account)) {
$account = $this->currentUser;
}
$route = $route_match->getRouteObject();
$checks = $route->getOption('_access_checks') ?: array();
// Filter out checks which require the incoming request.
if (!isset($request)) {
$checks = array_diff($checks, $this->checkProvider->getChecksNeedRequest());
}
$result = AccessResult::neutral();
if (!empty($checks)) {
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request);
if (!$checks) {
return AccessResult::neutral();
}
$result = AccessResult::allowed();
foreach ($checks as $service_id) {
$result = $result->andIf($this->performCheck($service_id, $arguments_resolver, $request));
}
}
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritdoc}
*/
protected function performCheck($service_id, ArgumentsResolverInterface $arguments_resolver, Request $request = NULL) {
$callable = $this->checkProvider->loadCheck($service_id);
$arguments = $arguments_resolver->getArguments($callable);
/** @var \Drupal\Core\Access\AccessResultInterface $service_access **/
$service_access = call_user_func_array($callable, $arguments);
if (!$service_access instanceof AccessResultInterface) {
throw new AccessException("Access error in $service_id. Access services must return an object that implements AccessResultInterface.");
}
if($request) {
$this->dataCollector->addAccessCheck($service_id, $callable, $request);
}
return $service_access;
}
/**
* @param \Drupal\webprofiler\DataCollector\RequestDataCollector $dataCollector
*/
public function setDataCollector(RequestDataCollector $dataCollector) {
$this->dataCollector = $dataCollector;
}
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\webprofiler\Asset;
use Drupal\Core\Asset\AssetCollectionRendererInterface;
use Drupal\webprofiler\DataCollector\AssetsDataCollector;
/**
* Class CssCollectionRendererWrapper.
*/
class CssCollectionRendererWrapper implements AssetCollectionRendererInterface {
/**
* @var \Drupal\Core\Asset\AssetCollectionRendererInterface
*/
private $assetCollectionRenderer;
/**
* @var \Drupal\webprofiler\DataCollector\AssetsDataCollector
*/
private $dataCollector;
/**
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $assetCollectionRenderer
* @param \Drupal\webprofiler\DataCollector\AssetsDataCollector $dataCollector
*/
public function __construct(AssetCollectionRendererInterface $assetCollectionRenderer, AssetsDataCollector $dataCollector) {
$this->assetCollectionRenderer = $assetCollectionRenderer;
$this->dataCollector = $dataCollector;
}
/**
* {@inheritdoc}
*/
public function render(array $css_assets) {
$this->dataCollector->addCssAsset($css_assets);
return $this->assetCollectionRenderer->render($css_assets);
}
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\webprofiler\Asset;
use Drupal\Core\Asset\AssetCollectionRendererInterface;
use Drupal\webprofiler\DataCollector\AssetsDataCollector;
/**
* Class JsCollectionRendererWrapper.
*/
class JsCollectionRendererWrapper implements AssetCollectionRendererInterface {
/**
* @var \Drupal\Core\Asset\AssetCollectionRendererInterface
*/
private $assetCollectionRenderer;
/**
* @var \Drupal\webprofiler\DataCollector\AssetsDataCollector
*/
private $dataCollector;
/**
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $assetCollectionRenderer
* @param \Drupal\webprofiler\DataCollector\AssetsDataCollector $dataCollector
*/
public function __construct(AssetCollectionRendererInterface $assetCollectionRenderer, AssetsDataCollector $dataCollector) {
$this->assetCollectionRenderer = $assetCollectionRenderer;
$this->dataCollector = $dataCollector;
}
/**
* {@inheritdoc}
*/
public function render(array $js_assets) {
$this->dataCollector->addJsAsset($js_assets);
return $this->assetCollectionRenderer->render($js_assets);
}
}
@@ -0,0 +1,179 @@
<?php
namespace Drupal\webprofiler\Cache;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\webprofiler\DataCollector\CacheDataCollector;
/**
* Wraps an existing cache backend to track calls to the cache backend.
*/
class CacheBackendWrapper implements CacheBackendInterface {
/**
* The data collector to register the calls.
*
* @var \Drupal\webprofiler\DataCollector\CacheDataCollector
*/
protected $cacheDataCollector;
/**
* The wrapped cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheBackend;
/**
* The name of the wrapped cache bin.
*
* @var string
*/
protected $bin;
/**
* Constructs a new CacheBackendWrapper.
*
* @param \Drupal\webprofiler\DataCollector\CacheDataCollector $cacheDataCollector
* The cache data collector to inform about cache get calls.
* @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
* The wrapped cache backend.
* @param string $bin
* The name of the wrapped cache bin.
*/
public function __construct(CacheDataCollector $cacheDataCollector, CacheBackendInterface $cacheBackend, $bin) {
$this->cacheDataCollector = $cacheDataCollector;
$this->cacheBackend = $cacheBackend;
$this->bin = $bin;
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
$cache = $this->cacheBackend->get($cid, $allow_invalid);
if ($cache) {
$cacheCopy = new \StdClass();
$cacheCopy->cid = $cache->cid;
$cacheCopy->expire = $cache->expire;
$cacheCopy->tags = $cache->tags;
$this->cacheDataCollector->registerCacheHit($this->bin, $cacheCopy);
}
else {
$this->cacheDataCollector->registerCacheMiss($this->bin, $cid);
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$cidsCopy = $cids;
$cache = $this->cacheBackend->getMultiple($cids, $allow_invalid);
foreach ($cidsCopy as $cid) {
if (in_array($cid, $cids)) {
$this->cacheDataCollector->registerCacheMiss($this->bin, $cid);
}
else {
$cacheCopy = new \StdClass();
$cacheCopy->cid = $cache[$cid]->cid;
$cacheCopy->expire = $cache[$cid]->expire;
$cacheCopy->tags = $cache[$cid]->tags;
$this->cacheDataCollector->registerCacheHit($this->bin, $cacheCopy);
}
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
return $this->cacheBackend->set($cid, $data, $expire, $tags);
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
return $this->cacheBackend->setMultiple($items);
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
return $this->cacheBackend->delete($cid);
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $cids) {
return $this->cacheBackend->deleteMultiple($cids);
}
/**
* {@inheritdoc}
*/
public function deleteTags(array $tags) {
return $this->cacheBackend->deleteTags($tags);
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
return $this->cacheBackend->deleteAll();
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
return $this->cacheBackend->invalidate($cid);
}
/**
* {@inheritdoc}
*/
public function invalidateMultiple(array $cids) {
return $this->cacheBackend->invalidateMultiple($cids);
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
return $this->cacheBackend->invalidateTags($tags);
}
/**
* {@inheritdoc}
*/
public function invalidateAll() {
return $this->cacheBackend->invalidateAll();
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
return $this->cacheBackend->garbageCollection();
}
/**
* {@inheritdoc}
*/
public function removeBin() {
return $this->cacheBackend->removeBin();
}
}
@@ -0,0 +1,62 @@
<?php
namespace Drupal\webprofiler\Cache;
use Drupal\Core\Cache\CacheFactoryInterface;
use Drupal\webprofiler\DataCollector\CacheDataCollector;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
* Wraps a cache factory to register all calls to the cache system.
*/
class CacheFactoryWrapper implements CacheFactoryInterface, ContainerAwareInterface {
use ContainerAwareTrait;
/**
* The cache factory.
*
* @var \Drupal\Core\Cache\CacheFactoryInterface
*/
protected $cacheFactory;
/**
* The cache data collector.
*
* @var \Drupal\webprofiler\DataCollector\CacheDataCollector
*/
protected $cacheDataCollector;
/**
* All wrapped cache backends.
*
* @var \Drupal\webprofiler\Cache\CacheBackendWrapper[]
*/
protected $cacheBackends = [];
/**
* Creates a new CacheFactoryWrapper instance.
*
* @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
* The cache factory.
* @param \Drupal\webprofiler\DataCollector\CacheDataCollector $cacheDataCollector
* The cache data collector.
*/
public function __construct(CacheFactoryInterface $cache_factory, CacheDataCollector $cacheDataCollector) {
$this->cacheFactory = $cache_factory;
$this->cacheDataCollector = $cacheDataCollector;
}
/**
* {@inheritdoc}
*/
public function get($bin) {
if (!isset($this->cacheBackends[$bin])) {
$cache_backend = $this->cacheFactory->get($bin);
$this->cacheBackends[$bin] = new CacheBackendWrapper($this->cacheDataCollector, $cache_backend, $bin);
}
return $this->cacheBackends[$bin];
}
}
@@ -0,0 +1,302 @@
<?php
namespace Drupal\webprofiler\Command;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Cookie\CookieJar;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Drupal\Console\Command\ContainerAwareCommand;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml;
/**
* Class BenchmarkCommand
*/
class BenchmarkCommand extends ContainerAwareCommand {
/**
* {@inheritdoc}
*/
protected function configure() {
$this
->setName('webprofiler:benchmark')
->setDescription($this->trans('commands.webprofiler.benchmark.description'))
->addArgument('url', InputArgument::REQUIRED, $this->trans('commands.webprofiler.benchmark.arguments.url'))
->addOption('runs', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.runs'), 100)
->addOption('file', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.file'))
->addOption('cache-rebuild', 'cr', InputOption::VALUE_NONE, $this->trans('commands.webprofiler.benchmark.options.cache_rebuild'));
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$runs = $input->getOption('runs');
$file = $input->getOption('file');
$cache_rebuild = $input->getOption('cache-rebuild');
// http://username:password@hostname/
$url = $input->getArgument('url');
$url_components = parse_url($url);
$login = isset($url_components['user']) && isset($url_components['pass']);
$steps = 3;
if ($cache_rebuild) {
$steps++;
}
if ($login) {
$steps++;
}
/** @var \Drupal\Core\Http\Client $client */
$client = $this->getContainer()->get('http_client');
$progress = new ProgressBar($output, $runs + $steps);
$progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
if ($cache_rebuild) {
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.cache_rebuild'));
$this->RebuildCache();
$progress->advance();
}
if ($login) {
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.login'));
$login_url = "{$url_components['scheme']}://{$url_components['host']}/user/login";
// Enable cookies storage.
$cookieJar = new CookieJar();
$client->setDefaultOption('cookies', $cookieJar);
// Retrieve a form_build_id using the DomCrawler component.
$response = $client->get($login_url)->getBody()->getContents();
$crawler = new Crawler($response);
$form_build_id = $crawler->filter('#user-login-form input[name=form_build_id]')
->attr('value');
$op = $crawler->filter('#user-login-form input[name=op]')->attr('value');
// Login a user.
$response = $client->post($login_url, [
'body' => [
'name' => $url_components['user'],
'pass' => $url_components['pass'],
'form_build_id' => $form_build_id,
'form_id' => 'user_login_form',
'op' => $op,
]
]);
$progress->advance();
if ($response->getStatusCode() != 200) {
throw new \Exception($this->trans('commands.webprofiler.benchmark.messages.error_login'));
}
}
$datas = [];
for ($i = 0; $i < $runs; $i++) {
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.get'));
$datas[] = $this->getData($client, $url);
$progress->advance();
}
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_avg'));
$avg = $this->computeAvg($datas);
$progress->advance();
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_median'));
$median = $this->computePercentile($datas, 50);
$progress->advance();
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_95percentile'));
$percentile95 = $this->computePercentile($datas, 95);
$progress->advance();
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.git_hash'));
$gitHash = $this->getGitHash();
$progress->advance();
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.yaml'));
$yaml = $this->generateYaml($gitHash, $runs, $url, $avg, $median, $percentile95);
$progress->advance();
$progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.done'));
$progress->finish();
$output->writeln('');
if ($file) {
file_put_contents($file, $yaml);
}
else {
$output->writeln($yaml);
}
}
/**
* @param \GuzzleHttp\ClientInterface $client
* @param $url
*
* @return array
*/
private function getData(ClientInterface $client, $url) {
/** @var \GuzzleHttp\Message\ResponseInterface $response */
$response = $client->get($url);
$token = $response->getHeader('X-Debug-Token');
/** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
$profiler = $this->getContainer()->get('profiler');
/** @var \Symfony\Component\HttpKernel\Profiler\Profile $profile */
$profile = $profiler->loadProfile($token);
/** @var \Drupal\webprofiler\DataCollector\TimeDataCollector $timeDataCollector */
$timeDataCollector = $profile->getCollector('time');
return new BenchmarkData(
$token,
$timeDataCollector->getMemory(),
$timeDataCollector->getDuration());
}
/**
* @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
*
* @return \Drupal\webprofiler\Command\BenchmarkData
*/
private function computeAvg($datas) {
$profiles = count($datas);
$totalTime = 0;
$totalMemory = 0;
foreach ($datas as $data) {
$totalTime += $data->getTime();
$totalMemory += $data->getMemory();
}
return new BenchmarkData(NULL, $totalMemory / $profiles, $totalTime / $profiles);
}
/**
* Computes percentile using The Nearest Rank method.
*
* @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
* @param $percentile
*
* @return \Drupal\webprofiler\Command\BenchmarkData
*
* @throws \Exception
*/
private function computePercentile($datas, $percentile) {
if ($percentile < 0 || $percentile > 100) {
throw new \Exception('Percentile has to be between 0 and 100');
}
$profiles = count($datas);
$n = ceil((($percentile / 100) * $profiles));
$index = (int) $n - 1;
$orderedTime = $datas;
$this->getOrderedDatas($orderedTime, 'Time');
$orderedMemory = $datas;
$this->getOrderedDatas($orderedMemory, 'Memory');
return new BenchmarkData(NULL, $orderedMemory[$index]->getMemory(), $orderedTime[$index]->getTime());
}
/**
* @return string
*/
private function getGitHash() {
try {
$process = new Process('git rev-parse HEAD');
$process->setTimeout(3600);
$process->run();
$git_hash = $process->getOutput();
} catch (\Exception $e) {
$git_hash = $this->trans('commands.webprofiler.benchmark.messages.not_git');
}
return $git_hash;
}
/**
* @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
* @param $string
*
* @return array
*/
private function getOrderedDatas(&$datas, $string) {
usort($datas, function ($a, $b) use ($string) {
$method = 'get' . $string;
if ($a->{$method} > $b->{$method}) {
return 1;
}
if ($a->{$method} < $b->{$method}) {
return -1;
}
return 0;
});
}
/**
* Rebuilds Drupal cache.
*/
protected function RebuildCache() {
require_once DRUPAL_ROOT . '/core/includes/utility.inc';
$kernelHelper = $this->getHelper('kernel');
$classLoader = $kernelHelper->getClassLoader();
$request = $kernelHelper->getRequest();
drupal_rebuild($classLoader, $request);
}
/**
* @param $gitHash
* @param $runs
* @param $url
* @param \Drupal\webprofiler\Command\BenchmarkData $avg
* @param \Drupal\webprofiler\Command\BenchmarkData $median
* @param \Drupal\webprofiler\Command\BenchmarkData $percentile95
*
* @return string
*/
private function generateYaml($gitHash, $runs, $url, BenchmarkData $avg, BenchmarkData $median, BenchmarkData $percentile95) {
$yaml = Yaml::dump([
'date' => date($this->trans('commands.webprofiler.list.rows.time'), time()),
'git_commit' => $gitHash,
'number_of_runs' => $runs,
'url' => $url,
'results' => [
'average' => [
'time' => sprintf('%.0f ms', $avg->getTime()),
'memory' => sprintf('%.1f MB', $avg->getMemory() / 1024 / 1024),
],
'median' => [
'time' => sprintf('%.0f ms', $median->getTime()),
'memory' => sprintf('%.1f MB', $median->getMemory() / 1024 / 1024),
],
'95_percentile' => [
'time' => sprintf('%.0f ms', $percentile95->getTime()),
'memory' => sprintf('%.1f MB', $percentile95->getMemory() / 1024 / 1024),
],
],
]);
return $yaml;
}
/**
* {@inheritdoc}
*/
public function showMessage($output, $message, $type = 'info') {
}
}
@@ -0,0 +1,57 @@
<?php
namespace Drupal\webprofiler\Command;
/**
* Class BenchmarkData
*/
class BenchmarkData {
/**
* @var
*/
private $token;
/**
* @var
*/
private $memory;
/**
* @var
*/
private $time;
/**
* @param $token
* @param $memory
* @param $time
*/
public function __construct($token, $memory, $time) {
$this->token = $token;
$this->memory = $memory;
$this->time = $time;
}
/**
* @return mixed
*/
public function getToken() {
return $this->token;
}
/**
* @return mixed
*/
public function getMemory() {
return $this->memory;
}
/**
* @return mixed
*/
public function getTime() {
return $this->time;
}
}
@@ -0,0 +1,151 @@
<?php
namespace Drupal\webprofiler\Command;
use Drupal\Console\Command\ContainerAwareCommand;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\webprofiler\Profiler\Profiler;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class ExportCommand
*/
class ExportCommand extends ContainerAwareCommand {
/** @var string */
private $filename;
/**
* {@inheritdoc}
*/
protected function configure() {
$this
->setName('webprofiler:export')
->setDescription($this->trans('commands.webprofiler.export.description'))
->addArgument('id', InputArgument::OPTIONAL, $this->trans('commands.webprofiler.export.arguments.id'))
->addOption('directory', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.export.options.directory'), '/tmp');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$id = $input->getArgument('id');
$directory = $input->getOption('directory');
/** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
$profiler = $this->getContainer()->get('profiler');
try {
if ($id) {
$this->filename = $this->exportSingle($profiler, $id, $directory);
}
else {
$this->filename = $this->exportAll($profiler, $directory, $output);
}
} catch (\Exception $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>');
}
}
/**
* Exports a single profile.
*
* @param \Drupal\webprofiler\Profiler\Profiler $profiler
* @param int $id
* @param string $directory
*
* @return string
*
* @throws \Exception
*/
private function exportSingle(Profiler $profiler, $id, $directory) {
$profile = $profiler->loadProfile($id);
if ($profile) {
$data = $profiler->export($profile);
$filename = $directory . DIRECTORY_SEPARATOR . $id . '.txt';
if (file_put_contents($filename, $data) === FALSE) {
throw new \Exception(sprintf(
$this->trans('commands.webprofiler.export.messages.error_writing'),
$filename));
}
}
else {
throw new \Exception(sprintf(
$this->trans('commands.webprofiler.export.messages.error_no_profile'),
$id));
}
return $filename;
}
/**
* Exports all stored profiles (cap limit at 1000 items).
*
* @param \Drupal\webprofiler\Profiler\Profiler $profiler
* @param string $directory
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return string
*/
private function exportAll(Profiler $profiler, $directory, $output) {
$filename = $directory . DIRECTORY_SEPARATOR . 'profiles_' . time() . '.tar.gz';
$archiver = new ArchiveTar($filename, 'gz');
$profiles = $profiler->find(NULL, NULL, 1000, NULL, '', '');
$progress = new ProgressBar($output, count($profiles) + 2);
$progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
$files = [];
$progress->start();
$progress->setMessage($this->trans('commands.webprofiler.export.progress.exporting'));
foreach ($profiles as $profile) {
$data = $profiler->export($profiler->loadProfile($profile['token']));
$profileFilename = $directory . "/{$profile['token']}.txt";
file_put_contents($profileFilename, $data);
$files[] = $profileFilename;
$progress->advance();
}
$progress->setMessage($this->trans('commands.webprofiler.export.progress.archive'));
$archiver->createModify($files, '', $directory);
$progress->advance();
$progress->setMessage($this->trans('commands.webprofiler.export.progress.delete_tmp'));
foreach ($files as $file) {
unlink($file);
}
$progress->advance();
$progress->setMessage($this->trans('commands.webprofiler.export.progress.done'));
$progress->finish();
$output->writeln('');
$output->writeln(sprintf(
$this->trans('commands.webprofiler.export.messages.exported_count'),
count($profiles)));
return $filename;
}
/**
* {@inheritdoc}
*/
public function showMessage($output, $message, $type = 'info') {
if (!$this->filename) {
return;
}
$completeMessageKey = 'commands.webprofiler.export.messages.success';
$completeMessage = sprintf($this->trans($completeMessageKey), $this->filename);
if ($completeMessage != $completeMessageKey) {
parent::showMessage($output, $completeMessage);
}
}
}
@@ -0,0 +1,73 @@
<?php
namespace Drupal\webprofiler\Command;
use Drupal\Console\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class ListCommand
*/
class ListCommand extends ContainerAwareCommand {
/**
* {@inheritdoc}
*/
protected function configure() {
$this
->setName('webprofiler:list')
->setDescription($this->trans('commands.webprofiler.list.description'))
->addOption('ip', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.ip'), NULL)
->addOption('url', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.url'), NULL)
->addOption('method', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.method'), NULL)
->addOption('limit', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.limit'), 10);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$ip = $input->getOption('ip');
$url = $input->getOption('url');
$method = $input->getOption('method');
$limit = $input->getOption('limit');
/** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
$profiler = $this->getContainer()->get('profiler');
$profiles = $profiler->find($ip, $url, $limit, $method, '', '');
$rows = [];
foreach ($profiles as $profile) {
$row = [];
$row[] = $profile['token'];
$row[] = $profile['ip'];
$row[] = $profile['method'];
$row[] = $profile['url'];
$row[] = date($this->trans('commands.webprofiler.list.rows.time'), $profile['time']);
$rows[] = $row;
}
$table = new Table($output);
$table
->setHeaders([
$this->trans('commands.webprofiler.list.header.token'),
$this->trans('commands.webprofiler.list.header.ip'),
$this->trans('commands.webprofiler.list.header.method'),
$this->trans('commands.webprofiler.list.header.url'),
$this->trans('commands.webprofiler.list.header.time'),
])
->setRows($rows);
$table->render();
}
/**
* {@inheritdoc}
*/
public function showMessage($output, $message, $type = 'info') {
}
}
@@ -0,0 +1,33 @@
<?php
namespace Drupal\webprofiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Class DecoratorPass.
*/
class DecoratorPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
// Builds a decorator around plugin.manager.mail.
$definition = $container->findDefinition('plugin.manager.mail');
$definition->setPublic(FALSE);
$container->setDefinition('webprofiler.debug.plugin.manager.mail.default', $definition);
$container->register('plugin.manager.mail', 'Drupal\webprofiler\Mail\MailManagerWrapper')
->addArgument(new Reference('container.namespaces'))
->addArgument(new Reference('cache.discovery'))
->addArgument(new Reference('module_handler'))
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('logger.factory'))
->addArgument(new Reference('string_translation'))
->addArgument(new Reference('webprofiler.debug.plugin.manager.mail.default'))
->addArgument(new Reference('webprofiler.mail'))
->setProperty('_serviceId', 'plugin.manager.mail');
}
}
@@ -0,0 +1,30 @@
<?php
namespace Drupal\webprofiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Class EventPass.
*/
class EventPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$definition = $container->findDefinition('http_kernel.basic');
$definition->replaceArgument(1, new Reference('webprofiler.debug.controller_resolver'));
// Replace the regular event_dispatcher service with the debug one.
$definition = $container->findDefinition('event_dispatcher');
$definition->setPublic(FALSE);
$container->setDefinition('webprofiler.debug.event_dispatcher.default', $definition);
$container->register('event_dispatcher', 'Drupal\webprofiler\TraceableEventDispatcher')
->addArgument(new Reference('webprofiler.debug.event_dispatcher.default'))
->addArgument(new Reference('stopwatch'))
->setProperty('_serviceId', 'event_dispatcher');
}
}
@@ -0,0 +1,64 @@
<?php
namespace Drupal\webprofiler\Compiler;
use Drupal\Core\StreamWrapper\PublicStream;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Class ProfilerPass
*/
class ProfilerPass implements CompilerPassInterface {
/**
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*
* @throws \InvalidArgumentException
*/
public function process(ContainerBuilder $container) {
// configure the profiler service
if (FALSE === $container->hasDefinition('profiler')) {
return;
}
$definition = $container->getDefinition('profiler');
$collectors = new \SplPriorityQueue();
$order = PHP_INT_MAX;
foreach ($container->findTaggedServiceIds('data_collector') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$template = NULL;
if (isset($attributes[0]['template'])) {
if (!isset($attributes[0]['id'])) {
throw new \InvalidArgumentException(sprintf('Data collector service "%s" must have an id attribute in order to specify a template', $id));
}
if (!isset($attributes[0]['title'])) {
throw new \InvalidArgumentException(sprintf('Data collector service "%s" must have a title attribute', $id));
}
$template = [
$attributes[0]['id'],
$attributes[0]['template'],
$attributes[0]['title']
];
}
$collectors->insert([$id, $template], [-$priority, --$order]);
}
$templates = [];
foreach ($collectors as $collector) {
$definition->addMethodCall('add', [new Reference($collector[0])]);
$templates[$collector[0]] = $collector[1];
}
$container->setParameter('data_collector.templates', $templates);
// set parameter to store the public folder path
$path = 'file:' . DRUPAL_ROOT . '/' . PublicStream::basePath() . '/profiler';
$container->setParameter('data_collector.storage', $path);
}
}
@@ -0,0 +1,110 @@
<?php
namespace Drupal\webprofiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraph;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
/**
* Class ServicePass
*/
class ServicePass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (FALSE === $container->hasDefinition('webprofiler.services')) {
return;
}
$definition = $container->getDefinition('webprofiler.services');
$graph = $container->getCompiler()->getServiceReferenceGraph();
$definition->addMethodCall('setServices', [$this->extractData($container, $graph)]);
}
/**
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param \Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraph $graph
*
* @return array
*/
private function extractData(ContainerBuilder $container, ServiceReferenceGraph $graph) {
$data = [];
foreach ($container->getDefinitions() as $id => $definition) {
$inEdges = [];
$outEdges = [];
if ($graph->hasNode($id)) {
$node = $graph->getNode($id);
/** @var \Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge $edge */
foreach ($node->getInEdges() as $edge) {
/** @var \Symfony\Component\DependencyInjection\Reference $edgeValue */
$edgeValue = $edge->getValue();
$inEdges[] = [
'id' => $edge->getSourceNode()->getId(),
'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL,
'strict' => $edgeValue ? $edgeValue->isStrict() : NULL,
];
}
/** @var \Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge $edge */
foreach ($node->getOutEdges() as $edge) {
/** @var \Symfony\Component\DependencyInjection\Reference $edgeValue */
$edgeValue = $edge->getValue();
$outEdges[] = [
'id' => $edge->getDestNode()->getId(),
'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL,
'strict' => $edgeValue ? $edgeValue->isStrict() : NULL,
];
}
}
if ($definition instanceof Definition) {
$class = $definition->getClass();
try {
$reflectedClass = new \ReflectionClass($class);
$file = $reflectedClass->getFileName();
} catch (\ReflectionException $e) {
$file = NULL;
}
$tags = $definition->getTags();
$public = $definition->isPublic();
$synthetic = $definition->isSynthetic();
}
else {
$id = $definition->__toString();
$class = NULL;
$file = NULL;
$tags = [];
$public = NULL;
$synthetic = NULL;
}
$data[$id] = [
'inEdges' => $inEdges,
'outEdges' => $outEdges,
'value' => [
'class' => $class,
'file' => $file,
'id' => $id,
'tags' => $tags,
'public' => $public,
'synthetic' => $synthetic,
],
];
}
return $data;
}
}
@@ -0,0 +1,32 @@
<?php
namespace Drupal\webprofiler\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Class StoragePass
*/
class StoragePass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (FALSE === $container->hasDefinition('profiler.storage_manager')) {
return;
}
$definition = $container->getDefinition('profiler.storage_manager');
foreach ($container->findTaggedServiceIds('webprofiler_storage') as $id => $attributes) {
$definition->addMethodCall('addStorage', [
$id,
$attributes[0]['title'],
new Reference($id)
]);
}
}
}
@@ -0,0 +1,44 @@
<?php
namespace Drupal\webprofiler\Config;
use Drupal\Core\Config\ConfigFactory;
use Drupal\webprofiler\DataCollector\ConfigDataCollector;
/**
* Wraps a config factory to be able to figure out all used config files.
*/
class ConfigFactoryWrapper extends ConfigFactory {
/**
* @var \Drupal\webprofiler\DataCollector\ConfigDataCollector
*/
private $dataCollector;
/**
* {@inheritdoc}
*/
public function get($name) {
$result = parent::get($name);
$this->dataCollector->addConfigName($name);
return $result;
}
/**
* {@inheritdoc}
*/
public function loadMultiple(array $names) {
$result = parent::loadMultiple($names);
foreach (array_keys($result) as $name) {
$this->dataCollector->addConfigName($name);
}
return $result;
}
/**
* @param \Drupal\webprofiler\DataCollector\ConfigDataCollector $dataCollector
*/
public function setDataCollector(ConfigDataCollector $dataCollector) {
$this->dataCollector = $dataCollector;
}
}
@@ -0,0 +1,243 @@
<?php
namespace Drupal\webprofiler\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Url;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\webprofiler\Profiler\ProfilerStorageManager;
use Drupal\webprofiler\Profiler\TemplateManager;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\webprofiler\Profiler\Profiler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\Routing\RouterInterface;
/**
* Class DashboardController
*/
class DashboardController extends ControllerBase {
/**
* @var \Drupal\webprofiler\Profiler\Profiler
*/
private $profiler;
/**
* @var \Symfony\Cmf\Component\Routing\ChainRouter
*/
private $router;
/**
* @var \Drupal\webprofiler\Profiler\TemplateManager
*/
private $templateManager;
/**
* @var \Drupal\Core\Datetime\DateFormatter
*/
private $date;
/**
* @var \Drupal\webprofiler\Profiler\ProfilerStorageManager
*/
private $storageManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('profiler'),
$container->get('router'),
$container->get('template_manager'),
$container->get('date.formatter'),
$container->get('profiler.storage_manager')
);
}
/**
* Constructs a new WebprofilerController.
*
* @param \Drupal\webprofiler\Profiler\Profiler $profiler
* @param \Symfony\Component\Routing\RouterInterface $router
* @param \Drupal\webprofiler\Profiler\TemplateManager $templateManager
* @param \Drupal\Core\Datetime\DateFormatter $date
* @param \Drupal\webprofiler\Profiler\ProfilerStorageManager $storageManager
*/
public function __construct(Profiler $profiler, RouterInterface $router, TemplateManager $templateManager, DateFormatter $date, ProfilerStorageManager $storageManager) {
$this->profiler = $profiler;
$this->router = $router;
$this->templateManager = $templateManager;
$this->date = $date;
$this->storageManager = $storageManager;
}
/**
* Generates the dashboard page.
*
* @param Profile $profile
*
* @return array
*/
public function dashboardAction(Profile $profile) {
$this->profiler->disable();
$templateManager = $this->templateManager;
$templates = $templateManager->getTemplates($profile);
$panels = [];
$libraries = ['webprofiler/dashboard'];
$drupalSettings = [
'webprofiler' => [
'token' => $profile->getToken(),
'ide_link' => $this->config('webprofiler.config')->get('ide_link'),
'ide_link_remote' => $this->config('webprofiler.config')->get('ide_link_remote'),
'ide_link_local' => $this->config('webprofiler.config')->get('ide_link_local'),
'collectors' => [],
],
];
foreach ($templates as $name => $template) {
/** @var DrupalDataCollectorInterface $collector */
$collector = $profile->getCollector($name);
if ($collector->hasPanel()) {
$rendered = $template->renderBlock('panel', [
'token' => $profile->getToken(),
'name' => $name,
]);
$panels[] = [
'#theme' => 'webprofiler_panel',
'#panel' => $rendered,
];
$drupalSettings['webprofiler']['collectors'][] = [
'id' => $name,
'name' => $name,
'label' => $collector->getTitle(),
'summary' => $collector->getPanelSummary(),
'icon' => $collector->getIcon(),
];
$libraries = array_merge($libraries, $collector->getLibraries());
$drupalSettings['webprofiler'] += $collector->getDrupalSettings();
}
}
$build = [];
$build['panels'] = [
'#theme' => 'webprofiler_dashboard',
'#profile' => $profile,
'#panels' => $panels,
'#spinner_path' => '/' . $this->moduleHandler()
->getModule('webprofiler')
->getPath() . '/images/searching.gif',
'#attached' => [
'drupalSettings' => $drupalSettings,
'library' => $libraries,
],
];
return $build;
}
/**
* Generates the list page.
*
* @param \Symfony\Component\HttpFoundation\Request $request
*
* @return array
*/
public function listAction(Request $request) {
$limit = $request->get('limit', 10);
$this->profiler->disable();
$ip = $request->query->get('ip');
$method = $request->query->get('method');
$url = $request->query->get('url');
$profiles = $this->profiler->find($ip, $url, $limit, $method, '', '');
$rows = [];
if (count($profiles)) {
foreach ($profiles as $profile) {
$row = [];
$row[] = $this->l($profile['token'], new Url('webprofiler.dashboard', ['profile' => $profile['token']]));
$row[] = $profile['ip'];
$row[] = $profile['method'];
$row[] = $profile['url'];
$row[] = $this->date->format($profile['time']);
$rows[] = $row;
}
}
else {
$rows[] = [
[
'data' => $this->t('No profiles found'),
'colspan' => 6,
],
];
}
$build = [];
$storage_id = $this->config('webprofiler.config')->get('storage');
$storage = $this->storageManager->getStorage($storage_id);
$build['resume'] = [
'#type' => 'inline_template',
'#template' => '<p>{{ message }}</p>',
'#context' => [
'message' => $this->t('Profiles stored with %storage service.', ['%storage' => $storage['title']]),
],
];
$build['filters'] = $this->formBuilder()
->getForm('Drupal\\webprofiler\\Form\\ProfilesFilterForm');
$build['table'] = [
'#type' => 'table',
'#rows' => $rows,
'#header' => [
$this->t('Token'),
[
'data' => $this->t('Ip'),
'class' => [RESPONSIVE_PRIORITY_LOW],
],
[
'data' => $this->t('Method'),
'class' => [RESPONSIVE_PRIORITY_LOW],
],
$this->t('Url'),
[
'data' => $this->t('Time'),
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
],
],
'#sticky' => TRUE,
];
return $build;
}
/**
* Exposes collector's data as JSON.
*
* @param \Symfony\Component\HttpKernel\Profiler\Profile $profile
* @param $collector
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function restCollectorAction(Profile $profile, $collector) {
$this->profiler->disable();
$data = $profile->getCollector($collector)->getData();
return new JsonResponse(['data' => $data]);
}
}
@@ -0,0 +1,95 @@
<?php
namespace Drupal\webprofiler\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\webprofiler\DataCollector\DatabaseDataCollector;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
/**
* Class DatabaseController
*/
class DatabaseController extends ControllerBase {
/**
* @var \Symfony\Component\HttpKernel\Profiler\Profiler
*/
private $profiler;
/**
* @var \Drupal\Core\Database\Connection
*/
private $database;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('profiler'),
$container->get('database')
);
}
/**
* Constructs a new WebprofilerController.
*
* @param \Symfony\Component\HttpKernel\Profiler\Profiler $profiler
* @param \Drupal\Core\Database\Connection $database
*/
public function __construct(Profiler $profiler, Connection $database) {
$this->profiler = $profiler;
$this->database = $database;
}
/**
* @param Profile $profile
* @param int $qid
*
* @return JsonResponse
*/
public function explainAction(Profile $profile, $qid) {
$query = $this->getQuery($profile, $qid);
$data = [];
$result = $this->database->query('EXPLAIN ' . $query['query'], (array) $query['args'])
->fetchAllAssoc('table');
$i = 1;
foreach ($result as $row) {
foreach ($row as $key => $value) {
$data[$i][$key] = $value;
}
$i++;
}
return new JsonResponse(['data' => $data]);
}
/**
* @param $profile ->getToken()
* @param int $qid
*
* @return array
*/
private function getQuery(Profile $profile, $qid) {
$this->profiler->disable();
$token = $profile->getToken();
if (!$profile = $this->profiler->loadProfile($token)) {
throw new NotFoundHttpException($this->t('Token @token does not exist.', ['@token' => $token]));
}
/** @var DatabaseDataCollector $databaseCollector */
$databaseCollector = $profile->getCollector('database');
$queries = $databaseCollector->getQueries();
$query = $queries[$qid];
return $query;
}
}
@@ -0,0 +1,108 @@
<?php
namespace Drupal\webprofiler\Controller;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\RendererInterface;
use Drupal\webprofiler\Profiler\Profiler;
use Drupal\webprofiler\Profiler\TemplateManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Profiler\Profile;
/**
* Class ToolbarController
*/
class ToolbarController extends ControllerBase {
/**
* @var \Drupal\webprofiler\Profiler\Profiler
*/
private $profiler;
/**
* @var \Drupal\webprofiler\Profiler\TemplateManager
*/
private $templateManager;
/**
* @var \Drupal\Core\Render\RendererInterface
*/
private $renderer;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('profiler'),
$container->get('template_manager'),
$container->get('renderer')
);
}
/**
* Constructs a new WebprofilerController.
*
* @param \Drupal\webprofiler\Profiler\Profiler $profiler
* @param \Drupal\webprofiler\Profiler\TemplateManager $templateManager
* @param \Drupal\Core\Render\RendererInterface $renderer
*/
public function __construct(Profiler $profiler, TemplateManager $templateManager, RendererInterface $renderer) {
$this->profiler = $profiler;
$this->templateManager = $templateManager;
$this->renderer = $renderer;
}
/**
* Generates the toolbar.
*
* @param Profile $profile
*
* @return array
*/
public function toolbarAction(Profile $profile) {
$this->profiler->disable();
$templates = $this->templateManager->getTemplates($profile);
$rendered = '';
foreach ($templates as $name => $template) {
$rendered .= $template->renderBlock('toolbar', [
'collector' => $profile->getcollector($name),
'token' => $profile->getToken(),
'name' => $name
]);
}
$toolbar = [
'#theme' => 'webprofiler_toolbar',
'#toolbar' => $rendered,
'#token' => $profile->getToken(),
];
return new Response($this->renderer->render($toolbar));
}
/**
* @param \Symfony\Component\HttpKernel\Profiler\Profile $profile
* @param \Symfony\Component\HttpFoundation\Request $request
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function savePerformanceTimingAction(Profile $profile, Request $request) {
$this->profiler->disable();
$data = Json::decode($request->getContent());
/** @var $collector */
$collector = $profile->getCollector('performance_timing');
$collector->setData($data);
$this->profiler->updateProfile($profile);
return new JsonResponse(['success' => TRUE]);
}
}
@@ -0,0 +1,107 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Component\Utility\NestedArray;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Collects data about the used assets (CSS/JS).
*/
class AssetsDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* Constructs a AssetDataCollector object.
*
* @param string $root
* The app root.
*/
public function __construct($root) {
$this->root = $root;
$this->data['js'] = [];
$this->data['css'] = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['assets']['installation_path'] = $this->root . '/';
}
/**
* @param $jsAsset
*/
public function addJsAsset($jsAsset) {
$this->data['js'] = NestedArray::mergeDeepArray([
$jsAsset,
$this->data['js']
]);
}
/**
* @param $cssAsset
*/
public function addCssAsset($cssAsset) {
$this->data['css'] = NestedArray::mergeDeepArray([
$cssAsset,
$this->data['css']
]);
}
/**
* Twig callback to return the amount of CSS files.
*/
public function getCssCount() {
return count($this->data['css']);
}
/**
* Twig callback to return the amount of JS files.
*/
public function getJsCount() {
return count($this->data['js']) - 1;
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'assets';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Assets');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Total: @count', ['@count' => ($this->getCssCount() + $this->getJsCount())]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxQUE0NEI2NTlCQTkxMUUzQkFDRjg2NUVCQ0NFNTcwQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxQUE0NEI2NjlCQTkxMUUzQkFDRjg2NUVCQ0NFNTcwQiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjFBQTQ0QjYzOUJBOTExRTNCQUNGODY1RUJDQ0U1NzBCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjFBQTQ0QjY0OUJBOTExRTNCQUNGODY1RUJDQ0U1NzBCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+C1mVdgAAAktJREFUeNrUlk+I6VEUx48/4SVKpkgiCywkK8ksWVhYSFm9hYXFqxfbt2KnLJS9hbKQhZXtFMVmUrKYkZmslPxLSvnvYZx37+2NTDPP03ss5tTpcu/xued77rm/Hw4iwqWNC1cwzv8CPlJ6lUypfSf+k256Ad8R/0HlL4l/uWCSL5zfO/xTLTkczrvx9aDwU7QUX61Ww/Pz88WAdrv9Oplyzz0UPp8PIpEIzrnWJ6EUJBAI2DgYDOD+/h7EYjFwuadz4X80SUFCoRB6vR6Mx2Mwm82QTqfh4eEBNBoN3NzcgFQqhdVqBbvd7s+ZUlkUJpFIoN1uQyQSAYvFAqlUiq2Xy2VwuVwQCoXAZrNBMpmE6XTK4nk83lsqOX0ki0h2xUajgYFAAJVKJRoMBoxGo9hqtXA4HCKNq1ar+Pj4iMFgEFUqFer1egyHw9jv93GxWDCOyWTCA5RaLBZjd9jhcGCz2cRXKxQKqNVqcTQaHeZKpRLqdDoWf3d3h6QMB+hB/nK5BL/fD4lEAsiPmUSfzwf1eh1qtRoYjUaQy+WQz+eBbAoej4fVN5PJgNVqZfV9J586lUBtMplgLpdDEoyktuj1ejEej7M1p9OJbrcbi8UirtdrNjebzQ6MN/KPfT6f436/Z3XudDp4e3uLlUqFQej37XbL5B7DjqEfthQBAgGzfpTJZJDNZpn0zWbDRgI/eQlOdjGFU1coFIfHGu3Lv90qbrfbXRFpJ4POAVGjh/r09PRCP38l3r3Q62RA/NtV3qb8T/Nn4irQXwIMANMNuV/Q8qbhAAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,149 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\block\Entity\Block;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\webprofiler\Entity\EntityDecorator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class BlocksDataCollector
*/
class BlocksDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
private $entityManager;
/**
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityManager
*/
public function __construct(EntityTypeManagerInterface $entityManager) {
$this->entityManager = $entityManager;
$this->data['blocks']['loaded'] = [];
$this->data['blocks']['rendered'] = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$storage = $this->entityManager->getStorage('block');
$loaded = $this->entityManager->getLoaded('config', 'block');
$rendered = $this->entityManager->getRendered('block');
if ($loaded) {
$this->data['blocks']['loaded'] = $this->getBlocksData($loaded, $storage);
}
if ($rendered) {
$this->data['blocks']['rendered'] = $this->getBlocksData($rendered, $storage);
}
}
/**
* @return array
*/
public function getRenderedBlocks() {
return $this->data['blocks']['rendered'];
}
/**
* @return int
*/
public function getRenderedBlocksCount() {
return count($this->getRenderedBlocks());
}
/**
* @return array
*/
public function getLoadedBlocks() {
return $this->data['blocks']['loaded'];
}
/**
* @return int
*/
public function getLoadedBlocksCount() {
return count($this->getLoadedBlocks());
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'blocks';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Blocks');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Loaded: @loaded, rendered: @rendered', [
'@loaded' => $this->getLoadedBlocksCount(),
'@rendered' => $this->getRenderedBlocksCount()
]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowNDgwMTE3NDA3MjA2ODExOEY2MkNCNjI0NDY3NzkwRCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQjg5OTA4OEYwQTgxMUUzQkJDRThFQjA5Q0E1REFCRCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQjg5OTA4N0YwQTgxMUUzQkJDRThFQjA5Q0E1REFCRCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDM4MDExNzQwNzIwNjgxMTg3MUZDQ0I0RjY1RTlEM0IiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDQ4MDExNzQwNzIwNjgxMThGNjJDQjYyNDQ2Nzc5MEQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz68h+kGAAAAWElEQVR42mL8//8/A7UBEwMNwAg3lAVEODg4UCW2Dhw4wDgapgyMtEj8eCMKFPCkyI1GFCJMiUnQVDUUX0SNllK4wxRf+JAdUeQUfaMRRd+ib4SHKUCAAQAMcyf8vLAstgAAAABJRU5ErkJggg==';
}
/**
* @param $decorator
* @param $storage
*
* @return array
*/
private function getBlocksData(EntityDecorator $decorator, EntityStorageInterface $storage) {
$blocks = [];
/** @var \Drupal\block\BlockInterface $block */
foreach ($decorator->getEntities() as $block) {
/** @var Block $entity */
if ($entity = $storage->load($block->get('id'))) {
$route = '';
if ($entity->hasLinkTemplate('edit-form')) {
$route = $entity->toUrl('edit-form')->toString();
}
$id = $block->get('id');
$blocks[$id] = [
'id' => $id,
'region' => $block->getRegion(),
'status' => $block->get('status'),
'theme' => $block->getTheme(),
'plugin' => $block->get('plugin'),
'settings' => $block->get('settings'),
'route' => $route,
];
}
}
return $blocks;
}
}
@@ -0,0 +1,172 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Collects the used cache bins and cache CIDs.
*/
class CacheDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
const WEBPROFILER_CACHE_HIT = 'bin_cids_hit';
const WEBPROFILER_CACHE_MISS = 'bin_cids_miss';
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
}
/**
*
*/
public function __construct() {
$this->data['total'][CacheDataCollector::WEBPROFILER_CACHE_HIT] = 0;
$this->data['total'][CacheDataCollector::WEBPROFILER_CACHE_MISS] = 0;
$this->data['cache'] = [];
}
/**
* Registers a cache get on a specific cache bin.
*
* @param $cache
*/
public function registerCacheHit($bin, $cache) {
$current = isset($this->data['cache'][$bin][$cache->cid]) ? $this->data['cache'][$bin][$cache->cid] : NULL;
if (!$current) {
$current = $cache;
$current->{CacheDataCollector::WEBPROFILER_CACHE_HIT} = 0;
$current->{CacheDataCollector::WEBPROFILER_CACHE_MISS} = 0;
$this->data['cache'][$bin][$cache->cid] = $current;
}
$current->{CacheDataCollector::WEBPROFILER_CACHE_HIT}++;
$this->data['total'][CacheDataCollector::WEBPROFILER_CACHE_HIT]++;
}
/**
* Registers a cache get on a specific cache bin.
*
* @param $bin
* @param $cid
*/
public function registerCacheMiss($bin, $cid) {
$current = isset($this->data['cache'][$bin][$cid]) ?
$this->data['cache'][$bin][$cid] : NULL;
if (!$current) {
$current = new \StdClass();
$current->{CacheDataCollector::WEBPROFILER_CACHE_HIT} = 0;
$current->{CacheDataCollector::WEBPROFILER_CACHE_MISS} = 0;
$this->data['cache'][$bin][$cid] = $current;
}
$current->{CacheDataCollector::WEBPROFILER_CACHE_MISS}++;
$this->data['total'][CacheDataCollector::WEBPROFILER_CACHE_MISS]++;
}
/**
* Callback to return the total amount of requested cache CIDS.
*
* @param string $type
*
* @return int
*/
public function getCacheCidsCount($type) {
return $this->data['total'][$type];
}
/**
* Callback to return the total amount of hit cache CIDS.
*
* @return int
*/
public function getCacheHitsCount() {
return $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_HIT);
}
/**
* Callback to return the total amount of miss cache CIDS.
*
* @return int
*/
public function getCacheMissesCount() {
return $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_MISS);
}
/**
* Callback to return the total amount of hit cache CIDs keyed by bin.
*
* @param $type
*
* @return array
*/
public function cacheCids($type) {
$hits = [];
foreach ($this->data['cache'] as $bin => $caches) {
$hits[$bin] = 0;
foreach ($caches as $cid => $cache) {
$hits[$bin] += $cache->{$type};
}
}
return $hits;
}
/**
* Callback to return hit cache CIDs keyed by bin.
*
* @return array
*/
public function getCacheHits() {
return $this->cacheCids(CacheDataCollector::WEBPROFILER_CACHE_HIT);
}
/**
* Callback to return miss cache CIDs keyed by bin.
*
* @return array
*/
public function getCacheMisses() {
return $this->cacheCids(CacheDataCollector::WEBPROFILER_CACHE_MISS);
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'cache';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Cache');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Hit: @cache_hit, miss: @cache_miss', [
'@cache_hit' => $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_HIT),
'@cache_miss' => $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_MISS),
]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2Njc3QTVEQTkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2Njc3QTVEQjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRGQTVBQzYxOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRGQTVBQzYyOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BsBwAAAAAJtJREFUeNpi/P//PwM1ARMDlcEIMdDBweEZjM0IihSgwEx8Gg4cOJCOrhGHOimqe5kF2QVYvDITl0vQvQwTo4oLkS1gQrPpPwiTEBkY6pnwKJ5JyOskJRvkcMUVxjgjhRhDsUUGSQZu3rwZb1j6+voyjhYOI9VAFmKTBTC3oMsTbyAx+RndAqxejo2NJdmL6HoYR6vRwWcgQIABAOn0PsqqgQzcAAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,68 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Provides a datacollector to show all requested configs.
*/
class ConfigDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
}
/**
* Registers a new requested config name.
*
* @param string $name
* The name of the config.
*/
public function addConfigName($name) {
$this->data['config_names'][$name] = isset($this->data['config_names'][$name]) ? $this->data['config_names'][$name] + 1 : 1;
}
/**
* Callback to display the config names.
*/
public function getConfigNames() {
return $this->data['config_names'];
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'config';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Config');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Total: @count', ['@count' => count($this->getConfigNames())]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABsAAAAcCAYAAACQ0cTtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2Njc3QTVFMjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2Njc3QTVFMzkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY2NzdBNUUwOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY2NzdBNUUxOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+f0Re8gAAA3RJREFUeNrsVk9ME1kY/82fTqfQf5ROW2gpuKCioHbdEDeBxITdxE08eOHkiQOr8cZhNx7k4MGDmpAQxTtHPBgPXjCbeLDE/XtglSUGGyhxgBYLdNpSOu3MPN+MwUSQP1HUZONv8st7897M9833fb837zGEEHwusPiM+P86AxjmEqVKSfaBGuWvlHgf6cUUb9+65WhsbPzo706n0/j5wgWddgOUK1vSKEnSvjgyEQwGQW1xtBv+4jXjc7kcBgcH981gJpPZ3tmnjoa7NgOSXwYK2U+bRu76y73XLBaLIRQKvXe8paVlZ0c35L2ncWho6K3B/v5+JBIJOJ3Ot+PmfV9fnzWvcyJWpeNQ/O0oH7oILhK7Q4cnKR9RjlEWrCVtF0Xy/alTW5z19PRgbGwMo6OjlmHToekolUpheHgYhYL1Pso2F+a+OQd/cwTftdlRH9QhujPGconJx5/VyRMvpD+IWrxCa5bmeJ6/GolEtjibmppCuVyGz+dDd3e3ZTwajWJgYMAatyJiBcyGf0J7pwcdzQEcbktBnW8Fr3NMXcOSeLRpKSCw2cD0tNgEQ3+0q0DGx8ettre3FyMjI+/MrYhReKMcgpII1jePdZVBWQXcBxJYfOFGzsjjYGsyErYvdkLJnN1V+hMTE1Zrpm+jv4FVxoemhjISyWqsKi68JDWwcSUsPhWQSQBSdA4Lz+vh92j18rRyZs/rbLMjE0WaTSJlsab+h1LqNES7BqLpYB2vIPwYx/zjNqwlDsBR8VaRwlzzrs42FGlGtgWaBiVZjzLngVChNYQBGATas5OoKCvQSB6k4x64uXYGTwyW322dmao01WgqczNEujMRRYSjqhUOhwAbz4Klu4nB8yhyEtZyPEqh36BquSLYzpltBdLV1WWJwmzN6EzZb4aPUZCfVRHyeiA53Qi6XKhzuxGurUFD8Qz8Xga1BQ+y8SNLEKoe8jupcEOJ26HWSGN5VYYiJ3Ei9i14GpaNezOn8gIcjISn/3Sks8n2PyFqDzhd1y/XeL02URQ/4ExhwFOSMbtux0qxgoDHBb+7GiLPG9l8ce2vyYI8nQjHMXn/Fzy+uWzu1Ofpeze32/D29scVgLpjwMEfSrSVUe1PoUK/Yib+N/69+zuy8gJ9apGxzgcfCkLMmjspq3b4qZsH03XKPPP1kLofeC3AAMaxXzYck+ZeAAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,253 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class DatabaseDataCollector
*/
class DatabaseDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Database\Connection
*/
private $database;
/**
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
private $configFactory;
/**
* @param \Drupal\Core\Database\Connection $database
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
*/
public function __construct(Connection $database, ConfigFactoryInterface $configFactory) {
$this->database = $database;
$this->configFactory = $configFactory;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$connections = [];
foreach (Database::getAllConnectionInfo() as $key => $info) {
$database = Database::getConnection('default', $key);
$connections[$key] = $database->getLogger()->get('webprofiler');
}
$this->data['connections'] = array_keys($connections);
$data = [];
foreach ($connections as $key => $queries) {
foreach ($queries as $query) {
// Remove caller args.
unset($query['caller']['args']);
// Remove query args element if empty.
if (empty($query['args'])) {
unset($query['args']);
}
// Save time in milliseconds.
$query['time'] = $query['time'] * 1000;
$query['database'] = $key;
$data[] = $query;
}
}
$querySort = $this->configFactory->get('webprofiler.config')
->get('query_sort');
if ('duration' === $querySort) {
usort(
$data, [
"Drupal\\webprofiler\\DataCollector\\DatabaseDataCollector",
"orderQueryByTime",
]
);
}
$this->data['queries'] = $data;
$options = $this->database->getConnectionOptions();
// Remove password for security.
unset($options['password']);
$this->data['database'] = $options;
}
/**
* @return array
*/
public function getDatabase() {
return $this->data['database'];
}
/**
* @return int
*/
public function getQueryCount() {
return count($this->data['queries']);
}
/**
* @return array
*/
public function getQueries() {
return $this->data['queries'];
}
/**
* Returns the total execution time.
*
* @return float
*/
public function getTime() {
$time = 0;
foreach ($this->data['queries'] as $query) {
$time += $query['time'];
}
return $time;
}
/**
* Returns a color based on the number of executed queries.
*
* @return string
*/
public function getColorCode() {
if ($this->getQueryCount() < 100) {
return 'green';
}
if ($this->getQueryCount() < 200) {
return 'yellow';
}
return 'red';
}
/**
* Returns the configured query highlight threshold.
*
* @return int
*/
public function getQueryHighlightThreshold() {
// When a profile is loaded from storage this object is deserialized and
// no constructor is called so we cannot use dependency injection.
return \Drupal::config('webprofiler.config')->get('query_highlight');
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'database';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Database');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Executed queries: @count', ['@count' => $this->getQueryCount()]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQRJREFUeNpi/P//PwM1ARMDlcGogZQDlpMnT7pxc3NbA9nhQKxOpL5rQLwJiPeBsI6Ozl+YBOOOHTv+AOllQNwtLS39F2owKYZ/gRq8G4i3ggxEToggWzvc3d2Pk+1lNL4fFAs6ODi8JzdS7mMRVyDVoAMHDsANdAPiOCC+jCQvQKqBQB/BDbwBxK5AHA3E/kB8nKJkA8TMQBwLxaBIKQbi70AvTADSBiSadwFXpCikpKQU8PDwkGTaly9fHFigkaKIJid4584dkiMFFI6jkTJII0WVmpHCAixZQEXWYhDeuXMnyLsVlEQKI45qFBQZ8eRECi4DBaAlDqle/8A48ip6gAADANdQY88Uc0oGAAAAAElFTkSuQmCC';
}
/**
* {@inheritdoc}
*/
public function getLibraries() {
return [
'webprofiler/database',
];
}
/**
* {@inheritdoc}
*/
public function getData() {
$data = $this->data;
$conn = Database::getConnection();
foreach ($data['queries'] as &$query) {
$explain = TRUE;
$type = 'select';
if (strpos($query['query'], 'INSERT') !== FALSE) {
$explain = FALSE;
$type = 'insert';
}
if (strpos($query['query'], 'UPDATE') !== FALSE) {
$explain = FALSE;
$type = 'update';
}
if (strpos($query['query'], 'CREATE') !== FALSE) {
$explain = FALSE;
$type = 'create';
}
if (strpos($query['query'], 'DELETE') !== FALSE) {
$explain = FALSE;
$type = 'delete';
}
$query['explain'] = $explain;
$query['type'] = $type;
$quoted = [];
foreach ((array) $query['args'] as $key => $val) {
$quoted[$key] = is_null($val) ? 'NULL' : $conn->quote($val);
}
$query['query_args'] = strtr($query['query'], $quoted);
}
$data['query_highlight_threshold'] = $this->getQueryHighlightThreshold();
return $data;
}
/**
* @param $a
* @param $b
*
* @return int
*/
private function orderQueryByTime($a, $b) {
$at = $a['time'];
$bt = $b['time'];
if ($at == $bt) {
return 0;
}
return ($at < $bt) ? 1 : -1;
}
}
@@ -0,0 +1,110 @@
<?php
/**
* Contains \Drupal\webprofiler\DataCollector\DevelDataCollector;
*/
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class DevelDataCollector
*/
class DevelDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
*/
private $menuLinkTree;
/**
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menuLinkTree
*/
public function __construct(MenuLinkTreeInterface $menuLinkTree) {
$this->menuLinkTree = $menuLinkTree;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['links'] = $this->develMenuLinks();
}
/**
* @return string
*/
public function getLinks() {
return $this->data['links'];
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'devel';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Development');
}
/**
* {@inheritdoc}
*/
public function hasPanel() {
return FALSE;
}
/**
* @return array
* Array containing Devel Menu links
*/
protected function develMenuLinks() {
$parameters = new MenuTreeParameters();
$parameters->setMaxDepth(1)->onlyEnabledLinks();
$tree = $this->menuLinkTree->load('devel', $parameters);
$manipulators = array(
array('callable' => 'menu.default_tree_manipulators:checkAccess'),
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
);
$tree = $this->menuLinkTree->transform($tree, $manipulators);
$links = array();
foreach ($tree as $item) {
if ($item->access->isAllowed()) {
$links[] = array(
'title' => $item->link->getTitle(),
'description' => $item->link->getDescription(),
'url' => $item->link->getUrlObject()->toString(),
);
}
}
return $links;
}
/**
* Returns the collector icon in base64 format.
*
* @return string
* The collector icon.
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABYAAAAcCAYAAABlL09dAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2RTdCREU2NUVFQUUxMUU1QTc4Q0Q0OEU5RUY0N0YwMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2RTdCREU2NkVFQUUxMUU1QTc4Q0Q0OEU5RUY0N0YwMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZFN0JERTYzRUVBRTExRTVBNzhDRDQ4RTlFRjQ3RjAzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjZFN0JERTY0RUVBRTExRTVBNzhDRDQ4RTlFRjQ3RjAzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+6sNOKAAAAPVJREFUeNpi/P//PwMtABMDjcCowbQ3mNHe3h6XnBUQTwJiTRzy14E4+8CBAyexSbLgsZQbiI2BOBCH/HqoGgZSDYaBDXjk/uMzuBaLzeVI7FV4DN7n4OAAojvRxL+CwvgnkMFG5bj7BUoVv4lUfBWIrxGp9jcoKM4DsQABhZ+A2BPqs61AzEVA/XuQwT1AzElA4Q8g/gJN951EBN13kMFLgJiHgMJvQCwNxOxAvJwIg7+wEBnGv4D4M1TtLyIMBocxMQUyKExnAzEzEHMQof4/C5GxDHJh4qAp3VhpYC4rKCieAbEQlQ1+yzhamY4aTD+DAQIMAFv+MFaJEyYhAAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,134 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;
/**
* Class DrupalDataCollector
*/
class DrupalDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Routing\RedirectDestinationInterface
*/
private $redirectDestination;
/**
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
private $urlGenerator;
/**
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirectDestination
* @param \Drupal\Core\Routing\UrlGeneratorInterface $urlGenerator
*/
public function __construct(RedirectDestinationInterface $redirectDestination, UrlGeneratorInterface $urlGenerator) {
$this->redirectDestination = $redirectDestination;
$this->urlGenerator = $urlGenerator;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['version'] = Drupal::VERSION;
$this->data['profile'] = drupal_get_profile();
$this->data['config_url'] = (new Drupal\Core\Url('webprofiler.settings', [], ['query' => $this->redirectDestination->getAsArray()]))->toString();
try {
$process = new Process("git log -1 --pretty=format:'%H - %s (%ci)' --abbrev-commit");
$process->setTimeout(3600);
$process->mustRun();
$this->data['git_commit'] = $process->getOutput();
$process = new Process("git log -1 --pretty=format:'%h' --abbrev-commit");
$process->setTimeout(3600);
$process->mustRun();
$this->data['abbr_git_commit'] = $process->getOutput();
} catch (ProcessFailedException $e) {
$this->data['git_commit'] = $this->data['git_commit_abbr'] = NULL;
} catch (RuntimeException $e) {
$this->data['git_commit'] = $this->data['git_commit_abbr'] = NULL;
}
}
/**
* @return string
*/
public function getVersion() {
return $this->data['version'];
}
/**
* @return string
*/
public function getProfile() {
return $this->data['profile'];
}
/**
* @return string
*/
public function getConfigUrl() {
return $this->data['config_url'];
}
/**
* @return string
*/
public function getGitCommit() {
return $this->data['git_commit'];
}
/**
* @return string
*/
public function getAbbrGitCommit() {
return $this->data['abbr_git_commit'];
}
/**
* Returns the name of the collector.
*
* @return string The collector name
*
* @api
*/
public function getName() {
return 'drupal';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Drupal');
}
/**
* {@inheritdoc}
*/
public function hasPanel() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCRDU5MUVDNzVGRTkxMUUzODdBMEJDOEZFRjA2QUY5MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCRDU5MUVDODVGRTkxMUUzODdBMEJDOEZFRjA2QUY5MiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkJENTkxRUM1NUZFOTExRTM4N0EwQkM4RkVGMDZBRjkyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkJENTkxRUM2NUZFOTExRTM4N0EwQkM4RkVGMDZBRjkyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+iYsuIgAAAiNJREFUeNq8lj3KwkAQhhOxUPAnoIgWQiysbFLYG2/gEVJaegQ9QfQECh4g3kA9QbxBLMQ2KaxE2G/ffG5Yl/ytiguDJBn32Z15d2dUQojy0wGgDHQwGFjj8Vh7hxFyZIHNZtOt1+uuDPRtYK/Xs4rFIpyJDFQaiIm73a7NYMyq1arf7/dnWWApIN3VHBPzINHwHX5J4FxAKg6j0Wh4aSDREGb8TxqIXJVKJSIDY1Yul30RmgqE87swHkrDq2cCkYNareZ9AmPWarX2mUAk/hswZrgkUoGyIsm7y1ggDachnrNPrVKpEKSJBxZYnC+Xi/l4PL56T99uN/y8KDYCUpj2i2JRzONkWZYyGo0UXdeV4/GobDYb5Xw+J/obxv+mTqdT8rWTpND1ek3ihm3bRNO0F188O44T+UwmE+TQjBXNcDiciLD5fE7Shuu6xDTN0JdGgfi+//J9v98nA6EmqIoHYsJPxuFwIOKxiERDVxNQ4I4PNw1Ram6DIFAWiwUWG5uv+/2+E98V+IdOp7OiZzF6pitMhC2XyxDEfOKg1+t1ldnTtNtthxcB8sAGcgQRUbWGufM8LxQPfJBv3n+73Tq5qgVyibqWdYsASOUfLYyJJ679yKyHadDZbBYqkn8HMHaO3ydMl674rI8RayOACKUo/+l0SuAf12ZI9TRYLSZCJYk76GgbnyA9TxOlMpiqqrnuQhxk7igd8raJbKi/bvX/BBgANANSieJk+XMAAAAASUVORK5CYII=';
}
}
@@ -0,0 +1,115 @@
<?php
namespace Drupal\webprofiler\DataCollector;
/**
* Class DrupalDataCollectorTrait
*/
trait DrupalDataCollectorTrait {
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function hasPanel() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getLibraries() {
return [];
}
/**
* {@inheritdoc}
*/
public function getDrupalSettings() {
return [];
}
/**
* @return mixed
*/
public function getData() {
return $this->data;
}
/**
* @param $class
* @param $method
*
* @return array
*
* @throws \ReflectionException
*/
public function getMethodData($class, $method) {
$class = is_object($class) ? get_class($class) : $class;
$data = [];
try {
$reflectedMethod = new \ReflectionMethod($class, $method);
$data = [
'class' => $class,
'method' => $method,
'file' => $reflectedMethod->getFilename(),
'line' => $reflectedMethod->getStartLine(),
];
} catch (\ReflectionException $re) {
// TODO: handle the exception.
} finally {
return $data;
}
}
/**
* @param $value
*
* @return int|string
*/
private function convertToBytes($value) {
if ('-1' === $value) {
return -1;
}
$value = strtolower($value);
$max = strtolower(ltrim($value, '+'));
if (0 === strpos($max, '0x')) {
$max = intval($max, 16);
}
elseif (0 === strpos($max, '0')) {
$max = intval($max, 8);
}
else {
$max = intval($max);
}
switch (substr($value, -1)) {
case 't':
$max *= 1024;
break;
case 'g':
$max *= 1024;
break;
case 'm':
$max *= 1024;
break;
case 'k':
$max *= 1024;
break;
}
return $max;
}
}
@@ -0,0 +1,90 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpKernel\DataCollector\EventDataCollector as BaseEventDataCollector;
/**
* Class EventsDataCollector
*/
class EventsDataCollector extends BaseEventDataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @return int
*/
public function getCalledListenersCount() {
return count($this->getCalledListeners());
}
/**
* @return int
*/
public function getNotCalledListenersCount() {
return count($this->getNotCalledListeners());
}
/**
* {@inheritdoc}
*/
public function setCalledListeners(array $listeners) {
$listeners = $this->computePriority($listeners);
$this->data['called_listeners'] = $listeners;
}
/**
* Adds the priority value to the $listeners array.
*
* @param array $listeners
* @return array
*/
private function computePriority(array $listeners) {
foreach ($listeners as &$listener) {
foreach ($listener['class']::getSubscribedEvents() as $event => $methods) {
if (is_string($methods)) {
$methods = [[$methods], 0];
}
else {
if (is_string($methods[0])) {
$methods = [$methods];
}
}
foreach ($methods as $method) {
if ($listener['event'] === $event) {
if ($listener['method'] === $method[0]) {
$listener['priority'] = isset($method[1]) ? $method[1] : 0;
}
}
}
}
}
return $listeners;
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Events');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Called listeners: @listeners', ['@listeners' => count($this->getCalledListeners())]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABFJJREFUeNrkVVlIY2cY/RMTE81NMkkajUs1OBqkiVsjjAtStGrtSGyFjOjAQNVCKRb66ot9KrjgQx+FUgTBKkURbIfighWl4r6h44pajcZEo3ESTeKS9PzB2AyNZaD1qRcOem+S83/f+c53Lsvj8ZD/+mKTB7gehJTj+2d9fZ1MTk6S0NBQSW9vb97e3t7jmpqaXzIzM185HA7vd4KDg8nGxoaysbGxVCwWm/V6/aDL5TKlpKSQpKSkv5NyuVxyc3Mj7e7u/jw2NjYxJyfnMDIykmGz2UQgEBAWi0XcbjeRSqWhZWVl4v39fXVXV5cqNzf3exxmCNj+9fU1MzQ09JVWq32sUqmMu7u7QhwiDwoKIoeHh2R7e5twOByCwcrQhUShUJjz8vJkw8PDX5+fn8sDkvb3938YHR39rlAoNBoMBgGqtWxubnJRKbu9vZ20trZSQoJnvKioKMvZ2Rn/6urKmpqayvT19ekCks7NzaUnJyeboK0kPj7+cGZmJprH4zGnp6duEBFUTg4ODqjmIfPz87GQxoRnori4ODOKUPuTsnw+RRvPGIYJMZvNDNplYmJiLvPz839oamoSj4yMfAJNuRqN5mV9ff0fOPDF1NSUAt85lclkDkjnys7O/vGOlZLeQgjIgUggnmqHqmMqKip+z8jI8MAFnpKSkpXZ2dn38JkIUAFRQNjt/R2Xv09twBFwAGwClunp6efLy8tZdFgUW1tbiaOjo1/is9fUhcA+YL69fzvzSyQSEQZHfBJBT4J2Bf9qo9Rq9bxcLndeXl4STJrA8B4Mc/atN4pesAk5OTkh1PB0exYXF/kWi4UTFhZG+Hw+wZQJ5BDR7fEPIroYASu9uLggJpOJYO2I0+kkqI47Njb2MdzAKS4uXisvL5/FurIGBgaeYoDS1dVVsrKyQpaWlghsF7hS2IJERER4T4U/qckT4ccP6BYplco+rOcxqn0fZFqj0fgkLS3tV18m0EICktJV9F101xcWFj5Cu+HQ1YGNoeSXWGErpv8IwVOSlZXVh7xw0zy4V1MY3/uXWgetMzB8EZUHw7lKSEjgQ0MONLei2kcTExN5R0dHMehshw7x3umLRKI7YDhaDOSJ18hstq2qquobLMG30DKYkuzs7KggTa5Pf4p/rJReSCud1WplEBYuSMGrra39FG1ywsPDgwsLC+0YFoMAKi0qKupA5c57K0V1XjsdHx+/g6mXUksVFBS8wmF23FeMj48/w7PXiLsxePcG65qPDNCsra15XRCQFNP1AgRPMaA4aOvp6OjQ2O12cVtb20vE389YAHFLS0sO2vbYbLYQHKRHShEEy5ul+kIAe02Q5vy6urouTNyDV8VNT0/PBGzzxW1wRIHsM7T+W3V1tROvEE9lZeUCKlVgSfyD6S9SGsKdnZ1pOp3OkJ6efj04OPgTnmsAlv8PACXa/Q4L4UByuZqbm/UNDQ1vkLL+3+/9ByH9U4ABADscgvUMKuLiAAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,156 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Defines a data collector for the extension system.
*/
class ExtensionDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param string $root
* The app root.
*/
public function __construct(ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $root) {
$this->moduleHandler = $module_handler;
$this->themeHandler = $theme_handler;
$this->root = $root;
$this->data['drupal_extension']['modules'] = [];
$this->data['drupal_extension']['themes'] = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$modules = $this->moduleHandler->getModuleList();
$themes = $this->themeHandler->listInfo();
$this->data['drupal_extension']['count'] = count($modules) + count($themes);
$this->data['drupal_extension']['modules'] = $modules;
$this->data['drupal_extension']['themes'] = $themes;
$this->data['drupal_extension']['installation_path'] = $this->root . '/';
}
/**
* Returns the total number of active extensions.
*
* @return int
*/
public function getExtensionsCount() {
return isset($this->data['drupal_extension']['count']) ? $this->data['drupal_extension']['count'] : 0;
}
/**
* Returns the total number of active modules.
*
* @return int
*/
public function getModulesCount() {
return count($this->data['drupal_extension']['modules']);
}
/**
* Returns the total number of active themes.
*
* @return int
*/
public function getThemesCount() {
return count($this->data['drupal_extension']['themes']);
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'drupal_extension';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Extensions');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Total: @extensions', ['@extensions' => $this->getExtensionsCount()]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0RkE1QUM1RjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0RkE1QUM2MDkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRGQTVBQzVEOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRGQTVBQzVFOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+7LqYtwAAAUBJREFUeNpi/P//PwM1ARMDlcGogYPQQBYQ4eDgQIza/SClQNwIxA3okgcOHCDZhRVAfA6I64F4EjW8fBLqwn1AnAvE8aQaqAc1QAZJ7DMQRwPxayDuAGIBYgwUAeL1QHwWGm6XgLgUSf4FEPcAsQQQ+xBjICh8AmARBgSCQNwFxEFIatYCMagQcEHXzGhvb09s6bAdiL2gbHYg/oEm/x8Y00ykRAofEvsvzlgGmsoIpIWBWBaIlYD4Bg61B5DY/ED8HRrjIH2yINchh+E7IH4CxPeBuAAam8jgBBD3IfEtgZgTiPdC9T1BySloYCcQ20DTmTg0tuegWVICjZSNWLMeFgBKKsU45EDi9kA8H4ivEmsgNsAKxEXQBH0TiMtwFg5EgvnQXHIFmibfUGrgeaghTdBIxAoYR2u9EWAgQIABAKKeRzEX0gXIAAAAAElFTkSuQmCC';
}
/**
* {@inheritdoc}
*/
public function getData() {
$data = $this->data;
// Copy protected properties over public ones to
// let json_encode to find them.
$this->copyToPublic($data['drupal_extension']['modules']);
$this->copyToPublic($data['drupal_extension']['themes']);
return $data;
}
/**
* Copies protected properties to public ones.
*
* @param \Drupal\Core\Extension\Extension[] $extensions
*/
private function copyToPublic($extensions) {
foreach ($extensions as &$extension) {
$extension->public_type = $extension->getType();
$extension->public_name = $extension->getName();
$extension->public_path = $extension->getPath();
$extension->public_pathname = $extension->getPathname();
$extension->public_filename = $extension->getFilename();
$extension->public_extension_pathname = $extension->getExtensionPathname();
$extension->public_extension_filename = $extension->getExtensionFilename();
}
}
}
@@ -0,0 +1,81 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\webprofiler\Form\FormBuilderWrapper;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class FormsDataCollector
*/
class FormsDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\webprofiler\Form\FormBuilderWrapper
*/
private $formBuilder;
/**
* @param \Drupal\webprofiler\Form\FormBuilderWrapper $formBuilder
*/
public function __construct(FormBuilderWrapper $formBuilder) {
$this->formBuilder = $formBuilder;
$this->data['forms'] = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['forms'] = $this->formBuilder->getBuildForm();
}
/**
* @return array
*/
public function getForms() {
return $this->data['forms'];
}
/**
* @return array
*/
public function getFormsCount() {
return count($this->getForms());
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'forms';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Forms');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Rendered: @forms', ['@forms' => $this->getFormsCount()]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0RkE1QUM1QjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0RkE1QUM1QzkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRGQTVBQzU5OTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRGQTVBQzVBOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+I7130QAAAKRJREFUeNrsVVsKxCAMzIgH8897eDHxGP55LX+0RmjpYxHdyrLsNl9iknEyExA5Z5oZgibH9wPKVlJrnVNKRwZCkPceQ4AhhOpUjJFaeaXUBRjssrW2FvDZGINS2GV9AQb3rpvCvZWhc24rKpdDmp17P2MKjzIFEAD16vdDi73Xbx2/pelZItnz6oiusneB32b4is0Iw8eUfzBlOiCef/l2LAIMAGACWQCJ5bXFAAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,168 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\Http\HttpClientMiddleware;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Collects data about http calls during request.
*/
class HttpDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \GuzzleHttp\Client
*/
private $middleware;
/**
* @param \Drupal\webprofiler\Http\HttpClientMiddleware $middleware
*/
public function __construct(HttpClientMiddleware $middleware) {
$this->middleware = $middleware;
$this->data['completed'] = [];
$this->data['failed'] = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$completed = $this->middleware->getCompletedRequests();
$failed = $this->middleware->getFailedRequests();
foreach ($completed as $data) {
/** @var \GuzzleHttp\Psr7\Request $request */
$request = $data['request'];
/** @var \GuzzleHttp\Psr7\Response $response */
$response = $data['response'];
/** @var \GuzzleHttp\TransferStats $stats */
$stats = $request->stats;
$uri = $request->getUri();
$this->data['completed'][] = [
'request' => [
'method' => $request->getMethod(),
'uri' => [
'schema' => $uri->getScheme(),
'host' => $uri->getHost(),
'port' => $uri->getPort(),
'path' => $uri->getPath(),
'query' => $uri->getQuery(),
'fragment' => $uri->getFragment(),
],
'headers' => $request->getHeaders(),
'protocol' => $request->getProtocolVersion(),
'request_target' => $request->getRequestTarget(),
'stats' => [
'transferTime' => $stats->getTransferTime(),
'handlerStats' => $stats->getHandlerStats()
],
],
'response' => [
'phrase' => $response->getReasonPhrase(),
'status' => $response->getStatusCode(),
'headers' => $response->getHeaders(),
'protocol' => $response->getProtocolVersion(),
]
];
}
foreach ($failed as $data) {
/** @var \GuzzleHttp\Psr7\Request $request */
$request = $data['request'];
/** @var \GuzzleHttp\Psr7\Response $response */
$response = $data['response'];
$uri = $request->getUri();
$this->data['failed'][] = [
'request' => [
'method' => $request->getMethod(),
'uri' => [
'schema' => $uri->getScheme(),
'host' => $uri->getHost(),
'port' => $uri->getPort(),
'path' => $uri->getPath(),
'query' => $uri->getQuery(),
'fragment' => $uri->getFragment(),
],
'headers' => $request->getHeaders(),
'protocol' => $request->getProtocolVersion(),
'request_target' => $request->getRequestTarget(),
],
'response' => [
'phrase' => $response->getReasonPhrase(),
'status' => $response->getStatusCode(),
'headers' => $response->getHeaders(),
'protocol' => $response->getProtocolVersion(),
]
];
}
}
/**
* @return int
*/
public function getCompletedRequestsCount() {
return count($this->getCompletedRequests());
}
/**
* @return array
*/
public function getCompletedRequests() {
return $this->data['completed'];
}
/**
* @return int
*/
public function getFailedRequestsCount() {
return count($this->getFailedRequests());
}
/**
* @return array
*/
public function getFailedRequests() {
return $this->data['failed'];
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'http';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Http');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Completed @completed, error @error', [
'@completed' => $this->getCompletedRequestsCount(),
'@error' => $this->getFailedRequestsCount()
]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAATlJREFUeNrsleERgjAMha0TdISOwAZ2BEZghI7ABo7AOQFuwAjoBLgBblBbfb0LudByp/4jdw+PNnxtkqYq7/3h13Y8/MF26B9spfo6yAX1QSa6EY2Y0xLrzROgddAMOcgLmuFbhDqyG00W8Rm1JWgEWCG0oQBuJKjGigNUs/xWUJdJheZQJzhZFGkgKWkw1mM8bmTCvOPQcSVXrTA+Ydc0kujXJGg6p5VwrG5BJzb2CLriN9kT74afUylPloQ+kdDPELXpg1qGvwbtURwjFGkkC8RIZw6d2QeO7MII81wRbDm0Z068Zf0G1bxQl8z1UH1zoQwk9NRZdkPoHt+KbZoqa+HYZS4T3iimdEvRDrIF4KIRclBNjk+uSB2/eBJUvR9KSek26BZHOit2zl3oqkV91P6//3N7CTAAIIc/qj2gy4gAAAAASUVORK5CYII=';
}
}
@@ -0,0 +1,99 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Mail\MailInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class MailDataCollector
*/
class MailDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var
*/
private $messages;
/**
*
*/
public function __construct() {
$this->messages = [];
}
/**
* Collects data for the given Request and Response.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
* @param \Exception $exception An Exception instance
*
* @api
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['mail'] = $this->messages;
}
/**
* @param $message
* @param \Drupal\Core\Mail\MailInterface $mail
*/
public function addMessage($message, MailInterface $mail) {
$class = get_class($mail);
$method = $this->getMethodData($class, 'mail');
$this->messages[] = [
'message' => $message,
'method' => $method,
];
}
/**
* @return int
*/
public function getMailSent() {
return count($this->data['mail']);
}
/**
* Returns the name of the collector.
*
* @return string The collector name
*
* @api
*/
public function getName() {
return 'mail';
}
/**
* Returns the datacollector title.
*
* @return string
* The datacollector title.
*/
public function getTitle() {
return $this->t('Mail');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Total: @count', ['@count' => $this->getMailSent()]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABcAAAAcCAYAAACK7SRjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6N0NEOTU1MjM0OThFMTFFMDg3NzJBNTE2ODgwQzMxMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6N0NEOTU1MjQ0OThFMTFFMDg3NzJBNTE2ODgwQzMxMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMEQ5OTQ5QzQ5OEMxMUUwODc3MkE1MTY4ODBDMzEzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3Q0Q5NTUyMjQ5OEUxMUUwODc3MkE1MTY4ODBDMzEzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpkRnSAAAAJ0SURBVHjaYvz//z8DrQATAw3BqOFYAaO9vT1FBhw4cGCAXA5MipxBQUHT3r17l0AVAxkZ/wkLC89as2ZNIcjlYkALXKnlWqBZTH/+/PEDmQsynLW/v3+NoaHhN2oYDjJn8uTJK4BMNpDhPwsLCwOKiop2+fn5vafEYC8vrw8gc/Lz8wOB3B8gw/nev38vn5WV5eTg4LA/Ly/vESsrK2npmYmJITU19SnQ8L0gc4DxpwgyF2S4EEjB58+f+crLy31YWFjOt7S0XBYUFPxHjMEcHBz/6+rqboqJiZ0qKSnxBpkDlRICGc4MU/j792+2CRMm+L18+fLSxIkTDykoKPzBZ7CoqOi/np6eE8rKylvb29v9fvz4wYEkzYKRzjk5OX/LyMjcnDRpEkjjdisrK6wRraOj8wvokAMLFy788ejRoxcaGhrPCWai4ODgB8DUE3/mzBknYMToASNoMzAfvEVW4+Tk9LmhoWFbTU2NwunTpx2BjiiMjo6+hm4WCzJHUlLyz+vXrxkfP36sDOI/ffpUPikpibe7u3sLsJjQW7VqlSrQxe+Avjmanp7u9PbtWzGQOmCCkARmmu/m5uYfT548yY/V5UpKSl+2b9+uiiz26dMnIWBSDTp27NgdYGrYCIzwE7m5uR4wg2Hg/PnzSsDI/QlKOcjZ3wGUBLm5uf+DwLdv38gub4AG/xcSEvr35s0bZmCB5sgCE/z69SsjyDJKMtG/f/8YQQYD8wkoGf8H51AbG5sH1CpbQBnQ09PzBiiHgoIFFHlBQGwLxLxUMP8dqJgH4k3gIhfIkAKVYkDMTmmhCHIxEL8A4peMo02L4WU4QIABANxZAoDIQDv3AAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,83 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\Frontend\PerformanceTimingData;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Collects data about frontend performance.
*/
class PerformanceTimingDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
}
/**
* @param $data
*/
public function setData($data) {
$this->data['performance'] = $data;
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'performance_timing';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Performance Timing');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
if (isset($this->data['performance'])) {
$performanceData = new PerformanceTimingData($this->data['performance']);
return $this->t('TTFB: @ttfb', ['@ttfb' => sprintf('%.0f ms', $performanceData->getTtfbTiming())]);
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpi/P//PwO1ARMDDcCooUPEUEYi1AgAcQIQ+0P5H4B4IxAvwKaYmCSqAMT7gbgBajhMrB8qLkCsoQFQQ0CuOw/EBjgsLIAajmEouvdBhukD8UQgdkASwwXuA7EhNEhwuvQ8iXHSj2Q53FBY7BtADVxIoqEfoQYnYJPEF3bEROZ6WDDBvO+ALcCxJCsBAmpA4SuA7P2PBDQUEOGTDTA1TNCYs6dCRgIlxQswQ0GMB0A8nwgv4gqa+VCXgpMWC1QiEerF9WgaDmJJp/OhkUNIHUHQgJ4ecQHkiMKXXALQIowqpdR8pJi/AA0qvC4lFsyHYqK8zzhaRQ8NQwECDABNaU12xhTp2QAAAABJRU5ErkJggg==';
}
/**
* {@inheritdoc}
*/
public function getData() {
$data = $this->data;
if (isset($this->data['performance'])) {
$performanceData = new PerformanceTimingData($this->data['performance']);
$data['performance']['computed']['DNS lookup time'] = $performanceData->getDNSTiming();
$data['performance']['computed']['TCP handshake time'] = $performanceData->getTCPTiming();
$data['performance']['computed']['Time to first byte'] = $performanceData->getTtfbTiming();
$data['performance']['computed']['Data download time'] = $performanceData->getDataTiming();
$data['performance']['computed']['DOM building time'] = $performanceData->getDomTiming();
}
return $data;
}
}
@@ -0,0 +1,163 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Routing\LinkGeneratorTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Provides a data collector to collect all kind of php information.
*/
class PhpConfigDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait, LinkGeneratorTrait;
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data = [
'token' => $response->headers->get('X-Debug-Token'),
'php_version' => PHP_VERSION,
'xdebug_enabled' => extension_loaded('xdebug'),
'xhprof_enabled' => extension_loaded('xhprof'),
'eaccel_enabled' => extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'),
'apc_enabled' => extension_loaded('apc') && ini_get('apc.enabled'),
'xcache_enabled' => extension_loaded('xcache') && ini_get('xcache.cacher'),
'wincache_enabled' => extension_loaded('wincache') && ini_get('wincache.ocenabled'),
'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'),
'sapi_name' => php_sapi_name()
];
}
/**
* Gets the token.
*
* @return string The token
*/
public function getToken() {
return $this->data['token'];
}
/**
* Gets the PHP version.
*
* @return string The PHP version
*/
public function getPhpVersion() {
return $this->data['php_version'];
}
/**
* Returns true if the XDebug is enabled.
*
* @return Boolean true if XDebug is enabled, false otherwise
*/
public function hasXDebug() {
return $this->data['xdebug_enabled'];
}
/**
* Returns true if the XHProf is enabled.
*
* @return Boolean true if XHProf is enabled, false otherwise
*/
public function hasXHProf() {
return $this->data['xhprof_enabled'];
}
/**
* Returns true if EAccelerator is enabled.
*
* @return Boolean true if EAccelerator is enabled, false otherwise
*/
public function hasEAccelerator() {
return $this->data['eaccel_enabled'];
}
/**
* Returns true if APC is enabled.
*
* @return Boolean true if APC is enabled, false otherwise
*/
public function hasApc() {
return $this->data['apc_enabled'];
}
/**
* Returns true if Zend OPcache is enabled
*
* @return Boolean true if Zend OPcache is enabled, false otherwise
*/
public function hasZendOpcache() {
return $this->data['zend_opcache_enabled'];
}
/**
* Returns true if XCache is enabled.
*
* @return Boolean true if XCache is enabled, false otherwise
*/
public function hasXCache() {
return $this->data['xcache_enabled'];
}
/**
* Returns true if WinCache is enabled.
*
* @return Boolean true if WinCache is enabled, false otherwise
*/
public function hasWinCache() {
return $this->data['wincache_enabled'];
}
/**
* Returns true if any accelerator is enabled.
*
* @return Boolean true if any accelerator is enabled, false otherwise
*/
public function hasAccelerator() {
return $this->hasApc() || $this->hasZendOpcache() || $this->hasEAccelerator() || $this->hasXCache() || $this->hasWinCache();
}
/**
* Gets the PHP SAPI name.
*
* @return string The environment
*/
public function getSapiName() {
return $this->data['sapi_name'];
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('PHP Config');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('PHP: @version', ['@version' => $this->getPhpVersion()]);
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'php_config';
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAMAAAC5xgRsAAAAZlBMVEX///////////////////////////////////////////////////////////////////////////////////////////+ZmZmZmZlISEhJSUmdnZ1HR0fR0dFZWVlpaWlfX18/Pz+puygPAAAAIXRSTlMACwwlJygpLzIzNjs8QEtMUmd6e32AucDBw8fIydTm6u5l8MjvAAAAo0lEQVR42r2P2Q6CMBBFL6XsZRGRfZv//0nbDBNEE19MnJeTc5ILKf58ahiUwzy/AJpIWwREwQnEXRdbGCLjrO+djWRvVMiJcigxB7viGogxDdJpSmHEmCVPS7YczJvgUu+CS30IvtbNYZMvsGVo2mVpG/kbm4auiCpdcC3YPCAhSpAdUzaAn6qPKZtUT6ZSzb4bi2hdo9MQ1nX4ASjfV+/4/Z40pyCHrNTbIgAAAABJRU5ErkJggg==';
}
}
@@ -0,0 +1,83 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector as BaseRequestDataCollector;
/**
* Integrate _content into the RequestDataCollector
*/
class RequestDataCollector extends BaseRequestDataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Controller\ControllerResolverInterface
*/
private $controllerResolver;
/**
* @var array
*/
private $accessCheck;
/**
* @param \Drupal\Core\Controller\ControllerResolverInterface $controllerResolver
*/
public function __construct(ControllerResolverInterface $controllerResolver) {
parent::__construct();
$this->controllerResolver = $controllerResolver;
$this->accessCheck = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
parent::collect($request, $response, $exception);
$controller = $this->controllerResolver->getController($request);
$this->data['controller'] = $this->getMethodData($controller[0], $controller[1]);
$this->data['access_check'] = $this->accessCheck;
}
/**
* @param $service_id
* @param $callable
* @param $request
*/
public function addAccessCheck($service_id, $callable, Request $request) {
$this->accessCheck[$request->getPathInfo()][] = [
'service_id' => $service_id,
'callable' => $this->getMethodData($callable[0], $callable[1]),
];
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Request');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->data['status_code'].' '.$this->data['status_text'];
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAQAAADYBBcfAAACvElEQVR42tVTbUhTYRTerDCnKVoUUr/KCZmypA9Koet0bXNLJ5XazDJ/WFaCUY0pExRZXxYiJgsxWWjkaL+yK+po1gjyR2QfmqWxtBmaBtqWGnabT++c11Fu4l/P4VzOPc95zoHznsNZodIbLDdRcKnc1Bu8DAK45ZsOnykQNMopsNooLxCknb0cDq5vml9FtHiIgpBR0R6iihYyFMTDt2Lg56ObPkI6TMGXSof1EV67IqCwisJSWliFAG/E0CfFIiebdNypcxi/1zgyFiIiZ3sJQr0RQx5frLa6k7SOKRo3oMFNR5t62h2rttKXEOKFqDCxtXNmmBokO2KKTlp3IdWuT2dYRNGKwEXEBCcL172G5FG0aIxC0kR9PBTVH1kkwQn+IqJnCE33EalVzT9GJQS1tAdD3CKicJYFrxqx7W2ejCEdZy1FiC5tZxHhLJKOZaRdQJAyV/YAvDliySALHxmxR4Hqe2iwvaOR/CEuZYJFSgYhVbZRkA8KGdEktrqnqra90NndCdkt77fjIHIhexOrfO6O3bbbOj/rqu5IptgyR3sU93QbOYhquZK4MCDp0Ina/PLsu5JvbCTRaapUdUmIV/RzoMdsk/0hWRNdAvKOmvqlN0drsJbJf1P4YsQ5lGrJeuosiOUgbOC8cto3LfOXTdVd7BqZsQKbse+0jUL6WPcesqs4MNSUTQAxGjwFiC8m3yzmqwHJBWYKBJ9WNqW/dHkpU/osch1Yj5RJfXPfSEe/2UPsN490NPfZG5CKyJmcV5ayHyzy7BMqsXfuHhGK/cjAIeSpR92gehR55D8TcQhDEKJwytBJ4fr4NULvrEM8NszfJPyxDoHYAQ1oPCWmIX4gifmDS/DV2DKeb25FHWr76yEG7/9L4YFPeiQQ4/8LkgJ8Et+NncTCsYqzXAEXa7CWdPZzGWdlyV+vST0JanfPvwAAAABJRU5ErkJggg==';
}
}
@@ -0,0 +1,91 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Provides a data collector which shows all available routes.
*/
class RoutingDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* The route profiler.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* Constructs a new RoutingDataCollector.
*
* @param \Drupal\Core\Routing\RouteProviderInterface $routeProvider
* The route provider.
*/
public function __construct(RouteProviderInterface $routeProvider) {
$this->routeProvider = $routeProvider;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['routing'] = [];
foreach ($this->routeProvider->getAllRoutes() as $route_name => $route) {
// @TODO Find a better visual representation.
$this->data['routing'][] = [
'name' => $route_name,
'path' => $route->getPath(),
];
}
}
/**
* @return int
*/
public function getRoutesCount() {
return count($this->routing());
}
/**
* Twig callback for displaying the routes.
*/
public function routing() {
return $this->data['routing'];
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Routing');
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'routing';
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Defined routes: @route', ['@route' => $this->getRoutesCount()]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPxJREFUeNrkVsERREAQ7D0koXwlIAISkIGXvwAkoJSfHITg5SMUVR4eMuCxZ1xxV+5uuTL30lV8bPX09PSuFVJKcOOGP+DipLrrusoFdV1Lz/NgmiaKosC0XrAopYT0fc/f/jAMa41Pj23bkrpi9bRpGmRZ9lRKFSzLkl9UHMI4jijLcuaaSZMkQdd1fNMn5r0EHIFhGEjTlDenYRjCcZyHUnqRwXEcz77sYepM+Z1yTOEXZEFVVYcUba2iItsNcbr9IAiw5HMd1CJZtU1VpG3bIooi3gPF933keY43pb9gb1Bskdrap58luMjvRNO04wcK18RfIa59mbgLMAASuWsKAyoEhgAAAABJRU5ErkJggg==';
}
}
@@ -0,0 +1,162 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DependencyInjection\TraceableContainer;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class ServicesDataCollector
*/
class ServicesDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Symfony\Component\DependencyInjection\IntrospectableContainerInterface
* $container
*/
private $container;
/**
* @param IntrospectableContainerInterface $container
*/
public function __construct(IntrospectableContainerInterface $container) {
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
if ($this->getServicesCount()) {
$tracedData = [];
if ($this->container instanceof TraceableContainer) {
$tracedData = $this->container->getTracedData();
}
foreach (array_keys($this->getServices()) as $id) {
$this->data['services'][$id]['initialized'] = ($this->container->initialized($id)) ? TRUE : FALSE;
$this->data['services'][$id]['time'] = isset($tracedData[$id]) ? $tracedData[$id] : NULL;
}
}
}
/**
* @param $services
*/
public function setServices($services) {
$this->data['services'] = $services;
}
/**
* @return array
*/
public function getServices() {
return $this->data['services'];
}
/**
* @return int
*/
public function getServicesCount() {
return count($this->getServices());
}
/**
* @return array
*/
public function getInitializedServices() {
return array_filter($this->getServices(), function($item) {
return $item['initialized'];
});
}
/**
* @return int
*/
public function getInitializedServicesCount() {
return count($this->getInitializedServices());
}
/**
* @return array
*/
public function getInitializedServicesWithoutWebprofiler() {
return array_filter($this->getInitializedServices(), function($item) {
return strpos($item['value']['id'], 'webprofiler') !== 0;
});
}
/**
* @return int
*/
public function getInitializedServicesWithoutWebprofilerCount() {
return count($this->getInitializedServicesWithoutWebprofiler());
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'services';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Services');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Initialized: @count', [
'@count' => $this->getInitializedServicesCount(),
]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQVJREFUeNrkVe0NgjAQBeMAdYO6AWxQNtANGEFHcALZANyADegGsIFsIBvgu6Q/LtWmxdTEjyYvd6Hw8t5de6TzPCex1yp5w/pz0rVrQymVIXSAACqt9TGG0p0hpHWIZb9lebWENOXn1FgWbL8GJHACNHs+ohyjlxSEZPEcKGYC6SbEvljgUHzEOR3IXiiB6YOTlLqdo1Y54tZHDLIauCHtETtn962P6EUVqhhi0gelIJEEk1MjMg9Py9xol/0SuBqFva/DULY3ZSqQF767v8TyZKv83tFXWVaEufsUG+DCr2nwQLGOlGQNizZPy3fMU16K5uV5+qQEpFTC+hCN9Pd/0XcBBgBxwVqjDkAznAAAAABJRU5ErkJggg==';
}
/**
* @return array
*/
public function getData() {
$data = $this->data;
$http_middleware = array_filter($data['services'], function($service) {
return isset($service['value']['tags']['http_middleware']);
});
foreach ($http_middleware as &$service) {
$service['value']['handle_method'] = $this->getMethodData($service['value']['class'], 'handle');
}
uasort($http_middleware, function ($a, $b) {
$va = $a['value']['tags']['http_middleware'][0]['priority'];
$vb = $b['value']['tags']['http_middleware'][0]['priority'];
if ($va == $vb) {
return 0;
}
return ($va > $vb) ? -1 : 1;
});
$data['http_middleware'] = $http_middleware;
return $data;
}
}
@@ -0,0 +1,65 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Provides a data collector to get all requested state values.
*/
class StateDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
}
/**
* @param $key
*/
public function addState($key) {
$this->data['state_get'][$key] = isset($this->data['state_get'][$key]) ? $this->data['state_get'][$key] + 1 : 1;
}
/**
* Twig callback to show all requested state keys.
*/
public function getStateKeysCount() {
return count($this->data['state_get']);
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('State');
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'state';
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Total: @variables', ['@variables' => $this->getStateKeysCount()]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2Njc3QTVERTkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2Njc3QTVERjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY2NzdBNURDOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY2NzdBNUREOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+tji6wwAAAUFJREFUeNpi/P//PwO1ARMDDcDQMZSBgZExE4h/AvF/KuA/QFzKCCS+TZ40iVNeXp5i9718+ZIhNS3tL5OoqChVDAQBcXFxBqBZzDQJU0Z2Do7/FubmVDPwzNmzwymdhoSEgDEM8PDwgPkJCQk49bDgMxCkEWQAyKAXL14wXLhwgWHChAlguTVr1pBnKMiwgoICsEEgC+7cuQPGHR0deH3HzMLC0iAjI4NV8tSpUwzv3r1jYGNjY3BycoJbgg88e/6cuIg6cuQImAZ5n2qx7+HhAaZtbGzArqXYUJCBIAwKR1jMq6ioMBgYGJAeURUVFWCNsHAERRAsKYFofJGF01CQIaBktGPHDjANAjU1NWBXg8IWX/gOobz/8+fP7x8/fqSKYV++fGH4+vXrX1B1EgXET6hUnTwH4jTG0Xqf6gAgwABnDJn0eW/JMwAAAABJRU5ErkJggg==';
}
}
@@ -0,0 +1,257 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\Theme\ThemeNegotiatorWrapper;
use Drupal\webprofiler\Twig\Dumper\HtmlDumper;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
/**
* Class ThemeDataCollector
*/
class ThemeDataCollector extends DataCollector implements DrupalDataCollectorInterface, LateDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
private $themeManager;
/**
* @var \Drupal\Core\Theme\ThemeNegotiatorInterface
*/
private $themeNegotiator;
/**
* @var \Twig_Profiler_Profile
*/
private $profile;
/**
* @var
*/
private $computed;
/**
* @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $themeNegotiator
* @param \Twig_Profiler_Profile $profile
*/
public function __construct(ThemeManagerInterface $themeManager, ThemeNegotiatorInterface $themeNegotiator, \Twig_Profiler_Profile $profile) {
$this->themeManager = $themeManager;
$this->themeNegotiator = $themeNegotiator;
$this->profile = $profile;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$activeTheme = $this->themeManager->getActiveTheme();
$this->data['activeTheme'] = [
'name' => $activeTheme->getName(),
'path' => $activeTheme->getPath(),
'engine' => $activeTheme->getEngine(),
'owner' => $activeTheme->getOwner(),
'baseThemes' => $activeTheme->getBaseThemes(),
'extension' => $activeTheme->getExtension(),
'styleSheetsRemove' => $activeTheme->getStyleSheetsRemove(),
'libraries' => $activeTheme->getLibraries(),
'regions' => $activeTheme->getRegions(),
];
if ($this->themeNegotiator instanceof ThemeNegotiatorWrapper) {
$this->data['negotiator'] = [
'class' => $this->getMethodData($this->themeNegotiator->getNegotiator(), 'determineActiveTheme'),
'id' => $this->themeNegotiator->getNegotiator()->_serviceId,
];
}
}
/**
* {@inheritdoc}
*/
public function lateCollect() {
$this->data['twig'] = serialize($this->profile);
}
/**
* @return string
*/
public function getActiveTheme() {
return $this->data['activeTheme'];
}
/**
* @return array
*/
public function getThemeNegotiator() {
return $this->data['negotiator'];
}
/**
* @return int
*/
public function getTime() {
return $this->getProfile()->getDuration() * 1000;
}
/**
* @return mixed
*/
public function getTemplateCount() {
return $this->getComputedData('template_count');
}
/**
* @return mixed
*/
public function getTemplates() {
return $this->getComputedData('templates');
}
/**
* @return mixed
*/
public function getBlockCount() {
return $this->getComputedData('block_count');
}
/**
* @return mixed
*/
public function getMacroCount() {
return $this->getComputedData('macro_count');
}
/**
* @return \Twig_Markup
*/
public function getHtmlCallGraph() {
$dumper = new HtmlDumper();
return new \Twig_Markup($dumper->dump($this->getProfile()), 'UTF-8');
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'theme';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Theme');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Name: @name', ['@name' => $this->getActiveTheme()['name']]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlVJREFUeNrMlk1IVFEUx53K6AOjUBQri6SihQiRziaRQegDlKJFBCpSiZtoE4hWtFFRwcCVBqEWfaAE7SoXajXLQoRcFAUSaBgUJQxKYUrT78D/wevx3psaE7rw49x377ln7vl4500kmUxm/OuxJmMVxrqwzVgsth5xHKogCnthCd7BU7gdj8envOciQe5j8CyiFQq0tAgDsA+OaG0eLmF4INQoxrYg+iEXhqEGiqGBw/3SGUeU6MgC61mBMUU5GzEKJ6ES5S5knbbXui/jmv8MjCkGMxEPFbsFyAeL1y6pXEenSO4fctlIhCXqqtmGj5ADcblZoX1z8aLn/Es475soDhcyfwt3LPDK8jPYFlIcZrCCEH0LqlP7NXN/CCUL/CvmkynKcczPoNv9Y5LXuHVC2Y6mMFoetOG4/0kl9LfDquMyN076ue/I11ANHSqVH0pgLUz7GK2HjUHuf1DGm/jVYZVYJeILz5163oy46Tn/GL5r396yc+hXOzcckiyVQp7CsVNvmI2DHoODcMFcR6eZ+RO4747pJuYjcFilssMM6vB7+Or8IGMWWjDWx7ntzHvglCWZtfbf3n0UtiJ6FVO/8QbuQh+H59C37nUD9sAj1k6ENZQylctuJWsGJswDDibY32C3gitKsCUwyt7nlK0vpMc2Wh/Q4zIcxeDztDu/QjSoPmujzWswZef3GLRXuV2xPa3u/yDwjfoDgxE1nP1aquOG91b64StWlp0xtaKvKbfMUS1maukWvEj7a6pxBg6oL1iddnsbSFox/S/+TKyK0V8CDABrCdI/1oTqiQAAAABJRU5ErkJggg==';
}
/**
* @return array
*/
public function getData() {
$data = $this->data;
$data['twig'] = [
'callgraph' => (string) $this->getHtmlCallGraph(),
'render_time' => $this->getTime(),
'template_count' => $this->getTemplateCount(),
'templates' => $this->getTemplates(),
'block_count' => $this->getBlockCount(),
'macro_count' => $this->getMacroCount(),
];
return $data;
}
/**
* @return mixed|\Twig_Profiler_Profile
*/
private function getProfile() {
if (NULL === $this->profile) {
$this->profile = unserialize($this->data['twig']);
}
return $this->profile;
}
/**
* @param $index
*
* @return mixed
*/
private function getComputedData($index) {
if (NULL === $this->computed) {
$this->computed = $this->computeData($this->getProfile());
}
return $this->computed[$index];
}
/**
* @param \Twig_Profiler_Profile $profile
*
* @return array
*/
private function computeData(\Twig_Profiler_Profile $profile) {
$data = [
'template_count' => 0,
'block_count' => 0,
'macro_count' => 0,
];
$templates = [];
foreach ($profile as $p) {
$d = $this->computeData($p);
$data['template_count'] += ($p->isTemplate() ? 1 : 0) + $d['template_count'];
$data['block_count'] += ($p->isBlock() ? 1 : 0) + $d['block_count'];
$data['macro_count'] += ($p->isMacro() ? 1 : 0) + $d['macro_count'];
if ($p->isTemplate()) {
if (!isset($templates[$p->getTemplate()])) {
$templates[$p->getTemplate()] = 1;
}
else {
$templates[$p->getTemplate()]++;
}
}
foreach ($d['templates'] as $template => $count) {
if (!isset($templates[$template])) {
$templates[$template] = $count;
}
else {
$templates[$template] += $count;
}
}
}
$data['templates'] = $templates;
return $data;
}
}
@@ -0,0 +1,157 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector as BaseTimeDataCollector;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Stopwatch\StopwatchEvent;
/**
* Class TimeDataCollector.
*/
class TimeDataCollector extends BaseTimeDataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @param \Symfony\Component\HttpKernel\KernelInterface $kernel
* @param $stopwatch
*/
public function __construct(KernelInterface $kernel = NULL, $stopwatch = NULL) {
parent::__construct($kernel, $stopwatch);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
parent::collect($request, $response, $exception);
$this->data['memory_limit'] = $this->convertToBytes(ini_get('memory_limit'));
$this->updateMemoryUsage();
}
/**
* {@inheritdoc}
*/
public function lateCollect() {
parent::lateCollect();
$this->updateMemoryUsage();
}
/**
* Gets the memory.
*
* @return int
* The memory
*/
public function getMemory() {
return $this->data['memory'];
}
/**
* Gets the PHP memory limit.
*
* @return int
* The memory limit
*/
public function getMemoryLimit() {
return $this->data['memory_limit'];
}
/**
* Updates the memory usage data.
*/
public function updateMemoryUsage() {
$this->data['memory'] = memory_get_peak_usage(TRUE);
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Timeline');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Duration: @duration', ['@duration' => sprintf('%.0f ms', $this->getDuration())]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAABqUlEQVR42t2Vv0sCYRyHX9OmEhsMx/YKGlwLQ69DTEUSBJEQEy5J3FRc/BsuiFqEIIcQIRo6ysUhoaBBWhoaGoJwiMJLglRKrs8bXgienmkQdPDAwX2f57j3fhFJkkbiPwTK5bIiFoul3kmPud8MqKMewDXpwuGww+12n9hsNhFnlijYf/Z4PDmO45Yxo+10ZFGTyWRMEItU6AdCx7lczkgd6n7J2Wx2xm63P6jJMk6n80YQBBN1aUDv9XqvlAbbm2LE7/cLODRB0un0VveAeoDC8/waCQQC18MGQqHQOcEKvw8bcLlcL6TfYnVtCrGRAlartUUYhmn1jKg/E3USjUYfhw3E4/F7ks/nz4YNFIvFQ/ogbUYikdefyqlU6gnuOg2YK5XKvs/n+xhUDgaDTVEUt+HO04ABOBA5isViDTU5kUi81Wq1AzhWMEkDGmAEq2C3UCjcYXGauDvfEsuyUjKZbJRKpVvM8IABU9SVX+cxYABmwIE9cFqtVi9xtgvsC2AHbIAFoKey0gdlHEyDObAEWLACFsEsMALdIJ80+dK0bTS95v7+v/AJnis0eO906QwAAAAASUVORK5CYII=';
}
/**
* {@inheritdoc}
*/
public function getLibraries() {
return [
'webprofiler/timeline',
];
}
/**
* {@inheritdoc}
*/
public function getDrupalSettings() {
/** @var StopwatchEvent[] $collectedEvents */
$collectedEvents = $this->getEvents();
if (!empty($collectedEvents)) {
$sectionPeriods = $collectedEvents['__section__']->getPeriods();
$endTime = end($sectionPeriods)->getEndTime();
$events = [];
foreach ($collectedEvents as $key => $collectedEvent) {
if ('__section__' != $key) {
$periods = [];
foreach ($collectedEvent->getPeriods() as $period) {
$periods[] = [
'start' => sprintf("%F", $period->getStartTime()),
'end' => sprintf("%F", $period->getEndTime()),
];
}
$events[] = [
"name" => $key,
"category" => $collectedEvent->getCategory(),
"origin" => sprintf("%F", $collectedEvent->getOrigin()),
"starttime" => sprintf("%F", $collectedEvent->getStartTime()),
"endtime" => sprintf("%F", $collectedEvent->getEndTime()),
"duration" => sprintf("%F", $collectedEvent->getDuration()),
"memory" => sprintf("%.1F", $collectedEvent->getMemory() / 1024 / 1024),
"periods" => $periods,
];
}
}
return ['time' => ['events' => $events, 'endtime' => $endTime]];
}
else {
return ['time' => ['events' => [], 'endtime' => 0]];
}
}
/**
* @return array
*/
public function getData() {
$data = $this->data;
$data['duration'] = $this->getDuration();
$data['initTime'] = $this->getInitTime();
return $data;
}
}
@@ -0,0 +1,99 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\webprofiler\StringTranslation\TranslationManagerWrapper;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class TranslationsDataCollector
*/
class TranslationsDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
private $translation;
/**
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
private $urlGenerator;
/**
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* @param \Drupal\Core\Routing\UrlGeneratorInterface $urlGenerator
*/
public function __construct(TranslationInterface $translation, UrlGeneratorInterface $urlGenerator) {
$this->translation = $translation;
$this->urlGenerator = $urlGenerator;
$this->data['translations']['translated'] = [];
$this->data['translations']['untranslated'] = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
if($this->translation instanceof TranslationManagerWrapper) {
/** \Drupal\webprofiler\StringTranslation\TranslationManagerWrapper $this->translation */
$this->data['translations']['translated'] = $this->translation->getTranslated();
$this->data['translations']['untranslated'] = $this->translation->getUntranslated();
}
$data['user_interface_translations_path'] = $this->urlGenerator->generateFromRoute('locale.translate_page');
}
/**
* @return int
*/
public function getTranslatedCount() {
return count($this->data['translations']['translated']);
}
/**
* @return int
*/
public function getUntranslatedCount() {
return count($this->data['translations']['untranslated']);
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'translations';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Translations');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Translated: @translated, untranslated: @untranslated', [
'@translated' => $this->getTranslatedCount(),
'@untranslated' => $this->getUntranslatedCount()
]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAgpJREFUeNrUVrFuwjAQPUcZiAQDQ6WEbgwwMCJVqhjgBxhYGSvEDzBkZYSBH+ADoFJHlg4MdIMKxiLBXKAbUmEm9bPiyAREEgRDTzqZ2Mfzu7tnJ8xxHLq1aXQHuwsos2078p9arVYg0yL3CXcnwCdubKDp3F+5myFin9xYK0xNzQiZm1c3KpVKUb1ep2QyebvuFwoFSqfTlM/nbweay+VoOBxeDar7JwC03W49UO5fs9nsZTwef8qYUqkUjSmAYrGYqKlhGFSpVH45oBFFdkdM0RjUst1uC7Z45ofjWdO0t8Ph8CDjyuUyZTIZ6na7tNvtTmSn+2s5n88FIMwdPzhgUWYBA2A2mxVjIpEQarEsiwaDAS2XSxPH9OI1xVNnbmo0Go28eTDkAOL3YrGg/X4v1tfr9WmjLplsULVaFawbjYbHkjfzqPsr7o9RwJE2HOkifbAGS1ljdL/G/ScsWK/XE+NmsxGMkW6/3xfgWMMIpu9hLgkEN5tNDwClgAoADjAYRszr8m7kD2gGU5uh1hEjmoGUUU/oGLVEXaVhA2yoKYAwRwFx/Ezj8bjHEIYaSgVgDptNp1NiePG5QCfS4qyZXANop9MR7JANWAFA6hWgWEcmqqTYOWBVl0jZf0ViU+hUNk0AyVf0ObYK0+8IslupF8qlkxVWdoipsaCPiaBr7uwr+t98ofwJMADuIP9IDjFbLwAAAABJRU5ErkJggg==';
}
}
@@ -0,0 +1,141 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Authentication\AuthenticationCollectorInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Class UserDataCollector
*/
class UserDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\Core\Session\AccountInterface
*/
private $currentUser;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
private $entityManager;
/**
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
private $configFactory;
/**
* @var \Drupal\Core\Authentication\AuthenticationCollectorInterface
*/
private $providerCollector;
/**
* @param \Drupal\Core\Session\AccountInterface $currentUser
* @param \Drupal\Core\Entity\EntityManagerInterface $entityManager
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* @param \Drupal\Core\Authentication\AuthenticationCollectorInterface $providerCollector
*/
public function __construct(AccountInterface $currentUser, EntityManagerInterface $entityManager, ConfigFactoryInterface $configFactory, AuthenticationCollectorInterface $providerCollector) {
$this->currentUser = $currentUser;
$this->entityManager = $entityManager;
$this->configFactory = $configFactory;
$this->providerCollector = $providerCollector;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$this->data['name'] = $this->currentUser->getDisplayName();
$this->data['authenticated'] = $this->currentUser->isAuthenticated();
$this->data['roles'] = [];
$storage = $this->entityManager->getStorage('user_role');
foreach ($this->currentUser->getRoles() as $role) {
$entity = $storage->load($role);
$this->data['roles'][] = $entity->label();
}
foreach ($this->providerCollector->getSortedProviders() as $provider_id => $provider) {
if ($provider->applies($request)) {
$this->data['provider'] = $provider_id;
}
}
$this->data['anonymous'] = $this->configFactory->get('user.settings')
->get('anonymous');
}
/**
* @return \Drupal\Core\Session\AccountInterface
*/
public function getUserName() {
return $this->data['name'];
}
/**
* @return bool
*/
public function getAuthenticated() {
return $this->data['authenticated'];
}
/**
* @return array
*/
public function getRoles() {
return $this->data['roles'];
}
/**
* @return string
*/
public function getProvider() {
return $this->data['provider'];
}
/**
* @return string
*/
public function getAnonymous() {
return $this->data['anonymous'];
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'user';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('User');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->getUserName();
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAcCAYAAAB75n/uAAAC70lEQVR42u2V3UtTYRzHu+mFwCwK+gO6CEryPlg7yiYx50vDqUwjFIZDSYUk2ZTmCysHvg9ZVggOQZiRScsR4VwXTjEwdKZWk8o6gd5UOt0mbev7g/PAkLONIOkiBx+25/v89vuc85zn2Q5Fo9F95UDwnwhS5HK5TyqVRv8m1JN6k+AiC+fn54cwbgFNIrTQ/J9IqDcJJDGBHsgDgYBSq9W6ysvLPf39/SSUUU7zsQ1yc3MjmN90OBzfRkZG1umzQqGIxPSTkIBjgdDkaGNjoza2kcFgUCE/QvMsq6io2PV6vQu1tbV8Xl7etkql2qqvr/+MbDE/Pz8s9OP2Cjhwwmw29+4R3Kec1WZnZ4fn5uamc3Jyttra2qbH8ero6JgdHh5+CvFHq9X6JZHgzODgoCVW0NPTY0N+ltU2Nzdv4GqXsYSrPp+vDw80aLFYxru6uhyQ/rDb7a8TCVJDodB1jUazTVlxcXGQ5/mbyE+z2u7u7veY38BVT3Z2djopm5qa6isrK/tQWVn5qb29fSGR4DC4PDAwMEsZHuArjGnyGKutq6v7ajQaF6urq9/MzMz0QuSemJiwQDwGkR0POhhXgILjNTU1TaWlpTxlOp1uyWQyaUjMajMzM8Nut/tJQUHBOpZppbCwkM/KytrBznuL9xDVxBMo8KXHYnu6qKjIivmrbIy67x6Px4Yd58W672ApfzY0NCyNjo7OZmRkiAv8fr+O47iwmABXtoXaG3uykF6vX7bZbF6cgZWqqiqezYkKcNtmjO+CF2AyhufgjsvlMiU7vXEF+4C4ALf9CwdrlVAqlcFkTdRqdQSHLUDgBEeSCrArAsiGwENs0XfJBE6ncxm1D8Aj/B6tigkkJSUlmxSwLYhMDeRsyyUCd+lHrWxtbe2aTCbbZTn1ZD92F0Cr8GBfgnsgDZwDt8EzMBmHMXBLqD0PDMAh9Gql3iRIESQSIAXp4CRIBZeEjIvDFZAm1J4C6UK9ROiZcvCn/+8FvwHtDdJEaRY+oQAAAABJRU5ErkJggg==';
}
}
@@ -0,0 +1,118 @@
<?php
namespace Drupal\webprofiler\DataCollector;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webprofiler\DrupalDataCollectorInterface;
use Drupal\webprofiler\Views\TraceableViewExecutable;
use Drupal\webprofiler\Views\ViewExecutableFactoryWrapper;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Collects data about rendered views.
*/
class ViewsDataCollector extends DataCollector implements DrupalDataCollectorInterface {
use StringTranslationTrait, DrupalDataCollectorTrait;
/**
* @var \Drupal\webprofiler\Views\ViewExecutableFactoryWrapper
*/
private $view_executable_factory;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
private $entityManager;
/**
* @param ViewExecutableFactoryWrapper $view_executable_factory
* @param \Drupal\Core\Entity\EntityManagerInterface $entityManager
*/
public function __construct(ViewExecutableFactoryWrapper $view_executable_factory, EntityManagerInterface $entityManager) {
$this->entityManager = $entityManager;
$this->view_executable_factory = $view_executable_factory;
$this->data['views'] = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = NULL) {
$views = $this->view_executable_factory->getViews();
$storage = $this->entityManager->getStorage('view');
/** @var TraceableViewExecutable $view */
foreach ($views as $view) {
if ($view->executed) {
$data = [
'id' => $view->storage->id(),
'current_display' => $view->current_display,
'build_time' => $view->getBuildTime(),
'execute_time' => $view->getExecuteTime(),
'render_time' => $view->getRenderTime(),
];
$entity = $storage->load($view->storage->id());
if ($entity->hasLinkTemplate('edit-display-form')) {
$route = $entity->toUrl('edit-display-form');
$route->setRouteParameter('display_id', $view->current_display);
$data['route'] = $route->toString();
}
$this->data['views'][] = $data;
}
}
// TODO: also use those data.
// $loaded = $this->entityManager->getLoaded('view');
//
// if ($loaded) {
// /** @var \Drupal\webprofiler\Entity\EntityStorageDecorator $views */
// foreach ($loaded->getEntities() as $views) {
// $this->data['views'][] = array(
// 'id' => $views->get('id'),
// );
// }
// }
}
/**
* @return int
*/
public function getViewsCount() {
return count($this->data['views']);
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'views';
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Views');
}
/**
* {@inheritdoc}
*/
public function getPanelSummary() {
return $this->t('Total: @count', ['@count' => $this->getViewsCount()]);
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowNDgwMTE3NDA3MjA2ODExOEY2MkNCNjI0NDY3NzkwRCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozNEVFREM2NkQ4MUMxMUUzQkMwRUNBMkQwMzE4QjVBMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozNEVFREM2NUQ4MUMxMUUzQkMwRUNBMkQwMzE4QjVBMyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDQ4MDExNzQwNzIwNjgxMThGNjJDQjYyNDQ2Nzc5MEQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDQ4MDExNzQwNzIwNjgxMThGNjJDQjYyNDQ2Nzc5MEQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6vqYfFAAAAXUlEQVR42mL8//8/A7UBEwMNwKih1AcsIGLz5s1USwK+vr6MLMgcSg2EOW6IhSkycHR0BHth//79jMh8fACmlr4uRbcVnT8apqNhOhqmAxZR1CyoGUfrfaoDgAADAA4QNs9x67RnAAAAAElFTkSuQmCC';
}
}
@@ -0,0 +1,94 @@
<?php
namespace Drupal\webprofiler;
/**
* Generic class Decorator.
*/
class Decorator {
/**
* @var
*/
protected $object;
/**
* Class constructor.
*
* @param object $object
* The object to decorate.
*/
public function __construct($object) {
$this->object = $object;
}
/**
* Return the original (i.e. non decorated) object.
*
* @return mixed
* The original object.
*/
public function getOriginalObject() {
$object = $this->object;
while ($object instanceof Decorator) {
$object = $object->getOriginalObject();
}
return $object;
}
/**
* Returns true if $method is a PHP callable.
*
* @param string $method
* The method name.
* @param bool $checkSelf
*
* @return bool|mixed
*/
public function isCallable($method, $checkSelf = FALSE) {
//Check the original object
$object = $this->getOriginalObject();
if (is_callable([$object, $method])) {
return $object;
}
// Check Decorators.
$object = $checkSelf ? $this : $this->object;
while ($object instanceof Decorator) {
if (is_callable([$object, $method])) {
return $object;
}
$object = $this->object;
}
return FALSE;
}
/**
* @param $method
* @param $args
*
* @return mixed
*
* @throws \Exception
*/
public function __call($method, $args) {
if ($object = $this->isCallable($method)) {
return call_user_func_array([$object, $method], $args);
}
throw new \Exception(
'Undefined method - ' . get_class($this->getOriginalObject()) . '::' . $method
);
}
/**
* @param $property
*
* @return null
*/
public function __get($property) {
$object = $this->getOriginalObject();
if (property_exists($object, $property)) {
return $object->$property;
}
return NULL;
}
}
@@ -0,0 +1,66 @@
<?php
namespace Drupal\webprofiler\DependencyInjection;
use Drupal\Component\Utility\Timer;
use Drupal\Core\DependencyInjection\Container;
/**
* Extends the Drupal container class to trace service instantiations.
*/
class TraceableContainer extends Container {
/**
* @var array
*/
protected $tracedData;
/**
* @var \Symfony\Component\Stopwatch\Stopwatch
*/
private $stopwatch = NULL;
/**
* @var bool
*/
private $hasStopwatch = FALSE;
/**
* @param string $id
* @param int $invalidBehavior
*
* @return object
*/
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) {
if (!$this->stopwatch && $this->has('stopwatch')) {
$this->stopwatch = parent::get('stopwatch');
$this->stopwatch->openSection();
$this->hasStopwatch = TRUE;
}
if ('stopwatch' === $id) {
return $this->stopwatch;
}
Timer::start($id);
if ($this->hasStopwatch) {
$e = $this->stopwatch->start($id, 'service');
}
$service = parent::get($id, $invalidBehavior);
$this->tracedData[$id] = Timer::stop($id);
if ($this->hasStopwatch && $e->isStarted()) {
$e->stop();
}
return $service;
}
/**
* @return array
*/
public function getTracedData() {
return $this->tracedData;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\webprofiler;
/**
* Interface DrupalDataCollectorInterface.
*/
interface DrupalDataCollectorInterface {
/**
* Returns the datacollector title.
*
* @return string
* The datacollector title.
*/
public function getTitle();
/**
* Returns the name of the collector.
*
* @return string
* The collector name.
*/
public function getName();
/**
* Returns the string used in vertical tab summary.
*
* @return string
* The panel summary.
*/
public function getPanelSummary();
/**
* Returns the collector icon in base64 format.
*
* @return string
* The collector icon.
*/
public function getIcon();
/**
* Returns true if this datacollector has a detail panel.
*
* @return bool
* True if datacollector has a detail panel, false otherwise.
*/
public function hasPanel();
/**
* Returns the libraries needed in detail panel.
*
* @return array
* The render array for detail panel.
*/
public function getLibraries();
/**
* @return array
*/
public function getDrupalSettings();
/**
* @return mixed
*/
public function getData();
}
@@ -0,0 +1,214 @@
<?php
namespace Drupal\webprofiler\Entity\Decorators\Config;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\webprofiler\Entity\EntityDecorator;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class ConfigEntityStorageDecorator
*/
class ConfigEntityStorageDecorator extends EntityDecorator implements ConfigEntityStorageInterface, ImportableEntityStorageInterface, EntityHandlerInterface {
/**
* @param ConfigEntityStorageInterface $controller
*/
public function __construct(ConfigEntityStorageInterface $controller) {
parent::__construct($controller);
$this->entities = [];
}
/**
* {@inheritdoc}
*/
public function resetCache(array $ids = NULL) {
$this->getOriginalObject()->resetCache($ids);
}
/**
* {@inheritdoc}
*/
public function loadMultiple(array $ids = NULL) {
$entities = $this->getOriginalObject()->loadMultiple($ids);
$this->entities = array_merge($this->entities, $entities);
return $entities;
}
/**
* {@inheritdoc}
*/
public function load($id) {
$entity = $this->getOriginalObject()->load($id);
$this->entities[$id] = $entity;
return $entity;
}
/**
* {@inheritdoc}
*/
public function loadUnchanged($id) {
return $this->getOriginalObject()->loadUnchanged($id);
}
/**
* {@inheritdoc}
*/
public function loadRevision($revision_id) {
return $this->getOriginalObject()->loadRevision($revision_id);
}
/**
* {@inheritdoc}
*/
public function deleteRevision($revision_id) {
$this->getOriginalObject()->deleteRevision($revision_id);
}
/**
* {@inheritdoc}
*/
public function loadByProperties(array $values = []) {
$entities = $this->getOriginalObject()->loadByProperties($values);
$this->entities = array_merge($this->entities, $entities);
return $entities;
}
/**
* {@inheritdoc}
*/
public function create(array $values = []) {
return $this->getOriginalObject()->create($values);
}
/**
* {@inheritdoc}
*/
public function delete(array $entities) {
$this->getOriginalObject()->delete($entities);
}
/**
* {@inheritdoc}
*/
public function save(EntityInterface $entity) {
return $this->getOriginalObject()->save($entity);
}
/**
* {@inheritdoc}
*/
public function getQuery($conjunction = 'AND') {
return $this->getOriginalObject()->getQuery($conjunction);
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->getOriginalObject()->getEntityTypeId();
}
/**
* {@inheritdoc}
*/
public function getEntityType() {
return $this->getOriginalObject()->getEntityType();
}
/**
* {@inheritdoc}
*/
public static function getIDFromConfigName($config_name, $config_prefix) {
return substr($config_name, strlen($config_prefix . '.'));
}
/**
* {@inheritdoc}
*/
public function createFromStorageRecord(array $values) {
return $this->getOriginalObject()->createFromStorageRecord($values);
}
/**
* {@inheritdoc}
*/
public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) {
return $this->getOriginalObject()
->updateFromStorageRecord($entity, $values);
}
/**
* {@inheritdoc}
*/
public function getAggregateQuery($conjunction = 'AND') {
return $this->getOriginalObject()->getAggregateQuery($conjunction);
}
/**
* {@inheritdoc}
*/
public function loadOverrideFree($id) {
return $this->getOriginalObject()->loadOverrideFree($id);
}
/**
* {@inheritdoc}
*/
public function loadMultipleOverrideFree(array $ids = NULL) {
return $this->getOriginalObject()->loadMultipleOverrideFree($ids);
}
/**
* {@inheritdoc}
*/
public function importCreate($name, Config $new_config, Config $old_config) {
$this->getOriginalObject()->importCreate($name, $new_config, $old_config);
}
/**
* {@inheritdoc}
*/
public function importUpdate($name, Config $new_config, Config $old_config) {
$this->getOriginalObject()->importUpdate($name, $new_config, $old_config);
}
/**
* {@inheritdoc}
*/
public function importDelete($name, Config $new_config, Config $old_config) {
$this->getOriginalObject()->importDelete($name, $new_config, $old_config);
}
/**
* {@inheritdoc}
*/
public function importRename($old_name, Config $new_config, Config $old_config) {
$this->getOriginalObject()->importRename($old_name, $new_config, $old_config);
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('language_manager')
);
}
}
@@ -0,0 +1,19 @@
<?php
namespace Drupal\webprofiler\Entity\Decorators\Config;
use Drupal\user\RoleStorageInterface;
/**
* Class RoleStorageDecorator
*/
class RoleStorageDecorator extends ConfigEntityStorageDecorator implements RoleStorageInterface {
/**
* {@inheritdoc}
*/
public function isPermissionInRoles($permission, array $rids) {
return $this->getOriginalObject()->isPermissionInRoles($permission, $rids);
}
}
@@ -0,0 +1,56 @@
<?php
namespace Drupal\webprofiler\Entity\Decorators\Config;
use Drupal\Core\Session\AccountInterface;
use Drupal\shortcut\ShortcutSetInterface;
use Drupal\shortcut\ShortcutSetStorageInterface;
/**
* Class ShortcutSetStorageDecorator
*/
class ShortcutSetStorageDecorator extends ConfigEntityStorageDecorator implements ShortcutSetStorageInterface {
/**
* {@inheritdoc}
*/
public function assignUser(ShortcutSetInterface $shortcut_set, $account) {
// TODO: Implement assignUser() method.
}
/**
* {@inheritdoc}
*/
public function unassignUser($account) {
// TODO: Implement unassignUser() method.
}
/**
* {@inheritdoc}
*/
public function deleteAssignedShortcutSets(ShortcutSetInterface $entity) {
// TODO: Implement deleteAssignedShortcutSets() method.
}
/**
* {@inheritdoc}
*/
public function getAssignedToUser($account) {
// TODO: Implement getAssignedToUser() method.
}
/**
* {@inheritdoc}
*/
public function countAssignedUsers(ShortcutSetInterface $shortcut_set) {
// TODO: Implement countAssignedUsers() method.
}
/**
* {@inheritdoc}
*/
public function getDefaultSet(AccountInterface $account) {
// TODO: Implement getDefaultSet() method.
}
}
@@ -0,0 +1,19 @@
<?php
namespace Drupal\webprofiler\Entity\Decorators\Config;
use Drupal\taxonomy\VocabularyStorageInterface;
/**
* Class EntityStorageDecorator
*/
class VocabularyStorageDecorator extends ConfigEntityStorageDecorator implements VocabularyStorageInterface {
/**
* {@inheritdoc}
*/
public function getToplevelTids($vids) {
$this->getOriginalObject()->getToplevelTids($vids);
}
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\webprofiler\Entity;
use Drupal\webprofiler\Decorator;
/**
* Class EntityDecorator
*/
class EntityDecorator extends Decorator {
/**
* @var array
*/
protected $entities;
/**
* @return mixed
*/
public function getEntities() {
return $this->entities;
}
}
@@ -0,0 +1,239 @@
<?php
namespace Drupal\webprofiler\Entity;
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\webprofiler\Entity\Decorators\Config\ConfigEntityStorageDecorator;
use Drupal\webprofiler\Entity\Decorators\Config\RoleStorageDecorator;
use Drupal\webprofiler\Entity\Decorators\Config\ShortcutSetStorageDecorator;
use Drupal\webprofiler\Entity\Decorators\Config\VocabularyStorageDecorator;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class EntityManagerWrapper
*/
class EntityManagerWrapper extends DefaultPluginManager implements EntityTypeManagerInterface, ContainerAwareInterface {
/**
* @var array
*/
private $loaded;
/**
* @var array
*/
private $rendered;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
private $entityManager;
/**
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityManager
*/
public function __construct(EntityTypeManagerInterface $entityManager) {
$this->entityManager = $entityManager;
}
/**
* {@inheritdoc}
*/
public function getStorage($entity_type) {
/** @var ConfigEntityStorageInterface $handler */
$handler = $this->getHandler($entity_type, 'storage');
$type = ($handler instanceof ConfigEntityStorageInterface) ? 'config' : 'content';
if (!isset($this->loaded[$type][$entity_type])) {
$handler = $this->getStorageDecorator($entity_type, $handler);
$this->loaded[$type][$entity_type] = $handler;
}
else {
$handler = $this->loaded[$type][$entity_type];
}
return $handler;
}
/**
* {@inheritdoc}
*/
public function getViewBuilder($entity_type) {
/** @var EntityViewBuilderInterface $handler */
$handler = $this->getHandler($entity_type, 'view_builder');
if ($handler instanceof EntityViewBuilderInterface) {
if (!isset($this->rendered[$entity_type])) {
$handler = new EntityViewBuilderDecorator($handler);
$this->rendered[$entity_type] = $handler;
}
else {
$handler = $this->rendered[$entity_type];
}
}
return $handler;
}
/**
* @param $entity_type
* @param $handler
*
* @return \Drupal\webprofiler\Entity\EntityDecorator
*/
private function getStorageDecorator($entity_type, $handler) {
if ($handler instanceof ConfigEntityStorageInterface) {
switch ($entity_type) {
case 'taxonomy_vocabulary':
return new VocabularyStorageDecorator($handler);
break;
case 'user_role':
return new RoleStorageDecorator($handler);
break;
case 'shortcut_set':
return new ShortcutSetStorageDecorator($handler);
break;
default:
return new ConfigEntityStorageDecorator($handler);
break;
}
}
return $handler;
}
/**
* @param $type
* @param $entity_type
*
* @return array
*/
public function getLoaded($type, $entity_type) {
return isset($this->loaded[$type][$entity_type]) ? $this->loaded[$type][$entity_type] : NULL;
}
/**
* @param $entity_type
*
* @return array
*/
public function getRendered( $entity_type) {
return isset($this->rendered[$entity_type]) ? $this->rendered[$entity_type] : NULL;
}
/**
* {@inheritdoc}
*/
public function useCaches($use_caches = FALSE) {
$this->entityManager->useCaches($use_caches);
}
/**
* {@inheritdoc}
*/
public function hasDefinition($plugin_id) {
return $this->entityManager->hasDefinition($plugin_id);
}
/**
* {@inheritdoc}
*/
public function getAccessControlHandler($entity_type) {
return $this->entityManager->getAccessControlHandler($entity_type);
}
/**
* {@inheritdoc}
*/
public function clearCachedDefinitions() {
$this->entityManager->clearCachedDefinitions();
}
/**
* {@inheritdoc}
*/
public function getListBuilder($entity_type) {
return $this->entityManager->getListBuilder($entity_type);
}
/**
* {@inheritdoc}
*/
public function getFormObject($entity_type, $operation) {
return $this->entityManager->getFormObject($entity_type, $operation);
}
/**
* {@inheritdoc}
*/
public function getRouteProviders($entity_type) {
return $this->entityManager->getRouteProviders($entity_type);
}
/**
* {@inheritdoc}
*/
public function hasHandler($entity_type, $handler_type) {
return $this->entityManager->hasHandler($entity_type, $handler_type);
}
/**
* {@inheritdoc}
*/
public function getHandler($entity_type, $handler_type) {
return $this->entityManager->getHandler($entity_type, $handler_type);
}
/**
* {@inheritdoc}
*/
public function createHandlerInstance(
$class,
EntityTypeInterface $definition = NULL
) {
return $this->entityManager->createHandlerInstance($class, $definition);
}
/**
* {@inheritdoc}
*/
public function getDefinition($entity_type_id, $exception_on_invalid = TRUE) {
return $this->entityManager->getDefinition(
$entity_type_id,
$exception_on_invalid
);
}
/**
* {@inheritdoc}
*/
public function getDefinitions() {
return $this->entityManager->getDefinitions();
}
/**
* {@inheritdoc}
*/
public function createInstance($plugin_id, array $configuration = []) {
return $this->entityManager->createInstance($plugin_id, $configuration);
}
/**
* {@inheritdoc}
*/
public function getInstance(array $options) {
return $this->entityManager->getInstance($options);
}
/**
* {@inheritdoc}
*/
public function setContainer(ContainerInterface $container = NULL) {
$this->entityManager->setContainer($container);
}
}
@@ -0,0 +1,92 @@
<?php
namespace Drupal\webprofiler\Entity;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class EntityViewBuilderDecorator
*/
class EntityViewBuilderDecorator extends EntityDecorator implements EntityHandlerInterface, EntityViewBuilderInterface {
/**
* @param EntityViewBuilderInterface $controller
*/
public function __construct(EntityViewBuilderInterface $controller) {
parent::__construct($controller);
$this->entities = [];
}
/**
* {@inheritdoc}
*/
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
$this->getOriginalObject()
->buildComponents($build, $entities, $displays, $view_mode, $langcode);
}
/**
* {@inheritdoc}
*/
public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
$this->entities[] = $entity;
return $this->getOriginalObject()->view($entity, $view_mode, $langcode);
}
/**
* {@inheritdoc}
*/
public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) {
$this->entities = array_merge($this->entities, $entities);
return $this->getOriginalObject()
->viewMultiple($entities, $view_mode, $langcode);
}
/**
* {@inheritdoc}
*/
public function resetCache(array $entities = NULL) {
$this->getOriginalObject()->resetCache($entities);
}
/**
* {@inheritdoc}
*/
public function viewField(FieldItemListInterface $items, $display_options = []) {
return $this->getOriginalObject()->viewField($items, $display_options);
}
/**
* {@inheritdoc}
*/
public function viewFieldItem(FieldItemInterface $item, $display_options = []) {
return $this->getOriginalObject()->viewFieldItem($item, $display_options);
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return $this->getOriginalObject()->getCacheTag();
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager'),
$container->get('language_manager')
);
}
}
@@ -0,0 +1,97 @@
<?php
namespace Drupal\webprofiler\EventSubscriber;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class WebprofilerEventSubscriber
*/
class WebprofilerEventSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Session\AccountInterface
*/
private $currentUser;
/**
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* @param \Drupal\Core\Session\AccountInterface $currentUser
* @param \Drupal\Core\Routing\UrlGeneratorInterface $urlGenerator
* @param \Drupal\Core\Render\RendererInterface $renderer
*/
public function __construct(AccountInterface $currentUser, UrlGeneratorInterface $urlGenerator, RendererInterface $renderer) {
$this->currentUser = $currentUser;
$this->urlGenerator = $urlGenerator;
$this->renderer = $renderer;
}
/**
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
*/
public function onKernelResponse(FilterResponseEvent $event) {
$response = $event->getResponse();
$request = $event->getRequest();
if ($response->headers->has('X-Debug-Token') && NULL !== $this->urlGenerator) {
$response->headers->set(
'X-Debug-Token-Link',
$this->urlGenerator->generate('webprofiler.dashboard', ['profile' => $response->headers->get('X-Debug-Token')])
);
}
// do not capture redirects or modify XML HTTP Requests
if ($request->isXmlHttpRequest()) {
return;
}
if ($this->currentUser->hasPermission('view webprofiler toolbar')) {
$this->injectToolbar($response);
}
}
/**
* @param \Symfony\Component\HttpFoundation\Response $response
*/
protected function injectToolbar(Response $response) {
$content = $response->getContent();
$pos = mb_strripos($content, '</body>');
if (FALSE !== $pos) {
if ($token = $response->headers->get('X-Debug-Token')) {
$loader = [
'#theme' => 'webprofiler_loader',
'#token' => $token,
'#profiler_url' => $this->urlGenerator->generate('webprofiler.toolbar', ['profile' => $token]),
];
$content = mb_substr($content, 0, $pos) . $this->renderer->renderRoot($loader) . mb_substr($content, $pos);
$response->setContent($content);
}
}
}
/**
* @return array
*/
public static function getSubscribedEvents() {
return [
KernelEvents::RESPONSE => ['onKernelResponse', -128],
];
}
}
@@ -0,0 +1,237 @@
<?php
namespace Drupal\webprofiler\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\webprofiler\Profiler\ProfilerStorageManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Profiler\Profiler;
/**
* Class ConfigForm
*/
class ConfigForm extends ConfigFormBase {
/**
* @var \Symfony\Component\HttpKernel\Profiler\Profiler
*/
private $profiler;
/**
* @var array
*/
private $templates;
/**
* @var \Drupal\webprofiler\Profiler\ProfilerStorageManager
*/
private $storageManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('profiler'),
$container->get('profiler.storage_manager'),
$container->getParameter('data_collector.templates')
);
}
/**
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* @param Profiler $profiler
* @param \Drupal\webprofiler\Profiler\ProfilerStorageManager $storageManager
* @param array $templates
*/
public function __construct(ConfigFactoryInterface $config_factory, Profiler $profiler, ProfilerStorageManager $storageManager, $templates) {
parent::__construct($config_factory);
$this->profiler = $profiler;
$this->templates = $templates;
$this->storageManager = $storageManager;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'webprofiler_config';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('webprofiler.config');
$form['purge_on_cache_clear'] = [
'#type' => 'checkbox',
'#title' => $this->t('Purge on cache clear'),
'#description' => $this->t('Deletes all profiler files during cache clear.'),
'#default_value' => $config->get('purge_on_cache_clear'),
];
$storages = $this->storageManager->getStorages();
$form['storage'] = [
'#type' => 'select',
'#title' => $this->t('Storage backend'),
'#description' => $this->t('Choose were to store profiler data.'),
'#options' => $storages,
'#default_value' => $config->get('storage'),
];
$form['exclude'] = [
'#type' => 'textarea',
'#title' => $this->t('Exclude'),
'#default_value' => $config->get('exclude'),
'#description' => $this->t('Paths to exclude for profiling. One path per line.'),
];
$form['active_toolbar_items'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Active toolbar items'),
'#options' => $this->getCollectors(),
'#description' => $this->t('Choose which items to show into the toolbar.'),
'#default_value' => $config->get('active_toolbar_items'),
];
$form['ide_settings'] = [
'#type' => 'details',
'#title' => $this->t('IDE settings'),
'#open' => FALSE,
];
$form['ide_settings']['ide_link'] = [
'#type' => 'textfield',
'#title' => $this->t('IDE link'),
'#description' => $this->t('IDE link for open files.'),
'#default_value' => $config->get('ide_link'),
];
$form['ide_settings']['ide_link_remote'] = [
'#type' => 'textfield',
'#title' => $this->t('IDE link remote path'),
'#description' => $this->t('The path of the remote docroot. Leave blank if the docroot is on the same machine of the IDE. No trailing slash.'),
'#default_value' => $config->get('ide_link_remote'),
];
$form['ide_settings']['ide_link_local'] = [
'#type' => 'textfield',
'#title' => $this->t('IDE link local path'),
'#description' => $this->t('The path of the local docroot. Leave blank if the docroot is on the same machine of IDE. No trailing slash.'),
'#default_value' => $config->get('ide_link_local'),
];
$form['database'] = [
'#type' => 'details',
'#title' => $this->t('Database settings'),
'#open' => FALSE,
'#states' => array(
'visible' => array(
array(
':input[name="active_toolbar_items[database]' => array('checked' => TRUE),
),
),
),
];
$form['database']['query_sort'] = [
'#type' => 'radios',
'#title' => $this->t('Sort query log'),
'#options' => ['source' => 'by source', 'duration' => 'by duration'],
'#description' => $this->t('The query table can be sorted in the order that the queries were executed or by descending duration.'),
'#default_value' => $config->get('query_sort'),
];
$form['database']['query_highlight'] = [
'#type' => 'number',
'#title' => $this->t('Slow query highlighting'),
'#description' => $this->t('Enter an integer in milliseconds. Any query which takes longer than this many milliseconds will be highlighted in the query log. This indicates a possibly inefficient query, or a candidate for caching.'),
'#default_value' => $config->get('query_highlight'),
'#min' => 0,
];
$storageId = $this->config('webprofiler.config')->get('storage');
$storage = $this->storageManager->getStorage($storageId);
$form['purge'] = [
'#type' => 'details',
'#title' => $this->t('Purge profiles'),
'#open' => FALSE,
];
$form['purge']['purge'] = [
'#type' => 'submit',
'#value' => $this->t('Purge'),
'#submit' => [[$this, 'purge']],
];
$form['purge']['purge-help'] = [
'#type' => 'inline_template',
'#template' => '<div class="form-item">{{ message }}</div>',
'#context' => [
'message' => $this->t('Purge %storage profiles.', ['%storage' => $storage['title']]),
],
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('webprofiler.config')
->set('purge_on_cache_clear', $form_state->getValue('purge_on_cache_clear'))
->set('storage', $form_state->getValue('storage'))
->set('exclude', $form_state->getValue('exclude'))
->set('active_toolbar_items', $form_state->getValue('active_toolbar_items'))
->set('ide_link', $form_state->getValue('ide_link'))
->set('ide_link_remote', $form_state->getValue('ide_link_remote'))
->set('ide_link_local', $form_state->getValue('ide_link_local'))
->set('query_sort', $form_state->getValue('query_sort'))
->set('query_highlight', $form_state->getValue('query_highlight'))
->save();
parent::submitForm($form, $form_state);
}
/**
* Purges profiles.
*/
public function purge(array &$form, FormStateInterface $form_state) {
$this->profiler->purge();
drupal_set_message($this->t('Profiles purged'));
}
/**
* @return array
*/
private function getCollectors() {
$options = [];
foreach ($this->templates as $template) {
// drupal collector should not be disabled
if ($template[0] != 'drupal') {
$options[$template[0]] = $template[2];
}
}
asort($options);
return $options;
}
/**
* {@inheritdoc}
*/
public function getEditableConfigNames() {
return [
'webprofiler.config',
];
}
}
@@ -0,0 +1,61 @@
<?php
namespace Drupal\webprofiler\Form;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Form\FormStateInterface;
/**
* Class FormBuilderWrapper
*/
class FormBuilderWrapper extends FormBuilder {
/**
* @var array
*/
private $buildForms;
/**
* @return array
*/
public function getBuildForm() {
return $this->buildForms;
}
/**
* {@inheritdoc}
*/
public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
parent::prepareForm($form_id, $form, $form_state);
if (!$this->buildForms) {
$this->buildForms = [];
}
$elements = [];
foreach ($form as $key => $value) {
if (strpos($key, '#') !== 0) {
$elements[$key]['#title'] = isset($value['#title']) ? $value['#title'] : NULL;
$elements[$key]['#access'] = isset($value['#access']) ? $value['#access'] : NULL;
$elements[$key]['#type'] = isset($value['#type']) ? $value['#type'] : NULL;
}
}
$buildInfo = $form_state->getBuildInfo();
$class = get_class($buildInfo['callback_object']);
$method = new \ReflectionMethod($class, 'buildForm');
$this->buildForms[$buildInfo['form_id']] = [
'class' => [
'class' => $class,
'method' => 'buildForm',
'file' => $method->getFilename(),
'line' => $method->getStartLine(),
],
'form' => $elements,
];
return $form;
}
}
@@ -0,0 +1,86 @@
<?php
namespace Drupal\webprofiler\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Class ProfilesFilterForm
*/
class ProfilesFilterForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'webprofiler_profiles_filter';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['ip'] = [
'#type' => 'textfield',
'#title' => $this->t('IP'),
'#size' => 30,
'#default_value' => $this->getRequest()->query->get('ip'),
'#prefix' => '<div class="form--inline clearfix">',
];
$form['url'] = [
'#type' => 'textfield',
'#title' => $this->t('Url'),
'#size' => 30,
'#default_value' => $this->getRequest()->query->get('url'),
];
$form['method'] = [
'#type' => 'select',
'#title' => $this->t('Method'),
'#options' => ['GET' => 'GET', 'POST' => 'POST'],
'#default_value' => $this->getRequest()->query->get('method'),
];
$limits = [10, 50, 100];
$form['limit'] = [
'#type' => 'select',
'#title' => $this->t('Limit'),
'#options' => array_combine($limits, $limits),
'#default_value' => $this->getRequest()->query->get('limit'),
];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['filter'] = [
'#type' => 'submit',
'#value' => t('Filter'),
'#attributes' => ['class' => ['button--primary']],
'#suffix' => '</div>',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$ip = $form_state->getValue('ip');// ['values']['ip'];
$url = $form_state->getValue('url');
$method = $form_state->getValue('method');
$limit = $form_state->getValue('limit');
$url = new Url('webprofiler.admin_list', [], [
'query' => [
'ip' => $ip,
'url' => $url,
'method' => $method,
'limit' => $limit,
]
]);
$form_state->setRedirectUrl($url);
}
}
@@ -0,0 +1,81 @@
<?php
namespace Drupal\webprofiler\Frontend;
/**
* Class PerformanceTimingData
*/
class PerformanceTimingData {
/**
* @var array
*/
private $data;
/**
* @param array $data
*/
public function __construct($data) {
$this->data = $data;
}
/**
* @return int
*/
public function getDNSTiming() {
if (isset($this->data['domainLookupEnd']) && isset($this->data['domainLookupStart'])) {
return $this->data['domainLookupEnd'] - $this->data['domainLookupStart'];
}
else {
return 0;
}
}
/**
* @return int
*/
public function getTCPTiming() {
if (isset($this->data['connectEnd']) && isset($this->data['connectStart'])) {
return $this->data['connectEnd'] - $this->data['connectStart'];
}
else {
return 0;
}
}
/**
* @return int
*/
public function getTtfbTiming() {
if (isset($this->data['responseStart']) && isset($this->data['connectEnd'])) {
return $this->data['responseStart'] - $this->data['connectEnd'];
}
else {
return 0;
}
}
/**
* @return int
*/
public function getDataTiming() {
if (isset($this->data['responseEnd']) && isset($this->data['responseStart'])) {
return $this->data['responseEnd'] - $this->data['responseStart'];
}
else {
return 0;
}
}
/**
* @return int
*/
public function getDomTiming() {
if (isset($this->data['loadEventStart']) && isset($this->data['responseEnd'])) {
return $this->data['loadEventStart'] - $this->data['responseEnd'];
}
else {
return 0;
}
}
}
@@ -0,0 +1,34 @@
<?php
namespace Drupal\webprofiler\Helper;
use Drupal\Component\Render\FormattableMarkup;
/**
* Class ClassShortener
*/
class ClassShortener implements ClassShortenerInterface {
/**
* {@inheritdoc}
*/
public function shortenClass($class) {
$parts = explode('\\', $class);
$result = [];
$size = count($parts) - 1;
foreach ($parts as $key => $part) {
if ($key < $size) {
$result[] = substr($part, 0, 1);
}
else {
$result[] = $part;
}
}
return new FormattableMarkup("<abbr title=\"@class\">@short</abbr>", [
'@class' => $class,
'@short' => implode('\\', $result)
]);
}
}
@@ -0,0 +1,17 @@
<?php
namespace Drupal\webprofiler\Helper;
/**
* Interface ClassShortenerInterface
*/
interface ClassShortenerInterface {
/**
* @param string $class
*
* @return string
*/
public function shortenClass($class);
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\webprofiler\Helper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Component\Render\FormattableMarkup;
/**
* Class IdeLinkGenerator.
*/
class IdeLinkGenerator implements IdeLinkGeneratorInterface {
/**
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
private $config_factory;
/**
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
*/
public function __construct(ConfigFactoryInterface $config_factory) {
$this->config_factory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function generateLink($file, $line) {
$ide_link = $this->config_factory->get('webprofiler.config')
->get('ide_link');
$ide_link_remote = $this->config_factory->get('webprofiler.config')
->get('ide_link_remote');
$ide_link_local = $this->config_factory->get('webprofiler.config')
->get('ide_link_local');
$file = str_replace($ide_link_remote, $ide_link_local, $file);
return new FormattableMarkup($ide_link, ['@file' => $file, '@line' => $line]);
}
}
@@ -0,0 +1,17 @@
<?php
namespace Drupal\webprofiler\Helper;
/**
* Interface IdeLinkGeneratorInterface.
*/
interface IdeLinkGeneratorInterface {
/**
* @param $file
* @param $line
*
* @return string
*/
public function generateLink($file, $line);
}
@@ -0,0 +1,84 @@
<?php
namespace Drupal\webprofiler\Http;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
/**
* Class HttpClientMiddleware
*/
class HttpClientMiddleware {
/**
* @var array
*/
private $completedRequests;
/**
* @var array
*/
private $failedRequests;
/**
*
*/
public function __construct() {
$this->completedRequests = [];
$this->failedRequests = [];
}
/**
* {@inheritdoc}
*/
public function __invoke() {
return function ($handler) {
return function (RequestInterface $request, array $options) use ($handler) {
$options['on_stats'] = function (TransferStats $stats) use ($request) {
$request->stats = $stats;
};
return $handler($request, $options)->then(
function ($response) use ($request) {
$this->completedRequests[] = [
'request' => $request,
'response' => $response,
];
return $response;
},
function ($reason) use ($request) {
$response = $reason instanceof RequestException
? $reason->getResponse()
: null;
$this->failedRequests[] = [
'request' => $request,
'response' => $response,
'message' => $reason->getMessage(),
];
return \GuzzleHttp\Promise\rejection_for($reason);
}
);
};
};
}
/**
* @return array
*/
public function getCompletedRequests() {
return $this->completedRequests;
}
/**
* @return array
*/
public function getFailedRequests() {
return $this->failedRequests;
}
}
@@ -0,0 +1,94 @@
<?php
namespace Drupal\webprofiler\Mail;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Mail\MailManagerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\webprofiler\DataCollector\MailDataCollector;
/**
* Class MailManagerWrapper
*/
class MailManagerWrapper extends DefaultPluginManager implements MailManagerInterface {
use StringTranslationTrait;
/**
* @var \Drupal\webprofiler\DataCollector\MailDataCollector
*/
private $dataCollector;
/**
* @var \Drupal\Core\Mail\MailManagerInterface
*/
private $mailManager;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* List of already instantiated mail plugins.
*
* @var array
*/
protected $instances = array();
/**
* Constructs the MailManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger channel factory.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Mail\MailManagerInterface $mailManager
* @param \Drupal\webprofiler\DataCollector\MailDataCollector $dataCollector
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, TranslationInterface $string_translation, MailManagerInterface $mailManager, MailDataCollector $dataCollector) {
parent::__construct('Plugin/Mail', $namespaces, $module_handler, 'Drupal\Core\Mail\MailInterface', 'Drupal\Core\Annotation\Mail');
$this->alterInfo('mail_backend_info');
$this->setCacheBackend($cache_backend, 'mail_backend_plugins');
$this->configFactory = $config_factory;
$this->loggerFactory = $logger_factory;
$this->stringTranslation = $string_translation;
$this->dataCollector = $dataCollector;
$this->mailManager = $mailManager;
}
/**
* {@inheritdoc}
*/
public function mail($module, $key, $to, $langcode, $params = array(), $reply = NULL, $send = TRUE) {
$message = $this->mailManager->mail($module, $key, $to, $langcode, $params, $reply, $send);
$instance = $this->mailManager->getInstance(['module' => $module, 'key' => $key]);
$this->dataCollector->addMessage($message, $instance);
return $message;
}
}
@@ -0,0 +1,158 @@
<?php
namespace Drupal\webprofiler\Profiler;
use Drupal\Core\Database\Connection;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
/**
* Implements a profiler storage using the DBTNG query api.
*/
class DatabaseProfilerStorage implements ProfilerStorageInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a new DatabaseProfilerStorage instance.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
*/
function __construct(Connection $database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit, $method, $start = NULL, $end = NULL) {
$select = $this->database->select('webprofiler', 'wp', ['fetch' => \PDO::FETCH_ASSOC]);
if (NULL === $start) {
$start = 0;
}
if (NULL === $end) {
$end = time();
}
if ($ip = preg_replace('/[^\d\.]/', '', $ip)) {
$select->condition('ip', '%' . $this->database->escapeLike($ip) . '%', 'LIKE');
}
if ($url) {
$select->condition('url', '%' . $this->database->escapeLike(addcslashes($url, '%_\\')) . '%', 'LIKE');
}
if ($method) {
$select->condition('method', $method);
}
if (!empty($start)) {
$select->condition('time', $start, '>=');
}
if (!empty($end)) {
$select->condition('time', $end, '<=');
}
$select->fields('wp', [
'token',
'ip',
'method',
'url',
'time',
'parent',
'status_code'
]);
$select->orderBy('time', 'DESC');
$select->range(0, $limit);
return $select->execute()
->fetchAllAssoc('token');
}
/**
* {@inheritdoc}
*/
public function read($token) {
$record = $this->database->select('webprofiler', 'w')
->fields('w')
->condition('token', $token)
->execute()
->fetch();
if (isset($record->data)) {
return $this->createProfileFromData($token, $record);
}
}
/**
* {@inheritdoc}
*/
public function write(Profile $profile) {
$args = [
'token' => $profile->getToken(),
'parent' => $profile->getParentToken(),
'data' => base64_encode(serialize($profile->getCollectors())),
'ip' => $profile->getIp(),
'method' => $profile->getMethod(),
'url' => $profile->getUrl(),
'time' => $profile->getTime(),
'created_at' => time(),
'status_code' => $profile->getStatusCode(),
];
try {
$query = $this->database->select('webprofiler', 'w')
->fields('w', ['token']);
$query->condition('token', $profile->getToken());
$count = $query->countQuery()->execute()->fetchAssoc();
if ($count['expression']) {
$this->database->update('webprofiler')
->fields($args)
->condition('token', $profile->getToken())
->execute();
}
else {
$this->database->insert('webprofiler')->fields($args)->execute();
}
$status = TRUE;
} catch (\Exception $e) {
$status = FALSE;
}
return $status;
}
/**
* {@inheritdoc}
*/
public function purge() {
$this->database->truncate('webprofiler')->execute();
}
/**
* @param string $token
* @param $data
*
* @return Profile
*/
private function createProfileFromData($token, $data) {
$profile = new Profile($token);
$profile->setIp($data->ip);
$profile->setMethod($data->method);
$profile->setUrl($data->url);
$profile->setTime($data->time);
$profile->setCollectors(unserialize(base64_decode($data->data)));
$profile->setStatusCode($data->status_code);
return $profile;
}
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\webprofiler\Profiler;
use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage as SymfonyFileProfilerStorage;
use Symfony\Component\HttpKernel\Profiler\Profile;
class FileProfilerStorage extends SymfonyFileProfilerStorage {
/**
* {@inheritdoc}
*/
protected function createProfileFromData($token, $data, $parent = NULL) {
$profile = new Profile($token);
$profile->setIp($data['ip']);
$profile->setMethod($data['method']);
$profile->setUrl($data['url']);
$profile->setTime($data['time']);
$profile->setCollectors($data['data']);
$profile->setStatusCode($data['status_code']);
return $profile;
}
}
@@ -0,0 +1,77 @@
<?php
namespace Drupal\webprofiler\Profiler;
use Drupal\Core\Config\ConfigFactoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler as SymfonyProfiler;
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
/**
* Class Profiler
*/
class Profiler extends SymfonyProfiler {
/**
* @var \Drupal\Core\Config\ConfigFactoryInterface $config
*/
private $config;
/**
* @var array
*/
private $activeToolbarItems;
private $localStorage;
private $localLogger;
/**
* Constructor.
*
* @param \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface $storage
* A ProfilerStorageInterface instance
* @param \Psr\Log\LoggerInterface $logger
* A LoggerInterface instance
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
*/
public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = NULL, ConfigFactoryInterface $config) {
parent::__construct($storage, $logger);
$this->localStorage = $storage;
$this->localLogger = $logger;
$this->config = $config;
$this->activeToolbarItems = $this->config->get('webprofiler.config')
->get('active_toolbar_items');
}
/**
* {@inheritdoc}
*/
public function add(DataCollectorInterface $collector) {
// drupal collector should not be disabled
if ($collector->getName() == 'drupal') {
parent::add($collector);
}
else {
if ($this->activeToolbarItems && array_key_exists($collector->getName(), $this->activeToolbarItems) && $this->activeToolbarItems[$collector->getName()] !== '0') {
parent::add($collector);
}
}
}
/**
* @param \Symfony\Component\HttpKernel\Profiler\Profile $profile
*
* @return bool
*/
public function updateProfile(Profile $profile) {
if (!($ret = $this->localStorage->write($profile)) && NULL !== $this->localLogger) {
$this->localLogger->warning('Unable to store the profiler information.');
}
return $ret;
}
}
@@ -0,0 +1,26 @@
<?php
namespace Drupal\webprofiler\Profiler;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class ProfilerStorageFactory
*/
class ProfilerStorageFactory {
/**
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
*
* @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface
*/
final public static function getProfilerStorage(ConfigFactoryInterface $config, ContainerInterface $container) {
$storage = $config->get('webprofiler.config')
->get('storage') ?: 'profiler.database_storage';
return $container->get($storage);
}
}
@@ -0,0 +1,52 @@
<?php
namespace Drupal\webprofiler\Profiler;
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
/**
* Class ProfilerStorageManager
*/
class ProfilerStorageManager {
/**
* @var array
*/
private $storages;
/**
* @return array
*/
public function getStorages() {
$output = [];
/** @var \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface $storage */
foreach ($this->storages as $id => $storage) {
$output[$id] = $storage['title'];
}
return $output;
}
/**
* @param $id
*
* @return array
*/
public function getStorage($id) {
return $this->storages[$id];
}
/**
* @param $id
* @param $title
* @param \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface $storage
*/
public function addStorage($id, $title, ProfilerStorageInterface $storage) {
$this->storages[$id] = [
'title' => $title,
'class' => $storage,
];
}
}
@@ -0,0 +1,130 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Drupal\webprofiler\Profiler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Profiler\Profiler as SymfonyProfiler;
use Symfony\Component\HttpKernel\Profiler\Profile;
/**
* Profiler Templates Manager
*/
class TemplateManager {
/**
* @var \Twig_Environment
*/
protected $twig;
/**
* @var \Twig_Loader_Chain
*/
protected $twigLoader;
/**
* @var array
*/
protected $templates;
/**
* @var \Symfony\Component\HttpKernel\Profiler\Profiler
*/
protected $profiler;
/**
* Constructor.
*
* @param SymfonyProfiler $profiler
* @param \Twig_Environment $twig
* @param \Twig_Loader_Chain $twigLoader
* @param array $templates
*/
public function __construct(SymfonyProfiler $profiler, \Twig_Environment $twig, \Twig_Loader_Chain $twigLoader, array $templates) {
$this->profiler = $profiler;
$this->twig = $twig;
$this->twigLoader = $twigLoader;
$this->templates = $templates;
}
/**
* Gets the template name for a given panel.
*
* @param Profile $profile
* @param string $panel
*
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function getName(Profile $profile, $panel) {
$templates = $this->getNames($profile);
if (!isset($templates[$panel])) {
throw new NotFoundHttpException(sprintf('Panel "%s" is not registered in profiler or is not present in viewed profile.', $panel));
}
return $templates[$panel];
}
/**
* Gets the templates for a given profile.
*
* @param \Symfony\Component\HttpKernel\Profiler\Profile $profile
*
* @return array
*/
public function getTemplates(Profile $profile) {
$templates = $this->getNames($profile);
foreach ($templates as $name => $template) {
$templates[$name] = $this->twig->loadTemplate($template);
}
return $templates;
}
/**
* Gets template names of templates that are present in the viewed profile.
*
* @param \Symfony\Component\HttpKernel\Profiler\Profile $profile
*
* @return array
*
* @throws \UnexpectedValueException
*/
protected function getNames(Profile $profile) {
$templates = [];
foreach ($this->templates as $arguments) {
if (NULL === $arguments) {
continue;
}
list($name, $template) = $arguments;
if (!$this->profiler->has($name) || !$profile->hasCollector($name)) {
continue;
}
if ('.html.twig' === substr($template, -10)) {
$template = substr($template, 0, -10);
}
if (!$this->twigLoader->exists($template . '.html.twig')) {
throw new \UnexpectedValueException(sprintf('The profiler template "%s.html.twig" for data collector "%s" does not exist.', $template, $name));
}
$templates[$name] = $template . '.html.twig';
}
return $templates;
}
}
@@ -0,0 +1,50 @@
<?php
namespace Drupal\webprofiler\RequestMatcher;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Path\PathMatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
/**
* Class WebprofilerRequestMatcher
*/
class WebprofilerRequestMatcher implements RequestMatcherInterface {
/**
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
private $configFactory;
/**
* @var \Drupal\Core\Path\PathMatcherInterface
*/
private $pathMatcher;
/**
* @param ConfigFactoryInterface $configFactory
* @param \Drupal\Core\Path\PathMatcherInterface $pathMatcher
*/
public function __construct(ConfigFactoryInterface $configFactory, PathMatcherInterface $pathMatcher) {
$this->configFactory = $configFactory;
$this->pathMatcher = $pathMatcher;
}
/**
* {@inheritdoc}
*/
public function matches(Request $request) {
$path = $request->getPathInfo();
$patterns = $this->configFactory->get('webprofiler.config')->get('exclude');
// never add Webprofiler to phpinfo page.
$patterns .= "\r\n/admin/reports/status/php";
// never add Webprofiler to uninstall confirm page.
$patterns .= "\r\n/admin/modules/uninstall/*";
return !$this->pathMatcher->matchPath($path, $patterns);
}
}
@@ -0,0 +1,54 @@
<?php
namespace Drupal\webprofiler\Routing;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Drupal\webprofiler\Profiler\Profiler;
use Symfony\Component\Routing\Route;
/**
* Class TokenConverter
*/
class TokenConverter implements ParamConverterInterface {
/**
* @var \Drupal\webprofiler\Profiler\Profiler
*/
private $profiler;
/**
* Constructs a new WebprofilerController.
*
* @param \Drupal\webprofiler\Profiler\Profiler $profiler
*/
public function __construct(Profiler $profiler) {
$this->profiler = $profiler;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
if (NULL === $this->profiler) {
return NULL;
}
$profile = $this->profiler->loadProfile($value);
if (NULL === $profile) {
return NULL;
}
return $profile;
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
if (!empty($definition['type']) && $definition['type'] === 'webprofiler:token') {
return TRUE;
}
return FALSE;
}
}
@@ -0,0 +1,41 @@
<?php
namespace Drupal\webprofiler\StackMiddleware;
use Drupal\Core\Database\Database;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Class WebprofilerMiddleware
*/
class WebprofilerMiddleware implements HttpKernelInterface {
/**
* The decorated kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* Constructs a WebprofilerMiddleware object.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The decorated kernel.
*/
public function __construct(HttpKernelInterface $http_kernel) {
$this->httpKernel = $http_kernel;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
foreach (Database::getAllConnectionInfo() as $key => $info) {
Database::startLog('webprofiler', $key);
}
return $this->httpKernel->handle($request, $type, $catch);
}
}
@@ -0,0 +1,86 @@
<?php
namespace Drupal\webprofiler\State;
use Drupal\Core\State\StateInterface;
use Drupal\webprofiler\DataCollector\StateDataCollector;
/**
* Class StateWrapper
*/
class StateWrapper implements StateInterface {
/**
* @var \Drupal\Core\State\StateInterface
*/
private $state;
/**
* @var \Drupal\webprofiler\DataCollector\StateDataCollector
*/
private $dataCollector;
/**
* @param \Drupal\Core\State\StateInterface $state
* @param \Drupal\webprofiler\DataCollector\StateDataCollector $dataCollector
*/
public function __construct(StateInterface $state, StateDataCollector $dataCollector) {
$this->state = $state;
$this->dataCollector = $dataCollector;
}
/**
* {@inheritdoc}
*/
public function get($key, $default = NULL) {
$this->dataCollector->addState($key);
return $this->state->get($key, $default);
}
/**
* {@inheritdoc}
*/
public function getMultiple(array $keys) {
foreach ($keys as $key) {
$this->dataCollector->addState($key);
}
return $this->state->getMultiple($keys);
}
/**
* {@inheritdoc}
*/
public function set($key, $value) {
$this->state->set($key, $value);
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $data) {
$this->state->setMultiple($data);
}
/**
* {@inheritdoc}
*/
public function delete($key) {
$this->state->delete($key);
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $keys) {
$this->state->deleteMultiple($keys);
}
/**
* {@inheritdoc}
*/
public function resetCache() {
$this->state->resetCache();
}
}
@@ -0,0 +1,543 @@
<?php
namespace Drupal\webprofiler {
/**
* Class Stopwatch
*/
class Stopwatch extends \Symfony\Component\Stopwatch\Stopwatch {
}
}
namespace Symfony\Component\Stopwatch {
/**
* Class Stopwatch
*/
class Stopwatch {
/**
* @var Section[]
*/
private $sections;
/**
* @var array
*/
private $activeSections;
/**
*
*/
public function __construct() {
$this->sections = $this->activeSections = ['__root__' => new Section('__root__')];
}
/**
* Creates a new section or re-opens an existing section.
*
* @param string|null $id The id of the session to re-open, null to create a new one
*
* @throws \LogicException When the section to re-open is not reachable
*/
public function openSection($id = NULL) {
$current = end($this->activeSections);
if (NULL !== $id && NULL === $current->get($id)) {
throw new \LogicException(sprintf('The section "%s" has been started at an other level and can not be opened.', $id));
}
$this->start('__section__.child', 'section');
$this->activeSections[] = $current->open($id);
$this->start('__section__');
}
/**
* Stops the last started section.
*
* The id parameter is used to retrieve the events from this section.
*
* @see getSectionEvents
*
* @param string $id The identifier of the section
*
* @throws \LogicException When there's no started section to be stopped
*/
public function stopSection($id) {
$this->stop('__section__');
if (1 == count($this->activeSections)) {
throw new \LogicException('There is no started section to stop.');
}
$this->sections[$id] = array_pop($this->activeSections)->setId($id);
$this->stop('__section__.child');
}
/**
* Starts an event.
*
* @param string $name The event name
* @param string $category The event category
*
* @return StopwatchEvent A StopwatchEvent instance
*/
public function start($name, $category = NULL) {
return end($this->activeSections)->startEvent($name, $category);
}
/**
* Checks if the event was started
*
* @param string $name The event name
*
* @return bool
*/
public function isStarted($name) {
return end($this->activeSections)->isEventStarted($name);
}
/**
* Stops an event.
*
* @param string $name The event name
*
* @return StopwatchEvent A StopwatchEvent instance
*/
public function stop($name) {
return end($this->activeSections)->stopEvent($name);
}
/**
* Stops then restarts an event.
*
* @param string $name The event name
*
* @return StopwatchEvent A StopwatchEvent instance
*/
public function lap($name) {
return end($this->activeSections)->stopEvent($name)->start();
}
/**
* Gets all events for a given section.
*
* @param string $id A section identifier
*
* @return StopwatchEvent[] An array of StopwatchEvent instances
*/
public function getSectionEvents($id) {
return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : [];
}
}
/**
* @internal This class is for internal usage only
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Section {
/**
* @var StopwatchEvent[]
*/
private $events = [];
/**
* @var null|float
*/
private $origin;
/**
* @var string
*/
private $id;
/**
* @var Section[]
*/
private $children = [];
/**
* Constructor.
*
* @param float|null $origin Set the origin of the events in this section, use null to set their origin to their start time
*/
public function __construct($origin = NULL) {
$this->origin = is_numeric($origin) ? $origin : NULL;
}
/**
* Returns the child section.
*
* @param string $id The child section identifier
*
* @return Section|null The child section or null when none found
*/
public function get($id) {
foreach ($this->children as $child) {
if ($id === $child->getId()) {
return $child;
}
}
return NULL;
}
/**
* Creates or re-opens a child section.
*
* @param string|null $id null to create a new section, the identifier to re-open an existing one.
*
* @return Section A child section
*/
public function open($id) {
if (NULL === $session = $this->get($id)) {
$session = $this->children[] = new self(microtime(TRUE) * 1000);
}
return $session;
}
/**
* @return string The identifier of the section
*/
public function getId() {
return $this->id;
}
/**
* Sets the session identifier.
*
* @param string $id The session identifier
*
* @return Section The current section
*/
public function setId($id) {
$this->id = $id;
return $this;
}
/**
* Starts an event.
*
* @param string $name The event name
* @param string $category The event category
*
* @return StopwatchEvent The event
*/
public function startEvent($name, $category) {
if (!isset($this->events[$name])) {
$this->events[$name] = new StopwatchEvent($this->origin ?: microtime(TRUE) * 1000, $category);
}
return $this->events[$name]->start();
}
/**
* Checks if the event was started
*
* @param string $name The event name
*
* @return bool
*/
public function isEventStarted($name) {
return isset($this->events[$name]) && $this->events[$name]->isStarted();
}
/**
* Stops an event.
*
* @param string $name The event name
*
* @return StopwatchEvent The event
*
* @throws \LogicException When the event has not been started
*/
public function stopEvent($name) {
if (!isset($this->events[$name])) {
throw new \LogicException(sprintf('Event "%s" is not started.', $name));
}
return $this->events[$name]->stop();
}
/**
* Stops then restarts an event.
*
* @param string $name The event name
*
* @return StopwatchEvent The event
*
* @throws \LogicException When the event has not been started
*/
public function lap($name) {
return $this->stopEvent($name)->start();
}
/**
* Returns the events from this section.
*
* @return StopwatchEvent[] An array of StopwatchEvent instances
*/
public function getEvents() {
return $this->events;
}
}
/**
* Class StopwatchEvent
*/
class StopwatchEvent {
/**
* @var StopwatchPeriod[]
*/
private $periods = [];
/**
* @var float
*/
private $origin;
/**
* @var string
*/
private $category;
/**
* @var float[]
*/
private $started = [];
/**
* Constructor.
*
* @param float $origin The origin time in milliseconds
* @param string|null $category The event category or null to use the default
*
* @throws \InvalidArgumentException When the raw time is not valid
*/
public function __construct($origin, $category = NULL) {
$this->origin = $this->formatTime($origin);
$this->category = is_string($category) ? $category : 'default';
}
/**
* Gets the category.
*
* @return string The category
*/
public function getCategory() {
return $this->category;
}
/**
* Gets the origin.
*
* @return float The origin in milliseconds
*/
public function getOrigin() {
return $this->origin;
}
/**
* Starts a new event period.
*
* @return StopwatchEvent The event
*/
public function start() {
$this->started[] = $this->getNow();
return $this;
}
/**
* Stops the last started event period.
*
* @throws \LogicException When start wasn't called before stopping
*
* @return StopwatchEvent The event
*
* @throws \LogicException When stop() is called without a matching call to start()
*/
public function stop() {
if (!count($this->started)) {
throw new \LogicException('stop() called but start() has not been called before.');
}
$this->periods[] = new StopwatchPeriod(array_pop($this->started), $this->getNow());
return $this;
}
/**
* Checks if the event was started
*
* @return bool
*/
public function isStarted() {
return !empty($this->started);
}
/**
* Stops the current period and then starts a new one.
*
* @return StopwatchEvent The event
*/
public function lap() {
return $this->stop()->start();
}
/**
* Stops all non already stopped periods.
*/
public function ensureStopped() {
while (count($this->started)) {
$this->stop();
}
}
/**
* Gets all event periods.
*
* @return StopwatchPeriod[] An array of StopwatchPeriod instances
*/
public function getPeriods() {
return $this->periods;
}
/**
* Gets the relative time of the start of the first period.
*
* @return integer The time (in milliseconds)
*/
public function getStartTime() {
return isset($this->periods[0]) ? $this->periods[0]->getStartTime() : 0;
}
/**
* Gets the relative time of the end of the last period.
*
* @return integer The time (in milliseconds)
*/
public function getEndTime() {
return ($count = count($this->periods)) ? $this->periods[$count - 1]->getEndTime() : 0;
}
/**
* Gets the duration of the events (including all periods).
*
* @return integer The duration (in milliseconds)
*/
public function getDuration() {
$total = 0;
foreach ($this->periods as $period) {
$total += $period->getDuration();
}
return $total;
}
/**
* Gets the max memory usage of all periods.
*
* @return integer The memory usage (in bytes)
*/
public function getMemory() {
$memory = 0;
foreach ($this->periods as $period) {
if ($period->getMemory() > $memory) {
$memory = $period->getMemory();
}
}
return $memory;
}
/**
* Return the current time relative to origin.
*
* @return float Time in ms
*/
protected function getNow() {
return $this->formatTime(microtime(TRUE) * 1000 - $this->origin);
}
/**
* Formats a time.
*
* @param integer|float $time A raw time
*
* @return float The formatted time
*
* @throws \InvalidArgumentException When the raw time is not valid
*/
private function formatTime($time) {
if (!is_numeric($time)) {
throw new \InvalidArgumentException('The time must be a numerical value');
}
return round($time, 1);
}
}
/**
* Class StopwatchPeriod
*/
class StopwatchPeriod {
private $start;
private $end;
private $memory;
/**
* Constructor.
*
* @param integer $start The relative time of the start of the period (in milliseconds)
* @param integer $end The relative time of the end of the period (in milliseconds)
*/
public function __construct($start, $end) {
$this->start = (integer) $start;
$this->end = (integer) $end;
$this->memory = memory_get_usage(TRUE);
}
/**
* Gets the relative time of the start of the period.
*
* @return integer The time (in milliseconds)
*/
public function getStartTime() {
return $this->start;
}
/**
* Gets the relative time of the end of the period.
*
* @return integer The time (in milliseconds)
*/
public function getEndTime() {
return $this->end;
}
/**
* Gets the time spent in this period.
*
* @return integer The period duration (in milliseconds)
*/
public function getDuration() {
return $this->end - $this->start;
}
/**
* Gets the memory usage.
*
* @return integer The memory usage (in bytes)
*/
public function getMemory() {
return $this->memory;
}
}
}
@@ -0,0 +1,69 @@
<?php
namespace Drupal\webprofiler\StringTranslation;
use Drupal\Core\StringTranslation\TranslationManager;
/**
* Class TranslationManagerWrapper
*/
class TranslationManagerWrapper extends TranslationManager {
/**
* @var \Drupal\webprofiler\StringTranslation\TranslationManagerWrapper
*/
private $translationManager;
/**
* @var array
*/
private $translated;
/**
* @var array
*/
private $untranslated;
/**
* @param \Drupal\webprofiler\StringTranslation\TranslationManagerWrapper $translationManager
*/
public function setDataCollector(TranslationManagerWrapper $translationManager) {
$this->translationManager = $translationManager;
}
/**
* {@inheritdoc}
*/
protected function doTranslate($string, array $options = array()) {
// Merge in defaults.
if (empty($options['langcode'])) {
$options['langcode'] = $this->defaultLangcode;
}
if (empty($options['context'])) {
$options['context'] = '';
}
$translation = $this->getStringTranslation($options['langcode'], $string, $options['context']);
if($translation) {
$this->translated[$string] = $translation;
} else {
$this->untranslated[$string] = $string;
}
return $translation === FALSE ? $string : $translation;
}
/**
* @return array
*/
public function getTranslated() {
return $this->translated;
}
/**
* @return array
*/
public function getUntranslated() {
return $this->untranslated;
}
}
@@ -0,0 +1,39 @@
<?php
namespace Drupal\webprofiler\Theme;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeNegotiator;
/**
* Class ThemeNegotiatorWrapper
*/
class ThemeNegotiatorWrapper extends ThemeNegotiator {
/**
* @var \Drupal\Core\Theme\ThemeNegotiatorInterface
*/
private $negotiator;
/**
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
foreach ($this->getSortedNegotiators() as $negotiator) {
if ($negotiator->applies($route_match)) {
$theme = $negotiator->determineActiveTheme($route_match);
if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) {
$this->negotiator = $negotiator;
return $theme;
}
}
}
}
/**
* @return \Drupal\Core\Theme\ThemeNegotiatorInterface
*/
public function getNegotiator() {
return $this->negotiator;
}
}
@@ -0,0 +1,70 @@
<?php
namespace Drupal\webprofiler;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\Event;
/**
* Class TraceableEventDispatcher
*/
class TraceableEventDispatcher extends BaseTraceableEventDispatcher {
/**
* {@inheritdoc}
*/
protected function preDispatch($eventName, Event $event) {
switch ($eventName) {
case KernelEvents::VIEW:
case KernelEvents::RESPONSE:
// stop only if a controller has been executed
if ($this->stopwatch->isStarted('controller')) {
$this->stopwatch->stop('controller');
}
break;
}
}
/**
* {@inheritdoc}
*/
protected function postDispatch($eventName, Event $event) {
switch ($eventName) {
case KernelEvents::CONTROLLER:
$this->stopwatch->start('controller', 'section');
break;
case KernelEvents::RESPONSE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {
}
break;
case KernelEvents::TERMINATE:
// In the special case described in the `preDispatch` method above, the `$token` section
// does not exist, then closing it throws an exception which must be caught.
$token = $event->getResponse()->headers->get('X-Debug-Token');
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {
}
break;
}
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener) {
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
if (FALSE !== ($key = array_search($listener, $listeners, TRUE))) {
return $priority;
}
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace Drupal\webprofiler\Twig\Dumper;
/**
* Class HtmlDumper
*/
class HtmlDumper extends \Twig_Profiler_Dumper_Text {
private static $colors = array(
'block' => '#dfd',
'macro' => '#ddf',
'template' => '#ffd',
'big' => '#d44',
);
/**
* {@inheritdoc}
*/
public function dump(\Twig_Profiler_Profile $profile) {
return '<pre class="h--word-intact"><code class="code--json">' . parent::dump($profile) . '</code></pre>';
}
protected function formatTemplate(\Twig_Profiler_Profile $profile, $prefix) {
return sprintf('%s└ <span style="background-color: %s">%s</span>', $prefix, self::$colors['template'], $profile->getTemplate());
}
protected function formatNonTemplate(\Twig_Profiler_Profile $profile, $prefix) {
return sprintf('%s└ %s::%s(<span style="background-color: %s">%s</span>)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName());
}
protected function formatTime(\Twig_Profiler_Profile $profile, $percent) {
return sprintf('<span style="color: %s">%.2fms/%.0f%%</span>', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent);
}
}
@@ -0,0 +1,93 @@
<?php
namespace Drupal\webprofiler\Twig\Extension;
use Drupal\webprofiler\Helper\ClassShortenerInterface;
use Drupal\webprofiler\Helper\IdeLinkGeneratorInterface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Class ProfilerExtension
*/
class ProfilerExtension extends \Twig_Extension_Profiler {
/**
* @var \Symfony\Component\Stopwatch\Stopwatch
*/
private $stopwatch;
private $events;
private $ideLink;
private $classShortener;
/**
* {@inheritdoc}
*/
public function __construct(\Twig_Profiler_Profile $profile, Stopwatch $stopwatch = NULL, IdeLinkGeneratorInterface $ideLink, ClassShortenerInterface $classShortener) {
parent::__construct($profile);
$this->ideLink = $ideLink;
$this->classShortener = $classShortener;
$this->stopwatch = $stopwatch;
$this->events = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function enter(\Twig_Profiler_Profile $profile) {
if ($this->stopwatch && $profile->isTemplate()) {
$this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template');
}
parent::enter($profile);
}
/**
* {@inheritdoc}
*/
public function leave(\Twig_Profiler_Profile $profile) {
parent::leave($profile);
if ($this->stopwatch && $profile->isTemplate()) {
$this->events[$profile]->stop();
unset($this->events[$profile]);
}
}
/**
* {@inheritdoc}
*/
public function getFunctions() {
return [
new \Twig_SimpleFunction('abbr', [$this, 'getAbbr']),
new \Twig_SimpleFunction('idelink', [$this, 'getIdeLink']),
];
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'native_profiler';
}
/**
* @param $class
*
* @return string
*/
public function getAbbr($class) {
return $this->classShortener->shortenClass($class);
}
/**
* @param $file
* @param $line
*
* @return string
*/
public function getIdeLink($file, $line) {
return $this->ideLink->generateLink($file, $line);
}
}
@@ -0,0 +1,156 @@
<?php
namespace Drupal\webprofiler\Views;
use Drupal\views\ViewExecutable;
use Symfony\Component\HttpFoundation\Response;
/**
* Class TraceableViewExecutable
*/
class TraceableViewExecutable extends ViewExecutable {
/**
* @var float
*/
protected $render_time;
/**
* Gets the build time.
*
* @return float
*/
public function getBuildTime() {
return $this->build_time;
}
/**
* Gets the execute time.
*
* @return float
*/
public function getExecuteTime() {
return $this->execute_time;
}
/**
* Gets the render time.
*
* @return float
*/
public function getRenderTime() {
return $this->render_time;
}
/**
* {@inheritdoc}
*/
public function render($display_id = NULL) {
$start = microtime(TRUE);
$this->execute($display_id);
// Check to see if the build failed.
if (!empty($this->build_info['fail'])) {
return;
}
if (!empty($this->build_info['denied'])) {
return;
}
$exposed_form = $this->display_handler->getPlugin('exposed_form');
$exposed_form->preRender($this->result);
$module_handler = \Drupal::moduleHandler();
// @TODO In the longrun, it would be great to execute a view without
// the theme system at all. See https://www.drupal.org/node/2322623.
$active_theme = \Drupal::theme()->getActiveTheme();
$themes = array_keys($active_theme->getBaseThemes());
$themes[] = $active_theme->getName();
// Check for already-cached output.
if (!empty($this->live_preview)) {
$cache = FALSE;
}
else {
/** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
$cache = $this->display_handler->getPlugin('cache');
}
// Run preRender for the pager as it might change the result.
if (!empty($this->pager)) {
$this->pager->preRender($this->result);
}
// Initialize the style plugin.
$this->initStyle();
if (!isset($this->response)) {
// Set the response so other parts can alter it.
$this->response = new Response('', 200);
}
// Give field handlers the opportunity to perform additional queries
// using the entire resultset prior to rendering.
if ($this->style_plugin->usesFields()) {
foreach ($this->field as $id => $handler) {
if (!empty($this->field[$id])) {
$this->field[$id]->preRender($this->result);
}
}
}
$this->style_plugin->preRender($this->result);
// Let each area handler have access to the result set.
$areas = ['header', 'footer'];
// Only call preRender() on the empty handlers if the result is empty.
if (empty($this->result)) {
$areas[] = 'empty';
}
foreach ($areas as $area) {
foreach ($this->{$area} as $handler) {
$handler->preRender($this->result);
}
}
// Let modules modify the view just prior to rendering it.
$module_handler->invokeAll('views_pre_render', [$this]);
// Let the themes play too, because pre render is a very themey thing.
foreach ($themes as $theme_name) {
$function = $theme_name . '_views_pre_render';
if (function_exists($function)) {
$function($this);
}
}
$this->display_handler->output = $this->display_handler->render();
$exposed_form->postRender($this->display_handler->output);
if ($cache) {
$cache->postRender($this->display_handler->output);
}
// Let modules modify the view output after it is rendered.
$module_handler->invokeAll('views_post_render', [
$this,
&$this->display_handler->output,
$cache
]);
// Let the themes play too, because post render is a very themey thing.
foreach ($themes as $theme_name) {
$function = $theme_name . '_views_post_render';
if (function_exists($function)) {
$function($this);
}
}
$this->render_time = microtime(TRUE) - $start;
return $this->display_handler->output;
}
}

Some files were not shown because too many files have changed in this diff Show More