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

RunController::deleteSubmit()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 3
nc 2
nop 1
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(Request $request, Response $response)
86
    {
87
        // Permalink views to a specific run are meant to be public and immutable.
88
        // But limit the cache to only a short period of time (enough to allow
89
        // handling of abuse or other stampedes). This way we don't have to
90
        // deal with any kind of purging system for when profiles are deleted,
91
        // or for after XHGui itself is upgraded and static assets may be
92
        // incompatible etc.
93
        // https://github.com/perftools/xhgui/issues/261
94
        $response->headers->set('Cache-Control', 'public, max-age=60, must-revalidate');
95
96
        $detailCount = $this->app->config('detail.count');
97
        $result = $this->searcher->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 = [];
109
        foreach ($this->searcher->getAllWatches() as $watch) {
110
            $matches = $result->getWatched($watch['name']);
111
            if ($matches) {
112
                $watchedFunctions = array_merge($watchedFunctions, $matches);
113
            }
114
        }
115
116
        if (false !== $request->get(self::FILTER_ARGUMENT_NAME, false)) {
117
            $profile = $result->sort('ewt', $result->filter($result->getProfile(), $this->getFilters()));
118
        } else {
119
            $profile = $result->sort('ewt', $result->getProfile());
120
        }
121
122
        $this->_template = 'runs/view.twig';
123
        $this->set([
124
            'profile' => $profile,
125
            'result' => $result,
126
            'wall_time' => $timeChart,
127
            'memory' => $memoryChart,
128
            'watches' => $watchedFunctions,
129
            'date_format' => $this->app->config('date.format'),
130
        ]);
131
    }
132
133
    /**
134
     * @return array
135
     */
136
    protected function getFilters()
137
    {
138
        $request = $this->app->request();
139
        $filterString = $request->get(self::FILTER_ARGUMENT_NAME);
140
        if (strlen($filterString) > 1 && $filterString !== 'true') {
141
            $filters = array_map('trim', explode(',', $filterString));
142
        } else {
143
            $filters = $this->app->config('run.view.filter.names');
144
        }
145
146
        return $filters;
147
    }
148
149
    public function deleteForm(Request $request)
150
    {
151
        $id = $request->get('id');
152
        if (!is_string($id) || !strlen($id)) {
153
            throw new Exception('The "id" parameter is required.');
154
        }
155
156
        // Get details
157
        $result = $this->searcher->get($id);
158
159
        $this->_template = 'runs/delete-form.twig';
160
        $this->set([
161
            'run_id' => $id,
162
            'result' => $result,
163
        ]);
164
    }
165
166
    public function deleteSubmit(Request $request)
167
    {
168
        $id = $request->post('id');
169
        // Don't call profilers->delete() unless $id is set,
170
        // otherwise it will turn the null into a MongoId and return "Sucessful".
171
        if (!is_string($id) || !strlen($id)) {
172
            // Form checks this already,
173
            // only reachable by handcrafted or malformed requests.
174
            throw new Exception('The "id" parameter is required.');
175
        }
176
177
        // Delete the profile run.
178
        $this->searcher->delete($id);
179
180
        $this->app->flash('success', 'Deleted profile ' . $id);
181
182
        $this->app->redirect($this->app->urlFor('home'));
183
    }
184
185
    public function deleteAllForm()
186
    {
187
        $this->_template = 'runs/delete-all-form.twig';
188
    }
189
190
    public function deleteAllSubmit()
191
    {
192
        // Delete all profile runs.
193
        $this->searcher->truncate();
194
195
        $this->app->flash('success', 'Deleted all profiles');
196
197
        $this->app->redirect($this->app->urlFor('home'));
198
    }
199
200
    public function url(Request $request)
201
    {
202
        $pagination = [
203
            'sort' => $request->get('sort'),
204
            'direction' => $request->get('direction'),
205
            'page' => $request->get('page'),
206
            'perPage' => $this->app->config('page.limit'),
207
        ];
208
209
        $search = [];
210
        $keys = ['date_start', 'date_end', 'limit', 'limit_custom'];
211
        foreach ($keys as $key) {
212
            $search[$key] = $request->get($key);
213
        }
214
215
        $runs = $this->searcher->getForUrl(
216
            $request->get('url'),
217
            $pagination,
218
            $search
219
        );
220
221
        if (isset($search['limit_custom']) &&
222
            strlen($search['limit_custom']) > 0 &&
223
            $search['limit_custom'][0] === 'P'
224
        ) {
225
            $search['limit'] = $search['limit_custom'];
226
        }
227
228
        $chartData = $this->searcher->getPercentileForUrl(
229
            90,
230
            $request->get('url'),
231
            $search
232
        );
233
234
        $paging = [
235
            'total_pages' => $runs['totalPages'],
236
            'sort' => $pagination['sort'],
237
            'page' => $runs['page'],
238
            'direction' => $runs['direction'],
239
        ];
240
241
        $this->_template = 'runs/url.twig';
242
        $this->set([
243
            'paging' => $paging,
244
            'base_url' => 'url.view',
245
            'runs' => $runs['results'],
246
            'url' => $request->get('url'),
247
            'chart_data' => $chartData,
248
            'date_format' => $this->app->config('date.format'),
249
            'search' => array_merge($search, ['url' => $request->get('url')]),
250
        ]);
251
    }
