├── .editorconfig ├── .github └── workflows │ └── build.yml ├── .gitignore ├── ChangeLog ├── README.md ├── binding.gyp ├── examples ├── basic_heapdiff.js ├── do_nothing_server.js └── slightly_leaky.js ├── include.js ├── main.test.js ├── package-lock.json ├── package.json └── src ├── heapdiff.cc ├── heapdiff.hh ├── init.cc ├── memwatch.cc ├── memwatch.hh ├── platformcompat.hh ├── util.cc └── util.hh /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node: [ 18, 20, 22 ] 17 | name: Node ${{ matrix.node }} 18 | continue-on-error: true 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Setup node 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node }} 25 | - run: npm ci 26 | - run: npm test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /node_modules 3 | *~ 4 | 5 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | v3.0.0 2 | * BREAKING: Drop support for Node <= 17. 3 | * Add support for Node >= 20. 4 | * Convert `mocha` tests to `vitest` 5 | * Convert CI from Travis.CI to GitHub workflows 6 | 7 | v2.0.0 8 | * BREAKING: Drop support for Node <= 9. 9 | * Add support for Node >= 10. 10 | 11 | v1.0.2 - 12 | * Fix race condition on gc timers and counters 13 | * Add gc_ts field to stats event 14 | 15 | v1.0.1 - 16 | * Fix build error on linux 17 | 18 | v1.0.0 - 19 | * First airbnb forked version. 20 | * Emit all HeapStatistics 21 | * Add GC Timers 22 | * Correctly count GC types 23 | * BREAKING: Completely change stats event type 24 | * BREAKING: Remove leak detection 25 | 26 | v0.3.0 - 27 | * TODO: ? 28 | 29 | v0.2.10 - 30 | * TODO: ? 31 | 32 | v0.2.9 - 33 | * TODO: ? 34 | 35 | v0.2.8 - 36 | * TODO: ? 37 | 38 | v0.2.7 - 39 | * TODO: ? 40 | 41 | v0.2.6 - 42 | * TODO: ? 43 | 44 | v0.2.5 - 45 | * TODO: ? 46 | 47 | v0.2.4 - 48 | * TODO: ? 49 | 50 | v0.2.3 - 51 | * TODO: ? 52 | 53 | v0.2.2 - 54 | * don't crash when a user accidentally allocates a HeapDiff without new (ala new require('memwatch').HeapDiff()) #30 55 | 56 | v0.2.1 - 57 | * 0.10.0 support (thanks @rvagg and @tmuellerleile) 58 | * improved windows build support (no longer requires sed in path, thanks @mscdex) 59 | * work around a windows specific crash due to upstream "bug" in libuv (https://github.com/joyent/libuv/pull/629) 60 | 61 | v0.2.0 - 62 | * fix memory leak of snapshots in HeapDiff #15 63 | * HeapDiff.end() throws an exception if invoked more than once. 64 | * aggressively clean up snapshots, at end() rather than next gc 65 | 66 | v0.1.5 - 67 | * compiles on windows (thanks @jmatthewsr-ms! sorry to make you wait) 68 | 69 | v0.1.4 - 70 | * migrate to node-gyp (thanks @jhaynie for getting it started) 71 | 72 | v0.1.3 - 73 | * node 0.8 support 74 | 75 | v0.1.2 - 76 | 77 | * Addition of unit tests (running on travis) 78 | * fix bug whereby events would not be emitted when listeners use .once() 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `node-memwatch`: Leak Detection and Heap Diffing for Node.JS 2 | ============================================================ 3 | 4 | `node-memwatch` is here to help you detect and find memory leaks in 5 | Node.JS code. It provides: 6 | 7 | - A `stats` event, emitted on full MarkSweepCompact GCs giving you 8 | data describing your heap usage and trends over time. 9 | 10 | - A `HeapDiff` class that lets you compare the state of your heap between 11 | two points in time, telling you what has been allocated, and what 12 | has been released. 13 | 14 | 15 | Installation 16 | ------------ 17 | 18 | - `npm install @airbnb/node-memwatch` 19 | 20 | 21 | Description 22 | ----------- 23 | 24 | There are a growing number of tools for debugging and profiling memory 25 | usage in Node.JS applications, but there is still a need for a 26 | platform-independent native module that requires no special 27 | instrumentation. This module attempts to satisfy that need. 28 | 29 | To get started, import `node-memwatch` like so: 30 | 31 | ```javascript 32 | var memwatch = require('@airbnb/node-memwatch'); 33 | ``` 34 | 35 | ### Leak Detection 36 | 37 | Currently unsupported while we explore heuristics 38 | 39 | ### Heap Usage 40 | 41 | The best way to evaluate your memory footprint is to look at heap 42 | usage right after V8 performs garbage collection. `memwatch` does 43 | exactly this - it checks heap usage only after GC to give you a stable 44 | baseline of your actual memory usage. 45 | 46 | When V8 performs a garbage collection (technically, we're talking 47 | about a full GC with heap compaction), `memwatch` will emit a `stats` 48 | event. 49 | 50 | ```javascript 51 | memwatch.on('stats', function(stats) { ... }); 52 | ``` 53 | 54 | The `stats` data will look something like this: 55 | 56 | ```javascript 57 | { 58 | gcScavengeCount: 1, 59 | gcScavengeTime: 1100880, // ns 60 | gcMarkSweepCompactCount: 2, 61 | gcMarkSweepCompactTime: 21157231, // ns 62 | gcIncrementalMarkingCount: 0, 63 | gcIncrementalMarkingTime: 0, //ns 64 | gcProcessWeakCallbacksCount: 0, 65 | gcProcessWeakCallbacksTime: 0, // ns 66 | total_heap_size: 16097280, // bytes 67 | total_heap_size_executable: 3670016, // bytes 68 | total_physical_size: 10741880, // bytes 69 | total_available_size: 1487689928, // bytes 70 | used_heap_size: 5691584, // bytes 71 | heap_size_limit: 1501560832, // bytes 72 | malloced_memory: 8192, 73 | peak_malloced_memory: 1185464, 74 | gc_time: 4587251 // ns 75 | } 76 | ``` 77 | 78 | V8 has its own idea of when it's best to perform a GC, and under a 79 | heavy load, it may defer this action for some time. To aid in 80 | speedier debugging, `memwatch` provides a `gc()` method to force V8 to 81 | do a full GC and heap compaction. 82 | 83 | 84 | ### Heap Diffing 85 | 86 | For leak isolation, it provides a `HeapDiff` class that takes two snapshots and 87 | computes a diff between them. For example: 88 | 89 | ```javascript 90 | // Take first snapshot 91 | var hd = new memwatch.HeapDiff(); 92 | 93 | // do some things ... 94 | 95 | // Take the second snapshot and compute the diff 96 | var diff = hd.end(); 97 | ``` 98 | 99 | The contents of `diff` will look something like: 100 | 101 | ```javascript 102 | { 103 | "before": { "nodes": 11625, "size_bytes": 1869904, "size": "1.78 mb" }, 104 | "after": { "nodes": 21435, "size_bytes": 2119136, "size": "2.02 mb" }, 105 | "change": { "size_bytes": 249232, "size": "243.39 kb", "freed_nodes": 197, 106 | "allocated_nodes": 10007, 107 | "details": [ 108 | { "what": "String", 109 | "size_bytes": -2120, "size": "-2.07 kb", "+": 3, "-": 62 110 | }, 111 | { "what": "Array", 112 | "size_bytes": 66687, "size": "65.13 kb", "+": 4, "-": 78 113 | }, 114 | { "what": "LeakingClass", 115 | "size_bytes": 239952, "size": "234.33 kb", "+": 9998, "-": 0 116 | } 117 | ] 118 | } 119 | } 120 | ``` 121 | 122 | The diff shows that during the sample period, the total number of 123 | allocated `String` and `Array` classes decreased, but `Leaking Class` 124 | grew by 9998 allocations. Hmmm. 125 | 126 | You can use `HeapDiff` in your `on('stats')` callback; even though it 127 | takes a memory snapshot, which triggers a V8 GC, it will not trigger 128 | the `stats` event itself. Because that would be silly. 129 | 130 | 131 | Future Work 132 | ----------- 133 | 134 | Please see the Issues to share suggestions and contribute! 135 | 136 | 137 | License 138 | ------- 139 | 140 | http://wtfpl.net 141 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'memwatch', 5 | 'include_dirs': [ 6 | " { 5 | it('The library exports a couple functions', () => { 6 | expect(memwatch.gc).toBeDefined(); 7 | expect(memwatch.on).toBeDefined(); 8 | expect(memwatch.once).toBeDefined(); 9 | expect(memwatch.removeAllListeners).toBeDefined(); 10 | expect(memwatch.HeapDiff).toBeDefined(); 11 | }); 12 | 13 | describe('.gc()', () => { 14 | it('causes a stats() event to be emitted', () => { 15 | return new Promise((resolve) => { 16 | memwatch.once('stats', (s) => { 17 | expect(typeof s).toBe('object'); 18 | resolve(); 19 | }); 20 | memwatch.gc(); 21 | }); 22 | }); 23 | }); 24 | 25 | describe('HeapDiff', () => { 26 | it('detects allocations', () => { 27 | function LeakingClass() {}; 28 | const arr = []; 29 | const hd = new memwatch.HeapDiff(); 30 | for (let i = 0; i < 100; i++) arr.push(new LeakingClass()); 31 | const diff = hd.end(); 32 | expect(Array.isArray(diff.change.details)).toBe(true); 33 | // find the LeakingClass elem 34 | let leakingReport; 35 | diff.change.details.forEach((d) => { 36 | if (d.what === 'LeakingClass') 37 | leakingReport = d; 38 | }); 39 | expect(leakingReport).toBeDefined(); 40 | expect(leakingReport['+'] - leakingReport['-']).toBeGreaterThan(0); 41 | }); 42 | 43 | it('has the correct output format', () => { 44 | function LeakingClass() {}; 45 | const arr = []; 46 | const hd = new memwatch.HeapDiff(); 47 | for (let i = 0; i < 100; i++) arr.push(new LeakingClass()); 48 | expect(hd.end()).toEqual( 49 | expect.objectContaining({ 50 | after: expect.objectContaining({ 51 | nodes: expect.any(Number), 52 | size: expect.any(String), 53 | size_bytes: expect.any(Number), 54 | }), 55 | before: expect.objectContaining({ 56 | nodes: expect.any(Number), 57 | size: expect.any(String), 58 | size_bytes: expect.any(Number), 59 | }), 60 | change: expect.objectContaining({ 61 | allocated_nodes: expect.any(Number), 62 | details: expect.arrayContaining([ 63 | expect.objectContaining({ 64 | '+': expect.any(Number), 65 | '-': expect.any(Number), 66 | size: expect.any(String), 67 | size_bytes: expect.any(Number), 68 | what: expect.any(String), 69 | }), 70 | ]), 71 | freed_nodes: expect.any(Number), 72 | size: expect.any(String), 73 | size_bytes: expect.any(Number), 74 | }), 75 | }) 76 | ) 77 | }); 78 | 79 | it('double end should throw', () => { 80 | const hd = new memwatch.HeapDiff(); 81 | expect(() => hd.end()).not.toThrow(); 82 | expect(() => hd.end()).toThrow(); 83 | }); 84 | 85 | it('improper HeapDiff allocation should throw an exception', () => { 86 | // equivalent to "new require('memwatch').HeapDiff()" 87 | // see issue #30 88 | expect(() => new (memwatch.HeapDiff())).toThrow(); 89 | }); 90 | }); 91 | }); 92 | 93 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@airbnb/node-memwatch", 3 | "version": "3.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@airbnb/node-memwatch", 9 | "version": "3.0.0", 10 | "hasInstallScript": true, 11 | "dependencies": { 12 | "bindings": "^1.5.0", 13 | "nan": "^2.22.0" 14 | }, 15 | "devDependencies": { 16 | "vitest": "^2.1.4" 17 | }, 18 | "engines": { 19 | "node": ">= 18" 20 | } 21 | }, 22 | "node_modules/@esbuild/aix-ppc64": { 23 | "version": "0.21.5", 24 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 25 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 26 | "cpu": [ 27 | "ppc64" 28 | ], 29 | "dev": true, 30 | "optional": true, 31 | "os": [ 32 | "aix" 33 | ], 34 | "engines": { 35 | "node": ">=12" 36 | } 37 | }, 38 | "node_modules/@esbuild/android-arm": { 39 | "version": "0.21.5", 40 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 41 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 42 | "cpu": [ 43 | "arm" 44 | ], 45 | "dev": true, 46 | "optional": true, 47 | "os": [ 48 | "android" 49 | ], 50 | "engines": { 51 | "node": ">=12" 52 | } 53 | }, 54 | "node_modules/@esbuild/android-arm64": { 55 | "version": "0.21.5", 56 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 57 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 58 | "cpu": [ 59 | "arm64" 60 | ], 61 | "dev": true, 62 | "optional": true, 63 | "os": [ 64 | "android" 65 | ], 66 | "engines": { 67 | "node": ">=12" 68 | } 69 | }, 70 | "node_modules/@esbuild/android-x64": { 71 | "version": "0.21.5", 72 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 73 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 74 | "cpu": [ 75 | "x64" 76 | ], 77 | "dev": true, 78 | "optional": true, 79 | "os": [ 80 | "android" 81 | ], 82 | "engines": { 83 | "node": ">=12" 84 | } 85 | }, 86 | "node_modules/@esbuild/darwin-arm64": { 87 | "version": "0.21.5", 88 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 89 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 90 | "cpu": [ 91 | "arm64" 92 | ], 93 | "dev": true, 94 | "optional": true, 95 | "os": [ 96 | "darwin" 97 | ], 98 | "engines": { 99 | "node": ">=12" 100 | } 101 | }, 102 | "node_modules/@esbuild/darwin-x64": { 103 | "version": "0.21.5", 104 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 105 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 106 | "cpu": [ 107 | "x64" 108 | ], 109 | "dev": true, 110 | "optional": true, 111 | "os": [ 112 | "darwin" 113 | ], 114 | "engines": { 115 | "node": ">=12" 116 | } 117 | }, 118 | "node_modules/@esbuild/freebsd-arm64": { 119 | "version": "0.21.5", 120 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 121 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 122 | "cpu": [ 123 | "arm64" 124 | ], 125 | "dev": true, 126 | "optional": true, 127 | "os": [ 128 | "freebsd" 129 | ], 130 | "engines": { 131 | "node": ">=12" 132 | } 133 | }, 134 | "node_modules/@esbuild/freebsd-x64": { 135 | "version": "0.21.5", 136 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 137 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 138 | "cpu": [ 139 | "x64" 140 | ], 141 | "dev": true, 142 | "optional": true, 143 | "os": [ 144 | "freebsd" 145 | ], 146 | "engines": { 147 | "node": ">=12" 148 | } 149 | }, 150 | "node_modules/@esbuild/linux-arm": { 151 | "version": "0.21.5", 152 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 153 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 154 | "cpu": [ 155 | "arm" 156 | ], 157 | "dev": true, 158 | "optional": true, 159 | "os": [ 160 | "linux" 161 | ], 162 | "engines": { 163 | "node": ">=12" 164 | } 165 | }, 166 | "node_modules/@esbuild/linux-arm64": { 167 | "version": "0.21.5", 168 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 169 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 170 | "cpu": [ 171 | "arm64" 172 | ], 173 | "dev": true, 174 | "optional": true, 175 | "os": [ 176 | "linux" 177 | ], 178 | "engines": { 179 | "node": ">=12" 180 | } 181 | }, 182 | "node_modules/@esbuild/linux-ia32": { 183 | "version": "0.21.5", 184 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 185 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 186 | "cpu": [ 187 | "ia32" 188 | ], 189 | "dev": true, 190 | "optional": true, 191 | "os": [ 192 | "linux" 193 | ], 194 | "engines": { 195 | "node": ">=12" 196 | } 197 | }, 198 | "node_modules/@esbuild/linux-loong64": { 199 | "version": "0.21.5", 200 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 201 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 202 | "cpu": [ 203 | "loong64" 204 | ], 205 | "dev": true, 206 | "optional": true, 207 | "os": [ 208 | "linux" 209 | ], 210 | "engines": { 211 | "node": ">=12" 212 | } 213 | }, 214 | "node_modules/@esbuild/linux-mips64el": { 215 | "version": "0.21.5", 216 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 217 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 218 | "cpu": [ 219 | "mips64el" 220 | ], 221 | "dev": true, 222 | "optional": true, 223 | "os": [ 224 | "linux" 225 | ], 226 | "engines": { 227 | "node": ">=12" 228 | } 229 | }, 230 | "node_modules/@esbuild/linux-ppc64": { 231 | "version": "0.21.5", 232 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 233 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 234 | "cpu": [ 235 | "ppc64" 236 | ], 237 | "dev": true, 238 | "optional": true, 239 | "os": [ 240 | "linux" 241 | ], 242 | "engines": { 243 | "node": ">=12" 244 | } 245 | }, 246 | "node_modules/@esbuild/linux-riscv64": { 247 | "version": "0.21.5", 248 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 249 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 250 | "cpu": [ 251 | "riscv64" 252 | ], 253 | "dev": true, 254 | "optional": true, 255 | "os": [ 256 | "linux" 257 | ], 258 | "engines": { 259 | "node": ">=12" 260 | } 261 | }, 262 | "node_modules/@esbuild/linux-s390x": { 263 | "version": "0.21.5", 264 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 265 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 266 | "cpu": [ 267 | "s390x" 268 | ], 269 | "dev": true, 270 | "optional": true, 271 | "os": [ 272 | "linux" 273 | ], 274 | "engines": { 275 | "node": ">=12" 276 | } 277 | }, 278 | "node_modules/@esbuild/linux-x64": { 279 | "version": "0.21.5", 280 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 281 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 282 | "cpu": [ 283 | "x64" 284 | ], 285 | "dev": true, 286 | "optional": true, 287 | "os": [ 288 | "linux" 289 | ], 290 | "engines": { 291 | "node": ">=12" 292 | } 293 | }, 294 | "node_modules/@esbuild/netbsd-x64": { 295 | "version": "0.21.5", 296 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 297 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 298 | "cpu": [ 299 | "x64" 300 | ], 301 | "dev": true, 302 | "optional": true, 303 | "os": [ 304 | "netbsd" 305 | ], 306 | "engines": { 307 | "node": ">=12" 308 | } 309 | }, 310 | "node_modules/@esbuild/openbsd-x64": { 311 | "version": "0.21.5", 312 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 313 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 314 | "cpu": [ 315 | "x64" 316 | ], 317 | "dev": true, 318 | "optional": true, 319 | "os": [ 320 | "openbsd" 321 | ], 322 | "engines": { 323 | "node": ">=12" 324 | } 325 | }, 326 | "node_modules/@esbuild/sunos-x64": { 327 | "version": "0.21.5", 328 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 329 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 330 | "cpu": [ 331 | "x64" 332 | ], 333 | "dev": true, 334 | "optional": true, 335 | "os": [ 336 | "sunos" 337 | ], 338 | "engines": { 339 | "node": ">=12" 340 | } 341 | }, 342 | "node_modules/@esbuild/win32-arm64": { 343 | "version": "0.21.5", 344 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 345 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 346 | "cpu": [ 347 | "arm64" 348 | ], 349 | "dev": true, 350 | "optional": true, 351 | "os": [ 352 | "win32" 353 | ], 354 | "engines": { 355 | "node": ">=12" 356 | } 357 | }, 358 | "node_modules/@esbuild/win32-ia32": { 359 | "version": "0.21.5", 360 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 361 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 362 | "cpu": [ 363 | "ia32" 364 | ], 365 | "dev": true, 366 | "optional": true, 367 | "os": [ 368 | "win32" 369 | ], 370 | "engines": { 371 | "node": ">=12" 372 | } 373 | }, 374 | "node_modules/@esbuild/win32-x64": { 375 | "version": "0.21.5", 376 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 377 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 378 | "cpu": [ 379 | "x64" 380 | ], 381 | "dev": true, 382 | "optional": true, 383 | "os": [ 384 | "win32" 385 | ], 386 | "engines": { 387 | "node": ">=12" 388 | } 389 | }, 390 | "node_modules/@jridgewell/sourcemap-codec": { 391 | "version": "1.5.0", 392 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 393 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 394 | "dev": true 395 | }, 396 | "node_modules/@rollup/rollup-android-arm-eabi": { 397 | "version": "4.24.4", 398 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz", 399 | "integrity": "sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==", 400 | "cpu": [ 401 | "arm" 402 | ], 403 | "dev": true, 404 | "optional": true, 405 | "os": [ 406 | "android" 407 | ] 408 | }, 409 | "node_modules/@rollup/rollup-android-arm64": { 410 | "version": "4.24.4", 411 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz", 412 | "integrity": "sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==", 413 | "cpu": [ 414 | "arm64" 415 | ], 416 | "dev": true, 417 | "optional": true, 418 | "os": [ 419 | "android" 420 | ] 421 | }, 422 | "node_modules/@rollup/rollup-darwin-arm64": { 423 | "version": "4.24.4", 424 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz", 425 | "integrity": "sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==", 426 | "cpu": [ 427 | "arm64" 428 | ], 429 | "dev": true, 430 | "optional": true, 431 | "os": [ 432 | "darwin" 433 | ] 434 | }, 435 | "node_modules/@rollup/rollup-darwin-x64": { 436 | "version": "4.24.4", 437 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz", 438 | "integrity": "sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==", 439 | "cpu": [ 440 | "x64" 441 | ], 442 | "dev": true, 443 | "optional": true, 444 | "os": [ 445 | "darwin" 446 | ] 447 | }, 448 | "node_modules/@rollup/rollup-freebsd-arm64": { 449 | "version": "4.24.4", 450 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz", 451 | "integrity": "sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==", 452 | "cpu": [ 453 | "arm64" 454 | ], 455 | "dev": true, 456 | "optional": true, 457 | "os": [ 458 | "freebsd" 459 | ] 460 | }, 461 | "node_modules/@rollup/rollup-freebsd-x64": { 462 | "version": "4.24.4", 463 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz", 464 | "integrity": "sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==", 465 | "cpu": [ 466 | "x64" 467 | ], 468 | "dev": true, 469 | "optional": true, 470 | "os": [ 471 | "freebsd" 472 | ] 473 | }, 474 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 475 | "version": "4.24.4", 476 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz", 477 | "integrity": "sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==", 478 | "cpu": [ 479 | "arm" 480 | ], 481 | "dev": true, 482 | "optional": true, 483 | "os": [ 484 | "linux" 485 | ] 486 | }, 487 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 488 | "version": "4.24.4", 489 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz", 490 | "integrity": "sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==", 491 | "cpu": [ 492 | "arm" 493 | ], 494 | "dev": true, 495 | "optional": true, 496 | "os": [ 497 | "linux" 498 | ] 499 | }, 500 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 501 | "version": "4.24.4", 502 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz", 503 | "integrity": "sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==", 504 | "cpu": [ 505 | "arm64" 506 | ], 507 | "dev": true, 508 | "optional": true, 509 | "os": [ 510 | "linux" 511 | ] 512 | }, 513 | "node_modules/@rollup/rollup-linux-arm64-musl": { 514 | "version": "4.24.4", 515 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz", 516 | "integrity": "sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==", 517 | "cpu": [ 518 | "arm64" 519 | ], 520 | "dev": true, 521 | "optional": true, 522 | "os": [ 523 | "linux" 524 | ] 525 | }, 526 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 527 | "version": "4.24.4", 528 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz", 529 | "integrity": "sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==", 530 | "cpu": [ 531 | "ppc64" 532 | ], 533 | "dev": true, 534 | "optional": true, 535 | "os": [ 536 | "linux" 537 | ] 538 | }, 539 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 540 | "version": "4.24.4", 541 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz", 542 | "integrity": "sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==", 543 | "cpu": [ 544 | "riscv64" 545 | ], 546 | "dev": true, 547 | "optional": true, 548 | "os": [ 549 | "linux" 550 | ] 551 | }, 552 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 553 | "version": "4.24.4", 554 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz", 555 | "integrity": "sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==", 556 | "cpu": [ 557 | "s390x" 558 | ], 559 | "dev": true, 560 | "optional": true, 561 | "os": [ 562 | "linux" 563 | ] 564 | }, 565 | "node_modules/@rollup/rollup-linux-x64-gnu": { 566 | "version": "4.24.4", 567 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz", 568 | "integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==", 569 | "cpu": [ 570 | "x64" 571 | ], 572 | "dev": true, 573 | "optional": true, 574 | "os": [ 575 | "linux" 576 | ] 577 | }, 578 | "node_modules/@rollup/rollup-linux-x64-musl": { 579 | "version": "4.24.4", 580 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz", 581 | "integrity": "sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==", 582 | "cpu": [ 583 | "x64" 584 | ], 585 | "dev": true, 586 | "optional": true, 587 | "os": [ 588 | "linux" 589 | ] 590 | }, 591 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 592 | "version": "4.24.4", 593 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz", 594 | "integrity": "sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==", 595 | "cpu": [ 596 | "arm64" 597 | ], 598 | "dev": true, 599 | "optional": true, 600 | "os": [ 601 | "win32" 602 | ] 603 | }, 604 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 605 | "version": "4.24.4", 606 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz", 607 | "integrity": "sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==", 608 | "cpu": [ 609 | "ia32" 610 | ], 611 | "dev": true, 612 | "optional": true, 613 | "os": [ 614 | "win32" 615 | ] 616 | }, 617 | "node_modules/@rollup/rollup-win32-x64-msvc": { 618 | "version": "4.24.4", 619 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz", 620 | "integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==", 621 | "cpu": [ 622 | "x64" 623 | ], 624 | "dev": true, 625 | "optional": true, 626 | "os": [ 627 | "win32" 628 | ] 629 | }, 630 | "node_modules/@types/estree": { 631 | "version": "1.0.6", 632 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 633 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 634 | "dev": true 635 | }, 636 | "node_modules/@vitest/expect": { 637 | "version": "2.1.4", 638 | "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", 639 | "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", 640 | "dev": true, 641 | "dependencies": { 642 | "@vitest/spy": "2.1.4", 643 | "@vitest/utils": "2.1.4", 644 | "chai": "^5.1.2", 645 | "tinyrainbow": "^1.2.0" 646 | }, 647 | "funding": { 648 | "url": "https://opencollective.com/vitest" 649 | } 650 | }, 651 | "node_modules/@vitest/mocker": { 652 | "version": "2.1.4", 653 | "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", 654 | "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", 655 | "dev": true, 656 | "dependencies": { 657 | "@vitest/spy": "2.1.4", 658 | "estree-walker": "^3.0.3", 659 | "magic-string": "^0.30.12" 660 | }, 661 | "funding": { 662 | "url": "https://opencollective.com/vitest" 663 | }, 664 | "peerDependencies": { 665 | "msw": "^2.4.9", 666 | "vite": "^5.0.0" 667 | }, 668 | "peerDependenciesMeta": { 669 | "msw": { 670 | "optional": true 671 | }, 672 | "vite": { 673 | "optional": true 674 | } 675 | } 676 | }, 677 | "node_modules/@vitest/pretty-format": { 678 | "version": "2.1.4", 679 | "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", 680 | "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", 681 | "dev": true, 682 | "dependencies": { 683 | "tinyrainbow": "^1.2.0" 684 | }, 685 | "funding": { 686 | "url": "https://opencollective.com/vitest" 687 | } 688 | }, 689 | "node_modules/@vitest/runner": { 690 | "version": "2.1.4", 691 | "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", 692 | "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", 693 | "dev": true, 694 | "dependencies": { 695 | "@vitest/utils": "2.1.4", 696 | "pathe": "^1.1.2" 697 | }, 698 | "funding": { 699 | "url": "https://opencollective.com/vitest" 700 | } 701 | }, 702 | "node_modules/@vitest/snapshot": { 703 | "version": "2.1.4", 704 | "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", 705 | "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", 706 | "dev": true, 707 | "dependencies": { 708 | "@vitest/pretty-format": "2.1.4", 709 | "magic-string": "^0.30.12", 710 | "pathe": "^1.1.2" 711 | }, 712 | "funding": { 713 | "url": "https://opencollective.com/vitest" 714 | } 715 | }, 716 | "node_modules/@vitest/spy": { 717 | "version": "2.1.4", 718 | "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", 719 | "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", 720 | "dev": true, 721 | "dependencies": { 722 | "tinyspy": "^3.0.2" 723 | }, 724 | "funding": { 725 | "url": "https://opencollective.com/vitest" 726 | } 727 | }, 728 | "node_modules/@vitest/utils": { 729 | "version": "2.1.4", 730 | "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", 731 | "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", 732 | "dev": true, 733 | "dependencies": { 734 | "@vitest/pretty-format": "2.1.4", 735 | "loupe": "^3.1.2", 736 | "tinyrainbow": "^1.2.0" 737 | }, 738 | "funding": { 739 | "url": "https://opencollective.com/vitest" 740 | } 741 | }, 742 | "node_modules/assertion-error": { 743 | "version": "2.0.1", 744 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 745 | "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 746 | "dev": true, 747 | "engines": { 748 | "node": ">=12" 749 | } 750 | }, 751 | "node_modules/bindings": { 752 | "version": "1.5.0", 753 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 754 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 755 | "dependencies": { 756 | "file-uri-to-path": "1.0.0" 757 | } 758 | }, 759 | "node_modules/cac": { 760 | "version": "6.7.14", 761 | "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 762 | "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 763 | "dev": true, 764 | "engines": { 765 | "node": ">=8" 766 | } 767 | }, 768 | "node_modules/chai": { 769 | "version": "5.1.2", 770 | "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", 771 | "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", 772 | "dev": true, 773 | "dependencies": { 774 | "assertion-error": "^2.0.1", 775 | "check-error": "^2.1.1", 776 | "deep-eql": "^5.0.1", 777 | "loupe": "^3.1.0", 778 | "pathval": "^2.0.0" 779 | }, 780 | "engines": { 781 | "node": ">=12" 782 | } 783 | }, 784 | "node_modules/check-error": { 785 | "version": "2.1.1", 786 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", 787 | "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", 788 | "dev": true, 789 | "engines": { 790 | "node": ">= 16" 791 | } 792 | }, 793 | "node_modules/deep-eql": { 794 | "version": "5.0.2", 795 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", 796 | "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", 797 | "dev": true, 798 | "engines": { 799 | "node": ">=6" 800 | } 801 | }, 802 | "node_modules/esbuild": { 803 | "version": "0.21.5", 804 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 805 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 806 | "dev": true, 807 | "hasInstallScript": true, 808 | "bin": { 809 | "esbuild": "bin/esbuild" 810 | }, 811 | "engines": { 812 | "node": ">=12" 813 | }, 814 | "optionalDependencies": { 815 | "@esbuild/aix-ppc64": "0.21.5", 816 | "@esbuild/android-arm": "0.21.5", 817 | "@esbuild/android-arm64": "0.21.5", 818 | "@esbuild/android-x64": "0.21.5", 819 | "@esbuild/darwin-arm64": "0.21.5", 820 | "@esbuild/darwin-x64": "0.21.5", 821 | "@esbuild/freebsd-arm64": "0.21.5", 822 | "@esbuild/freebsd-x64": "0.21.5", 823 | "@esbuild/linux-arm": "0.21.5", 824 | "@esbuild/linux-arm64": "0.21.5", 825 | "@esbuild/linux-ia32": "0.21.5", 826 | "@esbuild/linux-loong64": "0.21.5", 827 | "@esbuild/linux-mips64el": "0.21.5", 828 | "@esbuild/linux-ppc64": "0.21.5", 829 | "@esbuild/linux-riscv64": "0.21.5", 830 | "@esbuild/linux-s390x": "0.21.5", 831 | "@esbuild/linux-x64": "0.21.5", 832 | "@esbuild/netbsd-x64": "0.21.5", 833 | "@esbuild/openbsd-x64": "0.21.5", 834 | "@esbuild/sunos-x64": "0.21.5", 835 | "@esbuild/win32-arm64": "0.21.5", 836 | "@esbuild/win32-ia32": "0.21.5", 837 | "@esbuild/win32-x64": "0.21.5" 838 | } 839 | }, 840 | "node_modules/estree-walker": { 841 | "version": "3.0.3", 842 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 843 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 844 | "dev": true, 845 | "dependencies": { 846 | "@types/estree": "^1.0.0" 847 | } 848 | }, 849 | "node_modules/expect-type": { 850 | "version": "1.1.0", 851 | "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", 852 | "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", 853 | "dev": true, 854 | "engines": { 855 | "node": ">=12.0.0" 856 | } 857 | }, 858 | "node_modules/file-uri-to-path": { 859 | "version": "1.0.0", 860 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 861 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 862 | }, 863 | "node_modules/fsevents": { 864 | "version": "2.3.3", 865 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 866 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 867 | "dev": true, 868 | "hasInstallScript": true, 869 | "optional": true, 870 | "os": [ 871 | "darwin" 872 | ], 873 | "engines": { 874 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 875 | } 876 | }, 877 | "node_modules/loupe": { 878 | "version": "3.1.2", 879 | "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", 880 | "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", 881 | "dev": true 882 | }, 883 | "node_modules/magic-string": { 884 | "version": "0.30.12", 885 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", 886 | "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", 887 | "dev": true, 888 | "dependencies": { 889 | "@jridgewell/sourcemap-codec": "^1.5.0" 890 | } 891 | }, 892 | "node_modules/nan": { 893 | "version": "2.22.0", 894 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", 895 | "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==" 896 | }, 897 | "node_modules/nanoid": { 898 | "version": "3.3.7", 899 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 900 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 901 | "dev": true, 902 | "funding": [ 903 | { 904 | "type": "github", 905 | "url": "https://github.com/sponsors/ai" 906 | } 907 | ], 908 | "bin": { 909 | "nanoid": "bin/nanoid.cjs" 910 | }, 911 | "engines": { 912 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 913 | } 914 | }, 915 | "node_modules/pathe": { 916 | "version": "1.1.2", 917 | "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", 918 | "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", 919 | "dev": true 920 | }, 921 | "node_modules/pathval": { 922 | "version": "2.0.0", 923 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", 924 | "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", 925 | "dev": true, 926 | "engines": { 927 | "node": ">= 14.16" 928 | } 929 | }, 930 | "node_modules/picocolors": { 931 | "version": "1.1.1", 932 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 933 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 934 | "dev": true 935 | }, 936 | "node_modules/postcss": { 937 | "version": "8.4.47", 938 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", 939 | "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", 940 | "dev": true, 941 | "funding": [ 942 | { 943 | "type": "opencollective", 944 | "url": "https://opencollective.com/postcss/" 945 | }, 946 | { 947 | "type": "tidelift", 948 | "url": "https://tidelift.com/funding/github/npm/postcss" 949 | }, 950 | { 951 | "type": "github", 952 | "url": "https://github.com/sponsors/ai" 953 | } 954 | ], 955 | "dependencies": { 956 | "nanoid": "^3.3.7", 957 | "picocolors": "^1.1.0", 958 | "source-map-js": "^1.2.1" 959 | }, 960 | "engines": { 961 | "node": "^10 || ^12 || >=14" 962 | } 963 | }, 964 | "node_modules/rollup": { 965 | "version": "4.24.4", 966 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.4.tgz", 967 | "integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==", 968 | "dev": true, 969 | "dependencies": { 970 | "@types/estree": "1.0.6" 971 | }, 972 | "bin": { 973 | "rollup": "dist/bin/rollup" 974 | }, 975 | "engines": { 976 | "node": ">=18.0.0", 977 | "npm": ">=8.0.0" 978 | }, 979 | "optionalDependencies": { 980 | "@rollup/rollup-android-arm-eabi": "4.24.4", 981 | "@rollup/rollup-android-arm64": "4.24.4", 982 | "@rollup/rollup-darwin-arm64": "4.24.4", 983 | "@rollup/rollup-darwin-x64": "4.24.4", 984 | "@rollup/rollup-freebsd-arm64": "4.24.4", 985 | "@rollup/rollup-freebsd-x64": "4.24.4", 986 | "@rollup/rollup-linux-arm-gnueabihf": "4.24.4", 987 | "@rollup/rollup-linux-arm-musleabihf": "4.24.4", 988 | "@rollup/rollup-linux-arm64-gnu": "4.24.4", 989 | "@rollup/rollup-linux-arm64-musl": "4.24.4", 990 | "@rollup/rollup-linux-powerpc64le-gnu": "4.24.4", 991 | "@rollup/rollup-linux-riscv64-gnu": "4.24.4", 992 | "@rollup/rollup-linux-s390x-gnu": "4.24.4", 993 | "@rollup/rollup-linux-x64-gnu": "4.24.4", 994 | "@rollup/rollup-linux-x64-musl": "4.24.4", 995 | "@rollup/rollup-win32-arm64-msvc": "4.24.4", 996 | "@rollup/rollup-win32-ia32-msvc": "4.24.4", 997 | "@rollup/rollup-win32-x64-msvc": "4.24.4", 998 | "fsevents": "~2.3.2" 999 | } 1000 | }, 1001 | "node_modules/siginfo": { 1002 | "version": "2.0.0", 1003 | "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 1004 | "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 1005 | "dev": true 1006 | }, 1007 | "node_modules/source-map-js": { 1008 | "version": "1.2.1", 1009 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1010 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1011 | "dev": true, 1012 | "engines": { 1013 | "node": ">=0.10.0" 1014 | } 1015 | }, 1016 | "node_modules/stackback": { 1017 | "version": "0.0.2", 1018 | "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 1019 | "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 1020 | "dev": true 1021 | }, 1022 | "node_modules/std-env": { 1023 | "version": "3.7.0", 1024 | "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", 1025 | "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", 1026 | "dev": true 1027 | }, 1028 | "node_modules/tinybench": { 1029 | "version": "2.9.0", 1030 | "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", 1031 | "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", 1032 | "dev": true 1033 | }, 1034 | "node_modules/tinyexec": { 1035 | "version": "0.3.1", 1036 | "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", 1037 | "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", 1038 | "dev": true 1039 | }, 1040 | "node_modules/tinypool": { 1041 | "version": "1.0.1", 1042 | "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", 1043 | "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", 1044 | "dev": true, 1045 | "engines": { 1046 | "node": "^18.0.0 || >=20.0.0" 1047 | } 1048 | }, 1049 | "node_modules/tinyrainbow": { 1050 | "version": "1.2.0", 1051 | "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", 1052 | "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", 1053 | "dev": true, 1054 | "engines": { 1055 | "node": ">=14.0.0" 1056 | } 1057 | }, 1058 | "node_modules/tinyspy": { 1059 | "version": "3.0.2", 1060 | "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", 1061 | "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", 1062 | "dev": true, 1063 | "engines": { 1064 | "node": ">=14.0.0" 1065 | } 1066 | }, 1067 | "node_modules/vite": { 1068 | "version": "5.4.10", 1069 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", 1070 | "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", 1071 | "dev": true, 1072 | "dependencies": { 1073 | "esbuild": "^0.21.3", 1074 | "postcss": "^8.4.43", 1075 | "rollup": "^4.20.0" 1076 | }, 1077 | "bin": { 1078 | "vite": "bin/vite.js" 1079 | }, 1080 | "engines": { 1081 | "node": "^18.0.0 || >=20.0.0" 1082 | }, 1083 | "funding": { 1084 | "url": "https://github.com/vitejs/vite?sponsor=1" 1085 | }, 1086 | "optionalDependencies": { 1087 | "fsevents": "~2.3.3" 1088 | }, 1089 | "peerDependencies": { 1090 | "@types/node": "^18.0.0 || >=20.0.0", 1091 | "less": "*", 1092 | "lightningcss": "^1.21.0", 1093 | "sass": "*", 1094 | "sass-embedded": "*", 1095 | "stylus": "*", 1096 | "sugarss": "*", 1097 | "terser": "^5.4.0" 1098 | }, 1099 | "peerDependenciesMeta": { 1100 | "@types/node": { 1101 | "optional": true 1102 | }, 1103 | "less": { 1104 | "optional": true 1105 | }, 1106 | "lightningcss": { 1107 | "optional": true 1108 | }, 1109 | "sass": { 1110 | "optional": true 1111 | }, 1112 | "sass-embedded": { 1113 | "optional": true 1114 | }, 1115 | "stylus": { 1116 | "optional": true 1117 | }, 1118 | "sugarss": { 1119 | "optional": true 1120 | }, 1121 | "terser": { 1122 | "optional": true 1123 | } 1124 | } 1125 | }, 1126 | "node_modules/vite-node": { 1127 | "version": "2.1.4", 1128 | "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", 1129 | "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", 1130 | "dev": true, 1131 | "dependencies": { 1132 | "cac": "^6.7.14", 1133 | "debug": "^4.3.7", 1134 | "pathe": "^1.1.2", 1135 | "vite": "^5.0.0" 1136 | }, 1137 | "bin": { 1138 | "vite-node": "vite-node.mjs" 1139 | }, 1140 | "engines": { 1141 | "node": "^18.0.0 || >=20.0.0" 1142 | }, 1143 | "funding": { 1144 | "url": "https://opencollective.com/vitest" 1145 | } 1146 | }, 1147 | "node_modules/vite-node/node_modules/debug": { 1148 | "version": "4.3.7", 1149 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", 1150 | "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", 1151 | "dev": true, 1152 | "dependencies": { 1153 | "ms": "^2.1.3" 1154 | }, 1155 | "engines": { 1156 | "node": ">=6.0" 1157 | }, 1158 | "peerDependenciesMeta": { 1159 | "supports-color": { 1160 | "optional": true 1161 | } 1162 | } 1163 | }, 1164 | "node_modules/vite-node/node_modules/ms": { 1165 | "version": "2.1.3", 1166 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1167 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1168 | "dev": true 1169 | }, 1170 | "node_modules/vitest": { 1171 | "version": "2.1.4", 1172 | "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", 1173 | "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", 1174 | "dev": true, 1175 | "dependencies": { 1176 | "@vitest/expect": "2.1.4", 1177 | "@vitest/mocker": "2.1.4", 1178 | "@vitest/pretty-format": "^2.1.4", 1179 | "@vitest/runner": "2.1.4", 1180 | "@vitest/snapshot": "2.1.4", 1181 | "@vitest/spy": "2.1.4", 1182 | "@vitest/utils": "2.1.4", 1183 | "chai": "^5.1.2", 1184 | "debug": "^4.3.7", 1185 | "expect-type": "^1.1.0", 1186 | "magic-string": "^0.30.12", 1187 | "pathe": "^1.1.2", 1188 | "std-env": "^3.7.0", 1189 | "tinybench": "^2.9.0", 1190 | "tinyexec": "^0.3.1", 1191 | "tinypool": "^1.0.1", 1192 | "tinyrainbow": "^1.2.0", 1193 | "vite": "^5.0.0", 1194 | "vite-node": "2.1.4", 1195 | "why-is-node-running": "^2.3.0" 1196 | }, 1197 | "bin": { 1198 | "vitest": "vitest.mjs" 1199 | }, 1200 | "engines": { 1201 | "node": "^18.0.0 || >=20.0.0" 1202 | }, 1203 | "funding": { 1204 | "url": "https://opencollective.com/vitest" 1205 | }, 1206 | "peerDependencies": { 1207 | "@edge-runtime/vm": "*", 1208 | "@types/node": "^18.0.0 || >=20.0.0", 1209 | "@vitest/browser": "2.1.4", 1210 | "@vitest/ui": "2.1.4", 1211 | "happy-dom": "*", 1212 | "jsdom": "*" 1213 | }, 1214 | "peerDependenciesMeta": { 1215 | "@edge-runtime/vm": { 1216 | "optional": true 1217 | }, 1218 | "@types/node": { 1219 | "optional": true 1220 | }, 1221 | "@vitest/browser": { 1222 | "optional": true 1223 | }, 1224 | "@vitest/ui": { 1225 | "optional": true 1226 | }, 1227 | "happy-dom": { 1228 | "optional": true 1229 | }, 1230 | "jsdom": { 1231 | "optional": true 1232 | } 1233 | } 1234 | }, 1235 | "node_modules/vitest/node_modules/debug": { 1236 | "version": "4.3.7", 1237 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", 1238 | "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", 1239 | "dev": true, 1240 | "dependencies": { 1241 | "ms": "^2.1.3" 1242 | }, 1243 | "engines": { 1244 | "node": ">=6.0" 1245 | }, 1246 | "peerDependenciesMeta": { 1247 | "supports-color": { 1248 | "optional": true 1249 | } 1250 | } 1251 | }, 1252 | "node_modules/vitest/node_modules/ms": { 1253 | "version": "2.1.3", 1254 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1255 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1256 | "dev": true 1257 | }, 1258 | "node_modules/why-is-node-running": { 1259 | "version": "2.3.0", 1260 | "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 1261 | "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 1262 | "dev": true, 1263 | "dependencies": { 1264 | "siginfo": "^2.0.0", 1265 | "stackback": "0.0.2" 1266 | }, 1267 | "bin": { 1268 | "why-is-node-running": "cli.js" 1269 | }, 1270 | "engines": { 1271 | "node": ">=8" 1272 | } 1273 | } 1274 | } 1275 | } 1276 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@airbnb/node-memwatch", 3 | "description": "Keep an eye on your memory usage, and discover and isolate leaks.", 4 | "version": "3.0.0", 5 | "author": "Lloyd Hilaiel (http://lloyd.io)", 6 | "engines": { 7 | "node": ">= 18" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/airbnb/node-memwatch.git" 12 | }, 13 | "main": "include.js", 14 | "licenses": [ 15 | { 16 | "type": "wtfpl" 17 | } 18 | ], 19 | "bugs": { 20 | "url": "https://github.com/airbnb/node-memwatch/issues" 21 | }, 22 | "scripts": { 23 | "install": "node-gyp rebuild", 24 | "test": "vitest" 25 | }, 26 | "devDependencies": { 27 | "vitest": "^2.1.4" 28 | }, 29 | "contributors": [ 30 | "Jed Parsons (@jedp)", 31 | "Jeff Haynie (@jhaynie)", 32 | "Justin Matthews (@jmatthewsr-ms)" 33 | ], 34 | "dependencies": { 35 | "bindings": "^1.5.0", 36 | "nan": "^2.22.0" 37 | }, 38 | "publishConfig": { 39 | "access": "public" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/heapdiff.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 2012|lloyd|http://wtfpl.org 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include // abs() 10 | #include // time() 11 | 12 | #include "heapdiff.hh" 13 | #include "util.hh" 14 | 15 | using namespace v8; 16 | using namespace node; 17 | using namespace std; 18 | 19 | static bool s_inProgress = false; 20 | static time_t s_startTime; 21 | 22 | bool heapdiff::HeapDiff::InProgress() 23 | { 24 | return s_inProgress; 25 | } 26 | 27 | heapdiff::HeapDiff::HeapDiff() : ObjectWrap(), before(NULL), after(NULL), 28 | ended(false) 29 | { 30 | } 31 | 32 | heapdiff::HeapDiff::~HeapDiff() 33 | { 34 | if (before) { 35 | ((HeapSnapshot *) before)->Delete(); 36 | before = NULL; 37 | } 38 | 39 | if (after) { 40 | ((HeapSnapshot *) after)->Delete(); 41 | after = NULL; 42 | } 43 | } 44 | 45 | void 46 | heapdiff::HeapDiff::Initialize ( v8::Local target ) 47 | { 48 | Nan::HandleScope scope; 49 | 50 | v8::Local t = Nan::New(New); 51 | t->InstanceTemplate()->SetInternalFieldCount(1); 52 | t->SetClassName(Nan::New("HeapDiff").ToLocalChecked()); 53 | 54 | Nan::SetPrototypeMethod(t, "end", End); 55 | 56 | Nan::Set(target, Nan::New("HeapDiff").ToLocalChecked(), t->GetFunction( 57 | Nan::GetCurrentContext() 58 | ).ToLocalChecked()); 59 | } 60 | 61 | NAN_METHOD(heapdiff::HeapDiff::New) 62 | { 63 | // Don't blow up when the caller says "new require('memwatch').HeapDiff()" 64 | // issue #30 65 | // stolen from: https://github.com/kkaefer/node-cpp-modules/commit/bd9432026affafd8450ecfd9b49b7dc647b6d348 66 | if (!info.IsConstructCall()) { 67 | return Nan::ThrowTypeError("Use the new operator to create instances of this object."); 68 | } 69 | 70 | Nan::HandleScope scope; 71 | 72 | // allocate the underlying c++ class and wrap it up in the this pointer 73 | HeapDiff * self = new HeapDiff(); 74 | self->Wrap(info.This()); 75 | 76 | // take a snapshot and save a pointer to it 77 | s_inProgress = true; 78 | s_startTime = time(NULL); 79 | 80 | #if (NODE_MODULE_VERSION >= 0x002D) 81 | self->before = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(NULL); 82 | #else 83 | #if (NODE_MODULE_VERSION > 0x000B) 84 | self->before = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(Nan::New("").ToLocalChecked(), NULL); 85 | #else 86 | self->before = v8::HeapProfiler::TakeSnapshot(Nan::New("").ToLocalChecked(), HeapSnapshot::kFull, NULL); 87 | #endif 88 | #endif 89 | 90 | s_inProgress = false; 91 | 92 | info.GetReturnValue().Set(info.This()); 93 | } 94 | 95 | static string handleToStr(const Local & str) 96 | { 97 | return *Nan::Utf8String(str); 98 | } 99 | 100 | static void 101 | buildIDSet(set * seen, const HeapGraphNode* cur, int & s) 102 | { 103 | Nan::HandleScope scope; 104 | 105 | // cycle detection 106 | if (seen->find(cur->GetId()) != seen->end()) { 107 | return; 108 | } 109 | // always ignore HeapDiff related memory 110 | if (cur->GetType() == HeapGraphNode::kObject && 111 | handleToStr(cur->GetName()).compare("HeapDiff") == 0) 112 | { 113 | return; 114 | } 115 | 116 | // update memory usage as we go 117 | #if (NODE_MODULE_VERSION >= 0x002D) 118 | s += cur->GetShallowSize(); 119 | #else 120 | s += cur->GetSelfSize(); 121 | #endif 122 | seen->insert(cur->GetId()); 123 | 124 | for (int i=0; i < cur->GetChildrenCount(); i++) { 125 | buildIDSet(seen, cur->GetChild(i)->GetToNode(), s); 126 | } 127 | } 128 | 129 | typedef set idset; 130 | 131 | // why doesn't STL work? 132 | // XXX: improve this algorithm 133 | void setDiff(idset a, idset b, vector &c) 134 | { 135 | for (idset::iterator i = a.begin(); i != a.end(); i++) { 136 | if (b.find(*i) == b.end()) c.push_back(*i); 137 | } 138 | } 139 | 140 | 141 | class example 142 | { 143 | public: 144 | HeapGraphEdge::Type context; 145 | HeapGraphNode::Type type; 146 | std::string name; 147 | std::string value; 148 | std::string heap_value; 149 | int self_size; 150 | int retained_size; 151 | int retainers; 152 | 153 | example() : context(HeapGraphEdge::kHidden), 154 | type(HeapGraphNode::kHidden), 155 | self_size(0), retained_size(0), retainers(0) { }; 156 | }; 157 | 158 | class change 159 | { 160 | public: 161 | long int size; 162 | long int added; 163 | long int released; 164 | std::vector examples; 165 | 166 | change() : size(0), added(0), released(0) { } 167 | }; 168 | 169 | typedef std::mapchangeset; 170 | 171 | static void manageChange(changeset & changes, const HeapGraphNode * node, bool added) 172 | { 173 | std::string type; 174 | 175 | switch(node->GetType()) { 176 | case HeapGraphNode::kArray: 177 | type.append("Array"); 178 | break; 179 | case HeapGraphNode::kString: 180 | type.append("String"); 181 | break; 182 | case HeapGraphNode::kObject: 183 | type.append(handleToStr(node->GetName())); 184 | break; 185 | case HeapGraphNode::kCode: 186 | type.append("Code"); 187 | break; 188 | case HeapGraphNode::kClosure: 189 | type.append("Closure"); 190 | break; 191 | case HeapGraphNode::kRegExp: 192 | type.append("RegExp"); 193 | break; 194 | case HeapGraphNode::kHeapNumber: 195 | type.append("Number"); 196 | break; 197 | case HeapGraphNode::kNative: 198 | type.append("Native"); 199 | break; 200 | case HeapGraphNode::kHidden: 201 | default: 202 | return; 203 | } 204 | 205 | if (changes.find(type) == changes.end()) { 206 | changes[type] = change(); 207 | } 208 | 209 | changeset::iterator i = changes.find(type); 210 | 211 | #if (NODE_MODULE_VERSION >= 0x002D) 212 | i->second.size += node->GetShallowSize() * (added ? 1 : -1); 213 | #else 214 | i->second.size += node->GetSelfSize() * (added ? 1 : -1); 215 | #endif 216 | if (added) i->second.added++; 217 | else i->second.released++; 218 | 219 | // XXX: example 220 | 221 | return; 222 | } 223 | 224 | static Local changesetToObject(changeset & changes) 225 | { 226 | Nan::EscapableHandleScope scope; 227 | Local a = Nan::New(); 228 | 229 | for (changeset::iterator i = changes.begin(); i != changes.end(); i++) { 230 | Local d = Nan::New(); 231 | Nan::Set(d, Nan::New("what").ToLocalChecked(), Nan::New(i->first.c_str()).ToLocalChecked()); 232 | Nan::Set(d, Nan::New("size_bytes").ToLocalChecked(), Nan::New(i->second.size)); 233 | Nan::Set(d, Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(i->second.size).c_str()).ToLocalChecked()); 234 | Nan::Set(d, Nan::New("+").ToLocalChecked(), Nan::New(i->second.added)); 235 | Nan::Set(d, Nan::New("-").ToLocalChecked(), Nan::New(i->second.released)); 236 | Nan::Set(a, a->Length(), d); 237 | } 238 | 239 | return scope.Escape(a); 240 | } 241 | 242 | 243 | static v8::Local 244 | compare(const v8::HeapSnapshot * before, const v8::HeapSnapshot * after) 245 | { 246 | Nan::EscapableHandleScope scope; 247 | int s, diffBytes; 248 | 249 | Local o = Nan::New(); 250 | 251 | // first let's append summary information 252 | Local b = Nan::New(); 253 | Nan::Set(b, Nan::New("nodes").ToLocalChecked(), Nan::New(before->GetNodesCount())); 254 | //Nan::Set(b, Nan::New("time"), s_startTime); 255 | Nan::Set(o, Nan::New("before").ToLocalChecked(), b); 256 | 257 | Local a = Nan::New(); 258 | Nan::Set(a, Nan::New("nodes").ToLocalChecked(), Nan::New(after->GetNodesCount())); 259 | //Nan::Set(a, Nan::New("time"), time(NULL)); 260 | Nan::Set(o, Nan::New("after").ToLocalChecked(), a); 261 | 262 | // now let's get allocations by name 263 | set beforeIDs, afterIDs; 264 | s = 0; 265 | buildIDSet(&beforeIDs, before->GetRoot(), s); 266 | Nan::Set(b, Nan::New("size_bytes").ToLocalChecked(), Nan::New(s)); 267 | Nan::Set(b, Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(s).c_str()).ToLocalChecked()); 268 | 269 | diffBytes = s; 270 | s = 0; 271 | buildIDSet(&afterIDs, after->GetRoot(), s); 272 | Nan::Set(a, Nan::New("size_bytes").ToLocalChecked(), Nan::New(s)); 273 | Nan::Set(a, Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(s).c_str()).ToLocalChecked()); 274 | 275 | diffBytes = s - diffBytes; 276 | 277 | Local c = Nan::New(); 278 | Nan::Set(c, Nan::New("size_bytes").ToLocalChecked(), Nan::New(diffBytes)); 279 | Nan::Set(c, Nan::New("size").ToLocalChecked(), Nan::New(mw_util::niceSize(diffBytes).c_str()).ToLocalChecked()); 280 | Nan::Set(o, Nan::New("change").ToLocalChecked(), c); 281 | 282 | // before - after will reveal nodes released (memory freed) 283 | vector changedIDs; 284 | setDiff(beforeIDs, afterIDs, changedIDs); 285 | Nan::Set(c, Nan::New("freed_nodes").ToLocalChecked(), Nan::New(changedIDs.size())); 286 | 287 | // here's where we'll collect all the summary information 288 | changeset changes; 289 | 290 | // for each of these nodes, let's aggregate the change information 291 | for (unsigned long i = 0; i < changedIDs.size(); i++) { 292 | const HeapGraphNode * n = before->GetNodeById(changedIDs[i]); 293 | manageChange(changes, n, false); 294 | } 295 | 296 | changedIDs.clear(); 297 | 298 | // after - before will reveal nodes added (memory allocated) 299 | setDiff(afterIDs, beforeIDs, changedIDs); 300 | 301 | Nan::Set(c, Nan::New("allocated_nodes").ToLocalChecked(), Nan::New(changedIDs.size())); 302 | 303 | for (unsigned long i = 0; i < changedIDs.size(); i++) { 304 | const HeapGraphNode * n = after->GetNodeById(changedIDs[i]); 305 | manageChange(changes, n, true); 306 | } 307 | 308 | Nan::Set(c, Nan::New("details").ToLocalChecked(), changesetToObject(changes)); 309 | 310 | return scope.Escape(o); 311 | } 312 | 313 | NAN_METHOD(heapdiff::HeapDiff::End) 314 | { 315 | // take another snapshot and compare them 316 | Nan::HandleScope scope; 317 | 318 | HeapDiff *t = Unwrap( info.This() ); 319 | 320 | // How shall we deal with double .end()ing? The only reasonable 321 | // approach seems to be an exception, cause nothing else makes 322 | // sense. 323 | if (t->ended) { 324 | return Nan::ThrowError("attempt to end() a HeapDiff that was already ended"); 325 | } 326 | t->ended = true; 327 | 328 | s_inProgress = true; 329 | #if (NODE_MODULE_VERSION >= 0x002D) 330 | t->after = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(NULL); 331 | #else 332 | #if (NODE_MODULE_VERSION > 0x000B) 333 | t->after = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(Nan::New("").ToLocalChecked(), NULL); 334 | #else 335 | t->after = v8::HeapProfiler::TakeSnapshot(Nan::New("").ToLocalChecked(), HeapSnapshot::kFull, NULL); 336 | #endif 337 | #endif 338 | s_inProgress = false; 339 | 340 | v8::Local comparison = compare(t->before, t->after); 341 | // free early, free often. I mean, after all, this process we're in is 342 | // probably having memory problems. We want to help her. 343 | ((HeapSnapshot *) t->before)->Delete(); 344 | t->before = NULL; 345 | ((HeapSnapshot *) t->after)->Delete(); 346 | t->after = NULL; 347 | 348 | info.GetReturnValue().Set(comparison); 349 | } 350 | -------------------------------------------------------------------------------- /src/heapdiff.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * 2012|lloyd|http://wtfpl.org 3 | */ 4 | 5 | #ifndef __HEADDIFF_H 6 | #define __HEADDIFF_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace heapdiff 14 | { 15 | class HeapDiff : public Nan::ObjectWrap 16 | { 17 | public: 18 | static void Initialize ( v8::Local target ); 19 | 20 | static NAN_METHOD(New); 21 | static NAN_METHOD(End); 22 | static bool InProgress(); 23 | 24 | protected: 25 | HeapDiff(); 26 | ~HeapDiff(); 27 | private: 28 | const v8::HeapSnapshot * before; 29 | const v8::HeapSnapshot * after; 30 | bool ended; 31 | }; 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/init.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 2012|lloyd|do what the fuck you want to 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "heapdiff.hh" 9 | #include "memwatch.hh" 10 | 11 | extern "C" { 12 | void init (v8::Local target) 13 | { 14 | Nan::HandleScope scope; 15 | heapdiff::HeapDiff::Initialize(target); 16 | 17 | Nan::SetMethod(target, "upon_gc", memwatch::upon_gc); 18 | Nan::SetMethod(target, "gc", memwatch::trigger_gc); 19 | 20 | Nan::AddGCPrologueCallback(memwatch::before_gc); 21 | Nan::AddGCEpilogueCallback(memwatch::after_gc); 22 | } 23 | 24 | NODE_MODULE(memwatch, init); 25 | }; 26 | -------------------------------------------------------------------------------- /src/memwatch.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 2012|lloyd|http://wtfpl.org 3 | */ 4 | 5 | #include "platformcompat.hh" 6 | #include "memwatch.hh" 7 | #include "heapdiff.hh" 8 | #include "util.hh" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include // for pow 18 | #include // for time 19 | #include 20 | 21 | using namespace v8; 22 | using namespace node; 23 | 24 | Local g_context; 25 | 26 | class UponGCCallback : public Nan::AsyncResource { 27 | public: 28 | UponGCCallback(v8::Local callback_) : Nan::AsyncResource("memwatch:upon_gc") { 29 | callback.Reset(callback_); 30 | } 31 | ~UponGCCallback() { 32 | callback.Reset(); 33 | } 34 | 35 | void Call(int argc, Local argv[]) { 36 | v8::Isolate *isolate = v8::Isolate::GetCurrent(); 37 | runInAsyncScope(isolate->GetCurrentContext()->Global(), Nan::New(callback), argc, argv); 38 | } 39 | 40 | private: 41 | Nan::Persistent callback; 42 | }; 43 | 44 | UponGCCallback *uponGCCallback = NULL; 45 | 46 | struct GCStats { 47 | // counts of different types of gc events 48 | size_t gcScavengeCount; 49 | uint64_t gcScavengeTime; 50 | 51 | size_t gcMarkSweepCompactCount; 52 | uint64_t gcMarkSweepCompactTime; 53 | 54 | size_t gcIncrementalMarkingCount; 55 | uint64_t gcIncrementalMarkingTime; 56 | 57 | size_t gcProcessWeakCallbacksCount; 58 | uint64_t gcProcessWeakCallbacksTime; 59 | }; 60 | 61 | struct Baton { 62 | uv_work_t req; 63 | size_t total_heap_size; 64 | size_t total_heap_size_executable; 65 | size_t total_physical_size; 66 | size_t total_available_size; 67 | size_t used_heap_size; 68 | size_t heap_size_limit; 69 | size_t malloced_memory; 70 | size_t peak_malloced_memory; 71 | 72 | GCStats stats; 73 | 74 | uint64_t gc_time; 75 | 76 | uint64_t gc_ts; 77 | 78 | GCType type; 79 | GCCallbackFlags flags; 80 | }; 81 | 82 | static uint64_t currentGCStartTime = 0; 83 | static GCStats s_stats = {0, 0, 0, 0, 0, 0, 0, 0}; 84 | 85 | static v8::Local javascriptNumber(uint64_t n) { 86 | if (n < pow(2, 32)) { 87 | return Nan::New(static_cast(n)); 88 | } else { 89 | return Nan::New(static_cast(n)); 90 | } 91 | } 92 | 93 | static v8::Local javascriptNumberSize(size_t n) { 94 | return javascriptNumber(static_cast(n)); 95 | } 96 | 97 | static void AsyncMemwatchAfter(uv_work_t* request) { 98 | Nan::HandleScope scope; 99 | 100 | Baton * b = (Baton *) request->data; 101 | // if there are any listeners, it's time to emit! 102 | if (uponGCCallback) { 103 | Local argv[2]; 104 | 105 | Local stats = Nan::New(); 106 | 107 | Nan::Set(stats, Nan::New("gc_ts").ToLocalChecked(), javascriptNumber(b->gc_ts)); 108 | 109 | Nan::Set(stats, Nan::New("gcScavengeCount").ToLocalChecked(), javascriptNumberSize(b->stats.gcScavengeCount)); 110 | Nan::Set(stats, Nan::New("gcScavengeTime").ToLocalChecked(), javascriptNumber(b->stats.gcScavengeTime)); 111 | Nan::Set(stats, Nan::New("gcMarkSweepCompactCount").ToLocalChecked(), javascriptNumberSize(b->stats.gcMarkSweepCompactCount)); 112 | Nan::Set(stats, Nan::New("gcMarkSweepCompactTime").ToLocalChecked(), javascriptNumber(b->stats.gcMarkSweepCompactTime)); 113 | Nan::Set(stats, Nan::New("gcIncrementalMarkingCount").ToLocalChecked(), javascriptNumberSize(b->stats.gcIncrementalMarkingCount)); 114 | Nan::Set(stats, Nan::New("gcIncrementalMarkingTime").ToLocalChecked(), javascriptNumber(b->stats.gcIncrementalMarkingTime)); 115 | Nan::Set(stats, Nan::New("gcProcessWeakCallbacksCount").ToLocalChecked(), javascriptNumberSize(b->stats.gcProcessWeakCallbacksCount)); 116 | Nan::Set(stats, Nan::New("gcProcessWeakCallbacksTime").ToLocalChecked(), javascriptNumber(b->stats.gcProcessWeakCallbacksTime)); 117 | 118 | Nan::Set(stats, Nan::New("total_heap_size").ToLocalChecked(), javascriptNumberSize(b->total_heap_size)); 119 | Nan::Set(stats, Nan::New("total_heap_size_executable").ToLocalChecked(), javascriptNumberSize(b->total_heap_size_executable)); 120 | Nan::Set(stats, Nan::New("total_physical_size").ToLocalChecked(), javascriptNumberSize(b->total_physical_size)); 121 | Nan::Set(stats, Nan::New("total_available_size").ToLocalChecked(), javascriptNumberSize(b->total_available_size)); 122 | Nan::Set(stats, Nan::New("used_heap_size").ToLocalChecked(), javascriptNumberSize(b->used_heap_size)); 123 | Nan::Set(stats, Nan::New("heap_size_limit").ToLocalChecked(), javascriptNumberSize(b->heap_size_limit)); 124 | Nan::Set(stats, Nan::New("malloced_memory").ToLocalChecked(), javascriptNumberSize(b->malloced_memory)); 125 | Nan::Set(stats, Nan::New("peak_malloced_memory").ToLocalChecked(), javascriptNumberSize(b->peak_malloced_memory)); 126 | Nan::Set(stats, Nan::New("gc_time").ToLocalChecked(), javascriptNumber(b->gc_time)); 127 | 128 | // the type of event to emit 129 | argv[0] = Nan::New("stats").ToLocalChecked(); 130 | argv[1] = stats; 131 | uponGCCallback->Call(2, argv); 132 | } 133 | 134 | delete b; 135 | } 136 | 137 | NAN_GC_CALLBACK(memwatch::before_gc) { 138 | currentGCStartTime = uv_hrtime(); 139 | } 140 | 141 | static void noop_work_func(uv_work_t *) { } 142 | 143 | NAN_GC_CALLBACK(memwatch::after_gc) { 144 | if (heapdiff::HeapDiff::InProgress()) return; 145 | 146 | uint64_t gcEnd = uv_hrtime(); 147 | uint64_t gcTime = gcEnd - currentGCStartTime; 148 | 149 | switch(type) { 150 | case kGCTypeScavenge: 151 | s_stats.gcScavengeCount++; 152 | s_stats.gcScavengeTime += gcTime; 153 | return; 154 | case kGCTypeIncrementalMarking: 155 | s_stats.gcIncrementalMarkingCount++; 156 | s_stats.gcIncrementalMarkingTime += gcTime; 157 | return; 158 | case kGCTypeProcessWeakCallbacks: 159 | s_stats.gcProcessWeakCallbacksCount++; 160 | s_stats.gcProcessWeakCallbacksTime += gcTime; 161 | return; 162 | 163 | case kGCTypeMarkSweepCompact: 164 | case kGCTypeAll: 165 | break; 166 | } 167 | 168 | if (type == kGCTypeMarkSweepCompact) { 169 | s_stats.gcMarkSweepCompactCount++; 170 | s_stats.gcMarkSweepCompactTime += gcTime; 171 | 172 | Nan::HandleScope scope; 173 | 174 | Baton * baton = new Baton; 175 | v8::HeapStatistics hs; 176 | 177 | Nan::GetHeapStatistics(&hs); 178 | 179 | timeval tv; 180 | gettimeofday(&tv, NULL); 181 | 182 | baton->gc_ts = (tv.tv_sec * 1000000) + tv.tv_usec; 183 | 184 | baton->total_heap_size = hs.total_heap_size(); 185 | baton->total_heap_size_executable = hs.total_heap_size_executable(); 186 | baton->total_physical_size = hs.total_physical_size(); 187 | baton->total_available_size = hs.total_available_size(); 188 | baton->used_heap_size = hs.used_heap_size(); 189 | baton->heap_size_limit = hs.heap_size_limit(); 190 | baton->malloced_memory = hs.malloced_memory(); 191 | baton->peak_malloced_memory = hs.peak_malloced_memory(); 192 | baton->gc_time = gcTime; 193 | baton->type = type; 194 | baton->flags = flags; 195 | baton->stats = s_stats; 196 | baton->req.data = (void *) baton; 197 | 198 | // schedule our work to run in a moment, once gc has fully completed. 199 | // 200 | // here we pass a noop work function to work around a flaw in libuv, 201 | // uv_queue_work on unix works fine, but will will crash on 202 | // windows. see: https://github.com/joyent/libuv/pull/629 203 | uv_queue_work(uv_default_loop(), &(baton->req), 204 | noop_work_func, (uv_after_work_cb)AsyncMemwatchAfter); 205 | } 206 | } 207 | 208 | NAN_METHOD(memwatch::upon_gc) { 209 | Nan::HandleScope scope; 210 | if (info.Length() >= 1 && info[0]->IsFunction()) { 211 | uponGCCallback = new UponGCCallback(info[0].As()); 212 | } 213 | info.GetReturnValue().Set(Nan::Undefined()); 214 | } 215 | 216 | NAN_METHOD(memwatch::trigger_gc) { 217 | Nan::HandleScope scope; 218 | int deadline_in_ms = 500; 219 | if (info.Length() >= 1 && info[0]->IsNumber()) { 220 | deadline_in_ms = (int)(info[0]->Int32Value(Nan::GetCurrentContext()).FromJust()); 221 | } 222 | #if (NODE_MODULE_VERSION >= 0x002D) 223 | Nan::IdleNotification(deadline_in_ms); 224 | Nan::LowMemoryNotification(); 225 | #else 226 | while(!Nan::IdleNotification(deadline_in_ms)) {}; 227 | Nan::LowMemoryNotification(); 228 | #endif 229 | info.GetReturnValue().Set(Nan::Undefined()); 230 | } 231 | -------------------------------------------------------------------------------- /src/memwatch.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * 2012|lloyd|http://wtfpl.org 3 | */ 4 | 5 | #ifndef __MEMWATCH_HH 6 | #define __MEMWATCH_HH 7 | 8 | #include 9 | #include 10 | 11 | namespace memwatch 12 | { 13 | NAN_METHOD(upon_gc); 14 | NAN_METHOD(trigger_gc); 15 | NAN_GC_CALLBACK(before_gc); 16 | NAN_GC_CALLBACK(after_gc); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/platformcompat.hh: -------------------------------------------------------------------------------- 1 | #ifndef __PLATFORMCOMPAT_H 2 | #define __PLATFORMCOMPAT_H 3 | 4 | #if defined(_MSC_VER) 5 | #include //isinf, isnan 6 | #include //min 7 | #define ISINF _finite 8 | #define ISNAN _isnan 9 | #define FMIN __min 10 | #define ROUND(x) floor(x + 0.5) 11 | #else 12 | #include // round(), isinf, isnan 13 | #define ISINF std::isinf 14 | #define ISNAN std::isnan 15 | #define FMIN fmin 16 | #define ROUND round 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/util.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 2012|lloyd|http://wtfpl.org 3 | */ 4 | 5 | #include "platformcompat.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | 10 | #include // abs() 11 | 12 | std::string 13 | mw_util::niceSize(int bytes) 14 | { 15 | std::stringstream ss; 16 | 17 | if (abs(bytes) > 1024 * 1024) { 18 | ss << ROUND(bytes / (((double) 1024 * 1024 ) / 100)) / (double) 100 << " mb"; 19 | } else if (abs(bytes) > 1024) { 20 | ss << ROUND(bytes / (((double) 1024 ) / 100)) / (double) 100 << " kb"; 21 | } else { 22 | ss << bytes << " bytes"; 23 | } 24 | 25 | return ss.str(); 26 | } 27 | 28 | std::string 29 | mw_util::niceDelta(int seconds) 30 | { 31 | std::stringstream ss; 32 | 33 | if (seconds > (60*60)) { 34 | ss << (seconds / (60*60)) << "h "; 35 | seconds %= (60*60); 36 | } 37 | 38 | if (seconds > (60)) { 39 | ss << (seconds / (60)) << "m "; 40 | seconds %= (60); 41 | } 42 | 43 | ss << seconds << "s"; 44 | 45 | return ss.str(); 46 | } 47 | -------------------------------------------------------------------------------- /src/util.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * 2012|lloyd|http://wtfpl.org 3 | */ 4 | 5 | #include 6 | 7 | namespace mw_util { 8 | // given a size in bytes, return a human readable representation of the 9 | // string 10 | std::string niceSize(int bytes); 11 | 12 | // given a delta in seconds, return a human redable representation 13 | std::string niceDelta(int seconds); 14 | }; 15 | 16 | 17 | --------------------------------------------------------------------------------