Xhgui_Controller_Run   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 387
Duplicated Lines 6.2 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 3
dl 24
loc 387
rs 9.1199
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B index() 0 57 4
A view() 0 49 4
A getFilters() 0 12 3
A deleteForm() 0 17 3
A deleteSubmit() 0 19 3
A deleteAllForm() 0 4 1
A deleteAllSubmit() 0 9 1
B url() 0 53 5
B compare() 0 55 7
A symbol() 0 20 1
A symbolShort() 0 22 1
A callgraph() 0 11 1
A callgraphData() 12 12 3
A callgraphDataDot() 12 12 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Xhgui_Controller_Run often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Xhgui_Controller_Run, and based on these observations, apply Extract Interface, too.

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_Searcher_Interface
14
     */
15
    private $searcher;
16
17
    public function __construct(Slim $app, Xhgui_Searcher_Interface $searcher)
18
    {
19
        parent::__construct($app);
20
        $this->searcher = $searcher;
21
    }
22
23
    public function index()
24
    {
25
        $response = $this->app->response();
26
        // The list changes whenever new profiles are recorded.
27
        // Generally avoid caching, but allow re-use in browser's bfcache
28
        // and by cache proxies for concurrent requests.
29
        // https://github.com/perftools/xhgui/issues/261
30
        $response->headers->set('Cache-Control', 'public, max-age=0');
31
32
        $request = $this->app->request();
33
34
        $search = array();
35
        $keys = array('date_start', 'date_end', 'url');
36
        foreach ($keys as $key) {
37
            if ($request->get($key)) {
38
                $search[$key] = $request->get($key);
39
            }
40
        }
41
        $sort = $request->get('sort');
42
43
        $result = $this->searcher->getAll(array(
44
            'sort' => $sort,
45
            'page' => $request->get('page'),
46
            'direction' => $request->get('direction'),
47
            'perPage' => $this->app->config('page.limit'),
48
            'conditions' => $search,
49
            'projection' => true,
50
        ));
51
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[$sort])) {
59
            $title = $titleMap[$sort];
60
        }
61
62
        $paging = array(
63
            'total_pages' => $result['totalPages'],
64
            'page' => $result['page'],
65
            'sort' => $sort,
66
            'direction' => $result['direction']
67
        );
68
69
        $this->_template = 'runs/list.twig';
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' => $search,
76
            'has_search' => strlen(implode('', $search)) > 0,
77
            'title' => $title
78
        ));
79
    }
80
81
    public function view()
82
    {
83
        $response = $this->app->response();
84
        // Permalink views to a specific run are meant to be public and immutable.
85
        // But limit the cache to only a short period of time (enough to allow
86
        // handling of abuse or other stampedes). This way we don't have to
87
        // deal with any kind of purging system for when profiles are deleted,
88
        // or for after XHGui itself is upgraded and static assets may be
89
        // incompatible etc.
90
        // https://github.com/perftools/xhgui/issues/261
91
        $response->headers->set('Cache-Control', 'public, max-age=60, must-revalidate');
92
93
        $request = $this->app->request();
94
        $detailCount = $this->app->config('detail.count');
95
        $result = $this->searcher->get($request->get('id'));
96
97
        $result->calculateSelf();
98
99
        // Self wall time graph
100
        $timeChart = $result->extractDimension('ewt', $detailCount);
101
102
        // Memory Block
103
        $memoryChart = $result->extractDimension('emu', $detailCount);
104
105
        // Watched Functions Block
106
        $watchedFunctions = array();
107
        foreach ($this->searcher->getAllWatches() as $watch) {
108
            $matches = $result->getWatched($watch['name']);
109
            if ($matches) {
110
                $watchedFunctions = array_merge($watchedFunctions, $matches);
111
            }
112
        }
113
114
        if (false !== $request->get(self::FILTER_ARGUMENT_NAME, false)) {
115
            $profile = $result->sort('ewt', $result->filter($result->getProfile(), $this->getFilters()));
116
        } else {
117
            $profile = $result->sort('ewt', $result->getProfile());
118
        }
119
120
        $this->_template = 'runs/view.twig';
121
        $this->set(array(
122
            'profile' => $profile,
123
            'result' => $result,
124
            'wall_time' => $timeChart,
125
            'memory' => $memoryChart,
126
            'watches' => $watchedFunctions,
127
            'date_format' => $this->app->config('date.format'),
128
        ));
129
    }
130
131
    /**
132
     * @return array
133
     */
134
    protected function getFilters()
135
    {
136
        $request = $this->app->request();
137
        $filterString = $request->get(self::FILTER_ARGUMENT_NAME);
138
        if (strlen($filterString) > 1 && $filterString !== 'true') {
139
            $filters = array_map('trim', explode(',', $filterString));
140
        } else {
141
            $filters = $this->app->config('run.view.filter.names');
142
        }
143
144
        return $filters;
145
    }
146
147
    public function deleteForm()
148
    {
149
        $request = $this->app->request();
150
        $id = $request->get('id');
151
        if (!is_string($id) || !strlen($id)) {
152
            throw new Exception('The "id" parameter is required.');
153
        }
154
155
        // Get details
156
        $result = $this->searcher->get($id);
157
158
        $this->_template = 'runs/delete-form.twig';
159
        $this->set(array(
160
            'run_id' => $id,
161
            'result' => $result,
162
        ));
163
    }
164
165
    public function deleteSubmit()
