/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.cli.commands.tools;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import io.airlift.airline.Command;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.cli.Artemis;
import org.apache.activemq.artemis.cli.commands.ActionContext;
import org.apache.activemq.artemis.core.journal.RecordInfo;
import org.apache.activemq.artemis.core.message.impl.CoreMessagePersister;
import org.apache.activemq.artemis.core.paging.PagedMessage;
import org.apache.activemq.artemis.core.paging.PagingManager;
import org.apache.activemq.artemis.core.paging.PagingStore;
import org.apache.activemq.artemis.core.paging.PagingStoreFactory;
import org.apache.activemq.artemis.core.paging.cursor.PagePosition;
import org.apache.activemq.artemis.core.paging.cursor.impl.PagePositionImpl;
import org.apache.activemq.artemis.core.paging.impl.Page;
import org.apache.activemq.artemis.core.paging.impl.PageTransactionInfoImpl;
import org.apache.activemq.artemis.core.paging.impl.PagingManagerImpl;
import org.apache.activemq.artemis.core.paging.impl.PagingStoreFactoryNIO;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.persistence.impl.journal.DescribeJournal;
import org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds;
import org.apache.activemq.artemis.core.persistence.impl.journal.codec.CursorAckRecordEncoding;
import org.apache.activemq.artemis.core.persistence.impl.journal.codec.PageUpdateTXEncoding;
import org.apache.activemq.artemis.core.persistence.impl.nullpm.NullStorageManager;
import org.apache.activemq.artemis.core.protocol.core.impl.CoreProtocolManagerFactory;
import org.apache.activemq.artemis.core.server.impl.FileLockNodeManager;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.core.settings.impl.HierarchicalObjectRepository;
import org.apache.activemq.artemis.spi.core.protocol.MessagePersister;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.ExecutorFactory;
@Command(name = "print", description = "Print data records information (WARNING: don't use while a production server is running)")
public class PrintData extends OptionalLocking {
static {
MessagePersister.registerPersister(CoreProtocolManagerFactory.ID, CoreMessagePersister.getInstance());
}
@Override
public Object execute(ActionContext context) throws Exception {
super.execute(context);
try {
printData(new File(getBinding()), new File(getJournal()), new File(getPaging()));
} catch (Exception e) {
treatError(e, "data", "print");
}
return null;
}
public static void printData(File bindingsDirectory, File messagesDirectory, File pagingDirectory) throws Exception {
// Having the version on the data report is an information very useful to understand what happened
// When debugging stuff
Artemis.printBanner();
File serverLockFile = new File(messagesDirectory, "server.lock");
if (serverLockFile.isFile()) {
try {
FileLockNodeManager fileLock = new FileLockNodeManager(messagesDirectory, false);
fileLock.start();
System.out.println("********************************************");
System.out.println("Server's ID=" + fileLock.getNodeId().toString());
System.out.println("********************************************");
fileLock.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("********************************************");
System.out.println("B I N D I N G S J O U R N A L");
System.out.println("********************************************");
try {
DescribeJournal.describeBindingsJournal(bindingsDirectory);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println();
System.out.println("********************************************");
System.out.println("M E S S A G E S J O U R N A L");
System.out.println("********************************************");
DescribeJournal describeJournal = null;
try {
describeJournal = DescribeJournal.describeMessagesJournal(messagesDirectory);
} catch (Exception e) {
e.printStackTrace();
return;
}
try {
System.out.println();
System.out.println("********************************************");
System.out.println("P A G I N G");
System.out.println("********************************************");
printPages(pagingDirectory, describeJournal);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
private static void printPages(File pageDirectory, DescribeJournal describeJournal) {
try {
PageCursorsInfo cursorACKs = calculateCursorsInfo(describeJournal.getRecords());
Set<Long> pgTXs = cursorACKs.getPgTXs();
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1, ActiveMQThreadFactory.defaultThreadFactory());
final ExecutorService executor = Executors.newFixedThreadPool(10, ActiveMQThreadFactory.defaultThreadFactory());
ExecutorFactory execfactory = new ExecutorFactory() {
@Override
public Executor getExecutor() {
return executor;
}
};
final StorageManager sm = new NullStorageManager();
PagingStoreFactory pageStoreFactory = new PagingStoreFactoryNIO(sm, pageDirectory, 1000L, scheduled, execfactory, false, null);
HierarchicalRepository<AddressSettings> addressSettingsRepository = new HierarchicalObjectRepository<>();
addressSettingsRepository.setDefault(new AddressSettings());
PagingManager manager = new PagingManagerImpl(pageStoreFactory, addressSettingsRepository);
manager.start();
SimpleString[] stores = manager.getStoreNames();
for (SimpleString store : stores) {
PagingStore pgStore = manager.getPageStore(store);
File folder = null;
if (pgStore != null) {
folder = pgStore.getFolder();
}
System.out.println("####################################################################################################");
System.out.println("Exploring store " + store + " folder = " + folder);
int pgid = (int) pgStore.getFirstPage();
for (int pg = 0; pg < pgStore.getNumberOfPages(); pg++) {
System.out.println("******* Page " + pgid);
Page page = pgStore.createPage(pgid);
page.open();
List<PagedMessage> msgs = page.read(sm);
page.close();
int msgID = 0;
for (PagedMessage msg : msgs) {
msg.initMessage(sm);
System.out.print("pg=" + pgid + ", msg=" + msgID + ",pgTX=" + msg.getTransactionID() + ",userMessageID=" + (msg.getMessage().getUserID() != null ? msg.getMessage().getUserID() : "") + ", msg=" + msg.getMessage());
System.out.print(",Queues = ");
long[] q = msg.getQueueIDs();
for (int i = 0; i < q.length; i++) {
System.out.print(q[i]);
PagePosition posCheck = new PagePositionImpl(pgid, msgID);
boolean acked = false;
Set<PagePosition> positions = cursorACKs.getCursorRecords().get(q[i]);
if (positions != null) {
acked = positions.contains(posCheck);
}
if (acked) {
System.out.print(" (ACK)");
}
if (cursorACKs.getCompletePages(q[i]).contains(Long.valueOf(pgid))) {
System.out.println(" (PG-COMPLETE)");
}
if (i + 1 < q.length) {
System.out.print(",");
}
}
if (msg.getTransactionID() >= 0 && !pgTXs.contains(msg.getTransactionID())) {
System.out.print(", **PG_TX_NOT_FOUND**");
}
System.out.println();
msgID++;
}
pgid++;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Calculate the acks on the page system
*/
private static PageCursorsInfo calculateCursorsInfo(List<RecordInfo> records) throws Exception {
PageCursorsInfo cursorInfo = new PageCursorsInfo();
for (RecordInfo record : records) {
byte[] data = record.data;
ActiveMQBuffer buff = ActiveMQBuffers.wrappedBuffer(data);
if (record.userRecordType == JournalRecordIds.ACKNOWLEDGE_CURSOR) {
CursorAckRecordEncoding encoding = new CursorAckRecordEncoding();
encoding.decode(buff);
Set<PagePosition> set = cursorInfo.getCursorRecords().get(encoding.queueID);
if (set == null) {
set = new HashSet<>();
cursorInfo.getCursorRecords().put(encoding.queueID, set);
}
set.add(encoding.position);
} else if (record.userRecordType == JournalRecordIds.PAGE_CURSOR_COMPLETE) {
CursorAckRecordEncoding encoding = new CursorAckRecordEncoding();
encoding.decode(buff);
Long queueID = Long.valueOf(encoding.queueID);
Long pageNR = Long.valueOf(encoding.position.getPageNr());
if (!cursorInfo.getCompletePages(queueID).add(pageNR)) {
System.err.println("Page " + pageNR + " has been already set as complete on queue " + queueID);
}
} else if (record.userRecordType == JournalRecordIds.PAGE_TRANSACTION) {
if (record.isUpdate) {
PageUpdateTXEncoding pageUpdate = new PageUpdateTXEncoding();
pageUpdate.decode(buff);
cursorInfo.getPgTXs().add(pageUpdate.pageTX);
} else {
PageTransactionInfoImpl pageTransactionInfo = new PageTransactionInfoImpl();
pageTransactionInfo.decode(buff);
pageTransactionInfo.setRecordID(record.id);
cursorInfo.getPgTXs().add(pageTransactionInfo.getTransactionID());
}
}
}
return cursorInfo;
}
private static class PageCursorsInfo {
private final Map<Long, Set<PagePosition>> cursorRecords = new HashMap<>();
private final Set<Long> pgTXs = new HashSet<>();
private final Map<Long, Set<Long>> completePages = new HashMap<>();
private PageCursorsInfo() {
}
/**
* @return the pgTXs
*/
Set<Long> getPgTXs() {
return pgTXs;
}
/**
* @return the cursorRecords
*/
Map<Long, Set<PagePosition>> getCursorRecords() {
return cursorRecords;
}
Set<Long> getCompletePages(Long queueID) {
Set<Long> completePagesSet = completePages.get(queueID);
if (completePagesSet == null) {
completePagesSet = new HashSet<>();
completePages.put(queueID, completePagesSet);
}
return completePagesSet;
}
}
}