Skip to content
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

Huge memory usage with jslib URL #1851

Closed
zerda opened this issue Feb 11, 2021 · 4 comments
Closed

Huge memory usage with jslib URL #1851

zerda opened this issue Feb 11, 2021 · 4 comments
Labels

Comments

@zerda
Copy link

zerda commented Feb 11, 2021

Environment

  • k6 version: 0.30.0
  • OS and version: macOS 10.14.6
  • Docker version and image, if applicable: N/A

Expected Behavior

Stable and low memory usage with URL object.

Actual Behavior

With a simple script, memory usage is about 5.8GiB by 1000uv and 10s, and 7.5GiB by 1000uv and 30s.

It's about 1/2 memory when script is transformed to es5, and running with --compatibility-mode=base option.

Steps to Reproduce the Problem

// src/script.js
import { URL } from 'https://jslib.k6.io/url/1.0.0/index.js';

export default function() {
  var url = new URL('https://httpbin.test.k6.io/');
}
$ gtime -v k6 run -u 1000 -d 10s src/script.js

  execution: local
     script: src/script.js
     output: -

  scenarios: (100.00%) 1 scenario, 1000 max VUs, 40s max duration (incl. graceful stop):
           * default: 1000 looping VUs for 10s (gracefulStop: 30s)

running (10.8s), 0000/1000 VUs, 81958 complete and 0 interrupted iterations
default ✓ [======================================] 1000 VUs  10s

     data_received........: 0 B   0 B/s
     data_sent............: 0 B   0 B/s
     iteration_duration...: avg=34.35ms min=226.93µs med=601.38µs max=4.09s p(90)=1.37ms p(95)=2.83ms
     iterations...........: 81958 7609.974993/s
     vus..................: 1000  min=1000 max=1000
     vus_max..............: 1000  min=1000 max=1000

	Command being timed: "k6 run -u 1000 -d 10s src/script.js"
	User time (seconds): 120.26
	System time (seconds): 5.40
	Percent of CPU this job got: 537%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:23.37
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 5818768
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 3
	Minor (reclaiming a frame) page faults: 1547563
	Voluntary context switches: 4790
	Involuntary context switches: 111219
	Swaps: 0
	File system inputs: 0
	File system outputs: 0
	Socket messages sent: 25
	Socket messages received: 33
	Signals delivered: 6579
	Page size (bytes): 4096
	Exit status: 0
$ gtime -v k6 run -u 1000 -d 30s src/script.js

  execution: local
     script: src/script.js
     output: -

  scenarios: (100.00%) 1 scenario, 1000 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 1000 looping VUs for 30s (gracefulStop: 30s)


running (0m30.0s), 0000/1000 VUs, 237004 complete and 0 interrupted iterations
default ✓ [======================================] 1000 VUs  30s

     data_received........: 0 B    0 B/s
     data_sent............: 0 B    0 B/s
     iteration_duration...: avg=54.84ms min=241.35µs med=557.77µs max=5.86s p(90)=1.47ms p(95)=2.74ms
     iterations...........: 237004 7888.811574/s
     vus..................: 1000   min=1000 max=1000
     vus_max..............: 1000   min=1000 max=1000

	Command being timed: "k6 run -u 1000 -d 30s src/script.js"
	User time (seconds): 244.97
	System time (seconds): 9.10
	Percent of CPU this job got: 577%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:44.01
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 7543536
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 4
	Minor (reclaiming a frame) page faults: 2108962
	Voluntary context switches: 9365
	Involuntary context switches: 304976
	Swaps: 0
	File system inputs: 0
	File system outputs: 0
	Socket messages sent: 25
	Socket messages received: 32
	Signals delivered: 14391
	Page size (bytes): 4096
	Exit status: 0
@zerda zerda added the bug label Feb 11, 2021
@mstoykov
Copy link
Contributor

Hi @zerda ,

The problem doesn't seem to be with the jslib URL lib, but with your script and the fact that both golang (the language k6 is written) and JavaScript a garbage-collected languages. This means that if you generate objects they will not be cleaned up by you but by something called a garbage collector.

In your particular script, all you do is generate object so the only thing that is truly exercised is whether k6 can generate objects faster than the garbage collector can keep up ... and the garbage collector will at some point find equilibrium between letting the problem run (at all) and there not being too much garbage.

If you add an http.get with the url.toString() you will notice that at around 4GB the memory usage stops growing (at least for me, I think httpbin can't handle that much load given the times though). The current master, which drops core-js and as such has close to --compatibilit-mode=base memory footprint, goes up to 1.5GB before stabilizing in the same scenario.

and to prove to you that this has nothing to do with the lib try running:

export default function() {
    var s = {
        key: "value"
    }
}

you will notice that the memory usage will go up (to around 6GB over 1 minute for me). And this is basically what the code that you provide does ... just with a bunch more steps and with a much bigger object.

@zerda
Copy link
Author

zerda commented Feb 12, 2021

Thanks for your excellent explanation. I was trying to simplify this issue, but it's seems cause a little confusion.

In real world, I have extracted some parts from URL object to build a request signature. When running with 1000uv 5mins, It's exhausted all 32GiB memory.

// still a poc
import { URL } from 'https://jslib.k6.io/url/1.0.0/index.js';

function buildMessage(method, pathName, queryParams, requestHeaders, requestBody) {
  // emitted
}

export default function() {
  var url = new URL('https://httpbin.test.k6.io/');
  var method = 'GET';
  var headers = {};
  var body = "";
  const message = buildMessage(method, url.pathname, url.searchParams, headers, body);
}

Replace URL class with this incompleted but working method, the memory usage is about 6GiB and stop growing.
So I'm wondering if we can crafted a URL class with low memory usage or something else.

// simpified version of URL & URLSearchParams, for better memory usage.
function getUrlParts(url) {
  if (url === null || url === undefined || url === '') return '';
  const [left, querystring] = url.replace(/https*:\/\//, '').split('?');

  let queryParams = {};
  if (querystring) {
    queryParams = querystring.split('&').reduce(function(params, param) {
      param = param.split('=');
      if (Number.isInteger(parseInt(param[1])) && parseInt(param[1]) == param[1]) {
        params[param[0]] = parseInt(param[1]);
      } else {
        params[param[0]] = decodeURI(param[1]);
      }
    }, {});
  }

  return {
    path: left.substring(left.indexOf('/')),
    queryParams
  };
}

function buildMessage(method, pathName, queryParams, requestHeaders, requestBody) {
  // emitted
}

export default function() {
  var urlParts = getUrlParts('https://httpbin.test.k6.io/');
  var method = 'GET';
  var headers = {};
  var body = "";
  const message = buildMessage(method, urlParts.path, urlParts.queryParams, headers, body);
}

@na--
Copy link
Member

na-- commented Feb 12, 2021

I still don't see an actual HTTP request being made in your second example, so I'm guessing the 32+GB/6GB still has more to do with GC pressure than with realistic memory usage... But to respond to your question, we are probably going to get a native (i.e. Go, not JS) replacement of the URL object eventually. Follow #991 for details, though I can't give guarantees when it's going to land (we accept PRs though).

@na--
Copy link
Member

na-- commented Feb 15, 2021

I made a point of this issue in #991 (comment), so I'll close it, considering we track the adoption of a Go URL implementation in the other issue.

@na-- na-- closed this as completed Feb 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants