/*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 time = internal.time; const optsDefault = { concurrency: 1, duration: 10, // in minutes gnuplot: false, results: "results", runId: "run", slowBarrier: 1 }; //////////////////////////////////////////////////////////////////////////////// /// @brief collection numbers //////////////////////////////////////////////////////////////////////////////// const c = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; //////////////////////////////////////////////////////////////////////////////// /// @brief create image using gnuplot //////////////////////////////////////////////////////////////////////////////// function gnuplot() { executeExternalAndWait("gnuplot", "out/locks.plot"); } //////////////////////////////////////////////////////////////////////////////// /// @brief statistics generator //////////////////////////////////////////////////////////////////////////////// let statisticsInitialized = false; const locksLog = fs.join("out", "locks.csv"); const locksKeys = [ "runtime", "deadlocks", "success", "slow" ]; function statistics(opts) { if (!statisticsInitialized) { fs.makeDirectoryRecursive("out"); fs.write(locksLog, "# " + locksKeys.join("\t") + "\n"); statisticsInitialized = true; } const results = opts.results; const s = db._query( ` FOR u IN @@results COLLECT g = 1 AGGREGATE deadlocks = SUM(u.statistics.deadlocks), success = SUM(u.statistics.success), slow = SUM(u.statistics.slow) RETURN { deadlocks: deadlocks, success: success, slow: slow} `, { '@results': results }).toArray()[0]; if (s !== undefined) { s.runtime = Math.round(((new Date()) - opts.startTime) / 1000); fs.append(locksLog, locksKeys.map(function(x) { return s[x]; }).join("\t") + "\n"); } } //////////////////////////////////////////////////////////////////////////////// /// @brief lock cycle //////////////////////////////////////////////////////////////////////////////// exports.lockCycleRaw = function(opts) { const slowBarrier = opts.slowBarrier; 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) {} let deadlocks = 0; let success = 0; let slow = 0; let count = 0; r.save({ _key: runId, started: true, finished: false, startDate: start, endDate: end, statistics: { deadlocks: 0, success: 0, slow: 0 } }); const randomSort = function( /* l, r */ ) { return -0.5 + Math.random(); }; const transactionFunction = function() { return require("internal").db.c2.any(); }; while (true) { ++count; if (count % 1000 === 0) { r.update(runId, { statistics: { count: count, deadlocks: deadlocks, success: success, slow: slow } }); } try { var read = []; var write = []; var n = Math.floor(Math.random() * 7) + 2; c.sort(randomSort); for (let i = 0; i < n; ++i) { if (Math.random() > 0.8) { write.push("c" + c[i]); } else { read.push("c" + c[i]); } } const obj = { collections: { read, write }, action: transactionFunction }; const s = time(); db._executeTransaction(obj); if (time() - s > slowBarrier) { ++slow; } ++success; } catch (err) { ++deadlocks; } if (Date.now() > end) { break; } } r.update(runId, { finished: true, statistics: { count: count, deadlocks: deadlocks, success: success, slow: slow } }); }; //////////////////////////////////////////////////////////////////////////////// /// @brief lock cycle driver //////////////////////////////////////////////////////////////////////////////// exports.lockCycleParallel = function(opts) { _.defaults(opts, optsDefault); // start time opts.startTime = new Date(); // create the test collections c.forEach(function(i) { const name = "c" + i; db._drop(name); db._create(name); }); // create the "results" collection const results = opts.results; db._drop(results); db._create(results); // create output directory fs.makeDirectoryRecursive("out"); // start worker let n = opts.concurrency; print("Starting", n, "worker"); const cmd = function(params) { require("./js/server/tests/stress/deadlock").lockCycleRaw(params); }; for (let i = 0; i < n; ++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 === n) { break; } sleep(30); } if (m < n) { print("cannot start enough workers (want", n + ",", "got", m + "),", "please check number V8 contexts"); throw new Error("cannot start workers"); } if (opts.gnuplot) { fs.write(fs.join("out", "locks.plot"), ` set terminal png size 1024,1024 set size ratio 0.8 set output "out/locks.png" set autoscale set logscale y set xlabel "runtime in seconds" set ylabel "numbers" set key outside plot \ "out/locks.csv" using 1:2 title "deadlocks" with lines, \ '' using 1:3 title "success" with lines, \ '' using 1:4 title "slow" 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; };