Skip to content

Commit 9021561

Browse files
committed
Add command to view mview state and queue
This is similar to the magerun1 command here: netz98/n98-magerun#891 I like the ability to view the mview queue in realtime as its being processed, it can be quite helpful when debugging indexing issues. This command will actually show how many items are in the list pending processing, as well information from the `mview_state` table. ``` php bin/magento indexer:status:mview +---------------------------+----------+--------+---------------------+------------+---------+ | ID | Mode | Status | Updated | Version ID | Backlog | +---------------------------+----------+--------+---------------------+------------+---------+ | catalog_category_product | enabled | idle | 2017-11-02 10:00:00 | 1 | 0 | | catalog_product_attribute | enabled | idle | 2017-11-02 10:00:00 | 1 | 1 | | catalog_product_category | disabled | idle | 2017-11-02 10:00:00 | 1 | 0 | | catalog_product_price | enabled | idle | 2017-11-02 10:00:00 | 1 | 0 | +---------------------------+----------+--------+---------------------+------------+---------+ ``` I'll point this PR into 2.1.x and raise a separate PR to pop it into 2.2.x.
1 parent 148da00 commit 9021561

File tree

3 files changed

+329
-0
lines changed

3 files changed

+329
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Indexer\Console\Command;
7+
8+
use Symfony\Component\Console\Input\InputInterface;
9+
use Symfony\Component\Console\Output\OutputInterface;
10+
use Symfony\Component\Console\Command\Command;
11+
use Magento\Framework\Mview\View;
12+
13+
/**
14+
* Command for displaying status of mview indexers.
15+
*/
16+
class IndexerStatusMviewCommand extends Command
17+
{
18+
/** @var \Magento\Framework\Mview\View\CollectionInterface $mviewIndexersCollection */
19+
private $mviewIndexersCollection;
20+
21+
public function __construct(
22+
\Magento\Framework\Mview\View\CollectionInterface $collection
23+
) {
24+
$this->mviewIndexersCollection = $collection;
25+
parent::__construct();
26+
}
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
protected function configure()
32+
{
33+
$this->setName('indexer:status:mview')
34+
->setDescription('Shows status of Mview Indexers and their queue status');
35+
36+
parent::configure();
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
protected function execute(InputInterface $input, OutputInterface $output)
43+
{
44+
try {
45+
$table = $this->getHelperSet()->get('table');
46+
$table->setHeaders(['ID', 'Mode', 'Status', 'Updated', 'Version ID', 'Backlog']);
47+
48+
$rows = [];
49+
50+
/** @var \Magento\Framework\Mview\View $indexer */
51+
foreach ($this->mviewIndexersCollection as $indexer) {
52+
$state = $indexer->getState();
53+
$changelog = $indexer->getChangelog();
54+
55+
try {
56+
$currentVersionId = $changelog->getVersion();
57+
} catch (View\ChangelogTableNotExistsException $e) {
58+
continue;
59+
}
60+
61+
$pendingCount = count($changelog->getList($state->getVersionId(), $currentVersionId));
62+
63+
$pendingString = "<error>$pendingCount</error>";
64+
if ($pendingCount <= 0) {
65+
$pendingString = "<info>$pendingCount</info>";
66+
}
67+
68+
$rows[] = [
69+
$indexer->getData('view_id'),
70+
$state->getData('mode'),
71+
$state->getData('status'),
72+
$state->getData('updated'),
73+
$state->getData('version_id'),
74+
$pendingString,
75+
];
76+
}
77+
78+
usort($rows, function($a, $b) {
79+
return $a[0] <=> $b[0];
80+
});
81+
82+
$table->addRows($rows);
83+
$table->render($output);
84+
85+
return \Magento\Framework\Console\Cli::RETURN_SUCCESS;
86+
} catch (\Exception $e) {
87+
$output->writeln('<error>' . $e->getMessage() . '</error>');
88+
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
89+
$output->writeln($e->getTraceAsString());
90+
}
91+
92+
return \Magento\Framework\Console\Cli::RETURN_FAILURE;
93+
}
94+
}
95+
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Indexer\Test\Unit\Console\Command;
7+
8+
use \Magento\Framework\Mview;
9+
use Magento\Indexer\Console\Command\IndexerStatusMviewCommand;
10+
use Symfony\Component\Console\Tester\CommandTester;
11+
use Symfony\Component\Console\Helper\HelperSet;
12+
use Symfony\Component\Console\Helper\TableHelper;
13+
use Magento\Store\Model\Website;
14+
use Magento\Framework\Console\Cli;
15+
16+
class IndexerStatusMviewCommandTest extends \PHPUnit_Framework_TestCase
17+
{
18+
/**
19+
* @var IndexerStatusMviewCommand
20+
*/
21+
private $command;
22+
23+
/**
24+
* @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
25+
*/
26+
private $objectManager;
27+
28+
/**
29+
* @var \Magento\Framework\Mview\View\Collection
30+
*/
31+
private $collection;
32+
33+
protected function setUp()
34+
{
35+
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
36+
37+
/** @var \Magento\Framework\Mview\View\Collection $collection */
38+
$this->collection = $this->objectManager->getObject(Mview\View\Collection::class);
39+
40+
$reflectedCollection = new \ReflectionObject($this->collection);
41+
$isLoadedProperty = $reflectedCollection->getProperty('_isCollectionLoaded');
42+
$isLoadedProperty->setAccessible(true);
43+
$isLoadedProperty->setValue($this->collection, true);
44+
45+
$this->command = $this->objectManager->getObject(
46+
IndexerStatusMviewCommand::class,
47+
['collection' => $this->collection]
48+
);
49+
50+
/** @var HelperSet $helperSet */
51+
$helperSet = $this->objectManager->getObject(
52+
HelperSet::class,
53+
['helpers' => [$this->objectManager->getObject(TableHelper::class)]]
54+
);
55+
56+
//Inject table helper for output
57+
$this->command->setHelperSet($helperSet);
58+
}
59+
60+
public function testExecute()
61+
{
62+
$mviews = [
63+
[
64+
'view' => [
65+
'view_id' => 'catalog_category_product',
66+
'mode' => 'enabled',
67+
'status' => 'idle',
68+
'updated' => '2017-01-01 11:11:11',
69+
'version_id' => 100,
70+
],
71+
'changelog' => [
72+
'version_id' => 110
73+
],
74+
],
75+
[
76+
'view' => [
77+
'view_id' => 'catalog_product_category',
78+
'mode' => 'disabled',
79+
'status' => 'idle',
80+
'updated' => '2017-01-01 11:11:11',
81+
'version_id' => 100,
82+
],
83+
'changelog' => [
84+
'version_id' => 200
85+
],
86+
],
87+
[
88+
'view' => [
89+
'view_id' => 'catalog_product_attribute',
90+
'mode' => 'enabled',
91+
'status' => 'idle',
92+
'updated' => '2017-01-01 11:11:11',
93+
'version_id' => 100,
94+
],
95+
'changelog' => [
96+
'version_id' => 100
97+
],
98+
],
99+
];
100+
101+
foreach ($mviews as $data) {
102+
$this->collection->addItem($this->generateMviewStub($data['view'], $data['changelog']));
103+
}
104+
105+
/** @var Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject $stub */
106+
$changelog = $this->getMockBuilder(\Magento\Framework\Mview\View\Changelog::class)
107+
->disableOriginalConstructor()
108+
->getMock();
109+
110+
$changelog->expects($this->any())
111+
->method('getVersion')
112+
->willThrowException(
113+
new Mview\View\ChangelogTableNotExistsException(new \Magento\Framework\Phrase("Do not render"))
114+
);
115+
116+
/** @var Mview\View|\PHPUnit_Framework_MockObject_MockObject $notInitiatedMview */
117+
$notInitiatedMview = $this->getMockBuilder(\Magento\Framework\Mview\View::class)
118+
->disableOriginalConstructor()
119+
->getMock();
120+
121+
$notInitiatedMview->expects($this->any())
122+
->method('getChangelog')
123+
->willReturn($changelog);
124+
125+
$this->collection->addItem($notInitiatedMview);
126+
127+
$tester = new CommandTester($this->command);
128+
$this->assertEquals(Cli::RETURN_SUCCESS, $tester->execute([]));
129+
130+
$linesOutput = array_filter(explode(PHP_EOL, $tester->getDisplay()));
131+
$this->assertCount(7, $linesOutput, 'There should be 7 lines output. 3 Spacers, 1 header, 3 content.');
132+
$this->assertEquals($linesOutput[0], $linesOutput[2], "Lines 0, 2, 7 should be spacer lines");
133+
$this->assertEquals($linesOutput[2], $linesOutput[6], "Lines 0, 2, 6 should be spacer lines");
134+
135+
$headerValues = array_values(array_filter(explode('|', $linesOutput[1])));
136+
$this->assertEquals('ID', trim($headerValues[0]));
137+
$this->assertEquals('Mode', trim($headerValues[1]));
138+
$this->assertEquals('Status', trim($headerValues[2]));
139+
$this->assertEquals('Updated', trim($headerValues[3]));
140+
$this->assertEquals('Version ID', trim($headerValues[4]));
141+
$this->assertEquals('Backlog', trim($headerValues[5]));
142+
143+
$catalogCategoryProductMviewData = array_values(array_filter(explode('|', $linesOutput[3])));
144+
$this->assertEquals('catalog_category_product', trim($catalogCategoryProductMviewData[0]));
145+
$this->assertEquals('enabled', trim($catalogCategoryProductMviewData[1]));
146+
$this->assertEquals('idle', trim($catalogCategoryProductMviewData[2]));
147+
$this->assertEquals('2017-01-01 11:11:11', trim($catalogCategoryProductMviewData[3]));
148+
$this->assertEquals('100', trim($catalogCategoryProductMviewData[4]));
149+
$this->assertEquals('10', trim($catalogCategoryProductMviewData[5]));
150+
unset($catalogCategoryProductMviewData);
151+
152+
$catalogProductAttributeMviewData = array_values(array_filter(explode('|', $linesOutput[4])));
153+
$this->assertEquals('catalog_product_attribute', trim($catalogProductAttributeMviewData[0]));
154+
$this->assertEquals('enabled', trim($catalogProductAttributeMviewData[1]));
155+
$this->assertEquals('idle', trim($catalogProductAttributeMviewData[2]));
156+
$this->assertEquals('2017-01-01 11:11:11', trim($catalogProductAttributeMviewData[3]));
157+
$this->assertEquals('100', trim($catalogProductAttributeMviewData[4]));
158+
$this->assertEquals('0', trim($catalogProductAttributeMviewData[5]));
159+
unset($catalogProductAttributeMviewData);
160+
161+
$catalogCategoryProductMviewData = array_values(array_filter(explode('|', $linesOutput[5])));
162+
$this->assertEquals('catalog_product_category', trim($catalogCategoryProductMviewData[0]));
163+
$this->assertEquals('disabled', trim($catalogCategoryProductMviewData[1]));
164+
$this->assertEquals('idle', trim($catalogCategoryProductMviewData[2]));
165+
$this->assertEquals('2017-01-01 11:11:11', trim($catalogCategoryProductMviewData[3]));
166+
$this->assertEquals('100', trim($catalogCategoryProductMviewData[4]));
167+
$this->assertEquals('100', trim($catalogCategoryProductMviewData[5]));
168+
unset($catalogCategoryProductMviewData);
169+
}
170+
171+
/**
172+
* @param array $viewData
173+
* @param array $changelogData
174+
* @return Mview\View|Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject
175+
*/
176+
protected function generateMviewStub(array $viewData, array $changelogData)
177+
{
178+
/** @var Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject $stub */
179+
$changelog = $this->getMockBuilder(\Magento\Framework\Mview\View\Changelog::class)
180+
->disableOriginalConstructor()
181+
->getMock();
182+
183+
$list = [];
184+
if ($changelogData['version_id'] !== $viewData['version_id']) {
185+
$list = range($viewData['version_id']+1, $changelogData['version_id']);
186+
}
187+
188+
$changelog->expects($this->any())
189+
->method('getList')
190+
->willReturn($list);
191+
192+
$changelog->expects($this->any())
193+
->method('getVersion')
194+
->willReturn($changelogData['version_id']);
195+
196+
/** @var Mview\View|\PHPUnit_Framework_MockObject_MockObject $stub */
197+
$stub = $this->getMockBuilder(\Magento\Framework\Mview\View::class)
198+
->disableOriginalConstructor()
199+
->setMethods(['getChangelog', 'getState'])
200+
->getMock();
201+
202+
$stub->expects($this->any())
203+
->method('getChangelog')
204+
->willReturn($changelog);
205+
206+
$stub->expects($this->any())
207+
->method('getState')
208+
->willReturnSelf();
209+
210+
$stub->setData($viewData);
211+
212+
return $stub;
213+
}
214+
215+
public function testExecuteExceptionNoVerbosity()
216+
{
217+
/** @var \Magento\Framework\Mview\View|\PHPUnit_Framework_MockObject_MockObject $stub */
218+
$stub = $this->getMockBuilder(Mview\View::class)
219+
->disableOriginalConstructor()
220+
->getMock();
221+
222+
$stub->expects($this->any())
223+
->method('getChangelog')
224+
->willThrowException(new \Exception("Dummy test exception"));
225+
226+
$this->collection->addItem($stub);
227+
228+
$tester = new CommandTester($this->command);
229+
$this->assertEquals(Cli::RETURN_FAILURE, $tester->execute([]));
230+
$linesOutput = array_filter(explode(PHP_EOL, $tester->getDisplay()));
231+
$this->assertEquals('Dummy test exception', $linesOutput[0]);
232+
}
233+
}

app/code/Magento/Indexer/etc/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<item name="set-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerSetModeCommand</item>
5252
<item name="show-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerShowModeCommand</item>
5353
<item name="status" xsi:type="object">Magento\Indexer\Console\Command\IndexerStatusCommand</item>
54+
<item name="status-mview" xsi:type="object">Magento\Indexer\Console\Command\IndexerStatusMviewCommand</item>
5455
<item name="reset" xsi:type="object">Magento\Indexer\Console\Command\IndexerResetStateCommand</item>
5556
</argument>
5657
</arguments>

0 commit comments

Comments
 (0)