Browse code

added a profiler

Fabien Potencier authored on 14/01/2015 12:04:33
Showing 19 changed files
... ...
@@ -1,5 +1,6 @@
1
-* 1.17.1 (2015-XX-XX)
1
+* 1.18.0 (2015-XX-XX)
2 2
 
3
+ * added a profiler
3 4
  * fixed filesystem loader cache when different file paths are used for the same template
4 5
 
5 6
 * 1.17.0 (2015-01-14)
... ...
@@ -36,7 +36,7 @@
36 36
     },
37 37
     "extra": {
38 38
         "branch-alias": {
39
-            "dev-master": "1.17-dev"
39
+            "dev-master": "1.18-dev"
40 40
         }
41 41
     }
42 42
 }
... ...
@@ -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
 
... ...
@@ -15,7 +15,7 @@
15 15
 #ifndef PHP_TWIG_H
16 16
 #define PHP_TWIG_H
17 17
 
18
-#define PHP_TWIG_VERSION "1.17.1-DEV"
18
+#define PHP_TWIG_VERSION "1.18.0-DEV"
19 19
 
20 20
 #include "php.h"
21 21
 
... ...
@@ -16,7 +16,7 @@
16 16
  */
17 17
 class Twig_Environment
18 18
 {
19
-    const VERSION = '1.17.1-DEV';
19
+    const VERSION = '1.18.0-DEV';
20 20
 
21 21
     protected $charset;
22 22
     protected $loader;
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
+}