252
253
    public function compare(Request $request)
254
    {
255
        $baseRun = $headRun = $candidates = $comparison = null;
256
        $paging = [];
257
258
        if ($request->get('base')) {
259
            $baseRun = $this->searcher->get($request->get('base'));
260
        }
261
262
        if ($baseRun && !$request->get('head')) {
263
            $pagination = [
264
                'direction' => $request->get('direction'),
265
                'sort' => $request->get('sort'),
266
                'page' => $request->get('page'),
267
                'perPage' => $this->app->config('page.limit'),
268
            ];
269
            $candidates = $this->searcher->getForUrl(
270
                $baseRun->getMeta('simple_url'),
271
                $pagination
272
            );
273
274
            $paging = [
275
                'total_pages' => $candidates['totalPages'],
276
                'sort' => $pagination['sort'],
277
                'page' => $candidates['page'],
278
                'direction' => $candidates['direction'],
279
            ];
280
        }
281
282
        if ($request->get('head')) {
283
            $headRun = $this->searcher->get($request->get('head'));
284
        }
285
286
        if ($baseRun && $headRun) {
287
            $comparison = $baseRun->compare($headRun);
288
        }
289
290
        $this->_template = 'runs/compare.twig';
291
        $this->set([
292
            'base_url' => 'run.compare',
293
            'base_run' => $baseRun,
294
            'head_run' => $headRun,
295
            'candidates' => $candidates,
296
            'url_params' => $request->get(),
297
            'date_format' => $this->app->config('date.format'),
298
            'comparison' => $comparison,
299
            'paging' => $paging,
300
            'search' => [
301
                'base' => $request->get('base'),
302
                'head' => $request->get('head'),
303
            ],
304
        ]);
305
    }
306
307
    public function symbol(Request $request)
308
    {
309
        $id = $request->get('id');
310
        $symbol = $request->get('symbol');
311
312
        $profile = $this->searcher->get($id);
313
        $profile->calculateSelf();
314
        list($parents, $current, $children) = $profile->getRelatives($symbol);
315
316
        $this->_template = 'runs/symbol.twig';
317
        $this->set([
318
            'symbol' => $symbol,
319
            'id' => $id,
320
            'main' => $profile->get('main()'),
321
            'parents' => $parents,
322
            'current' => $current,
323
            'children' => $children,
324
        ]);
325
    }
326
327
    public function symbolShort(Request $request)
328
    {
329
        $id = $request->get('id');
330
        $threshold = $request->get('threshold');
331
        $symbol = $request->get('symbol');
332
        $metric = $request->get('metric');
333
334
        $profile = $this->searcher->get($id);
335
        $profile->calculateSelf();
336
        list($parents, $current, $children) = $profile->getRelatives($symbol, $metric, $threshold);
337
338
        $this->_template = 'runs/symbol-short.twig';
339
        $this->set([
340
            'symbol' => $symbol,
341
            'id' => $id,
342
            'main' => $profile->get('main()'),
343
            'parents' => $parents,
344
            'current' => $current,
345
            'children' => $children,
346
        ]);
347
    }
348
349
    public function callgraph(Request $request)
350
    {
351
        $profile = $this->searcher->get($request->get('id'));
352
353
        $this->_template = 'runs/callgraph.twig';
354
        $this->set([
355
            'profile' => $profile,
356
            'date_format' => $this->app->config('date.format'),
357
        ]);
358
    }
359
360 View Code Duplication
    public function callgraphData(Request $request, Response $response)
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...
361
    {
362
        $profile = $this->searcher->get($request->get('id'));
363
        $metric = $request->get('metric') ?: 'wt';
364
        $threshold = (float)$request->get('threshold') ?: 0.01;
365
        $callgraph = $profile->getCallgraph($metric, $threshold);
366
367
        $response['Content-Type'] = 'application/json';
368
369
        return $response->body(json_encode($callgraph));
370
    }
371
372 View Code Duplication
    public function callgraphDataDot(Request $request, Response $response)
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...
373
    {
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->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...
378
379
        $response['Content-Type'] = 'application/json';
380
381
        return $response->body(json_encode($callgraph));
382
    }
383
}
384