blob: cfc75ed1ded3fd3ac1fe98addafb3f5d6ac91ddd [file] [log] [blame]
/*
* 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.ignite.internal.processors.diagnostic;
import java.io.File;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.failure.FailureContext;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.processors.GridProcessorAdapter;
import org.apache.ignite.internal.processors.cache.persistence.tree.CorruptedTreeException;
import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.jetbrains.annotations.NotNull;
import static org.apache.ignite.internal.processors.diagnostic.DiagnosticProcessor.DiagnosticAction.PRINT_TO_FILE;
import static org.apache.ignite.internal.processors.diagnostic.DiagnosticProcessor.DiagnosticAction.PRINT_TO_LOG;
import static org.apache.ignite.internal.processors.diagnostic.DiagnosticProcessor.DiagnosticAction.PRINT_TO_RAW_FILE;
import static org.apache.ignite.internal.util.IgniteStopwatch.logTime;
/**
* Processor which contained helper methods for different diagnostic cases.
*/
public class DiagnosticProcessor extends GridProcessorAdapter {
/** Value of the system property that enables page locks dumping on failure. */
private static final boolean IGNITE_DUMP_PAGE_LOCK_ON_FAILURE =
IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_DUMP_PAGE_LOCK_ON_FAILURE, true);
/** Time formatter for dump file name. */
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss_SSS");
/** Folder name for store diagnostic info. **/
public static final String DEFAULT_TARGET_FOLDER = "diagnostic";
/** File format. */
static final String FILE_FORMAT = ".txt";
/** Raw file format. */
static final String RAW_FILE_FORMAT = ".raw";
/** Full path for store dubug info. */
private final Path diagnosticPath;
/** */
private final PageHistoryDiagnoster pageHistoryDiagnoster;
/**
* @param ctx Kernal context.
*/
public DiagnosticProcessor(GridKernalContext ctx) throws IgniteCheckedException {
super(ctx);
diagnosticPath = U.resolveWorkDirectory(ctx.config().getWorkDirectory(), DEFAULT_TARGET_FOLDER, false).toPath();
pageHistoryDiagnoster = new PageHistoryDiagnoster(ctx, this::diagnosticFile);
}
/** {@inheritDoc} */
@Override public void onKernalStart(boolean active) throws IgniteCheckedException {
super.onKernalStart(active);
pageHistoryDiagnoster.onStart();
}
/**
* Dump all history caches of given page.
*
* @param builder Parameters of dumping.
* @throws IgniteCheckedException If scanning was failed.
*/
public void dumpPageHistory(
@NotNull PageHistoryDiagnoster.DiagnosticPageBuilder builder
) throws IgniteCheckedException {
logTime(log, "DiagnosticPageHistory", () -> pageHistoryDiagnoster.dumpPageHistory(builder));
}
/**
* Print diagnostic info about failure occurred on {@code ignite} instance.
* Failure details is contained in {@code failureCtx}.
*
* @param ignite Ignite instance.
* @param failureCtx Failure context.
*/
public void onFailure(Ignite ignite, FailureContext failureCtx) {
// Dump data structures page locks.
if (IGNITE_DUMP_PAGE_LOCK_ON_FAILURE)
ctx.cache().context().diagnostic().pageLockTracker().dumpLocksToLog();
// If we have some corruption in data structure,
// we should scan WAL and print to log and save to file all pages related to corruption for
// future investigation.
if (X.hasCause(failureCtx.error(), CorruptedTreeException.class)) {
CorruptedTreeException corruptedTreeException = X.cause(failureCtx.error(), CorruptedTreeException.class);
T2<Integer, Long>[] pageIds = corruptedTreeException.pages();
try {
dumpPageHistory(
new PageHistoryDiagnoster.DiagnosticPageBuilder()
.pageIds(pageIds)
.addAction(PRINT_TO_LOG)
.addAction(PRINT_TO_FILE)
.addAction(PRINT_TO_RAW_FILE)
);
}
catch (IgniteCheckedException e) {
SB sb = new SB();
sb.a("[");
for (int i = 0; i < pageIds.length; i++)
sb.a("(").a(pageIds[i].get1()).a(",").a(pageIds[i].get2()).a(")");
sb.a("]");
ignite.log().error(
"Failed to dump diagnostic info on tree corruption. PageIds=" + sb, e);
}
}
}
/**
* Resolve file to store diagnostic info.
*
* @param customFile Custom file if customized.
* @param writeMode Diagnostic file write mode.
* @return File to store diagnostic info.
*/
private File diagnosticFile(File customFile, DiagnosticFileWriteMode writeMode) {
if (customFile == null)
return finalizeFile(diagnosticPath, writeMode);
if (customFile.isAbsolute())
return finalizeFile(customFile.toPath(), writeMode);
return finalizeFile(diagnosticPath.resolve(customFile.toPath()), writeMode);
}
/**
* @param diagnosticPath Path to diagnostic file.
* @param writeMode Diagnostic file write mode.
* @return File to store diagnostic info.
*/
private static File finalizeFile(Path diagnosticPath, DiagnosticFileWriteMode writeMode) {
diagnosticPath.toFile().mkdirs();
return diagnosticPath.resolve(LocalDateTime.now().format(TIME_FORMATTER) + getFileExtension(writeMode)).toFile();
}
/**
* Get file format for given write mode.
*
* @param writeMode Diagnostic file write mode.
* @return File extention with dot.
*/
private static String getFileExtension(DiagnosticFileWriteMode writeMode) {
switch (writeMode) {
case HUMAN_READABLE:
return FILE_FORMAT;
case RAW:
return RAW_FILE_FORMAT;
default:
throw new IllegalArgumentException("writeMode=" + writeMode);
}
}
/**
* Possible action after WAL scanning.
*/
public enum DiagnosticAction {
/** Print result to log. */
PRINT_TO_LOG,
/** Print result to file. */
PRINT_TO_FILE,
/** Print result to file in raw format. */
PRINT_TO_RAW_FILE
}
/**
* Mode of diagnostic dump file.
*/
public enum DiagnosticFileWriteMode {
/** Use humanly readable data representation. */
HUMAN_READABLE,
/** Use raw data format. */
RAW
}
}