Completed
Pull Request — master (#346)
by Elan
01:49
created

RunController::deleteAllSubmit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
371
    {
372
        $request = $this->app->request();
373
        $response = $this->app->response();
374
        $profile = $this->searcher->get($request->get('id'));
375
        $metric = $request->get('metric') ?: 'wt';
376
        $threshold = (float)$request->get('threshold') ?: 0.01;
377
        $callgraph = $profile->getCallgraph($metric, $threshold);
378
379
        $response['Content-Type'] = 'application/json';
380
381
        return $response->body(json_encode($callgraph));
0 ignored issues
show
Bug introduced by
The method body cannot be called on $response (of type array<string,string,{"Content-Type":"string"}>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
382
    }
383
384 View Code Duplication
    public function callgraphDataDot()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385
    {
386
        $request = $this->app->request();
387
        $response = $this->app->response();
388
        $profile = $this->searcher->get($request->get('id'));
389
        $metric = $request->get('metric') ?: 'wt';
390
        $threshold = (float)$request->get('threshold') ?: 0.01;
391
        $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...
392
393
        $response['Content-Type'] = 'application/json';
394
395
        return $response->body(json_encode($callgraph));
0 ignored issues
show
Bug introduced by
The method body cannot be called on $response (of type array<string,string,{"Content-Type":"string"}>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
396
    }
397
}
398