├── .gitignore
├── tests
├── bootstrap.php
└── unit
│ └── Badoo
│ ├── LiveProfilerUI
│ ├── Entity
│ │ ├── MethodTreeTest.php
│ │ ├── JobTest.php
│ │ ├── SnapshotTest.php
│ │ ├── MethodDaraTest.php
│ │ └── TopDiffTest.php
│ ├── DB
│ │ ├── Validators
│ │ │ ├── FieldTest.php
│ │ │ ├── OperatorTest.php
│ │ │ ├── TableTest.php
│ │ │ ├── FunctionsTest.php
│ │ │ └── DirectionTest.php
│ │ ├── DBUtilsTest.php
│ │ └── StorageTest.php
│ ├── DataPackerTest.php
│ ├── LiveProfilerUITest.php
│ ├── FieldListTest.php
│ ├── ViewTest.php
│ ├── LoggerTest.php
│ ├── FieldHandlerTest.php
│ ├── DateGeneratorTest.php
│ ├── DataProviders
│ │ ├── JobTest.php
│ │ └── SourceTest.php
│ └── FlameGraphTest.php
│ └── BaseTestCase.php
├── images
├── flame_graph.png
├── method_usage.png
├── methods_list.png
├── methods_tree.png
├── liveprofui_logo.png
├── top_diff_with_info.png
├── methods_tree_with_info.png
├── snapshots_diff_with_info.png
└── profile_list_page_with_info.png
├── install.sh
├── src
├── Badoo
│ └── LiveProfilerUI
│ │ ├── .DS_Store
│ │ ├── Exceptions
│ │ ├── DatabaseException.php
│ │ ├── FileNotFoundException.php
│ │ ├── InvalidFieldNameException.php
│ │ ├── InvalidOperatorException.php
│ │ ├── InvalidTableNameException.php
│ │ ├── InvalidFieldValueException.php
│ │ ├── InvalidFunctionNameException.php
│ │ └── InvalidOrderDirectionException.php
│ │ ├── Interfaces
│ │ ├── DataPackerInterface.php
│ │ ├── FieldHandlerInterface.php
│ │ ├── ViewInterface.php
│ │ └── StorageInterface.php
│ │ ├── DataProviders
│ │ ├── Interfaces
│ │ │ ├── SourceInterface.php
│ │ │ ├── MethodInterface.php
│ │ │ ├── JobInterface.php
│ │ │ ├── MethodDataInterface.php
│ │ │ ├── MethodTreeInterface.php
│ │ │ └── SnapshotInterface.php
│ │ ├── Base.php
│ │ ├── Job.php
│ │ ├── MethodData.php
│ │ ├── Source.php
│ │ ├── FileSource.php
│ │ ├── Method.php
│ │ └── MethodTree.php
│ │ ├── DB
│ │ ├── Validators
│ │ │ ├── Field.php
│ │ │ ├── Direction.php
│ │ │ ├── Functions.php
│ │ │ ├── Operator.php
│ │ │ └── Table.php
│ │ └── SqlTableBuilder.php
│ │ ├── DataPacker.php
│ │ ├── Entity
│ │ ├── MethodTree.php
│ │ ├── BaseEntity.php
│ │ ├── Snapshot.php
│ │ ├── Job.php
│ │ ├── TopDiff.php
│ │ └── MethodData.php
│ │ ├── Pages
│ │ ├── BasePage.php
│ │ ├── ProfileListPage.php
│ │ └── MethodUsagePage.php
│ │ ├── View.php
│ │ ├── Logger.php
│ │ ├── FieldHandler.php
│ │ ├── FieldList.php
│ │ ├── ConsoleCommands
│ │ ├── RemoveOldProfilesCommand.php
│ │ ├── CreateAggregatingJobsCommand.php
│ │ ├── AggregateManualCommand.php
│ │ ├── AggregateAllProfilesCommand.php
│ │ ├── AWeekDegradationExampleCommand.php
│ │ ├── InstallCommand.php
│ │ └── ProcessAggregatingJobsCommand.php
│ │ ├── DateGenerator.php
│ │ └── FlameGraph.php
├── templates
│ ├── error.php
│ ├── layout.php
│ ├── navbar.block.php
│ ├── profiler_result_view_part.php
│ ├── method_usage.php
│ └── flame_graph.php
└── www
│ ├── css
│ └── bootstrap3
│ │ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ │ └── js
│ │ └── npm.js
│ ├── router.php
│ └── js
│ └── rrd
│ └── libs
│ └── flot
│ ├── jquery.flot.symbol.min.js
│ ├── jquery.flot.threshold.min.js
│ ├── jquery.flot.crosshair.min.js
│ ├── jquery.flot.fillbetween.min.js
│ ├── jquery.flot.resize.min.js
│ ├── jquery.flot.stack.min.js
│ ├── jquery.flot.categories.min.js
│ ├── jquery.flot.image.min.js
│ ├── jquery.colorhelpers.min.js
│ ├── jquery.flot.canvas.min.js
│ └── jquery.flot.selection.min.js
├── phpstan.neon
├── bin
├── install_data
│ ├── pdo_sqlite
│ │ ├── source.sql
│ │ ├── jobs.sql
│ │ └── aggregator.sql
│ ├── mysqli
│ │ ├── source.sql
│ │ ├── jobs.sql
│ │ └── aggregator.sql
│ ├── pdo_pgsql
│ │ ├── source.sql
│ │ ├── jobs.sql
│ │ └── aggregator.sql
│ └── pdo_mysql
│ │ ├── source.sql
│ │ ├── jobs.sql
│ │ └── aggregator.sql
└── cli.php
├── .travis.yml
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── docker
└── web
│ └── Dockerfile
├── phpunit.xml
├── docker-compose.yml
├── .scrutinizer.yml
├── composer.json
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | vendor
4 | composer.lock
5 | db_data
6 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
= $data['error'] ?>
4 |
--------------------------------------------------------------------------------
/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/badoo/liveprof-ui/HEAD/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/badoo/liveprof-ui/HEAD/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/badoo/liveprof-ui/HEAD/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/badoo/liveprof-ui/HEAD/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/src/www/router.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class DatabaseException extends \RuntimeException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Exceptions/FileNotFoundException.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class FileNotFoundException extends \RuntimeException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Exceptions/InvalidFieldNameException.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class InvalidFieldNameException extends \InvalidArgumentException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Exceptions/InvalidOperatorException.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class InvalidOperatorException extends \InvalidArgumentException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Exceptions/InvalidTableNameException.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class InvalidTableNameException extends \InvalidArgumentException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Exceptions/InvalidFieldValueException.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class InvalidFieldValueException extends \InvalidArgumentException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Exceptions/InvalidFunctionNameException.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class InvalidFunctionNameException extends \InvalidArgumentException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Exceptions/InvalidOrderDirectionException.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\Exceptions;
7 |
8 | class InvalidOrderDirectionException extends \InvalidArgumentException
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Interfaces/DataPackerInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Interfaces;
8 |
9 | interface DataPackerInterface
10 | {
11 | public function pack(array $data): string;
12 | public function unpack(string $data): array;
13 | }
14 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_sqlite/source.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS details (
2 | id INTEGER PRIMARY KEY AUTOINCREMENT,
3 | app TEXT DEFAULT NULL,
4 | label TEXT DEFAULT NULL,
5 | timestamp DATETIME NOT NULL,
6 | perfdata BLOB
7 | );
8 | CREATE INDEX IF NOT EXISTS app ON details (app);
9 | CREATE INDEX IF NOT EXISTS label ON details (label);
10 | CREATE INDEX IF NOT EXISTS timestamp_label_idx ON details (timestamp,label);
11 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_sqlite/jobs.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS aggregator_jobs (
2 | id INTEGER PRIMARY KEY AUTOINCREMENT,
3 | app TEXT NOT NULL,
4 | date TEXT NOT NULL,
5 | label TEXT NOT NULL,
6 | type TEXT NOT NULL DEFAULT 'auto',
7 | status TEXT NOT NULL DEFAULT 'new'
8 | );
9 | CREATE INDEX IF NOT EXISTS status_idx ON aggregator_jobs (status);
10 | CREATE INDEX IF NOT EXISTS app_label_date_idx ON aggregator_jobs (app,label,date);
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 7.3
4 | - 7.4
5 | - 8.0
6 |
7 | before_script: composer install
8 | script: vendor/bin/phpunit --verbose --colors --coverage-clover=coverage.xml
9 |
10 | after_success:
11 | - bash <(curl -s https://codecov.io/bash)
12 | - wget https://scrutinizer-ci.com/ocular.phar
13 | - php ocular.phar code-coverage:upload --format=php-clover coverage.xml
14 |
15 | notifications:
16 | email: "timur.shagiakhmetov@corp.badoo.com"
17 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Interfaces/FieldHandlerInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Interfaces;
8 |
9 | interface FieldHandlerInterface
10 | {
11 | /**
12 | * @param string $function Name of aggregating function
13 | * @param array $data array of values
14 | * @return float|null
15 | */
16 | public function handle(string $function, array $data);
17 | }
18 |
--------------------------------------------------------------------------------
/bin/install_data/mysqli/source.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `details` (
2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3 | `app` varchar(32) DEFAULT NULL,
4 | `label` varchar(64) DEFAULT NULL,
5 | `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
6 | `perfdata` text,
7 | PRIMARY KEY (`id`),
8 | KEY `timestamp` (`timestamp`),
9 | KEY `app` (`app`),
10 | KEY `label` (`label`),
11 | KEY `timestamp_label_idx` (`timestamp`,`label`)
12 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
13 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_pgsql/source.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS details (
2 | id SERIAL NOT NULL PRIMARY KEY,
3 | app CHAR(32) DEFAULT NULL,
4 | label CHAR(64) DEFAULT NULL,
5 | timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
6 | perfdata text
7 | );
8 | CREATE INDEX IF NOT EXISTS timestamp_idx ON details (timestamp);
9 | CREATE INDEX IF NOT EXISTS app_idx ON details (app);
10 | CREATE INDEX IF NOT EXISTS label_idx ON details (label);
11 | CREATE INDEX IF NOT EXISTS timestamp_label_idx ON details (timestamp,label);
12 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_mysql/source.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `details` (
2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3 | `app` varchar(32) DEFAULT NULL,
4 | `label` varchar(64) DEFAULT NULL,
5 | `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
6 | `perfdata` mediumblob,
7 | PRIMARY KEY (`id`),
8 | KEY `timestamp` (`timestamp`),
9 | KEY `app` (`app`),
10 | KEY `label` (`label`),
11 | KEY `timestamp_label_idx` (`timestamp`,`label`)
12 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
13 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Interfaces/ViewInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Interfaces;
8 |
9 | interface ViewInterface
10 | {
11 | /**
12 | * @param string $template_name
13 | * @param array $data
14 | * @param bool $use_layout_locally
15 | * @return string
16 | */
17 | public function fetchFile(string $template_name, array $data, bool $use_layout_locally = true);
18 | }
19 |
--------------------------------------------------------------------------------
/bin/install_data/mysqli/jobs.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `aggregator_jobs` (
2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3 | `app` varchar(32) NOT NULL,
4 | `date` date NOT NULL,
5 | `label` varchar(100) NOT NULL,
6 | `type` enum('auto','manual') NOT NULL DEFAULT 'auto',
7 | `status` enum('new','processing','finished','error') NOT NULL DEFAULT 'new',
8 | PRIMARY KEY (`id`),
9 | KEY `status_idx` (`status`),
10 | KEY `app_label_date_idx` (`app`,`label`,`date`)
11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
12 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_mysql/jobs.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `aggregator_jobs` (
2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3 | `app` varchar(32) NOT NULL,
4 | `date` date NOT NULL,
5 | `label` varchar(100) NOT NULL,
6 | `type` enum('auto','manual') NOT NULL DEFAULT 'auto',
7 | `status` enum('new','processing','finished','error') NOT NULL DEFAULT 'new',
8 | PRIMARY KEY (`id`),
9 | KEY `status_idx` (`status`),
10 | KEY `app_label_date_idx` (`app`,`label`,`date`)
11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
12 |
--------------------------------------------------------------------------------
/src/www/css/bootstrap3/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Interfaces/SourceInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders\Interfaces;
8 |
9 | interface SourceInterface
10 | {
11 | public function getSnapshotsDataByDates(string $datetime_from, string $datetime_to) : array;
12 | public function getPerfData(string $app, string $label, string $date) : array;
13 | public function getLabelList() : array;
14 | public function getAppList() : array;
15 | }
16 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DB/Validators/Field.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DB\Validators;
8 |
9 | use Badoo\LiveProfilerUI\Exceptions\InvalidFieldNameException;
10 |
11 | class Field
12 | {
13 | public static function validate(string $field) : bool
14 | {
15 | if (!preg_match('/^[_a-z]+$/', $field)) {
16 | throw new InvalidFieldNameException('Invalid field: ' . $field);
17 | }
18 |
19 | return true;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataPacker.php:
--------------------------------------------------------------------------------
1 |
6 | */
7 |
8 | namespace Badoo\LiveProfilerUI;
9 |
10 | use Badoo\LiveProfilerUI\Interfaces\DataPackerInterface;
11 |
12 | class DataPacker implements DataPackerInterface
13 | {
14 | public function pack(array $data): string
15 | {
16 | return json_encode($data);
17 | }
18 |
19 | public function unpack(string $data): array
20 | {
21 | return json_decode($data, true);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DB/Validators/Direction.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DB\Validators;
8 |
9 | use Badoo\LiveProfilerUI\Exceptions\InvalidOrderDirectionException;
10 |
11 | class Direction
12 | {
13 | public static function validate(string $direction) : bool
14 | {
15 | if (!\in_array($direction, ['asc', 'desc'], true)) {
16 | throw new InvalidOrderDirectionException('Invalid order direction: ' . $direction);
17 | }
18 |
19 | return true;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: shagtv
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/Entity/MethodTreeTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\Entity;
8 |
9 | class MethodTreeTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testGetters()
12 | {
13 | $MethodTree = new \Badoo\LiveProfilerUI\Entity\MethodTree([], []);
14 |
15 | $empty_parent_id = $MethodTree->getParentId();
16 | self::assertEquals(0, $empty_parent_id);
17 |
18 | $MethodTree->setParentId(1);
19 | $new_parent_id = $MethodTree->getParentId();
20 | self::assertEquals(1, $new_parent_id);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Base.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\Interfaces\StorageInterface;
10 | use Badoo\LiveProfilerUI\FieldList;
11 |
12 | class Base
13 | {
14 | /** @var StorageInterface */
15 | protected $AggregatorStorage;
16 | /** @var FieldList */
17 | protected $FieldList;
18 |
19 | public function __construct(StorageInterface $AggregatorStorage, FieldList $FieldList)
20 | {
21 | $this->AggregatorStorage = $AggregatorStorage;
22 | $this->FieldList = $FieldList;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/docker/web/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:8.0
2 | MAINTAINER Timur Shagiakhmetov
3 |
4 | RUN apt-get update && apt-get -y install git-core unzip \
5 | && pecl install xdebug \
6 | && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini \
7 | && curl --silent --show-error https://getcomposer.org/installer | php \
8 | && mv composer.phar /usr/local/bin/composer
9 |
10 | # mysql extension
11 | RUN docker-php-ext-install -j$(nproc) mysqli pdo_mysql
12 |
13 | # postgresql extension
14 | RUN apt-get -y install libpq-dev \
15 | && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
16 | && docker-php-ext-install -j$(nproc) pgsql pdo_pgsql
17 |
18 | WORKDIR /app
19 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Interfaces/MethodInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders\Interfaces;
8 |
9 | interface MethodInterface
10 | {
11 | public function findByName(string $method_name, bool $strict = false) : array;
12 | public function all() : array;
13 | public function getListByNames(array $names) : array;
14 | public function getListByIds(array $ids) : array;
15 | public function insertMany(array $inserts) : bool;
16 | public function injectMethodNames(array $records) : array;
17 | public function setLastUsedDate(array $ids, string $date) : bool;
18 | }
19 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DB/Validators/Functions.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DB\Validators;
8 |
9 | use Badoo\LiveProfilerUI\Exceptions\InvalidFunctionNameException;
10 |
11 | class Functions
12 | {
13 | /** @var string[] */
14 | protected static $allowed_functions = [
15 | 'sum',
16 | 'date',
17 | 'max',
18 | 'min',
19 | ];
20 |
21 | public static function validate(string $function) : bool
22 | {
23 | if (!\in_array($function, self::$allowed_functions, true)) {
24 | throw new InvalidFunctionNameException('Invalid function name: ' . $function);
25 | }
26 |
27 | return true;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/templates/layout.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Live profiler
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | = $content ?>
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Entity/MethodTree.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Entity;
8 |
9 | class MethodTree extends MethodData
10 | {
11 | /** @var int */
12 | protected $parent_id = 0;
13 |
14 | public function __construct(array $data, array $fields)
15 | {
16 | parent::__construct($data, $fields);
17 | $this->parent_id = isset($data['parent_id']) ? (int)$data['parent_id'] : 0;
18 | }
19 |
20 | public function getParentId() : int
21 | {
22 | return $this->parent_id;
23 | }
24 |
25 | public function setParentId(int $parent_id) : self
26 | {
27 | $this->parent_id = $parent_id;
28 | return $this;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DB/Validators/FieldTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DB\Adapters;
8 |
9 | use Badoo\LiveProfilerUI\DB\Validators\Field;
10 |
11 | class FieldTest extends \unit\Badoo\BaseTestCase
12 | {
13 | public function testValidate()
14 | {
15 | $field = '_abc_';
16 | $result = Field::validate($field);
17 |
18 | self::assertTrue($result);
19 | }
20 |
21 | public function testValidateError()
22 | {
23 | $this->expectException(\InvalidArgumentException::class);
24 | $this->expectExceptionMessage('Invalid field: _123_');
25 | $field = '_123_';
26 | Field::validate($field);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_pgsql/jobs.sql:
--------------------------------------------------------------------------------
1 | DO $$
2 | BEGIN
3 | IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'types') THEN
4 | create type types AS ENUM('auto','manual');
5 | END IF;
6 | END
7 | $$;
8 |
9 | DO $$
10 | BEGIN
11 | IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statuses') THEN
12 | create type statuses AS ENUM('new','processing','finished','error');
13 | END IF;
14 | END
15 | $$;
16 |
17 | CREATE TABLE IF NOT EXISTS aggregator_jobs (
18 | id SERIAL NOT NULL PRIMARY KEY,
19 | app CHAR(32) NOT NULL,
20 | date date NOT NULL,
21 | label CHAR(100) NOT NULL,
22 | type types NOT NULL DEFAULT 'auto',
23 | status statuses NOT NULL DEFAULT 'new'
24 | );
25 | CREATE INDEX IF NOT EXISTS status_idx ON aggregator_jobs (status);
26 | CREATE INDEX IF NOT EXISTS app_label_date_idx ON aggregator_jobs (app,label,date);
27 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Interfaces/StorageInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Interfaces;
8 |
9 | interface StorageInterface
10 | {
11 | public function getAll(string $table, array $fields, array $params) : array;
12 | public function getOne(string $table, array $fields, array $params) : array;
13 | public function update(string $table, array $fields, array $params) : bool;
14 | public function delete(string $table, array $params) : bool;
15 | public function insert(string $table, array $fields) : int;
16 | public function insertMany(string $table, array $fields) : bool;
17 | public function getType() : string;
18 | public function multiQuery(string $sql) : bool;
19 | }
20 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DB/Validators/OperatorTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DB\Adapters;
8 |
9 | use Badoo\LiveProfilerUI\DB\Validators\Operator;
10 |
11 | class OperatorTest extends \unit\Badoo\BaseTestCase
12 | {
13 | public function testValidate()
14 | {
15 | $operator = '>';
16 | $result = Operator::validate($operator);;
17 |
18 | self::assertTrue($result);
19 | }
20 |
21 | public function testValidateError()
22 | {
23 | $this->expectException(\InvalidArgumentException::class);
24 | $this->expectExceptionMessage('Invalid operator: !!');
25 | $operator = '!!';
26 | Operator::validate($operator);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DB/Validators/TableTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DB\Adapters;
8 |
9 | use Badoo\LiveProfilerUI\DB\Validators\Table;
10 |
11 | class TableTest extends \unit\Badoo\BaseTestCase
12 | {
13 | public function testValidate()
14 | {
15 | $table = 'aggregator_snapshots';
16 | $result = Table::validate($table);
17 |
18 | self::assertTrue($result);
19 | }
20 |
21 | public function testValidateError()
22 | {
23 | $this->expectException(\InvalidArgumentException::class);
24 | $this->expectExceptionMessage('Invalid table name: table');
25 | $table = 'table';
26 | Table::validate($table);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DB/Validators/Operator.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DB\Validators;
8 |
9 | use Badoo\LiveProfilerUI\Exceptions\InvalidOperatorException;
10 |
11 | class Operator
12 | {
13 | /** @var string[] */
14 | private static $allowed_operators = [
15 | '=',
16 | 'like',
17 | '!=',
18 | '<',
19 | '>',
20 | '>=',
21 | '<=',
22 | ];
23 |
24 | public static function validate(string $operator) : bool
25 | {
26 | if (!\in_array($operator, self::$allowed_operators, true)) {
27 | throw new InvalidOperatorException('Invalid operator: ' . $operator);
28 | }
29 |
30 | return true;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DB/Validators/FunctionsTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DB\Adapters;
8 |
9 | use Badoo\LiveProfilerUI\DB\Validators\Functions;
10 |
11 | class FunctionsTest extends \unit\Badoo\BaseTestCase
12 | {
13 | public function testValidate()
14 | {
15 | $function = 'sum';
16 | $result = Functions::validate($function);
17 |
18 | self::assertTrue($result);
19 | }
20 |
21 | public function testValidateError()
22 | {
23 | $this->expectException(\InvalidArgumentException::class);
24 | $this->expectExceptionMessage('Invalid function name: div');
25 | $function = 'div';
26 | Functions::validate($function);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DB/Validators/DirectionTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DB\Adapters;
8 |
9 | use Badoo\LiveProfilerUI\DB\Validators\Direction;
10 |
11 | class DirectionTest extends \unit\Badoo\BaseTestCase
12 | {
13 | public function testValidate()
14 | {
15 | $direction = 'desc';
16 | $result = Direction::validate($direction);
17 |
18 | self::assertTrue($result);
19 | }
20 |
21 | public function testValidateError()
22 | {
23 | $this->expectException(\InvalidArgumentException::class);
24 | $this->expectExceptionMessage('Invalid order direction: Invalid');
25 | $direction = 'Invalid';
26 | Direction::validate($direction);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DB/Validators/Table.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DB\Validators;
8 |
9 | use Badoo\LiveProfilerUI\Exceptions\InvalidTableNameException;
10 |
11 | class Table
12 | {
13 | /** @var string[] */
14 | protected static $allowed_table_names = [
15 | 'details',
16 | 'aggregator_snapshots',
17 | 'aggregator_tree',
18 | 'aggregator_method_data',
19 | 'aggregator_metods',
20 | 'aggregator_jobs',
21 | ];
22 |
23 | public static function validate(string $table) : bool
24 | {
25 | if (!\in_array($table, self::$allowed_table_names, true)) {
26 | throw new InvalidTableNameException('Invalid table name: ' . $table);
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | src/Badoo
6 |
7 |
8 | src/Badoo/ConsoleCommands
9 |
10 |
11 |
12 |
13 | tests/unit
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | services:
3 | # mysql storage
4 | db_mysql:
5 | image: mysql:5.7
6 | restart: always
7 | ports:
8 | - 3306:3306
9 | volumes:
10 | - "./db_data/mysql:/var/lib/mysql:rw"
11 | environment:
12 | MYSQL_ROOT_PASSWORD: db_password
13 | MYSQL_DATABASE: Profiler
14 |
15 | # postgresql storage
16 | db_pgsql:
17 | image: postgres
18 | restart: always
19 | ports:
20 | - 5432:5432
21 | volumes:
22 | - "./db_data/pg:/var/lib/postgresql/data:rw"
23 | environment:
24 | POSTGRES_USER: db_user
25 | POSTGRES_PASSWORD: db_password
26 | POSTGRES_DB: Profiler
27 |
28 | web:
29 | restart: always
30 | build: 'docker/web'
31 | command:
32 | php -S 0.0.0.0:8000 -t src/www/ src/www/router.php
33 | ports:
34 | - "8000:8000"
35 | volumes:
36 | - .:/app
37 | - ./db_data:/app/db_data
38 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DataPackerTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class DataPackerTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testPack()
12 | {
13 | $data = ['a' => 1];
14 | $Packer = new \Badoo\LiveProfilerUI\DataPacker();
15 |
16 | $result = $Packer->pack($data);
17 |
18 | static::assertEquals(json_encode($data), $result);
19 | }
20 |
21 | /**
22 | * @depends testPack
23 | */
24 | public function testUnPack()
25 | {
26 | $data = ['a' => 1];
27 | $Packer = new \Badoo\LiveProfilerUI\DataPacker();
28 | $packed_data = $Packer->pack($data);
29 |
30 | $result = $Packer->unpack($packed_data);
31 |
32 | static::assertEquals(json_decode($packed_data, true), $result);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | before_commands:
2 | - "composer install --no-dev --prefer-source"
3 |
4 | tools:
5 | external_code_coverage:
6 | timeout: 1200
7 | php_code_coverage:
8 | enabled: true
9 | php_code_sniffer:
10 | enabled: true
11 | config:
12 | standard: PSR2
13 | filter:
14 | paths: ["src/*"]
15 | php_cpd:
16 | enabled: true
17 | excluded_dirs: ["ide-stubs", "tests", 'vendor']
18 | php_cs_fixer:
19 | enabled: true
20 | config:
21 | level: all
22 | filter:
23 | paths: ["src/*"]
24 | php_loc:
25 | enabled: true
26 | excluded_dirs: ["ide-stubs", "tests", 'vendor']
27 | php_pdepend:
28 | enabled: true
29 | excluded_dirs: ["ide-stubs", "tests", 'vendor']
30 | php_analyzer:
31 | enabled: true
32 | filter:
33 | paths: ["src/*"]
34 | sensiolabs_security_checker: true
35 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "badoo/liveprof-ui",
3 | "type": "library",
4 | "description": "An aggregator and web interface for Live Profiler",
5 | "minimum-stability": "stable",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Badoo Development"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=7.0",
14 | "symfony/dependency-injection": "~3.0|~4.0",
15 | "symfony/config": "~3.0|~4.0",
16 | "symfony/yaml": "~3.0|~4.0",
17 | "symfony/console": "~3.0|~4.0",
18 | "doctrine/dbal": "~2.0",
19 | "psr/log": "~1.0",
20 | "ext-json": "*",
21 | "ext-zlib": "*"
22 | },
23 | "require-dev": {
24 | "phpunit/phpunit": "~9.5.4",
25 | "phpstan/phpstan": "~0.12.88",
26 | "vimeo/psalm": "~4.7.2"
27 | },
28 | "autoload": {
29 | "psr-4": {
30 | "Badoo\\LiveProfilerUI\\": "src/Badoo/LiveProfilerUI"}
31 | },
32 | "autoload-dev": {
33 | "psr-4": {"unit\\Badoo\\": "tests/unit/Badoo"}
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Entity/BaseEntity.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Entity;
8 |
9 | class BaseEntity
10 | {
11 | protected function prepareValues(array $data, array $fields) : array
12 | {
13 | $values = [];
14 | foreach ($data as $field => $value) {
15 | if (!empty($fields[$field])) {
16 | $values[$field] = \is_string($value) ? (float)$value : $value;
17 | }
18 | }
19 | return $values;
20 | }
21 |
22 | protected function prepareFormattedValues(array $values) : array
23 | {
24 | $formatted_values = [];
25 | foreach ($values as $key => $value) {
26 | $formatted_values[$key] = is_numeric($value)
27 | ? str_replace('.000', '', number_format((float)$value, 3))
28 | : '-';
29 | }
30 | return $formatted_values;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Interfaces/JobInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders\Interfaces;
8 |
9 | use Badoo\LiveProfilerUI\Entity\Job;
10 |
11 | interface JobInterface
12 | {
13 | const TYPE_AUTO = 'auto';
14 | const TYPE_MANUAL = 'manual';
15 | const STATUS_NEW = 'new';
16 | const STATUS_PROCESSING = 'processing';
17 | const STATUS_FINISHED = 'finished';
18 | const STATUS_ERROR = 'error';
19 |
20 | /**
21 | * @param string $status
22 | * @param int $limit
23 | * @return Job[]
24 | */
25 | public function getJobs(string $status, int $limit) : array;
26 | public function add(string $app, string $label, string $date, string $type = 'auto') : int;
27 | public function getJob(string $app, string $label, string $date, array $statuses) : Job;
28 | public function changeStatus(int $job_id, string $status) : bool;
29 | }
30 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Interfaces/MethodDataInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders\Interfaces;
8 |
9 | use Badoo\LiveProfilerUI\Entity\MethodData;
10 |
11 | interface MethodDataInterface
12 | {
13 | /**
14 | * @param int $snapshot_id
15 | * @return MethodData[]
16 | */
17 | public function getDataBySnapshotId(int $snapshot_id) : array;
18 |
19 | /**
20 | * @param int[] $snapshot_ids
21 | * @param int[] $method_ids
22 | * @param int $limit
23 | * @param int $start_snapshot_id
24 | * @return MethodData[]
25 | */
26 | public function getDataByMethodIdsAndSnapshotIds(array $snapshot_ids, array $method_ids, int $limit = 0, int $start_snapshot_id = 0) : array;
27 | public function getOneParamDataBySnapshotIds(array $snapshot_ids, string $param, int $threshold = 1000) : array;
28 | public function deleteBySnapshotId(int $snapshot_id) : bool;
29 | public function insertMany(array $inserts) : bool;
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Badoo Development
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.symbol.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){function processRawData(plot,series,datapoints){var handlers={square:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.rect(x-size,y-size,size+size,size+size)},diamond:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI/2);ctx.moveTo(x-size,y);ctx.lineTo(x,y-size);ctx.lineTo(x+size,y);ctx.lineTo(x,y+size);ctx.lineTo(x-size,y)},triangle:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var height=size*Math.sin(Math.PI/3);ctx.moveTo(x-size/2,y+height/2);ctx.lineTo(x+size/2,y+height/2);if(!shadow){ctx.lineTo(x,y-height/2);ctx.lineTo(x-size/2,y+height/2)}},cross:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.moveTo(x-size,y-size);ctx.lineTo(x+size,y+size);ctx.moveTo(x-size,y+size);ctx.lineTo(x+size,y-size)}};var s=series.points.symbol;if(handlers[s])series.points.symbol=handlers[s]}function init(plot){plot.hooks.processDatapoints.push(processRawData)}$.plot.plugins.push({init:init,name:"symbols",version:"1.0"})})(jQuery);
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Interfaces/MethodTreeInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders\Interfaces;
8 |
9 | use Badoo\LiveProfilerUI\Entity\MethodTree;
10 |
11 | interface MethodTreeInterface
12 | {
13 | /**
14 | * @param int $snapshot_id
15 | * @return MethodTree[]
16 | */
17 | public function getSnapshotMethodsTree(int $snapshot_id) : array;
18 |
19 | /**
20 | * @param int[] $snapshot_ids
21 | * @param int[] $method_ids
22 | * @return MethodTree[]
23 | */
24 | public function getDataByMethodIdsAndSnapshotIds(array $snapshot_ids, array $method_ids) : array;
25 |
26 | /**
27 | * @param int[] $snapshot_ids
28 | * @param int[] $parent_ids
29 | * @return MethodTree[]
30 | */
31 | public function getDataByParentIdsAndSnapshotIds(array $snapshot_ids, array $parent_ids) : array;
32 | public function getSnapshotParentsData(array $snapshot_ids, array $fields = [], int $threshold = 0) : array;
33 | public function deleteBySnapshotId(int $snapshot_id) : bool;
34 | public function insertMany(array $inserts) : bool;
35 | }
36 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/LiveProfilerUITest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class LiveProfilerUITest extends \unit\Badoo\BaseTestCase
10 | {
11 | /**
12 | * @throws \ReflectionException
13 | */
14 | public function testGetContainer()
15 | {
16 | $LiveProfilerUI = new \Badoo\LiveProfilerUI\LiveProfilerUI();
17 |
18 | $Container1 = $this->invokeMethod($LiveProfilerUI, 'getContainer');
19 | $Container2 = $this->invokeMethod($LiveProfilerUI, 'getContainer');
20 |
21 | self::assertSame($Container1, $Container2);
22 | }
23 |
24 | /**
25 | * @throws \ReflectionException
26 | */
27 | public function testGetContainerEnv()
28 | {
29 | putenv('AGGREGATOR_CONFIG_PATH=' . __DIR__ . '/../../../../src/config/services.yaml');
30 |
31 | $LiveProfilerUI = new \Badoo\LiveProfilerUI\LiveProfilerUI();
32 |
33 | $Container1 = $this->invokeMethod($LiveProfilerUI, 'getContainer');
34 | $Container2 = $this->invokeMethod($LiveProfilerUI, 'getContainer');
35 |
36 | self::assertSame($Container1, $Container2);
37 |
38 | putenv('AGGREGATOR_CONFIG_PATH=');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Pages/BasePage.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Pages;
8 |
9 | use Badoo\LiveProfilerUI\Interfaces\ViewInterface;
10 |
11 | abstract class BasePage
12 | {
13 | /** @var string */
14 | protected static $template_path;
15 | /** @var array */
16 | protected $data = [];
17 | /** @var ViewInterface */
18 | protected $View;
19 |
20 | /**
21 | * @return array
22 | * @throws \InvalidArgumentException
23 | */
24 | abstract public function getTemplateData() : array;
25 | abstract protected function cleanData() : bool;
26 |
27 | /**
28 | * @param array $data
29 | * @return $this
30 | */
31 | public function setData(array $data) : self
32 | {
33 | $this->data = $data;
34 | return $this;
35 | }
36 |
37 | /**
38 | * @return string
39 | */
40 | public function render()
41 | {
42 | try {
43 | $this->cleanData();
44 | return $this->View->fetchFile(static::$template_path, $this->getTemplateData());
45 | } catch (\Throwable $Ex) {
46 | return $this->View->fetchFile('error', ['error' => $Ex->getMessage()]);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/FieldListTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class FieldListTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testFields()
12 | {
13 | $fields = ['field1', ['field2_profile' => 'field2']];
14 | $variations = ['min', 'max'];
15 | $descriptions = [];
16 | $FieldList = new \Badoo\LiveProfilerUI\FieldList($fields, $variations, $descriptions);
17 |
18 | $expected_fields = [
19 | 'field1' => 'field1',
20 | 'field2_profile' => 'field2'
21 | ];
22 |
23 | $expected_fields_with_variations = [
24 | 'field1' => 'field1',
25 | 'min_field1' => 'min_field1',
26 | 'max_field1' => 'max_field1',
27 | 'field2' => 'field2',
28 | 'min_field2' => 'min_field2',
29 | 'max_field2' => 'max_field2'
30 | ];
31 |
32 | self::assertEquals($expected_fields, $FieldList->getFields());
33 | self::assertEquals($variations, $FieldList->getFieldVariations());
34 | self::assertEquals($descriptions, $FieldList->getFieldDescriptions());
35 | self::assertEquals($expected_fields_with_variations, $FieldList->getAllFieldsWithVariations());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/templates/navbar.block.php:
--------------------------------------------------------------------------------
1 |
13 |
14 |
19 |
20 |
38 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/ViewTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class ViewTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testFetchFileWrongTemplate()
12 | {
13 | $this->expectException(\Exception::class);
14 | $this->expectExceptionMessage('Template file not found');
15 | $View = new \Badoo\LiveProfilerUI\View();
16 | $View->fetchFile('wrong_template', []);
17 | }
18 |
19 | public function providerFetchFile()
20 | {
21 | return [
22 | [
23 | 'use_layout' => false,
24 | 'expected' => "test error
\n"
25 | ],
26 | [
27 | 'use_layout' => true,
28 | 'expected' => ''
29 | ]
30 | ];
31 | }
32 |
33 | /**
34 | * @dataProvider providerFetchFile
35 | * @param $use_layout
36 | * @param $expected
37 | * @throws \Exception
38 | */
39 | public function testFetchFile($use_layout, $expected)
40 | {
41 | $View = new \Badoo\LiveProfilerUI\View($use_layout);
42 | $result = $View->fetchFile('error', ['error' => 'test error']);
43 |
44 | self::assertStringContainsString($expected, $result);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/bin/cli.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
7 | */
8 |
9 | $vendor_path = __DIR__ . '/../vendor/autoload.php';
10 | $use_as_library_vendor_path = __DIR__ . '/../../../../vendor/autoload.php';
11 | if (file_exists($vendor_path)) {
12 | require_once $vendor_path;
13 | } elseif (file_exists($use_as_library_vendor_path)) {
14 | require_once $use_as_library_vendor_path;
15 | }
16 |
17 | use Symfony\Component\Console\Application;
18 | use Badoo\LiveProfilerUI\ConsoleCommands\RemoveOldProfilesCommand;
19 | use Badoo\LiveProfilerUI\ConsoleCommands\AggregateAllProfilesCommand;
20 | use Badoo\LiveProfilerUI\ConsoleCommands\CreateAggregatingJobsCommand;
21 | use Badoo\LiveProfilerUI\ConsoleCommands\ProcessAggregatingJobsCommand;
22 | use Badoo\LiveProfilerUI\ConsoleCommands\AggregateManualCommand;
23 | use Badoo\LiveProfilerUI\ConsoleCommands\InstallCommand;
24 | use Badoo\LiveProfilerUI\ConsoleCommands\AWeekDegradationExampleCommand;
25 |
26 | $application = new Application();
27 | $application->add(new RemoveOldProfilesCommand());
28 | $application->add(new AggregateAllProfilesCommand());
29 | $application->add(new CreateAggregatingJobsCommand());
30 | $application->add(new ProcessAggregatingJobsCommand());
31 | $application->add(new AggregateManualCommand());
32 | $application->add(new InstallCommand());
33 | $application->add(new AWeekDegradationExampleCommand());
34 | $application->run();
35 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/LoggerTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class LoggerTest extends \unit\Badoo\BaseTestCase
10 | {
11 | /**
12 | * @throws \ReflectionException
13 | */
14 | public function testGetLogMsg()
15 | {
16 | $Logger = new \Badoo\LiveProfilerUI\Logger();
17 | $log_msg = $this->invokeMethod($Logger, 'getLogMsg', ['error', 'Error msg', ['param' => 1]]);
18 | self::assertEquals(date('Y-m-d H:i:s'). "\terror\tError msg\t{\"param\":1}\n", $log_msg);
19 | }
20 |
21 | public function testLog()
22 | {
23 | $tmp_log_file = tempnam('/tmp', 'live.profiling');
24 | $Logger = new \Badoo\LiveProfilerUI\Logger($tmp_log_file);
25 | $Logger->log('error', 'Error msg');
26 |
27 | $log_msg = file_get_contents($tmp_log_file);
28 | unset($tmp_log_file);
29 |
30 | self::assertEquals(date('Y-m-d H:i:s'). "\terror\tError msg\n", $log_msg);
31 | }
32 |
33 | public function testLogSetFile()
34 | {
35 | $tmp_log_file = tempnam('/tmp', 'live.profiling');
36 | $Logger = new \Badoo\LiveProfilerUI\Logger();
37 | $Logger->setLogFile($tmp_log_file);
38 | $Logger->log('error', 'Error msg');
39 |
40 | $log_msg = file_get_contents($tmp_log_file);
41 | unset($tmp_log_file);
42 |
43 | self::assertEquals(date('Y-m-d H:i:s'). "\terror\tError msg\n", $log_msg);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/Entity/JobTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\Entity;
8 |
9 | class JobTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testGetters()
12 | {
13 | $data = [
14 | 'id' => '1',
15 | 'app' => ' app ',
16 | 'label' => ' label ',
17 | 'date' => ' date ',
18 | 'type' => ' type ',
19 | 'status' => ' status ',
20 | ];
21 | $Job = new \Badoo\LiveProfilerUI\Entity\Job($data);
22 |
23 | self::assertEquals(1, $Job->getId());
24 | self::assertEquals('app', $Job->getApp());
25 | self::assertEquals('label', $Job->getLabel());
26 | self::assertEquals('date', $Job->getDate());
27 | self::assertEquals('type', $Job->getType());
28 | self::assertEquals('status', $Job->getStatus());
29 |
30 | $Job->setId(2);
31 | $Job->setApp('new app');
32 | $Job->setLabel('new label');
33 | $Job->setDate('new date');
34 | $Job->setType('new type');
35 | $Job->setStatus('new status');
36 |
37 | self::assertEquals(2, $Job->getId());
38 | self::assertEquals('new app', $Job->getApp());
39 | self::assertEquals('new label', $Job->getLabel());
40 | self::assertEquals('new date', $Job->getDate());
41 | self::assertEquals('new type', $Job->getType());
42 | self::assertEquals('new status', $Job->getStatus());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_sqlite/aggregator.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS aggregator_metods (
2 | id INTEGER PRIMARY KEY AUTOINCREMENT,
3 | name TEXT,
4 | date TEXT NOT NULL DEFAULT '1970-01-01'
5 | );
6 | CREATE TABLE IF NOT EXISTS aggregator_snapshots (
7 | id INTEGER PRIMARY KEY AUTOINCREMENT,
8 | calls_count INTEGER NOT NULL,
9 | app TEXT DEFAULT NULL,
10 | label TEXT DEFAULT NULL,
11 | date TEXT NOT NULL,
12 | %SNAPSHOT_CUSTOM_FIELDS%
13 | type TEXT NOT NULL DEFAULT 'auto'
14 | );
15 | CREATE INDEX IF NOT EXISTS app_idx ON aggregator_snapshots (app);
16 | CREATE TABLE IF NOT EXISTS aggregator_tree (
17 | id INTEGER PRIMARY KEY AUTOINCREMENT,
18 | snapshot_id INTEGER NOT NULL,
19 | method_id INTEGER NOT NULL,
20 | parent_id INTEGER NOT NULL,
21 | %TREE_CUSTOM_FIELDS%
22 | FOREIGN KEY (method_id) REFERENCES aggregator_metods (id) ON DELETE CASCADE ON UPDATE NO ACTION,
23 | FOREIGN KEY (parent_id) REFERENCES aggregator_metods (id) ON DELETE CASCADE ON UPDATE NO ACTION,
24 | FOREIGN KEY (snapshot_id) REFERENCES aggregator_snapshots (id) ON DELETE CASCADE ON UPDATE NO ACTION
25 | );
26 | CREATE TABLE IF NOT EXISTS aggregator_method_data (
27 | id INTEGER PRIMARY KEY AUTOINCREMENT,
28 | snapshot_id INTEGER NOT NULL,
29 | method_id INTEGER NOT NULL,
30 | %DATA_CUSTOM_FIELDS%
31 | FOREIGN KEY (method_id) REFERENCES aggregator_metods (id) ON DELETE CASCADE ON UPDATE NO ACTION,
32 | FOREIGN KEY (snapshot_id) REFERENCES aggregator_snapshots (id) ON DELETE CASCADE ON UPDATE NO ACTION
33 | );
34 | CREATE INDEX IF NOT EXISTS snapshot_id_method_id_idx ON aggregator_method_data (snapshot_id, method_id);
35 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Interfaces/SnapshotInterface.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders\Interfaces;
8 |
9 | use Badoo\LiveProfilerUI\Entity\Snapshot;
10 |
11 | interface SnapshotInterface
12 | {
13 | /**
14 | * @param string $app
15 | * @return Snapshot[]
16 | */
17 | public function getList(string $app = '') : array;
18 | public function getLastSnapshots(string $app = '', string $from_date = '') : array;
19 | public function getOneById(int $snapshot_id) : Snapshot;
20 | public function getListByIds(array $snapshot_ids) : array;
21 | public function getOneByAppAndLabel(string $app, string$label) : Snapshot;
22 | public function getOneByAppAndLabelAndDate(string $app, string $label, string $date) : Snapshot;
23 | public function getSnapshotsByDates(array $dates, string $param = '') : array;
24 | public function getSnapshotIdsByDates(array $dates, string $app, string $label) : array;
25 | public function getMinSnapshotIdByDates(array $dates) : int;
26 | public function getOldSnapshots(int $keep_days = 200, int $limit = 2000) : array;
27 | public function getDatesByAppAndLabel(string $app, string $label) : array;
28 | public function getMaxCallsCntByAppAndLabel(string $app, string $label) : int;
29 | public function getAppList(string $label = '') : array;
30 | public function updateSnapshot(int $snapshot_id, array $snapshot_data) : bool;
31 | public function createSnapshot(array $snapshot_data) : int;
32 | public function deleteById(int $snapshot_id) : bool;
33 | }
34 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/View.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI;
8 |
9 | use Badoo\LiveProfilerUI\Exceptions\FileNotFoundException;
10 | use Badoo\LiveProfilerUI\Interfaces\ViewInterface;
11 |
12 | class View implements ViewInterface
13 | {
14 | /** @var bool */
15 | protected $use_layout;
16 |
17 | /**
18 | * @param bool $use_layout flag to include layout in template
19 | */
20 | public function __construct(bool $use_layout = true)
21 | {
22 | $this->use_layout = $use_layout;
23 | }
24 |
25 | /**
26 | * @param string $template_name
27 | * @param array $data template data
28 | * @param bool $use_layout_locally if true - render template without layout for local usage
29 | * @return string
30 | * @throws FileNotFoundException
31 | */
32 | public function fetchFile(string $template_name, array $data, bool $use_layout_locally = true)
33 | {
34 | $template_filename = __DIR__ . '/../../templates/' . $template_name . '.php';
35 | if (!file_exists($template_filename)) {
36 | throw new FileNotFoundException('Template file not found: ' . $template_filename);
37 | }
38 |
39 | ob_start();
40 | include $template_filename;
41 | $content = ob_get_clean();
42 |
43 | if (!$this->use_layout || !$use_layout_locally) {
44 | return $content;
45 | }
46 |
47 | ob_start();
48 | $layout_filename = __DIR__ . '/../../templates/layout.php';
49 | include $layout_filename;
50 |
51 | return ob_get_clean();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_pgsql/aggregator.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS aggregator_metods (
2 | id SERIAL NOT NULL PRIMARY KEY,
3 | name CHAR(300) NOT NULL,
4 | date date NOT NULL DEFAULT '1970-01-01'
5 | );
6 | CREATE UNIQUE INDEX IF NOT EXISTS name_idx on aggregator_metods (name);
7 |
8 | DO $$
9 | BEGIN
10 | IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'types') THEN
11 | create type types AS ENUM('auto','manual');
12 | END IF;
13 | END
14 | $$;
15 |
16 | CREATE TABLE IF NOT EXISTS aggregator_snapshots (
17 | id SERIAL NOT NULL PRIMARY KEY,
18 | calls_count INT NOT NULL,
19 | app CHAR(32) DEFAULT NULL,
20 | date date NOT NULL,
21 | label CHAR(100) DEFAULT NULL,
22 | type types NOT NULL DEFAULT 'auto',
23 | %SNAPSHOT_CUSTOM_FIELDS%
24 | );
25 | CREATE INDEX IF NOT EXISTS app_idx ON aggregator_snapshots (app);
26 |
27 | CREATE TABLE IF NOT EXISTS aggregator_tree (
28 | id SERIAL NOT NULL PRIMARY KEY,
29 | snapshot_id INT NOT NULL,
30 | method_id INT references aggregator_metods(id),
31 | parent_id INT references aggregator_metods(id),
32 | %TREE_CUSTOM_FIELDS%
33 | );
34 | CREATE INDEX IF NOT EXISTS snapshot_id_parent_id_idx ON aggregator_tree (snapshot_id, parent_id);
35 | CREATE INDEX IF NOT EXISTS snapshot_id_method_id_idx ON aggregator_tree (snapshot_id, method_id);
36 |
37 | CREATE TABLE IF NOT EXISTS aggregator_method_data (
38 | id SERIAL NOT NULL PRIMARY KEY,
39 | snapshot_id INT references aggregator_snapshots(id),
40 | method_id INT references aggregator_metods(id),
41 | %DATA_CUSTOM_FIELDS%
42 | );
43 | CREATE INDEX IF NOT EXISTS aggregator_method_data_ibfk_1 ON aggregator_method_data (snapshot_id,method_id);
44 | CREATE INDEX IF NOT EXISTS aggregator_method_data_ibfk_2 ON aggregator_method_data (method_id);
45 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Logger.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI;
8 |
9 | use Psr\Log\LoggerInterface;
10 | use Psr\Log\LoggerTrait;
11 |
12 | class Logger implements LoggerInterface
13 | {
14 | use LoggerTrait;
15 |
16 | /** @var string */
17 | protected $logfile;
18 |
19 | /**
20 | * Logger constructor.
21 | */
22 | public function __construct(string $logfile = '')
23 | {
24 | if ($logfile) {
25 | $this->logfile = $logfile;
26 | } else {
27 | $this->logfile = __DIR__ . '/../../../live.profiler.ui.log';
28 | }
29 | }
30 |
31 | public function setLogFile($logfile)
32 | {
33 | $this->logfile = $logfile;
34 | }
35 |
36 | /**
37 | * @param mixed $level
38 | * @param string $message
39 | * @param array $context
40 | */
41 | public function log($level, $message, array $context = array())
42 | {
43 | $log_string = $this->getLogMsg($level, $message, $context);
44 | file_put_contents($this->logfile, $log_string, FILE_APPEND);
45 | }
46 |
47 | /**
48 | * @param string $level
49 | * @param string $message
50 | * @param array $context
51 | * @return string
52 | */
53 | protected function getLogMsg($level, $message, array $context = array())
54 | {
55 | $log_string = sprintf("%s\t%s\t%s", date('Y-m-d H:i:s'), $level, $message);
56 |
57 | if (!empty($context)) {
58 | $log_string .= "\t" . json_encode($context);
59 | }
60 |
61 | $log_string .= "\n";
62 |
63 | return $log_string;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/Entity/SnapshotTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\Entity;
8 |
9 | class SnapshotTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testValues()
12 | {
13 | $Snapshot = new \Badoo\LiveProfilerUI\Entity\Snapshot([], []);
14 |
15 | $empty_values = $Snapshot->getValues();
16 | $empty_formatted_values = $Snapshot->getFormattedValues();
17 | self::assertEquals([], $empty_values);
18 | self::assertEquals([], $empty_formatted_values);
19 |
20 | $Snapshot->setValues(['wt' => 1, 'ct' => null]);
21 |
22 | $new_values = $Snapshot->getValues();
23 | $new_formatted_values = $Snapshot->getFormattedValues();
24 | self::assertEquals(['wt' => 1, 'ct' => null], $new_values);
25 | self::assertEquals(['wt' => '1', 'ct' => '-'], $new_formatted_values);
26 | }
27 |
28 | public function testGetters()
29 | {
30 | $data = [
31 | 'id' => '1',
32 | 'calls_count' => '2',
33 | 'app' => ' app ',
34 | 'label' => ' label ',
35 | 'date' => ' date ',
36 | 'type' => ' type '
37 | ];
38 | $Snapshot = new \Badoo\LiveProfilerUI\Entity\Snapshot($data, []);
39 |
40 | self::assertEquals(1, $Snapshot->getId());
41 | self::assertEquals('app', $Snapshot->getApp());
42 | self::assertEquals('label', $Snapshot->getLabel());
43 | self::assertEquals('date', $Snapshot->getDate());
44 | self::assertEquals('type', $Snapshot->getType());
45 | self::assertEquals(2, $Snapshot->getCallsCount());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.threshold.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){var options={series:{threshold:null}};function init(plot){function thresholdData(plot,s,datapoints,below,color){var ps=datapoints.pointsize,i,x,y,p,prevp,thresholded=$.extend({},s);thresholded.datapoints={points:[],pointsize:ps,format:datapoints.format};thresholded.label=null;thresholded.color=color;thresholded.threshold=null;thresholded.originSeries=s;thresholded.data=[];var origpoints=datapoints.points,addCrossingPoints=s.lines.show;var threspoints=[];var newpoints=[];var m;for(i=0;i0&&origpoints[i-ps]!=null){var interx=x+(below-y)*(x-origpoints[i-ps])/(y-origpoints[i-ps+1]);prevp.push(interx);prevp.push(below);for(m=2;m0){var origIndex=$.inArray(s,plot.getData());plot.getData().splice(origIndex+1,0,thresholded)}}function processThresholds(plot,s,datapoints){if(!s.threshold)return;if(s.threshold instanceof Array){s.threshold.sort(function(a,b){return a.below-b.below});$(s.threshold).each(function(i,th){thresholdData(plot,s,datapoints,th.below,th.color)})}else{thresholdData(plot,s,datapoints,s.threshold.below,s.threshold.color)}}plot.hooks.processDatapoints.push(processThresholds)}$.plot.plugins.push({init:init,options:options,name:"threshold",version:"1.2"})})(jQuery);
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/FieldHandler.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI;
8 |
9 | use Badoo\LiveProfilerUI\Interfaces\FieldHandlerInterface;
10 |
11 | class FieldHandler implements FieldHandlerInterface
12 | {
13 | /** @var float */
14 | protected $percentile = 0.95;
15 |
16 | /**
17 | * @param string $function
18 | * @param array $data
19 | * @return null|float
20 | */
21 | public function handle(string $function, array $data)
22 | {
23 | if (empty($data)) {
24 | return null;
25 | }
26 |
27 | if (method_exists($this, $function)) {
28 | return $this->$function($data);
29 | }
30 |
31 | return null;
32 | }
33 |
34 | protected function min(array $data) : float
35 | {
36 | return (float)min($data);
37 | }
38 |
39 | protected function max(array $data) : float
40 | {
41 | return (float)max($data);
42 | }
43 |
44 | protected function avg(array $data) : float
45 | {
46 | return array_sum($data)/\count($data);
47 | }
48 |
49 | /**
50 | * @param array $data
51 | * @return null|float
52 | */
53 | protected function percent(array $data)
54 | {
55 | $count = \count($data);
56 | if ($count < 50) {
57 | return null;
58 | }
59 |
60 | sort($data);
61 | $index = $this->percentile * $count;
62 | $index_rounded = floor($index);
63 | if ($index_rounded === $index) {
64 | $index = (int)$index;
65 | $percent = ($data[$index - 1] + $data[$index]) / 2;
66 | } else {
67 | $percent = $data[(int)$index_rounded];
68 | }
69 | return $percent;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/FieldList.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI;
8 |
9 | class FieldList
10 | {
11 | /** @var string[] */
12 | private $fields = [];
13 | /** @var string[] */
14 | private $variations;
15 | /** @var string[] */
16 | private $descriptions;
17 |
18 | public function __construct(array $fields, array $variations, array $descriptions)
19 | {
20 | foreach ($fields as $field) {
21 | if (\is_array($field)) {
22 | foreach ($field as $profiler_field => $aggregator_field) {
23 | $this->fields[$profiler_field] = $aggregator_field;
24 | }
25 | } else {
26 | $this->fields[$field] = $field;
27 | }
28 | }
29 | $this->variations = $variations;
30 | $this->descriptions = $descriptions;
31 | }
32 |
33 | /**
34 | * @return string[]
35 | */
36 | public function getFields() : array
37 | {
38 | return $this->fields;
39 | }
40 |
41 | /**
42 | * @return string[]
43 | */
44 | public function getFieldVariations() : array
45 | {
46 | return $this->variations;
47 | }
48 |
49 | /**
50 | * @return string[]
51 | */
52 | public function getFieldDescriptions() : array
53 | {
54 | return $this->descriptions;
55 | }
56 |
57 | /**
58 | * @return string[]
59 | */
60 | public function getAllFieldsWithVariations() : array
61 | {
62 | $result = [];
63 | foreach ($this->fields as $field) {
64 | $result[$field] = $field;
65 | foreach ($this->variations as $variation) {
66 | $result[$variation . '_' . $field] = $variation . '_' . $field;
67 | }
68 | }
69 |
70 | return $result;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/templates/profiler_result_view_part.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | | # |
4 |
5 | hide
6 |
7 | |
8 |
9 | name |
10 |
11 | = $param ?>
12 |
13 | |
14 |
15 |
16 |
17 |
18 |
22 |
23 | |
24 |
25 |
26 |
27 | |
28 |
29 |
30 |
31 | |
32 |
33 |
34 |
35 | = $MethodData->getMethodName() ?>
36 |
37 | |
38 |
39 | = $MethodData->getFormattedValue($param) ?> |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/bin/install_data/mysqli/aggregator.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `aggregator_metods` (
2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3 | `name` varchar(300) NOT NULL,
4 | `date` date NOT NULL DEFAULT '1970-01-01',
5 | PRIMARY KEY (`id`),
6 | UNIQUE KEY `name_idx` (`name`)
7 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
8 |
9 | CREATE TABLE IF NOT EXISTS `aggregator_snapshots` (
10 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
11 | `calls_count` int(11) unsigned NOT NULL,
12 | `app` varchar(32) DEFAULT NULL,
13 | `date` date NOT NULL,
14 | `label` varchar(100) DEFAULT NULL,
15 | `type` enum('auto','manual') NOT NULL DEFAULT 'auto',
16 | %SNAPSHOT_CUSTOM_FIELDS%
17 | PRIMARY KEY (`id`),
18 | KEY `app_idx` (`app`)
19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
20 |
21 | CREATE TABLE IF NOT EXISTS `aggregator_tree` (
22 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
23 | `snapshot_id` int(11) unsigned NOT NULL,
24 | `method_id` int(11) unsigned NOT NULL,
25 | `parent_id` int(11) unsigned NOT NULL,
26 | %TREE_CUSTOM_FIELDS%
27 | PRIMARY KEY (`id`),
28 | KEY `snapshot_id_parent_id_idx` (`snapshot_id`,`parent_id`),
29 | KEY `snapshot_id_method_id_idx` (`snapshot_id`,`method_id`),
30 | CONSTRAINT `aggregator_tree_ibfk_3` FOREIGN KEY (`snapshot_id`) REFERENCES `aggregator_snapshots` (`id`) ON DELETE CASCADE
31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
32 |
33 | CREATE TABLE IF NOT EXISTS `aggregator_method_data` (
34 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
35 | `snapshot_id` int(11) unsigned NOT NULL,
36 | `method_id` int(11) unsigned NOT NULL,
37 | %DATA_CUSTOM_FIELDS%
38 | PRIMARY KEY (`id`),
39 | KEY `snapshot_id_method_id_idx` (`snapshot_id`,`method_id`),
40 | KEY `method_id` (`method_id`),
41 | CONSTRAINT `aggregator_method_data_ibfk_1` FOREIGN KEY (`method_id`) REFERENCES `aggregator_metods` (`id`),
42 | CONSTRAINT `aggregator_method_data_ibfk_2` FOREIGN KEY (`snapshot_id`) REFERENCES `aggregator_snapshots` (`id`) ON DELETE CASCADE
43 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
44 |
--------------------------------------------------------------------------------
/bin/install_data/pdo_mysql/aggregator.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `aggregator_metods` (
2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3 | `name` varchar(300) NOT NULL,
4 | `date` date NOT NULL DEFAULT '1970-01-01',
5 | PRIMARY KEY (`id`),
6 | UNIQUE KEY `name_idx` (`name`)
7 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
8 |
9 | CREATE TABLE IF NOT EXISTS `aggregator_snapshots` (
10 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
11 | `calls_count` int(11) unsigned NOT NULL,
12 | `app` varchar(32) DEFAULT NULL,
13 | `date` date NOT NULL,
14 | `label` varchar(100) DEFAULT NULL,
15 | `type` enum('auto','manual') NOT NULL DEFAULT 'auto',
16 | %SNAPSHOT_CUSTOM_FIELDS%
17 | PRIMARY KEY (`id`),
18 | KEY `app_idx` (`app`)
19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
20 |
21 | CREATE TABLE IF NOT EXISTS `aggregator_tree` (
22 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
23 | `snapshot_id` int(11) unsigned NOT NULL,
24 | `method_id` int(11) unsigned NOT NULL,
25 | `parent_id` int(11) unsigned NOT NULL,
26 | %TREE_CUSTOM_FIELDS%
27 | PRIMARY KEY (`id`),
28 | KEY `snapshot_id_parent_id_idx` (`snapshot_id`,`parent_id`),
29 | KEY `snapshot_id_method_id_idx` (`snapshot_id`,`method_id`),
30 | CONSTRAINT `aggregator_tree_ibfk_3` FOREIGN KEY (`snapshot_id`) REFERENCES `aggregator_snapshots` (`id`) ON DELETE CASCADE
31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
32 |
33 | CREATE TABLE IF NOT EXISTS `aggregator_method_data` (
34 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
35 | `snapshot_id` int(11) unsigned NOT NULL,
36 | `method_id` int(11) unsigned NOT NULL,
37 | %DATA_CUSTOM_FIELDS%
38 | PRIMARY KEY (`id`),
39 | KEY `snapshot_id_method_id_idx` (`snapshot_id`,`method_id`),
40 | KEY `method_id` (`method_id`),
41 | CONSTRAINT `aggregator_method_data_ibfk_1` FOREIGN KEY (`method_id`) REFERENCES `aggregator_metods` (`id`),
42 | CONSTRAINT `aggregator_method_data_ibfk_2` FOREIGN KEY (`snapshot_id`) REFERENCES `aggregator_snapshots` (`id`) ON DELETE CASCADE
43 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
44 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/ConsoleCommands/RemoveOldProfilesCommand.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\ConsoleCommands;
7 |
8 | use Symfony\Component\Console\Command\Command;
9 | use Symfony\Component\Console\Input\InputArgument;
10 | use Symfony\Component\Console\Input\InputInterface;
11 | use Symfony\Component\Console\Output\OutputInterface;
12 |
13 | class RemoveOldProfilesCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this
18 | ->setName('cron:remove-old-profiles')
19 | ->setDescription('Removes all profile data older N days.')
20 | ->setHelp('Removes all profile data older N days.')
21 | ->addArgument('keep_days', InputArgument::OPTIONAL, 'Number of days to keep profiles data.', 200);
22 | }
23 |
24 | /**
25 | * @param InputInterface $input
26 | * @param OutputInterface $output
27 | * @return void
28 | * @throws \Exception
29 | */
30 | protected function execute(InputInterface $input, OutputInterface $output)
31 | {
32 | $output->writeln($this->getName() . ' started');
33 |
34 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
35 |
36 | $Snapshot = $App->getSnapshotDataProvider();
37 |
38 | $keep_days = (int)$input->getArgument('keep_days');
39 | $output->writeln('Deletes profiles older ' . $keep_days . ' days');
40 |
41 | $old_snapshots = $Snapshot->getOldSnapshots($keep_days);
42 |
43 | foreach ($old_snapshots as $old_snapshot) {
44 | $result = $Snapshot->deleteById((int)$old_snapshot['id']);
45 |
46 | $output->writeln(
47 | $old_snapshot['app'] . ' ' .
48 | $old_snapshot['label'] . ' ' .
49 | $old_snapshot['date'] . ' ' .
50 | ($result ? 'DELETED' : 'ERROR')
51 | );
52 | }
53 |
54 | $output->writeln($this->getName() . ' finished');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/ConsoleCommands/CreateAggregatingJobsCommand.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\ConsoleCommands;
7 |
8 | use Symfony\Component\Console\Command\Command;
9 | use Symfony\Component\Console\Input\InputArgument;
10 | use Symfony\Component\Console\Input\InputInterface;
11 | use Symfony\Component\Console\Output\OutputInterface;
12 |
13 | class CreateAggregatingJobsCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this
18 | ->setName('cron:create-aggregating-jobs')
19 | ->setDescription('Creates aggregating jobs for last N days.')
20 | ->setHelp('Creates aggregating jobs for last N days.')
21 | ->addArgument('last_num_days', InputArgument::OPTIONAL, 'Number of last days for aggregating.', 3);
22 | }
23 |
24 | /**
25 | * @param InputInterface $input
26 | * @param OutputInterface $output
27 | * @return void
28 | * @throws \Exception
29 | */
30 | protected function execute(InputInterface $input, OutputInterface $output)
31 | {
32 | $output->writeln($this->getName() . ' started');
33 |
34 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
35 | $last_num_days = (int)$input->getArgument('last_num_days');
36 | $output->writeln('Creating jobs for ' . $last_num_days . ' days');
37 |
38 | $snapshots = $App->getAggregator()->getSnapshotsDataForProcessing($last_num_days);
39 | $JobStorage = $App->getJobDataProvider();
40 |
41 | foreach ($snapshots as $snapshot) {
42 | $result = $JobStorage->add($snapshot['app'], $snapshot['label'], $snapshot['date']);
43 |
44 | $output->writeln(
45 | $snapshot['app'] . ' ' .
46 | $snapshot['label'] . ' ' .
47 | $snapshot['date'] . ' ' .
48 | ($result ? 'JOB CREATED' : 'ERROR')
49 | );
50 | }
51 |
52 | $output->writeln($this->getName() . ' finished');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Pages/ProfileListPage.php:
--------------------------------------------------------------------------------
1 |
6 | */
7 |
8 | namespace Badoo\LiveProfilerUI\Pages;
9 |
10 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\SourceInterface;
11 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\SnapshotInterface;
12 | use Badoo\LiveProfilerUI\FieldList;
13 | use Badoo\LiveProfilerUI\Interfaces\ViewInterface;
14 |
15 | class ProfileListPage extends BasePage
16 | {
17 | /** @var string */
18 | protected static $template_path = 'profile_list';
19 | /** @var SnapshotInterface */
20 | protected $Snapshot;
21 | /** @var FieldList */
22 | protected $FieldList;
23 |
24 | public function __construct(
25 | ViewInterface $View,
26 | SnapshotInterface $Snapshot,
27 | FieldList $FieldList
28 | ) {
29 | $this->View = $View;
30 | $this->Snapshot = $Snapshot;
31 | $this->FieldList = $FieldList;
32 | }
33 |
34 | protected function cleanData() : bool
35 | {
36 | $this->data['app'] = isset($this->data['app']) ? trim($this->data['app']) : '';
37 | $this->data['label'] = isset($this->data['label']) ? trim($this->data['label']) : '';
38 | $this->data['date'] = isset($this->data['date']) ? trim($this->data['date']) : '';
39 |
40 | return true;
41 | }
42 |
43 | public function getTemplateData() : array
44 | {
45 | $snapshots = $this->Snapshot->getList($this->data['app']);
46 |
47 | $fields = $this->FieldList->getFields();
48 | $field_descriptions = $this->FieldList->getFieldDescriptions();
49 |
50 | $apps = $this->Snapshot->getAppList();
51 |
52 | return [
53 | 'app' => $this->data['app'],
54 | 'label' => $this->data['label'],
55 | 'date' => $this->data['date'],
56 | 'apps' => $apps,
57 | 'results' => $snapshots,
58 | 'fields' => $fields,
59 | 'field_descriptions' => $field_descriptions,
60 | ];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/ConsoleCommands/AggregateManualCommand.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\ConsoleCommands;
7 |
8 | use Symfony\Component\Console\Command\Command;
9 | use Symfony\Component\Console\Input\InputArgument;
10 | use Symfony\Component\Console\Input\InputInterface;
11 | use Symfony\Component\Console\Output\OutputInterface;
12 |
13 | class AggregateManualCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
18 |
19 | $this
20 | ->setName('aggregator:aggregate-manual')
21 | ->setDescription('Aggregate profiles for last N days.')
22 | ->setHelp('Aggregate profiles for last N days.')
23 | ->addArgument('label', InputArgument::REQUIRED, 'Label of profile.')
24 | ->addArgument('app', InputArgument::OPTIONAL, 'App of profile.', $App->getDefaultApp())
25 | ->addArgument('date', InputArgument::OPTIONAL, 'Date of profile.', date('Y-m-d'));
26 | }
27 |
28 | /**
29 | * @param InputInterface $input
30 | * @param OutputInterface $output
31 | * @return void
32 | * @throws \Exception
33 | */
34 | protected function execute(InputInterface $input, OutputInterface $output)
35 | {
36 | ini_set('memory_limit', '1G');
37 |
38 | $output->writeln($this->getName() . ' started');
39 |
40 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
41 |
42 | $app = $input->getArgument('app');
43 | $label = $input->getArgument('label');
44 | $date = $input->getArgument('date');
45 |
46 | $result = $App->getAggregator()
47 | ->setApp($app)
48 | ->setLabel($label)
49 | ->setDate($date)
50 | ->setIsManual(true)
51 | ->reset()
52 | ->process();
53 |
54 | $output->writeln("$app $label $date " . ($result ? 'AGGREGATED' : 'ERROR'));
55 |
56 | $output->writeln($this->getName() . ' finished');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.crosshair.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){var options={crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}};function init(plot){var crosshair={x:-1,y:-1,locked:false};plot.setCrosshair=function setCrosshair(pos){if(!pos)crosshair.x=-1;else{var o=plot.p2c(pos);crosshair.x=Math.max(0,Math.min(o.left,plot.width()));crosshair.y=Math.max(0,Math.min(o.top,plot.height()))}plot.triggerRedrawOverlay()};plot.clearCrosshair=plot.setCrosshair;plot.lockCrosshair=function lockCrosshair(pos){if(pos)plot.setCrosshair(pos);crosshair.locked=true};plot.unlockCrosshair=function unlockCrosshair(){crosshair.locked=false};function onMouseOut(e){if(crosshair.locked)return;if(crosshair.x!=-1){crosshair.x=-1;plot.triggerRedrawOverlay()}}function onMouseMove(e){if(crosshair.locked)return;if(plot.getSelection&&plot.getSelection()){crosshair.x=-1;return}var offset=plot.offset();crosshair.x=Math.max(0,Math.min(e.pageX-offset.left,plot.width()));crosshair.y=Math.max(0,Math.min(e.pageY-offset.top,plot.height()));plot.triggerRedrawOverlay()}plot.hooks.bindEvents.push(function(plot,eventHolder){if(!plot.getOptions().crosshair.mode)return;eventHolder.mouseout(onMouseOut);eventHolder.mousemove(onMouseMove)});plot.hooks.drawOverlay.push(function(plot,ctx){var c=plot.getOptions().crosshair;if(!c.mode)return;var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);if(crosshair.x!=-1){var adj=plot.getOptions().crosshair.lineWidth%2?.5:0;ctx.strokeStyle=c.color;ctx.lineWidth=c.lineWidth;ctx.lineJoin="round";ctx.beginPath();if(c.mode.indexOf("x")!=-1){var drawX=Math.floor(crosshair.x)+adj;ctx.moveTo(drawX,0);ctx.lineTo(drawX,plot.height())}if(c.mode.indexOf("y")!=-1){var drawY=Math.floor(crosshair.y)+adj;ctx.moveTo(0,drawY);ctx.lineTo(plot.width(),drawY)}ctx.stroke()}ctx.restore()});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mouseout",onMouseOut);eventHolder.unbind("mousemove",onMouseMove)})}$.plot.plugins.push({init:init,options:options,name:"crosshair",version:"1.0"})})(jQuery);
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/FieldHandlerTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class FieldHandlerTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function providerPercent() : array
12 | {
13 | return [
14 | [
15 | 'array' => [],
16 | 'expected' => null
17 | ],
18 | [
19 | 'array' => range(0, 100),
20 | 'expected' => 95
21 | ],
22 | [
23 | 'array' => range(0, 99),
24 | 'expected' => 94.5
25 | ]
26 | ];
27 | }
28 |
29 | /**
30 | * @dataProvider providerPercent
31 | * @param $array
32 | * @param $expected
33 | * @throws \ReflectionException
34 | */
35 | public function testGetPercentil($array, $expected)
36 | {
37 | $FieldHandler = new \Badoo\LiveProfilerUI\FieldHandler();
38 |
39 | $result = $this->invokeMethod($FieldHandler, 'percent', [$array]);
40 |
41 | self::assertEquals($expected, $result);
42 | }
43 |
44 | /**
45 | * @throws \ReflectionException
46 | */
47 | public function testAvg()
48 | {
49 | $array = [1, 2, 3];
50 | $FieldHandler = new \Badoo\LiveProfilerUI\FieldHandler();
51 |
52 | $result = $this->invokeMethod($FieldHandler, 'avg', [$array]);
53 |
54 | $expected = 2.0;
55 | self::assertEquals($expected, $result);
56 | }
57 |
58 | public function testHandleEmpty()
59 | {
60 | $FieldHandler = new \Badoo\LiveProfilerUI\FieldHandler();
61 |
62 | $result = $FieldHandler->handle('min', []);
63 |
64 | self::assertEquals(null, $result);
65 | }
66 |
67 | public function testHandleNotExistsFunction()
68 | {
69 | $array = [1, 2, 3];
70 | $FieldHandler = new \Badoo\LiveProfilerUI\FieldHandler();
71 |
72 | $result = $FieldHandler->handle('invalid_function', $array);
73 |
74 | self::assertEquals(null, $result);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DateGenerator.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI;
8 |
9 | class DateGenerator
10 | {
11 | /**
12 | * Generate an array of dates
13 | * @param string $date last date
14 | * @param int $interval_in_days total interval in days
15 | * @param int $count required count of dates
16 | * @return array
17 | */
18 | public static function getDatesArray(string $date, int $interval_in_days, int $count = 7) : array
19 | {
20 | // return empty array for invalid input data
21 | if (!$interval_in_days || !$count) {
22 | return [];
23 | }
24 |
25 | // return no more than $interval_in_days dates
26 | if ($interval_in_days < $count) {
27 | $count = $interval_in_days;
28 | }
29 |
30 | $step_size = (int)($interval_in_days / $count);
31 | $dates = [];
32 | for ($i = $count - 1; $i >= 0; $i--) {
33 | $days = $i * $step_size;
34 | $dates[] = date('Y-m-d', strtotime($date . " -{$days} day"));
35 | }
36 | return $dates;
37 | }
38 |
39 | /**
40 | * @param string $date_from
41 | * @param string $date_to
42 | * @return array
43 | */
44 | public static function getDatesByRange(string $date_from, string $date_to) : array
45 | {
46 | if (strtotime($date_from) > strtotime($date_to)) {
47 | return [];
48 | }
49 |
50 | try {
51 | $period = new \DatePeriod(
52 | new \DateTime($date_from),
53 | new \DateInterval('P1D'),
54 | new \DateTime($date_to)
55 | );
56 | } catch (\Exception $e) {
57 | return [];
58 | }
59 |
60 | $dates = [];
61 | foreach ($period as $Date) {
62 | /** @var \DateTime $Date */
63 | $date = $Date->format('Y-m-d');
64 | $dates[$date] = $date;
65 | }
66 | $dates[$date_to] = $date_to;
67 |
68 | return array_values($dates);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/ConsoleCommands/AggregateAllProfilesCommand.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\ConsoleCommands;
7 |
8 | use Symfony\Component\Console\Command\Command;
9 | use Symfony\Component\Console\Input\InputArgument;
10 | use Symfony\Component\Console\Input\InputInterface;
11 | use Symfony\Component\Console\Output\OutputInterface;
12 |
13 | class AggregateAllProfilesCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this
18 | ->setName('cron:aggregate-all-profiles')
19 | ->setDescription('Aggregate profiles for last N days.')
20 | ->setHelp('Aggregate profiles for last N days.')
21 | ->addArgument('last_num_days', InputArgument::OPTIONAL, 'Number of last days for aggregating.', 3);
22 | }
23 |
24 | /**
25 | * @param InputInterface $input
26 | * @param OutputInterface $output
27 | * @return void
28 | * @throws \Exception
29 | */
30 | protected function execute(InputInterface $input, OutputInterface $output)
31 | {
32 | ini_set('memory_limit', '1G');
33 |
34 | $output->writeln($this->getName() . ' started');
35 |
36 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
37 |
38 | $last_num_days = (int)$input->getArgument('last_num_days');
39 | $output->writeln('Aggregating profiles for ' . $last_num_days . ' days');
40 |
41 | $snapshots = $App->getAggregator()->getSnapshotsDataForProcessing($last_num_days);
42 |
43 | foreach ($snapshots as $snapshot) {
44 | $result = $App->getAggregator()
45 | ->setApp($snapshot['app'])
46 | ->setLabel($snapshot['label'])
47 | ->setDate($snapshot['date'])
48 | ->reset()
49 | ->process();
50 |
51 | $output->writeln(
52 | $snapshot['app'] . ' ' .
53 | $snapshot['label'] . ' ' .
54 | $snapshot['date'] . ' ' .
55 | ($result ? 'AGGREGATED' : 'ERROR')
56 | );
57 | }
58 |
59 | $output->writeln($this->getName() . ' finished');
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/ConsoleCommands/AWeekDegradationExampleCommand.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\ConsoleCommands;
7 |
8 | use Symfony\Component\Console\Command\Command;
9 | use Symfony\Component\Console\Input\InputInterface;
10 | use Symfony\Component\Console\Output\OutputInterface;
11 |
12 | class AWeekDegradationExampleCommand extends Command
13 | {
14 | protected function configure()
15 | {
16 | $this
17 | ->setName('example:a-week-degradation')
18 | ->setDescription('Runs test code profiling which gets worse during 7 days.')
19 | ->setHelp('Runs test code profiling which gets worse during 7 days.');
20 | }
21 |
22 | /**
23 | * @param InputInterface $input
24 | * @param OutputInterface $output
25 | * @return void
26 | * @throws \Exception
27 | */
28 | protected function execute(InputInterface $input, OutputInterface $output)
29 | {
30 | $output->writeln($this->getName() . ' started');
31 |
32 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
33 |
34 | $SourceStorage = $App->getSourceStorage();
35 |
36 | $install_data_path = __DIR__ . '/../../../../bin/install_data/';
37 | $source_type = $SourceStorage->getType();
38 | $source_sql = file_get_contents($install_data_path . $source_type . '/example.sql');
39 |
40 | $insert_result = $SourceStorage->multiQuery($source_sql);
41 | $output->writeln('Example data inserted: ' . ($insert_result ? 'success' : 'error'));
42 |
43 | $Aggregator = $App->getAggregator();
44 |
45 | $dates = \Badoo\LiveProfilerUI\DateGenerator::getDatesArray(
46 | date('Y-m-d'),
47 | 8,
48 | 8
49 | );
50 | foreach ($dates as $date) {
51 | // Run aggregating process manually to see results
52 | $Aggregator->setApp('App')
53 | ->setLabel('A week degradation')
54 | ->setDate($date)
55 | ->setIsManual(true)
56 | ->reset()
57 | ->process();
58 | }
59 |
60 | $output->writeln($this->getName() . ' finished');
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.fillbetween.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){var options={series:{fillBetween:null}};function init(plot){function findBottomSeries(s,allseries){var i;for(i=0;i=allseries.length){return null}return allseries[s.fillBetween]}return null}function computeFillBottoms(plot,s,datapoints){if(s.fillBetween==null){return}var other=findBottomSeries(s,plot.getData());if(!other){return}var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,withbottom=ps>2&&datapoints.format[2].y,withsteps=withlines&&s.lines.steps,fromgap=true,i=0,j=0,l,m;while(true){if(i>=points.length){break}l=newpoints.length;if(points[i]==null){for(m=0;m=otherpoints.length){if(!withlines){for(m=0;mqx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+1]-py)*(qx-px)/(points[i-ps]-px);newpoints.push(qx);newpoints.push(intery);for(m=2;m0&&otherpoints[j-otherps]!=null){bottom=qy+(otherpoints[j-otherps+1]-qy)*(px-qx)/(otherpoints[j-otherps]-qx)}i+=ps}fromgap=false;if(l!==newpoints.length&&withbottom){newpoints[l+2]=bottom}}if(withsteps&&l!==newpoints.length&&l>0&&newpoints[l]!==null&&newpoints[l]!==newpoints[l-ps]&&newpoints[l+1]!==newpoints[l-ps+1]){for(m=0;m=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)return;plot.resize();plot.setupGrid();plot.draw()}function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize)}function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"resize",version:"1.0"})})(jQuery);
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.stack.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){var options={series:{stack:null}};function init(plot){function findMatchingSeries(s,allseries){var res=null;for(var i=0;i2&&(horizontal?datapoints.format[2].x:datapoints.format[2].y),withsteps=withlines&&s.lines.steps,fromgap=true,keyOffset=horizontal?1:0,accumulateOffset=horizontal?0:1,i=0,j=0,l,m;while(true){if(i>=points.length)break;l=newpoints.length;if(points[i]==null){for(m=0;m=otherpoints.length){if(!withlines){for(m=0;mqx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+accumulateOffset]-py)*(qx-px)/(points[i-ps+keyOffset]-px);newpoints.push(qx);newpoints.push(intery+qy);for(m=2;m0&&otherpoints[j-otherps]!=null)bottom=qy+(otherpoints[j-otherps+accumulateOffset]-qy)*(px-qx)/(otherpoints[j-otherps+keyOffset]-qx);newpoints[l+accumulateOffset]+=bottom;i+=ps}fromgap=false;if(l!=newpoints.length&&withbottom)newpoints[l+2]+=bottom}if(withsteps&&l!=newpoints.length&&l>0&&newpoints[l]!=null&&newpoints[l]!=newpoints[l-ps]&&newpoints[l+1]!=newpoints[l-ps+1]){for(m=0;m
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class DateGeneratorTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function providerGetDatesArray()
12 | {
13 | return [
14 | [
15 | 1,
16 | 1,
17 | ['2019-01-10']
18 | ],
19 | [
20 | 2,
21 | 3,
22 | ['2019-01-09', '2019-01-10']
23 | ],
24 | [
25 | 0,
26 | 0,
27 | []
28 | ],
29 | ];
30 | }
31 |
32 | /**
33 | * @dataProvider providerGetDatesArray
34 | * @param $interval_in_days
35 | * @param $count
36 | * @param $expected
37 | */
38 | public function testGetDatesArray($interval_in_days, $count, $expected)
39 | {
40 | $result = \Badoo\LiveProfilerUI\DateGenerator::getDatesArray('2019-01-10', $interval_in_days, $count);
41 |
42 | self::assertEquals($expected, $result);
43 | }
44 |
45 | public function providerGetDatesByRange() : array
46 | {
47 | return [
48 | [
49 | '2019-01-10',
50 | '2019-01-10',
51 | [
52 | '2019-01-10'
53 | ]
54 | ],
55 | [
56 | '2019-01-10',
57 | '2019-01-13',
58 | [
59 | '2019-01-10',
60 | '2019-01-11',
61 | '2019-01-12',
62 | '2019-01-13',
63 | ]
64 | ],
65 | [
66 | '2019-01-10',
67 | '2019-01-01',
68 | []
69 | ],
70 | [
71 | 'abc',
72 | '123',
73 | []
74 | ],
75 | ];
76 | }
77 |
78 | /**
79 | * @dataProvider providerGetDatesByRange
80 | * @param string $date_from
81 | * @param string $date_to
82 | * @param array $expected
83 | */
84 | public function testGetDatesByRange(string $date_from, string $date_to, array $expected)
85 | {
86 | $result = \Badoo\LiveProfilerUI\DateGenerator::getDatesByRange($date_from, $date_to);
87 |
88 | self::assertEquals($expected, $result);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Entity/Snapshot.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Entity;
8 |
9 | class Snapshot extends BaseEntity
10 | {
11 | /** @var int */
12 | protected $id = 0;
13 | /** @var int */
14 | protected $calls_count = 0;
15 | /** @var string */
16 | protected $app = '';
17 | /** @var string */
18 | protected $label = '';
19 | /** @var string */
20 | protected $date = '';
21 | /** @var string */
22 | protected $type = '';
23 | /** @var bool */
24 | protected $is_auto_method = false;
25 | /** @var array */
26 | protected $values = [];
27 | /** @var array */
28 | protected $formatted_values = [];
29 |
30 | public function __construct(array $data, array $fields)
31 | {
32 | $this->id = isset($data['id']) ? (int)$data['id'] : 0;
33 | $this->calls_count = isset($data['calls_count']) ? (int)$data['calls_count'] : 0;
34 | $this->app = isset($data['app']) ? trim($data['app']) : '';
35 | $this->label = isset($data['label']) ? trim($data['label']) : '';
36 | $this->date = isset($data['date']) ? trim($data['date']) : '';
37 | $this->type = isset($data['type']) ? trim($data['type']) : '';
38 | $this->is_auto_method = !empty($data['is_auto_method']);
39 |
40 | $this->setValues($this->prepareValues($data, $fields));
41 | }
42 |
43 | public function getId() : int
44 | {
45 | return $this->id;
46 | }
47 |
48 | public function getApp() : string
49 | {
50 | return $this->app;
51 | }
52 |
53 | public function getLabel() : string
54 | {
55 | return $this->label;
56 | }
57 |
58 | public function getDate() : string
59 | {
60 | return $this->date;
61 | }
62 |
63 | public function getType() : string
64 | {
65 | return $this->type;
66 | }
67 |
68 | public function setValues(array $values) : self
69 | {
70 | $this->values = $values;
71 | $this->formatted_values = $this->prepareFormattedValues($values);
72 | return $this;
73 | }
74 |
75 | public function getValues() : array
76 | {
77 | return $this->values;
78 | }
79 |
80 | public function getFormattedValues() : array
81 | {
82 | return $this->formatted_values;
83 | }
84 |
85 | public function getCallsCount() : int
86 | {
87 | return $this->calls_count;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.categories.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){var options={xaxis:{categories:null},yaxis:{categories:null}};function processRawData(plot,series,data,datapoints){var xCategories=series.xaxis.options.mode=="categories",yCategories=series.yaxis.options.mode=="categories";if(!(xCategories||yCategories))return;var format=datapoints.format;if(!format){var s=series;format=[];format.push({x:true,number:true,required:true});format.push({y:true,number:true,required:true});if(s.bars.show||s.lines.show&&s.lines.fill){var autoscale=!!(s.bars.show&&s.bars.zero||s.lines.show&&s.lines.zero);format.push({y:true,number:true,required:false,defaultValue:0,autoscale:autoscale});if(s.bars.horizontal){delete format[format.length-1].y;format[format.length-1].x=true}}datapoints.format=format}for(var m=0;mindex)index=categories[v];return index+1}function categoriesTickGenerator(axis){var res=[];for(var label in axis.categories){var v=axis.categories[label];if(v>=axis.min&&v<=axis.max)res.push([v,label])}res.sort(function(a,b){return a[0]-b[0]});return res}function setupCategoriesForAxis(series,axis,datapoints){if(series[axis].options.mode!="categories")return;if(!series[axis].categories){var c={},o=series[axis].options.categories||{};if($.isArray(o)){for(var i=0;i
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\JobInterface;
10 |
11 | class Job extends Base implements JobInterface
12 | {
13 | const TABLE_NAME = 'aggregator_jobs';
14 |
15 | /**
16 | * @param string $status
17 | * @param int $limit
18 | * @return \Badoo\LiveProfilerUI\Entity\Job[]
19 | */
20 | public function getJobs(string $status, int $limit = 100) : array
21 | {
22 | $records = $this->AggregatorStorage->getAll(
23 | self::TABLE_NAME,
24 | ['all'],
25 | [
26 | 'filter' => [['status', $status]],
27 | 'limit' => $limit
28 | ]
29 | );
30 |
31 | $return = [];
32 | if (!empty($records)) {
33 | foreach ($records as $record) {
34 | $return[] = new \Badoo\LiveProfilerUI\Entity\Job($record);
35 | }
36 | }
37 |
38 | return $return;
39 | }
40 |
41 | public function getJob(
42 | string $app,
43 | string $label,
44 | string $date,
45 | array $statuses
46 | ) : \Badoo\LiveProfilerUI\Entity\Job {
47 | $record = $this->AggregatorStorage->getOne(
48 | self::TABLE_NAME,
49 | ['all'],
50 | [
51 | 'filter' => [
52 | ['app', $app],
53 | ['label', $label],
54 | ['date', $date],
55 | ['status', $statuses]
56 | ],
57 | 'order' => ['id' => 'desc']
58 | ]
59 | );
60 |
61 | if (empty($record)) {
62 | throw new \InvalidArgumentException('Can\'t get job');
63 | }
64 |
65 | return new \Badoo\LiveProfilerUI\Entity\Job($record);
66 | }
67 |
68 | public function add(string $app, string $label, string $date, string $type = 'auto') : int
69 | {
70 | $fields = [
71 | 'app' => $app,
72 | 'label' => $label,
73 | 'date' => $date,
74 | 'type' => $type
75 | ];
76 |
77 | return $this->AggregatorStorage->insert(self::TABLE_NAME, $fields);
78 | }
79 |
80 | public function changeStatus(int $job_id, string $status) : bool
81 | {
82 | return $this->AggregatorStorage->update(
83 | self::TABLE_NAME,
84 | ['status' => $status],
85 | ['id' => $job_id]
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.image.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){var options={series:{images:{show:false,alpha:1,anchor:"corner"}}};$.plot.image={};$.plot.image.loadDataImages=function(series,options,callback){var urls=[],points=[];var defaultShow=options.series.images.show;$.each(series,function(i,s){if(!(defaultShow||s.images.show))return;if(s.data)s=s.data;$.each(s,function(i,p){if(typeof p[0]=="string"){urls.push(p[0]);points.push(p)}})});$.plot.image.load(urls,function(loadedImages){$.each(points,function(i,p){var url=p[0];if(loadedImages[url])p[0]=loadedImages[url]});callback()})};$.plot.image.load=function(urls,callback){var missing=urls.length,loaded={};if(missing==0)callback({});$.each(urls,function(i,url){var handler=function(){--missing;loaded[url]=this;if(missing==0)callback(loaded)};$("
").load(handler).error(handler).attr("src",url)})};function drawSeries(plot,ctx,series){var plotOffset=plot.getPlotOffset();if(!series.images||!series.images.show)return;var points=series.datapoints.points,ps=series.datapoints.pointsize;for(var i=0;ix2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}if(series.images.anchor=="center"){tmp=.5*(x2-x1)/(img.width-1);x1-=tmp;x2+=tmp;tmp=.5*(y2-y1)/(img.height-1);y1-=tmp;y2+=tmp}if(x1==x2||y1==y2||x1>=xaxis.max||x2<=xaxis.min||y1>=yaxis.max||y2<=yaxis.min)continue;var sx1=0,sy1=0,sx2=img.width,sy2=img.height;if(x1xaxis.max){sx2+=(sx2-sx1)*(xaxis.max-x2)/(x2-x1);x2=xaxis.max}if(y1yaxis.max){sy1+=(sy1-sy2)*(yaxis.max-y2)/(y2-y1);y2=yaxis.max}x1=xaxis.p2c(x1);x2=xaxis.p2c(x2);y1=yaxis.p2c(y1);y2=yaxis.p2c(y2);if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}tmp=ctx.globalAlpha;ctx.globalAlpha*=series.images.alpha;ctx.drawImage(img,sx1,sy1,sx2-sx1,sy2-sy1,x1+plotOffset.left,y1+plotOffset.top,x2-x1,y2-y1);ctx.globalAlpha=tmp}}function processRawData(plot,series,data,datapoints){if(!series.images.show)return;datapoints.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.drawSeries.push(drawSeries)}$.plot.plugins.push({init:init,options:options,name:"image",version:"1.1"})})(jQuery);
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/Entity/MethodDaraTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\Entity;
8 |
9 | class MethodDaraTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testGetters()
12 | {
13 | $data = [
14 | 'snapshot_id' => '1',
15 | 'method_id' => '2',
16 | 'method_name' => ' this is a very long method name with length more than 60 characters ',
17 | 'wt' => 123,
18 | 'ct' => null,
19 | ];
20 | $MethodData = new \Badoo\LiveProfilerUI\Entity\MethodData($data, ['ct' => 'ct', 'wt' => 'wt']);
21 |
22 | self::assertEquals(1, $MethodData->getSnapshotId());
23 | self::assertEquals(2, $MethodData->getMethodId());
24 | self::assertEquals('ng method name with length more than 60 characters', $MethodData->getMethodName());
25 | self::assertEquals(
26 | 'this is a very long method name with length more than 60 characters',
27 | $MethodData->getMethodNameAlt()
28 | );
29 | self::assertEquals(['wt' => 123, 'ct' => null], $MethodData->getValues());
30 | self::assertEquals(['wt' => '123', 'ct' => '-'], $MethodData->getFormattedValues());
31 | }
32 |
33 | public function testValue()
34 | {
35 | $MethodData = new \Badoo\LiveProfilerUI\Entity\MethodData([], []);
36 |
37 | $empty_value = $MethodData->getValue('wt');
38 | $empty_formatted_value = $MethodData->getFormattedValue('wt');
39 | self::assertEquals(0, $empty_value);
40 | self::assertEquals('-', $empty_formatted_value);
41 |
42 | $MethodData->setValue('wt', 123);
43 |
44 | $new_value = $MethodData->getValue('wt');
45 | $new_formatted_value = $MethodData->getValue('wt');
46 | self::assertEquals(123, $new_value);
47 | self::assertEquals('123', $new_formatted_value);
48 | }
49 |
50 | public function testHistoryData()
51 | {
52 | $MethodData = new \Badoo\LiveProfilerUI\Entity\MethodData([], []);
53 |
54 | $empty_value = $MethodData->getHistoryData();
55 | self::assertEquals([], $empty_value);
56 |
57 | $MethodData->setHistoryData(['data']);
58 |
59 | $new_value = $MethodData->getHistoryData();
60 | self::assertEquals(['data'], $new_value);
61 | }
62 |
63 | public function testJsonEncode()
64 | {
65 | $MethodData = new \Badoo\LiveProfilerUI\Entity\MethodData([], []);
66 |
67 | $json = json_encode($MethodData);
68 |
69 | self::assertNotEmpty($json);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/BaseTestCase.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo;
8 |
9 | use Symfony\Component\DependencyInjection\ContainerBuilder;
10 | use Symfony\Component\Config\FileLocator;
11 | use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
12 | use Symfony\Component\DependencyInjection\ContainerInterface;
13 |
14 | class BaseTestCase extends \PHPUnit\Framework\TestCase
15 | {
16 | /**
17 | * @var ContainerInterface;
18 | */
19 | protected static $Container;
20 |
21 | /**
22 | * @throws \Exception
23 | */
24 | public static function setUpBeforeClass(): void
25 | {
26 | parent::setUpBeforeClass();
27 |
28 | self::$Container = new ContainerBuilder();
29 | $DILoader = new YamlFileLoader(self::$Container, new FileLocator(__DIR__ . '/../../../src/config'));
30 | $DILoader->load('services.yaml');
31 | }
32 |
33 | /**
34 | * Call protected/private method of a class.
35 | * @param object &$object Instantiated object that we will run method on.
36 | * @param string $methodName Method name to call
37 | * @param array $parameters Array of parameters to pass into method.
38 | * @return mixed Method return.
39 | * @throws \ReflectionException
40 | */
41 | public function invokeMethod(&$object, $methodName, array $parameters = array())
42 | {
43 | $reflection = new \ReflectionClass(\get_class($object));
44 | $method = $reflection->getMethod($methodName);
45 | $method->setAccessible(true);
46 |
47 | return $method->invokeArgs($object, $parameters);
48 | }
49 |
50 | /**
51 | * @param $object
52 | * @param $property
53 | * @param $value
54 | * @throws \ReflectionException
55 | */
56 | public function setProtectedProperty(&$object, $property, $value)
57 | {
58 | $reflection = new \ReflectionClass(\get_class($object));
59 | $reflection_property = $reflection->getProperty($property);
60 | $reflection_property->setAccessible(true);
61 | $reflection_property->setValue($object, $value);
62 | }
63 |
64 | /**
65 | * @param $object
66 | * @param $property
67 | * @return mixed
68 | * @throws \ReflectionException
69 | */
70 | public function getProtectedProperty(&$object, $property)
71 | {
72 | $reflection = new \ReflectionClass(\get_class($object));
73 | $reflection_property = $reflection->getProperty($property);
74 | $reflection_property->setAccessible(true);
75 | return $reflection_property->getValue($object);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DB/DBUtilsTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DB\Adapters;
8 |
9 | class DBUtilsTest extends \unit\Badoo\BaseTestCase
10 | {
11 | /**
12 | * @throws \Exception
13 | */
14 | public function testPrepareCreateTablesSqlite()
15 | {
16 | $sql = 'create table t (%SNAPSHOT_CUSTOM_FIELDS% %DATA_CUSTOM_FIELDS% %TREE_CUSTOM_FIELDS%)';
17 | $result = \Badoo\LiveProfilerUI\DB\SqlTableBuilder::prepareCreateTables('pdo_sqlite', $sql, ['wt', 'ct', 'ct_min'], 'ct');
18 |
19 | $expected = <<expectException(\Exception::class);
74 | $this->expectExceptionMessage('Not supported db type: invalid');
75 | \Badoo\LiveProfilerUI\DB\SqlTableBuilder::prepareCreateTables('invalid', '', ['wt'], 'ct');
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Entity/Job.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Entity;
8 |
9 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\JobInterface;
10 |
11 | class Job
12 | {
13 | /** @var int */
14 | protected $id = 0;
15 | /** @var string */
16 | protected $app = '';
17 | /** @var string */
18 | protected $label = '';
19 | /** @var string */
20 | protected $date = '';
21 | /** @var string */
22 | protected $type = '';
23 | /** @var string */
24 | protected $status = '';
25 |
26 | public function __construct(array $data)
27 | {
28 | $this->id = isset($data['id']) ? (int)$data['id'] : 0;
29 | $this->app = isset($data['app']) ? trim($data['app']) : '';
30 | $this->label = isset($data['label']) ? trim($data['label']) : '';
31 | $this->date = isset($data['date']) ? trim($data['date']) : '';
32 | $this->type = isset($data['type']) ? trim($data['type']) : JobInterface::TYPE_AUTO;
33 | $this->status = isset($data['status']) ? trim($data['status']) : JobInterface::STATUS_NEW;
34 | }
35 |
36 | public function getId(): int
37 | {
38 | return $this->id;
39 | }
40 |
41 | public function setId(int $id) : self
42 | {
43 | $this->id = $id;
44 | return $this;
45 | }
46 |
47 | public function getApp(): string
48 | {
49 | return $this->app;
50 | }
51 |
52 | public function setApp(string $app) : self
53 | {
54 | $this->app = $app;
55 | return $this;
56 | }
57 |
58 | public function getLabel(): string
59 | {
60 | return $this->label;
61 | }
62 |
63 | public function setLabel(string $label) : self
64 | {
65 | $this->label = $label;
66 | return $this;
67 | }
68 |
69 | public function getDate(): string
70 | {
71 | return $this->date;
72 | }
73 |
74 | public function setDate(string $date) : self
75 | {
76 | $this->date = $date;
77 | return $this;
78 | }
79 |
80 | public function getType(): string
81 | {
82 | return $this->type;
83 | }
84 |
85 | public function setType(string $type) : self
86 | {
87 | $this->type = $type;
88 | return $this;
89 | }
90 |
91 | public function getStatus(): string
92 | {
93 | return $this->status;
94 | }
95 |
96 | public function setStatus(string $status) : self
97 | {
98 | $this->status = $status;
99 | return $this;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/ConsoleCommands/InstallCommand.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\ConsoleCommands;
7 |
8 | use Symfony\Component\Console\Command\Command;
9 | use Symfony\Component\Console\Input\InputInterface;
10 | use Symfony\Component\Console\Output\OutputInterface;
11 |
12 | class InstallCommand extends Command
13 | {
14 | protected function configure()
15 | {
16 | $this
17 | ->setName('aggregator:install')
18 | ->setDescription('Prepares tables for keep profiles and aggregating data.')
19 | ->setHelp('Prepares tables for keep profiles and aggregating data.');
20 | }
21 |
22 | /**
23 | * @param InputInterface $input
24 | * @param OutputInterface $output
25 | * @return void
26 | * @throws \Exception
27 | */
28 | protected function execute(InputInterface $input, OutputInterface $output)
29 | {
30 | $output->writeln($this->getName() . ' started');
31 |
32 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
33 |
34 | $SourceStorage = $App->getSourceStorage();
35 | $AggregatorStorage = $App->getAggregatorStorage();
36 |
37 | $source_type = $SourceStorage->getType();
38 | $aggregator_type = $AggregatorStorage->getType();
39 |
40 | $install_data_path = __DIR__ . '/../../../../bin/install_data/';
41 |
42 | $source_sql = file_get_contents($install_data_path . $source_type . '/source.sql');
43 | $aggregator_sql = file_get_contents($install_data_path. $aggregator_type . '/aggregator.sql');
44 |
45 | $source_result = $SourceStorage->multiQuery($source_sql);
46 | $output->writeln('Source storage creating: ' . ($source_result ? 'success' : 'error'));
47 |
48 | $FieldList = $App->getFieldService();
49 | $fields = $FieldList->getAllFieldsWithVariations();
50 | $calls_count_field = $App->getCallsCountField();
51 |
52 | $aggregator_sql = \Badoo\LiveProfilerUI\DB\SqlTableBuilder::prepareCreateTables(
53 | $aggregator_type,
54 | $aggregator_sql,
55 | $fields,
56 | $calls_count_field
57 | );
58 |
59 | $use_jobs_in_aggregation = $App->isUseJobsInAggregation();
60 | if ($use_jobs_in_aggregation) {
61 | $jobs_sql = file_get_contents($install_data_path . $aggregator_type . '/jobs.sql');
62 | $aggregator_sql .= "\n" . $jobs_sql;
63 | }
64 |
65 | $aggregator_result = $AggregatorStorage->multiQuery($aggregator_sql);
66 | $output->writeln('Aggregator storage creating: ' . ($aggregator_result ? 'success' : 'error'));
67 | $output->writeln($this->getName() . ' finished');
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/ConsoleCommands/ProcessAggregatingJobsCommand.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace Badoo\LiveProfilerUI\ConsoleCommands;
7 |
8 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\JobInterface;
9 | use Symfony\Component\Console\Command\Command;
10 | use Symfony\Component\Console\Input\InputInterface;
11 | use Symfony\Component\Console\Output\OutputInterface;
12 |
13 | class ProcessAggregatingJobsCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this
18 | ->setName('cron:process-aggregating-jobs')
19 | ->setDescription('Processes aggregating jobs.')
20 | ->setHelp('Processes aggregating jobs.');
21 | }
22 |
23 | /**
24 | * @param InputInterface $input
25 | * @param OutputInterface $output
26 | * @return void
27 | * @throws \Exception
28 | */
29 | protected function execute(InputInterface $input, OutputInterface $output)
30 | {
31 | $output->writeln($this->getName() . ' started');
32 |
33 | $App = new \Badoo\LiveProfilerUI\LiveProfilerUI();
34 |
35 | $lock_filename = $App->getAggregatingJobsLockFile();
36 | !file_exists($lock_filename) && touch($lock_filename);
37 | $lock_fp = fopen($lock_filename, 'rb+');
38 | if (!flock($lock_fp, LOCK_EX | LOCK_NB)) {
39 | $output->writeln('script is already running');
40 | return;
41 | }
42 |
43 | ini_set('memory_limit', '1G');
44 |
45 | $JobStorage = $App->getJobDataProvider();
46 |
47 | $started_ts = time();
48 | while (time() - $started_ts < 300) {
49 | $jobs = $JobStorage->getJobs(JobInterface::STATUS_NEW, 100);
50 | foreach ($jobs as $Job) {
51 | try {
52 | $JobStorage->changeStatus($Job->getId(), JobInterface::STATUS_PROCESSING);
53 |
54 | $Aggregator = $App->getAggregator()
55 | ->setApp($Job->getApp())
56 | ->setLabel($Job->getLabel())
57 | ->setDate($Job->getDate())
58 | ->setIsManual($Job->getType() && $Job->getType() === 'manual');
59 |
60 | $result = $Aggregator->process();
61 | if (!empty($result)) {
62 | $JobStorage->changeStatus($Job->getId(), JobInterface::STATUS_FINISHED);
63 | } else {
64 | $JobStorage->changeStatus($Job->getId(), JobInterface::STATUS_ERROR);
65 | }
66 | } catch (\Exception $Ex) {
67 | $JobStorage->changeStatus($Job->getId(), JobInterface::STATUS_ERROR);
68 | }
69 | }
70 | sleep(2);
71 | }
72 |
73 | $output->writeln($this->getName() . ' finished');
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.colorhelpers.min.js:
--------------------------------------------------------------------------------
1 | (function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/FlameGraph.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI;
8 |
9 | class FlameGraph
10 | {
11 | /**
12 | * Get svg data for flame graph
13 | * @param string $graph_input
14 | * @return string
15 | */
16 | public static function getSVG(string $graph_input) : string
17 | {
18 | if (!$graph_input) {
19 | return '';
20 | }
21 |
22 | $tmp_file = tempnam(__DIR__, 'flamefile');
23 | file_put_contents($tmp_file, $graph_input);
24 | exec('perl ' . __DIR__ . '/../../../scripts/flamegraph.pl ' . $tmp_file, $output);
25 | unlink($tmp_file);
26 |
27 | return implode("\n", $output);
28 | }
29 |
30 | /**
31 | * @param \Badoo\LiveProfilerUI\Entity\MethodTree[] $elements
32 | * @param array $parents_param
33 | * @param array $parent
34 | * @param string $param
35 | * @param float $threshold
36 | * @param int $level
37 | * @return string
38 | */
39 | public static function buildFlameGraphInput(
40 | array $elements,
41 | array $parents_param,
42 | array $parent,
43 | string $param,
44 | float $threshold,
45 | int $level = 0
46 | ) : string {
47 | if ($level > 50) {
48 | // limit nesting level
49 | return '';
50 | }
51 |
52 | $texts = '';
53 | foreach ($elements as $Element) {
54 | if ($Element->getParentId() === $parent['method_id']) {
55 | $element_value = $Element->getValue($param);
56 | $value = $parent[$param] - $element_value;
57 |
58 | if (($value <= 0) && !empty($parents_param[$Element->getParentId()])) {
59 | $p = $parents_param[$Element->getParentId()];
60 | $sum_p = array_sum($p);
61 | $element_value = 0;
62 | if ($sum_p !== 0) {
63 | $element_value = ($parent[$param] / $sum_p) * $Element->getValue($param);
64 | }
65 | $value = $parent[$param] - $element_value;
66 | }
67 |
68 | if ($element_value < $threshold) {
69 | continue;
70 | }
71 |
72 | $new_parent = [
73 | 'method_id' => $Element->getMethodId(),
74 | 'name' => $parent['name'] . ';' . $Element->getMethodNameAlt(),
75 | $param => $element_value
76 | ];
77 | $texts .= self::buildFlameGraphInput(
78 | $elements,
79 | $parents_param,
80 | $new_parent,
81 | $param,
82 | $threshold,
83 | $level + 1
84 | );
85 | $parent[$param] = $value;
86 | }
87 | }
88 |
89 | $texts .= $parent['name'] . ' ' . $parent[$param] . "\n";
90 |
91 | return $texts;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DataProviders/JobTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\Entity\Job;
10 |
11 | class JobTest extends \unit\Badoo\BaseTestCase
12 | {
13 | protected $last_sql = '';
14 | /** @var \Badoo\LiveProfilerUI\DB\Storage */
15 | protected $AggregatorStorage;
16 | protected $FieldList;
17 |
18 | protected function setUp(): void
19 | {
20 | parent::setUp();
21 |
22 | $this->AggregatorStorage = $this->getMockBuilder(\Badoo\LiveProfilerUI\DB\Storage::class)
23 | ->setConstructorArgs(['sqlite:///:memory:'])
24 | ->setMethods()
25 | ->getMock();
26 |
27 | $this->AggregatorStorage->query(
28 | 'create table aggregator_jobs(id integer, app text, label text, date text, type text, status text)'
29 | );
30 | $this->AggregatorStorage->insert(
31 | 'aggregator_jobs',
32 | ['id' => 1, 'app' => 'app1', 'label' => 'label1', 'date' => '2019-01-01', 'status' => 'new']
33 | );
34 |
35 | $this->FieldList = new \Badoo\LiveProfilerUI\FieldList(['wt'], [], []);
36 | }
37 |
38 | public function testGetSnapshotsDataByDates()
39 | {
40 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Job($this->AggregatorStorage, $this->FieldList);
41 | $result = $Source->getJobs('new', 1);
42 |
43 | $expected = [
44 | new Job([
45 | 'id' => 1,
46 | 'app' => 'app1',
47 | 'label' => 'label1',
48 | 'date' => '2019-01-01',
49 | ])
50 | ];
51 | self::assertEquals($expected, $result);
52 | }
53 |
54 | public function testGetNotExistsJob()
55 | {
56 | $this->expectException(\InvalidArgumentException::class);
57 | $this->expectExceptionMessage('Can\'t get job');
58 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Job($this->AggregatorStorage, $this->FieldList);
59 | $result = $Source->getJob('app2', 'label2', '2019-01-02', ['new']);
60 |
61 | $expected = new Job([
62 | 'id' => 1,
63 | 'app' => 'app1',
64 | 'label' => 'label1',
65 | 'date' => '2019-01-01',
66 | ]);
67 | self::assertEquals($expected, $result);
68 | }
69 |
70 | public function testGetJob()
71 | {
72 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Job($this->AggregatorStorage, $this->FieldList);
73 | $result = $Source->getJob('app1', 'label1', '2019-01-01', ['new']);
74 |
75 | $expected = new Job([
76 | 'id' => 1,
77 | 'app' => 'app1',
78 | 'label' => 'label1',
79 | 'date' => '2019-01-01',
80 | ]);
81 | self::assertEquals($expected, $result);
82 | }
83 |
84 | public function testAdd()
85 | {
86 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Job($this->AggregatorStorage, $this->FieldList);
87 | $result = $Source->add('app2', 'label2', '2019-01-02', 'auto');
88 |
89 | self::assertEquals(2, $result);
90 | }
91 |
92 | public function testChangeStatus()
93 | {
94 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Job($this->AggregatorStorage, $this->FieldList);
95 | $result = $Source->changeStatus(1, 'finished');
96 |
97 | self::assertTrue($result);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/FlameGraphTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI;
8 |
9 | class FlameGraphTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testGetSVGEmptyInput()
12 | {
13 | $result = \Badoo\LiveProfilerUI\FlameGraph::getSVG('');
14 | self::assertEquals('', $result);
15 | }
16 |
17 | public function testBuildFlameGraphInputNestedLevel()
18 | {
19 | $elements = [];
20 | $parents_param = [];
21 | $root = [];
22 | $param = 'wt';
23 | $threshold = 0;
24 |
25 | $result = \Badoo\LiveProfilerUI\FlameGraph::buildFlameGraphInput(
26 | $elements,
27 | $parents_param,
28 | $root,
29 | $param,
30 | $threshold,
31 | 51
32 | );
33 | self::assertEquals('', $result);
34 | }
35 |
36 | public function providerBuildFlameGraphInput() : array
37 | {
38 | return [
39 | [
40 | 'elements' => [
41 | ['method_id' => 1, 'parent_id' => 0, 'method_name' => 'main()', 'wt' => 7],
42 | ],
43 | 'expected' => "main() 7\n",
44 | ],
45 | [
46 | 'elements' => [
47 | ['method_id' => 2, 'parent_id' => 1, 'method_name' => 'f', 'wt' => 1],
48 | ['method_id' => 1, 'parent_id' => 0, 'method_name' => 'main()', 'wt' => 2],
49 | ],
50 | 'expected' => "main();f 1\nmain() 6\n",
51 | ],
52 | [
53 | 'elements' => [
54 | ['method_id' => 5, 'parent_id' => 4, 'method_name' => 'c2', 'wt' => 2],
55 | ['method_id' => 4, 'parent_id' => 3, 'method_name' => 'c1', 'wt' => 2],
56 | ['method_id' => 4, 'parent_id' => 2, 'method_name' => 'c1', 'wt' => 2],
57 | ['method_id' => 3, 'parent_id' => 1, 'method_name' => 'p1', 'wt' => 3],
58 | ['method_id' => 2, 'parent_id' => 1, 'method_name' => 'p2', 'wt' => 3],
59 | ],
60 | 'expected' => "main();p1;c1;c2 1\nmain();p1;c1 1\nmain();p1 1\nmain();p2;c1;c2 1\nmain();p2;c1 1\nmain();p2 1\nmain() 1\n",
61 | ],
62 | ];
63 | }
64 |
65 | /**
66 | * @dataProvider providerBuildFlameGraphInput
67 | * @param array $elements
68 | * @param string $expected
69 | * @throws \ReflectionException
70 | */
71 | public function testBuildFlameGraphInput($elements, $expected)
72 | {
73 | foreach ($elements as &$element) {
74 | $element = new \Badoo\LiveProfilerUI\Entity\MethodTree($element, ['wt' => 'wt']);
75 | }
76 | unset($element);
77 |
78 | /** @var \Badoo\LiveProfilerUI\Pages\FlameGraphPage $PageMock */
79 | $PageMock = $this->getMockBuilder(\Badoo\LiveProfilerUI\Pages\FlameGraphPage::class)
80 | ->disableOriginalConstructor()
81 | ->setMethods(['__construct'])
82 | ->getMock();
83 |
84 | $root_method_data = ['method_id' => 1, 'name' => 'main()', 'wt' => 7];
85 | $param = 'wt';
86 | $threshold = 0;
87 | $parents_param = $this->invokeMethod($PageMock, 'getAllMethodParentsParam', [$elements, $param]);
88 | $result = \Badoo\LiveProfilerUI\FlameGraph::buildFlameGraphInput(
89 | $elements,
90 | $parents_param,
91 | $root_method_data,
92 | $param,
93 | $threshold
94 | );
95 |
96 | self::assertEquals($expected, $result);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/Entity/TopDiffTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\Entity;
8 |
9 | class TopDiffTest extends \unit\Badoo\BaseTestCase
10 | {
11 | public function testApp()
12 | {
13 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
14 |
15 | $empty_app = $TopDiff->getApp();
16 | self::assertEquals('', $empty_app);
17 |
18 | $TopDiff->setApp('new app');
19 | $new_app = $TopDiff->getApp();
20 | self::assertEquals('new app', $new_app);
21 | }
22 |
23 | public function testLabel()
24 | {
25 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
26 |
27 | $empty_label = $TopDiff->getLabel();
28 | self::assertEquals('', $empty_label);
29 |
30 | $TopDiff->setLabel('new label');
31 | $new_label = $TopDiff->getLabel();
32 | self::assertEquals('new label', $new_label);
33 | }
34 |
35 | public function testMethodId()
36 | {
37 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
38 |
39 | $empty_method_id = $TopDiff->getMethodId();
40 | self::assertEquals(0, $empty_method_id);
41 |
42 | $TopDiff->setMethodId(1);
43 | $new_method_id = $TopDiff->getMethodId();
44 | self::assertEquals(1, $new_method_id);
45 | }
46 |
47 | public function testMethodName()
48 | {
49 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
50 |
51 | $empty_method_name = $TopDiff->getMethodName();
52 | self::assertEquals('', $empty_method_name);
53 |
54 | $TopDiff->setMethodName('new method name');
55 | $new_method_name = $TopDiff->getMethodName();
56 | self::assertEquals('new method name', $new_method_name);
57 | }
58 |
59 | public function testValue()
60 | {
61 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
62 |
63 | $empty_value = $TopDiff->getValue();
64 | $empty_formatted_value = $TopDiff->getFormattedValue();
65 | self::assertEquals(0, $empty_value);
66 | self::assertEquals(0, $empty_formatted_value);
67 |
68 | $TopDiff->setValue(1.0);
69 | $new_value = $TopDiff->getValue();
70 | $new_formatted_value = $TopDiff->getFormattedValue();
71 | self::assertEquals(1.0, $new_value);
72 | self::assertEquals(1.0, $new_formatted_value);
73 | }
74 |
75 | public function testPercent()
76 | {
77 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
78 |
79 | $empty_percent = $TopDiff->getPercent();
80 | self::assertEquals(0, $empty_percent);
81 |
82 | $TopDiff->setPercent(1);
83 | $new_percent = $TopDiff->getPercent();
84 | self::assertEquals(1, $new_percent);
85 | }
86 |
87 | public function testFromValue()
88 | {
89 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
90 |
91 | $from_value = $TopDiff->getFromValue();
92 | self::assertEquals('', $from_value);
93 |
94 | $TopDiff->setFromValue(1);
95 | $new_from_value = $TopDiff->getFromValue();
96 | self::assertEquals(1, $new_from_value);
97 | }
98 |
99 | public function testToValue()
100 | {
101 | $TopDiff = new \Badoo\LiveProfilerUI\Entity\TopDiff([]);
102 |
103 | $to_value = $TopDiff->getToValue();
104 | self::assertEquals('', $to_value);
105 |
106 | $TopDiff->setToValue(1);
107 | $new_to_value = $TopDiff->getToValue();
108 | self::assertEquals(1, $new_to_value);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/templates/method_usage.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
13 |
14 |
15 | Method usage of = $data['method'] ?>
16 |
17 |
18 |
19 |
24 |
25 |
26 | = $data['error'] ?>
27 |
28 |
29 |
30 |
31 |
32 |
33 | | # |
34 | date |
35 | label |
36 | app |
37 | $field_value) { ?>
38 |
39 | = $field_name ?>
40 |
41 |
42 | |
43 |
44 |
45 |
46 |
47 |
48 |
49 | |
50 |
51 |
52 |
53 | |
54 | = $result['date'] ?> |
55 | = $result['label'] ?> |
56 | = $result['app'] ?> |
57 |
58 | = $field ?> |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
99 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/MethodData.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodDataInterface;
10 |
11 | class MethodData extends Base implements MethodDataInterface
12 | {
13 | const TABLE_NAME = 'aggregator_method_data';
14 |
15 | public function getDataBySnapshotId(int $snapshot_id) : array
16 | {
17 | $records = $this->AggregatorStorage->getAll(
18 | self::TABLE_NAME,
19 | ['all'],
20 | [
21 | 'filter' => [
22 | ['snapshot_id', $snapshot_id]
23 | ],
24 | ]
25 | );
26 |
27 | $return = [];
28 | if (!empty($records)) {
29 | foreach ($records as $record) {
30 | $return[] = new \Badoo\LiveProfilerUI\Entity\MethodData(
31 | $record,
32 | $this->FieldList->getAllFieldsWithVariations()
33 | );
34 | }
35 | }
36 |
37 | return $return;
38 | }
39 |
40 | public function getDataByMethodIdsAndSnapshotIds(
41 | array $snapshot_ids,
42 | array $method_ids,
43 | int $limit = 0,
44 | int $start_snapshot_id = 0
45 | ) : array {
46 | if (empty($snapshot_ids) && empty($method_ids)) {
47 | return [];
48 | }
49 |
50 | $filters = [];
51 | if (!empty($snapshot_ids)) {
52 | $filters[] = ['snapshot_id', $snapshot_ids];
53 | }
54 | if (!empty($method_ids)) {
55 | $filters[] = ['method_id', $method_ids];
56 | }
57 | if ($start_snapshot_id) {
58 | $filters[] = ['snapshot_id', $start_snapshot_id, '>='];
59 | }
60 |
61 | $records = $this->AggregatorStorage->getAll(
62 | self::TABLE_NAME,
63 | ['all'],
64 | [
65 | 'filter' => $filters,
66 | 'order' => ['snapshot_id' => 'desc'],
67 | 'limit' => $limit,
68 | ]
69 | );
70 |
71 | $return = [];
72 | if (!empty($records)) {
73 | foreach ($records as $record) {
74 | $return[] = new \Badoo\LiveProfilerUI\Entity\MethodData(
75 | $record,
76 | $this->FieldList->getAllFieldsWithVariations()
77 | );
78 | }
79 | }
80 |
81 | return $return;
82 | }
83 |
84 | public function getOneParamDataBySnapshotIds(array $snapshot_ids, string $param, int $threshold = 1000) : array
85 | {
86 | if (empty($snapshot_ids)) {
87 | return [];
88 | }
89 |
90 | $result = $this->AggregatorStorage->getAll(
91 | self::TABLE_NAME,
92 | ['snapshot_id', 'method_id', $param],
93 | [
94 | 'filter' => [
95 | ['snapshot_id', $snapshot_ids],
96 | [$param, $threshold, '>='],
97 | ]
98 | ]
99 | );
100 | return $result;
101 | }
102 |
103 | public function deleteBySnapshotId(int $snapshot_id) : bool
104 | {
105 | return $this->AggregatorStorage->delete(
106 | self::TABLE_NAME,
107 | ['snapshot_id' => $snapshot_id]
108 | );
109 | }
110 |
111 | public function insertMany(array $inserts) : bool
112 | {
113 | if (empty($inserts)) {
114 | return false;
115 | }
116 |
117 | return $this->AggregatorStorage->insertMany(self::TABLE_NAME, $inserts);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.canvas.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){var options={canvas:true};var render,getTextInfo,addText;var hasOwnProperty=Object.prototype.hasOwnProperty;function init(plot,classes){var Canvas=classes.Canvas;if(render==null){getTextInfo=Canvas.prototype.getTextInfo,addText=Canvas.prototype.addText,render=Canvas.prototype.render}Canvas.prototype.render=function(){if(!plot.getOptions().canvas){return render.call(this)}var context=this.context,cache=this._textCache;context.save();context.textBaseline="middle";for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layerCache=cache[layerKey];for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey],updateStyles=true;for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var info=styleCache[key],positions=info.positions,lines=info.lines;if(updateStyles){context.fillStyle=info.font.color;context.font=info.font.definition;updateStyles=false}for(var i=0,position;position=positions[i];i++){if(position.active){for(var j=0,line;line=position.lines[j];j++){context.fillText(lines[j].text,line[0],line[1])}}else{positions.splice(i--,1)}}if(positions.length==0){delete styleCache[key]}}}}}}}context.restore()};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){if(!plot.getOptions().canvas){return getTextInfo.call(this,layer,text,font,angle,width)}var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var context=this.context;if(typeof font!=="object"){var element=$("
").css("position","absolute").addClass(typeof font==="string"?font:null).appendTo(this.getTextLayer(layer));font={lineHeight:element.height(),style:element.css("font-style"),variant:element.css("font-variant"),weight:element.css("font-weight"),family:element.css("font-family"),color:element.css("color")};font.size=element.css("line-height",1).height();element.remove()}textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family;info=styleCache[text]={width:0,height:0,positions:[],lines:[],font:{definition:textStyle,color:font.color}};context.save();context.font=textStyle;var lines=(text+"").replace(/
|\r\n|\r/g,"\n").split("\n");for(var i=0;i
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Entity;
8 |
9 | class TopDiff
10 | {
11 | /** @var string */
12 | protected $app = '';
13 | /** @var string */
14 | protected $label = '';
15 | /** @var int */
16 | protected $method_id = 0;
17 | /** @var string */
18 | protected $method_name = '';
19 | /** @var float */
20 | protected $value;
21 | /** @var string */
22 | protected $from_value;
23 | /** @var string */
24 | protected $to_value;
25 | /** @var string */
26 | protected $formatted_value;
27 | /** @var float */
28 | protected $percent;
29 |
30 | public function __construct(array $data)
31 | {
32 | $this->app = isset($data['app']) ? trim($data['app']) : '';
33 | $this->label = isset($data['label']) ? trim($data['label']) : '';
34 | $this->method_id = isset($data['method_id']) ? (int)$data['method_id'] : 0;
35 | $this->value = isset($data['value']) ? (float)$data['value'] : 0.0;
36 | $this->from_value = isset($data['from_value']) ? number_format((float)$data['from_value']) : '';
37 | $this->to_value = isset($data['to_value']) ? number_format((float)$data['to_value']) : '';
38 | $this->formatted_value = number_format($this->value);
39 | $this->percent = isset($data['percent']) ? (float)$data['percent'] : 0.0;
40 | }
41 |
42 | public function getApp() : string
43 | {
44 | return $this->app;
45 | }
46 |
47 | public function setApp(string $app) : self
48 | {
49 | $this->app = $app;
50 | return $this;
51 | }
52 |
53 | public function getLabel() : string
54 | {
55 | return $this->label;
56 | }
57 |
58 | public function setLabel(string $label) : self
59 | {
60 | $this->label = $label;
61 | return $this;
62 | }
63 |
64 | public function getMethodId() : int
65 | {
66 | return $this->method_id;
67 | }
68 |
69 | public function setMethodId(int $method_id) : self
70 | {
71 | $this->method_id = $method_id;
72 | return $this;
73 | }
74 |
75 | public function getValue() : float
76 | {
77 | return $this->value;
78 | }
79 |
80 | public function getFormattedValue() : string
81 | {
82 | return $this->formatted_value;
83 | }
84 |
85 | public function setValue(float $value) : self
86 | {
87 | $this->value = $value;
88 | $this->formatted_value = number_format($this->value);
89 | return $this;
90 | }
91 |
92 | public function getFromValue() : string
93 | {
94 | return $this->from_value;
95 | }
96 |
97 | public function setFromValue(float $value) : self
98 | {
99 | $this->from_value = number_format($value);
100 | return $this;
101 | }
102 |
103 | public function getToValue() : string
104 | {
105 | return $this->to_value;
106 | }
107 |
108 | public function setToValue(float $value) : self
109 | {
110 | $this->to_value = number_format($value);
111 | return $this;
112 | }
113 |
114 | public function getPercent() : float
115 | {
116 | return $this->percent;
117 | }
118 |
119 | public function setPercent(float $percent) : self
120 | {
121 | $this->percent = $percent;
122 | return $this;
123 | }
124 |
125 | public function getMethodName() : string
126 | {
127 | return $this->method_name;
128 | }
129 |
130 | public function setMethodName(string $method_name) : self
131 | {
132 | $this->method_name = $method_name;
133 | return $this;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Entity/MethodData.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\Entity;
8 |
9 | class MethodData extends BaseEntity implements \JsonSerializable
10 | {
11 | const MAX_NAME_LENGTH = 50;
12 |
13 | /** @var int */
14 | protected $snapshot_id = 0;
15 | /** @var int */
16 | protected $method_id = 0;
17 | /** @var string */
18 | protected $method_name = '';
19 | /** @var string */
20 | protected $method_name_alt = '';
21 | /** @var array */
22 | protected $values = [];
23 | /** @var array */
24 | protected $formatted_values = [];
25 | /** @var array */
26 | protected $history_data = [];
27 |
28 | public function __construct(array $data, array $fields)
29 | {
30 | $this->snapshot_id = isset($data['snapshot_id']) ? (int)$data['snapshot_id'] : 0;
31 | $this->method_id = isset($data['method_id']) ? (int)$data['method_id'] : 0;
32 | $this->setMethodName(isset($data['method_name']) ? trim($data['method_name']) : '');
33 | $this->setValues($this->prepareValues($data, $fields));
34 | }
35 |
36 | public function getSnapshotId() : int
37 | {
38 | return $this->snapshot_id;
39 | }
40 |
41 | public function getMethodId() : int
42 | {
43 | return $this->method_id;
44 | }
45 |
46 | public function getValues() : array
47 | {
48 | return $this->values;
49 | }
50 |
51 | public function getValue(string $param) : float
52 | {
53 | return $this->values[$param] ?? 0;
54 | }
55 |
56 | public function getFormattedValue(string $param) : string
57 | {
58 | return $this->formatted_values[$param] ?? '-';
59 | }
60 |
61 | public function getFormattedValues() : array
62 | {
63 | return $this->formatted_values;
64 | }
65 |
66 | public function getMethodName() : string
67 | {
68 | return $this->method_name;
69 | }
70 |
71 | public function getMethodNameAlt() : string
72 | {
73 | return $this->method_name_alt;
74 | }
75 |
76 | public function getHistoryData() : array
77 | {
78 | return $this->history_data;
79 | }
80 |
81 | public function setMethodId(int $method_id) : self
82 | {
83 | $this->method_id = $method_id;
84 | return $this;
85 | }
86 |
87 | public function setValue(string $param, float $value) : self
88 | {
89 | $this->values[$param] = $value;
90 | $this->formatted_values = $this->prepareFormattedValues($this->values);
91 | return $this;
92 | }
93 |
94 | public function setValues(array $values) : self
95 | {
96 | $this->values = $values;
97 | $this->formatted_values = $this->prepareFormattedValues($values);
98 | return $this;
99 | }
100 |
101 | public function setMethodName(string $method_name) : self
102 | {
103 | $this->method_name_alt = $method_name;
104 | if (\strlen($method_name) > self::MAX_NAME_LENGTH) {
105 | $method_name = substr(
106 | $method_name,
107 | -self::MAX_NAME_LENGTH,
108 | self::MAX_NAME_LENGTH
109 | );
110 | }
111 | $this->method_name = $method_name;
112 |
113 | return $this;
114 | }
115 |
116 | public function setHistoryData(array $history_data) : self
117 | {
118 | $this->history_data = $history_data;
119 | return $this;
120 | }
121 |
122 | /**
123 | * Specify data which should be serialized to JSON
124 | * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
125 | * @return array data which can be serialized by json_encode,
126 | * which is a value of any type other than a resource.
127 | * @since 5.4.0
128 | */
129 | public function jsonSerialize() : array
130 | {
131 | return get_object_vars($this);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Source.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\SourceInterface;
10 | use Badoo\LiveProfilerUI\Interfaces\DataPackerInterface;
11 | use Badoo\LiveProfilerUI\Interfaces\StorageInterface;
12 |
13 | class Source implements SourceInterface
14 | {
15 | const MAX_SELECT_LIMIT = 10000;
16 | const SELECT_LIMIT = 1440;
17 | const TABLE_NAME = 'details';
18 |
19 | /** @var StorageInterface */
20 | protected $SourceStorage;
21 | /** @var DataPackerInterface */
22 | protected $DataPacker;
23 |
24 | public function __construct(StorageInterface $SourceStorage, DataPackerInterface $DataPacker)
25 | {
26 | $this->SourceStorage = $SourceStorage;
27 | $this->DataPacker = $DataPacker;
28 | }
29 |
30 | public function getSnapshotsDataByDates(string $datetime_from, string $datetime_to) : array
31 | {
32 | $snapshots = $this->SourceStorage->getAll(
33 | self::TABLE_NAME,
34 | ['app', 'label', ['field' => 'timestamp', 'function' => 'date', 'alias' => 'date']],
35 | [
36 | 'filter' => [
37 | ['label', '', '!='],
38 | ['timestamp', $datetime_from, '>='],
39 | ['timestamp', $datetime_to, '<='],
40 | ],
41 | 'group' => ['app', 'label', 'date'],
42 | ]
43 | );
44 |
45 | return $snapshots ?? [];
46 | }
47 |
48 | public function getPerfData(string $app, string $label, string $date) : array
49 | {
50 | $result = $this->SourceStorage->getAll(
51 | self::TABLE_NAME,
52 | ['id'],
53 | [
54 | 'filter' => [
55 | ['timestamp', $date . ' 00:00:00', '>='],
56 | ['timestamp', $date . ' 23:59:59', '<='],
57 | ['app', $app],
58 | ['label', $label],
59 | ],
60 | 'limit' => self::MAX_SELECT_LIMIT,
61 | ]
62 | );
63 |
64 | if (empty($result)) {
65 | return [];
66 | }
67 |
68 | // get maximum SELECT_LIMIT random records
69 | shuffle($result);
70 | $result = array_slice($result, 0, self::SELECT_LIMIT + 1);
71 |
72 | $ids = $result ? array_column($result, 'id') : [];
73 |
74 | $result = $this->SourceStorage->getAll(
75 | self::TABLE_NAME,
76 | ['perfdata'],
77 | [
78 | 'filter' => [
79 | ['id', $ids],
80 | ],
81 | 'limit' => self::SELECT_LIMIT + 1,
82 | ]
83 | );
84 |
85 | return $result ? array_column($result, 'perfdata') : [];
86 | }
87 |
88 | public function getLabelList() : array
89 | {
90 | $labels = $this->SourceStorage->getAll(
91 | self::TABLE_NAME,
92 | ['label'],
93 | [
94 | 'filter' => [
95 | ['timestamp', date('Y-m-d 00:00:00', strtotime('-1 day')), '>'],
96 | ['label', '', '!=']
97 | ],
98 | 'group' => ['label'],
99 | 'order' => ['label' => 'asc']
100 | ]
101 | );
102 |
103 | return $labels ? array_column($labels, 'label') : [];
104 | }
105 |
106 | public function getAppList() : array
107 | {
108 | $labels = $this->SourceStorage->getAll(
109 | self::TABLE_NAME,
110 | ['app'],
111 | [
112 | 'filter' => [
113 | ['timestamp', date('Y-m-d 00:00:00', strtotime('-1 day')), '>'],
114 | ],
115 | 'group' => ['app'],
116 | 'order' => ['app' => 'asc']
117 | ]
118 | );
119 |
120 | return $labels ? array_column($labels, 'app') : [];
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/FileSource.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\SourceInterface;
10 | use Badoo\LiveProfilerUI\Interfaces\DataPackerInterface;
11 |
12 | class FileSource implements SourceInterface
13 | {
14 | const SELECT_LIMIT = 1440;
15 |
16 | /** @var string */
17 | protected $path;
18 | /** @var DataPackerInterface */
19 | protected $DataPacker;
20 |
21 | public function __construct(string $path, DataPackerInterface $DataPacker)
22 | {
23 | $this->path = $path;
24 | $this->DataPacker = $DataPacker;
25 | }
26 |
27 | public function getSnapshotsDataByDates(string $datetime_from, string $datetime_to): array
28 | {
29 | $snapshots = [];
30 | $apps = $this->getAppList();
31 | foreach ($apps as $app) {
32 | $label_dirs = scandir($this->path . '/' . $app, SCANDIR_SORT_NONE);
33 | foreach ($label_dirs as $label_dir) {
34 | if ($label_dir === '.' || $label_dir === '..') {
35 | continue;
36 | }
37 | $files = scandir($this->path . '/' . $app . '/' . $label_dir, SCANDIR_SORT_NONE);
38 | foreach ($files as $file) {
39 | if ($file === '.' || $file === '..') {
40 | continue;
41 | }
42 | $timestamp = (int)str_replace('.json', '', $file);
43 | if ($timestamp >= strtotime($datetime_from) && $timestamp <= strtotime($datetime_to)) {
44 | $label = base64_decode($label_dir);
45 | $date = date('Y-m-d', $timestamp);
46 | $snapshots["$app|$label|$date"] = [
47 | 'app' => $app,
48 | 'label' => $label,
49 | 'date' => $date,
50 | ];
51 | }
52 | }
53 | }
54 | }
55 |
56 | return $snapshots;
57 | }
58 |
59 | public function getPerfData(string $app, string $label, string $date): array
60 | {
61 | $perf_data = [];
62 | $dir = $this->path . '/' . $app . '/' . base64_encode($label);
63 | if (is_dir($dir)) {
64 | $files = scandir($dir, SCANDIR_SORT_NONE);
65 | foreach ($files as $file) {
66 | if ($file === '.' || $file === '..') {
67 | continue;
68 | }
69 | $timestamp = (int)str_replace('.json', '', $file);
70 | if ($timestamp >= strtotime($date . ' 00:00:00') && $timestamp <= strtotime($date . ' 23:59:59')) {
71 | $perf_data[] = file_get_contents($dir . '/' . $file);
72 | if (count($perf_data) >= self::SELECT_LIMIT + 1) {
73 | break;
74 | }
75 | }
76 | }
77 | }
78 |
79 | return $perf_data;
80 | }
81 |
82 | public function getLabelList(): array
83 | {
84 | $labels = [];
85 | $apps = $this->getAppList();
86 | foreach ($apps as $app) {
87 | $label_dirs = scandir($this->path . '/' . $app, SCANDIR_SORT_NONE);
88 | foreach ($label_dirs as $label_dir) {
89 | if ($label_dir !== '.' && $label_dir !== '..' && !isset($labels[$label_dir])) {
90 | $label = base64_decode($label_dir);
91 | $labels[$label_dir] = $label;
92 | }
93 | }
94 | }
95 | sort($labels);
96 |
97 | return $labels;
98 | }
99 |
100 | public function getAppList(): array
101 | {
102 | $apps = [];
103 | $dirs = scandir($this->path, SCANDIR_SORT_ASCENDING);
104 | foreach ($dirs as $dir) {
105 | if ($dir !== '.' && $dir !== '..') {
106 | $apps[] = $dir;
107 | }
108 | }
109 |
110 | return $apps;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DataProviders/SourceTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DataProviders;
8 |
9 | class SourceTest extends \unit\Badoo\BaseTestCase
10 | {
11 | /** @var \Badoo\LiveProfilerUI\DB\Storage */
12 | protected $SourceStorage;
13 | protected $DataPacker;
14 |
15 | protected function setUp(): void
16 | {
17 | parent::setUp();
18 |
19 | $this->SourceStorage = $this->getMockBuilder(\Badoo\LiveProfilerUI\DB\Storage::class)
20 | ->setConstructorArgs(['sqlite:///:memory:'])
21 | ->setMethods()
22 | ->getMock();
23 |
24 | $this->SourceStorage->query(
25 | 'create table details (id integer, app text, label text, timestamp text, perfdata text)'
26 | );
27 | $this->SourceStorage->insert(
28 | 'details',
29 | [
30 | 'id' => 1,
31 | 'app' => 'app1',
32 | 'label' => 'label1',
33 | 'timestamp' => date('Y-m-d 01:00:00', strtotime('-1 day')),
34 | 'perfdata' => '1'
35 | ]
36 | );
37 | $this->SourceStorage->insert(
38 | 'details',
39 | [
40 | 'app' => 'app2',
41 | 'label' => 'label1',
42 | 'timestamp' => date('Y-m-d 02:00:00', strtotime('-1 day')),
43 | 'perfdata' => '2'
44 | ]
45 | );
46 | $this->SourceStorage->insert(
47 | 'details',
48 | [
49 | 'app' => 'app2',
50 | 'label' => 'label2',
51 | 'timestamp' => date('Y-m-d 03:00:00'),
52 | 'perfdata' => '3'
53 | ]
54 | );
55 | $this->SourceStorage->insert(
56 | 'details',
57 | [
58 | 'app' => 'app2',
59 | 'label' => 'label1',
60 | 'timestamp' => date('Y-m-d 04:00:00'),
61 | 'perfdata' => '4'
62 | ]
63 | );
64 |
65 | $this->DataPacker = new \Badoo\LiveProfilerUI\DataPacker();
66 | }
67 |
68 | public function testGetSnapshotsDataByDates()
69 | {
70 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Source($this->SourceStorage, $this->DataPacker);
71 | $result = $Source->getSnapshotsDataByDates(date('Y-m-d', strtotime('-1 day')), date('Y-m-d'));
72 |
73 | $expected = [
74 | ['app' => 'app1', 'label' => 'label1', 'date' => date('Y-m-d', strtotime('-1 day'))],
75 | ['app' => 'app2', 'label' => 'label1', 'date' => date('Y-m-d', strtotime('-1 day'))],
76 | ];
77 | self::assertEquals($expected, $result);
78 | }
79 |
80 | public function testGetPerfDataEmptyData()
81 | {
82 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Source($this->SourceStorage, $this->DataPacker);
83 | $result = $Source->getPerfData('new_app', 'new_label', date('Y-m-d', strtotime('-1 day')));
84 |
85 | $expected = [];
86 | self::assertEquals($expected, $result);
87 | }
88 |
89 | public function testGetPerfData()
90 | {
91 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Source($this->SourceStorage, $this->DataPacker);
92 | $result = $Source->getPerfData('app1', 'label1', date('Y-m-d', strtotime('-1 day')));
93 |
94 | $expected = ['1'];
95 | self::assertEquals($expected, $result);
96 | }
97 |
98 | public function testGetLabelList()
99 | {
100 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Source($this->SourceStorage, $this->DataPacker);
101 | $result = $Source->getLabelList();
102 |
103 | $expected = ['label1', 'label2'];
104 | self::assertEquals($expected, $result);
105 | }
106 |
107 | public function testGetAppList()
108 | {
109 | $Source = new \Badoo\LiveProfilerUI\DataProviders\Source($this->SourceStorage, $this->DataPacker);
110 | $result = $Source->getAppList();
111 |
112 | $expected = ['app1', 'app2'];
113 | self::assertEquals($expected, $result);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/Method.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodInterface;
10 |
11 | class Method extends Base implements MethodInterface
12 | {
13 | const MAX_METHODS_BY_NAME = 20;
14 | const TABLE_NAME = 'aggregator_metods';
15 |
16 | public function findByName(string $method_name, bool $strict = false) : array
17 | {
18 | if (!$method_name) {
19 | return [];
20 | }
21 |
22 | $name_filter = ['name', $method_name];
23 | if (!$strict) {
24 | $name_filter[] = 'like';
25 | }
26 |
27 | $result = $this->AggregatorStorage->getAll(
28 | self::TABLE_NAME,
29 | ['all'],
30 | [
31 | 'filter' => [$name_filter],
32 | 'limit' => self::MAX_METHODS_BY_NAME
33 | ]
34 | );
35 | $methods = [];
36 | if (!empty($result)) {
37 | foreach ($result as $row) {
38 | $methods[$row['id']] = $row;
39 | }
40 | }
41 |
42 | return $methods;
43 | }
44 |
45 | public function all() : array
46 | {
47 | $result = $this->AggregatorStorage->getAll(
48 | self::TABLE_NAME,
49 | ['all'],
50 | []
51 | );
52 | $methods = [];
53 | if (!empty($result)) {
54 | foreach ($result as $row) {
55 | $methods[$row['id']] = $row;
56 | }
57 | }
58 |
59 | return $methods;
60 | }
61 |
62 | public function getListByNames(array $names) : array
63 | {
64 | if (empty($names)) {
65 | return [];
66 | }
67 |
68 | $result = $this->AggregatorStorage->getAll(
69 | self::TABLE_NAME,
70 | ['all'],
71 | [
72 | 'filter' => [
73 | ['name', $names]
74 | ]
75 | ]
76 | );
77 |
78 | return $result;
79 | }
80 |
81 | public function getListByIds(array $ids) : array
82 | {
83 | if (empty($ids)) {
84 | return [];
85 | }
86 |
87 | $methods = [];
88 | while (!empty($ids)) {
89 | $ids_to_het = \array_slice($ids, 0, 500);
90 | $ids = \array_slice($ids, 500);
91 |
92 | $result = $this->AggregatorStorage->getAll(
93 | self::TABLE_NAME,
94 | ['all'],
95 | [
96 | 'filter' => [
97 | ['id', $ids_to_het]
98 | ]
99 | ]
100 | );
101 |
102 | if (!empty($result)) {
103 | foreach ($result as $method) {
104 | $methods[$method['id']] = $method['name'];
105 | }
106 | }
107 | }
108 |
109 | return $methods;
110 | }
111 |
112 | public function insertMany(array $inserts) : bool
113 | {
114 | if (empty($inserts)) {
115 | return false;
116 | }
117 |
118 | return $this->AggregatorStorage->insertMany(self::TABLE_NAME, $inserts);
119 | }
120 |
121 | public function injectMethodNames(array $data) : array
122 | {
123 | $method_ids = [];
124 | foreach ($data as $Item) {
125 | $method_ids[$Item->getMethodId()] = $Item->getMethodId();
126 | }
127 |
128 | $methods = $this->getListByIds($method_ids);
129 |
130 | if (!empty($methods)) {
131 | foreach ($data as $key => $Item) {
132 | $Item->setMethodName(
133 | isset($methods[$Item->getMethodId()])
134 | ? trim($methods[$Item->getMethodId()])
135 | : '?'
136 | );
137 | $data[$key] = $Item;
138 | }
139 | }
140 |
141 | return $data;
142 | }
143 |
144 | public function setLastUsedDate(array $ids, string $date) : bool
145 | {
146 | if (empty($ids)) {
147 | return false;
148 | }
149 |
150 | return $this->AggregatorStorage->update(
151 | self::TABLE_NAME,
152 | ['date' => $date],
153 | ['id' => $ids]
154 | );
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/templates/flame_graph.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Flame graph for = $data['snapshot_date'] ?> - = $data['snapshot_app'] ?> - = $data['snapshot_label'] ?>
5 |
8 |
9 |
10 |
11 | = $data['error'] ?>
12 |
13 |
17 |
32 |
33 |
71 |
72 |
73 | Flame graph of param diff
74 |
75 | Flame graph for = $data['snapshot_date'] ?>
76 |
77 |
78 |
79 | = $data['svg'] ?>
80 |
81 |
82 |
83 | * double click on a method cell to see the method's graphs in new tab
84 |
85 |
86 |
102 |
103 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DB/SqlTableBuilder.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DB;
8 |
9 | class SqlTableBuilder
10 | {
11 | public static function prepareCreateTables(
12 | string $db_type,
13 | string $sql,
14 | array $custom_fields,
15 | string $call_count_field
16 | ) : string {
17 | switch ($db_type) {
18 | case 'mysqli':
19 | case 'pdo_mysql':
20 | return self::mysql($sql, $custom_fields, $call_count_field);
21 | case 'pdo_pgsql':
22 | return self::pgsql($sql, $custom_fields, $call_count_field);
23 | case 'pdo_sqlite':
24 | return self::sqlite($sql, $custom_fields, $call_count_field);
25 | default:
26 | throw new \InvalidArgumentException('Not supported db type: ' . $db_type);
27 | }
28 | }
29 |
30 | public static function mysql(string $sql, array $custom_fields, string $call_count_field) : string
31 | {
32 | $snapshot_sql = '';
33 | $data_sql = '';
34 | $tree_sql = '';
35 | foreach ($custom_fields as $custom_field) {
36 | if ($custom_field === $call_count_field) {
37 | $data_sql .= "`{$custom_field}` float DEFAULT NULL,\n";
38 | $tree_sql .= "`{$custom_field}` float DEFAULT NULL,\n";
39 | } elseif (strpos($custom_field, $call_count_field) !== false) {
40 | $data_sql .= "`{$custom_field}` int(11) unsigned DEFAULT NULL,\n";
41 | $tree_sql .= "`{$custom_field}` int(11) unsigned DEFAULT NULL,\n";
42 | } else {
43 | $snapshot_sql .= "`{$custom_field}` int(11) unsigned DEFAULT NULL,\n";
44 | $data_sql .= "`{$custom_field}` int(11) unsigned DEFAULT NULL,\n";
45 | $tree_sql .= "`{$custom_field}` int(11) unsigned DEFAULT NULL,\n";
46 | }
47 | }
48 | $sql = str_replace(
49 | ['%SNAPSHOT_CUSTOM_FIELDS%', '%DATA_CUSTOM_FIELDS%', '%TREE_CUSTOM_FIELDS%'],
50 | [$snapshot_sql, $data_sql, $tree_sql],
51 | $sql
52 | );
53 | return $sql;
54 | }
55 |
56 | public static function pgsql(string $sql, array $custom_fields, string $call_count_field) : string
57 | {
58 | $snapshot_sql = [];
59 | $data_sql = [];
60 | $tree_sql = [];
61 | foreach ($custom_fields as $custom_field) {
62 | if ($custom_field === $call_count_field) {
63 | $data_sql[] = "{$custom_field} REAL DEFAULT NULL";
64 | $tree_sql[] = "{$custom_field} REAL DEFAULT NULL";
65 | } elseif (strpos($custom_field, $call_count_field) !== false) {
66 | $data_sql[] = "{$custom_field} INT DEFAULT NULL";
67 | $tree_sql[] = "{$custom_field} INT DEFAULT NULL";
68 | } else {
69 | $snapshot_sql[] = "{$custom_field} INT DEFAULT NULL";
70 | $data_sql[] = "{$custom_field} INT DEFAULT NULL";
71 | $tree_sql[] = "{$custom_field} INT DEFAULT NULL";
72 | }
73 | }
74 | $snapshot_sql = implode(",\n", $snapshot_sql);
75 | $data_sql = implode(",\n", $data_sql);
76 | $tree_sql = implode(",\n", $tree_sql);
77 |
78 | $sql = str_replace(
79 | ['%SNAPSHOT_CUSTOM_FIELDS%', '%DATA_CUSTOM_FIELDS%', '%TREE_CUSTOM_FIELDS%'],
80 | [$snapshot_sql, $data_sql, $tree_sql],
81 | $sql
82 | );
83 | return $sql;
84 | }
85 |
86 | public static function sqlite(string $sql, array $custom_fields, string $call_count_field) : string
87 | {
88 | $snapshot_sql = '';
89 | $data_sql = '';
90 | $tree_sql = '';
91 | foreach ($custom_fields as $custom_field) {
92 | if ($custom_field === $call_count_field) {
93 | $data_sql .= "{$custom_field} REAL DEFAULT NULL,\n";
94 | $tree_sql .= "{$custom_field} REAL DEFAULT NULL,\n";
95 | } elseif (strpos($custom_field, $call_count_field) !== false) {
96 | $data_sql .= "{$custom_field} INTEGER DEFAULT NULL,\n";
97 | $tree_sql .= "{$custom_field} INTEGER DEFAULT NULL,\n";
98 | } else {
99 | $snapshot_sql .= "{$custom_field} INTEGER DEFAULT NULL,\n";
100 | $data_sql .= "{$custom_field} INTEGER DEFAULT NULL,\n";
101 | $tree_sql .= "{$custom_field} INTEGER DEFAULT NULL,\n";
102 | }
103 | }
104 | $sql = str_replace(
105 | ['%SNAPSHOT_CUSTOM_FIELDS%', '%DATA_CUSTOM_FIELDS%', '%TREE_CUSTOM_FIELDS%'],
106 | [$snapshot_sql, $data_sql, $tree_sql],
107 | $sql
108 | );
109 | return $sql;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/tests/unit/Badoo/LiveProfilerUI/DB/StorageTest.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace unit\Badoo\LiveProfilerUI\DB\Adapters;
8 |
9 | class StorageTest extends \unit\Badoo\BaseTestCase
10 | {
11 | /** @var \Badoo\LiveProfilerUI\DB\Storage */
12 | protected $Storage;
13 |
14 | protected function setUp(): void
15 | {
16 | parent::setUp();
17 |
18 | $this->Storage = new \Badoo\LiveProfilerUI\DB\Storage('sqlite:///:memory:');
19 | }
20 |
21 | public function providerGetSelectQueryBuilder() : array
22 | {
23 | return [
24 | [
25 | 'fields' => ['all'],
26 | 'params' => [],
27 | 'expected' => 'SELECT * FROM details'
28 | ],
29 | [
30 | 'fields' => ['a', ['field' => 'b']],
31 | 'params' => [],
32 | 'expected' => 'SELECT a,b b FROM details'
33 | ],
34 | [
35 | 'fields' => ['all'],
36 | 'params' => ['filter' => [['a', 1]]],
37 | 'expected' => 'SELECT * FROM details WHERE a = :dcValue1'
38 | ],
39 | [
40 | 'fields' => [['field' => 'a', 'function' => 'sum', 'alias' => 'sum_a']],
41 | 'params' => [
42 | 'group' => ['a'],
43 | 'order' => ['b' => 'desc'],
44 | 'having' => [['sum_a', 1, '>'], ['sum_a', 0]],
45 | 'limit' => 10
46 | ],
47 | 'expected' => 'SELECT sum(a) sum_a FROM details GROUP BY a HAVING (sum_a > :dcValue1) AND (sum_a = :dcValue2) ORDER BY b desc LIMIT 10'
48 | ],
49 | ];
50 | }
51 |
52 | /**
53 | * @dataProvider providerGetSelectQueryBuilder
54 | * @param $fields
55 | * @param $params
56 | * @param $expected
57 | * @throws \ReflectionException
58 | */
59 | public function testGetSelectQueryBuilder(array $fields, array $params, string $expected)
60 | {
61 | $table = 'details';
62 | $QueryBuilder = $this->invokeMethod($this->Storage, 'getSelectQueryBuilder', [$table, $fields, $params]);
63 |
64 | self::assertEquals($expected, $QueryBuilder->getSql());
65 | }
66 |
67 | public function testInsertError()
68 | {
69 | $this->expectException(\Badoo\LiveProfilerUI\Exceptions\DatabaseException::class);
70 | $this->expectExceptionMessage('Can\'t insert into details');
71 | $this->Storage->insert('details', ['a' => 1]);
72 | }
73 |
74 | public function testInsertManyError()
75 | {
76 | $this->expectException(\Badoo\LiveProfilerUI\Exceptions\DatabaseException::class);
77 | $this->expectExceptionMessage('Can\'t insert into details');
78 | $this->Storage->insertMany('details', [['a' => 1]]);
79 | }
80 |
81 | public function testInsertManyEmptyData()
82 | {
83 | $this->expectException(\Badoo\LiveProfilerUI\Exceptions\InvalidFieldValueException::class);
84 | $this->expectExceptionMessage('Can\'t insert empty data');
85 | $this->Storage->insertMany('details', []);
86 | }
87 |
88 | public function testInsertManyEmptyOneOfData()
89 | {
90 | $this->expectException(\Badoo\LiveProfilerUI\Exceptions\InvalidFieldValueException::class);
91 | $this->expectExceptionMessage('Can\'t insert empty data');
92 | $this->Storage->insertMany('details', [[]]);
93 | }
94 |
95 | /**
96 | * @throws \Doctrine\DBAL\DBALException
97 | */
98 | public function testMultiQuery()
99 | {
100 | $sql = "SELECT 1;\n\nSELECT 2;";
101 | $result = $this->Storage->multiQuery($sql);
102 |
103 | self::assertTrue($result);
104 | }
105 |
106 | public function testDeleteEmptyData()
107 | {
108 | $this->expectException(\InvalidArgumentException::class);
109 | $this->expectExceptionMessage('Can\'t delete without any conditions');
110 | $this->Storage->delete('details', []);
111 | }
112 |
113 | public function testUpdateEmptyParams()
114 | {
115 | $this->expectException(\InvalidArgumentException::class);
116 | $this->expectExceptionMessage('Can\'t update without any conditions');
117 | $this->Storage->update('details', ['a' => 1], []);
118 | }
119 |
120 | public function testUpdateEmptyFields()
121 | {
122 | $this->expectException(\InvalidArgumentException::class);
123 | $this->expectExceptionMessage('Can\'t update without any fields');
124 | $this->Storage->update('details', [], ['a' => 1]);
125 | }
126 |
127 | public function testQueryError()
128 | {
129 | $result = $this->Storage->query('SELECT * FROM t;');
130 |
131 | self::assertFalse($result);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/Pages/MethodUsagePage.php:
--------------------------------------------------------------------------------
1 |
6 | */
7 |
8 | namespace Badoo\LiveProfilerUI\Pages;
9 |
10 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodInterface;
11 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodDataInterface;
12 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\SnapshotInterface;
13 | use Badoo\LiveProfilerUI\FieldList;
14 | use Badoo\LiveProfilerUI\View;
15 |
16 | class MethodUsagePage extends BasePage
17 | {
18 | /** @var string */
19 | protected static $template_path = 'method_usage';
20 | /** @var SnapshotInterface */
21 | protected $Snapshot;
22 | /** @var MethodInterface */
23 | protected $Method;
24 | /** @var MethodDataInterface */
25 | protected $MethodData;
26 | /** @var FieldList */
27 | protected $FieldList;
28 | /** @var bool */
29 | protected $use_method_usage_optimisation = false;
30 |
31 | public function __construct(
32 | View $View,
33 | SnapshotInterface $Snapshot,
34 | MethodInterface $Method,
35 | MethodDataInterface $MethodData,
36 | FieldList $FieldList,
37 | bool $use_method_usage_optimisation = false
38 | ) {
39 | $this->View = $View;
40 | $this->Snapshot = $Snapshot;
41 | $this->Method = $Method;
42 | $this->MethodData = $MethodData;
43 | $this->FieldList = $FieldList;
44 | $this->use_method_usage_optimisation = $use_method_usage_optimisation;
45 | }
46 |
47 | protected function cleanData() : bool
48 | {
49 | $this->data['method'] = isset($this->data['method']) ? trim($this->data['method']) : '';
50 |
51 | return true;
52 | }
53 |
54 | /**
55 | * @return array
56 | * @throws \InvalidArgumentException
57 | */
58 | public function getTemplateData() : array
59 | {
60 | $error = '';
61 | if (!$this->data['method']) {
62 | $error = 'Enter method name';
63 | }
64 |
65 | $method = [];
66 | if (!$error) {
67 | $this->data['method'] = ltrim($this->data['method'], '\\');
68 | $methods = $this->Method->findByName($this->data['method'], true);
69 |
70 | if (empty($methods)) {
71 | $error = 'Method "' . $this->data['method'] . '" not found';
72 | } else {
73 | $method = current($methods);
74 | }
75 | }
76 |
77 | $fields = $this->FieldList->getFields();
78 | $field_descriptions = $this->FieldList->getFieldDescriptions();
79 |
80 | $results = [];
81 | if (!empty($method)) {
82 | $start_snapshot_id = 0;
83 | if ($this->use_method_usage_optimisation) {
84 | $last_two_days = \Badoo\LiveProfilerUI\DateGenerator::getDatesArray(date('Y-m-d'), 2, 2);
85 | $start_snapshot_id = in_array($method['date'], $last_two_days, true)
86 | ? $this->Snapshot->getMinSnapshotIdByDates($last_two_days)
87 | : 0;
88 | }
89 |
90 | $method_data = $this->MethodData->getDataByMethodIdsAndSnapshotIds(
91 | [],
92 | [$method['id']],
93 | 200,
94 | $start_snapshot_id
95 | );
96 |
97 | $snapshot_ids = [];
98 | foreach ($method_data as $Row) {
99 | $snapshot_id = $Row->getSnapshotId();
100 | $snapshot_ids[$snapshot_id] = $snapshot_id;
101 | }
102 | $snapshots = $this->Snapshot->getListByIds($snapshot_ids);
103 |
104 | foreach ($method_data as $Row) {
105 | $result = [];
106 | $result['date'] = $snapshots[$Row->getSnapshotId()]['date'];
107 | $result['method_id'] = $Row->getMethodId();
108 | $result['app'] = $snapshots[$Row->getSnapshotId()]['app'];
109 | $result['label'] = $snapshots[$Row->getSnapshotId()]['label'];
110 | $values = $Row->getFormattedValues();
111 | foreach ($fields as $field) {
112 | $result['fields'][$field] = $values[$field];
113 | }
114 | $result['fields']['calls_count'] = $snapshots[$Row->getSnapshotId()]['calls_count'];
115 | $results[] = $result;
116 | }
117 |
118 | if (empty($results)) {
119 | $error = 'There is no result for ' . $this->data['method'] . '. Last time it was called on ' . $method['date'] . '.';
120 | }
121 | }
122 |
123 | return [
124 | 'method' => $this->data['method'],
125 | 'results' => $results,
126 | 'field_descriptions' => $field_descriptions,
127 | 'error' => $error
128 | ];
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/www/js/rrd/libs/flot/jquery.flot.selection.min.js:
--------------------------------------------------------------------------------
1 | /* Javascript plotting library for jQuery, version 0.8.3.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | */
7 | (function($){function init(plot){var selection={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var savedhandlers={};var mouseUpHandler=null;function onMouseMove(e){if(selection.active){updateSelection(e);plot.getPlaceholder().trigger("plotselecting",[getSelection()])}}function onMouseDown(e){if(e.which!=1)return;document.body.focus();if(document.onselectstart!==undefined&&savedhandlers.onselectstart==null){savedhandlers.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&savedhandlers.ondrag==null){savedhandlers.ondrag=document.ondrag;document.ondrag=function(){return false}}setSelectionPos(selection.first,e);selection.active=true;mouseUpHandler=function(e){onMouseUp(e)};$(document).one("mouseup",mouseUpHandler)}function onMouseUp(e){mouseUpHandler=null;if(document.onselectstart!==undefined)document.onselectstart=savedhandlers.onselectstart;if(document.ondrag!==undefined)document.ondrag=savedhandlers.ondrag;selection.active=false;updateSelection(e);if(selectionIsSane())triggerSelectedEvent();else{plot.getPlaceholder().trigger("plotunselected",[]);plot.getPlaceholder().trigger("plotselecting",[null])}return false}function getSelection(){if(!selectionIsSane())return null;if(!selection.show)return null;var r={},c1=selection.first,c2=selection.second;$.each(plot.getAxes(),function(name,axis){if(axis.used){var p1=axis.c2p(c1[axis.direction]),p2=axis.c2p(c2[axis.direction]);r[name]={from:Math.min(p1,p2),to:Math.max(p1,p2)}}});return r}function triggerSelectedEvent(){var r=getSelection();plot.getPlaceholder().trigger("plotselected",[r]);if(r.xaxis&&r.yaxis)plot.getPlaceholder().trigger("selected",[{x1:r.xaxis.from,y1:r.yaxis.from,x2:r.xaxis.to,y2:r.yaxis.to}])}function clamp(min,value,max){return valuemax?max:value}function setSelectionPos(pos,e){var o=plot.getOptions();var offset=plot.getPlaceholder().offset();var plotOffset=plot.getPlotOffset();pos.x=clamp(0,e.pageX-offset.left-plotOffset.left,plot.width());pos.y=clamp(0,e.pageY-offset.top-plotOffset.top,plot.height());if(o.selection.mode=="y")pos.x=pos==selection.first?0:plot.width();if(o.selection.mode=="x")pos.y=pos==selection.first?0:plot.height()}function updateSelection(pos){if(pos.pageX==null)return;setSelectionPos(selection.second,pos);if(selectionIsSane()){selection.show=true;plot.triggerRedrawOverlay()}else clearSelection(true)}function clearSelection(preventEvent){if(selection.show){selection.show=false;plot.triggerRedrawOverlay();if(!preventEvent)plot.getPlaceholder().trigger("plotunselected",[])}}function extractRange(ranges,coord){var axis,from,to,key,axes=plot.getAxes();for(var k in axes){axis=axes[k];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1)key=coord+"axis";if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?plot.getXAxes()[0]:plot.getYAxes()[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function setSelection(ranges,preventEvent){var axis,range,o=plot.getOptions();if(o.selection.mode=="y"){selection.first.x=0;selection.second.x=plot.width()}else{range=extractRange(ranges,"x");selection.first.x=range.axis.p2c(range.from);selection.second.x=range.axis.p2c(range.to)}if(o.selection.mode=="x"){selection.first.y=0;selection.second.y=plot.height()}else{range=extractRange(ranges,"y");selection.first.y=range.axis.p2c(range.from);selection.second.y=range.axis.p2c(range.to)}selection.show=true;plot.triggerRedrawOverlay();if(!preventEvent&&selectionIsSane())triggerSelectedEvent()}function selectionIsSane(){var minSize=plot.getOptions().selection.minSize;return Math.abs(selection.second.x-selection.first.x)>=minSize&&Math.abs(selection.second.y-selection.first.y)>=minSize}plot.clearSelection=clearSelection;plot.setSelection=setSelection;plot.getSelection=getSelection;plot.hooks.bindEvents.push(function(plot,eventHolder){var o=plot.getOptions();if(o.selection.mode!=null){eventHolder.mousemove(onMouseMove);eventHolder.mousedown(onMouseDown)}});plot.hooks.drawOverlay.push(function(plot,ctx){if(selection.show&&selectionIsSane()){var plotOffset=plot.getPlotOffset();var o=plot.getOptions();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var c=$.color.parse(o.selection.color);ctx.strokeStyle=c.scale("a",.8).toString();ctx.lineWidth=1;ctx.lineJoin=o.selection.shape;ctx.fillStyle=c.scale("a",.4).toString();var x=Math.min(selection.first.x,selection.second.x)+.5,y=Math.min(selection.first.y,selection.second.y)+.5,w=Math.abs(selection.second.x-selection.first.x)-1,h=Math.abs(selection.second.y-selection.first.y)-1;ctx.fillRect(x,y,w,h);ctx.strokeRect(x,y,w,h);ctx.restore()}});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mousedown",onMouseDown);if(mouseUpHandler)$(document).unbind("mouseup",mouseUpHandler)})}$.plot.plugins.push({init:init,options:{selection:{mode:null,color:"#e8cfac",shape:"round",minSize:5}},name:"selection",version:"1.1"})})(jQuery);
--------------------------------------------------------------------------------
/src/Badoo/LiveProfilerUI/DataProviders/MethodTree.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | namespace Badoo\LiveProfilerUI\DataProviders;
8 |
9 | use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodTreeInterface;
10 |
11 | class MethodTree extends Base implements MethodTreeInterface
12 | {
13 | const TABLE_NAME = 'aggregator_tree';
14 |
15 | public function getSnapshotMethodsTree(int $snapshot_id) : array
16 | {
17 | $records = $this->AggregatorStorage->getAll(
18 | self::TABLE_NAME,
19 | ['all'],
20 | [
21 | 'filter' => [
22 | ['snapshot_id', $snapshot_id],
23 | ]
24 | ]
25 | );
26 |
27 | $return = [];
28 | if (!empty($records)) {
29 | foreach ($records as $record) {
30 | $MethodTree = new \Badoo\LiveProfilerUI\Entity\MethodTree(
31 | $record,
32 | $this->FieldList->getAllFieldsWithVariations()
33 | );
34 | $return[$MethodTree->getParentId() . '|' . $MethodTree->getMethodId()] = $MethodTree;
35 | }
36 | }
37 |
38 | return $return;
39 | }
40 |
41 | public function getDataByMethodIdsAndSnapshotIds(array $snapshot_ids, array $method_ids) : array
42 | {
43 | if (empty($snapshot_ids) || empty($method_ids)) {
44 | return [];
45 | }
46 |
47 | $records = $this->AggregatorStorage->getAll(
48 | self::TABLE_NAME,
49 | ['all'],
50 | [
51 | 'filter' => [
52 | ['snapshot_id', $snapshot_ids],
53 | ['method_id', $method_ids],
54 | ],
55 | ]
56 | );
57 |
58 | $return = [];
59 | if (!empty($records)) {
60 | foreach ($records as $record) {
61 | $return[] = new \Badoo\LiveProfilerUI\Entity\MethodTree(
62 | $record,
63 | $this->FieldList->getAllFieldsWithVariations()
64 | );
65 | }
66 | }
67 |
68 | return $return;
69 | }
70 |
71 | public function getDataByParentIdsAndSnapshotIds(array $snapshot_ids, array $parent_ids) : array
72 | {
73 | if (empty($snapshot_ids) || empty($parent_ids)) {
74 | return [];
75 | }
76 |
77 | $records = $this->AggregatorStorage->getAll(
78 | self::TABLE_NAME,
79 | ['all'],
80 | [
81 | 'filter' => [
82 | ['snapshot_id', $snapshot_ids],
83 | ['parent_id', $parent_ids],
84 | ],
85 | ]
86 | );
87 |
88 | $return = [];
89 | if (!empty($records)) {
90 | foreach ($records as $record) {
91 | $return[] = new \Badoo\LiveProfilerUI\Entity\MethodTree(
92 | $record,
93 | $this->FieldList->getAllFieldsWithVariations()
94 | );
95 | }
96 | }
97 |
98 | return $return;
99 | }
100 |
101 | public function getSnapshotParentsData(array $snapshot_ids, array $fields = [], int $threshold = 0) : array
102 | {
103 | if (empty($snapshot_ids)) {
104 | return [];
105 | }
106 |
107 | if (empty($fields)) {
108 | $fields = $this->FieldList->getFields();
109 | }
110 |
111 | $fields_cond = ['snapshot_id', 'parent_id'];
112 | $having_cond = [];
113 | foreach ($fields as $field) {
114 | $fields_cond[] = ['field' => $field, 'function' => 'sum'];
115 | if ($threshold) {
116 | $having_cond[] = ['sum(' . $field . ')', $threshold, '>='];
117 | }
118 | }
119 |
120 | $records = $this->AggregatorStorage->getAll(
121 | self::TABLE_NAME,
122 | $fields_cond,
123 | [
124 | 'filter' => [
125 | ['snapshot_id', $snapshot_ids]
126 | ],
127 | 'group' => ['snapshot_id', 'parent_id'],
128 | 'having' => $having_cond,
129 | ]
130 | );
131 | $parent_stats = [];
132 | if (!empty($records)) {
133 | foreach ($records as $record) {
134 | foreach ($fields as $field) {
135 | $parent_stats[$record['snapshot_id']][$record['parent_id']][$field] = (int)$record[$field];
136 | }
137 | }
138 | }
139 |
140 | return $parent_stats;
141 | }
142 |
143 | public function deleteBySnapshotId(int $snapshot_id) : bool
144 | {
145 | return $this->AggregatorStorage->delete(
146 | self::TABLE_NAME,
147 | ['snapshot_id' => $snapshot_id]
148 | );
149 | }
150 |
151 | public function insertMany(array $inserts) : bool
152 | {
153 | if (empty($inserts)) {
154 | return false;
155 | }
156 |
157 | return $this->AggregatorStorage->insertMany(self::TABLE_NAME, $inserts);
158 | }
159 | }
160 |
--------------------------------------------------------------------------------