Completed
Push — master ( 081dfd...1825d7 )
by Shagiakhmetov
01:30
created

LiveProfiler::setPath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * @maintainer Timur Shagiakhmetov <[email protected]>
5
 */
6
7
namespace Badoo\LiveProfiler;
8
9
use Doctrine\DBAL\Connection;
10
use Doctrine\DBAL\DBALException;
11
use Psr\Log\LoggerInterface;
12
13
class LiveProfiler
14
{
15
    CONST MODE_DB = 'db';
0 ignored issues
show
Coding Style introduced by
As per coding-style, PHP keywords should be in lowercase; expected const, but found CONST.
Loading history...
16
    CONST MODE_FILES = 'files';
0 ignored issues
show
Coding Style introduced by
As per coding-style, PHP keywords should be in lowercase; expected const, but found CONST.
Loading history...
17
18
    /** @var LiveProfiler */
19
    protected static $instance;
20
    /** @var string */
21
    protected $mode = self::MODE_DB;
22
    /** @var string */
23
    protected $path = '';
24
    /** @var Connection */
25
    protected $Conn;
26
    /** @var LoggerInterface */
27
    protected $Logger;
28
    /** @var DataPackerInterface */
29
    protected $DataPacker;
30
    /** @var string */
31
    protected $connection_string;
32
    /** @var string */
33
    protected $app;
34
    /** @var string */
35
    protected $label;
36
    /** @var string */
37
    protected $datetime;
38
    /** @var int */
39
    protected $divider = 1000;
40
    /** @var int */
41
    protected $total_divider = 10000;
42
    /** @var callable callback to start profiling */
43
    protected $start_callback;
44
    /** @var callable callback to end profiling */
45
    protected $end_callback;
46
    /** @var bool */
47
    protected $is_enabled = false;
48
    /** @var array */
49
    protected $last_profile_data = [];
50
51
    /**
52
     * LiveProfiler constructor.
53
     * @param string $connection_string_or_path
54
     * @param string $mode
55
     */
56 15
    public function __construct($connection_string_or_path = '', $mode = self::MODE_DB)
57
    {
58 15
        $this->mode = $mode;
59
60 15
        $this->app = 'Default';
61 15
        $this->label = $this->getAutoLabel();
62 15
        $this->datetime = date('Y-m-d H:i:s');
63
64 15
        $this->detectProfiler();
65 15
        $this->Logger = new Logger();
66 15
        $this->DataPacker = new DataPacker();
67
68 15
        if ($mode === self::MODE_DB) {
69 14
            $this->connection_string = $connection_string_or_path ?: getenv('LIVE_PROFILER_CONNECTION_URL');
70
        } else {
71 1
            $this->setPath($connection_string_or_path ?: getenv('LIVE_PROFILER_PATH'));
72
        }
73 15
    }
74
75 1
    public static function getInstance($connection_string = '', $mode = self::MODE_DB)
76
    {
77 1
        if (self::$instance === null) {
78 1
            self::$instance = new static($connection_string, $mode);
79
        }
80
81 1
        return self::$instance;
82
    }
83
84 7
    public function start()
85
    {
86 7
        if ($this->is_enabled) {
87 1
            return true;
88
        }
89
90 6
        if (null === $this->start_callback) {
91 1
            return true;
92
        }
93
94 5
        if ($this->needToStart($this->divider)) {
95 4
            $this->is_enabled = true;
96 1
        } elseif ($this->needToStart($this->total_divider)) {
97 1
            $this->is_enabled = true;
98 1
            $this->label = 'All';
99
        }
100
101 5
        if ($this->is_enabled) {
102 5
            register_shutdown_function([$this, 'end']);
103 5
            call_user_func($this->start_callback);
104
        }
105
106 5
        return true;
107
    }
108
109
    /**
110
     * @return bool
111
     */
112 6
    public function end()
113
    {
114 6
        if (!$this->is_enabled) {
115 1
            return true;
116
        }
117
118 5
        $this->is_enabled = false;
119
120 5
        if (null === $this->end_callback) {
121 1
            return true;
122
        }
123
124 4
        $data = call_user_func($this->end_callback);
125 4
        if (!is_array($data)) {
126 1
            $this->Logger->warning('Invalid profiler data: ' . var_export($data, true));
127 1
            return false;
128
        }
129
130 3
        $this->last_profile_data = $data;
131 3
        $result = $this->save($this->app, $this->label, $this->datetime, $data);
132
133 3
        if (!$result) {
134 2
            $this->Logger->warning('Can\'t insert profile data');
135
        }
136
137 3
        return $result;
138
    }
139
140 16
    public function detectProfiler()
141
    {
142 16
        if (function_exists('xhprof_enable')) {
143
            return $this->useXhprof();
144
        }
145
146 16
        if (function_exists('tideways_xhprof_enable')) {
147
            return $this->useTidyWays();
148
        }
149
150 16
        if (function_exists('uprofiler_enable')) {
151
            return $this->useUprofiler();
152
        }
153
154 16
        return false;
155
    }
156
157 2
    public function useXhprof()
158
    {
159 2
        if ($this->is_enabled) {
160 1
            $this->Logger->warning('can\'t change profiler after profiling started');
161 1
            return false;
162
        }
163
164
        $this->start_callback = function () {
165
            xhprof_enable(XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_CPU);
166
        };
167
168
        $this->end_callback = function () {
169
            return xhprof_disable();
170
        };
171
172 1
        return true;
173
    }
174
175 2
    public function useTidyWays()
176
    {
177 2
        if ($this->is_enabled) {
178 1
            $this->Logger->warning('can\'t change profiler after profiling started');
179 1
            return false;
180
        }
181
182
        $this->start_callback = function () {
183
            tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_MEMORY | TIDEWAYS_XHPROF_FLAGS_CPU);
184
        };
185
186
        $this->end_callback = function () {
187
            return tideways_xhprof_disable();
188
        };
189
190 1
        return true;
191
    }
