New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
performance degredation #912
Comments
Are you using typescript? If not, try If so, or maybe along with it, try In my experience so far, the huge perf hit is usually ts-node doing type checking, the fact that it's so much slower in a big project dir really makes that seem more likely. If that fixes the problem for you, I'll probably make |
If it is a ts-node issue, then it can also be affected pretty profoundly by your tsconfig.json |
Great suggestions! We do use typescript, but our project is compiled and we run tests from tsconfig:
Narrowing down one slow test with the config you described is interesting. package.json:
Its difficult to get a feel for timing and order of operations, but it feels like a surprise compile-like step is taking place prior to any testing. |
Ah, there's this:
Even though you're running the tests as JavaScript, they're being run with the If you take that out (or add I'm a little bit surprised that it's running with Any chance this is a repo that you could link to or give me access to? I'd love to see it happen if possible. |
It could be that the build-on-demand isn't removing I'm as sure as I can be that it's typescript-related somehow. The use of proxies and loaders does add some overhead above what node 16 did, but it should definitely not be more than 5-10% slower on average. |
Ok, here's what I'm seeing: there are essentially a few different sources of slowness happening here:
|
It's good for correctness, but it's way too slow, and on large projects, that's just a bit of a foot-gun. Also, the @tapjs/processinfo module needs to be investigated for perf optimization potential, it's adding a bit more overhead than I'd like. Re: #912
Just shipped 18.1, which makes Next task will be digging into |
This solution is surprising, especially with the removal of the typescript plugin, and I'd have never guessed that I shouldn't "allow js files in my double secret compile from js to js" 😄 Tap v18 assumes I'll need to compile my tests, and the presence of flags like Thanks for looking into this! |
So, in the immediate term, to keep your builds the same but make tests fast, you can create a file at {
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": false
}
} And add this to package.json: {
"tap": {
"tsconfig": "./tsconfig.test.json"
}
} I'll have a think on a better way to make this a bit smoother. In general, you almost never want allowJs when using ts-node (at best, it screws up sourcemaps in a weird way, at worst, it's punishingly slow), and usually want it when using tsc directly, for type safety, so it's kind of a footgun. Better perf tracking in the debug output is definitely on my radar, just haven't gotten to it yet. |
I found out that you have to explicitly disable typecheck, even that v18.1 changelog says it's disabled by default |
🤦 Yes, you are correct. I'd been disabling it in a bunch of my modules explicitly, so didn't notice. There were even tests verifying that it still is unset by default lol. Will be fixed in 18.3.1 patch release. |
It stopped defaulting true, but all it did was just not set TS_NODE_TRANSPILE_ONLY to anything, which leaves ts-node defaulting in typecheck mode. Re: #912
Update: this will be addressed more satisfactorily by the introduction of a new typescript importer that can do type checking and uses a persistent daemon process to avoid repeatedly creating the same Program object (which is the part that takes from 400ms anywhere up to multiple seconds, and is done for every test file). I have the general approach sketched out and validated at this point, but it's still not quite ready for prime time. |
For us, running the tests with TAP 394 TAP: done processing [] null false
TAP 394 TAP: idle after #process false null
TAP 394 TAP: OIE(TAP) shifted into queue []
TAP 394 TAP: PROCESSING(TAP) 0
TAP 394 TAP: > end of queue
TAP 394 TAP: done processing [] null false
TAP 394 TAP: idle after #process false null
TAP 394 Purchases in a new week add points again: > EOF Purchases in a new week add points again
TAP 394 Purchases in a new week add points again: eof end parser Purchases in a new week add points
again
TAP 394 Purchases in a new week add points again: > end of queue
TAP 394 Purchases in a new week add points again: done processing [] null Symbol(implicit end)
TAP 394 Purchases in a new week add points again: idle after #process Symbol(implicit end) null
TAP 394 Purchases in a new week add points again: awaited end in process Symbol(implicit end)
TAP 394 Purchases in a new week add points again: END Purchases in a new week add points again
implicit=true
TAP 394 Purchases in a new week add points again: already ended, ignore implicit end After that, it takes tap another 10 seconds to do seemingly nothing, all tests have run, and then we get the following debug output: TAP 394 TAP: END TAP implicit=true
TAP 394 TAP: push obe
TAP 394 TAP: END(TAP) implicit plan 13
TAP 394 TAP: END TAP implicit=true
TAP 394 TAP: #end: queue not empty, or occupied false null [ '1..13\n' ]
TAP 394 TAP: PROCESSING(TAP) 1
TAP 394 TAP: > STRING
TAP 394 TAP: LINE "1..13\n" [ 'TAP', '' ]
TAP 394 TAP: > end of queue
TAP 394 TAP: done processing [] null Symbol(implicit end)
TAP 394 TAP: idle after #process Symbol(implicit end) null
TAP 394 TAP: awaited end in process Symbol(implicit end)
TAP 394 TAP: END TAP implicit=true
TAP 394 TAP: set ended=true
TAP 394 TAP: PROCESSING(TAP) 1
TAP 394 TAP: > EOF TAP
TAP 394 TAP: call onEOF TAP
TAP 394 TAP: onEOF is promise
TAP 394 TAP: < already processing
TAP 394 TAP: done processing [
TAP 394 TAP: Waiter {
TAP 394 TAP: cb: [Function (anonymous)],
TAP 394 TAP: ready: false,
TAP 394 TAP: value: undefined,
TAP 394 TAP: resolved: false,
TAP 394 TAP: rejected: false,
TAP 394 TAP: done: false,
TAP 394 TAP: finishing: false,
TAP 394 TAP: expectReject: false,
TAP 394 TAP: promise: Promise {
TAP 394 TAP: <pending>,
TAP 394 TAP: [Symbol(async_id_symbol)]: 4318,
TAP 394 TAP: [Symbol(trigger_async_id_symbol)]: 0
TAP 394 TAP: },
TAP 394 TAP: resolve: [Function (anonymous)]
TAP 394 TAP: }
TAP 394 TAP: ] null Symbol(implicit end)
TAP 339 test/routes/earned-steps.ts: LINE "1..13\n" [ 'test/routes/earned-steps.ts', ' ' ]
TAP 339 TAP: LINE " 1..13\n" [ 'TAP', '' ]
2> test/routes/earned-steps.ts
TAP 394 TAP: PROCESSING(TAP) 1
TAP 394 TAP: done processing [] Waiter {
TAP 394 TAP: cb: [Function (anonymous)],
TAP 394 TAP: ready: true,
TAP 394 TAP: value: undefined,
TAP 394 TAP: resolved: false,
TAP 394 TAP: rejected: false,
TAP 394 TAP: done: false,
TAP 394 TAP: finishing: false,
TAP 394 TAP: expectReject: false,
TAP 394 TAP: promise: Promise {
TAP 394 TAP: <pending>,
TAP 394 TAP: [Symbol(async_id_symbol)]: 4318,
TAP 394 TAP: [Symbol(trigger_async_id_symbol)]: 0
TAP 394 TAP: },
TAP 394 TAP: resolve: [Function (anonymous)]
TAP 394 TAP: } Symbol(implicit end)
TAP 394 TAP: set ended=true
TAP 394 TAP: PROCESSING(TAP) 1
TAP 394 TAP: done processing [ Symbol(EOF) ] Waiter {
TAP 394 TAP: cb: [Function (anonymous)],
TAP 394 TAP: ready: true,
TAP 394 TAP: value: undefined,
TAP 394 TAP: resolved: false,
TAP 394 TAP: rejected: false,
TAP 394 TAP: done: false,
TAP 394 TAP: finishing: false,
TAP 394 TAP: expectReject: false,
TAP 394 TAP: promise: Promise {
TAP 394 TAP: <pending>,
TAP 394 TAP: [Symbol(async_id_symbol)]: 4318,
TAP 394 TAP: [Symbol(trigger_async_id_symbol)]: 0
TAP 394 TAP: },
TAP 394 TAP: resolve: [Function (anonymous)]
TAP 394 TAP: } Symbol(implicit end)
TAP 394 TAP: PROCESSING(TAP) 2
TAP 394 TAP: done processing [ Symbol(EOF), Symbol(EOF) ] Waiter {
TAP 394 TAP: cb: [Function (anonymous)],
TAP 394 TAP: ready: true,
TAP 394 TAP: value: undefined,
TAP 394 TAP: resolved: true,
TAP 394 TAP: rejected: false,
TAP 394 TAP: done: true,
TAP 394 TAP: finishing: true,
TAP 394 TAP: expectReject: false,
TAP 394 TAP: promise: Promise {
TAP 394 TAP: <pending>,
TAP 394 TAP: [Symbol(async_id_symbol)]: 4318,
TAP 394 TAP: [Symbol(trigger_async_id_symbol)]: 0
TAP 394 TAP: },
TAP 394 TAP: resolve: [Function (anonymous)]
TAP 394 TAP: } Symbol(implicit end)
TAP 394 TAP: PROCESSING(TAP) 2
TAP 394 TAP: > EOF TAP
TAP 394 TAP: eof end parser TAP
TAP 394 TAP: ONCOMPLETE "TAP"
{"ok":true,"count":13,"pass":13,"fail":0,"bailout":false,"todo":0,"skip":0,"failures":[],"time":null,
"plan":{"start":1,"end":13,"skipAll":false,"skipReason":"","comment":""},"skips":[],"todos":[]}
TAP 394 TAP: > EOF TAP
TAP 394 TAP: eof end parser TAP
TAP 394 TAP: > end of queue
TAP 394 TAP: done processing [] null Symbol(implicit end)
TAP 394 TAP: idle after #process Symbol(implicit end) null
TAP 394 TAP: awaited end in process Symbol(implicit end)
TAP 394 TAP: END TAP implicit=true
TAP 394 TAP: already ended, ignore implicit end
TAP 339 test/routes/earned-steps.ts: ONCOMPLETE "test/routes/earned-steps.ts"
{"ok":true,"count":13,"pass":13,"fail":0,"bailout":false,"todo":0,"skip":0,"failures":[],"time":null,
"plan":{"start":1,"end":13,"skipAll":false,"skipReason":"","comment":""},"skips":[],"todos":[]}
PASS test/routes/earned-steps.ts 44 OK 12s
TAP 339 test/routes/earned-steps.ts: SPAWN close 0 null
TAP 339 TAP: onIndentedEnd test/routes/earned-steps.ts
TAP 339 TAP: #onIndentedEnd TAP(test/routes/earned-steps.ts)
TAP 339 TAP: OIE(TAP) >shift into queue []
TAP 339 TAP: LINE "ok 1 - test/routes/earned-steps.ts # time=12212.635ms\n" [ 'TAP', '' ]
TAP 339 TAP: LINE "\n" [ 'TAP', '' ]
TAP 339 TAP: PROCESSING(TAP) 0
TAP 339 TAP: > end of queue
TAP 339 TAP: done processing [] null false
TAP 339 TAP: idle after #process false null
TAP 339 TAP: END TAP implicit=true
TAP 339 TAP: push obe
TAP 339 TAP: set ended=true
TAP 339 TAP: PROCESSING(TAP) 1
TAP 339 TAP: > EOF TAP
TAP 339 TAP: call onEOF TAP
TAP 339 TAP: onEOF is promise
TAP 339 TAP: < already processing
TAP 339 TAP: done processing [
TAP 339 TAP: Waiter {
TAP 339 TAP: cb: [Function (anonymous)],
TAP 339 TAP: ready: false,
TAP 339 TAP: value: undefined,
TAP 339 TAP: resolved: false,
TAP 339 TAP: rejected: false,
TAP 339 TAP: done: false,
TAP 339 TAP: finishing: false,
TAP 339 TAP: expectReject: false,
TAP 339 TAP: promise: Promise {
TAP 339 TAP: <pending>,
TAP 339 TAP: [Symbol(async_id_symbol)]: 2593,
TAP 339 TAP: [Symbol(trigger_async_id_symbol)]: 2138
TAP 339 TAP: },
TAP 339 TAP: resolve: [Function (anonymous)]
TAP 339 TAP: }
TAP 339 TAP: ] null false
TAP 339 TAP: PROCESSING(TAP) 1
TAP 339 TAP: done processing [] Waiter {
TAP 339 TAP: cb: [Function (anonymous)],
TAP 339 TAP: ready: true,
TAP 339 TAP: value: undefined,
TAP 339 TAP: resolved: false,
TAP 339 TAP: rejected: false,
TAP 339 TAP: done: false,
TAP 339 TAP: finishing: false,
TAP 339 TAP: expectReject: false,
TAP 339 TAP: promise: Promise {
TAP 339 TAP: <pending>,
TAP 339 TAP: [Symbol(async_id_symbol)]: 2593,
TAP 339 TAP: [Symbol(trigger_async_id_symbol)]: 2138
TAP 339 TAP: },
TAP 339 TAP: resolve: [Function (anonymous)]
TAP 339 TAP: } false
TAP 339 TAP: PROCESSING(TAP) 0
TAP 339 TAP: done processing [] Waiter {
TAP 339 TAP: cb: [Function (anonymous)],
TAP 339 TAP: ready: true,
TAP 339 TAP: value: undefined,
TAP 339 TAP: resolved: false,
TAP 339 TAP: rejected: false,
TAP 339 TAP: done: false,
TAP 339 TAP: finishing: false,
TAP 339 TAP: expectReject: false,
TAP 339 TAP: promise: Promise {
TAP 339 TAP: <pending>,
TAP 339 TAP: [Symbol(async_id_symbol)]: 2593,
TAP 339 TAP: [Symbol(trigger_async_id_symbol)]: 2138
TAP 339 TAP: },
TAP 339 TAP: resolve: [Function (anonymous)]
TAP 339 TAP: } false
TAP 339 TAP: OIE(TAP) shifted into queue []
TAP 339 TAP: PROCESSING(TAP) 0
TAP 339 TAP: done processing [] Waiter {
TAP 339 TAP: cb: [Function (anonymous)],
TAP 339 TAP: ready: true,
TAP 339 TAP: value: undefined,
TAP 339 TAP: resolved: false,
TAP 339 TAP: rejected: false,
TAP 339 TAP: done: false,
TAP 339 TAP: finishing: false,
TAP 339 TAP: expectReject: false,
TAP 339 TAP: promise: Promise {
TAP 339 TAP: <pending>,
TAP 339 TAP: [Symbol(async_id_symbol)]: 2593,
TAP 339 TAP: [Symbol(trigger_async_id_symbol)]: 2138
TAP 339 TAP: },
TAP 339 TAP: resolve: [Function (anonymous)]
TAP 339 TAP: } false We are on the latest version of tap (so typecheck is disabled by default) and we don't have the |
Hey, I just wanted to say congrats on the latest release. I've been using tap a long time! Our project has struggled with closing the process completely for a long time, and this latest release has helped me track down issues more successfully with the debug flag. It would be awesome if any SIGTERM/KILLS/WHATEVER could report on all open handles. If that is possible, let me know and I'll open a ticket!
On to the point: I'm dealing with a large, old set of tests: 206 suites, 8790 asserts. They include integration style tests with a running server, postgres, etc. Tap 16 could complete this suite in 11m.
The latest Tap release has changed that. On both node 16 and 18 it's taking much longer, completing in ~1h locally. This is consistent across multiple dev machines. Slower machines (CI/CD runners) can no longer complete the tests, timing out at ~6h.
For fun I've tried the following test file, running with
tap
as well asnode
directly. Unfortunately, reproducibility suffers across users here, but version 18 seems slower across the board. Additionally, the time discrepancy betweentap
andnode
appears to have widened.For some of our users, running via
tap
takes seconds now, while running via node finishes in the low hundreds of milliseconds. For others the difference isn't as noticeable. Granted, this feels like a bad benchmark.One more thing I noticed: If i put that file into the aforementioned large project directory and run
tap ./test.js
it takes almost 10s to complete. For large portions of this time, a timer ticks without any output. In a new project directory the test runs as expected on version 16 and slower on version 18.Please let me know if i can provide any additional details. I hope I haven't missed something obvious in the config.
Thank you!
The text was updated successfully, but these errors were encountered: