... | ... |
@@ -285,6 +285,9 @@ Twig comes bundled with the following extensions: |
285 | 285 |
* *Twig_Extension_Sandbox*: Adds a sandbox mode to the default Twig |
286 | 286 |
environment, making it safe to evaluate untrusted code. |
287 | 287 |
|
288 |
+* *Twig_Extension_Profiler*: Enabled the built-in Twig profiler (as of Twig |
|
289 |
+ 1.18). |
|
290 |
+ |
|
288 | 291 |
* *Twig_Extension_Optimizer*: Optimizes the node tree before compilation. |
289 | 292 |
|
290 | 293 |
The core, escaper, and optimizer extensions do not need to be added to the |
... | ... |
@@ -453,6 +456,37 @@ the extension constructor:: |
453 | 456 |
|
454 | 457 |
$sandbox = new Twig_Extension_Sandbox($policy, true); |
455 | 458 |
|
459 |
+Profiler Extension |
|
460 |
+~~~~~~~~~~~~~~~~~~ |
|
461 |
+ |
|
462 |
+.. versionadded:: 1.18 |
|
463 |
+ The Profile extension was added in Twig 1.18. |
|
464 |
+ |
|
465 |
+The ``profiler`` extension enables a profiler for Twig templates; it should |
|
466 |
+only be used on your development machines as it adds some overhead:: |
|
467 |
+ |
|
468 |
+ $profile = new Twig_Profiler_Profile(); |
|
469 |
+ $twig->addExtension(new Twig_Extension_Profiler($profile)); |
|
470 |
+ |
|
471 |
+ $dumper = new Twig_Profiler_Dumper_Text(); |
|
472 |
+ echo $dumper->dump($profile); |
|
473 |
+ |
|
474 |
+A profile contains information about time and memory consumption for template, |
|
475 |
+block, and macro executions. |
|
476 |
+ |
|
477 |
+You can also dump the data in a `Blackfire.io <https://blackfire.io/>`_ |
|
478 |
+compatible format:: |
|
479 |
+ |
|
480 |
+ $dumper = new Twig_Profiler_Dumper_Blackfire(); |
|
481 |
+ file_put_contents('/path/to/profile.prof', $dumper->dump($profile)); |
|
482 |
+ |
|
483 |
+Upload the profile to visualize it (create a `free account |
|
484 |
+<https://blackfire.io/signup>`_ first): |
|
485 |
+ |
|
486 |
+.. code-block:: sh |
|
487 |
+ |
|
488 |
+ blackfire --slot=7 upload /path/to/profile.prof |
|
489 |
+ |
|
456 | 490 |
Optimizer Extension |
457 | 491 |
~~~~~~~~~~~~~~~~~~~ |
458 | 492 |
|
23 | 23 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,52 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+class Twig_Extension_Profiler extends Twig_Extension |
|
13 |
+{ |
|
14 |
+ private $actives; |
|
15 |
+ |
|
16 |
+ public function __construct(Twig_Profiler_Profile $profile) |
|
17 |
+ { |
|
18 |
+ $this->actives = array($profile); |
|
19 |
+ } |
|
20 |
+ |
|
21 |
+ public function enter(Twig_Profiler_Profile $profile) |
|
22 |
+ { |
|
23 |
+ $this->actives[0]->addProfile($profile); |
|
24 |
+ array_unshift($this->actives, $profile); |
|
25 |
+ } |
|
26 |
+ |
|
27 |
+ public function leave(Twig_Profiler_Profile $profile) |
|
28 |
+ { |
|
29 |
+ $profile->leave(); |
|
30 |
+ array_shift($this->actives); |
|
31 |
+ |
|
32 |
+ if (1 === count($this->actives)) { |
|
33 |
+ $this->actives[0]->leave(); |
|
34 |
+ } |
|
35 |
+ } |
|
36 |
+ |
|
37 |
+ /** |
|
38 |
+ * {@inheritdoc} |
|
39 |
+ */ |
|
40 |
+ public function getNodeVisitors() |
|
41 |
+ { |
|
42 |
+ return array(new Twig_Profiler_NodeVisitor_Profiler($this->getName())); |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ /** |
|
46 |
+ * {@inheritdoc} |
|
47 |
+ */ |
|
48 |
+ public function getName() |
|
49 |
+ { |
|
50 |
+ return 'profiler'; |
|
51 |
+ } |
|
52 |
+} |
... | ... |
@@ -20,7 +20,19 @@ class Twig_Node_Module extends Twig_Node |
20 | 20 |
public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename) |
21 | 21 |
{ |
22 | 22 |
// embedded templates are set as attributes so that they are only visited once by the visitors |
23 |
- parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename, 'index' => null, 'embedded_templates' => $embeddedTemplates), 1); |
|
23 |
+ parent::__construct(array( |
|
24 |
+ 'parent' => $parent, |
|
25 |
+ 'body' => $body, |
|
26 |
+ 'blocks' => $blocks, |
|
27 |
+ 'macros' => $macros, |
|
28 |
+ 'traits' => $traits, |
|
29 |
+ 'display_enter' => new Twig_Node(), |
|
30 |
+ 'display_leave' => new Twig_Node(), |
|
31 |
+ ), array( |
|
32 |
+ 'filename' => $filename, |
|
33 |
+ 'index' => null, |
|
34 |
+ 'embedded_templates' => $embeddedTemplates, |
|
35 |
+ ), 1); |
|
24 | 36 |
} |
25 | 37 |
|
26 | 38 |
public function setIndex($index) |
... | ... |
@@ -271,12 +283,14 @@ class Twig_Node_Module extends Twig_Node |
271 | 283 |
$compiler |
272 | 284 |
->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n") |
273 | 285 |
->indent() |
286 |
+ ->subcompile($this->getNode('display_enter')) |
|
274 | 287 |
; |
275 | 288 |
} |
276 | 289 |
|
277 | 290 |
protected function compileDisplayFooter(Twig_Compiler $compiler) |
278 | 291 |
{ |
279 | 292 |
$compiler |
293 |
+ ->subcompile($this->getNode('display_leave')) |
|
280 | 294 |
->outdent() |
281 | 295 |
->write("}\n\n") |
282 | 296 |
; |
283 | 297 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,68 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+/** |
|
13 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
14 |
+ */ |
|
15 |
+class Twig_Profiler_Dumper_Blackfire |
|
16 |
+{ |
|
17 |
+ public function dump(Twig_Profiler_Profile $profile) |
|
18 |
+ { |
|
19 |
+ $data = array(); |
|
20 |
+ $this->dumpProfile('main()', $profile, $data); |
|
21 |
+ $this->dumpChildren('main()', $profile, $data); |
|
22 |
+ |
|
23 |
+ $start = microtime(true); |
|
24 |
+ $str = <<<EOF |
|
25 |
+file-format: BlackfireProbe |
|
26 |
+cost-dimensions: wt mu pmu |
|
27 |
+request-start: {$start} |
|
28 |
+ |
|
29 |
+ |
|
30 |
+EOF; |
|
31 |
+ |
|
32 |
+ foreach ($data as $name => $values) { |
|
33 |
+ $str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; |
|
34 |
+ } |
|
35 |
+ |
|
36 |
+ return $str; |
|
37 |
+ } |
|
38 |
+ |
|
39 |
+ private function dumpChildren($parent, Twig_Profiler_Profile $profile, &$data) |
|
40 |
+ { |
|
41 |
+ foreach ($profile as $p) { |
|
42 |
+ if ($p->isTemplate()) { |
|
43 |
+ $name = $p->getTemplate(); |
|
44 |
+ } else { |
|
45 |
+ $name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); |
|
46 |
+ } |
|
47 |
+ $this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data); |
|
48 |
+ $this->dumpChildren($name, $p, $data); |
|
49 |
+ } |
|
50 |
+ } |
|
51 |
+ |
|
52 |
+ private function dumpProfile($edge, Twig_Profiler_Profile $profile, &$data) |
|
53 |
+ { |
|
54 |
+ if (isset($data[$edge])) { |
|
55 |
+ $data[$edge]['ct'] += 1; |
|
56 |
+ $data[$edge]['wt'] += floor($profile->getDuration() * 1000000); |
|
57 |
+ $data[$edge]['mu'] += $profile->getMemoryUsage(); |
|
58 |
+ $data[$edge]['pmu'] += $profile->getPeakMemoryUsage(); |
|
59 |
+ } else { |
|
60 |
+ $data[$edge] = array( |
|
61 |
+ 'ct' => 1, |
|
62 |
+ 'wt' => floor($profile->getDuration() * 1000000), |
|
63 |
+ 'mu' => $profile->getMemoryUsage(), |
|
64 |
+ 'pmu' => $profile->getPeakMemoryUsage(), |
|
65 |
+ ); |
|
66 |
+ } |
|
67 |
+ } |
|
68 |
+} |
0 | 69 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,43 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+/** |
|
13 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
14 |
+ */ |
|
15 |
+class Twig_Profiler_Dumper_Html extends Twig_Profiler_Dumper_Text |
|
16 |
+{ |
|
17 |
+ static private $colors = array( |
|
18 |
+ 'block' => '#dfd', |
|
19 |
+ 'macro' => '#ddf', |
|
20 |
+ 'template' => '#ffd', |
|
21 |
+ 'big' => '#d44', |
|
22 |
+ ); |
|
23 |
+ |
|
24 |
+ public function dump(Twig_Profiler_Profile $profile) |
|
25 |
+ { |
|
26 |
+ return '<pre>'.parent::dump($profile).'</pre>'; |
|
27 |
+ } |
|
28 |
+ |
|
29 |
+ protected function formatTemplate(Twig_Profiler_Profile $profile, $prefix) |
|
30 |
+ { |
|
31 |
+ return sprintf('%sâ”” <span style="background-color: %s">%s</span>', $prefix, self::$colors['template'], $profile->getTemplate()); |
|
32 |
+ } |
|
33 |
+ |
|
34 |
+ protected function formatNonTemplate(Twig_Profiler_Profile $profile, $prefix) |
|
35 |
+ { |
|
36 |
+ 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()); |
|
37 |
+ } |
|
38 |
+ |
|
39 |
+ protected function formatTime(Twig_Profiler_Profile $profile, $percent) |
|
40 |
+ { |
|
41 |
+ return sprintf('<span style="color: %s">%.2fms/%.0f%%</span>', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); |
|
42 |
+ } |
|
43 |
+} |
0 | 44 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,68 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+/** |
|
13 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
14 |
+ */ |
|
15 |
+class Twig_Profiler_Dumper_Text |
|
16 |
+{ |
|
17 |
+ private $root; |
|
18 |
+ |
|
19 |
+ public function dump(Twig_Profiler_Profile $profile) |
|
20 |
+ { |
|
21 |
+ return $this->dumpProfile($profile); |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ protected function formatTemplate(Twig_Profiler_Profile $profile, $prefix) |
|
25 |
+ { |
|
26 |
+ return sprintf('%sâ”” %s', $prefix, $profile->getTemplate()); |
|
27 |
+ } |
|
28 |
+ |
|
29 |
+ protected function formatNonTemplate(Twig_Profiler_Profile $profile, $prefix) |
|
30 |
+ { |
|
31 |
+ return sprintf('%sâ”” %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); |
|
32 |
+ } |
|
33 |
+ |
|
34 |
+ protected function formatTime(Twig_Profiler_Profile $profile, $percent) |
|
35 |
+ { |
|
36 |
+ return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); |
|
37 |
+ } |
|
38 |
+ |
|
39 |
+ private function dumpProfile(Twig_Profiler_Profile $profile, $prefix = '', $sibling = false) |
|
40 |
+ { |
|
41 |
+ if ($profile->isRoot()) { |
|
42 |
+ $this->root = $profile->getDuration(); |
|
43 |
+ $start = $profile->getName(); |
|
44 |
+ } else { |
|
45 |
+ if ($profile->isTemplate()) { |
|
46 |
+ $start = $this->formatTemplate($profile, $prefix); |
|
47 |
+ } else { |
|
48 |
+ $start = $this->formatNonTemplate($profile, $prefix); |
|
49 |
+ } |
|
50 |
+ $prefix .= $sibling ? '│ ' : ' '; |
|
51 |
+ } |
|
52 |
+ |
|
53 |
+ $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0; |
|
54 |
+ |
|
55 |
+ if ($profile->getDuration() * 1000 < 1) { |
|
56 |
+ $str = $start."\n"; |
|
57 |
+ } else { |
|
58 |
+ $str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); |
|
59 |
+ } |
|
60 |
+ |
|
61 |
+ $nCount = count($profile->getProfiles()); |
|
62 |
+ foreach ($profile as $i => $p) { |
|
63 |
+ $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount); |
|
64 |
+ } |
|
65 |
+ |
|
66 |
+ return $str; |
|
67 |
+ } |
|
68 |
+} |
0 | 69 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,40 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+/** |
|
13 |
+ * Represents a profile enter node. |
|
14 |
+ * |
|
15 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
16 |
+ */ |
|
17 |
+class Twig_Profiler_Node_EnterProfile extends Twig_Node |
|
18 |
+{ |
|
19 |
+ public function __construct($extensionName, $type, $name, $varName) |
|
20 |
+ { |
|
21 |
+ parent::__construct(array(), array('extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName)); |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ /** |
|
25 |
+ * {@inheritdoc} |
|
26 |
+ */ |
|
27 |
+ public function compile(Twig_Compiler $compiler) |
|
28 |
+ { |
|
29 |
+ $compiler |
|
30 |
+ ->write(sprintf("\$%s = \$this->env->getExtension(", $this->getAttribute('var_name'))) |
|
31 |
+ ->repr($this->getAttribute('extension_name')) |
|
32 |
+ ->raw(");\n") |
|
33 |
+ ->write(sprintf("\$%s->enter(\$%s = new Twig_Profiler_Profile(\$this->getTemplateName(), ", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) |
|
34 |
+ ->repr($this->getAttribute('type')) |
|
35 |
+ ->raw(", ") |
|
36 |
+ ->repr($this->getAttribute('name')) |
|
37 |
+ ->raw("));\n\n") |
|
38 |
+ ; |
|
39 |
+ } |
|
40 |
+} |
0 | 41 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,34 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+/** |
|
13 |
+ * Represents a profile leave node. |
|
14 |
+ * |
|
15 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
16 |
+ */ |
|
17 |
+class Twig_Profiler_Node_LeaveProfile extends Twig_Node |
|
18 |
+{ |
|
19 |
+ public function __construct($varName) |
|
20 |
+ { |
|
21 |
+ parent::__construct(array(), array('var_name' => $varName)); |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ /** |
|
25 |
+ * {@inheritdoc} |
|
26 |
+ */ |
|
27 |
+ public function compile(Twig_Compiler $compiler) |
|
28 |
+ { |
|
29 |
+ $compiler |
|
30 |
+ ->write("\n") |
|
31 |
+ ->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) |
|
32 |
+ ; |
|
33 |
+ } |
|
34 |
+} |
0 | 35 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,72 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+/** |
|
13 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
14 |
+ */ |
|
15 |
+class Twig_Profiler_NodeVisitor_Profiler implements Twig_NodeVisitorInterface |
|
16 |
+{ |
|
17 |
+ private $extensionName; |
|
18 |
+ |
|
19 |
+ public function __construct($extensionName) |
|
20 |
+ { |
|
21 |
+ $this->extensionName = $extensionName; |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ /** |
|
25 |
+ * {@inheritdoc} |
|
26 |
+ */ |
|
27 |
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) |
|
28 |
+ { |
|
29 |
+ return $node; |
|
30 |
+ } |
|
31 |
+ |
|
32 |
+ /** |
|
33 |
+ * {@inheritdoc} |
|
34 |
+ */ |
|
35 |
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) |
|
36 |
+ { |
|
37 |
+ if ($node instanceof Twig_Node_Module) { |
|
38 |
+ $varName = $this->getVarName(); |
|
39 |
+ $node->setNode('display_enter', new Twig_Node(array(new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::TEMPLATE, $node->getAttribute('filename'), $varName), $node->getNode('display_enter')))); |
|
40 |
+ $node->setNode('display_leave', new Twig_Node(array(new Twig_Profiler_Node_LeaveProfile($varName), $node->getNode('display_leave')))); |
|
41 |
+ } elseif ($node instanceof Twig_Node_Block) { |
|
42 |
+ $varName = $this->getVarName(); |
|
43 |
+ $node->setNode('body', new Twig_Node_Body(array( |
|
44 |
+ new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::BLOCK, $node->getAttribute('name'), $varName), |
|
45 |
+ $node->getNode('body'), |
|
46 |
+ new Twig_Profiler_Node_LeaveProfile($varName), |
|
47 |
+ ))); |
|
48 |
+ } elseif ($node instanceof Twig_Node_Macro) { |
|
49 |
+ $varName = $this->getVarName(); |
|
50 |
+ $node->setNode('body', new Twig_Node_Body(array( |
|
51 |
+ new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::MACRO, $node->getAttribute('name'), $varName), |
|
52 |
+ $node->getNode('body'), |
|
53 |
+ new Twig_Profiler_Node_LeaveProfile($varName), |
|
54 |
+ ))); |
|
55 |
+ } |
|
56 |
+ |
|
57 |
+ return $node; |
|
58 |
+ } |
|
59 |
+ |
|
60 |
+ private function getVarName() |
|
61 |
+ { |
|
62 |
+ return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); |
|
63 |
+ } |
|
64 |
+ |
|
65 |
+ /** |
|
66 |
+ * {@inheritdoc} |
|
67 |
+ */ |
|
68 |
+ public function getPriority() |
|
69 |
+ { |
|
70 |
+ return 0; |
|
71 |
+ } |
|
72 |
+} |
0 | 73 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,150 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) 2015 Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+/** |
|
13 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
14 |
+ */ |
|
15 |
+class Twig_Profiler_Profile implements IteratorAggregate, Serializable |
|
16 |
+{ |
|
17 |
+ const ROOT = 'ROOT'; |
|
18 |
+ const BLOCK = 'block'; |
|
19 |
+ const TEMPLATE = 'template'; |
|
20 |
+ const MACRO = 'macro'; |
|
21 |
+ |
|
22 |
+ private $template; |
|
23 |
+ private $name; |
|
24 |
+ private $type; |
|
25 |
+ private $starts = array(); |
|
26 |
+ private $ends = array(); |
|
27 |
+ private $profiles = array(); |
|
28 |
+ |
|
29 |
+ public function __construct($template = 'main', $type = Twig_Profiler_Profile::ROOT, $name = 'main') |
|
30 |
+ { |
|
31 |
+ $this->template = $template; |
|
32 |
+ $this->type = $type; |
|
33 |
+ $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; |
|
34 |
+ $this->enter(); |
|
35 |
+ } |
|
36 |
+ |
|
37 |
+ public function getTemplate() |
|
38 |
+ { |
|
39 |
+ return $this->template; |
|
40 |
+ } |
|
41 |
+ |
|
42 |
+ public function getType() |
|
43 |
+ { |
|
44 |
+ return $this->type; |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ public function getName() |
|
48 |
+ { |
|
49 |
+ return $this->name; |
|
50 |
+ } |
|
51 |
+ |
|
52 |
+ public function isRoot() |
|
53 |
+ { |
|
54 |
+ return self::ROOT === $this->type; |
|
55 |
+ } |
|
56 |
+ |
|
57 |
+ public function isTemplate() |
|
58 |
+ { |
|
59 |
+ return self::TEMPLATE === $this->type; |
|
60 |
+ } |
|
61 |
+ |
|
62 |
+ public function isBlock() |
|
63 |
+ { |
|
64 |
+ return self::BLOCK === $this->type; |
|
65 |
+ } |
|
66 |
+ |
|
67 |
+ public function isMacro() |
|
68 |
+ { |
|
69 |
+ return self::MACRO === $this->type; |
|
70 |
+ } |
|
71 |
+ |
|
72 |
+ public function getProfiles() |
|
73 |
+ { |
|
74 |
+ return $this->profiles; |
|
75 |
+ } |
|
76 |
+ |
|
77 |
+ public function addProfile(Twig_Profiler_Profile $profile) |
|
78 |
+ { |
|
79 |
+ $this->profiles[] = $profile; |
|
80 |
+ } |
|
81 |
+ |
|
82 |
+ /** |
|
83 |
+ * Returns the duration in microseconds. |
|
84 |
+ * |
|
85 |
+ * @return int |
|
86 |
+ */ |
|
87 |
+ public function getDuration() |
|
88 |
+ { |
|
89 |
+ return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; |
|
90 |
+ } |
|
91 |
+ |
|
92 |
+ /** |
|
93 |
+ * Returns the memory usage in bytes. |
|
94 |
+ * |
|
95 |
+ * @return int |
|
96 |
+ */ |
|
97 |
+ public function getMemoryUsage() |
|
98 |
+ { |
|
99 |
+ return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; |
|
100 |
+ } |
|
101 |
+ |
|
102 |
+ /** |
|
103 |
+ * Returns the peak memory usage in bytes. |
|
104 |
+ * |
|
105 |
+ * @return int |
|
106 |
+ */ |
|
107 |
+ public function getPeakMemoryUsage() |
|
108 |
+ { |
|
109 |
+ return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; |
|
110 |
+ } |
|
111 |
+ |
|
112 |
+ /** |
|
113 |
+ * Starts the profiling. |
|
114 |
+ */ |
|
115 |
+ public function enter() |
|
116 |
+ { |
|
117 |
+ $this->starts = array( |
|
118 |
+ 'wt' => microtime(true), |
|
119 |
+ 'mu' => memory_get_usage(), |
|
120 |
+ 'pmu' => memory_get_peak_usage(), |
|
121 |
+ ); |
|
122 |
+ } |
|
123 |
+ |
|
124 |
+ /** |
|
125 |
+ * Stops the profiling. |
|
126 |
+ */ |
|
127 |
+ public function leave() |
|
128 |
+ { |
|
129 |
+ $this->ends = array( |
|
130 |
+ 'wt' => microtime(true), |
|
131 |
+ 'mu' => memory_get_usage(), |
|
132 |
+ 'pmu' => memory_get_peak_usage(), |
|
133 |
+ ); |
|
134 |
+ } |
|
135 |
+ |
|
136 |
+ public function getIterator() |
|
137 |
+ { |
|
138 |
+ return new ArrayIterator($this->profiles); |
|
139 |
+ } |
|
140 |
+ |
|
141 |
+ public function serialize() |
|
142 |
+ { |
|
143 |
+ return serialize(array($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles)); |
|
144 |
+ } |
|
145 |
+ |
|
146 |
+ public function unserialize($data) |
|
147 |
+ { |
|
148 |
+ list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = unserialize($data); |
|
149 |
+ } |
|
150 |
+} |
0 | 151 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,44 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+abstract class Twig_Tests_Profiler_Dumper_AbstractTest extends PHPUnit_Framework_TestCase |
|
13 |
+{ |
|
14 |
+ protected function getProfile() |
|
15 |
+ { |
|
16 |
+ $profile = new Twig_Profiler_Profile(); |
|
17 |
+ $index = new Twig_Profiler_Profile('index.twig', Twig_Profiler_Profile::TEMPLATE); |
|
18 |
+ $profile->addProfile($index); |
|
19 |
+ $body = new Twig_Profiler_Profile('embedded.twig', Twig_Profiler_Profile::BLOCK, 'body'); |
|
20 |
+ $body->leave(); |
|
21 |
+ $index->addProfile($body); |
|
22 |
+ $embedded = new Twig_Profiler_Profile('embedded.twig', Twig_Profiler_Profile::TEMPLATE); |
|
23 |
+ $included = new Twig_Profiler_Profile('included.twig', Twig_Profiler_Profile::TEMPLATE); |
|
24 |
+ $embedded->addProfile($included); |
|
25 |
+ $index->addProfile($embedded); |
|
26 |
+ $included->leave(); |
|
27 |
+ $embedded->leave(); |
|
28 |
+ |
|
29 |
+ $macro = new Twig_Profiler_Profile('index.twig', Twig_Profiler_Profile::MACRO, 'foo'); |
|
30 |
+ $macro->leave(); |
|
31 |
+ $index->addProfile($macro); |
|
32 |
+ |
|
33 |
+ $embedded = clone $embedded; |
|
34 |
+ $index->addProfile($embedded); |
|
35 |
+ $a = range(1, 1000); |
|
36 |
+ $embedded->leave(); |
|
37 |
+ $profile->leave(); |
|
38 |
+ |
|
39 |
+ usleep(5000); |
|
40 |
+ $index->leave(); |
|
41 |
+ |
|
42 |
+ return $profile; |
|
43 |
+ } |
|
44 |
+} |
0 | 45 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,32 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+class Twig_Tests_Profiler_Dumper_BlackfireTest extends Twig_Tests_Profiler_Dumper_AbstractTest |
|
13 |
+{ |
|
14 |
+ public function testDump() |
|
15 |
+ { |
|
16 |
+ $dumper = new Twig_Profiler_Dumper_Blackfire(); |
|
17 |
+ |
|
18 |
+ $this->assertStringMatchesFormat(<<<EOF |
|
19 |
+file-format: BlackfireProbe |
|
20 |
+cost-dimensions: wt mu pmu |
|
21 |
+request-start: %d.%d |
|
22 |
+ |
|
23 |
+main()//1 %d %d %d |
|
24 |
+main()==>index.twig//1 %d %d %d |
|
25 |
+index.twig==>embedded.twig::block(body)//1 %d %d 0 |
|
26 |
+index.twig==>embedded.twig//2 %d %d %d |
|
27 |
+embedded.twig==>included.twig//2 %d %d %d |
|
28 |
+index.twig==>index.twig::macro(foo)//1 %d %d %d |
|
29 |
+EOF |
|
30 |
+ , $dumper->dump($this->getProfile())); |
|
31 |
+ } |
|
32 |
+} |
0 | 33 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,30 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+class Twig_Tests_Profiler_Dumper_HtmlTest extends Twig_Tests_Profiler_Dumper_AbstractTest |
|
13 |
+{ |
|
14 |
+ public function testDump() |
|
15 |
+ { |
|
16 |
+ $dumper = new Twig_Profiler_Dumper_Html(); |
|
17 |
+ $this->assertStringMatchesFormat(<<<EOF |
|
18 |
+<pre>main |
|
19 |
+â”” <span style="background-color: #ffd">index.twig</span> <span style="color: #d44">%d.%dms/%d%</span> |
|
20 |
+ â”” embedded.twig::block(<span style="background-color: #dfd">body</span>) |
|
21 |
+ â”” <span style="background-color: #ffd">embedded.twig</span> |
|
22 |
+ │ └ <span style="background-color: #ffd">included.twig</span> |
|
23 |
+ â”” index.twig::macro(<span style="background-color: #ddf">foo</span>) |
|
24 |
+ â”” <span style="background-color: #ffd">embedded.twig</span> |
|
25 |
+ â”” <span style="background-color: #ffd">included.twig</span> |
|
26 |
+</pre> |
|
27 |
+EOF |
|
28 |
+ , $dumper->dump($this->getProfile())); |
|
29 |
+ } |
|
30 |
+} |
0 | 31 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,30 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+class Twig_Tests_Profiler_Dumper_TextTest extends Twig_Tests_Profiler_Dumper_AbstractTest |
|
13 |
+{ |
|
14 |
+ public function testDump() |
|
15 |
+ { |
|
16 |
+ $dumper = new Twig_Profiler_Dumper_Text(); |
|
17 |
+ $this->assertStringMatchesFormat(<<<EOF |
|
18 |
+main |
|
19 |
+â”” index.twig %d.%dms/%d% |
|
20 |
+ â”” embedded.twig::block(body) |
|
21 |
+ â”” embedded.twig |
|
22 |
+ │ └ included.twig |
|
23 |
+ â”” index.twig::macro(foo) |
|
24 |
+ â”” embedded.twig |
|
25 |
+ â”” included.twig |
|
26 |
+ |
|
27 |
+EOF |
|
28 |
+ , $dumper->dump($this->getProfile())); |
|
29 |
+ } |
|
30 |
+} |
0 | 31 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,99 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of Twig. |
|
5 |
+ * |
|
6 |
+ * (c) Fabien Potencier |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+class Twig_Tests_Profiler_ProfileTest extends PHPUnit_Framework_TestCase |
|
13 |
+{ |
|
14 |
+ public function testConstructor() |
|
15 |
+ { |
|
16 |
+ $profile = new Twig_Profiler_Profile('template', 'type', 'name'); |
|
17 |
+ |
|
18 |
+ $this->assertEquals('template', $profile->getTemplate()); |
|
19 |
+ $this->assertEquals('type', $profile->getType()); |
|
20 |
+ $this->assertEquals('name', $profile->getName()); |
|
21 |
+ } |
|
22 |
+ |
|
23 |
+ public function testIsRoot() |
|
24 |
+ { |
|
25 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); |
|
26 |
+ $this->assertTrue($profile->isRoot()); |
|
27 |
+ |
|
28 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::TEMPLATE); |
|
29 |
+ $this->assertFalse($profile->isRoot()); |
|
30 |
+ } |
|
31 |
+ |
|
32 |
+ public function testIsTemplate() |
|
33 |
+ { |
|
34 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::TEMPLATE); |
|
35 |
+ $this->assertTrue($profile->isTemplate()); |
|
36 |
+ |
|
37 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); |
|
38 |
+ $this->assertFalse($profile->isTemplate()); |
|
39 |
+ } |
|
40 |
+ |
|
41 |
+ public function testIsBlock() |
|
42 |
+ { |
|
43 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::BLOCK); |
|
44 |
+ $this->assertTrue($profile->isBlock()); |
|
45 |
+ |
|
46 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); |
|
47 |
+ $this->assertFalse($profile->isBlock()); |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ public function testIsMacro() |
|
51 |
+ { |
|
52 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::MACRO); |
|
53 |
+ $this->assertTrue($profile->isMacro()); |
|
54 |
+ |
|
55 |
+ $profile = new Twig_Profiler_Profile('template', Twig_Profiler_Profile::ROOT); |
|
56 |
+ $this->assertFalse($profile->isMacro()); |
|
57 |
+ } |
|
58 |
+ |
|
59 |
+ public function testGetAddProfile() |
|
60 |
+ { |
|
61 |
+ $profile = new Twig_Profiler_Profile(); |
|
62 |
+ $profile->addProfile($a = new Twig_Profiler_Profile()); |
|
63 |
+ $profile->addProfile($b = new Twig_Profiler_Profile()); |
|
64 |
+ |
|
65 |
+ $this->assertSame(array($a, $b), $profile->getProfiles()); |
|
66 |
+ $this->assertSame(array($a, $b), iterator_to_array($profile)); |
|
67 |
+ } |
|
68 |
+ |
|
69 |
+ public function testGetDuration() |
|
70 |
+ { |
|
71 |
+ $profile = new Twig_Profiler_Profile(); |
|
72 |
+ $profile->leave(); |
|
73 |
+ |
|
74 |
+ $this->assertTrue($profile->getDuration() > 0); |
|
75 |
+ } |
|
76 |
+ |
|
77 |
+ public function testSerialize() |
|
78 |
+ { |
|
79 |
+ $profile = new Twig_Profiler_Profile('template', 'type', 'name'); |
|
80 |
+ $profile1 = new Twig_Profiler_Profile('template1', 'type1', 'name1'); |
|
81 |
+ $profile->addProfile($profile1); |
|
82 |
+ $profile->leave(); |
|
83 |
+ $profile1->leave(); |
|
84 |
+ |
|
85 |
+ $profile2 = unserialize(serialize($profile)); |
|
86 |
+ $profiles = $profile->getProfiles(); |
|
87 |
+ $this->assertCount(1, $profiles); |
|
88 |
+ $profile3 = $profiles[0]; |
|
89 |
+ |
|
90 |
+ $this->assertEquals($profile->getTemplate(), $profile2->getTemplate()); |
|
91 |
+ $this->assertEquals($profile->getType(), $profile2->getType()); |
|
92 |
+ $this->assertEquals($profile->getName(), $profile2->getName()); |
|
93 |
+ $this->assertEquals($profile->getDuration(), $profile2->getDuration()); |
|
94 |
+ |
|
95 |
+ $this->assertEquals($profile1->getTemplate(), $profile3->getTemplate()); |
|
96 |
+ $this->assertEquals($profile1->getType(), $profile3->getType()); |
|
97 |
+ $this->assertEquals($profile1->getName(), $profile3->getName()); |
|
98 |
+ } |
|
99 |
+} |