Repository: simonepri/sympact
Branch: master
Commit: ff60a2325afa
Files: 20
Total size: 38.9 KB
Directory structure:
gitextract_ri8w7s5_/
├── .appveyor.yml
├── .codecov.yml
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── code-of-conduct.md
│ ├── contributing.md
│ ├── issue-template.md
│ └── pull-request-template.md
├── .gitignore
├── .npmrc
├── .travis.yml
├── cli.js
├── index.js
├── lib/
│ ├── profiler.js
│ ├── vm.js
│ └── worker.js
├── license
├── package.json
├── readme.md
└── test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .appveyor.yml
================================================
build: off
version: '{build}'
environment:
matrix:
- nodejs_version: '8'
- nodejs_version: '10'
platform:
- x86
- x64
cache:
- node_modules
install:
- ps: Install-Product node $env:nodejs_version
- npm install
test_script:
- node --version
- npm --version
- npm test
after_test:
- npx codecov
================================================
FILE: .codecov.yml
================================================
parsers:
javascript:
enable_partials: yes
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .gitattributes
================================================
* text=auto
*.js text eol=lf
================================================
FILE: .github/code-of-conduct.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [simonepri@outlook.com](mailto:simonepri@outlook.com). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: .github/contributing.md
================================================
# Contributing
First and foremost, thank you! We appreciate that you want to contribute to this project, your time is valuable, and your contributions mean a lot to us.
## Important!
By contributing to this project, you:
- Agree that you have authored 100% of the content
- Agree that you have the necessary rights to the content
- Agree that you have received the necessary permissions from your employer to make the contributions (if applicable)
- Agree that the content you contribute may be provided under the Project license(s)
## Getting started
**What does "contributing" mean?**
Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following:
- Updating or correcting documentation
- Feature requests
- Bug reports
If you'd like to learn more about contributing in general, the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) has a lot of useful information.
## Issues
### Before creating an issue
Please try to determine if the issue is caused by an underlying library, and if so, create the issue there. Sometimes this is difficult to know. We only ask that you attempt to give a reasonable attempt to find out. Oftentimes the readme will have advice about where to go to create issues.
Try to follow these guidelines
- **Avoid creating issues for implementation help**. It's much better for discoverability, SEO, and semantics - to keep the issue tracker focused on bugs and feature requests - to ask implementation-related questions on [so](stackoverflow.com)
- **Investigate the issue**:
- **Check the readme** - oftentimes you will find notes about creating issues, and where to go depending on the type of issue.
- Create the issue in the appropriate repository.
### Creating an issue
Please be as descriptive as possible when creating an issue. Give us the information we need to successfully answer your question or address your issue by answering the following in your issue:
- **version**: please note the version of the project are you using
- **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using
- **error messages**: please paste any error messages into the issue, or a [gist](https://gist.github.com/)
### Closing issues
The original poster or the maintainer's of the project may close an issue at any time. Typically, but not exclusively, issues are closed when:
- The issue is resolved
- The project's maintainers have determined the issue is out of scope
- An issue is clearly a duplicate of another issue, in which case the duplicate issue will be linked.
- A discussion has clearly run its course
## Next steps
**Tips for creating idiomatic issues**
Spending just a little extra time to review best practices and brush up on your contributing skills will, at minimum, make your issue easier to read, easier to resolve, and more likely to be found by others who have the same or similar issue in the future. At best, it will open up doors and potential career opportunities by helping you be at your best.
The following resources were hand-picked to help you be the most effective contributor you can be:
- The [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) is a great place for newcomers to start, but there is also information for experienced contributors there.
- Take some time to learn basic markdown. We can't stress this enough. Don't start pasting code into GitHub issues before you've taken a moment to review this [markdown cheatsheet](https://gist.github.com/jonschlinkert/5854601)
- The GitHub guide to [basic markdown](https://help.github.com/articles/markdown-basics/) is another great markdown resource.
- Learn about [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/). And if you want to really go above and beyond, read [mastering markdown](https://guides.github.com/features/mastering-markdown/).
At the very least, please try to:
- Use backticks to wrap code. This ensures that it retains its formatting and isn't modified when it's rendered by GitHub, and makes the code more readable to others
- When applicable, use syntax highlighting by adding the correct language name after the first "code fence"
================================================
FILE: .github/issue-template.md
================================================
<!--
Thanks for reporting an Issue!
If you haven't already read the contributing guidelines,
Please do that now then proceed.
-->
## I'm submitting a
- [ ] bug report
- [ ] feature request
- [ ] support request => Please do not submit support request here, see note at the top of this template.
<!-- Please mark with [x] the appropriate answer. -->
## Checklist
- [ ] Searched both open and closed issues for duplicates of this issue
- [ ] Title adequately and *concisely* reflects the feature or the bug
<!-- Please mark with [x] the appropriate answer. -->
## Information
<!--
Please provide detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc
-->
================================================
FILE: .github/pull-request-template.md
================================================
<!--
Thanks for submitting a Pull Request!
If you haven't already read the contributing guidelines,
Please do that now then proceed.
-->
## Checklist
- [ ] All tests are passing
- [ ] New tests were created to address changes in pr (and tests are passing)
- [ ] Updated README and/or documentation, if necessary
- [ ] Added myself / the `contributors` array in package.json
<!-- Please mark with [x] the appropriate answer. -->
## Changes
<!--
Please describe the purpose of the pull request.
Thanks for contributing!
-->
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/linux,macos,windows,node
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/linux,macos,windows,node
================================================
FILE: .npmrc
================================================
package-lock=false
loglevel=silent
================================================
FILE: .travis.yml
================================================
sudo: false
language: node_js
node_js:
- '8'
- '10'
os:
- linux
- osx
cache:
directories:
- node_modules
script:
- node --version
- npm --version
- npm test
after_test:
- npx codecov
================================================
FILE: cli.js
================================================
#!/usr/bin/env node
/* eslint-disable promise/prefer-await-to-then */
'use strict';
const si = require('systeminformation');
const chalk = require('chalk');
const meow = require('meow');
const logSymbols = require('log-symbols');
const updateNotifier = require('update-notifier');
const Table = require('easy-table');
const impact = require('.');
const cli = meow(
`
Usage
$ impact <script>
Options
--raw Show the report as raw object instead of using
the GUI.
--interval <number> The sampling rate in milliseconds.
Examples
$ impact "console.log('Hello World')"
$ impact --interval=5 "console.log('Hello World')"
`,
{
flags: {
raw: {type: 'boolean', default: false},
interval: {type: 'number', default: 125},
cwd: {type: 'string', default: process.cwd()}
}
}
);
Promise.resolve()
.then(() => {
updateNotifier({pkg: cli.pkg}).notify();
})
.then(() => {
if (cli.input.length !== 1) {
cli.showHelp(0);
}
return impact(cli.input[0], {
interval: cli.flags.interval,
cwd: cli.flags.cwd
});
})
.then(async report => {
if (cli.flags.raw) {
return JSON.stringify(report, null, 2);
}
// SO INFO - BEGIN
const osInfo = await si.osInfo();
const soT = new Table();
soT.cell('Distro', `${osInfo.distro}`);
soT.cell('Release', `${osInfo.release}`);
soT.cell('Release', `${osInfo.release}`);
soT.cell('Platform', `${osInfo.platform}`);
soT.cell('Arch', `${osInfo.arch}`);
soT.newRow();
// SO INFO - END
// CPU INFO - BEGIN
const osCpu = await si.cpu();
const cpuT = new Table();
cpuT.cell('CPU', `${osCpu.manufacturer}`);
cpuT.cell('Brand', `${osCpu.brand}`);
cpuT.cell('Clock', `${osCpu.speed} GHz`);
cpuT.cell('Cores', `${osCpu.cores}`);
cpuT.newRow();
// CPU INFO - BEGIN
// MEM INFO - BEGIN
const osMemLayout = await si.memLayout();
const memT = new Table();
osMemLayout.forEach(mem => {
memT.cell('Memory', `${mem.manufacturer}`);
memT.cell('Type', `${mem.type}`);
memT.cell('Size', `${(mem.size / 1e6).toFixed(3)} MB`);
memT.cell('Clock', `${mem.clockSpeed} MHz`);
memT.newRow();
});
// MEM INFO - BEGIN
// CPU TABLE - BEGIN
const [cpu] = [report.stats.cpu];
const ctAvarageStr = chalk.keyword('orangered')('avarage');
const ctStdevStr = chalk.yellow('σ');
const ctAvarageVal = chalk.keyword('orangered')(cpu.mean.toFixed(2));
const ctStdevVal = chalk.yellow(cpu.stdev.toFixed(2));
const ctMinStr = chalk.cyan('min');
const ctMaxStr = chalk.red('max');
const ctMinVal = chalk.cyan(cpu.min.toFixed(2));
const ctMaxVal = chalk.red(cpu.max.toFixed(2));
const cuseT = new Table();
cuseT.cell(
`CPU Usage (${ctAvarageStr} ± ${ctStdevStr})`,
`${ctAvarageVal} % ± ${ctStdevVal} %`
);
cuseT.cell(
`CPU Usage Range (${ctMinStr} … ${ctMaxStr})`,
`${ctMinVal} % … ${ctMaxVal} %`
);
cuseT.newRow();
// CPU TABLE - END
// RAM TABLE - BEGIN
const [ram] = [report.stats.memory];
const rtAvarageStr = chalk.keyword('olivedrab')('avarage');
const rtStdevStr = chalk.yellow('σ');
const rtAvarageVal = chalk.keyword('olivedrab')(
(ram.mean / 1e6).toFixed(3)
);
const rtStdevVal = chalk.yellow((ram.stdev / 1e6).toFixed(3));
const rtMinStr = chalk.cyan('min');
const rtMaxStr = chalk.red('max');
const rtMinVal = chalk.cyan((ram.min / 1e6).toFixed(3));
const rtMaxVal = chalk.red((ram.max / 1e6).toFixed(3));
const museT = new Table();
museT.cell(
`RAM Usage (${rtAvarageStr} ± ${rtStdevStr})`,
`${rtAvarageVal} MB ± ${rtStdevVal} MB`
);
museT.cell(
`RAM Usage Range (${rtMinStr} … ${rtMaxStr})`,
`${rtMinVal} MB … ${rtMaxVal} MB`
);
museT.newRow();
// RAM TABLE - END
// SAMPLES INFO TABLE - BEGIN
const [times] = [report.times];
const [samples] = [report.samples];
const stVal = chalk.magenta(samples.count);
const stRealStr = chalk.keyword('deeppink')('real');
const stTargetStr = chalk.blue('target');
const stTargetFrequencyVal = chalk.blue((1000 / samples.period).toFixed(3));
const stRealFrequencyVal = chalk.keyword('deeppink')(
(
1000 /
((times.execution.end - times.execution.start) / samples.count)
).toFixed(3)
);
const stTargetPeriodVal = chalk.blue((samples.period / 1000).toFixed(3));
const stRealPeriodVal = chalk.keyword('deeppink')(
(
(times.execution.end - times.execution.start) /
samples.count /
1000
).toFixed(3)
);
const tt = new Table();
tt.cell(
'Execution time',
`${chalk.cyan(
((times.execution.end - times.execution.start) / 1000).toFixed(3)
)} s`
);
tt.cell(
'Sampling time',
`${chalk.cyan(
(times.sampling.end - times.sampling.start) / (1000).toFixed(3)
)} s`
);
tt.cell('Samples', `${stVal} samples`);
tt.newRow();
const st = new Table();
st.cell(
`Frequency (${stRealStr} ∴ ${stTargetStr})`,
`${stRealFrequencyVal} sample/s ∴ ${stTargetFrequencyVal} sample/s`
);
st.cell(
`Period (${stRealStr} ∴ ${stTargetStr})`,
`${stRealPeriodVal} s/sample ∴ ${stTargetPeriodVal} s/sample`
);
st.newRow();
// SAMPLES INFO TABLE - END
// SAMPLES - BEGIN
const frames = Object.keys(samples.list);
const samT = new Table();
frames.forEach(frame => {
const tfInstantVal = chalk.cyan((frame / 1000).toFixed(3));
const tfCPUVal = chalk.keyword('orangered')(
samples.list[frame].cpu.toFixed(2)
);
const tfRAMVal = chalk.keyword('olivedrab')(
(samples.list[frame].memory / 1e6).toFixed(3)
);
const tfPIDSVal = samples.list[frame].processes
.map(proc => chalk.gray(proc.pid))
.sort();
samT.cell('Instant', `${tfInstantVal} s`);
samT.cell('CPU Usage', `${tfCPUVal} %`);
samT.cell('RAM Usage', `${tfRAMVal} MB`);
samT.cell('PIDS', `${tfPIDSVal}`);
samT.newRow();
});
// SAMPLES - END
return (
`\n` +
`► ${chalk.bold('SYSTEM REPORT')}\n\n` +
`${soT}\n` +
`${cpuT}\n` +
`${memT}\n` +
`► ${chalk.bold('USAGE STATISTICS')}\n\n` +
`${cuseT}\n` +
`${museT}\n` +
`► ${chalk.bold('SAMPLING STATISTICS')}\n\n` +
`${tt}\n` +
`${st}\n` +
`► ${chalk.bold('SAMPLES')}\n\n` +
`${samT}`
);
})
.then(console.log)
.catch(err => {
console.error(`\n${logSymbols.error} ${err.stack}`);
process.exit(1);
});
================================================
FILE: index.js
================================================
'use strict';
const path = require('path');
const caller = require('caller');
const Worker = require('./lib/worker.js');
const Profiler = require('./lib/profiler.js');
/**
* Measures the impact of running a certain script on your system.
* Monitors the cpu and memory usage of the whole tree of processes generated by
* the script provided.
* @public
* @param {string} code The source code to test.
* @param {Object} [options] Optional configurations.
* @param {number} [options.interval=125] Sampling interval in milliseconds.
* @param {string} [options.cwd=caller] CWD for the script.
* @return {Promise.<Object>} An object containing the results.
*/
async function sympact(code, options) {
if (typeof code !== 'string') {
throw new TypeError("The 'code' paramater must a string'");
}
if (typeof options === 'undefined') {
options = {};
}
if (typeof options !== 'object') {
throw new TypeError("The 'options' paramater must an object'");
}
const interval = options.interval || 125;
const cwd = options.cwd || path.dirname(caller());
if (interval < 1) {
throw new TypeError("The 'interval' paramater must be greater than 0'");
}
return new Promise((resolve, reject) => {
const slave = new Worker(code, cwd);
const probe = new Profiler(slave.pid(), interval);
slave.on('ready', async () => {
await probe.watch();
slave.run();
});
slave.on('after', async (start, end) => {
await probe.unwatch();
slave.kill();
resolve(probe.report(start, end));
});
slave.on('error', async err => {
await probe.unwatch();
slave.kill();
reject(err);
});
});
}
module.exports = sympact;
================================================
FILE: lib/profiler.js
================================================
'use strict';
const stats = require('stats-lite');
const pidusage = require('pidusage');
const pidtree = require('pidtree');
function getAverages(values) {
return {
mean: stats.mean(values),
median: stats.median(values),
stdev: stats.stdev(values),
max: Math.max(...values),
min: Math.min(...values)
};
}
function Profiler(ppid, period) {
const snapshots = {};
let ending = false;
let interval = null;
let start = null;
let end = null;
async function read() {
if (!start || end) return;
try {
const procl = await pidtree(ppid, {root: true});
const statistics = await pidusage(procl);
const frame = Date.now() - start;
snapshots[frame] = Object.values(statistics);
} catch (err) {
console.log(err);
}
}
async function watch() {
if (start || end) return;
start = Date.now();
await read();
interval = setInterval(read, period);
}
async function unwatch() {
if (!start || end || ending) return;
ending = true;
clearInterval(interval);
await read();
end = Date.now();
interval = null;
}
async function report(from, to) {
const frames = Object.keys(snapshots);
const samples = {};
frames.forEach(frame => {
samples[frame] = {
cpu: snapshots[frame].reduce((acc, cur) => acc + cur.cpu, 0),
memory: snapshots[frame].reduce((acc, cur) => acc + cur.memory, 0),
processes: snapshots[frame]
};
});
const usage = {
cpu: getAverages(frames.map(frame => samples[frame].cpu)),
memory: getAverages(frames.map(frame => samples[frame].memory))
};
const report = {
times: {
sampling: {
start,
end
},
execution: {
start: from || start,
end: to || end
}
},
stats: {
cpu: usage.cpu,
memory: usage.memory
},
samples: {
period,
count: frames.length,
list: samples
}
};
return report;
}
return {
watch,
unwatch,
report
};
}
module.exports = Profiler;
================================================
FILE: lib/vm.js
================================================
process.on('uncaughtException', err => {
process.send({event: 'error', error: plain(err)});
});
process.on('unhandledRejection', reason => {
process.send({event: 'error', error: plain(reason)});
});
process.on('message', async msg => {
if (typeof msg !== 'object' && msg.event !== 'run') return;
try {
const start = Date.now();
await run();
const end = Date.now();
process.send({event: 'after', start, end});
} catch (err) {
process.send({event: 'error', error: plain(err)});
}
});
setTimeout(() => {
process.send({event: 'ready'});
}, 1000);
function plain(obj) {
const plainObject = {};
Object.getOwnPropertyNames(obj).forEach(key => {
plainObject[key] = obj[key];
});
return plainObject;
}
const req = require;
// eslint-disable-next-line no-global-assign
require = module => {
return module === '.' || module.startsWith('./')
? req(req('path').join(process.cwd(), module))
: req(module);
};
async function run() {
/* CODE */
}
================================================
FILE: lib/worker.js
================================================
'use strict';
const EventEmitter = require('events');
const {fork} = require('child_process');
const fs = require('fs-extra');
const tempy = require('tempy');
const VM = fs.readFileSync(`${__dirname}/vm.js`, 'utf8');
function Worker(script, cwd) {
const emitter = new EventEmitter();
const path = tempy.file({name: 'code.js'});
const code = VM.replace('/* CODE */', script);
fs.writeFileSync(path, code);
const child = fork(path, {windowsHide: true, cwd});
child.on('message', async message => {
if (message.event === 'error') {
const err = new Error('unable to execute');
err.stack = `${err.stack}\nCaused By: ${message.error.stack}`;
emitter.emit(message.event, err);
} else if (message.event === 'after') {
emitter.emit(message.event, message.start, message.end);
} else {
emitter.emit(message.event);
}
});
child.on('error', error => {
const err = new Error('unable to execute');
err.stack = `${err.stack}\nCaused By: ${error.stack}`;
emitter.emit('error', err);
});
emitter.pid = () => child.pid;
emitter.run = () => {
child.send({event: 'run'});
};
emitter.kill = () => {
child.kill();
};
return emitter;
}
module.exports = Worker;
================================================
FILE: license
================================================
MIT License
Copyright (c) 2018 Simone Primarosa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "sympact",
"version": "0.0.7",
"description": "An easy way to calculate the 'impact' of running a task in Node.JS",
"license": "MIT",
"homepage": "https://github.com/simonepri/sympact#readme",
"repository": "github:simonepri/sympact",
"bugs": {
"url": "https://github.com/simonepri/sympact/issues",
"email": "simonepri@outlook.com"
},
"author": "Simone Primarosa <simonepri@outlook.com> (https://simoneprimarosa.com)",
"contributors": [
"Simone Primarosa <simonepri@outlook.com> (https://simoneprimarosa.com)"
],
"keywords": [
"benchmark",
"benchmarking",
"profile",
"profiler",
"profiling",
"sys",
"system",
"compute",
"calculate",
"impact",
"impactjs",
"damage",
"process",
"processes",
"isolation",
"vm",
"fork",
"script",
"task",
"tree",
"pid",
"pids",
"cpu",
"ram",
"processor",
"memory",
"cli"
],
"main": "index.js",
"bin": {
"sympact": "cli.js"
},
"files": [
"lib",
"index.js",
"cli.js"
],
"engines": {
"node": ">=8"
},
"scripts": {
"start": "node cli.js",
"test": "xo &&nyc ava",
"release": "np",
"update": "npm-check -u"
},
"dependencies": {
"caller": "^1.0.1",
"chalk": "^2.4.1",
"easy-table": "^1.1.1",
"fs-extra": "^6.0.1",
"log-symbols": "^2.2.0",
"meow": "^5.0.0",
"pidtree": "^0.3.0",
"pidusage": "^2.0.6",
"stats-lite": "^2.1.1",
"systeminformation": "^3.41.3",
"tempy": "^0.2.1",
"update-notifier": "^2.5.0"
},
"devDependencies": {
"ava": "*",
"joi": "^13.3.0",
"np": "^3.0.1",
"npm-check": "*",
"nyc": "*",
"xo": "*"
},
"ava": {
"verbose": true
},
"nyc": {
"reporter": [
"lcovonly",
"text"
]
},
"xo": {
"prettier": true,
"space": true
}
}
================================================
FILE: readme.md
================================================
<p align="center">
<a href="https://github.com/simonepri/sympact">
<img src="https://github.com/simonepri/sympact/raw/master/media/sympact.png" alt="sympact" width="150"/>
</a>
</p>
<p align="center">
<!-- CI - TravisCI -->
<a href="https://travis-ci.org/simonepri/sympact">
<img src="https://img.shields.io/travis/simonepri/sympact/master.svg?label=MacOS%20%26%20Linux" alt="Mac/Linux Build Status" />
</a>
<!-- CI - AppVeyor -->
<a href="https://ci.appveyor.com/project/simonepri/sympact">
<img src="https://img.shields.io/appveyor/ci/simonepri/sympact/master.svg?label=Windows" alt="Windows Build status" />
</a>
<!-- Coverage - Codecov -->
<a href="https://codecov.io/gh/simonepri/sympact">
<img src="https://img.shields.io/codecov/c/github/simonepri/sympact/master.svg" alt="Codecov Coverage report" />
</a>
<!-- DM - Snyk -->
<a href="https://snyk.io/test/github/simonepri/sympact?targetFile=package.json">
<img src="https://snyk.io/test/github/simonepri/sympact/badge.svg?targetFile=package.json" alt="Known Vulnerabilities" />
</a>
<!-- DM - David -->
<a href="https://david-dm.org/simonepri/sympact">
<img src="https://david-dm.org/simonepri/sympact/status.svg" alt="Dependency Status" />
</a>
<br/>
<!-- Code Style - XO-Prettier -->
<a href="https://github.com/xojs/xo">
<img src="https://img.shields.io/badge/code_style-XO+Prettier-5ed9c7.svg" alt="XO Code Style used" />
</a>
<!-- Test Runner - AVA -->
<a href="https://github.com/avajs/ava">
<img src="https://img.shields.io/badge/test_runner-AVA-fb3170.svg" alt="AVA Test Runner used" />
</a>
<!-- Test Coverage - Istanbul -->
<a href="https://github.com/istanbuljs/nyc">
<img src="https://img.shields.io/badge/test_coverage-NYC-fec606.svg" alt="Istanbul Test Coverage used" />
</a>
<!-- Init - ni -->
<a href="https://github.com/simonepri/ni">
<img src="https://img.shields.io/badge/initialized_with-ni-e74c3c.svg" alt="NI Scaffolding System used" />
</a>
<!-- Release - np -->
<a href="https://github.com/sindresorhus/np">
<img src="https://img.shields.io/badge/released_with-np-6c8784.svg" alt="NP Release System used" />
</a>
<br/>
<!-- Version - npm -->
<a href="https://www.npmjs.com/package/sympact">
<img src="https://img.shields.io/npm/v/sympact.svg" alt="Latest version on npm" />
</a>
<!-- License - MIT -->
<a href="https://github.com/simonepri/sympact/tree/master/license">
<img src="https://img.shields.io/github/license/simonepri/sympact.svg" alt="Project license" />
</a>
</p>
<p align="center">
🔥 An easy way to calculate the 'impact' of running a task in Node.JS
<br/>
<sub>
Coded with ❤️ by <a href="#authors">Simone Primarosa</a>.
</sub>
</p>
## Synopsis
Sympact runs a script and profiles its execution time, CPU usage, and memory usage. Sympact then returns an execution report containing the averages of the results.
Do you believe that this is *useful*?
Has it *saved you time*?
Or maybe you simply *like it*?
If so, [show your appreciation with a Star ⭐️][start].
## How it works
sympact spawns a separate process and runs your script in an isolated
node process and then collects statistics about the system's resource used by
your script.
The data are collected using [pidusage][gh:pidusage] in combination with
[pidtree][gh:pidtree].
**The main difference between other projects is that sympact will also
"profile" processes spawned by your script or by any of its children.**
Finally a report of the samples taken is computed and returned to you.
## Install
```bash
npm install --save sympact
```
## Usage
```js
const impact = require('sympact');
const report = await impact(`
let r = 2;
let c = 10e7;
while (c--) r = Math.pow(r, r);
return r;
`, {interval: 125}); // 125 ms of sampling rate
console.log(report.times.execution.end - report.times.execution.start);
// => 2700 ms
console.log(report.stats.cpu.mean);
// => 90.45 % on my machine
console.log(report.stats.memory.mean);
// => 27903317.33 bytes on my machine
```
## CLI
<img src="https://github.com/simonepri/sympact/raw/master/media/cli.gif" alt="sympact CLI" width="475" align="right"/>
To make it more usable, a CLI is bundled with the package allowing for an aesthetically pleasing report.
```bash
npx sympact "console.log('Hello World')"
```
You can even require other files.
```bash
npx sympact "
const {spawn} = require('child_process');
let childno = 10;
let childs = [];
for (let i = 0; i < childno; i++) {
childs.push(spawn('node', ['-e', 'setInterval(()=>{let c=10e3;while(c--);},10)']));
}
let c = 10e6;
let m = {};
while (c--) m[c] = c;
for (let i = 0; i < childno; i++) {
childs[i].kill();
}
"
```
<br/><br/>
## Report object
The object returned by the promise will look like this.
```js
{
"times": {
"sampling": {
"start": 1521666020917, // ms since epoch
"end": 1521666036041 // ms since epoch
},
"execution": {
"start": 1521666020958, // ms since epoch
"end": 1521666036006 // ms since epoch
}
},
"stats": {
"cpu": { // CPU usage statistics (percentage)
"mean": 74.17368421052636,
"median": 75.1,
"stdev": 11.820700343128212,
"max": 94.7,
"min": 0.7
},
"memory": { // RAM usage statistics (bytes)
"mean": 1080202186.1052632,
"median": 1327509504,
"stdev": 416083837.44653314,
"max": 1327513600,
"min": 23441408
}
},
"samples": { // List of all the samples taken
"period": 125, // Sampling period
"count": 114, // Number of samples taken
"list": {
"39": { // Taken after 39ms after the start of the watch command
"cpu": 0.7, // Sum of the usages of all the processes
"memory": 23441408, // Sum of the memory of all the processes
"processes": [{ // List of processes profiled in this timeframe
"cpu": 0.7,
"memory": 23441408,
"ppid": 837,
"pid": 839,
"ctime": 6000,
"elapsed": 1000,
"timestamp": 1521666020955 // ms since epoch
}]
},
"205": {
"cpu": 14.8,
"memory": 55685120,
"processes": [{
"cpu": 14.8,
"memory": 55685120,
"ppid": 837,
"pid": 839,
"ctime": 15000,
"elapsed": 2000,
"timestamp": 1521666021122
}]
},
[...]
"15124": {
"cpu": 81.2,
"memory": 878133248,
"processes": [{
"cpu": 81.2,
"memory": 878133248,
"ppid": 837,
"pid": 839,
"ctime": 47600,
"elapsed": 17000,
"timestamp": 1521666036041
}]
}
}
}
}
```
## API
<a name="sympact"></a>
### sympact(code, [options]) ⇒ <code>Promise.<Object></code>
Measures the impact of running a certain script on your system.
Monitors the cpu and memory usage of the whole tree of processes generated by
the script provided.
**Kind**: global function
**Returns**: <code>Promise.<Object></code> - An object containing the results.
**Access**: public
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| code | <code>string</code> | | The source code to test. |
| [options] | <code>Object</code> | | Optional configurations. |
| [options.interval] | <code>number</code> | <code>125</code> | Sampling interval in milliseconds. |
| [options.cwd] | <code>string</code> | <code>"caller path"</code> | CWD for the script. |
## Contributing
Contributions are REALLY welcome and if you find a security flaw in this code, PLEASE [report it][new issue].
Please check the [contributing guidelines][contributing] for more details. Thanks!
## Authors
- **Simone Primarosa** - *Follow* me on *Github* ([:octocat:@simonepri][github:simonepri]) and on *Twitter* ([🐦@simonepri][twitter:simoneprimarosa])
See also the list of [contributors][contributors] who participated in this project.
## License
This project is licensed under the MIT License - see the [license][license] file for details.
<!-- Links -->
[start]: https://github.com/simonepri/sympact#start-of-content
[new issue]: https://github.com/simonepri/sympact/issues/new
[contributors]: https://github.com/simonepri/sympact/contributors
[license]: https://github.com/simonepri/sympact/tree/master/license
[contributing]: https://github.com/simonepri/sympact/tree/master/.github/contributing.md
[github:simonepri]: https://github.com/simonepri
[twitter:simoneprimarosa]: http://twitter.com/intent/user?screen_name=simoneprimarosa
[gh:pidusage]: https://github.com/soyuka/pidusage
[gh:pidtree]: https://github.com/simonepri/pidtree
================================================
FILE: test.js
================================================
import test from 'ava';
import joi from 'joi';
import m from '.';
const schema = {
integer: joi
.number()
.integer()
.min(0),
time: joi.object().keys({
start: joi
.number()
.integer()
.min(0),
end: joi
.number()
.integer()
.min(0)
}),
avarage: joi.object().keys({
mean: joi.number().min(0),
median: joi.number().min(0),
stdev: joi.number().min(0),
max: joi.number().min(0),
min: joi.number().min(0)
}),
sample: joi.object().keys({
cpu: joi.number().min(0),
memory: joi
.number()
.integer()
.min(0),
processes: joi.array().items(
joi.object().keys({
cpu: joi.number().min(0),
memory: joi
.number()
.integer()
.min(0),
ppid: joi
.number()
.integer()
.min(0),
pid: joi
.number()
.integer()
.min(0),
ctime: joi
.number()
.integer()
.min(0),
elapsed: joi
.number()
.integer()
.min(0),
timestamp: joi
.number()
.integer()
.min(0)
})
)
})
};
test('should monitor pid without childs correctly', async t => {
const report = await m(`
let r = 2;
let c = 10e5;
while (c--) r = Math.pow(r, r);
return r;
`);
t.is(typeof report, 'object');
t.is(typeof report.times, 'object');
t.is(joi.validate(report.times.sampling, schema.time).error, null);
t.is(joi.validate(report.times.execution, schema.time).error, null);
t.is(typeof report.stats, 'object');
t.is(joi.validate(report.stats.cpu, schema.avarage).error, null);
t.is(joi.validate(report.stats.memory, schema.avarage).error, null);
t.is(typeof report.samples, 'object');
t.is(joi.validate(report.samples.count, schema.integer).error, null);
t.is(joi.validate(report.samples.period, schema.integer).error, null);
t.is(typeof report.samples.list, 'object');
// eslint-disable-next-line guard-for-in
for (const key in report.samples.list) {
t.is(joi.validate(report.samples.list[key], schema.sample).error, null);
}
});
test('should monitor pid with childs correctly', async t => {
const report = await m(`
const {spawn} = require('child_process');
let childno = 10;
let childs = [];
for (let i = 0; i < childno; i++) {
childs.push(spawn('node', ['-e', 'setInterval(()=>{let c=10e2;while(c--);},1000)']));
}
let c = 10e6;
let m = {};
while (c--) m[c] = c;
for (let i = 0; i < childno; i++) {
childs[i].kill();
}
`);
t.is(typeof report, 'object');
t.is(typeof report.times, 'object');
t.is(joi.validate(report.times.sampling, schema.time).error, null);
t.is(joi.validate(report.times.execution, schema.time).error, null);
t.is(typeof report.stats, 'object');
t.is(joi.validate(report.stats.cpu, schema.avarage).error, null);
t.is(joi.validate(report.stats.memory, schema.avarage).error, null);
t.is(typeof report.samples, 'object');
t.is(joi.validate(report.samples.count, schema.integer).error, null);
t.is(joi.validate(report.samples.period, schema.integer).error, null);
t.is(typeof report.samples.list, 'object');
// eslint-disable-next-line guard-for-in
for (const key in report.samples.list) {
t.is(joi.validate(report.samples.list[key], schema.sample).error, null);
}
});
gitextract_ri8w7s5_/ ├── .appveyor.yml ├── .codecov.yml ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── code-of-conduct.md │ ├── contributing.md │ ├── issue-template.md │ └── pull-request-template.md ├── .gitignore ├── .npmrc ├── .travis.yml ├── cli.js ├── index.js ├── lib/ │ ├── profiler.js │ ├── vm.js │ └── worker.js ├── license ├── package.json ├── readme.md └── test.js
SYMBOL INDEX (6 symbols across 4 files)
FILE: index.js
function sympact (line 20) | async function sympact(code, options) {
FILE: lib/profiler.js
function getAverages (line 7) | function getAverages(values) {
function Profiler (line 17) | function Profiler(ppid, period) {
FILE: lib/vm.js
function plain (line 25) | function plain(obj) {
function run (line 41) | async function run() {
FILE: lib/worker.js
function Worker (line 10) | function Worker(script, cwd) {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (43K chars).
[
{
"path": ".appveyor.yml",
"chars": 319,
"preview": "build: off\nversion: '{build}'\nenvironment:\n matrix:\n - nodejs_version: '8'\n - nodejs_version: '10'\nplatform:\n - "
},
{
"path": ".codecov.yml",
"chars": 48,
"preview": "parsers:\n javascript:\n enable_partials: yes\n"
},
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\nindent_size = 2\nindent_style = space\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
},
{
"path": ".gitattributes",
"chars": 29,
"preview": "* text=auto\n*.js text eol=lf\n"
},
{
"path": ".github/code-of-conduct.md",
"chars": 3250,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": ".github/contributing.md",
"chars": 4311,
"preview": "# Contributing\n\nFirst and foremost, thank you! We appreciate that you want to contribute to this project, your time is v"
},
{
"path": ".github/issue-template.md",
"chars": 745,
"preview": "<!--\nThanks for reporting an Issue! \nIf you haven't already read the contributing guidelines,\nPlease do that now then p"
},
{
"path": ".github/pull-request-template.md",
"chars": 529,
"preview": "<!--\nThanks for submitting a Pull Request! \nIf you haven't already read the contributing guidelines,\nPlease do that now"
},
{
"path": ".gitignore",
"chars": 2018,
"preview": "\n# Created by https://www.gitignore.io/api/linux,macos,windows,node\n\n### Linux ###\n*~\n\n# temporary files which can be cr"
},
{
"path": ".npmrc",
"chars": 35,
"preview": "package-lock=false\nloglevel=silent\n"
},
{
"path": ".travis.yml",
"chars": 203,
"preview": "sudo: false\nlanguage: node_js\nnode_js:\n - '8'\n - '10'\nos:\n - linux\n - osx\ncache:\n directories:\n - node_modules\nscr"
},
{
"path": "cli.js",
"chars": 6760,
"preview": "#!/usr/bin/env node\n/* eslint-disable promise/prefer-await-to-then */\n'use strict';\n\nconst si = require('systeminformati"
},
{
"path": "index.js",
"chars": 1712,
"preview": "'use strict';\n\nconst path = require('path');\nconst caller = require('caller');\n\nconst Worker = require('./lib/worker.js'"
},
{
"path": "lib/profiler.js",
"chars": 2124,
"preview": "'use strict';\n\nconst stats = require('stats-lite');\nconst pidusage = require('pidusage');\nconst pidtree = require('pidtr"
},
{
"path": "lib/vm.js",
"chars": 995,
"preview": "process.on('uncaughtException', err => {\n process.send({event: 'error', error: plain(err)});\n});\n\nprocess.on('unhandled"
},
{
"path": "lib/worker.js",
"chars": 1242,
"preview": "'use strict';\n\nconst EventEmitter = require('events');\nconst {fork} = require('child_process');\nconst fs = require('fs-e"
},
{
"path": "license",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2018 Simone Primarosa\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "package.json",
"chars": 1891,
"preview": "{\n \"name\": \"sympact\",\n \"version\": \"0.0.7\",\n \"description\": \"An easy way to calculate the 'impact' of running a task i"
},
{
"path": "readme.md",
"chars": 9016,
"preview": "<p align=\"center\">\n <a href=\"https://github.com/simonepri/sympact\">\n <img src=\"https://github.com/simonepri/sympact/"
},
{
"path": "test.js",
"chars": 3434,
"preview": "import test from 'ava';\n\nimport joi from 'joi';\n\nimport m from '.';\n\nconst schema = {\n integer: joi\n .number()\n ."
}
]
About this extraction
This page contains the full source code of the simonepri/sympact GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (38.9 KB), approximately 10.9k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.