1 <?php
2
3 4 5 6 7 8 9 10
11
12 namespace Icybee;
13
14 use ICanBoogie\Debug;
15 use ICanBoogie\HTTP\Dispatcher;
16
17 18 19
20 class StatsDecorator extends \Brickrouge\Decorator
21 {
22 23 24 25 26 27 28
29 static public function on_dispatcher_dispatch(Dispatcher\DispatchEvent $event, Dispatcher $target)
30 {
31 if ($event->request->is_xhr)
32 {
33 return;
34 }
35
36
37
38
39
40
41 $event->chain(function(Dispatcher\DispatchEvent $event, Dispatcher $target)
42 {
43 $response = $event->response;
44
45 if (!$response || $response->content_type->type != 'text/html')
46 {
47 return;
48 }
49
50 $response->body = new StatsDecorator($response->body);
51 });
52 }
53
54 public function render()
55 {
56 global $core;
57
58 $now = microtime(true);
59
60 $queries_count = 0;
61 $queries_time = 0;
62 $queries_stats = array();
63
64 foreach ($core->connections as $id => $connection)
65 {
66 $count = $connection->queries_count;
67 $queries_count += $count;
68 $queries_stats[] = $id . ': ' . $count;
69
70 foreach ($connection->profiling as $note)
71 {
72 $queries_time += $note[1] - $note[0];
73 }
74 }
75
76 $html = $this->component . PHP_EOL . '<!-- ' . \ICanBoogie\format
77 (
78 'Icybee – in :elapsed ms (core: :elapsed_core ms, db: :elapsed_queries ms), using :memory-usage (peak: :memory-peak), queries: :queries-count (:queries-details)', array
79 (
80 'elapsed' => number_format(($now - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2, '.', ''),
81 'elapsed_core' => number_format(($_SERVER['ICANBOOGIE_READY_TIME_FLOAT'] - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2, '.', ''),
82 'elapsed_queries' => number_format($queries_time * 1000, 2, '.', ''),
83 'memory-usage' => number_format(memory_get_usage() / (1024 * 1024), 3) . 'Mb',
84 'memory-peak' => number_format(memory_get_peak_usage() / (1024 * 1024), 3) . 'Mb',
85 'queries-count' => $queries_count,
86 'queries-details' => implode(', ', $queries_stats)
87 )
88 );
89
90 if (Debug::is_dev() || $core->user->is_admin)
91 {
92 $html .= "\n\n" . $this->render_events();
93 $html .= "\n\n" . $this->render_queries();
94 $html .= $this->render_translations();
95 }
96
97 $html .= ' -->';
98
99 return $html;
100 }
101
102 protected function render_events()
103 {
104 $events = \ICanBoogie\Event::$profiling['hooks'];
105
106 $max_length_type = 0;
107 $max_length_callback = 0;
108
109 $time_total = 0;
110 $time_by_type = array();
111
112 $calls_total = 0;
113 $calls_by_type = array();
114
115 foreach ($events as $i => $event)
116 {
117 list(, $type, $callback, $time) = $event;
118
119 if (!($callback instanceof \Closure))
120 {
121 continue;
122 }
123
124 $reflection = new \ReflectionFunction($callback);
125
126 $callback = '(closure) ' . \ICanBoogie\strip_root($reflection->getFileName()) . '@' . $reflection->getStartLine();
127
128 $events[$i][2] = $callback;
129 }
130
131 foreach ($events as $event)
132 {
133 list(, $type, $callback, $time) = $event;
134
135 if (!is_string($callback))
136 {
137 $callback = implode('::', $callback);
138 }
139
140 $max_length_type = max($max_length_type, strlen($type));
141 $max_length_callback = max($max_length_callback, strlen($callback));
142
143 if (empty($time_by_type[$type]))
144 {
145 $time_by_type[$type] = 0;
146 }
147
148 $time_total += $time;
149 $time_by_type[$type] += $time;
150
151 if (empty($calls_by_type[$type]))
152 {
153 $calls_by_type[$type] = 0;
154 }
155
156 $calls_total++;
157 $calls_by_type[$type]++;
158 }
159
160 $line_width = 4 + 2 + 8 + 1 + $max_length_type + 1 + $max_length_callback;
161
162 $title = sprintf("Events: %d in %.3f ms", count($events), $time_total * 1000);
163
164 $html = PHP_EOL;
165 $html .= $title . PHP_EOL;
166 $html .= str_repeat('—', strlen($title)) . PHP_EOL;
167
168 foreach ($events as $i => $event)
169 {
170 list(, $type, $callback, $time) = $event;
171
172 if ($callback instanceof \Closure)
173 {
174 $callback = 'Closure 0x' . spl_object_hash($callback);
175 }
176 else if (is_array($callback))
177 {
178 $callback = (is_string($callback[0]) ? $callback[0] : get_class($callback[0])) . '::' . $callback[1];
179 }
180
181 $html .= sprintf("%4d: %2.3f %-{$max_length_type}s %-{$max_length_callback}s", $i, $time * 1000, $type, $callback) . PHP_EOL;
182 }
183
184
185
186
187
188 $html .= "\n\nUnused events\n";
189 $html .= "—————————————\n";
190
191 $time_ref = $_SERVER['REQUEST_TIME_FLOAT'];
192
193 foreach (\ICanBoogie\Event::$profiling['unused'] as $i => $trace)
194 {
195 list($time, $type) = $trace;
196
197 $html .= sprintf("%4d: %9s %s\n", $i, sprintf("%5.3f", ($time - $time_ref) * 1000), $type);
198 }
199
200 return $html;
201 }
202
203 protected function render_queries()
204 {
205 global $core;
206
207 $html = '';
208
209 foreach ($core->connections as $id => $connection)
210 {
211 $traces = $connection->profiling;
212 $total_time = 0;
213 $lines = '';
214 $line_width = 0;
215
216 foreach ($traces as $i => $trace)
217 {
218 list($start, $finish, $query) = $trace;
219
220 $total_time += $finish - $start;
221 $line = sprintf("%4d: %9s %s\n", $i, sprintf("%5.3f", ($finish - $start) * 1000), $query);
222 $line_width = max($line_width, strlen($line));
223 $lines .= $line;
224 }
225
226 $header = sprintf("Queries to '%s': %d in %s ms", $id, count($traces), sprintf("%5.3f", ($total_time) * 1000));
227
228 $html .= $header . PHP_EOL;
229 $html .= str_repeat('—', strlen($header)) . PHP_EOL;
230 $html .= $lines . PHP_EOL . PHP_EOL;
231 }
232
233 return $html;
234 }
235
236 protected function render_translations()
237 {
238 $html = '';
239
240 foreach (\ICanBoogie\I18n\Translator::$missing as $str)
241 {
242 $html .= $str . PHP_EOL;
243 }
244
245 if (!$html)
246 {
247 return;
248 }
249
250 return "\n\nMissing translations\n————————————————————\n\n$html\n";
251 }
252 }