1
0
Fork 0

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:
Jan 2018-04-05 14:06:51 +02:00 committed by GitHub
parent bccf6bbe4e
commit ae0b99de63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 218 additions and 6 deletions

View File

@ -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.

View File

@ -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

View File

@ -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;