192
193 2
    public function useUprofiler()
194
    {
195 2
        if ($this->is_enabled) {
196 1
            $this->Logger->warning('can\'t change profiler after profiling started');
197 1
            return false;
198
        }
199
200
        $this->start_callback = function () {
201
            uprofiler_enable(UPROFILER_FLAGS_CPU | UPROFILER_FLAGS_MEMORY);
202
        };
203
204
        $this->end_callback = function () {
205
            return uprofiler_disable();
206
        };
207
208 1
        return true;
209
    }
210
211
    /**
212
     * @return bool
213
     */
214 1
    public function reset()
215
    {
216 1
        if ($this->is_enabled) {
217 1
            call_user_func($this->end_callback);
218 1
            $this->is_enabled = false;
219
        }
220
221 1
        return true;
222
    }
223
224
    /**
225
     * @param string $mode
226
     * @return $this
227
     */
228 1
    public function setMode($mode)
229
    {
230 1
        $this->mode = $mode;
231 1
        return $this;
232
    }
233
234
    /**
235
     * @return string
236
     */
237 1
    public function getMode()
238
    {
239 1
        return $this->mode;
240
    }
241
242
    /**
243
     * @param string $path
244
     * @return $this
245
     */
246 2
    public function setPath($path)
247
    {
248 2
        if (!is_dir($path)) {
249 1
            $this->Logger->error('Directory ' . $path . ' does not exists');
250
        }
251
252 2
        $this->path = $path;
253 2
        return $this;
254
    }
255
256
    /**
257
     * @return string
258
     */
259 1
    public function getPath()
260
    {
261 1
        return $this->path;
262
    }
263
264
    /**
265
     * @param string $app
266
     * @return $this
267
     */
268 1
    public function setApp($app)
269
    {
270 1
        $this->app = $app;
271 1
        return $this;
272
    }
273
274
    /**
275
     * @return string
276
     */
277 1
    public function getApp()
278
    {
279 1
        return $this->app;
280
    }
281
282
    /**
283
     * @param string $label
284
     * @return $this
285
     */
286 1
    public function setLabel($label)
287
    {
288 1
        $this->label = $label;
289 1
        return $this;
290
    }
291
292
    /**
293
     * @return string
294
     */
295 1
    public function getLabel()
296
    {
297 1
        return $this->label;
298
    }
299
300
    /**
301
     * @param string $datetime
302
     * @return $this
303
     */
304 1
    public function setDateTime($datetime)
305
    {
306 1
        $this->datetime = $datetime;
307 1
        return $this;
308
    }
309
310
    /**
311
     * @return string
312
     */
313 1
    public function getDateTime()
314
    {
315 1
        return $this->datetime;
316
    }
317
318
    /**
319
     * @param int $divider
320
     * @return $this
321
     */
322 5
    public function setDivider($divider)
323
    {
324 5
        $this->divider = $divider;
325 5
        return $this;
326
    }
327
328
    /**
329
     * @param int $total_divider
330
     * @return $this
331
     */
332 2
    public function setTotalDivider($total_divider)
333
    {
334 2
        $this->total_divider = $total_divider;
335 2
        return $this;
336
    }
337
338
    /**
339
     * @param \Closure $start_callback
340
     * @return $this
341
     */
342 5
    public function setStartCallback(\Closure $start_callback)
343
    {
344 5
        $this->start_callback = $start_callback;
345 5
        return $this;
346
    }
347
348
    /**
349
     * @param \Closure $end_callback
350
     * @return $this
351
     */
352 5
    public function setEndCallback(\Closure $end_callback)
353
    {
354 5
        $this->end_callback = $end_callback;
355 5
        return $this;
356
    }
