├── Block └── Backend │ ├── Dashboard.php │ └── Dashboard │ └── Grid │ └── Column │ ├── Renderer │ └── Buttons.php │ └── Statuses.php ├── CHANGELOG.md ├── Controller └── Adminhtml │ └── Dashboard │ └── Index.php ├── DEVELOPMENT.md ├── LICENSE ├── Logger ├── Handler.php └── Logger.php ├── Model ├── DashboardRow.php ├── DashboardRow │ ├── AppStateMode.php │ ├── AsyncIndexes.php │ ├── CacheEnabled.php │ ├── CacheStorage.php │ ├── ComposerAutoloader.php │ ├── ConfigSetting.php │ ├── HttpVersion.php │ ├── MySQLSettings.php │ ├── NonCacheableLayouts.php │ ├── PhpSettings.php │ ├── PhpVersion.php │ └── SessionStorage.php ├── DashboardRowFactory.php ├── DashboardRowInterface.php ├── Layout │ └── LayoutPlugin.php └── ResourceModel │ └── Grid │ └── Collection.php ├── README.md ├── composer.json ├── doc ├── installation_guide.pdf ├── menu.png ├── screenshot.png └── user_guide.pdf ├── etc ├── adminhtml │ ├── menu.xml │ └── routes.xml ├── di.xml ├── frontend │ └── di.xml └── module.xml ├── registration.php └── view └── adminhtml └── layout ├── magehost_performance_dashboard_block.xml └── magehost_performance_dashboard_index.xml /Block/Backend/Dashboard.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Block\Backend; 16 | 17 | use Magento\Backend\Block\Widget\Grid\Container; 18 | 19 | /** 20 | * Class Dashboard 21 | * 22 | * Container for the dashboard status grid. 23 | * 24 | * @category MageHost 25 | * @package MageHost\PerformanceDashboard\Block\Backend 26 | * @author Jeroen Vermeulen 27 | * @license https://opensource.org/licenses/MIT MIT License 28 | * @link https://github.com/magehost/performance-dashboard 29 | */ 30 | class Dashboard extends Container 31 | { 32 | /** 33 | * Initialize object state with incoming parameters 34 | * 35 | * Running phpcs --standard=MEQP2 warns because it is protected, 36 | * like the parent class. 37 | * 38 | * Running phpcs --standard=Magento2 warns because the name is prefixed with an 39 | * underscore, but this must be the function name for the 'internal constructor', 40 | * as explained in \Magento\Framework\View\Element\AbstractBlock 41 | * 42 | * @noinspection PhpUnused 43 | * 44 | * @return void 45 | */ 46 | protected function _construct() 47 | { 48 | $this->setData('block_group', 'MageHost_PerformanceDashboard'); 49 | $this->_controller = 'Backend_Dashboard'; 50 | $this->_headerText = __('MageHost Performance Dashboard'); 51 | parent::_construct(); 52 | $this->buttonList->remove('add'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Block/Backend/Dashboard/Grid/Column/Renderer/Buttons.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Block\Backend\Dashboard\Grid\Column\Renderer; 16 | 17 | use Magento\Backend\Block\Context; 18 | use Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer; 19 | use Magento\Framework\App\ProductMetadataInterface; 20 | use Magento\Framework\DataObject; 21 | 22 | /** 23 | * Class Buttons 24 | * 25 | * @category MageHost 26 | * @package MageHost\PerformanceDashboard\Block\Backend\Dashboard\Grid\Column\Renderer 27 | * @author Jeroen Vermeulen 28 | * @license https://opensource.org/licenses/MIT MIT License 29 | * @link https://github.com/magehost/performance-dashboard 30 | */ 31 | class Buttons extends AbstractRenderer 32 | { 33 | /** 34 | * Magento application product metadata 35 | * 36 | * @var ProductMetadataInterface 37 | */ 38 | private $_productMetadata; 39 | 40 | /** 41 | * Buttons constructor. 42 | * 43 | * @param ProductMetadataInterface $productMetadata - 44 | * @param Context $context - 45 | * @param array $data - 46 | */ 47 | public function __construct( 48 | ProductMetadataInterface $productMetadata, 49 | Context $context, 50 | array $data = [] 51 | ) { 52 | $this->_productMetadata = $productMetadata; 53 | parent::__construct($context, $data); 54 | } 55 | 56 | /** 57 | * Render grid row 58 | * 59 | * @param DataObject $row The row which needs to be rendered. 60 | * 61 | * @return string 62 | */ 63 | public function render(DataObject $row) 64 | { 65 | /** @noinspection PhpUndefinedMethodInspection */ 66 | $buttons = $row->getButtons(); 67 | $buttonsHtml = []; 68 | if (!empty($buttons)) { 69 | if (!is_array($buttons) || isset($buttons['url'])) { 70 | $buttons = [$buttons]; 71 | } 72 | foreach ($buttons as $button) { 73 | if (is_string($button)) { 74 | $button = ['url'=>$button]; 75 | } 76 | $buttonsHtml[] = $this->_getButtonHtml($button); 77 | } 78 | } 79 | return implode("
\n", $buttonsHtml); 80 | } 81 | 82 | /** 83 | * Get HTML for one button / link 84 | * 85 | * @param array $button Array with data about the button 86 | * 87 | * @return string 88 | */ 89 | private function _getButtonHtml($button) 90 | { 91 | if (empty($button['url'])) { 92 | return ''; 93 | } 94 | $result = ''; 95 | $magentoVersionArray = explode( 96 | '.', 97 | $this->_productMetadata->getVersion() 98 | ); 99 | $button['url'] = str_replace( 100 | '[devdocs-guides]', 101 | sprintf( 102 | 'http://devdocs.magento.com/guides/v%d.%d', 103 | $magentoVersionArray[0], 104 | $magentoVersionArray[1] 105 | ), 106 | $button['url'] 107 | ); 108 | $button['url'] = str_replace( 109 | '[user-guides]', 110 | sprintf( 111 | 'https://docs.magento.com/%s/%s/user_guide', 112 | 'm2', 113 | 'ce' 114 | ), 115 | $button['url'] 116 | ); 117 | if (preg_match('#^https?://#', $button['url'])) { 118 | $target = empty($button['target']) ? '_blank' : $button['target']; 119 | if (empty($button['label']) 120 | && false !== strpos($button['url'], '//devdocs.magento.com/') 121 | ) { 122 | $button['label'] = 'DevDocs'; 123 | } 124 | } else { 125 | $routeParams = empty($button['url_params']) ? 126 | null : $button['url_params']; 127 | $button['url'] = $this->_urlBuilder->getUrl( 128 | $button['url'], 129 | $routeParams 130 | ); 131 | $target = empty($button['target']) ? '_top' : $button['target']; 132 | } 133 | $result .= sprintf('', $button['url'], $target); 134 | $label = empty($button['label']) ? __('Info') : $button['label']; 135 | // To show button: 136 | // 137 | $result .= sprintf( 138 | '%s', 139 | str_replace(' ', ' ', $label) 140 | ); 141 | $result .= ''; 142 | return $result; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Block/Backend/Dashboard/Grid/Column/Statuses.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Block\Backend\Dashboard\Grid\Column; 16 | 17 | use MageHost\PerformanceDashboard\Model\DashboardRow; 18 | use Magento\Backend\Block\Widget\Grid\Column; 19 | use Magento\Framework\Model\AbstractModel; 20 | 21 | /** 22 | * Class Statuses 23 | * 24 | * Column to show OK / WARNING / PROBLEM / UNKNOWN status in dashboard grid. 25 | * 26 | * @category MageHost 27 | * @package MageHost\PerformanceDashboard\Block\Backend\Dashboard\Grid\Column 28 | * @author Jeroen Vermeulen 29 | * @license https://opensource.org/licenses/MIT MIT License 30 | * @link https://github.com/magehost/performance-dashboard 31 | */ 32 | class Statuses extends Column 33 | { 34 | /** 35 | * Add to column decorated status 36 | * 37 | * @return array 38 | */ 39 | public function getFrameCallback() 40 | { 41 | return [$this, 'decorateStatus']; 42 | } 43 | 44 | /** 45 | * Decorate status column values 46 | * 47 | * @noinspection PhpUnused 48 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 49 | * 50 | * @param string $value - Column value 51 | * @param AbstractModel $row - Grid row 52 | * @param Column $column - Grid column 53 | * @param bool $isExport - If exporting 54 | * 55 | * @return string 56 | */ 57 | public function decorateStatus($value, $row, $column, $isExport) 58 | { 59 | // Extra check but mostly to get rid of phpcs warning about unused parameters 60 | if ($isExport || 'status' != $column->getId()) { 61 | return $value; 62 | } 63 | $cell = htmlentities($value); 64 | $severity = [ 65 | DashboardRow::STATUS_OK => 'notice', 66 | DashboardRow::STATUS_WARNING => 'minor', 67 | DashboardRow::STATUS_PROBLEM => 'critical', 68 | DashboardRow::STATUS_UNKNOWN => 'minor' 69 | ]; 70 | if (isset($severity[$row->getStatus()])) { 71 | $cell = sprintf( 72 | '%s', 73 | $severity[$row->getStatus()], 74 | $cell 75 | ); 76 | } else { 77 | $cell = sprintf(__("Unknown status: %s"), json_encode($value)); 78 | } 79 | return $cell; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.15.4 (2022-05-03) 2 | * Fixed Log Handler compatibility with Magento 2.4.4 3 | 4 | ### 1.15.3 (2021-08-26) 5 | 6 | * Added MySQL default settings check 7 | * Added move_script_to_bottom check 8 | * Removed merge and bundling checks since they often hurt performance 9 | 10 | ### 1.15.2 (2021-07-23) 11 | 12 | * Fixed logger path 13 | 14 | ### 1.15.1 (2021-07-15) 15 | 16 | * Made ElasticSearch check Magento 2.4 compatible 17 | 18 | ### 1.15.0 (2020-05-12) 19 | 20 | * Added ElasticSearch check 21 | * Improved DevDoc URLs 22 | * Magento2 Coding Standard 23 | 24 | ### 1.14.2 (2018-07-16) 25 | 26 | * Fixed config link on HTTP/2 check for HTTPS requirement 27 | 28 | ### 1.14.1 (2018-07-16) 29 | 30 | * Added check if frontend is HTTPS, required for HTTP/2 31 | * Fixed #6: Lowered history for logging Non Cacheable Layouts to 3 days. 32 | 33 | ### 1.14.0 (2018-07-16) 34 | 35 | * Added alternative way of checking HTTP/2 36 | 37 | ### 1.13.0 (2017-12-31) 38 | 39 | * Added check if web server is running HTTP/2 40 | * Disabled checks for JS/CSS Bundling/Merging if on HTTP/2 41 | * Fixes issue #4. Thanks to @JeroenVanLeusden for reporting. 42 | 43 | ### 1.12.3 (2017-11-01) => reverted 44 | 45 | * Changed composer install into 1.* so it doesn't get locked to minor version 46 | 47 | ### 1.12.2 (2017-11-01) 48 | 49 | * Recognise 'db' as (non optimal) session storage 50 | * Issue #3: Published to Packagist.org. Thanks @paales. 51 | * Issue #3: Improved composer instructions. Thanks @paales. 52 | 53 | ### 1.12.1 (2017-10-14) 54 | 55 | * Updated documents 56 | * Bump version for resubmit to Magento Marketplace 57 | 58 | ### 1.12.0 (2017-09-25) 59 | 60 | * Approved for Magento Marketplace, is called 1.11.4 there 61 | * Links to DevDocs now contain the actual Magento version 62 | * Magento 2.2: Removed dev/.../... config settings. Does no longer exist in production mode. 63 | * Added DevDocs link explaining Cache types 64 | * Moved ideas for future checks to [Wiki](https://github.com/magehost/performance-dashboard/wiki/Ideas-for-future-checks) 65 | * Moved Development instructions to [Wiki](https://github.com/magehost/performance-dashboard/wiki/Development) 66 | * Using Magento's wrapper functions instead of `glob()` and `file()` 67 | 68 | ### 1.11.5 (2017-09-07) 69 | 70 | * Fix error during setup:di:compile, was double dependency in Buttons Renderer 71 | 72 | ### 1.11.4 (2017-09-01) 73 | 74 | * Changed composer requirements to allow newer versions 75 | * Updated Manuals 76 | * PHPDoc type fixes for Magento 2.2 RC 77 | * Works on Magento 2.2 RC 78 | 79 | ### 1.11.0 (2017-08-24) 80 | 81 | * Solved Issue #2: Removed check for Flat Category & Product Indexes 82 | * Added DevDocs links 83 | * Added links to config, cache & index management 84 | * Split PHP Version & Configuration rows 85 | * Show current PHP Configuration 86 | 87 | ### 1.10.0 (2017-08-21) 88 | 89 | * Added check for Async Indexes 90 | * Added check for Minify HTML 91 | * Added check for Async sending of sales emails 92 | * Moved PHP check to top of list 93 | * Moved 'grouped' processing of info/problems/warnings/actions to Abstract class 94 | 95 | ### 1.9.0 (2017-08-21) 96 | 97 | * Added check for PHP Version and Settings 98 | * Fixed some interface strings that were not translatable 99 | 100 | ### 1.8.0 (2017-08-21) 101 | 102 | * Added check if Composer's autoloader is optimized 103 | * Used constants instead of status 0-3 104 | 105 | ### 1.7.1 (2017-08-21) 106 | 107 | * Fixed issue #1 - Monolog error on Magento 2.1.8 108 | 109 | ### 1.7.0 (2017-08-09) 110 | 111 | * Added check if Varnish FPC is enabled 112 | * Config data checks can now use a source model 113 | * Updated installation instructions 114 | 115 | ### 1.6.2 (2017-08-09) 116 | 117 | * Updated installation instructions 118 | 119 | ### 1.6.1 (2017-08-08) 120 | 121 | * First version submitted to the Magento Marketplace 122 | * Improved documentation 123 | * Improved Composer requirements 124 | * Tested with Magento 2.0.14 and 2.1.7 125 | 126 | ### 1.6.0 (2017-08-08) 127 | 128 | * Replaced find+grep on layouts by frontend logger. … 129 | 130 | ### 1.5.8 (2017-07-29) 131 | 132 | * Increased truncate size of info + action 133 | 134 | ### 1.5.7 (2017-07-29) 135 | 136 | * Fixed bugs in 'Non Cacheable Templates' 137 | 138 | ### 1.5.5 (2017-07-20) 139 | 140 | * Code improved based on: phpcs --standard=MEQP2 141 | 142 | ### 1.5.1 (2017-07-28) 143 | 144 | * Removed PHP version dependency 145 | 146 | ### 1.5.0 (2017-07-28) 147 | 148 | * Improved PHPDOC 149 | * fixed PHPMD warnings. 150 | 151 | ### 1.4.0 (2017-07-27) 152 | 153 | * Restructured extension files. 154 | 155 | ### 1.3.0 (2017-07-27) 156 | 157 | * First version with all functions working 158 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Dashboard/Index.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Controller\Adminhtml\Dashboard; 16 | 17 | use Magento\Backend\App\Action; 18 | use Magento\Backend\App\Action\Context; 19 | use Magento\Framework\View\Result\Page; 20 | use Magento\Framework\View\Result\PageFactory; 21 | 22 | /** 23 | * Performance Dashboard Index Controller 24 | * 25 | * @category MageHost 26 | * @package MageHost\PerformanceDashboard\Controller\Adminhtml\Dashboard 27 | * @author Jeroen Vermeulen 28 | * @license https://opensource.org/licenses/MIT MIT License 29 | * @link https://github.com/magehost/performance-dashboard 30 | */ 31 | class Index extends Action 32 | { 33 | /** 34 | * Controller for the overview page of the Performance Dashboard 35 | * 36 | * @var PageFactory 37 | */ 38 | private $_resultPageFactory; 39 | 40 | /** 41 | * Constructor 42 | * 43 | * @param Context $context - 44 | * @param PageFactory $resultPageFactory - 45 | */ 46 | public function __construct( 47 | Context $context, 48 | PageFactory $resultPageFactory 49 | ) { 50 | 51 | parent::__construct($context); 52 | $this->_resultPageFactory = $resultPageFactory; 53 | } 54 | 55 | /** 56 | * Load the page defined in 57 | * view/adminhtml/layout/magehost_performance_dashboard_index.xml 58 | * 59 | * @return Page 60 | */ 61 | public function execute() 62 | { 63 | return $this->_resultPageFactory->create(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | See [Wiki](https://github.com/magehost/performance-dashboard/wiki/Development) 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 MageHost BV 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 | -------------------------------------------------------------------------------- /Logger/Handler.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Logger; 16 | 17 | use Magento\Framework\Exception\FileSystemException; 18 | use Magento\Framework\Filesystem\DirectoryList; 19 | use Magento\Framework\Filesystem\Glob; 20 | use Monolog\Handler\RotatingFileHandler; 21 | 22 | /** 23 | * Class Handler 24 | * 25 | * Log handler creating rotating logs. 26 | * We use it to log detected performance problems in the frontend. 27 | * 28 | * @category MageHost 29 | * @package MageHost\PerformanceDashboard\Logger 30 | * @author Jeroen Vermeulen 31 | * @license https://opensource.org/licenses/MIT MIT License 32 | * @link https://github.com/magehost/performance-dashboard 33 | */ 34 | class Handler extends RotatingFileHandler 35 | { 36 | /** 37 | * Application file system directories dictionary. 38 | * 39 | * @var DirectoryList 40 | */ 41 | private $_directoryList; 42 | 43 | /** 44 | * Constructor 45 | * 46 | * @param DirectoryList $directoryList Application file system directories 47 | * dictionary. 48 | * @param string $filename @inheritdoc 49 | * @param int $maxFiles @inheritdoc 50 | * @param int $level @inheritdoc 51 | * @param bool $bubble @inheritdoc 52 | * @param int|null $filePermission @inheritdoc 53 | * @param bool $useLocking @inheritdoc 54 | */ 55 | public function __construct( 56 | DirectoryList $directoryList, 57 | $filename, 58 | $maxFiles = 0, 59 | $level = Logger::DEBUG, 60 | $bubble = true, 61 | $filePermission = null, 62 | $useLocking = false 63 | ) { 64 | $this->_directoryList = $directoryList; 65 | parent::__construct( 66 | $filename, 67 | $maxFiles, 68 | $level, 69 | $bubble, 70 | $filePermission, 71 | $useLocking 72 | ); 73 | } 74 | 75 | /** 76 | * Receive currently stored log files 77 | * 78 | * @return array 79 | */ 80 | public function getLogFiles() 81 | { 82 | if (strpos($this->filename, $this->_directoryList->getPath('log')) === false) { 83 | // Fix dir location 84 | $this->filename = sprintf( 85 | "%s/%s", 86 | $this->_directoryList->getPath('log'), 87 | basename($this->filename) 88 | ); 89 | } 90 | return Glob::glob($this->getGlobPattern()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Logger/Logger.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Logger; 16 | 17 | /** 18 | * Class Logger 19 | * 20 | * We need this class to be able to use our own log handler. 21 | * 22 | * @category MageHost 23 | * @package MageHost\PerformanceDashboard\Logger 24 | * @author Jeroen Vermeulen 25 | * @license https://opensource.org/licenses/MIT MIT License 26 | * @link https://github.com/magehost/performance-dashboard 27 | */ 28 | class Logger extends \Monolog\Logger 29 | { 30 | // Fix for phpcs --standard=MEQP2 warning 31 | const I_AM_NOT_EMPTY = true; 32 | } 33 | -------------------------------------------------------------------------------- /Model/DashboardRow.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model; 17 | 18 | use Magento\Framework\DataObject; 19 | 20 | /** 21 | * Class DashboardRow 22 | * 23 | * @category MageHost 24 | * @package MageHost\PerformanceDashboard\Model 25 | * @author Jeroen Vermeulen 26 | * @license https://opensource.org/licenses/MIT MIT License 27 | * @link https://github.com/magehost/performance-dashboard 28 | */ 29 | abstract class DashboardRow extends DataObject implements 30 | DashboardRowInterface 31 | { 32 | const STATUS_OK = 0; 33 | const STATUS_WARNING = 1; 34 | const STATUS_PROBLEM = 2; 35 | const STATUS_UNKNOWN = 3; 36 | 37 | public $problems = ''; 38 | public $warnings = ''; 39 | public $info = ''; 40 | public $actions = ''; 41 | public $buttons = []; 42 | 43 | public function groupProcess() 44 | { 45 | if ($this->problems) { 46 | $this->setStatus(self::STATUS_PROBLEM); 47 | } elseif ($this->warnings) { 48 | $this->setStatus(self::STATUS_WARNING); 49 | } else { 50 | $this->setStatus(self::STATUS_OK); 51 | }; 52 | $this->setInfo($this->problems . $this->warnings . $this->info); 53 | $this->setAction($this->actions); 54 | if ($this->getButtons()) { 55 | $existingButtons = $this->getButtons(); 56 | if (!is_array($existingButtons) || isset($existingButtons['url'])) { 57 | $existingButtons = [$existingButtons]; 58 | } 59 | $this->buttons = array_merge($this->buttons, $existingButtons); 60 | } 61 | $this->setButtons($this->buttons); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Model/DashboardRow/AppStateMode.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use MageHost\PerformanceDashboard\Model\DashboardRow; 19 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 20 | use Magento\Framework\App\State; 21 | 22 | /** 23 | * Class AppStateMode 24 | * 25 | * Dashboard row to show Magento Mode: developer / production / default 26 | * 27 | * @category MageHost 28 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 29 | * @author Jeroen Vermeulen 30 | * @license https://opensource.org/licenses/MIT MIT License 31 | * @link https://github.com/magehost/performance-dashboard 32 | */ 33 | class AppStateMode extends DashboardRow implements DashboardRowInterface 34 | { 35 | /** 36 | * Application state flags. 37 | * 38 | * @var State 39 | */ 40 | private $_appState; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param State $appState - Application state flags 46 | * @param array $data - Data for object 47 | */ 48 | public function __construct( 49 | State $appState, 50 | array $data = [] 51 | ) { 52 | 53 | $this->_appState = $appState; 54 | parent::__construct($data); 55 | } 56 | 57 | /** 58 | * Load Row, is called by DashboardRowFactory 59 | * 60 | * @return void 61 | */ 62 | public function load() 63 | { 64 | $appMode = $this->_appState->getMode(); 65 | $this->setTitle('Magento Mode'); 66 | $this->setButtons( 67 | '[devdocs-guides]/config-guide/cli/config-cli-subcommands-mode.html' . 68 | '#production-mode' 69 | ); 70 | 71 | $this->setInfo(sprintf(__("Magento is running in '%s' mode"), $appMode)); 72 | if (State::MODE_PRODUCTION == $appMode) { 73 | $this->setStatus(self::STATUS_OK); 74 | } else { 75 | $this->setStatus(self::STATUS_PROBLEM); 76 | $this->setAction(__("Switch to Production Mode")); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Model/DashboardRow/AsyncIndexes.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use MageHost\PerformanceDashboard\Model\DashboardRow; 19 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 20 | use Magento\Framework\App\Config\ScopeConfigInterface; 21 | use Magento\Framework\App\ProductMetadataInterface; 22 | use Magento\Indexer\Model\Indexer\Collection; 23 | 24 | /** 25 | * Class AsyncIndexes 26 | * 27 | * Dashboard row to check if Async Indexes are enabled 28 | * 29 | * @category MageHost 30 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 31 | * @author Jeroen Vermeulen 32 | * @license https://opensource.org/licenses/MIT MIT License 33 | * @link https://github.com/magehost/performance-dashboard 34 | */ 35 | class AsyncIndexes extends DashboardRow implements DashboardRowInterface 36 | { 37 | /** 38 | * Collection of indexers 39 | * 40 | * @var Collection 41 | */ 42 | private $_indexerCollection; 43 | 44 | /** 45 | * Magento application config 46 | * 47 | * @var ScopeConfigInterface $_scopeConfig 48 | */ 49 | private $_scopeConfig; 50 | 51 | /** 52 | * Magento application product metadata 53 | * 54 | * @var ProductMetadataInterface $_productMetadata 55 | */ 56 | private $_productMetadata; 57 | 58 | /** 59 | * Constructor. 60 | * 61 | * @param Collection $indexerCollection - 62 | * @param ScopeConfigInterface $scopeConfig - 63 | * @param ProductMetadataInterface $productMetadata - 64 | * @param array $data - 65 | */ 66 | public function __construct( 67 | Collection $indexerCollection, 68 | ScopeConfigInterface $scopeConfig, 69 | ProductMetadataInterface $productMetadata, 70 | array $data = [] 71 | ) { 72 | $this->_indexerCollection = $indexerCollection; 73 | $this->_scopeConfig = $scopeConfig; 74 | $this->_productMetadata = $productMetadata; 75 | parent::__construct($data); 76 | } 77 | 78 | /** 79 | * Load Row, is called by DashboardRowFactory 80 | * 81 | * @return void 82 | */ 83 | public function load() 84 | { 85 | $this->setTitle(__("Asynchronous Indexing")); 86 | 87 | if (version_compare( 88 | $this->_productMetadata->getVersion(), 89 | '2.2.0.dev', 90 | '<' 91 | ) 92 | ) { 93 | if (!$this->_scopeConfig->getValue('dev/grid/async_indexing')) { 94 | $this->warnings .= __("'Asynchronous indexing' is not enabled") . 95 | "\n"; 96 | $this->actions .= __("Switch to 'Enabled' in Default Config") . 97 | "\n"; 98 | $this->buttons[] = [ 99 | 'label' => __('Default Config'), 100 | 'url' => 'adminhtml/system_config/edit/section/dev', 101 | 'url_params' => ['_fragment' => 'dev_grid-link'] 102 | ]; 103 | } 104 | } 105 | 106 | if (empty($this->warnings)) { 107 | foreach ($this->_indexerCollection->getItems() as $indexer) { 108 | if (!$indexer->isScheduled()) { 109 | $this->warnings .= sprintf( 110 | __("%s Index is set to 'Update on Save'") . "\n", 111 | $indexer->getTitle() 112 | ); 113 | $this->actions .= sprintf( 114 | __("Switch to 'Update on Schedule'") . "\n", 115 | $indexer->getTitle() 116 | ); 117 | } 118 | } 119 | if ($this->warnings) { 120 | $this->buttons[] = [ 121 | 'label' => 'Index Management', 122 | 'url' => 'indexer/indexer/list' 123 | ]; 124 | } 125 | } 126 | $this->buttons[] 127 | = '[devdocs-guides]/performance-best-practices/configuration.html' . 128 | '#indexers'; 129 | 130 | $this->groupProcess(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Model/DashboardRow/CacheEnabled.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use MageHost\PerformanceDashboard\Model\DashboardRow; 19 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 20 | use Magento\Framework\App\Cache\TypeListInterface; 21 | 22 | /** 23 | * Class CacheEnabled 24 | * 25 | * Dashboard row to check if all caches are enabled. 26 | * 27 | * @category MageHost 28 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 29 | * @author Jeroen Vermeulen 30 | * @license https://opensource.org/licenses/MIT MIT License 31 | * @link https://github.com/magehost/performance-dashboard 32 | */ 33 | class CacheEnabled extends DashboardRow implements DashboardRowInterface 34 | { 35 | /** 36 | * List of Magento application caches 37 | * 38 | * @var TypeListInterface 39 | */ 40 | private $_cacheTypeList; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param TypeListInterface $cacheTypeList - 46 | * @param array $data - 47 | */ 48 | public function __construct( 49 | TypeListInterface $cacheTypeList, 50 | array $data = [] 51 | ) { 52 | $this->_cacheTypeList = $cacheTypeList; 53 | parent::__construct($data); 54 | } 55 | 56 | /** 57 | * Load Row, is called by DashboardRowFactory 58 | * 59 | * @return void 60 | */ 61 | public function load() 62 | { 63 | $this->setTitle('Cache Enabled'); 64 | $this->buttons[] = [ 65 | 'label' => __('Cache Management'), 66 | 'url' => 'adminhtml/cache/index' 67 | ]; 68 | $this->buttons[] = [ 69 | 'url' => 70 | '[devdocs-guides]/config-guide/cli/config-cli-subcommands-cache.html' 71 | . '#config-cli-subcommands-cache-clean-over' 72 | ]; 73 | 74 | foreach ($this->_cacheTypeList->getTypes() as $type) { 75 | if (! $type->getStatus()) { 76 | $this->problems .= sprintf( 77 | __('Cache is disabled: %s')."\n", 78 | $type->getCacheType() 79 | ); 80 | $this->actions .= sprintf( 81 | __("Enable %s cache")."\n", 82 | $type->getCacheType() 83 | ); 84 | } 85 | } 86 | if (empty($this->actions)) { 87 | $this->info .= __('All cache is enabled')."\n"; 88 | } 89 | 90 | $this->groupProcess(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Model/DashboardRow/CacheStorage.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use MageHost\PerformanceDashboard\Model\DashboardRow; 19 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 20 | use Magento\Framework\App\Cache\Frontend\Pool; 21 | 22 | /** 23 | * Class CacheStorage 24 | * 25 | * Dashboard row to check if optimal cache storage is used. 26 | * 27 | * @category MageHost 28 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 29 | * @author Jeroen Vermeulen 30 | * @license https://opensource.org/licenses/MIT MIT License 31 | * @link https://github.com/magehost/performance-dashboard 32 | */ 33 | class CacheStorage extends DashboardRow implements DashboardRowInterface 34 | { 35 | /** 36 | * In-memory readonly pool of all cache front-end instances known to the system 37 | * 38 | * @var Pool 39 | */ 40 | private $_cacheFrontendPool; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * Expects $data keys 'identifier' and 'name' to be set. 46 | * 47 | * @param Pool $cacheFrontendPool - 48 | * @param array $data - 49 | */ 50 | public function __construct( 51 | Pool $cacheFrontendPool, 52 | array $data 53 | ) { 54 | $this->_cacheFrontendPool = $cacheFrontendPool; 55 | parent::__construct($data); 56 | } 57 | 58 | /** 59 | * Load Row, is called by DashboardRowFactory 60 | * 61 | * @return void 62 | */ 63 | public function load() 64 | { 65 | $this->setTitle(sprintf(__('%s Storage'), $this->getName())); 66 | $currentBackend = $this->_cacheFrontendPool->get( 67 | $this->getIdentifier() 68 | )->getBackend(); 69 | $currentBackendClass = get_class($currentBackend); 70 | $this->setInfo(sprintf(__('%s'), $currentBackendClass)); 71 | if (is_a($currentBackend, 'Cm_Cache_Backend_Redis')) { 72 | $this->setStatus(self::STATUS_OK); 73 | } elseif ('Zend_Cache_Backend_File' == $currentBackendClass) { 74 | $this->setStatus(self::STATUS_PROBLEM); 75 | $this->setAction( 76 | sprintf(__('%s is slow!'), $currentBackendClass) . "\n" . 77 | sprintf( 78 | __('Store in Redis using Cm_Cache_Backend_Redis'), 79 | $this->getName() 80 | ) 81 | ); 82 | } elseif (is_a($currentBackend, 'Cm_Cache_Backend_File')) { 83 | $this->setStatus(self::STATUS_WARNING); 84 | $this->setAction( 85 | sprintf( 86 | __('Store in Redis using Cm_Cache_Backend_Redis'), 87 | $this->getName() 88 | ) 89 | ); 90 | } else { 91 | $this->setStatus(self::STATUS_UNKNOWN); 92 | $this->setInfo( 93 | sprintf( 94 | __("Unknown cache storage: '%s'"), 95 | get_class($currentBackend) 96 | ) 97 | ); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Model/DashboardRow/ComposerAutoloader.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use Composer\Autoload\ClassLoader; 19 | use MageHost\PerformanceDashboard\Model\DashboardRow; 20 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 21 | 22 | /** 23 | * Class ComposerAutoloader 24 | * 25 | * Dashboard row to show if the Composer Autoloader is optimized 26 | * 27 | * @category MageHost 28 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 29 | * @author Jeroen Vermeulen 30 | * @license https://opensource.org/licenses/MIT MIT License 31 | * @link https://github.com/magehost/performance-dashboard 32 | */ 33 | class ComposerAutoloader extends DashboardRow implements DashboardRowInterface 34 | { 35 | /** 36 | * Load Row, is called by DashboardRowFactory 37 | * 38 | * @return void 39 | */ 40 | public function load() 41 | { 42 | $this->setTitle(__("Composer autoloader")); 43 | $this->setButtons( 44 | '[devdocs-guides]/performance-best-practices/deployment-flow.html' . 45 | '#preprocess-dependency-injection-instructions' 46 | ); 47 | 48 | /** 49 | * Find the \Composer\Autoload\ClassLoader class among the autoloaders 50 | * 51 | * @noinspection PhpUndefinedClassInspection 52 | * @var null|ClassLoader $classLoader 53 | */ 54 | $classLoader = null; 55 | foreach (spl_autoload_functions() as $function) { 56 | 57 | if (is_array($function) 58 | && $function[0] instanceof ClassLoader 59 | ) { 60 | $classLoader = $function[0]; 61 | break; 62 | } 63 | } 64 | 65 | if (empty($classLoader)) { 66 | $this->setStatus(self::STATUS_UNKNOWN); 67 | $this->setInfo(__("Could not find Composer AutoLoader.")); 68 | return; 69 | } 70 | 71 | if (array_key_exists( 72 | \Magento\Config\Model\Config::class, 73 | $classLoader->getClassMap() 74 | )) { 75 | $this->setStatus(self::STATUS_OK); 76 | $this->setInfo(__("Composer's autoloader is optimized")); 77 | } else { 78 | $this->setStatus(self::STATUS_PROBLEM); 79 | $this->setInfo(__("Composer's autoloader is not optimized.")); 80 | $this->setAction(__("Execute: 'composer dump-autoload -o --apcu'")); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Model/DashboardRow/ConfigSetting.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use InvalidArgumentException; 19 | use MageHost\PerformanceDashboard\Model\DashboardRow; 20 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 21 | use Magento\Config\Model\Config\SourceFactory; 22 | use Magento\Framework\App\Config\ScopeConfigInterface; 23 | use Magento\Framework\Phrase; 24 | use Magento\Store\Api\Data\StoreInterface; 25 | use Magento\Store\Api\Data\WebsiteInterface; 26 | use Magento\Store\Model\ScopeInterface; 27 | use Magento\Store\Model\StoreManagerInterface; 28 | 29 | /** 30 | * Class ConfigSetting 31 | * 32 | * Dashboard rows to check optimal config settings. 33 | * 34 | * @category MageHost 35 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 36 | * @author Jeroen Vermeulen 37 | * @license https://opensource.org/licenses/MIT MIT License 38 | * @link https://github.com/magehost/performance-dashboard 39 | */ 40 | class ConfigSetting extends DashboardRow implements DashboardRowInterface 41 | { 42 | /** 43 | * @var ScopeConfigInterface 44 | */ 45 | private $scopeConfig; 46 | 47 | /** 48 | * @var StoreManagerInterface 49 | */ 50 | private $storeManager; 51 | 52 | /** 53 | * @var SourceFactory 54 | */ 55 | private $sourceFactory; 56 | 57 | /** 58 | * Constructor. 59 | * 60 | * Expects data[] keys 'title', 'path' and 'recommended' to be set 61 | * 62 | * @param ScopeConfigInterface $scopeConfig 63 | * @param StoreManagerInterface $storeManager 64 | * @param SourceFactory $sourceFactory 65 | * @param array $data 66 | */ 67 | public function __construct( 68 | ScopeConfigInterface $scopeConfig, 69 | StoreManagerInterface $storeManager, 70 | SourceFactory $sourceFactory, 71 | array $data 72 | ) { 73 | $this->scopeConfig = $scopeConfig; 74 | $this->storeManager = $storeManager; 75 | $this->sourceFactory = $sourceFactory; 76 | parent::__construct($data); 77 | } 78 | 79 | /** 80 | * Load Row, is called by DashboardRowFactory 81 | */ 82 | public function load() 83 | { 84 | $defaultResult = $this->checkConfigSetting($this->getPath(), $this->getRecommended()); 85 | 86 | $pathParts = explode('/', $this->getPath()); 87 | if (0 < $defaultResult['status']) { 88 | $this->warnings .= $defaultResult['info'] . "\n"; 89 | $this->actions .= $defaultResult['action'] . "\n"; 90 | $this->buttons[] = [ 91 | 'label' => __('Default Config'), 92 | 'url' => sprintf('adminhtml/system_config/edit/section/%s', $pathParts[0]), 93 | 'url_params' => [ '_fragment'=> sprintf('%s_%s-link', $pathParts[0], $pathParts[1]) ] 94 | ]; 95 | } 96 | /** 97 | * @var WebsiteInterface $website 98 | */ 99 | foreach ($this->storeManager->getWebsites() as $website) { 100 | $websiteResult = $this->checkConfigSetting($this->getPath(), $this->getRecommended(), $website); 101 | if ($websiteResult['status'] > $defaultResult['status']) { 102 | $this->warnings .= $websiteResult['info'] . "\n"; 103 | $this->actions .= $websiteResult['action'] . "\n"; 104 | $this->buttons[] = [ 105 | 'label' => sprintf(__('%s Config'), $website->getName()), 106 | 'url' => sprintf( 107 | 'adminhtml/system_config/edit/section/%s/website/%s', 108 | $pathParts[0], 109 | $website->getId() 110 | ), 111 | 'url_params' => [ '_fragment'=> sprintf('%s_%s-link', $pathParts[0], $pathParts[1]) ] 112 | ]; 113 | } 114 | foreach ($this->storeManager->getStores() as $store) { 115 | if ($store->getWebsiteId() == $website->getId()) { 116 | $storeResult = $this->checkConfigSetting($this->getPath(), $this->getRecommended(), $store); 117 | if ($storeResult['status'] > $websiteResult['status']) { 118 | $this->warnings .= $storeResult['info'] . "\n"; 119 | $this->actions .= $storeResult['action'] . "\n"; 120 | $this->buttons[] = [ 121 | 'label' => sprintf(__('%s Config'), $store->getName()), 122 | 'url' => sprintf( 123 | 'adminhtml/system_config/edit/section/%s/store/%s', 124 | $pathParts[0], 125 | $store->getId() 126 | ), 127 | 'url_params' => [ '_fragment'=> sprintf('%s_%s-link', $pathParts[0], $pathParts[1]) ] 128 | ]; 129 | } 130 | } 131 | } 132 | } 133 | 134 | if (empty($this->actions)) { 135 | $this->info .= $defaultResult['info'] . "\n"; 136 | } 137 | $this->groupProcess(); 138 | } 139 | 140 | /** 141 | * Check a config setting for a specific scope 142 | * 143 | * @param string $path 144 | * @param mixed $recommended 145 | * @param string|null $scope -- null = default scope 146 | * @return array 147 | */ 148 | private function checkConfigSetting( 149 | $path, 150 | $recommended, 151 | $scope = null 152 | ) { 153 | $result = []; 154 | 155 | if (null === $scope) { 156 | $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT; 157 | $scopeCode = null; 158 | $showScope = __('in Default Config'); 159 | } elseif ($scope instanceof WebsiteInterface) { 160 | $scopeType = ScopeInterface::SCOPE_WEBSITE; 161 | $scopeCode = $scope->getCode(); 162 | $showScope = sprintf(__("for website '%s'"), $scope->getName()); 163 | } elseif ($scope instanceof StoreInterface) { 164 | $scopeType = ScopeInterface::SCOPE_STORE; 165 | $scopeCode = $scope->getCode(); 166 | $showScope = sprintf(__("for store '%s'"), $scope->getName()); 167 | } else { 168 | $result['status'] = self::STATUS_UNKNOWN; 169 | $result['info'] = sprintf(__("Unknown scope")); 170 | return $result; 171 | } 172 | 173 | $result['value'] = $this->scopeConfig->getValue($path, $scopeType, $scopeCode); 174 | 175 | $result['info'] = sprintf( 176 | __("'%s' %s"), 177 | ucfirst($this->getShowValue($result['value'], $recommended)), 178 | $showScope 179 | ); 180 | if ($recommended == $result['value']) { 181 | $result['status'] = self::STATUS_OK; 182 | } else { 183 | $result['status'] = self::STATUS_WARNING; 184 | $result['action'] = sprintf( 185 | __("Switch to '%s' %s"), 186 | ucfirst($this->getShowValue($recommended, $recommended)), 187 | $showScope 188 | ); 189 | } 190 | 191 | return $result; 192 | } 193 | 194 | /** 195 | * Format a value to show in frontend 196 | * 197 | * @param mixed $value 198 | * @param mixed $recommended 199 | * @return Phrase|string 200 | * @throws InvalidArgumentException 201 | */ 202 | private function getShowValue($value, $recommended) 203 | { 204 | if (is_bool($recommended)) { 205 | $showValue = $value ? __('enabled') : __('disabled'); 206 | } elseif (is_string($recommended) || is_int($recommended)) { 207 | $showValue = $value; 208 | } else { 209 | throw new InvalidArgumentException('Unsupported type of recommended value'); 210 | } 211 | if ($this->getSource()) { 212 | $sourceModel = $this->sourceFactory->create($this->getSource()); 213 | $sourceArray = $sourceModel->toOptionArray(); 214 | foreach ($sourceArray as $item) { 215 | if ($item['value'] == $showValue && !empty($item['label'])) { 216 | $showValue = $item['label']; 217 | break; 218 | } 219 | } 220 | } 221 | return $showValue; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Model/DashboardRow/HttpVersion.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use Exception; 19 | use MageHost\PerformanceDashboard\Model\DashboardRow; 20 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 21 | use Magento\Framework\App\RequestInterface; 22 | use Magento\Framework\Exception\NoSuchEntityException; 23 | use Magento\Store\Model\StoreManagerInterface; 24 | use Psr\Log\LoggerInterface; 25 | 26 | /** 27 | * Class HttpVersion 28 | * 29 | * Dashboard row to check HTTP version 30 | * 31 | * @category MageHost 32 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 33 | * @author Jeroen Vermeulen 34 | * @license https://opensource.org/licenses/MIT MIT License 35 | * @link https://github.com/magehost/performance-dashboard 36 | */ 37 | class HttpVersion extends DashboardRow implements DashboardRowInterface 38 | { 39 | /** 40 | * @var RequestInterface 41 | */ 42 | private $request; 43 | 44 | /** 45 | * @var StoreManagerInterface 46 | */ 47 | private $storeManager; 48 | 49 | /** 50 | * @var LoggerInterface 51 | */ 52 | private $logger; 53 | 54 | public function __construct( 55 | RequestInterface $request, 56 | StoreManagerInterface $storeManager, 57 | LoggerInterface $logger, 58 | array $data 59 | ) { 60 | $this->request = $request; 61 | $this->storeManager = $storeManager; 62 | $this->logger = $logger; 63 | parent::__construct($data); 64 | } 65 | 66 | /** 67 | * Load Row, is called by DashboardRowFactory 68 | * 69 | * @throws NoSuchEntityException 70 | */ 71 | public function load() 72 | { 73 | $this->setTitle(__('HTTP Version')); 74 | $this->setButtons('https://css-tricks.com/http2-real-world-performance-test-analysis/'); 75 | 76 | $frontUrl = $this->storeManager->getStore()->getBaseUrl('link', false); 77 | if (preg_match('|^http://|', $frontUrl)) { 78 | $this->setStatus(self::STATUS_PROBLEM); 79 | $this->setInfo(sprintf(__("Your frontend is not HTTPS")."\n")); 80 | $this->setAction( 81 | __( 82 | "Update 'Base URL' to use HTTPS.\n". 83 | "This is required for HTTP/2\n" 84 | ) 85 | ); 86 | $this->setButtons( 87 | [ 88 | 'label' => __('Default Config'), 89 | 'url' => 'adminhtml/system_config/edit/section/web', 90 | 'url_params' => [ '_fragment'=> 'web_unsecure-link' ] 91 | ] 92 | ); 93 | return; 94 | } 95 | 96 | $httpVersion = $this->getHttpVersion(); 97 | if (null === $httpVersion) { 98 | $this->setStatus(self::STATUS_UNKNOWN); 99 | $this->setInfo( 100 | __( 101 | "Could not check if you are running HTTP/2\n". 102 | "\$_SERVER['SERVER_PROTOCOL'] may be missing.\n" 103 | ) 104 | ); 105 | } elseif (floatval($httpVersion) >= 2) { 106 | $this->setStatus(self::STATUS_OK); 107 | $this->setInfo(sprintf(__("HTTP Version: %s\n"), $httpVersion)); 108 | } else { 109 | $this->setStatus(self::STATUS_PROBLEM); 110 | $this->setInfo(sprintf(__("Your connection is HTTP %s")."\n", $httpVersion)); 111 | $this->setAction( 112 | __( 113 | "Upgrade to a web server supporting HTTP/2.\n". 114 | "Check if HTTP/2 is enabled in your server config.\n" 115 | ) 116 | ); 117 | } 118 | } 119 | 120 | /** 121 | * @return string|null 122 | * @throws NoSuchEntityException 123 | */ 124 | public function getHttpVersion() 125 | { 126 | // We are looking for HTTP/2 or higher. 127 | // The $_SERVER['SERVER_PROTOCOL'] is the first place to look. 128 | // We assume if the current request on the Admin runs on HTTP/2, the frontend does too. 129 | 130 | $serverProtocol = $this->request->getServerValue('SERVER_PROTOCOL'); 131 | if (!empty($serverProtocol)) { 132 | $versionSplit = explode('/', $serverProtocol); 133 | $version = $versionSplit[1]; 134 | if (floatval($version) >= 2) { 135 | return $version; 136 | } 137 | } 138 | 139 | // However, the webserver may be behind a reverse proxy. 140 | // If the reverse proxy is talking HTTP/2 to the client we are still happy. 141 | // It doesn't matter if the internal connection to the webserver is HTTP/1. 142 | 143 | return $this->getHttpVersionUsingRequest(); 144 | } 145 | 146 | /** 147 | * @return string|null 148 | * @throws NoSuchEntityException 149 | */ 150 | public function getHttpVersionUsingRequest() 151 | { 152 | // We will use Curl to do a HEAD request to the frontend using HTTP/2. 153 | // This will not work when you are debugging using XDebug because it can't handle 2 requests a the same time. 154 | 155 | $frontUrl = $this->storeManager->getStore()->getBaseUrl(); 156 | 157 | try { 158 | if (!defined('CURL_HTTP_VERSION_2_0')) { 159 | define('CURL_HTTP_VERSION_2_0', 3); 160 | } 161 | // magento-coding-standard discourages use of Curl but it is the best way to check for HTTP/2. 162 | $curl = curl_init(); 163 | curl_setopt_array( 164 | $curl, 165 | [ 166 | CURLOPT_URL => $frontUrl, 167 | CURLOPT_NOBODY => true, 168 | CURLOPT_HEADER => true, 169 | CURLOPT_RETURNTRANSFER => true, 170 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, // Enable HTTP/2 in the request 171 | CURLOPT_SSL_VERIFYPEER => false, 172 | CURLOPT_SSL_VERIFYHOST => false, 173 | CURLOPT_CONNECTTIMEOUT => 2, // seconds 174 | CURLOPT_TIMEOUT => 10 // seconds 175 | ] 176 | ); 177 | $httpResponse = curl_exec($curl); 178 | curl_close($curl); 179 | } catch (Exception $e) { 180 | $msg = sprintf("%s: Error fetching '%s': %s", __CLASS__, $frontUrl, $e->getMessage()); 181 | $this->logger->info($msg); 182 | } 183 | 184 | if (!empty($httpResponse)) { 185 | $responseHeaders = explode("\r\n", $httpResponse); 186 | $version = null; 187 | foreach ($responseHeaders as $header) { 188 | if (preg_match('|^HTTP/([\d\.]+)|', $header, $matches)) { 189 | $version = $matches[1]; 190 | break; 191 | } 192 | } 193 | if (empty($version) || floatval($version) < 2) { 194 | foreach ($responseHeaders as $header) { 195 | if (preg_match('|^Upgrade: h([\d\.]+)|', $header, $matches)) { 196 | $version = $matches[1]; 197 | break; 198 | } 199 | } 200 | } 201 | return $version; 202 | } 203 | 204 | return null; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Model/DashboardRow/MySQLSettings.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2019 MageHost BV (https://magehost.pro) 13 | * @license https://opensource.org/licenses/MIT MIT License 14 | * @link https://github.com/magehost/performance-dashboard 15 | */ 16 | 17 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 18 | 19 | use MageHost\PerformanceDashboard\Model\DashboardRow; 20 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 21 | use Magento\Framework\App\ResourceConnection; 22 | 23 | /** 24 | * Class MySQLSettings 25 | * 26 | * Dashboard row to check MySQL configuration 27 | * 28 | * @category MageHost 29 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 30 | * @author Jeroen Vermeulen 31 | * @license https://opensource.org/licenses/MIT MIT License 32 | * @link https://github.com/magehost/performance-dashboard 33 | */ 34 | class MySQLSettings extends DashboardRow implements DashboardRowInterface 35 | { 36 | /** 37 | * @var ResourceConnection 38 | */ 39 | private $resourceConnection; 40 | 41 | 42 | /** 43 | * @var int[] 44 | */ 45 | private $defaultValues 46 | = [ 47 | 'innodb_buffer_pool_size' => 134217728, 48 | 'max_connections' => 150, 49 | 'innodb_thread_concurrency' => 0, 50 | ]; 51 | 52 | /** 53 | * MySQLSettings constructor. 54 | * 55 | * @param ResourceConnection $resourceConnection 56 | * @param array $data 57 | */ 58 | public function __construct( 59 | ResourceConnection $resourceConnection, 60 | array $data 61 | ) { 62 | $this->resourceConnection = $resourceConnection; 63 | parent::__construct($data); 64 | } 65 | 66 | /** 67 | * Load Row, is called by DashboardRowFactory 68 | */ 69 | public function load() 70 | { 71 | /** @noinspection PhpUndefinedMethodInspection */ 72 | $this->setTitle(__('MySQL Configuration')); 73 | $this->buttons[] = '[devdocs-guides]/performance-best-practices/software.html' . 74 | '#mysql'; 75 | 76 | $connection = $this->resourceConnection->getConnection(); 77 | $info = ''; 78 | foreach ($this->defaultValues as $key => $value) { 79 | $currentValue = $connection->fetchRow('SHOW VARIABLES LIKE \'' . $key . '\''); 80 | if (is_array($currentValue) == false) continue; 81 | if ($currentValue['Value'] <= $value) { 82 | $this->problems .= $key . ' lower than - or equal to the default value: ' 83 | . $currentValue['Value']; 84 | $this->actions .= 'Ask your hosting to tune ' . $key; 85 | } 86 | $info .= $key . ' = ' . $currentValue['Value'] . "\n"; 87 | } 88 | 89 | if ($this->problems == '') { 90 | $this->info = $info; 91 | } 92 | 93 | $this->groupProcess(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Model/DashboardRow/NonCacheableLayouts.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use MageHost\PerformanceDashboard\Logger\Handler; 19 | use MageHost\PerformanceDashboard\Model\DashboardRow; 20 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 21 | use Magento\Framework\Filesystem\DirectoryList; 22 | use Magento\Framework\Filesystem\DriverPool; 23 | use Magento\Framework\Filesystem\File\ReadFactory; 24 | 25 | /** 26 | * Class NonCacheableLayouts 27 | * 28 | * Dashboard row to show if non cacheable layouts were detected in the frontend. 29 | * 30 | * @category MageHost 31 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 32 | * @author Jeroen Vermeulen 33 | * @license https://opensource.org/licenses/MIT MIT License 34 | * @link https://github.com/magehost/performance-dashboard 35 | */ 36 | class NonCacheableLayouts extends DashboardRow implements DashboardRowInterface 37 | { 38 | /** 39 | * @var DirectoryList 40 | */ 41 | private $directoryList; 42 | 43 | /** 44 | * @var Handler 45 | */ 46 | private $logHandler; 47 | 48 | /** 49 | * @var ReadFactory 50 | */ 51 | private $readFactory; 52 | 53 | /** 54 | * Constructor. 55 | * 56 | * @param DirectoryList $directoryList 57 | * @param Handler $logHandler 58 | * @param ReadFactory $readFactory 59 | * @param array $data 60 | */ 61 | public function __construct( 62 | DirectoryList $directoryList, 63 | Handler $logHandler, 64 | ReadFactory $readFactory, 65 | array $data = [] 66 | ) { 67 | $this->directoryList = $directoryList; 68 | $this->logHandler = $logHandler; 69 | $this->readFactory = $readFactory; 70 | parent::__construct($data); 71 | } 72 | 73 | /** 74 | * Load Row, is called by DashboardRowFactory 75 | */ 76 | public function load() 77 | { 78 | $this->setTitle('Non Cacheable Layouts'); 79 | $this->setButtons('[devdocs-guides]/frontend-dev-guide/cache_for_frontdevs.html#cache-over-cacheable'); 80 | 81 | $output = ''; 82 | $logFiles = $this->logHandler->getLogFiles(); 83 | $now = time(); 84 | foreach ($logFiles as $logFile) { 85 | $fileMatches = []; 86 | preg_match('/-(\d\d\d\d-\d\d-\d\d)\./', $logFile, $fileMatches); 87 | if (empty($fileMatches[1])) { 88 | continue; 89 | } 90 | $date = $fileMatches[1]; 91 | if ($now - strtotime($date) > 3 * 86400) { 92 | // Older than 3 days 93 | continue; 94 | } 95 | $output .= $this->processLogFile($date, $logFile); 96 | } 97 | 98 | if (empty($output)) { 99 | $this->setInfo(__('Collecting data from frontend, no problems found (yet).')); 100 | $this->setStatus(self::STATUS_OK); 101 | } else { 102 | $this->setInfo($output); 103 | $this->setAction( 104 | __("Search in frontend layout XML for: cacheable=\"false\"") 105 | ); 106 | $this->setStatus(self::STATUS_PROBLEM); 107 | } 108 | return $this; 109 | } 110 | 111 | /** 112 | * Process one logfile 113 | * 114 | * @param $date string 115 | * @param $logFile string 116 | * @return string 117 | */ 118 | private function processLogFile($date, $logFile) 119 | { 120 | $output = ''; 121 | $moduleCount = []; 122 | $fileReader = $this->readFactory->create($logFile, DriverPool::FILE); 123 | $logLines = explode("\n", $fileReader->readAll()); 124 | $fileReader->close(); 125 | foreach ($logLines as $line) { 126 | $lineMatches = []; 127 | if (preg_match('/ non_cacheable_layout (\{.+\})(?:\s|$)/', $line, $lineMatches)) { 128 | $data = json_decode($lineMatches[1], true); 129 | if (empty($data['Md'])) { 130 | continue; 131 | } 132 | if (!isset($moduleCount[$data['Md']])) { 133 | $moduleCount[$data['Md']] = 0; 134 | } 135 | $moduleCount[$data['Md']]++; 136 | } 137 | } 138 | foreach ($moduleCount as $module => $count) { 139 | $output .= sprintf(__("%s: non cacheable %s page loads: %d\n"), $date, $module, $count); 140 | } 141 | return $output; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Model/DashboardRow/PhpSettings.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 16 | 17 | use MageHost\PerformanceDashboard\Model\DashboardRow; 18 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 19 | 20 | /** 21 | * Class PhpSettings 22 | * 23 | * Dashboard row to check PHP configuration 24 | * 25 | * @category MageHost 26 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 27 | * @author Jeroen Vermeulen 28 | * @license https://opensource.org/licenses/MIT MIT License 29 | * @link https://github.com/magehost/performance-dashboard 30 | */ 31 | class PhpSettings extends DashboardRow implements DashboardRowInterface 32 | { 33 | private $exactValues = [ 34 | 'opcache.enable_cli' => 1, 35 | 'opcache.save_comments' => 1, 36 | // opcache.validate_timestamps = 0 // If you have very fast disk access you don't need this. 37 | 'opcache.consistency_checks' => 0 38 | ]; 39 | 40 | private $minimalValues = [ 41 | 'opcache.memory_consumption' => 512, 42 | 'opcache.max_accelerated_files' => 100000 43 | ]; 44 | 45 | /** 46 | * Load Row, is called by DashboardRowFactory 47 | */ 48 | public function load() 49 | { 50 | /** @noinspection PhpUndefinedMethodInspection */ 51 | $this->setTitle(__('PHP Configuration')); 52 | $this->buttons[] = '[devdocs-guides]/install-gde/prereq/php-settings.html' . 53 | '#php-required-opcache'; 54 | 55 | foreach ($this->exactValues as $key => $value) { 56 | $curValue = ini_get($key); 57 | if (false === $curValue) { 58 | $this->problems .= $this->getProblem($key, $curValue); 59 | $this->actions .= sprintf(__("Set PHP ini setting '%s' to '%s'")."\n", $key, $value); 60 | } elseif (ini_get($key) != $value) { 61 | $this->problems .= $this->getProblem($key, $curValue); 62 | $this->actions .= sprintf(__("Change '%s' to '%s'")."\n", $key, $value); 63 | } 64 | $this->info .= sprintf(__("%s = %s")."\n", $key, $curValue); 65 | } 66 | foreach ($this->minimalValues as $key => $value) { 67 | $curValue = ini_get($key); 68 | if (false === $curValue) { 69 | $this->problems .= $this->getProblem($key, $curValue); 70 | $this->actions .= sprintf(__("Set PHP setting '%s' to '%s' or higher")."\n", $key, $value); 71 | } elseif (ini_get($key) < $value) { 72 | $this->problems .= $this->getProblem($key, $curValue); 73 | $this->actions .= sprintf(__("Change '%s' to '%s' or higher")."\n", $key, $value); 74 | } 75 | $this->info .= sprintf(__("%s = %s")."\n", $key, $curValue); 76 | } 77 | 78 | $this->groupProcess(); 79 | } 80 | 81 | private function getProblem($key, $curValue) 82 | { 83 | if (false === $curValue) { 84 | return sprintf(__("'%s' is not set")."\n", $key); 85 | } else { 86 | return sprintf(__("'%s' is not optimal: '%s'")."\n", $key, $curValue); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Model/DashboardRow/PhpVersion.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use MageHost\PerformanceDashboard\Model\DashboardRow; 19 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 20 | 21 | /** 22 | * Class PhpVersion 23 | * 24 | * Dashboard row to check PHP version 25 | * 26 | * @category MageHost 27 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 28 | * @author Jeroen Vermeulen 29 | * @license https://opensource.org/licenses/MIT MIT License 30 | * @link https://github.com/magehost/performance-dashboard 31 | */ 32 | class PhpVersion extends DashboardRow implements DashboardRowInterface 33 | { 34 | /** 35 | * Load Row, is called by DashboardRowFactory 36 | */ 37 | public function load() 38 | { 39 | $this->setTitle(__('PHP Version')); 40 | $this->setButtons( 41 | '[devdocs-guides]/install-gde/system-requirements-tech.html#php' 42 | ); 43 | 44 | $phpVersionSplit = explode('-', PHP_VERSION, 2); 45 | $showVersion = reset($phpVersionSplit); 46 | if (version_compare(PHP_VERSION, '7.0.0', '>=')) { 47 | $this->setStatus(self::STATUS_OK); 48 | $this->setInfo(sprintf(__("PHP Version: %s\n"), $showVersion)); 49 | } else { 50 | $this->setStatus(self::STATUS_PROBLEM); 51 | $this->setInfo(sprintf(__("PHP Version %s is older than 7.0")."\n", $showVersion)); 52 | $this->setAction(__("Upgrade to PHP 7.0 or higher")."\n"); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Model/DashboardRow/SessionStorage.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | * @noinspection PhpUndefinedMethodInspection 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\DashboardRow; 17 | 18 | use MageHost\PerformanceDashboard\Model\DashboardRow; 19 | use MageHost\PerformanceDashboard\Model\DashboardRowInterface; 20 | use Magento\Framework\App\DeploymentConfig; 21 | use Magento\Framework\Session\Config; 22 | use Magento\Framework\Session\SaveHandlerInterface; 23 | 24 | /** 25 | * Class SessionStorage 26 | * 27 | * Dashboard row to check if optimal session storage is used. 28 | * 29 | * @category MageHost 30 | * @package MageHost\PerformanceDashboard\Model\DashboardRow 31 | * @author Jeroen Vermeulen 32 | * @license https://opensource.org/licenses/MIT MIT License 33 | * @link https://github.com/magehost/performance-dashboard 34 | */ 35 | class SessionStorage extends DashboardRow implements DashboardRowInterface 36 | { 37 | /** 38 | * @var DeploymentConfig 39 | */ 40 | private $deploymentConfig; 41 | 42 | /** 43 | * Constructor 44 | * 45 | * @param DeploymentConfig $deploymentConfig 46 | * @param array $data 47 | */ 48 | public function __construct( 49 | DeploymentConfig $deploymentConfig, 50 | array $data = [] 51 | ) { 52 | $this->deploymentConfig = $deploymentConfig; 53 | parent::__construct($data); 54 | } 55 | 56 | /** 57 | * Load Row, is called by DashboardRowFactory 58 | */ 59 | public function load() 60 | { 61 | $this->setTitle('Session Storage'); 62 | $this->setButtons('[devdocs-guides]/config-guide/redis/redis-session.html'); 63 | 64 | /** 65 | * @see \Magento\Framework\Session\SaveHandler::__construct() 66 | */ 67 | $defaultSaveHandler = ini_get('session.save_handler') ?: 68 | SaveHandlerInterface::DEFAULT_HANDLER; 69 | $saveHandler = $this->deploymentConfig->get( 70 | Config::PARAM_SESSION_SAVE_METHOD, 71 | $defaultSaveHandler 72 | ); 73 | 74 | switch ($saveHandler) { 75 | case 'redis': 76 | case 'memcache': 77 | case 'memcached': 78 | $this->setStatus(self::STATUS_OK); 79 | $this->setInfo(sprintf(__('Sessions are saved in %s'), ucfirst($saveHandler))); 80 | break; 81 | case 'files': 82 | case 'db': 83 | $this->setStatus(self::STATUS_PROBLEM); 84 | $this->setInfo(sprintf(__('Sessions are saved in %s'), ucfirst($saveHandler))); 85 | $this->setAction('Save sessions in Redis or Memcached'); 86 | break; 87 | default: 88 | $this->setInfo(sprintf(__('Unknown session save handler: %s'), $saveHandler)); 89 | $this->setStatus(self::STATUS_UNKNOWN); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Model/DashboardRowFactory.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Model; 16 | 17 | use Magento\Framework\DataObject; 18 | use Magento\Framework\ObjectManagerInterface; 19 | use UnexpectedValueException; 20 | 21 | /** 22 | * Class DashboardRowFactory 23 | * 24 | * Factory for dashboard row classes. 25 | * 26 | * @category MageHost 27 | * @package MageHost\PerformanceDashboard\Model 28 | * @author Jeroen Vermeulen 29 | * @license https://opensource.org/licenses/MIT MIT License 30 | * @link https://github.com/magehost/performance-dashboard 31 | */ 32 | class DashboardRowFactory 33 | { 34 | /** 35 | * We need the ObjectManager because this is a factory class 36 | * 37 | * @var ObjectManagerInterface 38 | */ 39 | private $objectManager; 40 | 41 | /** 42 | * Constructor 43 | * 44 | * @param ObjectManagerInterface $objectManager 45 | */ 46 | public function __construct(ObjectManagerInterface $objectManager) 47 | { 48 | $this->objectManager = $objectManager; 49 | } 50 | 51 | /** 52 | * Get DashboardRow Model 53 | * 54 | * @param string $instanceName 55 | * @param array $data 56 | * @return DataObject 57 | * @throws UnexpectedValueException 58 | */ 59 | public function create($instanceName, array $data = []) 60 | { 61 | $instanceName = 'MageHost\PerformanceDashboard\Model\DashboardRow\\' . 62 | $instanceName; 63 | $instance = $this->objectManager->create($instanceName, ['data'=>$data]); 64 | if (!$instance instanceof DashboardRowInterface) { 65 | throw new UnexpectedValueException( 66 | "Row class '{$instanceName}' has to be a Dashboard Row." 67 | ); 68 | } 69 | $instance->load(); 70 | return $instance; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Model/DashboardRowInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Model; 16 | 17 | /** 18 | * Interface DashboardRowInterface 19 | * 20 | * @category MageHost 21 | * @package MageHost\PerformanceDashboard\Model 22 | * @author Jeroen Vermeulen 23 | * @license https://opensource.org/licenses/MIT MIT License 24 | * @link https://github.com/magehost/performance-dashboard 25 | */ 26 | interface DashboardRowInterface 27 | { 28 | public function load(); 29 | } 30 | -------------------------------------------------------------------------------- /Model/Layout/LayoutPlugin.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | namespace MageHost\PerformanceDashboard\Model\Layout; 16 | 17 | use MageHost\PerformanceDashboard\Logger\Logger; 18 | use Magento\Framework\App\RequestInterface; 19 | use Magento\Framework\Filesystem\DirectoryList; 20 | use Magento\Framework\View\Layout; 21 | use Magento\PageCache\Model\Config; 22 | 23 | /** 24 | * Class LayoutPlugin 25 | * 26 | * Frontend interceptor to check if a non cacheable layout was used. 27 | * 28 | * @category MageHost 29 | * @package MageHost\PerformanceDashboard\Model\Layout 30 | * @author Jeroen Vermeulen 31 | * @license https://opensource.org/licenses/MIT MIT License 32 | * @link https://github.com/magehost/performance-dashboard 33 | */ 34 | class LayoutPlugin 35 | { 36 | /** 37 | * @var Config 38 | */ 39 | private $config; 40 | 41 | /** 42 | * @var DirectoryList 43 | */ 44 | private $directoryList; 45 | 46 | /** 47 | * @var RequestInterface 48 | */ 49 | private $request; 50 | 51 | /** 52 | * @var Logger 53 | */ 54 | private $logger; 55 | 56 | /** 57 | * @var array 58 | */ 59 | private $cacheableModules = ['cms','catalog']; 60 | 61 | const LOG_PREFIX = 'mh_noncacheable'; 62 | 63 | /** 64 | * Constructor. 65 | * 66 | * @param Config $config 67 | * @param RequestInterface $request 68 | * @param DirectoryList $directoryList 69 | * @param Logger $logger 70 | */ 71 | public function __construct( 72 | Config $config, 73 | RequestInterface $request, 74 | DirectoryList $directoryList, 75 | Logger $logger 76 | ) { 77 | $this->config = $config; 78 | $this->request = $request; 79 | $this->directoryList = $directoryList; 80 | $this->logger = $logger; 81 | } 82 | 83 | /** 84 | * @param Layout $subject 85 | * @param mixed $result 86 | * @return mixed 87 | */ 88 | public function afterGenerateXml(Layout $subject, $result) 89 | { 90 | $module = $this->request->getModuleName(); 91 | if (!$subject->isCacheable() && in_array($module, $this->cacheableModules)) { 92 | $data = [ 93 | 'V' => 1, // Record version, for forward compatibility 94 | 'Md' => $this->request->getModuleName(), 95 | 'Ct' => $this->request->getControllerName(), 96 | 'Ac' => $this->request->getActionName(), 97 | 'Rt' => $this->request->getRouteName() 98 | ]; 99 | $this->logger->info('non_cacheable_layout', $data); 100 | } 101 | return $result; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Model/ResourceModel/Grid/Collection.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2019 MageHost BV (https://magehost.pro) 12 | * @license https://opensource.org/licenses/MIT MIT License 13 | * @link https://github.com/magehost/performance-dashboard 14 | */ 15 | 16 | namespace MageHost\PerformanceDashboard\Model\ResourceModel\Grid; 17 | 18 | use MageHost\PerformanceDashboard\Model\DashboardRowFactory; 19 | use Magento\Framework\App\Config\ScopeConfigInterface; 20 | use Magento\Framework\App\ProductMetadataInterface; 21 | use Magento\Framework\Data\Collection\EntityFactory; 22 | use Magento\PageCache\Model\Config; 23 | use Psr\Log\LoggerInterface; 24 | 25 | /** 26 | * Class Collection 27 | * 28 | * Data provider for dashboard grid. 29 | * 30 | * @category MageHost 31 | * @package MageHost\PerformanceDashboard\Model\ResourceModel\Grid 32 | * @author Jeroen Vermeulen 33 | * @license https://opensource.org/licenses/MIT MIT License 34 | * @link https://github.com/magehost/performance-dashboard 35 | */ 36 | class Collection extends \Magento\Framework\Data\Collection 37 | { 38 | /** 39 | * @var DashboardRowFactory 40 | */ 41 | private $rowFactory; 42 | 43 | /** 44 | * @var LoggerInterface 45 | */ 46 | private $logger; 47 | 48 | /** 49 | * @var ScopeConfigInterface 50 | */ 51 | private $scopeConfig; 52 | 53 | /** 54 | * @var ProductMetadataInterface 55 | */ 56 | private $productMetadata; 57 | 58 | /** 59 | * Constructor 60 | * 61 | * @param EntityFactory $entityFactory 62 | * @param DashboardRowFactory $rowFactory 63 | * @param ScopeConfigInterface $scopeConfig 64 | * @param ProductMetadataInterface $productMetadata 65 | * @param LoggerInterface $logger 66 | */ 67 | public function __construct( 68 | EntityFactory $entityFactory, 69 | DashboardRowFactory $rowFactory, 70 | ScopeConfigInterface $scopeConfig, 71 | ProductMetadataInterface $productMetadata, 72 | LoggerInterface $logger 73 | ) { 74 | $this->rowFactory = $rowFactory; 75 | $this->scopeConfig = $scopeConfig; 76 | $this->logger = $logger; 77 | $this->productMetadata = $productMetadata; 78 | parent::__construct($entityFactory); 79 | } 80 | 81 | /** 82 | * Load data 83 | * 84 | * @param bool $printQuery 85 | * @param bool $logQuery 86 | * @return $this 87 | * @throws \Exception 88 | * @throws \UnexpectedValueException 89 | */ 90 | public function loadData($printQuery = false, $logQuery = false) 91 | { 92 | if (!$this->isLoaded()) { 93 | if ($printQuery || $logQuery) { 94 | $this->logger->debug( 95 | sprintf( 96 | "%s::%s does not get its data from direct database queries," . 97 | "it is gathered from several internal Magento objects and logging.", 98 | __CLASS__, 99 | __FUNCTION__ 100 | ) 101 | ); 102 | } 103 | /** @noinspection PhpUnhandledExceptionInspection */ 104 | $this->addItem($this->rowFactory->create('PhpVersion')); 105 | /** @noinspection PhpUnhandledExceptionInspection */ 106 | $this->addItem($this->rowFactory->create('PhpSettings')); 107 | /** @noinspection PhpUnhandledExceptionInspection */ 108 | $this->addItem($this->rowFactory->create('MySQLSettings')); 109 | /** @noinspection PhpUnhandledExceptionInspection */ 110 | $this->addItem($this->rowFactory->create('AppStateMode')); 111 | /** @noinspection PhpUnhandledExceptionInspection */ 112 | $this->addItem($this->rowFactory->create('HttpVersion')); 113 | $this->addItemsCache(); 114 | $this->addItemsSearch(); 115 | /** @noinspection PhpUnhandledExceptionInspection */ 116 | $this->addItem($this->rowFactory->create('ComposerAutoloader')); 117 | $this->addItemsConfig(); 118 | /** @noinspection PhpUnhandledExceptionInspection */ 119 | $this->addItem($this->rowFactory->create('AsyncIndexes')); 120 | $this->_setIsLoaded(true); 121 | } 122 | return $this; 123 | } 124 | 125 | /** 126 | * Add Cache / Session related dashboard items 127 | * 128 | * @throws \Exception 129 | * @throws UnexpectedValueException 130 | */ 131 | private function addItemsCache() 132 | { 133 | $this->addItem( 134 | $this->rowFactory->create( 135 | 'CacheStorage', 136 | [ 137 | 'identifier' => 'default', 138 | 'name' => 'Magento Cache', 139 | 'buttons' => '[devdocs-guides]/config-guide/redis/redis-pg-cache.html' 140 | ] 141 | ) 142 | ); 143 | if (Config::BUILT_IN == $this->scopeConfig->getValue('system/full_page_cache/caching_application')) { 144 | $this->addItem( 145 | $this->rowFactory->create( 146 | 'CacheStorage', 147 | [ 148 | 'identifier' => 'page_cache', 149 | 'name' => 'Full Page Cache', 150 | 'buttons' => '[devdocs-guides]/config-guide/redis/redis-pg-cache.html' 151 | ] 152 | ) 153 | ); 154 | } 155 | $this->addItem($this->rowFactory->create('CacheEnabled')); 156 | $this->addItem($this->rowFactory->create('SessionStorage')); 157 | $this->addItem($this->rowFactory->create('NonCacheableLayouts')); 158 | } 159 | 160 | /** 161 | * Add search related dashboard items 162 | * 163 | * @throws \Exception 164 | * @throws UnexpectedValueException 165 | */ 166 | private function addItemsSearch() 167 | { 168 | if (version_compare($this->productMetadata->getVersion(), '2.3.1', '>=') && version_compare($this->productMetadata->getVersion(), '2.4.0', '<')) { 169 | $this->addItem( 170 | $this->rowFactory->create( 171 | 'ConfigSetting', 172 | [ 173 | 'title' => 'Catalog Search Engine', 174 | 'path' => 'catalog/search/engine', 175 | 'recommended' => 'elasticsearch6', 176 | 'source' => \Magento\Search\Model\Adminhtml\System\Config\Source\Engine::class, 177 | 'buttons' => '[devdocs-guides]/config-guide/elasticsearch/es-overview.html' 178 | ] 179 | ) 180 | ); 181 | } 182 | 183 | if (version_compare($this->productMetadata->getVersion(), '2.4.0', '>=')) { 184 | $this->addItem( 185 | $this->rowFactory->create( 186 | 'ConfigSetting', 187 | [ 188 | 'title' => 'Catalog Search Engine', 189 | 'path' => 'catalog/search/engine', 190 | 'recommended' => 'elasticsearch7', 191 | 'source' => \Magento\Search\Model\Adminhtml\System\Config\Source\Engine::class, 192 | 'buttons' => '[devdocs-guides]/config-guide/elasticsearch/es-overview.html' 193 | ] 194 | ) 195 | ); 196 | } 197 | } 198 | 199 | /** 200 | * Add configuration related items 201 | * 202 | * @throws \Exception 203 | * @throws UnexpectedValueException 204 | */ 205 | private function addItemsConfig() 206 | { 207 | $this->addItem( 208 | $this->rowFactory->create( 209 | 'ConfigSetting', 210 | [ 211 | 'title' => 'Full Page Caching Application', 212 | 'path' => 'system/full_page_cache/caching_application', 213 | 'recommended' => Config::VARNISH, 214 | 'source' => \Magento\PageCache\Model\System\Config\Source\Application::class, 215 | 'buttons' => '[devdocs-guides]/config-guide/varnish/config-varnish.html' 216 | ] 217 | ) 218 | ); 219 | if (version_compare($this->productMetadata->getVersion(), '2.2.0.dev', '<')) { 220 | if (!$this->runningHttp2()) { 221 | $this->addItem( 222 | $this->rowFactory->create( 223 | 'ConfigSetting', 224 | [ 225 | 'title' => 'Enable JavaScript Bundling', 226 | 'path' => 'dev/js/enable_js_bundling', 227 | 'recommended' => true, 228 | 'buttons' => '[devdocs-guides]/frontend-dev-guide/themes/js-bundling.html' 229 | ] 230 | ) 231 | ); 232 | $this->addItem( 233 | $this->rowFactory->create( 234 | 'ConfigSetting', 235 | [ 236 | 'title' => 'Merge JavaScript Files', 237 | 'path' => 'dev/js/merge_files', 238 | 'recommended' => true, 239 | 'buttons' => '[devdocs-guides]/config-guide/prod/prod_perf-optimize.html' . 240 | '#magento---performance-optimizations' 241 | ] 242 | ) 243 | ); 244 | $this->addItem( 245 | $this->rowFactory->create( 246 | 'ConfigSetting', 247 | [ 248 | 'title' => 'Merge CSS Files', 249 | 'path' => 'dev/css/merge_css_files', 250 | 'recommended' => true, 251 | 'buttons' => '[devdocs-guides]/config-guide/prod/prod_perf-optimize.html' . 252 | '#magento---performance-optimizations' 253 | ] 254 | ) 255 | ); 256 | } 257 | $this->addItem( 258 | $this->rowFactory->create( 259 | 'ConfigSetting', 260 | [ 261 | 'title' => 'Minify JavaScript Files', 262 | 'path' => 'dev/js/minify_files', 263 | 'recommended' => true, 264 | 'buttons' => '[devdocs-guides]/config-guide/prod/prod_perf-optimize.html' . 265 | '#magento---performance-optimizations' 266 | ] 267 | ) 268 | ); 269 | $this->addItem( 270 | $this->rowFactory->create( 271 | 'ConfigSetting', 272 | [ 273 | 'title' => 'Minify CSS Files', 274 | 'path' => 'dev/css/minify_files', 275 | 'recommended' => true, 276 | 'buttons' => '[devdocs-guides]/config-guide/prod/prod_perf-optimize.html' . 277 | '#magento---performance-optimizations' 278 | ] 279 | ) 280 | ); 281 | $this->addItem( 282 | $this->rowFactory->create( 283 | 'ConfigSetting', 284 | [ 285 | 'title' => 'Minify HTML', 286 | 'path' => 'dev/template/minify_html', 287 | 'recommended' => true, 288 | 'buttons' => '[devdocs-guides]/config-guide/prod/prod_perf-optimize.html' . 289 | '#magento---performance-optimizations' 290 | ] 291 | ) 292 | ); 293 | }; 294 | $this->addItem( 295 | $this->rowFactory->create( 296 | 'ConfigSetting', 297 | [ 298 | 'title' => 'Asynchronous sending of sales emails', 299 | 'path' => 'sales_email/general/async_sending', 300 | 'recommended' => true, 301 | 'buttons' => '[user-guides]/configuration/sales/sales-emails.html' . 302 | '#stores---configuration---sales---sales-emails' 303 | ] 304 | ) 305 | ); 306 | } 307 | 308 | private function runningHttp2() 309 | { 310 | $httpVersion = $this->rowFactory->create('HttpVersion')->getHttpVersion(); 311 | return (!empty($httpVersion) && version_compare($httpVersion, '2.0', '>=')); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Performance Dashboard Extension for Magento 2 2 | ===================== 3 | 4 | The free Performance Dashboard Extension by [MageHost.pro](https://magehost.pro) adds a screen to the Magento Store Admin called "Performance Dashboard". In this screen you get a clear overview of areas where the performance of your Magento 2 can be improved. 5 | 6 | # Install via Composer # 7 | 8 | ``` 9 | composer require magehost/performance-dashboard 10 | php bin/magento module:enable MageHost_PerformanceDashboard 11 | php bin/magento setup:upgrade 12 | php bin/magento setup:di:compile 13 | php bin/magento setup:static-content:deploy --area adminhtml 14 | ``` 15 | 16 | # Usage # 17 | 18 | * In Admin go to *_System > Tools > Performance Dashboard_*. 19 | 20 | # Uninstall # 21 | ``` 22 | php bin/magento module:disable MageHost_PerformanceDashboard 23 | composer remove magehost/performance-dashboard 24 | php bin/magento setup:upgrade 25 | php bin/magento setup:di:compile 26 | php bin/magento setup:static-content:deploy --area adminhtml 27 | ``` 28 | 29 | # Screenshot # 30 | ![screenshot](https://raw.githubusercontent.com/magehost/performance-dashboard/master/doc/screenshot.png) 31 | 32 | # Description # 33 | **This extension is free, licence: [MIT](https://github.com/magehost/performance-dashboard/blob/master/LICENSE).** 34 | 35 | Using our experience as [Magento Hosting professionals](https://magehost.pro) we created a list of best-practises for a high performance Magento 2 setup. 36 | Based on this list we have created a dashboard which automatically tests these various config settings and other setup choices. 37 | Checks executed: 38 | 39 | * Is PHP 7 in use? 40 | * Is HTTP/2 in use? 41 | * Are the PHP performance settings correct? 42 | * Are the MySQL performance settings tuned? 43 | * Is Magento in Production mode? 44 | * Is the Magento Cache stored in Redis? 45 | * Is the Full Page Cache stored in Redis? 46 | * Are all caches enabled? 47 | * Are sessions stored in Redis or Memcached? 48 | * A check which logs CMS and Catalog pages which can't be cached in full-page-cache because of `cacheable="false"`. 49 | * Is Composer's autoloader optimized? 50 | * Is the Full Page Cache using Varnish? 51 | * For Magento >= 2.3.1: 52 | * Is Elastic Search in use? 53 | * For Magento >= 2.3.2: 54 | * Is JavaScript defered? 55 | * For Magento < 2.2: 56 | * If not on HTTP/2: 57 | * Is merging CSS files enabled? 58 | * Is minify of JavaScript files enabled? 59 | * Is minify of CSS files enabled? 60 | * Is minify of HTML enabled? 61 | * Asynchronous sending of sales emails enabled? 62 | * All indexes set to Asynchronous? 63 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magehost/performance-dashboard", 3 | "description": "Performance Dashboard for Magento 2.x by MageHost.pro", 4 | "homepage": "https://github.com/magehost/performance-dashboard", 5 | "type": "magento2-module", 6 | "version": "1.15.4", 7 | "minimum-stability": "beta", 8 | "license": "MIT", 9 | "authors":[ 10 | { 11 | "name":"MageHost.pro - Jeroen Vermeulen", 12 | "email":"jeroen@magehost.pro" 13 | } 14 | ], 15 | "require": { 16 | "magento/framework": ">=100.0", 17 | "magento/module-backend": ">=100.0", 18 | "magento/module-store": ">=100.0", 19 | "magento/module-page-cache": ">=100.0", 20 | "monolog/monolog": ">=1.11", 21 | "psr/log": ">=1.0", 22 | "ext-curl": "*", 23 | "ext-json": "*" 24 | }, 25 | "autoload": { 26 | "files": [ 27 | "registration.php" 28 | ], 29 | "psr-4": { 30 | "MageHost\\PerformanceDashboard\\": "" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /doc/installation_guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magehost/performance-dashboard/f0ead111a593b8127cd18dff1e6992b1bc71c737/doc/installation_guide.pdf -------------------------------------------------------------------------------- /doc/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magehost/performance-dashboard/f0ead111a593b8127cd18dff1e6992b1bc71c737/doc/menu.png -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magehost/performance-dashboard/f0ead111a593b8127cd18dff1e6992b1bc71c737/doc/screenshot.png -------------------------------------------------------------------------------- /doc/user_guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magehost/performance-dashboard/f0ead111a593b8127cd18dff1e6992b1bc71c737/doc/user_guide.pdf -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mh_noncacheable.log 7 | 3 8 | 9 | 10 | 11 | 12 | mh_performance_dashboard 13 | 14 | MageHost\PerformanceDashboard\Logger\Handler 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2019 MageHost BV (https://magehost.pro) 11 | * @license https://opensource.org/licenses/MIT MIT License 12 | * @link https://github.com/magehost/performance-dashboard 13 | */ 14 | 15 | use \Magento\Framework\Component\ComponentRegistrar; 16 | 17 | ComponentRegistrar::register( 18 | ComponentRegistrar::MODULE, 19 | 'MageHost_PerformanceDashboard', 20 | __DIR__ 21 | ); 22 | -------------------------------------------------------------------------------- /view/adminhtml/layout/magehost_performance_dashboard_block.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dashboard_grid 9 | MageHost\PerformanceDashboard\Model\ResourceModel\Grid\Collection 10 | 0 11 | 12 | 13 | 14 | 0 15 | 16 | 17 | 18 | Optimization 19 | title 20 | text 21 | 180 22 | left 23 | 24 | 25 | 26 | 27 | Status 28 | status 29 | options 30 | 120 31 | left 32 | 33 | 34 | 0 35 | OK 36 | 37 | 38 | 1 39 | Warning 40 | 41 | 42 | 2 43 | Problem 44 | 45 | 46 | 3 47 | Unknown 48 | 49 | 50 | 51 | 52 | 53 | 54 | Information 55 | info 56 | text 57 | left 58 | true 59 | 2048 60 | 61 | 62 | 63 | 64 | Recommended Action 65 | action 66 | text 67 | left 68 | true 69 | 2048 70 | 71 | 72 | 73 | 74 | Links 75 | buttons 76 | left 77 | MageHost\PerformanceDashboard\Block\Backend\Dashboard\Grid\Column\Renderer\Buttons 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /view/adminhtml/layout/magehost_performance_dashboard_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | Performance Dashboard - by MageHost.pro 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------