@acromedia/sloth 中文文档教程
Description
Sloth 是一个 Node 模块,旨在允许在 NodeJS 脚本、项目和测试中进行简单和通用的内存分析。
Table of Contents
- Installation
- Usage
- Documentation
- Using the Profiler class
- Using the bench function
- Benchmarking a file
- ProfileResults
Installation
- With npm:
npm install sloth
- With yarn:
yarn add sloth
Usage
ES6 import
s:
import * as sloth from 'sloth'
// OR
import { Profiler, bench } from 'sloth'
使用 require
s:
const sloth = require('sloth')
// OR
const { Profiler, bench, benchFile } = require('sloth')
有关更详细的描述和代码示例,请参见下文
Documentation
Using the Profiler class
:分析任何应用程序的方法,只要你有它的 PID,尽管它的目的更多是分析它在其中创建的应用程序。
Creating an instance
const { Profiler } = require('sloth')
const profiler = new Profiler(pid, options)
选项在下面描述,所有都是可选的:
Option | Type | Description |
---|---|---|
toFile | Boolean (optional) | Whether to export the results to a file or not |
timestep | Number (optional) | How long, in milliseconds, the memory monitor will check memory usage |
wait | Number (optional) | How long, in milliseconds, the profiler should wait before returning results |
trimNodeProcessUsage | Boolean (optional) | Takes the memory usage of the node process before anything has happened, and removes that from the data. Should allow for yielding more accurate results in most cases |
方法在下面描述,都是异步的:
Method | Description |
---|---|
start | Begins the profiling, returns itself |
end | Stops profiling, returns instance of ProfileResults . See more about ProfileResults here |
Examples
创建一个新的 Profiler 实例,它将保持跟踪它自己的进程的使用情况:
const { Profiler } = require('sloth')
const profiler = new Profiler(process.pid, {
timestep: 100,
wait: 1000,
// Helps since the profiler will likely skew the results otherwise
trimNodeProcessUsage: true
})
分析大型数组的创建:
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
const results = (await profiler.end()).results
console.log(results)
在 Jest 中使用 Profiler
const { Profiler } = require('sloth')
// Your Jest test suite
describe('test test', () => {
// To make the tests cleaner, you should probably make them async
it ('does something', async () => {
const profiler = new Profiler(process.pid, {
timestep: 100,
wait: 1000,
// This will shave off all of the memory currently
// taken up by the Jest test. This way, it'll be more
// accurate in case you just did a bunch of crazy
// stuff before and didn't clean it all up properly.
trimNodeProcessUsage: true
})
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
const results = (await profiler.end()).results
// Should've taken less than 5 seconds
// (I doubt it'd actually take that little time, but it's just an example)
expect(results.time_elapsed < 5000).toBeTruthy()
})
})
Extra Notes
根据所分析的功能有多大,您可能希望保留 节点垃圾收集器 记在心里。 考虑以下示例:
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
myHugeArr = null
const results = (await profiler.end()).results
console.log(results)
当数组设置为 null 时,除非运行垃圾收集器,否则内存实际上不会更改。 要更改此设置,请使用 --expose-gc
标志运行脚本,如下所示:
node --expose-gc index
并调用垃圾收集器:
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
myHugeArr = null
// Important:
global.gc()
const results = (await profiler.end()).results
console.log(results)
Using the bench function
bench()
函数接受一个函数,将其抛出进入一个单独的进程,在进程上运行分析器,并将它们全部包装在一起,并在顶部打上蝴蝶结*。
* 免责声明:实际上并没有在顶部提供蝴蝶结。
Calling bench()
调用 bench()
函数是这样完成的:
const { bench } = require('sloth')
await bench(function, arguments, options)
它将返回一个 ProfileResults
实例(有关 ProfileResults
的更多信息,请点击此处)。
有关选项的详细信息,请参阅使用 Profiler 类,因为此函数使用完全相同的选项,但有一个重要的补充:
Option | Type | Description |
---|---|---|
setup | Function (optional) | Code to run in the "global" scope. Useful for require() s and other otherwise globally defined variables |
requirements | Array (optional) | A nicer alternative to setup . Each item should be an object with a name (what it's defined as) and path (what is actually require ed) |
Examples
* 仅供参考:“全局”一词" 通常在引号中,因为它在技术上不是全局的,而是在函数的外部范围内。
设置测量大型数组创建的测试:
function f() {
let arr = new Array(1e8).fill(0)
}
// OR
const f = () => {
let arr = new Array(1e8).fill(0)
}
const results = await bench(f, [], {
// Less useful, since the process is basically isolated, but still a good idea
trimNodeProcessUsage: true
})
测试匿名函数:
const results = bench(() => {
console.log('I work!')
})
// OR
const results = bench(function() {
console.log('I work!')
})
使用参数:
function log(text) {
console.log(text)
}
const results = bench(log, ['I work!'])
// OR
const results = bench((text) => console.log(text), ['I work!'])
使用设置函数:
// Logging a "globally" defined variable
function f() {
console.log(myVar)
}
const results = await bench(f, [], {
setup: () => {
let myVar = 'I work!'
}
})
// Using a module imported using require()
function f(data) {
fs.writeFileSync('text.txt', data, 'utf8')
}
const results = await bench(f, ['I work!'], {
setup: () => {
const fs = require('fs')
}
})
* Linters 可能会对您的设置函数非常生气,因为它们定义了未使用的变量。 只是提醒一下。
使用需求数组
// Using a package
function f() {
filesystem.writeFileSync('test.txt', 'test')
}
const results = await bench(f, [], {
requirements: [
{
// Parsed as `const filesystem = require('fs')`
name: 'filesystem',
path: 'fs'
}
]
})
Extra Notes
当一个线程被生成时,它会自动使用 --expose-gc
选项运行,并且一旦一切都完成,它将始终运行 global.gc()
,但在探查器完成之前。 这是为了更好地了解结束内存使用情况(results
对象中的 end_usage_bytes
。)。
显然,以序列化到反序列化的方式运行代码时存在安全隐患,即使它在单独的进程中并且您可以完全控制进入的代码。注意如何以及在何处使用 bench()
,有时使用 Profiler
类会更安全。
Benchmarking a file
benchFile()
函数采用现有的 bench()
函数及其所有有趣的函数包装优点,并为整个文件实现它。 它允许您输入路径以及 Node AND CLI 选项,这应该允许您控制究竟运行什么。 这也会返回 ProfileResults
的一个实例。
它采用三个参数:
Option | Type | Description |
---|---|---|
path | String | The path to the file. This can be relative or absolute, although absolute is much more ideal. |
nodeArgs | Array (optional) | An array of Node options, like --expose-gc . These are options passed to the Node process itself |
cliArgs | Array (optional) | An array of CLI options. These are options likely found or used in your own code. |
Examples
对单个文件
const { benchFile } = require('sloth')
await benchFile('/home/project/myFile.js')
进行基准测试 对文件进行基准测试并提供一些 Node 参数
await benchFile(__dirname + '/myFile.js/', [
'--expose-gc',
'--no-warnings'
])
对文件进行基准测试并提供一些 CLI 参数
await benchFile(__dirname + '/myFile.js', [], [
'--do-ten-times',
'-f',
'--silent',
'--input-dir=' + getSomeDir()
])
ProfileResults
一旦 Profiler.end()
或 bench()
是调用,它将返回一个 ProfileResults
实例。 这包含所有分析数据,以及一些有助于查看和理解数据的额外功能。
ProfileResults.data
属性中的值概述如下:
Property | Type | Description |
---|---|---|
start | Number | The timestamp in milliseconds the profiling was started |
end | Number | The timestamp in milliseconds the profiling finished |
time_elapsed | Number | The amount of time profiling took, in milliseconds |
timestep_ms | Number | The amount of milliseconds per memory check |
mem_list | Array | List of memory values collected |
startusagebytes | Number | The amount of bytes being used before or as profiling began |
peakusagebytes | Number | The largest amount of memory being used at one time |
endusagebytes | Number | The amount of memory being used by the end of the profile |
baseprocessbytes | Number | The amount of memory used by the process without anything having been done |
提供的一些方法应该有助于理解某些数据:
Method | Description |
---|---|
averageMemoryUsage() | Get average memory usage throughout the whole profile |
medianMemoryUsage() | Get middle memory usage, when list is sorted least to greatest or greatest to least |
modeMemoryUsage() | Get most frequently occuring memory value |
memoryAtElapsed(ms) | Get the amount of memory being used at a certain point in the profile |
Extra Notes
如果您打算在自动化测试中使用此模块,请记住该分析在不同的机器上可能需要不同的时间。 这可能会扭曲平均值等,因此请坚持测试更具体的值,例如 peak_usage_bytes
。
Description
Sloth is a Node module created with the intention of allowing easy and versatile memory profiling in NodeJS scripts, projects, and tests.
Table of Contents
- Installation
- Usage
- Documentation
- Using the Profiler class
- Using the bench function
- Benchmarking a file
- ProfileResults
Installation
- With npm:
npm install sloth
- With yarn:
yarn add sloth
Usage
ES6 import
s:
import * as sloth from 'sloth'
// OR
import { Profiler, bench } from 'sloth'
Using require
s:
const sloth = require('sloth')
// OR
const { Profiler, bench, benchFile } = require('sloth')
For more detailed descriptions and code examples, see below:
Documentation
Using the Profiler class
The Profiler
class is provided as a quick and easy way of profiling any application, provided you have it's PID, although it's aimed more towards profiling the application it's created in.
Creating an instance
const { Profiler } = require('sloth')
const profiler = new Profiler(pid, options)
Options are described below, all are optional:
Option | Type | Description |
---|---|---|
toFile | Boolean (optional) | Whether to export the results to a file or not |
timestep | Number (optional) | How long, in milliseconds, the memory monitor will check memory usage |
wait | Number (optional) | How long, in milliseconds, the profiler should wait before returning results |
trimNodeProcessUsage | Boolean (optional) | Takes the memory usage of the node process before anything has happened, and removes that from the data. Should allow for yielding more accurate results in most cases |
Methods are described below, all are async:
Method | Description |
---|---|
start | Begins the profiling, returns itself |
end | Stops profiling, returns instance of ProfileResults . See more about ProfileResults here |
Examples
Creating a new Profiler instance that will keep track of it's own process's usage:
const { Profiler } = require('sloth')
const profiler = new Profiler(process.pid, {
timestep: 100,
wait: 1000,
// Helps since the profiler will likely skew the results otherwise
trimNodeProcessUsage: true
})
Profiling the creation of a large array:
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
const results = (await profiler.end()).results
console.log(results)
Using the Profiler within Jest
const { Profiler } = require('sloth')
// Your Jest test suite
describe('test test', () => {
// To make the tests cleaner, you should probably make them async
it ('does something', async () => {
const profiler = new Profiler(process.pid, {
timestep: 100,
wait: 1000,
// This will shave off all of the memory currently
// taken up by the Jest test. This way, it'll be more
// accurate in case you just did a bunch of crazy
// stuff before and didn't clean it all up properly.
trimNodeProcessUsage: true
})
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
const results = (await profiler.end()).results
// Should've taken less than 5 seconds
// (I doubt it'd actually take that little time, but it's just an example)
expect(results.time_elapsed < 5000).toBeTruthy()
})
})
Extra Notes
Depending on how large the functionality being profiled is, you may want the keep the Node garbage collector in mind. Consider the following example:
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
myHugeArr = null
const results = (await profiler.end()).results
console.log(results)
While the array is set to null, the memory won't actually change unless the garbage collector is run. To change this, run your script with the --expose-gc
flag, like so:
node --expose-gc index
And call the garbage collector:
await profiler.start()
// 100,000,000 zeros
let myHugeArr = new Array(1e8).fill(0)
myHugeArr = null
// Important:
global.gc()
const results = (await profiler.end()).results
console.log(results)
Using the bench function
The bench()
function takes a function, throws it into a separate process, runs the profiler on the process, and wraps it all together complete with a bow on top*.
* Disclaimer: does not actually provide a bow on top.
Calling bench()
Calling the bench()
function is done like so:
const { bench } = require('sloth')
await bench(function, arguments, options)
It will return a ProfileResults
instance (see more about ProfileResults
here).
For details on options, see Using the Profiler class, as this function uses the exact same options with one important addition:
Option | Type | Description |
---|---|---|
setup | Function (optional) | Code to run in the "global" scope. Useful for require() s and other otherwise globally defined variables |
requirements | Array (optional) | A nicer alternative to setup . Each item should be an object with a name (what it's defined as) and path (what is actually require ed) |
Examples
* FYI: The word "global" is often in quotes, as it technically isn't global, but instead in the outer scope of the function.
Setting up a test that measures the creation of a large array:
function f() {
let arr = new Array(1e8).fill(0)
}
// OR
const f = () => {
let arr = new Array(1e8).fill(0)
}
const results = await bench(f, [], {
// Less useful, since the process is basically isolated, but still a good idea
trimNodeProcessUsage: true
})
Testing out an anonymous function:
const results = bench(() => {
console.log('I work!')
})
// OR
const results = bench(function() {
console.log('I work!')
})
Using arguments:
function log(text) {
console.log(text)
}
const results = bench(log, ['I work!'])
// OR
const results = bench((text) => console.log(text), ['I work!'])
Using a setup function:
// Logging a "globally" defined variable
function f() {
console.log(myVar)
}
const results = await bench(f, [], {
setup: () => {
let myVar = 'I work!'
}
})
// Using a module imported using require()
function f(data) {
fs.writeFileSync('text.txt', data, 'utf8')
}
const results = await bench(f, ['I work!'], {
setup: () => {
const fs = require('fs')
}
})
* Linters will probably get pretty pissy at your setup functions, given they define variables that aren't used. Just a heads up.
Using a requirements array
// Using a package
function f() {
filesystem.writeFileSync('test.txt', 'test')
}
const results = await bench(f, [], {
requirements: [
{
// Parsed as `const filesystem = require('fs')`
name: 'filesystem',
path: 'fs'
}
]
})
Extra Notes
When a thread is spawned, it is automatically run with the --expose-gc
option and will always run global.gc()
once everything has completed, but before the profiler is finished. This is to give a better insight on ending memory usage (end_usage_bytes
in the results
object.).
Obviously there are security implications when it comes to running code in a serialized-to-unserialized way, even if it's in a separate process and you have complete control of the code going in. Be careful as to how and where you use bench()
, sometimes using the Profiler
class will be safer.
Benchmarking a file
The benchFile()
function takes the existing bench()
function and all it's fun function-wrappy goodness, and implements that for an entire file. It allows you to input a path as well as Node AND CLI options, which should allow you control over what exactly is run. This also returns an instance of ProfileResults
.
It take three arguments:
Option | Type | Description |
---|---|---|
path | String | The path to the file. This can be relative or absolute, although absolute is much more ideal. |
nodeArgs | Array (optional) | An array of Node options, like --expose-gc . These are options passed to the Node process itself |
cliArgs | Array (optional) | An array of CLI options. These are options likely found or used in your own code. |
Examples
Benchmarking a single file
const { benchFile } = require('sloth')
await benchFile('/home/project/myFile.js')
Benchmarking a file and providing some Node arguments
await benchFile(__dirname + '/myFile.js/', [
'--expose-gc',
'--no-warnings'
])
Benchmarking a file and providing some CLI arguments
await benchFile(__dirname + '/myFile.js', [], [
'--do-ten-times',
'-f',
'--silent',
'--input-dir=' + getSomeDir()
])
ProfileResults
Once Profiler.end()
or bench()
is called, it will return a ProfileResults
instance. This contains all of the profiling data, as well as some extra functions that should help aid in viewing and understanding the data.
The values in the ProfileResults.data
property are outlined below:
Property | Type | Description |
---|---|---|
start | Number | The timestamp in milliseconds the profiling was started |
end | Number | The timestamp in milliseconds the profiling finished |
time_elapsed | Number | The amount of time profiling took, in milliseconds |
timestep_ms | Number | The amount of milliseconds per memory check |
mem_list | Array | List of memory values collected |
startusagebytes | Number | The amount of bytes being used before or as profiling began |
peakusagebytes | Number | The largest amount of memory being used at one time |
endusagebytes | Number | The amount of memory being used by the end of the profile |
baseprocessbytes | Number | The amount of memory used by the process without anything having been done |
There are a few methods provided that should help make sense of some of the data:
Method | Description |
---|---|
averageMemoryUsage() | Get average memory usage throughout the whole profile |
medianMemoryUsage() | Get middle memory usage, when list is sorted least to greatest or greatest to least |
modeMemoryUsage() | Get most frequently occuring memory value |
memoryAtElapsed(ms) | Get the amount of memory being used at a certain point in the profile |
Extra Notes
If you intend on using this module in automated testing, keep in mind that profiling may take different amounts of time on different machines. This could possibly skew averages and such, so stick to testing on more concrete values like peak_usage_bytes
.