357
358
    /**
359
     * @param LoggerInterface $Logger
360
     * @return $this
361
     */
362 8
    public function setLogger(LoggerInterface $Logger)
363
    {
364 8
        $this->Logger = $Logger;
365 8
        return $this;
366
    }
367
368
    /**
369
     * @param DataPackerInterface $DataPacker
370
     * @return $this
371
     */
372 2
    public function setDataPacker($DataPacker)
373
    {
374 2
        $this->DataPacker = $DataPacker;
375 2
        return $this;
376
    }
377
378
    /**
379
     * @return array
380
     */
381 1
    public function getLastProfileData()
382
    {
383 1
        return $this->last_profile_data;
384
    }
385
386
    /**
387
     * @return Connection
388
     * @throws DBALException
389
     */
390 3
    protected function getConnection()
391
    {
392 3
        if (null === $this->Conn) {
393 3
            $config = new \Doctrine\DBAL\Configuration();
394 3
            $connectionParams = ['url' => $this->connection_string];
395 3
            $this->Conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);
396
        }
397
398 3
        return $this->Conn;
399
    }
400
401
    /**
402
     * @param Connection $Conn
403
     * @return $this
404
     */
405 1
    public function setConnection(Connection $Conn)
406
    {
407 1
        $this->Conn = $Conn;
408 1
        return $this;
409
    }
410
411
    /**
412
     * @param string $connection_string
413
     * @return $this
414
     */
415 1
    public function setConnectionString($connection_string)
416
    {
417 1
        $this->connection_string = $connection_string;
418 1
        return $this;
419
    }
420
421
    /**
422
     * @param string $app
423
     * @param string $label
424
     * @param string $datetime
425
     * @param array $data
426
     * @return bool
427
     */
428 1
    protected function save($app, $label, $datetime, $data)
429
    {
430 1
        if ($this->mode === self::MODE_DB) {
431 1
            return $this->saveToDB($app, $label, $datetime, $data);
432
        }
433
434
        return $this->saveToFile($app, $label, $datetime, $data);
435
    }
436
437
    /**
438
     * @param string $app
439
     * @param string $label
440
     * @param string $datetime
441
     * @param array $data
442
     * @return bool
443
     */
444 1
    protected function saveToDB($app, $label, $datetime, $data)
445
    {
446 1
        $packed_data = $this->DataPacker->pack($data);
447
448
        try {
449 1
            return (bool)$this->getConnection()->insert(
450 1
                'details',
451
                [
452 1
                    'app' => $app,
453 1
                    'label' => $label,
454 1
                    'perfdata' => $packed_data,
455 1
                    'timestamp' => $datetime
456
                ]
457
            );
458
        } catch (DBALException $Ex) {
459
            $this->Logger->error('Error in insertion profile data: ' . $Ex->getMessage());
460
            return false;
461
        }
462
    }
463
464
    /**
465
     * @param string $app
466
     * @param string $label
467
     * @param string $datetime
468
     * @param array $data
469
     * @return bool
470
     */
471 1
    private function saveToFile($app, $label, $datetime, $data)
472
    {
473 1
        $path = sprintf('%s/%s/%s', $this->path, $app, base64_encode($label));
474
475 1
        if (!is_dir($path) && !mkdir($path, 0755, true) && !is_dir($path)) {
476
            $this->Logger->error('Directory "'. $path .'" was not created');
477
            return false;
478
        }
479
480 1
        $filename = sprintf('%s/%s.json', $path, strtotime($datetime));
481 1
        $packed_data = $this->DataPacker->pack($data);
482 1
        return (bool)file_put_contents($filename, $packed_data);
483
    }
484
485
    /**
486
     * @throws DBALException
487
     */
488 3
    public function createTable()
489
    {
490 3
        $driver_name = $this->getConnection()->getDriver()->getName();
491 3
        $sql_path = __DIR__ . '/../../../bin/install_data/' . $driver_name . '/source.sql';
492 3
        if (!file_exists($sql_path)) {
493 1
            $this->Logger->error('Invalid sql path:' . $sql_path);
494 1
            return false;
495
        }
496
497 2
        $sql = file_get_contents($sql_path);
498
499 2
        $this->getConnection()->exec($sql);
500 2
        return true;
501
    }
502
503
    /**
504
     * @return string
505
     */
506 15
    protected function getAutoLabel()
507
    {
508 15
        if (!empty($_SERVER['REQUEST_URI'])) {
509 14
            $label = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
510 14
            return $label ?: $_SERVER['REQUEST_URI'];
511
        }
512
513 1
        return $_SERVER['SCRIPT_NAME'];
514
    }
515
516
    /**
517
     * @param int $divider
518
     * @return bool
519
     */
520 5
    protected function needToStart($divider)
521
    {
522 5
        return mt_rand(1, $divider) === 1;
523
    }
524
}
525