mirror of https://gitee.com/bigwinds/arangodb
676 lines
18 KiB
JavaScript
676 lines
18 KiB
JavaScript
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief arango datafile debugger
|
|
///
|
|
/// @file
|
|
///
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2014 ArangoDB GmbH, Cologne, Germany
|
|
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed 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.
|
|
///
|
|
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// @author Dr. Frank Celler
|
|
/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
|
|
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
var internal = require("internal");
|
|
var console = require("console");
|
|
var fs = require("fs");
|
|
|
|
var printf = internal.printf;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief unload a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function UnloadCollection (collection) {
|
|
var tries = 0;
|
|
|
|
// unload collection if not yet unloaded (2) & not corrupted (0)
|
|
while (collection.status() !== 2 && collection.status() !== 0) {
|
|
collection.unload();
|
|
|
|
if (++tries >= 20) {
|
|
break;
|
|
}
|
|
if (tries === 1) {
|
|
printf("Trying to unload collection '%s', current status: %s\n", collection.name(), collection.status());
|
|
}
|
|
|
|
internal.wait(1, true);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove datafile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function RemoveDatafile (collection, type, datafile) {
|
|
var backup = datafile + ".corrupt";
|
|
|
|
fs.move(datafile, backup);
|
|
|
|
printf("Removed %s at %s\n", type, datafile);
|
|
printf("Backup is in %s\n", backup);
|
|
printf("\n");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief try to repair a datafile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function TryRepairDatafile (collection, datafile) {
|
|
UnloadCollection(collection);
|
|
return collection.tryRepairDatafile(datafile);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief wipe entries
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function WipeDatafile (collection, type, datafile, lastGoodPos) {
|
|
UnloadCollection(collection);
|
|
collection.truncateDatafile(datafile, lastGoodPos);
|
|
|
|
printf("Truncated and sealed datafile\n");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief queries user to wipe a datafile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function QueryWipeDatafile (collection, type, datafile, scan, lastGoodPos) {
|
|
var entries = scan.entries;
|
|
|
|
if (entries.length === 0) {
|
|
if (type === "journal" || type === "compactor") {
|
|
printf("WARNING: The journal is empty. Even the header is missing. Going\n");
|
|
printf(" to remove the file.\n");
|
|
}
|
|
else {
|
|
printf("WARNING: The datafile is empty. Even the header is missing. Going\n");
|
|
printf(" to remove the datafile. This should never happen. Datafiles\n");
|
|
printf(" are append-only. Make sure your hard disk does not contain\n");
|
|
printf(" any hardware errors.\n");
|
|
}
|
|
printf("\n");
|
|
|
|
RemoveDatafile(collection, type, datafile);
|
|
return;
|
|
}
|
|
|
|
var ask = true;
|
|
var tryRepair = false;
|
|
|
|
if (type === "journal") {
|
|
if (entries.length === lastGoodPos + 3 && entries[lastGoodPos + 2].status === 2) {
|
|
printf("WARNING: The journal was not closed properly, the last entry is corrupted.\n");
|
|
printf(" This might happen if ArangoDB was killed and the last entry was not\n");
|
|
printf(" fully written to disk. Going to remove the last entry.\n");
|
|
ask = false;
|
|
}
|
|
else {
|
|
printf("WARNING: The journal was not closed properly, the last entries are corrupted.\n");
|
|
printf(" This might happen if ArangoDB was killed and the last entries were not\n");
|
|
printf(" fully written to disk.\n");
|
|
}
|
|
}
|
|
else {
|
|
printf("WARNING: The datafile contains corrupt entries. This should never happen.\n");
|
|
printf(" Datafiles are append-only. Make sure your hard disk does not contain\n");
|
|
printf(" any hardware errors.\n");
|
|
|
|
tryRepair = true;
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
var entry = entries[lastGoodPos];
|
|
|
|
if (ask) {
|
|
var line;
|
|
|
|
if (tryRepair) {
|
|
printf("Try to repair the error(s) (Y/N)? ");
|
|
line = console.getline();
|
|
|
|
if (line === "yes" || line === "YES" || line === "y" || line === "Y") {
|
|
if (TryRepairDatafile(collection, datafile)) {
|
|
printf("Repair succeeded.\n");
|
|
return;
|
|
}
|
|
|
|
printf("Repair failed.\n");
|
|
}
|
|
}
|
|
|
|
printf("Wipe the last entries (Y/N)? ");
|
|
line = console.getline();
|
|
|
|
if (line !== "yes" && line !== "YES" && line !== "y" && line !== "Y") {
|
|
printf("ABORTING\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
WipeDatafile(collection, type, datafile, entry.position + entry.realSize);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief prints details about entries
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function PrintEntries (entries, amount) {
|
|
var start, end;
|
|
|
|
if (amount > 0) {
|
|
start = 0;
|
|
end = amount;
|
|
if (end > entries.length) {
|
|
end = entries.length;
|
|
}
|
|
}
|
|
else {
|
|
start = entries.length + amount - 1;
|
|
if (start < 0) {
|
|
return;
|
|
}
|
|
if (start < Math.abs(amount)) {
|
|
return;
|
|
}
|
|
|
|
end = entries.length;
|
|
}
|
|
|
|
for (var i = start; i < end; ++i) {
|
|
var entry = entries[i], extra;
|
|
|
|
var s = "unknown";
|
|
|
|
switch (entry.status) {
|
|
case 1: s = "OK"; break;
|
|
case 2: s = "OK (end)"; break;
|
|
case 3: s = "FAILED (empty)"; break;
|
|
case 4: s = "FAILED (too small)"; break;
|
|
case 5: s = "FAILED (crc mismatch)"; break;
|
|
}
|
|
|
|
if (entry.key) {
|
|
extra = " - key: " + entry.key;
|
|
}
|
|
else {
|
|
extra = "";
|
|
}
|
|
|
|
printf(" %d: status %s type %d (%s) size %d, tick %s%s\n", i, s, entry.type, entry.typeName, entry.realSize, entry.tick, extra);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief checks a datafile deeply
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function DeepCheckDatafile (collection, type, datafile, scan) {
|
|
var entries = scan.entries;
|
|
|
|
var diagnosis = "";
|
|
var lastGood, firstBad;
|
|
var lastGoodPos = 0;
|
|
var stillGood = true;
|
|
|
|
for (var i = 0; i < entries.length; ++i) {
|
|
var entry = entries[i];
|
|
|
|
if (entry.status === 1 || entry.status === 2) {
|
|
if (stillGood) {
|
|
lastGood = entry;
|
|
lastGoodPos = i;
|
|
}
|
|
}
|
|
else {
|
|
stillGood = false;
|
|
diagnosis = entry.diagnosis || "";
|
|
firstBad = entry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! stillGood) {
|
|
printf(" Last good marker: start: %d (hex %s), length: %d (hex %s)\n",
|
|
lastGood.position,
|
|
lastGood.position.toString(16),
|
|
lastGood.realSize,
|
|
lastGood.realSize.toString(16));
|
|
|
|
printf(" First bad marker: start: %d (hex %s), length: %d (hex %s)\n",
|
|
firstBad.position,
|
|
firstBad.position.toString(16),
|
|
firstBad.realSize,
|
|
firstBad.realSize.toString(16));
|
|
|
|
if (diagnosis) {
|
|
printf(" Diagnosis: %s\n", diagnosis);
|
|
}
|
|
printf("\n");
|
|
|
|
QueryWipeDatafile(collection, type, datafile, scan, lastGoodPos);
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief checks a datafile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function CheckDatafile (collection, type, datafile, issues, details) {
|
|
printf("Datafile\n");
|
|
printf(" path: %s\n", datafile);
|
|
printf(" type: %s\n", type);
|
|
|
|
var scan = collection.datafileScan(datafile);
|
|
|
|
printf(" current size: %d\n", scan.currentSize);
|
|
printf(" maximal size: %d\n", scan.maximalSize);
|
|
printf(" total used: %d\n", scan.endPosition);
|
|
printf(" # of entries: %d\n", scan.numberMarkers);
|
|
printf(" status: %d\n", scan.status);
|
|
printf(" isSealed: %s\n", scan.isSealed ? "yes" : "no");
|
|
|
|
// set default value to unknown
|
|
var statusMessage = "UNKNOWN (" + scan.status + ")";
|
|
var color = internal.COLORS.COLOR_YELLOW;
|
|
|
|
switch (scan.status) {
|
|
case 1:
|
|
statusMessage = "OK";
|
|
color = internal.COLORS.COLOR_GREEN;
|
|
if (! scan.isSealed && type === "datafile") {
|
|
color = internal.COLORS.COLOR_YELLOW;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
statusMessage = "NOT OK (reached empty marker)";
|
|
color = internal.COLORS.COLOR_RED;
|
|
break;
|
|
|
|
case 3:
|
|
statusMessage = "FATAL (reached corrupted marker)";
|
|
color = internal.COLORS.COLOR_RED;
|
|
break;
|
|
|
|
case 4:
|
|
statusMessage = "FATAL (crc failed)";
|
|
color = internal.COLORS.COLOR_RED;
|
|
break;
|
|
|
|
case 5:
|
|
statusMessage = "FATAL (cannot open datafile or too small)";
|
|
color = internal.COLORS.COLOR_RED;
|
|
break;
|
|
}
|
|
|
|
printf(color);
|
|
printf(" status: %s\n\n", statusMessage);
|
|
printf(internal.COLORS.COLOR_RESET);
|
|
|
|
if (scan.status !== 1) {
|
|
issues.push({
|
|
collection: collection.name(),
|
|
path: datafile,
|
|
type: type,
|
|
status: scan.status,
|
|
message: statusMessage,
|
|
color: color
|
|
});
|
|
}
|
|
|
|
if (scan.numberMarkers === 0) {
|
|
statusMessage = "datafile contains no entries";
|
|
color = internal.COLORS.COLOR_YELLOW;
|
|
|
|
issues.push({
|
|
collection: collection.name(),
|
|
path: datafile,
|
|
type: type,
|
|
status: scan.status,
|
|
message: statusMessage,
|
|
color: color
|
|
});
|
|
|
|
printf(color);
|
|
printf("WARNING: %s\n", statusMessage);
|
|
printf(internal.COLORS.COLOR_RESET);
|
|
RemoveDatafile(collection, type, datafile);
|
|
return;
|
|
}
|
|
|
|
const TRI_DF_MARKER_HEADER = 10;
|
|
const TRI_DF_MARKER_COL_HEADER = 20;
|
|
|
|
if (scan.entries[0].type !== TRI_DF_MARKER_HEADER) {
|
|
// asserting a TRI_DF_MARKER_HEADER as first marker
|
|
statusMessage = "datafile contains no datafile header marker at pos #0!";
|
|
color = internal.COLORS.COLOR_YELLOW;
|
|
|
|
issues.push({
|
|
collection: collection.name(),
|
|
path: datafile,
|
|
type: type,
|
|
status: scan.status,
|
|
message: statusMessage,
|
|
color: color
|
|
});
|
|
|
|
printf(color);
|
|
printf("WARNING: %s\n", statusMessage);
|
|
printf(internal.COLORS.COLOR_RESET);
|
|
RemoveDatafile(collection, type, datafile);
|
|
return;
|
|
}
|
|
|
|
if (scan.entries.length === 2 && scan.entries[1].type !== TRI_DF_MARKER_COL_HEADER) {
|
|
// asserting a TRI_COL_MARKER_HEADER as second marker
|
|
statusMessage = "datafile contains no collection header marker at pos #1!";
|
|
color = internal.COLORS.COLOR_YELLOW;
|
|
|
|
issues.push({
|
|
collection: collection.name(),
|
|
path: datafile,
|
|
type: type,
|
|
status: scan.status,
|
|
message: statusMessage,
|
|
color: color
|
|
});
|
|
|
|
printf(color);
|
|
printf("WARNING: %s\n", statusMessage);
|
|
printf(internal.COLORS.COLOR_RESET);
|
|
RemoveDatafile(collection, type, datafile);
|
|
return;
|
|
}
|
|
|
|
if (type !== "journal" && scan.entries.length === 3 && scan.entries[2].type === 0) {
|
|
// got the two initial header markers but nothing else...
|
|
statusMessage = "datafile is empty but not sealed";
|
|
color = internal.COLORS.COLOR_YELLOW;
|
|
|
|
issues.push({
|
|
collection: collection.name(),
|
|
path: datafile,
|
|
type: type,
|
|
status: scan.status,
|
|
message: statusMessage,
|
|
color: color
|
|
});
|
|
|
|
printf(color);
|
|
printf("WARNING: %s\n", statusMessage);
|
|
printf(internal.COLORS.COLOR_RESET);
|
|
RemoveDatafile(collection, type, datafile);
|
|
return;
|
|
}
|
|
|
|
if (details) {
|
|
// print details
|
|
printf("Entries\n");
|
|
if (details === "FULL") {
|
|
// print all markers
|
|
PrintEntries(scan.entries, scan.entries.length);
|
|
}
|
|
else {
|
|
// print an excerpt of the markers
|
|
PrintEntries(scan.entries, 10);
|
|
if (scan.entries.length > 20) {
|
|
printf("...\n");
|
|
}
|
|
PrintEntries(scan.entries, -10);
|
|
}
|
|
}
|
|
|
|
if (scan.status === 1 && scan.isSealed) {
|
|
return;
|
|
}
|
|
|
|
DeepCheckDatafile(collection, type, datafile, scan);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief checks a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function CheckCollection (collection, issues, details) {
|
|
printf("Database\n");
|
|
printf(" name: %s\n", internal.db._name());
|
|
printf(" path: %s\n", internal.db._path());
|
|
printf("\n");
|
|
|
|
printf("Collection\n");
|
|
printf(" name: %s\n", collection.name());
|
|
printf(" identifier: %s\n", collection._id);
|
|
printf("\n");
|
|
|
|
let datafiles = collection.datafiles();
|
|
|
|
printf("Datafiles\n");
|
|
printf(" # of journals: %d\n", datafiles.journals.length);
|
|
printf(" # of compactors: %d\n", datafiles.compactors.length);
|
|
printf(" # of datafiles: %d\n", datafiles.datafiles.length);
|
|
printf("\n");
|
|
|
|
for (let i = 0; i < datafiles.journals.length; ++i) {
|
|
CheckDatafile(collection, "journal", datafiles.journals[i], issues, details);
|
|
}
|
|
|
|
for (let i = 0; i < datafiles.datafiles.length; ++i) {
|
|
CheckDatafile(collection, "datafile", datafiles.datafiles[i], issues, details);
|
|
}
|
|
|
|
for (let i = 0; i < datafiles.compactors.length; ++i) {
|
|
CheckDatafile(collection, "compactor", datafiles.compactors[i], issues, details);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief select and check a collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function main (argv) {
|
|
printf("%s\n", " ___ _ __ _ _ ___ ___ ___ ");
|
|
printf("%s\n", " / \\__ _| |_ __ _ / _(_) | ___ / \\/ __\\ / _ \\");
|
|
printf("%s\n", " / /\\ / _` | __/ _` | |_| | |/ _ \\ / /\\ /__\\// / /_\\/");
|
|
printf("%s\n", " / /_// (_| | || (_| | _| | | __/ / /_// \\/ \\/ /_\\\\ ");
|
|
printf("%s\n", "/___,' \\__,_|\\__\\__,_|_| |_|_|\\___| /___,'\\_____/\\____/ ");
|
|
printf("\n");
|
|
|
|
if (!internal.db._engine().supports.dfdb) {
|
|
printf("\narango-dfdb is not intended to be used with this storage engine (" + internal.db._engine().name + ").\n");
|
|
return;
|
|
}
|
|
|
|
var databases = internal.db._databases();
|
|
var i;
|
|
|
|
var collectionSorter = function (l, r) {
|
|
var lName = l.name().toLowerCase();
|
|
var rName = r.name().toLowerCase();
|
|
|
|
if (lName !== rName) {
|
|
return lName < rName ? -1 : 1;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
var pad = function (s, l) {
|
|
if (s.length < l) {
|
|
s += Array(l - s.length).join(" ");
|
|
}
|
|
return s;
|
|
};
|
|
|
|
if (databases.length === 0) {
|
|
printf("No databases available. Exiting\n");
|
|
return;
|
|
}
|
|
|
|
databases.sort();
|
|
printf("Available databases:\n");
|
|
|
|
for (i = 0; i < databases.length; ++i) {
|
|
printf(" %d: %s\n", i, pad(databases[i], 4));
|
|
}
|
|
|
|
var line;
|
|
|
|
printf("Database to check: ");
|
|
while (true) {
|
|
line = console.getline();
|
|
|
|
if (line === "") {
|
|
printf("Exiting. Please wait.\n");
|
|
return;
|
|
}
|
|
else {
|
|
let l = parseInt(line);
|
|
|
|
if (l < 0 || l >= databases.length || l === null || l === undefined || isNaN(l)) {
|
|
printf("Please select a number between 0 and %d: ", databases.length - 1);
|
|
}
|
|
else {
|
|
internal.db._useDatabase(databases[l]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
|
|
var collections = internal.db._collections();
|
|
if (collections.length === 0) {
|
|
printf("No collections available. Exiting\n");
|
|
return;
|
|
}
|
|
|
|
// sort the collections
|
|
collections.sort(collectionSorter);
|
|
|
|
printf("Available collections:\n");
|
|
|
|
for (i = 0; i < collections.length; ++i) {
|
|
printf(" %d: %s (%s)\n", pad(i, 4), pad(collections[i].name(), 40), collections[i]._id);
|
|
}
|
|
|
|
printf(" *: all\n");
|
|
|
|
printf("\n");
|
|
|
|
printf("Collection to check: ");
|
|
var a = [];
|
|
|
|
while (true) {
|
|
line = console.getline();
|
|
|
|
if (line === "") {
|
|
printf("Exiting. Please wait.\n");
|
|
return;
|
|
}
|
|
|
|
if (line === "*") {
|
|
for (i = 0; i < collections.length; ++i) {
|
|
a.push(i);
|
|
}
|
|
|
|
break;
|
|
}
|
|
else {
|
|
let l = parseInt(line);
|
|
|
|
if (l < 0 || l >= collections.length || l === null || l === undefined || isNaN(l)) {
|
|
printf("Please select a number between 0 and %d: ", collections.length - 1);
|
|
}
|
|
else {
|
|
a.push(l);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
printf("Prints details (Y/N/full)? ");
|
|
|
|
var details = false;
|
|
|
|
while (true) {
|
|
line = console.getline();
|
|
|
|
if (line === "") {
|
|
printf("Exiting. Please wait.\n");
|
|
return;
|
|
}
|
|
|
|
line = line.toUpperCase();
|
|
if (line === "Y" || line === "YES") {
|
|
details = true;
|
|
}
|
|
else if (line === "F" || line === "FULL") {
|
|
details = "FULL";
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
var issues = [ ];
|
|
|
|
for (i = 0; i < a.length; ++i) {
|
|
var collection = collections[a[i]];
|
|
|
|
printf("Checking collection #%d: %s\n", a[i], collection.name());
|
|
printf("-------------------------------------------------------------------\n");
|
|
|
|
UnloadCollection(collection);
|
|
printf("\n");
|
|
|
|
CheckCollection(collection, issues, details);
|
|
}
|
|
|
|
|
|
if (issues.length > 0) {
|
|
// report issues
|
|
printf("\n%d issue(s) found:\n------------------------------------------\n", issues.length);
|
|
|
|
for (i = 0; i < issues.length; ++i) {
|
|
var issue = issues[i];
|
|
printf(" issue #%d\n collection: %s\n path: %s\n type: %s\n",
|
|
i,
|
|
issue.collection,
|
|
issue.path,
|
|
String(issue.type));
|
|
|
|
printf("%s", issue.color);
|
|
printf(" message: %s", issue.message);
|
|
printf("%s", internal.COLORS.COLOR_RESET);
|
|
printf("\n\n");
|
|
}
|
|
}
|
|
else {
|
|
printf("\nNo issues found.\n");
|
|
}
|
|
}
|