Completed
Pull Request — master (#273)
by
unknown
01:12
created

Xhgui_Controller_Run::index()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

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