├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin ├── cli.php └── install_data │ ├── mysqli │ ├── aggregator.sql │ ├── example.sql │ ├── jobs.sql │ └── source.sql │ ├── pdo_mysql │ ├── aggregator.sql │ ├── example.sql │ ├── jobs.sql │ └── source.sql │ ├── pdo_pgsql │ ├── aggregator.sql │ ├── example.sql │ ├── jobs.sql │ └── source.sql │ └── pdo_sqlite │ ├── aggregator.sql │ ├── example.sql │ ├── jobs.sql │ └── source.sql ├── composer.json ├── docker-compose.yml ├── docker └── web │ └── Dockerfile ├── images ├── flame_graph.png ├── liveprofui_logo.png ├── method_usage.png ├── methods_list.png ├── methods_tree.png ├── methods_tree_with_info.png ├── profile_list_page_with_info.png ├── snapshots_diff_with_info.png └── top_diff_with_info.png ├── install.sh ├── phpstan.neon ├── phpunit.xml ├── scripts └── flamegraph.pl ├── src ├── Badoo │ └── LiveProfilerUI │ │ ├── .DS_Store │ │ ├── Aggregator.php │ │ ├── ConsoleCommands │ │ ├── AWeekDegradationExampleCommand.php │ │ ├── AggregateAllProfilesCommand.php │ │ ├── AggregateManualCommand.php │ │ ├── CreateAggregatingJobsCommand.php │ │ ├── InstallCommand.php │ │ ├── ProcessAggregatingJobsCommand.php │ │ └── RemoveOldProfilesCommand.php │ │ ├── DB │ │ ├── SqlTableBuilder.php │ │ ├── Storage.php │ │ └── Validators │ │ │ ├── Direction.php │ │ │ ├── Field.php │ │ │ ├── Functions.php │ │ │ ├── Operator.php │ │ │ └── Table.php │ │ ├── DataPacker.php │ │ ├── DataProviders │ │ ├── Base.php │ │ ├── FileSource.php │ │ ├── Interfaces │ │ │ ├── JobInterface.php │ │ │ ├── MethodDataInterface.php │ │ │ ├── MethodInterface.php │ │ │ ├── MethodTreeInterface.php │ │ │ ├── SnapshotInterface.php │ │ │ └── SourceInterface.php │ │ ├── Job.php │ │ ├── Method.php │ │ ├── MethodData.php │ │ ├── MethodTree.php │ │ ├── Snapshot.php │ │ └── Source.php │ │ ├── DateGenerator.php │ │ ├── Entity │ │ ├── BaseEntity.php │ │ ├── Job.php │ │ ├── MethodData.php │ │ ├── MethodTree.php │ │ ├── Snapshot.php │ │ └── TopDiff.php │ │ ├── Exceptions │ │ ├── DatabaseException.php │ │ ├── FileNotFoundException.php │ │ ├── InvalidFieldNameException.php │ │ ├── InvalidFieldValueException.php │ │ ├── InvalidFunctionNameException.php │ │ ├── InvalidOperatorException.php │ │ ├── InvalidOrderDirectionException.php │ │ └── InvalidTableNameException.php │ │ ├── FieldHandler.php │ │ ├── FieldList.php │ │ ├── FlameGraph.php │ │ ├── Interfaces │ │ ├── DataPackerInterface.php │ │ ├── FieldHandlerInterface.php │ │ ├── StorageInterface.php │ │ └── ViewInterface.php │ │ ├── LiveProfilerUI.php │ │ ├── Logger.php │ │ ├── Pages │ │ ├── AjaxPages.php │ │ ├── BasePage.php │ │ ├── FlameGraphPage.php │ │ ├── MethodUsagePage.php │ │ ├── ProfileListPage.php │ │ ├── ProfileMethodListPage.php │ │ ├── ProfileMethodTreePage.php │ │ ├── SnapshotsDiffPage.php │ │ └── TopDiffPage.php │ │ └── View.php ├── config │ └── services.yaml ├── templates │ ├── error.php │ ├── flame_graph.php │ ├── layout.php │ ├── method_usage.php │ ├── navbar.block.php │ ├── profile_list.php │ ├── profile_method_list.php │ ├── profile_method_tree.php │ ├── profiler_result_view_part.php │ ├── snapshots_diff.php │ └── top_diff.php └── www │ ├── css │ └── bootstrap3 │ │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ └── bootstrap.min.css │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js │ ├── index.php │ ├── js │ ├── jquery-3.6.0.min.js │ ├── jquery-migrate │ │ ├── jquery-migrate-1.4.1.js │ │ └── jquery-migrate-1.4.1.min.js │ ├── jquery-ui │ │ ├── css │ │ │ └── jquery-ui.css │ │ └── jquery-ui.min.js │ ├── jquery.sparkline.min.js │ └── rrd │ │ └── libs │ │ ├── flot-plugins │ │ ├── jquery.flot.axislabels.js │ │ ├── jquery.flot.dashes.js │ │ └── jquery.flot.tickrotor.js │ │ ├── flot │ │ ├── jquery.colorhelpers.min.js │ │ ├── jquery.flot.canvas.min.js │ │ ├── jquery.flot.categories.min.js │ │ ├── jquery.flot.crosshair.min.js │ │ ├── jquery.flot.errorbars.min.js │ │ ├── jquery.flot.fillbetween.min.js │ │ ├── jquery.flot.image.min.js │ │ ├── jquery.flot.min.js │ │ ├── jquery.flot.navigate.min.js │ │ ├── jquery.flot.pie.min.js │ │ ├── jquery.flot.resize.min.js │ │ ├── jquery.flot.selection.min.js │ │ ├── jquery.flot.stack.min.js │ │ ├── jquery.flot.symbol.min.js │ │ ├── jquery.flot.threshold.min.js │ │ └── jquery.flot.time.min.js │ │ └── jquery-tablesorter │ │ ├── jquery.tablesorter.min.js │ │ ├── jquery.tablesorter.pager.js │ │ ├── jquery.tablesorter.widgets.js │ │ └── theme.blue.css │ ├── profiler │ └── widget-columnSelector.js │ └── router.php └── tests ├── bootstrap.php └── unit └── Badoo ├── BaseTestCase.php └── LiveProfilerUI ├── AggregatorTest.php ├── DB ├── DBUtilsTest.php ├── StorageTest.php └── Validators │ ├── DirectionTest.php │ ├── FieldTest.php │ ├── FunctionsTest.php │ ├── OperatorTest.php │ └── TableTest.php ├── DataPackerTest.php ├── DataProviders ├── FileSourceTest.php ├── JobTest.php ├── MethodDataTest.php ├── MethodTest.php ├── MethodTreeTest.php ├── SnapshotTest.php └── SourceTest.php ├── DateGeneratorTest.php ├── Entity ├── JobTest.php ├── MethodDaraTest.php ├── MethodTreeTest.php ├── SnapshotTest.php └── TopDiffTest.php ├── FieldHandlerTest.php ├── FieldListTest.php ├── FlameGraphTest.php ├── LiveProfilerUITest.php ├── LoggerTest.php ├── Pages ├── AjaxPagesTest.php ├── FlameGraphPageTest.php ├── MethodUsagePageTest.php ├── ProfileListPageTest.php ├── ProfileMethodListPageTest.php ├── ProfileMethodTreePageTest.php ├── SnapshotsDiffPageTest.php └── TopDiffPageTest.php └── ViewTest.php /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | vendor 4 | composer.lock 5 | db_data 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /images/flame_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/flame_graph.png -------------------------------------------------------------------------------- /images/liveprofui_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/liveprofui_logo.png -------------------------------------------------------------------------------- /images/method_usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/method_usage.png -------------------------------------------------------------------------------- /images/methods_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/methods_list.png -------------------------------------------------------------------------------- /images/methods_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/methods_tree.png -------------------------------------------------------------------------------- /images/methods_tree_with_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/methods_tree_with_info.png -------------------------------------------------------------------------------- /images/profile_list_page_with_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/profile_list_page_with_info.png -------------------------------------------------------------------------------- /images/snapshots_diff_with_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/snapshots_diff_with_info.png -------------------------------------------------------------------------------- /images/top_diff_with_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/images/top_diff_with_info.png -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | composer install 3 | php bin/cli.php aggregator:install 4 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | autoload_files: 3 | - vendor/autoload.php 4 | excludes_analyse: 5 | - %rootDir%/src/templates/* 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Badoo/LiveProfilerUI/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/src/Badoo/LiveProfilerUI/.DS_Store -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/DataProviders/Job.php: -------------------------------------------------------------------------------- 1 | 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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/Badoo/LiveProfilerUI/Entity/TopDiff.php: -------------------------------------------------------------------------------- 1 | 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/Exceptions/DatabaseException.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/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/InvalidOperatorException.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Badoo\LiveProfilerUI\Exceptions; 7 | 8 | class InvalidOperatorException 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/Exceptions/InvalidTableNameException.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Badoo\LiveProfilerUI\Exceptions; 7 | 8 | class InvalidTableNameException extends \InvalidArgumentException 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /src/templates/error.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/templates/flame_graph.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Flame graph for - - 5 | 8 |

9 | 10 | 11 | 12 | 13 | 17 |
18 | 19 | Methods tree 20 | 21 | 22 | Diff interface 23 | 24 | 25 | Methods list 26 | 27 | 29 | Flame graph 30 | 31 |
32 | 33 |
34 |
35 |
36 | 37 | 44 |
45 |
46 | 47 | > 48 |
49 |
50 | 51 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 | 66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 |

Flame graph of param diff

74 | 75 |

Flame graph for

76 | 77 | 78 | 79 | 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/templates/layout.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Live profiler 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /src/templates/method_usage.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 |

15 | Method usage of 16 | 17 |

18 | 19 |
20 | 21 | 22 | 23 |
24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | $field_value) { ?> 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
#datelabelapp 39 | 40 | 41 | 42 |
50 | 51 | 52 | 53 |
64 | 65 | 66 | 99 | -------------------------------------------------------------------------------- /src/templates/navbar.block.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | 20 | 38 | -------------------------------------------------------------------------------- /src/templates/profiler_result_view_part.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | 5 | hide 6 | 7 | 8 | 9 | name 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | getMethodName() ?> 36 | 37 | 38 | 39 | getFormattedValue($param) ?> 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badoo/liveprof-ui/cb5b1c6110846cd1668b70a7c5d84df7389f0357/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/cb5b1c6110846cd1668b70a7c5d84df7389f0357/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/cb5b1c6110846cd1668b70a7c5d84df7389f0357/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/cb5b1c6110846cd1668b70a7c5d84df7389f0357/src/www/css/bootstrap3/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /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/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/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;iindex)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=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").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); -------------------------------------------------------------------------------- /src/www/js/rrd/libs/flot/jquery.flot.resize.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($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=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.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/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;m0&&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/www/router.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /tests/unit/Badoo/LiveProfilerUI/DateGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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' => "\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 | --------------------------------------------------------------------------------