├── .gitignore ├── .prettierignore ├── src ├── lib │ ├── combine.js │ ├── index.js │ ├── get.js │ ├── utils.js │ └── collect.js ├── commands.js └── actions.js ├── index.js ├── prettier.config.js ├── bin └── index.js ├── .eslintrc.json ├── .vscode └── launch.json ├── .gitlab-ci.yml └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | index.js 3 | src/ -------------------------------------------------------------------------------- /src/lib/combine.js: -------------------------------------------------------------------------------- 1 | function combine () {} 2 | 3 | module.exports = combine 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | register: require('./src/commands'), 3 | lib: require('./src/lib') 4 | } 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'es5', 4 | semi: false, 5 | printWidth: 110, 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get: require('./get'), 3 | collect: require('./collect'), 4 | combine: require('./combine') 5 | } 6 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const prog = require('caporal') 4 | const register = require('../src/commands') 5 | 6 | prog 7 | .bin('shardus-debug') 8 | .name('Shardus Debug') 9 | .version('1.0.0') 10 | 11 | for (const command in register) { 12 | register[command](prog) 13 | } 14 | 15 | prog.parse(process.argv) 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true 5 | }, 6 | "plugins": ["security"], 7 | "extends": ["eslint:recommended", "plugin:security/recommended", "prettier"], 8 | "ignorePatterns": [], 9 | "rules": { 10 | "no-empty": [ 11 | 1, 12 | { 13 | "allowEmptyCatch": true 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/get.js: -------------------------------------------------------------------------------- 1 | const { ensureExists, streamExtractFile } = require('./utils') 2 | 3 | async function get (instanceUrl, instanceDir, progressFn) { 4 | // Ensure that the instanceDir exists 5 | await ensureExists(instanceDir) 6 | 7 | // Stream and extract tar.gz debug file into instance dir 8 | await streamExtractFile(`http://${instanceUrl}/debug`, instanceDir, progressFn) 9 | } 10 | 11 | module.exports = get 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "attach", 10 | "name": "Attach by Process ID", 11 | "processId": "${command:PickProcess}" 12 | }, 13 | 14 | 15 | { 16 | "type": "node", 17 | "request": "launch", 18 | "name": "Launch Program", 19 | "program": "${workspaceFolder}/index.js" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: 'registry.gitlab.com/shardus/dev-container' 2 | 3 | before_script: 4 | - node -v 5 | 6 | stages: 7 | - build 8 | 9 | cache: 10 | paths: 11 | - node_modules/ 12 | 13 | # Build Job: Compiles the code 14 | install-job: 15 | stage: build 16 | script: 17 | - echo "Clean install of project dependencies..." 18 | - npm ci # Clean install of project dependencies 19 | - echo "Install complete." 20 | 21 | # Lint Job: Runs ESLint for code linting 22 | lint-job: 23 | stage: build 24 | script: 25 | - echo "Running ESlint..." 26 | - npm run lint 27 | - echo "Running ESlint complete." 28 | 29 | # Format Checker Job: Runs a code formatter 30 | format-checker-job: 31 | stage: build 32 | script: 33 | - echo "Running code formatter..." 34 | - npm run format-check 35 | - echo "Code formatting complete." 36 | -------------------------------------------------------------------------------- /src/commands.js: -------------------------------------------------------------------------------- 1 | const actions = require('./actions') 2 | 3 | const register = { 4 | get (prog, namespace) { 5 | prog 6 | .command(`${namespace ? namespace + ' ' : ''}get`, 'Get debug data for the given instance') 7 | .argument('', 'URL of the instance to download debug data from') 8 | .argument('[instanceDir]', 'Path to unpack the instances debug data into. Unpacks into current path if not given') 9 | .action(actions.get) 10 | }, 11 | collect (prog, namespace) { 12 | prog 13 | .command(`${namespace ? namespace + ' ' : ''}collect`, 'Collect debug data of all network instances') 14 | .argument('', 'URL of an instance in the network to download debug info from') 15 | .argument('[networkDir]', 'Path to put all network instances debug info into. Uses current path if not given') 16 | .action(actions.collect) 17 | }, 18 | combine (prog, namespace) { 19 | prog 20 | .command(`${namespace ? namespace + ' ' : ''}combine`, 'Combine the logs of all instances in a test net') 21 | .argument('', 'A path containing multiple instanceDirs with logs') 22 | .action(actions.combine) 23 | } 24 | } 25 | 26 | module.exports = register 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shardus/debug-tool", 3 | "version": "1.0.2", 4 | "description": "Shardus application debugging tools.", 5 | "main": "index.js", 6 | "scripts": { 7 | "release": "np --no-tests --no-yarn", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint", 10 | "format-check": "prettier --check '**.js'" 11 | }, 12 | "bin": { 13 | "shardus-debug": "./bin/index.js" 14 | }, 15 | "author": "Aamir Syed", 16 | "license": "ISC", 17 | "dependencies": { 18 | "caporal": "1.3.0", 19 | "got": "11.8.5", 20 | "tar": "4.4.18", 21 | "eslint": "8.43.0" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "devDependencies": { 27 | "np": "7.6.0", 28 | "standard": "13.1.0", 29 | "eslint-config-prettier": "8.8.0", 30 | "eslint-plugin-security": "1.7.1", 31 | "prettier": "2.8.8" 32 | }, 33 | "overrides": { 34 | "word-wrap": "1.2.5", 35 | "tar": "4.4.18", 36 | "ansi-regex": "3.0.1 || 4.1.1", 37 | "hosted-git-info": "2.8.9", 38 | "http-cache-semantics": "4.1.1", 39 | "minimist": "0.2.4 || 1.2.6", 40 | "normalize-url": "4.5.1", 41 | "got": "11.8.5", 42 | "semver": "5.7.2 || 6.3.1 || 7.5.2" 43 | } 44 | } -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | const lib = require('./lib') 2 | 3 | async function get (args, options, logger) { 4 | try { 5 | await lib.get(args['instanceUrl'], args['instanceDir'] || process.cwd(), ({ url, savePath, transferred }) => { 6 | process.stdout.clearLine() 7 | process.stdout.cursorTo(0) 8 | process.stdout.write(`Downloading from ${url} into ${savePath} (${transferred} B)`) 9 | }) 10 | process.stdout.write('\n') 11 | } catch (e) { 12 | logger.error('Error: ' + e.message) 13 | } 14 | } 15 | 16 | async function collect (args, options, logger) { 17 | try { 18 | await lib.collect(args['instanceUrl'], args['networkDir'] || process.cwd(), ({ currentNode, totalNodes, url, savePath, transferred }) => { 19 | process.stdout.clearLine() 20 | process.stdout.cursorTo(0) 21 | process.stdout.write(`(${currentNode}/${totalNodes}) Downloading from ${url} into ${savePath} (${transferred} B)`) 22 | }) 23 | process.stdout.write('\n') 24 | } catch (e) { 25 | logger.error('Error: ' + e.message) 26 | } 27 | } 28 | 29 | function combine (args, options, logger) { 30 | try { 31 | lib.combine(args['networkDir']) 32 | } catch (e) { 33 | logger.error('Error: ' + e.message) 34 | } 35 | } 36 | 37 | exports.get = get 38 | exports.collect = collect 39 | exports.combine = combine 40 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const got = require('got') 4 | const tar = require('tar') 5 | 6 | // From: https://stackoverflow.com/a/21196961 7 | function ensureExists (dir) { 8 | return new Promise((resolve, reject) => { 9 | fs.mkdir(dir, { recursive: true }, (err) => { 10 | if (err) { 11 | // Ignore err if folder exists 12 | if (err.code === 'EEXIST') resolve() 13 | // Something else went wrong 14 | else reject(err) 15 | } else { 16 | // Successfully created folder 17 | resolve() 18 | } 19 | }) 20 | }) 21 | } 22 | 23 | function streamExtractFile (url, savePath, progressFn) { 24 | return new Promise((resolve, reject) => { 25 | const download = got.stream(url, { decompress: false }) 26 | download.on('error', reject) 27 | download.on('downloadProgress', progress => { 28 | const normalized = path.normalize(path.relative(process.cwd(), savePath)) 29 | progressFn({ url, savePath: normalized, ...progress }) 30 | }) 31 | 32 | const out = tar.extract({ cwd: savePath }) 33 | out.on('error', reject) 34 | out.on('close', resolve) 35 | 36 | download.pipe(out) 37 | }) 38 | } 39 | 40 | exports.ensureExists = ensureExists 41 | exports.streamExtractFile = streamExtractFile 42 | -------------------------------------------------------------------------------- /src/lib/collect.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const got = require('got') 3 | const { ensureExists, streamExtractFile } = require('./utils') 4 | 5 | async function collect (instanceUrl, networkDir, progressFn) { 6 | // Ensure that the networkDir exists 7 | await ensureExists(networkDir) 8 | 9 | // Get nodelist 10 | const nodelistUrl = new URL('/nodelist', `http://${instanceUrl}`) 11 | const { body: { nodelist } } = await got.get(nodelistUrl.toString(), { json: true }) 12 | 13 | // Stream and extract tar.gz debug file into each instances dir 14 | for (const [index, node] of nodelist.entries()) { 15 | // Hacks for the case when a remote server is running local instances 16 | if (instanceUrl.split(':').length === 2) instanceUrl = instanceUrl.split(':')[0] 17 | if (node.externalIp === '127.0.0.1' && instanceUrl !== '127.0.0.1') node.externalIp = instanceUrl 18 | 19 | const debugUrl = new URL('/debug', `http://${node.externalIp}:${node.externalPort}`) 20 | const savePath = path.join(networkDir, `debug-${debugUrl.hostname}-${debugUrl.port}`) 21 | await ensureExists(savePath) 22 | try { 23 | await streamExtractFile(debugUrl.toString(), savePath, progress => progressFn({ currentNode: index + 1, totalNodes: nodelist.length, ...progress })) 24 | } catch (err) { 25 | console.log('ERR: ', `http://${node.externalIp}:${node.externalPort} Connection refused`) 26 | continue 27 | } 28 | } 29 | } 30 | 31 | module.exports = collect 32 | --------------------------------------------------------------------------------