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
12namespace Twig\Profiler;
13
14/**
15 * @author Fabien Potencier <fabien@symfony.com>
16 */
17final class Profile implements \IteratorAggregate, \Serializable
18{
19    public const ROOT = 'ROOT';
20    public const BLOCK = 'block';
21    public const TEMPLATE = 'template';
22    public const MACRO = 'macro';
23    private $starts = [];
24    private $ends = [];
25    private $profiles = [];
26
27    public function __construct(
28        private string $template = 'main',
29        private string $type = self::ROOT,
30        private string $name = 'main',
31    ) {
32        $this->name = str_starts_with($name, '__internal_') ? 'INTERNAL' : $name;
33        $this->enter();
34    }
35
36    public function getTemplate(): string
37    {
38        return $this->template;
39    }
40
41    public function getType(): string
42    {
43        return $this->type;
44    }
45
46    public function getName(): string
47    {
48        return $this->name;
49    }
50
51    public function isRoot(): bool
52    {
53        return self::ROOT === $this->type;
54    }
55
56    public function isTemplate(): bool
57    {
58        return self::TEMPLATE === $this->type;
59    }
60
61    public function isBlock(): bool
62    {
63        return self::BLOCK === $this->type;
64    }
65
66    public function isMacro(): bool
67    {
68        return self::MACRO === $this->type;
69    }
70
71    /**
72     * @return Profile[]
73     */
74    public function getProfiles(): array
75    {
76        return $this->profiles;
77    }
78
79    public function addProfile(self $profile): void
80    {
81        $this->profiles[] = $profile;
82    }
83
84    /**
85     * Returns the duration in microseconds.
86     */
87    public function getDuration(): float
88    {
89        if ($this->isRoot() && $this->profiles) {
90            // for the root node with children, duration is the sum of all child durations
91            $duration = 0;
92            foreach ($this->profiles as $profile) {
93                $duration += $profile->getDuration();
94            }
95
96            return $duration;
97        }
98
99        return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0;
100    }
101
102    /**
103     * Returns the start time in microseconds.
104     */
105    public function getStartTime(): float
106    {
107        return $this->starts['wt'] ?? 0.0;
108    }
109
110    /**
111     * Returns the end time in microseconds.
112     */
113    public function getEndTime(): float
114    {
115        return $this->ends['wt'] ?? 0.0;
116    }
117
118    /**
119     * Returns the memory usage in bytes.
120     */
121    public function getMemoryUsage(): int
122    {
123        return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0;
124    }
125
126    /**
127     * Returns the peak memory usage in bytes.
128     */
129    public function getPeakMemoryUsage(): int
130    {
131        return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0;
132    }
133
134    /**
135     * Starts the profiling.
136     */
137    public function enter(): void
138    {
139        $this->starts = [
140            'wt' => microtime(true),
141            'mu' => memory_get_usage(),
142            'pmu' => memory_get_peak_usage(),
143        ];
144    }
145
146    /**
147     * Stops the profiling.
148     */
149    public function leave(): void
150    {
151        $this->ends = [
152            'wt' => microtime(true),
153            'mu' => memory_get_usage(),
154            'pmu' => memory_get_peak_usage(),
155        ];
156    }
157
158    public function reset(): void
159    {
160        $this->starts = $this->ends = $this->profiles = [];
161        $this->enter();
162    }
163
164    public function getIterator(): \Traversable
165    {
166        return new \ArrayIterator($this->profiles);
167    }
168
169    public function serialize(): string
170    {
171        return serialize($this->__serialize());
172    }
173
174    public function unserialize($data): void
175    {
176        $this->__unserialize(unserialize($data, ['allowed_classes' => [self::class]]));
177    }
178
179    /**
180     * @internal
181     */
182    public function __serialize(): array
183    {
184        return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles];
185    }
186
187    /**
188     * @internal
189     */
190    public function __unserialize(array $data): void
191    {
192        [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles] = $data;
193    }
194}
195