mirror of https://gitee.com/bigwinds/arangodb
added ArangoShell helper function for packaging all information about an AQL query so it can be run and analyzed elsewhere: (#5010)
query = "FOR doc IN @@collection FILTER doc.value > @value RETURN doc"; bind = { value: 42, "@collection": "mycollection" }; options = { examples: 10, anonymize: true }; require("@arangodb/aql/explainer").debugDump("/tmp/query-debug-info", query, bind, options); Entitled users can send the generated file to the ArangoDB support to facilitate reproduction and debugging. The data from the generated file can be restored and analyzed via the *inspectDump* function: require("@arangodb/aql/explainer").inspectDump("/tmp/query-debug-info");
This commit is contained in:
parent
bccf6bbe4e
commit
ae0b99de63
11
CHANGELOG
11
CHANGELOG
|
@ -1,6 +1,15 @@
|
|||
v3.3.6 (2018-XX-XX)
|
||||
v3.3.6 (XXXX-XX-XX)
|
||||
-------------------
|
||||
|
||||
* added ArangoShell helper function for packaging all information about an
|
||||
AQL query so it can be run and analyzed elsewhere:
|
||||
|
||||
query = "FOR doc IN mycollection FILTER doc.value > 42 RETURN doc";
|
||||
require("@arangodb/aql/explainer").debugDump("/tmp/query-debug-info", query);
|
||||
|
||||
Entitled users can send the generated file to the ArangoDB support to facilitate
|
||||
reproduction and debugging.
|
||||
|
||||
* added hidden option `--server.ask-jwt-secret`. This is an internal option
|
||||
for debugging and should not be exposed to end-users.
|
||||
|
||||
|
|
|
@ -122,3 +122,51 @@ The above command prints the query's execution plan in the ArangoShell directly,
|
|||
on the most important information.
|
||||
|
||||
|
||||
### Gathering debug information about a query
|
||||
|
||||
If an explain provides no suitable insight into why a query does not perform as
|
||||
expected, it may be reported to the ArangoDB support. In order to make this as easy
|
||||
as possible, there is a built-in command in ArangoShell for packaging the query, its
|
||||
bind parameters and all data required to execute the query elsewhere.
|
||||
|
||||
The command will store all data in a file with a configurable filename:
|
||||
|
||||
@startDocuBlockInline 10_workWithAQL_debugging1
|
||||
@EXAMPLE_ARANGOSH_OUTPUT{10_workWithAQL_debugging1}
|
||||
var query = "FOR doc IN mycollection FILTER doc.value > 42 RETURN doc";
|
||||
require("@arangodb/aql/explainer").debugDump("/tmp/query-debug-info", query);
|
||||
@END_EXAMPLE_ARANGOSH_OUTPUT
|
||||
@endDocuBlock 10_workWithAQL_debugging1
|
||||
|
||||
Entitled users can send the generated file to the ArangoDB support to facilitate
|
||||
reproduction and debugging.
|
||||
|
||||
If a query contains bind parameters, they will need to specified along with the query
|
||||
string:
|
||||
|
||||
@startDocuBlockInline 10_workWithAQL_debugging2
|
||||
@EXAMPLE_ARANGOSH_OUTPUT{10_workWithAQL_debugging2}
|
||||
var query = "FOR doc IN @@collection FILTER doc.value > @value RETURN doc";
|
||||
var bind = { value: 42, "@collection": "mycollection" };
|
||||
require("@arangodb/aql/explainer").debugDump("/tmp/query-debug-info", query, bind);
|
||||
@END_EXAMPLE_ARANGOSH_OUTPUT
|
||||
@endDocuBlock 10_workWithAQL_debugging2
|
||||
|
||||
It is also possible to include example documents from the underlying collection in
|
||||
order to make reproduction even easier. Example documents can be sent as they are, or
|
||||
in an anonymized form. The number of example documents can be specified in the *examples*
|
||||
options attribute, and should generally be kept low. The *anonymize* option will replace
|
||||
the contents of string attributes in the examples with "XXX". It will however not
|
||||
replace any other types of data (e.g. numeric values) or attribute names. Attribute
|
||||
names in the examples will always be preserved because they may be indexed and used in
|
||||
queries:
|
||||
|
||||
@startDocuBlockInline 10_workWithAQL_debugging3
|
||||
@EXAMPLE_ARANGOSH_OUTPUT{10_workWithAQL_debugging3}
|
||||
var query = "FOR doc IN @@collection FILTER doc.value > @value RETURN doc";
|
||||
var bind = { value: 42, "@collection": "mycollection" };
|
||||
var options = { examples: 10, anonymize: true };
|
||||
require("@arangodb/aql/explainer").debugDump("/tmp/query-debug-info", query, bind, options);
|
||||
@END_EXAMPLE_ARANGOSH_OUTPUT
|
||||
@endDocuBlock 10_workWithAQL_debugging3
|
||||
|
||||
|
|
|
@ -1254,7 +1254,7 @@ function processQuery (query, explain) {
|
|||
printWarnings(explain.warnings);
|
||||
}
|
||||
|
||||
/* the exposed function */
|
||||
/* the exposed explain function */
|
||||
function explain(data, options, shouldPrint) {
|
||||
'use strict';
|
||||
if (typeof data === 'string') {
|
||||
|
@ -1270,9 +1270,8 @@ function explain (data, options, shouldPrint) {
|
|||
options = options || { };
|
||||
setColors(options.colors === undefined ? true : options.colors);
|
||||
|
||||
var stmt = db._createStatement(data);
|
||||
|
||||
var result = stmt.explain(options);
|
||||
let stmt = db._createStatement(data);
|
||||
let result = stmt.explain(options);
|
||||
|
||||
stringBuilder.clearOutput();
|
||||
processQuery(data.query, result, true);
|
||||
|
@ -1284,4 +1283,160 @@ function explain (data, options, shouldPrint) {
|
|||
}
|
||||
}
|
||||
|
||||
/* the exposed debug function */
|
||||
function debug(query, bindVars, options) {
|
||||
'use strict';
|
||||
let input = {};
|
||||
if (query instanceof Object) {
|
||||
if (typeof query.toAQL === 'function') {
|
||||
query = query.toAQL();
|
||||
}
|
||||
input = query;
|
||||
} else {
|
||||
input.query = query;
|
||||
if (bindVars !== undefined) {
|
||||
input.bindVars = bindVars;
|
||||
}
|
||||
if (options !== undefined) {
|
||||
input.options = options;
|
||||
}
|
||||
}
|
||||
if (!input.options) {
|
||||
input.options = {};
|
||||
}
|
||||
|
||||
let anonymize = function(doc) {
|
||||
if (Array.isArray(doc)) {
|
||||
return doc.map(anonymize);
|
||||
}
|
||||
if (typeof doc === 'string') {
|
||||
return Array(doc.length).join("X");
|
||||
}
|
||||
if (doc === null || typeof doc === 'number' || typeof doc === 'boolean') {
|
||||
return doc;
|
||||
}
|
||||
if (typeof doc === 'object') {
|
||||
let result = {};
|
||||
Object.keys(doc).forEach(function(key) {
|
||||
result[key] = anonymize(doc[key]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return doc;
|
||||
};
|
||||
|
||||
let result = {
|
||||
engine: db._engine(),
|
||||
version: db._version(true),
|
||||
query: input,
|
||||
collections: {}
|
||||
};
|
||||
|
||||
result.fancy = require("@arangodb/aql/explainer").explain(input, { colors: false }, false);
|
||||
|
||||
let stmt = db._createStatement(input);
|
||||
result.explain = stmt.explain(input.options);
|
||||
|
||||
// add collection information
|
||||
result.explain.plan.collections.forEach(function(collection) {
|
||||
let c = db._collection(collection.name);
|
||||
let examples;
|
||||
if (input.options.examples) {
|
||||
// include example data from collections
|
||||
let max = 10; // default number of documents
|
||||
if (typeof input.options.examples === 'number') {
|
||||
max = input.options.examples;
|
||||
}
|
||||
if (max > 100) {
|
||||
max = 100;
|
||||
} else if (max < 0) {
|
||||
max = 0;
|
||||
}
|
||||
examples = db._query("FOR doc IN @@collection LIMIT @max RETURN doc", { max, "@collection": collection.name }).toArray();
|
||||
if (input.options.anonymize) {
|
||||
examples = examples.map(anonymize);
|
||||
}
|
||||
}
|
||||
result.collections[collection.name] = {
|
||||
type: c.type() === 2,
|
||||
properties: c.properties(),
|
||||
indexes: c.getIndexes(true),
|
||||
count: c.count(),
|
||||
counts: c.count(true),
|
||||
examples
|
||||
};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function debugDump(filename, query, bindVars, options) {
|
||||
let result = debug(query, bindVars, options);
|
||||
require("fs").write(filename, JSON.stringify(result));
|
||||
require("console").log("stored query debug information in file '" + filename + "'");
|
||||
}
|
||||
|
||||
function inspectDump(filename) {
|
||||
let data = JSON.parse(require("fs").read(filename));
|
||||
if (db._engine().name !== data.engine.name) {
|
||||
print("/* using different storage engine (' " + db._engine().name + "') than in debug information ('" + data.engine.name + "') */");
|
||||
}
|
||||
|
||||
// all collections and indexes first, as data insertion may go wrong later
|
||||
print("/* collections and indexes setup */");
|
||||
Object.keys(data.collections).forEach(function(collection) {
|
||||
let details = data.collections[collection];
|
||||
print("db._drop(" + JSON.stringify(collection) + ");");
|
||||
if (details.type === 3) {
|
||||
print("db._createEdgeCollection(" + JSON.stringify(collection) + ", " + JSON.stringify(details.properties) + ");");
|
||||
} else {
|
||||
print("db._create(" + JSON.stringify(collection) + ", " + JSON.stringify(details.properties) + ");");
|
||||
}
|
||||
details.indexes.forEach(function(index) {
|
||||
delete index.figures;
|
||||
delete index.selectivityEstimate;
|
||||
if (index.type !== 'primary' && index.type !== 'edge') {
|
||||
print("db[" + JSON.stringify(collection) + "].ensureIndex(" + JSON.stringify(index) + ");");
|
||||
}
|
||||
});
|
||||
print();
|
||||
});
|
||||
print();
|
||||
|
||||
// insert example data
|
||||
print("/* example data */");
|
||||
Object.keys(data.collections).forEach(function(collection) {
|
||||
let details = data.collections[collection];
|
||||
if (details.examples) {
|
||||
details.examples.forEach(function(example) {
|
||||
print("db[" + JSON.stringify(collection) + "].insert(" + JSON.stringify(example) + ");");
|
||||
});
|
||||
}
|
||||
let missing = details.count;
|
||||
if (details.examples) {
|
||||
missing -= details.examples.length;
|
||||
}
|
||||
if (missing > 0) {
|
||||
print("/* collection '" + collection + "' needs " + missing + " more document(s) */");
|
||||
}
|
||||
print();
|
||||
});
|
||||
print();
|
||||
|
||||
print("/* explain result */");
|
||||
print(data.fancy.trim().split(/\n/).map(function(line) { return "// " + line; }).join("\n"));
|
||||
print();
|
||||
|
||||
print("/* explain command */");
|
||||
if (data.query.options) {
|
||||
delete data.query.options.anonymize;
|
||||
delete data.query.options.colors;
|
||||
delete data.query.options.examples;
|
||||
}
|
||||
print("db._explain(" + JSON.stringify(data.query) + ");");
|
||||
print();
|
||||
}
|
||||
|
||||
exports.explain = explain;
|
||||
exports.debug = debug;
|
||||
exports.debugDump = debugDump;
|
||||
exports.inspectDump = inspectDump;
|
||||
|
|
Loading…
Reference in New Issue