Completed
Pull Request — master (#210)
by
unknown
04:24
created

Xhgui_Profiles::getAvgsForUrl()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 37
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 8.439
c 0
b 0
f 0
cc 5
eloc 26
nc 12
nop 2
1
<?php
2
/**
3
 * Contains logic for getting/creating/removing profile records.
4
 */
5
class Xhgui_Profiles
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
6
{
7
    protected $_collection;
8
9
    protected $_mapper;
10
11
    public function __construct(MongoDb $db)
12
    {
13
        $this->_collection = $db->results;
0 ignored issues
show
Bug introduced by
The property results does not seem to exist in MongoDB.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
14
        $this->_mapper = new Xhgui_Db_Mapper();
15
    }
16
17
    /**
18
     * Get the latest profile data.
19
     *
20
     * @return Xhgui_Profile
21
     */
22
    public function latest()
23
    {
24
        $cursor = $this->_collection->find()
25
            ->sort(array('meta.request_date' => -1))
26
            ->limit(1);
27
        $result = $cursor->getNext();
28
        return $this->_wrap($result);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_wrap($result); of type Xhgui_Profile|array adds the type array to the return on line 28 which is incompatible with the return type documented by Xhgui_Profiles::latest of type Xhgui_Profile.
Loading history...
29
    }
30
31
    public function query($conditions, $fields = null)
32
    {
33
        return $this->_collection->find($conditions, $fields);
34
    }
35
36
    /**
37
     * Get a single profile run by id.
38
     *
39
     * @param string $id The id of the profile to get.
40
     * @return Xhgui_Profile
41
     */
42
    public function get($id)
43
    {
44
        return $this->_wrap($this->_collection->findOne(array(
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_wrap($this->_col...> new \MongoId($id)))); of type Xhgui_Profile|array adds the type array to the return on line 44 which is incompatible with the return type documented by Xhgui_Profiles::get of type Xhgui_Profile.
Loading history...
45
            '_id' => new MongoId($id)
46
        )));
47
    }
48
49
    /**
50
     * Get the list of profiles for a simplified url.
51
     *
52
     * @param string $url The url to load profiles for.
53
     * @param array $options Pagination options to use.
54
     * @param array $conditions The search options.
55
     * @return MongoCursor
56
     */
57
    public function getForUrl($url, $options, $conditions = array())
58
    {
59
        $conditions = array_merge(
60
            (array)$conditions,
61
            array('simple_url' => $url)
62
        );
63
        $options = array_merge($options, array(
64
            'conditions' => $conditions,
65
        ));
66
        return $this->paginate($options);
67
    }
68
69
    public function paginate($options)
70
    {
71
        $opts = $this->_mapper->convert($options);
72
73
        $totalRows = $this->_collection->find(
74
            $opts['conditions'],
75
            array('_id' => 1))->count();
76
77
        $totalPages = max(ceil($totalRows / $opts['perPage']), 1);
78
        $page = 1;
79
        if (isset($options['page'])) {
80
            $page = min(max($options['page'], 1), $totalPages);
81
        }
82
83
        $projection = false;
84
        if (isset($options['projection'])) {
85
            if ($options['projection'] === true) {
86
                $projection = array('meta' => 1, 'profile.main()' => 1);
87
            } else {
88
                $projection = $options['projection'];
89
            }
90
        }
91
92
        if ($projection === false) {
93
            $cursor = $this->_collection->find($opts['conditions'])
94
                ->sort($opts['sort'])
95
                ->skip((int)($page - 1) * $opts['perPage'])
96
                ->limit($opts['perPage']);
97
        } else {
98
            $cursor = $this->_collection->find($opts['conditions'], $projection)
99
                ->sort($opts['sort'])
100
                ->skip((int)($page - 1) * $opts['perPage'])
101
                ->limit($opts['perPage']);
102
        }
103
104
        return array(
105
            'results' => $this->_wrap($cursor),
106
            'sort' => $opts['sort'],
107
            'direction' => $opts['direction'],
108
            'page' => $page,
109
            'perPage' => $opts['perPage'],
110
            'totalPages' => $totalPages
111
        );
112
    }
113
114
    /**
115
     * Get the Percentile metrics for a URL
116
     *
117
     * This will group data by date and returns only the
118
     * percentile + date, making the data ideal for time series graphs
119
     *
120
     * @param integer $percentile The percentile you want. e.g. 90.
121
     * @param string $url
122
     * @param array $search Search options containing date_start and or date_end
123
     * @return array Array of metrics grouped by date
124
     */
125
    public function getPercentileForUrl($percentile, $url, $search = array())
