Completed
Pull Request — master (#268)
by
unknown
01:13
created

Xhgui_Controller_Run::index()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 9.264
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
use Slim\Slim;
4
5
class Xhgui_Controller_Run extends Xhgui_Controller
6
{
7
    /**
8
     * HTTP GET attribute name for comma separated filters
9
     */
10
    const FILTER_ARGUMENT_NAME = 'filter';
11
    
12
    /**
13
     * @var Xhgui_Profiles
14
     */
15
    private $profiles;
16
17
    /**
18
     * @var \Xhgui_WatchedFunctionsStorageInterface
19
     */
20
    private $watches;
21
22
    /**
23
     * Xhgui_Controller_Run constructor.
24
     * @param Slim $app
25
     * @param Xhgui_Profiles $profiles
26
     * @param Xhgui_WatchedFunctionsStorageInterface $watches
27
     */
28
    public function __construct(Slim $app, Xhgui_Profiles $profiles, \Xhgui_WatchedFunctionsStorageInterface $watches)
29
    {
30
        parent::__construct($app);
31
        $this->setProfiles($profiles);
32
        $this->setWatches($watches);
33
    }
34
35
    /**
36
     *
37
     */
38
    public function index()
39
    {
40
        $response = $this->app->response();
41
        // The list changes whenever new profiles are recorded.
42
        // Generally avoid caching, but allow re-use in browser's bfcache
43
        // and by cache proxies for concurrent requests.
44
        // https://github.com/perftools/xhgui/issues/261
45
        $response->headers->set('Cache-Control', 'public, max-age=0');
46
47
        $request = $this->app->request();
48
49
        $filter = Xhgui_Storage_Filter::fromRequest($request);
50
51
        $result = $this->getProfiles()->getAll($filter);
52
        $title = 'Recent runs';
53
        $titleMap = array(
54
            'wt'    => 'Longest wall time',
55
            'cpu'   => 'Most CPU time',
56
            'mu'    => 'Highest memory use',
57
        );
58
        if (isset($titleMap[$filter->getSort()])) {
59
            $title = $titleMap[$filter->getSort()];
60
        }
61
        $paging = array(
62
            'total_pages'   => $result['totalPages'],
63
            'page'          => $result['page'],
64
            'sort'          => $filter->getSort(),
65
            'direction'     => $result['direction']
66
        );
67
68
        $this->_template = 'runs/list.twig';
69
70
        $this->set(array(
71
            'paging'        => $paging,
72
            'base_url'      => 'home',
73
            'runs'          => $result['results'],
74
            'date_format'   => $this->app->config('date.format'),
75
            'search'        => $filter->toArray(),
76
            'title'         => $title
77
        ));
78
    }
79
80
    /**
81
     *
82
     */
83
    public function view()
84
    {
85
        $response = $this->app->response();
86
        // Permalink views to a specific run are meant to be public and immutable.
87
        // But limit the cache to only a short period of time (enough to allow
88
        // handling of abuse or other stampedes). This way we don't have to
89
        // deal with any kind of purging system for when profiles are deleted,
90
        // or for after XHGui itself is upgraded and static assets may be
91
        // incompatible etc.
92
        // https://github.com/perftools/xhgui/issues/261
93
        $response->headers->set('Cache-Control', 'public, max-age=60, must-revalidate');
94
95
        $request = $this->app->request();
96
        $detailCount = $this->app->config('detail.count');
97
        $result = $this->getProfiles()->get($request->get('id'));
98
99
        $result->calculateSelf();
100
101
        // Self wall time graph
102
        $timeChart = $result->extractDimension('ewt', $detailCount);
103
104
        // Memory Block
105
        $memoryChart = $result->extractDimension('emu', $detailCount);
106
107
        // Watched Functions Block
108
        $watchedFunctions = array();
109
        foreach ($this->getWatches()->getWatchedFunctions() as $watch) {
110
            $matches = $result->getWatched($watch['name']);
111
112
            if ($matches) {
113
                $watchedFunctions = array_merge($watchedFunctions, $matches);
114
            }
115
        }
116
117
        if (false !== $request->get(self::FILTER_ARGUMENT_NAME, false)) {
118
            $profile = $result->sort('ewt', $result->filter($result->getProfile(), $this->getFilters()));
119
        } else {
120
            $profile = $result->sort('ewt', $result->getProfile());
121
        }
122
123
        $this->_template = 'runs/view.twig';
124
        $this->set(array(
125
            'profile'       => $profile,
126
            'result'        => $result,
127
            'wall_time'     => $timeChart,
128
            'memory'        => $memoryChart,
129
            'watches'       => $watchedFunctions,
130
            'date_format'   => $this->app->config('date.format'),
131
        ));
132
    }
133
134
    /**
135
     * @return array
136
     */
137
    protected function getFilters()
138
    {
139
        $request = $this->app->request();
140
        $filterString = $request->get(self::FILTER_ARGUMENT_NAME);
141
        if (strlen($filterString) > 1 && $filterString !== 'true') {
142
            $filters = array_map('trim', explode(',', $filterString));
143
        } else {
144
            $filters = $this->app->config('run.view.filter.names');
145
        }
146
147
        return $filters;
148
    }
149
    
150
    /**
151
     * @throws Exception
152
     */
153
    public function deleteForm()
154
    {
155
        $request = $this->app->request();
156
        $id = $request->get('id');
157
        if (empty($id)) {
158
            throw new Exception('The "id" parameter is required.');
159
        }
160
161
        // Get details
162
        $result = $this->getProfiles()->get($id);
163
164
        $this->_template = 'runs/delete-form.twig';
165
        $this->set(array(
166
            'run_id' => $id,
167
            'result' => $result,
168
        ));
169
    }
170
171
    /**
172
     * @throws Exception
173
     */
174
    public function deleteSubmit()
175
    {
176
        $request = $this->app->request();
177
        $id = $request->post('id');
178
179
        // Don't call profilers->delete() unless $id is set,
180
        // otherwise it will turn the null into a MongoId and return "Sucessful".
181
        if (empty($id)) {
182
            // Form checks this already,
183
            // only reachable by handcrafted or malformed requests.
184
            throw new Exception('The "id" parameter is required.');
185
        }
186
187
        // Delete the profile run.
188
        $this->getProfiles()->delete($id);
189
190
        $this->app->flash('success', 'Deleted profile ' . $id);
191
192
        $this->app->redirect($this->app->urlFor('home'));
193
    }
194
195
    /**
196
     *
197
     */
198
    public function deleteAllForm()
199
    {
200
        $this->_template = 'runs/delete-all-form.twig';
201
    }
202
203
    /**
204
     *
205
     */
206
    public function deleteAllSubmit()
207
    {
208
        // Delete all profile runs.
209
        $this->getProfiles()->truncate();
210
211
        $this->app->flash('success', 'Deleted all profiles');
212
213
        $this->app->redirect($this->app->urlFor('home'));
214
    }
215
216
    /**
217
     *
218
     */
219
    public function url()
220
    {
221
        $request = $this->app->request();
222
223
        $filter = Xhgui_Storage_Filter::fromRequest($request);
224
        $filter->setUrl($request->get('url'));
225
        $result = $this->getProfiles()->getAll($filter);
226
227
        $chartData = $this->getProfiles()->getPercentileForUrl(
228
            90,
229
            $request->get('url'),
230
            $filter
231
        );
232
233
        $paging = array(
234
            'total_pages'   => $result['totalPages'],
235
            'sort'          => $filter->getSort(),
236
            'page'          => $result['page'],
237
            'direction'     => $result['direction']
238
        );
239
240
        $this->_template = 'runs/url.twig';
241
        $this->set(array(
242
            'paging'        => $paging,
243
            'base_url'      => 'url.view',
244
            'runs'          => $result['results'],
245
            'url'           => $filter->getUrl(),
246
            'chart_data'    => $chartData,
247
            'date_format'   => $this->app->config('date.format'),
248
            'search'        => array_merge($filter->toArray(), array('url' => $request->get('url'))),
249
        ));
250
    }
251
252
    /**
253
     *
254
     */
255
    public function compare()
256
    {
257
        $request = $this->app->request();
258
259
        $baseRun = $headRun = $candidates = $comparison = null;
260
        $paging = array();
261
262
        if ($request->get('base')) {
263
            $baseRun = $this->getProfiles()->get($request->get('base'));
264
        }
265
266
        // we have one selected but we need to list other runs.
267
        if ($baseRun && !$request->get('head')) {
268
            $filter = Xhgui_Storage_Filter::fromRequest($request);
269
            $filter->setUrl($baseRun->getMeta('simple_url'));
270
271
            $candidates = $this->getProfiles()->getAll($filter);
272
273
            $paging = array(
274
                'total_pages'   => $candidates['totalPages'],
275
                'page'          => $candidates['page'],
276
                'sort'          => $filter->getSort(),
277
                'direction'     => $candidates['direction']
278
            );
279
        }
280
281
        if ($request->get('head')) {
282
            $headRun = $this->getProfiles()->get($request->get('head'));
283
        }
284
285
        if ($baseRun && $headRun) {
286
            $comparison = $baseRun->compare($headRun);
287
        }
288
289
        $this->_template = 'runs/compare.twig';
290
        $this->set(array(
291
            'base_url'      => 'run.compare',
292
            'base_run'      => $baseRun,
293
            'head_run'      => $headRun,
294
            'candidates'    => $candidates,
295
            'url_params'    => $request->get(),
296
            'date_format'   => $this->app->config('date.format'),
297
            'comparison'    => $comparison,
298
            'paging'        => $paging,
299
            'search'        => array(
300
                'base' => $request->get('base'),
301
                'head' => $request->get('head'),
302
            )
303
        ));
304
    }
305
306
    /**
307
     *
308
     */
309
    public function symbol()
310
    {
311
        $request    = $this->app->request();
312
        $id         = $request->get('id');
313
        $symbol     = $request->get('symbol');
314
315
        $profile = $this->getProfiles()->get($id);
316
        $profile->calculateSelf();
317
        list($parents, $current, $children) = $profile->getRelatives($symbol);
318
319
        $this->_template = 'runs/symbol.twig';
320
        $this->set(array(
321
            'symbol'    => $symbol,
322
            'id'        => $id,
323
            'main'      => $profile->get('main()'),
324
            'parents'   => $parents,
325
            'current'   => $current,
326
            'children'  => $children,
327
        ));
328
    }
329
330
    /**
331
     *
332
     */
333
    public function symbolShort()
334
    {
335
        $request    = $this->app->request();
336
        $id         = $request->get('id');
337
        $threshold  = $request->get('threshold');
338
        $symbol     = $request->get('symbol');
339
        $metric     = $request->get('metric');
340
341
        $profile = $this->getProfiles()->get($id);
342
        $profile->calculateSelf();
343
        list($parents, $current, $children) = $profile->getRelatives($symbol, $metric, $threshold);
344
345
        $this->_template = 'runs/symbol-short.twig';
346
        $this->set(array(
347
            'symbol'    => $symbol,
348
            'id'        => $id,
349
            'main'      => $profile->get('main()'),
350
            'parents'   => $parents,
351
            'current'   => $current,
352
            'children'  => $children,
353
        ));
354
    }
355
356
    /**
357
     *
358
     */
359
    public function callgraph()
360
    {
361
        $request = $this->app->request();
362
        $profile = $this->getProfiles()->get($request->get('id'));
363
364
        $this->_template = 'runs/callgraph.twig';
365
        $this->set(array(
366
            'profile'       => $profile,
367
            'date_format'   => $this->app->config('date.format'),
368
        ));
369
    }
370
371
    /**
372
     * @return string
373
     * @throws Exception
374
     */
375
    public function callgraphData($nodes = false)
376
    {
377
        $request    = $this->app->request();
378
        $response   = $this->app->response();
379
        $profile    = $this->getProfiles()->get($request->get('id'));
380
        $metric     = $request->get('metric') ?: 'wt';
381
        $threshold  = (float)$request->get('threshold') ?: 0.01;
382
383
        if ($nodes) {
384
            $callgraph  = $profile->getCallgraphNodes($metric, $threshold);
0 ignored issues
show
Bug introduced by
The method getCallgraphNodes() does not exist on Xhgui_Profile. Did you maybe mean getCallgraph()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
385
        } else {
386
            $callgraph  = $profile->getCallgraph($metric, $threshold);
387
        }
388
389
        $response->headers->set('Content-Type', 'application/json');
390
        return $response->body(json_encode($callgraph));
391
    }
392
393
    /**
394
     * @return Xhgui_WatchedFunctionsStorageInterface
395
     */
396
    public function getWatches()
397
    {
398
        return $this->watches;
399
    }
400
401
    /**
402
     * @param Xhgui_WatchedFunctionsStorageInterface $watches
403
     */
404
    public function setWatches($watches)
405
    {
406
        $this->watches = $watches;
407
    }
408
409
    /**
410
     * @return Xhgui_Profiles
411
     */
412
    public function getProfiles()
413
    {
414
        return $this->profiles;
415
    }
416
417
    /**
418
     * @param Xhgui_Profiles $profiles
419
     */
420
    public function setProfiles($profiles)
421
    {
422
        $this->profiles = $profiles;
423
    }
424
}
425