Commit 380ab4e1 authored by lussoluca's avatar lussoluca Committed by moshe weitzman

Issue #2609242 by lussoluca: Move Webprofiler to Devel

parent 286373c8
devel.entities:
class: \Drupal\Core\Menu\LocalTaskDefault
deriver: \Drupal\devel\Plugin\Derivative\DevelLocalTask
devel.admin_settings:
title: 'Settings'
route_name: devel.admin_settings
base_route: devel.admin_settings
weight: 0
!! README.md is a work in progress !!
#Dependencies:
- d3.js: Webprofiler module requires D3 library to proper 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 this two lines to settings.php (or, event better, to settings.local.php):
```
$class_loader->addPsr4('Drupal\\webprofiler\\', [ __DIR__ . '/../../modules/contrib/webprofiler/src']);
$settings['container_base_class'] = '\Drupal\webprofiler\DependencyInjection\TraceableContainer';
```
{
"name": "drupal/webprofiler",
"description": "Drupal Web Profiler.",
"type": "drupal-module",
"license": "GPL-2.0+",
"require": {
"symfony/stopwatch": "2.4.*"
}
}
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"
active_toolbar_items:
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
# 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'
query_sort:
type: string
label: 'Sort query log'
query_highlight:
type: integer
label: 'Slow query highlighting'
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.
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.
This diff is collapsed.
/**
* 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: 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;
}
\ No newline at end of file
<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>
(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));
(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;
return drupalSettings.webprofiler.idelink.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));
(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] + ']');
unselected.push('[data-wp-' + key + ']:not([data-wp-' + key + ' *= ' + select[sel] + '])');
}
}
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));
(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));
(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));
(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;