166
    {
167
        $request = $this->app->request();
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()
201
    {
202
        $request = $this->app->request();
203
        $pagination = array(
204
            'sort' => $request->get('sort'),
205
            'direction' => $request->get('direction'),
206
            'page' => $request->get('page'),
207
            'perPage' => $this->app->config('page.limit'),
208
        );
209
210
        $search = array();
211
        $keys = array('date_start', 'date_end', 'limit', 'limit_custom');
212
        foreach ($keys as $key) {
213
            $search[$key] = $request->get($key);
214
        }
215
216
        $runs = $this->searcher->getForUrl(
217
            $request->get('url'),
218
            $pagination,
219
            $search
220
        );
221
222
        if (isset($search['limit_custom']) &&
223
            strlen($search['limit_custom']) > 0 &&
224
            $search['limit_custom'][0] === 'P'
225
        ) {
226
            $search['limit'] = $search['limit_custom'];
227
        }
228
229
        $chartData = $this->searcher->getPercentileForUrl(
230
            90,
231
            $request->get('url'),
232
            $search
233
        );
234
235
        $paging = array(
236
            'total_pages' => $runs['totalPages'],
237
            'sort' => $pagination['sort'],
238
            'page' => $runs['page'],
239
            'direction' => $runs['direction']
240
        );
241
242
        $this->_template = 'runs/url.twig';
243
        $this->set(array(
244
            'paging' => $paging,
245
            'base_url' => 'url.view',
246
            'runs' => $runs['results'],
247
            'url' => $request->get('url'),
248
            'chart_data' => $chartData,
249
            'date_format' => $this->app->config('date.format'),
250
            'search' => array_merge($search, array('url' => $request->get('url'))),
251
        ));
252
    }
253
254
    public function compare()
255
    {
256
        $request = $this->app->request();
257
258
        $baseRun = $headRun = $candidates = $comparison = null;
259
        $paging = array();
260
261
        if ($request->get('base')) {
262
            $baseRun = $this->searcher->get($request->get('base'));
263
        }
264
265
        if ($baseRun && !$request->get('head')) {
266
            $pagination = array(
267
                'direction' => $request->get('direction'),
268
                'sort' => $request->get('sort'),
269
                'page' => $request->get('page'),
270
                'perPage' => $this->app->config('page.limit'),
271
            );
272
            $candidates = $this->searcher->getForUrl(
273
                $baseRun->getMeta('simple_url'),
274
                $pagination
275
            );
276
277
            $paging = array(
278
                'total_pages' => $candidates['totalPages'],
279
                'sort' => $pagination['sort'],
280
                'page' => $candidates['page'],
281
                'direction' => $candidates['direction']
282
            );
283
        }
284
285
        if ($request->get('head')) {
286
            $headRun = $this->searcher->get($request->get('head'));
287
        }
288
289
        if ($baseRun && $headRun) {
290
            $comparison = $baseRun->compare($headRun);
291
        }
292
293
        $this->_template = 'runs/compare.twig';
294
        $this->set(array(
295
            'base_url' => 'run.compare',
296
            'base_run' => $baseRun,
297
            'head_run' => $headRun,
298
            'candidates' => $candidates,
299
            'url_params' => $request->get(),
300
            'date_format' => $this->app->config('date.format'),
301
            'comparison' => $comparison,
302
            'paging' => $paging,
303
            'search' => array(
304
                'base' => $request->get('base'),
305
                'head' => $request->get('head'),
306
            )
307
        ));
308
    }
309
310
    public function symbol()
311
    {
312
        $request = $this->app->request();
313
        $id = $request->get('id');
314
        $symbol = $request->get('symbol');
315
316
        $profile = $this->searcher->get($id);
317
        $profile->calculateSelf();
318
        list($parents, $current, $children) = $profile->getRelatives($symbol);
319
320
        $this->_template = 'runs/symbol.twig';
321
        $this->set(array(
322
            'symbol' => $symbol,
323
            'id' => $id,
324
            'main' => $profile->get('main()'),
325
            'parents' => $parents,
326
            'current' => $current,
327
            'children' => $children,
328
        ));
329
    }
330
331
    public function symbolShort()
332
    {
333
        $request = $this->app->request();
334
        $id = $request->get('id');
335
        $threshold = $request->get('threshold');
336
        $symbol = $request->get('symbol');
337
        $metric = $request->get('metric');
338
339
        $profile = $this->searcher->get($id);
340
        $profile->calculateSelf();
341
        list($parents, $current, $children) = $profile->getRelatives($symbol, $metric, $threshold);
342
343
        $this->_template = 'runs/symbol-short.twig';
344
        $this->set(array(
345
            'symbol' => $symbol,
346
            'id' => $id,
347
            'main' => $profile->get('main()'),
348
            'parents' => $parents,
349
            'current' => $current,
350
            'children' => $children,
351
        ));
352
    }
353
354
    public function callgraph()
355
    {
356
        $request = $this->app->request();
357
        $profile = $this->searcher->get($request->get('id'));
358
359
        $this->_template = 'runs/callgraph.twig';
360
        $this->set(array(
361
            'profile' => $profile,
362
            'date_format' => $this->app->config('date.format'),
363
        ));
364
    }
365
366 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...
367
    {
368
        $request = $this->app->request();
369
        $response = $this->app->response();
370
        $profile = $this->searcher->get($request->get('id'));
371
        $metric = $request->get('metric') ?: 'wt';
372
        $threshold = (float)$request->get('threshold') ?: 0.01;
373
        $callgraph = $profile->getCallgraph($metric, $threshold);
374
375
        $response['Content-Type'] = 'application/json';
376
        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...
377
    }
378
379 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...
380
    {
381
        $request = $this->app->request();
382
        $response = $this->app->response();
383
        $profile = $this->searcher->get($request->get('id'));
384
        $metric = $request->get('metric') ?: 'wt';
385
        $threshold = (float)$request->get('threshold') ?: 0.01;
386
        $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...
387
388
        $response['Content-Type'] = 'application/json';
389
        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...
390
    }
391
}
392