126
    {
127
        $result = $this->_mapper->convert(array(
128
            'conditions' => $search + array('simple_url' => $url)
129
        ));
130
        $match = $result['conditions'];
131
132
        $col = '$meta.request_date';
133 View Code Duplication
        if (!empty($search['limit']) && $search['limit'][0] == "P") {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
134
            $col = '$meta.request_ts';
135
        }
136
137
        $results = $this->_collection->aggregate(array(
138
            array('$match' => $match),
139
            array(
140
                '$project' => array(
141
                    'date' => $col,
142
                    'profile.main()' => 1
143
                )
144
            ),
145
            array(
146
                '$group' => array(
147
                    '_id' => '$date',
148
                    'row_count' => array('$sum' => 1),
149
                    'wall_times' => array('$push' => '$profile.main().wt'),
150
                    'cpu_times' => array('$push' => '$profile.main().cpu'),
151
                    'mu_times' => array('$push' => '$profile.main().mu'),
152
                    'pmu_times' => array('$push' => '$profile.main().pmu'),
153
                )
154
            ),
155
            array(
156
                '$project' => array(
157
                    'date' => '$date',
158
                    'row_count' => '$row_count',
159
                    'raw_index' => array(
160
                        '$multiply' => array(
161
                            '$row_count',
162
                            $percentile / 100
163
                        )
164
                    ),
165
                    'wall_times' => '$wall_times',
166
                    'cpu_times' => '$cpu_times',
167
                    'mu_times' => '$mu_times',
168
                    'pmu_times' => '$pmu_times',
169
                )
170
            ),
171
            array('$sort' => array('_id' => 1)),
172
        ));
173
174
        if (empty($results['result'])) {
175
            return array();
176
        }
177
        $keys = array(
178
            'wall_times' => 'wt',
179
            'cpu_times' => 'cpu',
180
            'mu_times' => 'mu',
181
            'pmu_times' => 'pmu'
182
        );
183
        foreach ($results['result'] as &$result) {
184
            $result['date'] = ($result['_id'] instanceof MongoDate) ? date('Y-m-d H:i:s', $result['_id']->sec) : $result['_id'];
185
            unset($result['_id']);
186
            $index = max(round($result['raw_index']) - 1, 0);
187
            foreach ($keys as $key => $out) {
188
                sort($result[$key]);
189
                $result[$out] = isset($result[$key][$index]) ? $result[$key][$index] : null;
190
                unset($result[$key]);
191
            }
192
        }
193
        return $results['result'];
194
    }
195
196
    /**
197
     * Get the Average metrics for a URL
198
     *
199
     * This will group data by date and returns only the
200
     * avg + date, making the data ideal for time series graphs
201
     *
202
     * @param string $url
203
     * @param array $search Search options containing date_start and or date_end
204
     * @return array Array of metrics grouped by date
205
     */
206
    public function getAvgsForUrl($url, $search = array())
207
    {
208
        $match = array('meta.simple_url' => $url);
209
        if (isset($search['date_start'])) {
210
            $match['meta.request_date']['$gte'] = (string)$search['date_start'];
211
        }
212
        if (isset($search['date_end'])) {
213
            $match['meta.request_date']['$lte'] = (string)$search['date_end'];
214
        }
215
        $results = $this->_collection->aggregate(array(
216
            array('$match' => $match),
217
            array(
218
                '$project' => array(
219
                    'date' => '$meta.request_date',
220
                    'profile.main()' => 1,
221
                )
222
            ),
223
            array(
224
                '$group' => array(
225
                    '_id' => '$date',
226
                    'avg_wt' => array('$avg' => '$profile.main().wt'),
227
                    'avg_cpu' => array('$avg' => '$profile.main().cpu'),
228
                    'avg_mu' => array('$avg' => '$profile.main().mu'),
229
                    'avg_pmu' => array('$avg' => '$profile.main().pmu'),
230
                )
231
            ),
232
            array('$sort' => array('_id' => 1))
233
        ));
234
        if (empty($results['result'])) {
235
            return array();
236
        }
237
        foreach ($results['result'] as $i => $result) {
238
            $results['result'][$i]['date'] = $result['_id'];
239
            unset($results['result'][$i]['_id']);
240
        }
241
        return $results['result'];
242
    }
243
244
    /**
245
     * Get a paginated set of results.
246
     *
247
     * @param array $options The find options to use.
248
     * @return array An array of result data.
249
     */
250
    public function getAll($options = array())
251
    {
252
        return $this->paginate($options);
253
    }
254
255
    /**
256
     * Encodes a profile to avoid mongodb key errors.
257
     * @param array $profile
258
     *
259
     * @return array
260
     */
261
    protected function encodeProfile($profile) {
262
        if (!is_array($profile)) {
263
            return $profile;
264
        }
265
        $target = array(
266
          '__encoded' => true,
267
        );
268
        foreach($profile as $k => $v) {
269
            if (is_array($v)) {
270
                $v = $this->encodeProfile($v);
271
            }
272
            $replacementKey = strtr($k, array(
273
              '.' => '.',
274
            ));
275
            $target[$replacementKey] = $v;
276
        }
277
        return $target;
278
    }
279
280
    /**
281
     * Insert a profile run.
282
     *
283
     * Does unchecked inserts.
284
     *
285
     * @param array $profile The profile data to save.
286
     */
287
    public function insert($profile)
288
    {
289
        $profile['profile'] = $this->encodeProfile($profile['profile']);
290
        return $this->_collection->insert($profile, array('w' => 0));
291
    }
292
293
    /**
294
     * Used to truncate a collection.
295
     *
296
     * Primarly used in test cases to reset the test db.
297
     *
298
     * @return boolean
299
     */
300
    public function truncate()
301
    {
302
        return $this->_collection->drop();
303
    }
304
305
    /**
306
     * Converts arrays + MongoCursors into Xhgui_Profile instances.
307
     *
308
     * @param array|MongoCursor $data The data to transform.
309
     * @return Xhgui_Profile|array The transformed/wrapped results.
310
     */
311
    protected function _wrap($data)
312
    {
313
        if ($data === null) {
314
            throw new Exception('No profile data found.');
315
        }
316
317
        if (is_array($data)) {
318
            return new Xhgui_Profile($data);
319
        }
320
        $results = array();
321
        foreach ($data as $row) {
322
            $results[] = new Xhgui_Profile($row);
323
        }
324
        return $results;
325
    }
326
}
327