/* jshint strict: false, sub: true */ /* global print */ 'use strict'; // ////////////////////////////////////////////////////////////////////////////// // / DISCLAIMER // / // / Copyright 2016 ArangoDB 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 // ////////////////////////////////////////////////////////////////////////////// const internal = require('internal'); const fs = require('fs'); const tasks = require('org/arangodb/tasks'); const _ = require('lodash'); const executeExternalAndWait = require('internal').executeExternalAndWait; const db = internal.db; const sleep = internal.sleep; const optsDefault = { duration: 60, // in minutes gnuplot: false, results: 'results', runId: 'run' }; // ////////////////////////////////////////////////////////////////////////////// // / @brief create image using gnuplot // ////////////////////////////////////////////////////////////////////////////// function gnuplot () { executeExternalAndWait('gnuplot', 'out/kills.plot'); } // ////////////////////////////////////////////////////////////////////////////// // / @brief statistics generator // ////////////////////////////////////////////////////////////////////////////// let statisticsInitialized = false; const killsLog = fs.join('out', 'kills.csv'); const types = ['inserter', 'updater', 'remover', 'killer']; function statistics (opts) { if (!statisticsInitialized) { fs.makeDirectoryRecursive('out'); fs.write(killsLog, '# ' + types.join('\t') + '\n'); statisticsInitialized = true; } const results = opts.results; let runtime = Math.round(((new Date()) - opts.startTime) / 1000); let line = [runtime]; for (let i = 0; i < types.length; ++i) { const s = db._query( ` FOR u IN @@results FILTER u.type == @type RETURN u.statistics `, { '@results': results, 'type': types[i] }).toArray()[0]; if (s !== undefined) { line.push(s.count); line.push(s.success); line.push(s.failures); } else { line.push(0); line.push(0); line.push(0); } } fs.append(killsLog, line.join('\t') + '\n'); } // ////////////////////////////////////////////////////////////////////////////// // / @brief INSERTER // ////////////////////////////////////////////////////////////////////////////// exports.inserter = function (opts) { const runId = opts.runId; const duration = opts.duration; const start = Date.now(); const end = start + 1000 * 60 * duration; const results = opts.results; const r = db._collection(results); try { r.remove(runId); } catch (err) {} r.save({ _key: runId, started: true, finished: false, startDate: start, endDate: end, type: 'inserter', statistics: { count: 0, success: 0, failures: 0 } }); let count = 0; let success = 0; let failures = 0; let lastErr = ''; while (true) { ++count; if (count % 1000 === 0) { r.update(runId, { statistics: { count: count, success: success, failures: failures }, lastError: lastErr }); } let c = Math.floor(Math.random() * 8) + 2; let q = ` FOR doc IN 1..@c INSERT { _from: CONCAT("posts/test", doc), _to: CONCAT("posts/test", @c), value: doc } IN edges`; try { db._query({ query: q, bindVars: { c} }); ++success; } catch (err) { if (err.errorNum !== 1500) { lastErr = String(err); print(err); ++failures; } } if (Date.now() > end) { break; } } r.update(runId, { finished: true, statistics: { count: count, success: success, failures: failures } }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief UPDATER // ////////////////////////////////////////////////////////////////////////////// exports.updater = function (opts) { const runId = opts.runId; const duration = opts.duration; const start = Date.now(); const end = start + 1000 * 60 * duration; const results = opts.results; const r = db._collection(results); try { r.remove(runId); } catch (err) {} r.save({ _key: runId, started: true, finished: false, startDate: start, endDate: end, type: 'updater', statistics: { count: 0, success: 0, failures: 0 } }); let count = 0; let success = 0; let failures = 0; let lastErr = ''; while (true) { ++count; if (count % 1000 === 0) { r.update(runId, { statistics: { count: count, success: success, failures: failures } }); } let c = Math.floor(Math.random() * 8) + 2; let q = ` FOR doc IN edges SORT RAND() LIMIT @c UPDATE doc WITH { value: doc.value + 1 } IN edges`; try { db._query({ query: q, bindVars: { c} }); success++; } catch (err) { if (err.errorNum !== 1500) { lastErr = String(err); print(err); ++failures; } } if (Date.now() > end) { break; } } r.update(runId, { finished: true, statistics: { count: count, success: success, failures: failures } }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief REOMVER // ////////////////////////////////////////////////////////////////////////////// exports.remover = function (opts) { const runId = opts.runId; const duration = opts.duration; const start = Date.now(); const end = start + 1000 * 60 * duration; const results = opts.results; const r = db._collection(results); try { r.remove(runId); } catch (err) {} r.save({ _key: runId, started: true, finished: false, startDate: start, endDate: end, type: 'remover', statistics: { count: 0, success: 0, failures: 0 } }); let count = 0; let success = 0; let failures = 0; let lastErr = ''; while (true) { ++count; if (count % 1000 === 0) { r.update(runId, { statistics: { count: count, success: success, failures: failures } }); } let c = Math.floor(Math.random() * 8) + 2; let q = ` FOR doc IN edges SORT RAND() LIMIT @c REMOVE doc._key IN edges`; try { db._query({ query: q, bindVars: { c} }); success++; } catch (err) { if (err.errorNum !== 1500) { lastErr = String(err); print(err); ++failures; } } if (Date.now() > end) { break; } } r.update(runId, { finished: true, statistics: { count: count, success: success, failures: failures } }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief KILLER // ////////////////////////////////////////////////////////////////////////////// exports.killer = function (opts) { const runId = opts.runId; const duration = opts.duration; const start = Date.now(); const end = start + 1000 * 60 * duration; const results = opts.results; const r = db._collection(results); try { r.remove(runId); } catch (err) {} r.save({ _key: runId, started: true, finished: false, startDate: start, endDate: end, type: 'killer', statistics: { count: 0, success: 0, failures: 0 } }); let count = 0; let success = 0; let failures = 0; let lastErr = ''; const queries = require('org/arangodb/aql/queries'); while (true) { ++count; if (count % 100 === 0) { r.update(runId, { statistics: { count: count, success: success, failures: failures } }); } queries.current().forEach(function (q) { if (q.query.indexOf('edges') !== -1) { try { queries.kill(q.id); success++; } catch (err) { if (err.errorNum !== 1591) { lastErr = String(err); print(err); } ++failures; } } }); if (Date.now() > end) { break; } internal.wait(Math.random() * 0.1, false); } r.update(runId, { finished: true, statistics: { count: count, success: success, failures: failures } }); }; // ////////////////////////////////////////////////////////////////////////////// // / @brief killing // ////////////////////////////////////////////////////////////////////////////// exports.killingParallel = function (opts) { _.defaults(opts, optsDefault); // start time opts.startTime = new Date(); // create the test collections db._drop('edges'); db._createEdgeCollection('edges'); db._drop('posts'); db._create('posts'); // create the "results" collection const results = opts.results; db._drop(results); db._create(results); // create output directory fs.makeDirectoryRecursive('out'); // start worker const w = [ function (params) { require('./js/server/tests/stress/killingQueries').inserter(params); }, function (params) { require('./js/server/tests/stress/killingQueries').updater(params); }, function (params) { require('./js/server/tests/stress/killingQueries').remover(params); }, function (params) { require('./js/server/tests/stress/killingQueries').killer(params); } ]; for (let i = 0; i < w.length; ++i) { const cmd = w[i]; let o = JSON.parse(JSON.stringify(opts)); o.runId = 'run_' + i; tasks.register({ id: 'stress' + i, name: 'stress test ' + i, offset: i, params: o, command: cmd }); } // wait for a result const countDone = function () { const a = db._query('FOR u IN @@results FILTER u.finished RETURN 1', { '@results': 'results' }); const b = db._query('FOR u IN @@results FILTER u.started RETURN 1', { '@results': 'results' }); return a.count() === b.count(); }; let m = 0; for (let i = 0; i < 10; ++i) { m = db._query('FOR u IN @@results FILTER u.started RETURN 1', { '@results': 'results' }).count(); print(m + ' workers are up and running'); if (m === w.length) { break; } sleep(30); } if (m < w.length) { print('cannot start enough workers (want', w.length + ',', 'got', m + '),', 'please check number V8 contexts'); throw new Error('cannot start workers'); } if (opts.gnuplot) { fs.write(fs.join('out', 'kills.plot'), ` set terminal png size 1024,1024 set size ratio 0.3 set output "out/kills.png" set multiplot layout 4, 1 title "Killing Queries Stress Test" font ",14" set autoscale set logscale y set key outside set xlabel "runtime in seconds" set ylabel "inserter" plot \ "out/kills.csv" using 1:2 title "count" with lines lw 2, \ '' using 1:3 title "success" with lines, \ '' using 1:4 title "failures" with lines set ylabel "updater" plot \ "out/kills.csv" using 1:5 title "count" with lines lw 2, \ '' using 1:6 title "success" with lines, \ '' using 1:7 title "failures" with lines set ylabel "remover" plot \ "out/kills.csv" using 1:8 title "count" with lines lw 2, \ '' using 1:9 title "success" with lines, \ '' using 1:10 title "failures" with lines set ylabel "killer" plot \ "out/kills.csv" using 1:11 title "count" with lines lw 2, \ '' using 1:12 title "success" with lines, \ '' using 1:13 title "failures" with lines `); } let count = 0; while (!countDone()) { ++count; statistics(opts); if (opts.gnuplot && count % 6 === 0) { print('generating image'); gnuplot(); } sleep(10); } statistics(opts); if (opts.gnuplot) { gnuplot(); } return true; };