grafana / k6

A modern load testing tool, using Go and JavaScript - https://k6.io
GNU Affero General Public License v3.0
24.93k stars 1.23k forks source link

Expose golang time module for microsecond and nanosecond granularity in custom metrics #2362

Open jdheyburn opened 2 years ago

jdheyburn commented 2 years ago

Feature Description

I am using a custom Trend metric to record latency of non-HTTP commands.

I am able to use JSs Date.now() to retrieve millisecond granularity time spans. However for some of my commands I am benchmarking I need microsecond granularity, since many of the commands complete < 1 ms.

NodeJS has process.hrtime() and a browser would have window.performance available to them, but k6 does not have this.

Since I am already using a custom k6 binary building with extensions, I have done the following to expose the Golang methods to JS:

func (c *Client) Getnano() int64 {
    return time.Now().UnixNano()
}

func (c *Client) Getmicro() int64 {
    return time.Now().UnixMicro()
}

Then in my script.js I can call them back using:

const start = client.getmicro();
// do work
const latency = client.getmicro() - start;

Suggested Solution (optional)

If k6 has some utils section which exposes this to Javascript, then ideally it could go there or somewhere suitable so that something like below could be used:

import { time } from 'k6/x/utils';

const unixMicro = time.unixMicro();
const unixNano = time.unixNano();

Already existing or connected issues / PRs (optional)

No response

na-- commented 2 years ago

Thanks for opening this issue, it seems like a very valid use case and something we should expose! The only question in my mind is how exactly to expose it... I am not sure it deserves its own new module, unless we figure out other things we can combine it with. utils is also quite vague, I am not sure we should go in that direction.

Maybe we could add it as a property to k6/execution.instance? We already have currentTestRunDuration there, so we can add currentTimestamp or something like that? :thinking:

Another concern is how to implement this. I see node's process.hrtime uses BigInt, but unfortunately that's something that goja (the JS runtime k6 uses) doesn't support that yet :disappointed: So yeah, there are some things to figure out before this can be implemented in the k6 core...

na-- commented 2 years ago

Actually, @jdheyburn, for your use case, I think you should be able to use k6/execution.instance.currentTestRunDuration even now. It measures time in milliseconds, but it's a float value that doesn't actually truncate the nanoseconds, so it technically has full precision: https://github.com/grafana/k6/blob/0dda328d9a7f563607dae4fe89ff8c5138f8dc09/js/modules/k6/execution/execution.go#L147-L149

Try this script, for example:

import exec from 'k6/execution';
import { sleep } from 'k6';

export const options = {
    vus: 1,
    iterations: 10,
}

export default function () {
    console.log(`[t=${exec.instance.currentTestRunDuration.toFixed(5)}ms] VU{${exec.vu.idInTest}} started iteration ${exec.scenario.iterationInTest}`);
    sleep(Math.random());
}

You will see that this gives you sub-millisecond precision.

mstoykov commented 2 months ago

This likely go with the a lot more standard performance.now()