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

Add module to operate k6 project #1

Merged
merged 7 commits into from
May 14, 2024
Merged

Conversation

wingyplus
Copy link
Contributor

@wingyplus wingyplus commented May 9, 2024

This PR introduce 2 module commands:

  1. k6/new - a module command to generate a runner, an entrypoint file uses for running k6.
  2. k6/build - building a gleam code into js module with esbuild. Previously is src/build.gleam.

The reason I move the commands to under src/k6 because I want a namespace when running with gleam run -m.

Usage

  1. Create project with gleam new
  2. Inside project, add k6_gleam to a dependencies list.
  3. Run gleam run -m k6/new to generate necessary file.
  4. Run gleam run -m k6/build to build javascript.
  5. Run k6 run --vu <vu> --duration <duration> runner.mjs. You will get a ton of console.log. 🎉

I also add an example to this PR. You can use gleam run -m k6/build and k6 to run the project.

@wingyplus wingyplus marked this pull request as draft May 9, 2024 10:39
Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
wingyplus added 3 commits May 9, 2024 22:14
Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
@wingyplus wingyplus marked this pull request as ready for review May 9, 2024 15:30
esgleam.new("./dist/static")
|> esgleam.entry(project_name <> ".gleam")
|> esgleam.target("es2020")
|> esgleam.raw("--external:k6/http")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to make it configurable but it's ok for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh the external flag can use * to match wildcard. Will find the time to try. 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wingyplus
Copy link
Contributor Author

@tsloughter The PR is ready now. Let me know if you have any feedback. :)

@tsloughter
Copy link
Owner

Should src/runner.js be removed? same with src/k6_gleam_example.gleam?

@tsloughter
Copy link
Owner

Also, maybe can be done later but I think new should take a name as an optional argument. Projects are going to have more than 1 test in them, so only being able to create a single runner is restrictive.

Similarly runner.mjs should probably be like <name>_runner.mjs.

Also, why .mjs? I'm fine with it, since I don't know what it means, hehe, aside from what a quick google told me, but curious.

@tsloughter
Copy link
Owner

Looks like another option to consider instead of using runner.mjs is to add a scenario and an exec in the template to set the function to call to something besides default:

https://grafana.com/docs/k6/latest/using-k6/scenarios/#options

@tsloughter
Copy link
Owner

Oh, but we probably can't even export export const options

@wingyplus
Copy link
Contributor Author

consider instead of using

Oh yes, it should remove.

@wingyplus
Copy link
Contributor Author

Also, maybe can be done later but I think new should take a name as an optional argument. Projects are going to have more than 1 test in them, so only being able to create a single runner is restrictive.

Similarly runner.mjs should probably be like <name>_runner.mjs.

I thought about generating gleam code as well. When we call gleam run -m k6/new <name>, it'll generate js runner and gleam source in src with pattern src/<name>.gleam.

@wingyplus
Copy link
Contributor Author

wingyplus commented May 10, 2024

Also, why .mjs? I'm fine with it, since I don't know what it means, hehe, aside from what a quick google told me, but curious.

I remember that some runtime, they threat mjs to use ecmascript module (import / export syntax). But in k6, looks like they threat the same because of babel, I guess.

I also fine with either js or mjs as it can run with k6. 😂

@wingyplus
Copy link
Contributor Author

Oh, but we probably can't even export export const options

We might need to make options as a function in gleam. And then detect object somehow in runner.mjs. 🤔

Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
@@ -1,6 +1,3 @@
// TODO: move it to k6/http.gleam.
@external(javascript, "./k6_wrapper.js", "get")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I leave it for now. Will be revisit after working on binding. :)

@tsloughter
Copy link
Owner

So I don't think we'll be able to do the export const config = .. because it has to be json and, I could be wrong, no way to write some data structure in Gleam that gets compiled to json for this. And you can't do pub const config = some_fun() in Gleam as a global.

But maybe there are like anonymous structs I don't know about in Gleam that would convert from type Config (noConnectionReuse: boolean) to the json {"noConnectionReuse": ...}?

@tsloughter
Copy link
Owner

But, what I did to test to make sure this at least worked to call a non-default function was create a config.json with it:

...
   "scenarios": {
     "shared_iter_scenario": {
       "exec": "main",
... 
$ k6 run --config options.json dist/static/k6_gleam_example.js 

Just annoying that you have to define a scenario (though maybe any tests beyond playing around with k6 would warrant this anyway).

@wingyplus
Copy link
Contributor Author

So I don't think we'll be able to do the export const config = .. because it has to be json and, I could be wrong, no way to write some data structure in Gleam that gets compiled to json for this. And you can't do pub const config = some_fun() in Gleam as a global.

But maybe there are like anonymous structs I don't know about in Gleam that would convert from type Config (noConnectionReuse: boolean) to the json {"noConnectionReuse": ...}?

We might not need to convert to json but a do something in the runner.mjs to convert an option in gleam into js object.

Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
@wingyplus
Copy link
Contributor Author

Also, maybe can be done later but I think new should take a name as an optional argument. Projects are going to have more than 1 test in them, so only being able to create a single runner is restrictive.
Similarly runner.mjs should probably be like <name>_runner.mjs.

I thought about generating gleam code as well. When we call gleam run -m k6/new <name>, it'll generate js runner and gleam source in src with pattern src/<name>.gleam.

I modify k6/new and k6/build to accept name as a first argument. The k6/new will generate runner and gleam source for the given name. The k6/build will build a script based on the given name also. The example is updated.

Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
@wingyplus
Copy link
Contributor Author

wingyplus commented May 11, 2024

So I don't think we'll be able to do the export const config = .. because it has to be json and, I could be wrong, no way to write some data structure in Gleam that gets compiled to json for this. And you can't do pub const config = some_fun() in Gleam as a global.

But maybe there are like anonymous structs I don't know about in Gleam that would convert from type Config (noConnectionReuse: boolean) to the json {"noConnectionReuse": ...}?

@tsloughter I just make options configuration works in Gleam. The idea is having a Gleam record that reflect to k6 configuration and in javascript runner file, calling the options function in Gleam runner code and put the options to javascript. Here is the diff

diff --git a/examples/basic_k6/hello_runner.js b/examples/basic_k6/hello_runner.js
index ffe6939..b5701b1 100644
--- a/examples/basic_k6/hello_runner.js
+++ b/examples/basic_k6/hello_runner.js
@@ -1,4 +1,14 @@
-import { main } from './dist/static/hello_runner.js'
+import * as hello_runner from "./dist/static/hello_runner.js";
 
-export default main
-  
\ No newline at end of file
+if (!hello_runner.main) {
+  throw new Error("A main function is required");
+}
+
+let runner_options = hello_runner.options();
+export const options = {
+  duration: runner_options.duration[0],
+  vus: runner_options.vus[0],
+  vusMax: runner_options.vus_max[0],
+};
+
+export default hello_runner.main;
diff --git a/examples/basic_k6/src/hello_runner.gleam b/examples/basic_k6/src/hello_runner.gleam
index ab628e2..ea5f64b 100644
--- a/examples/basic_k6/src/hello_runner.gleam
+++ b/examples/basic_k6/src/hello_runner.gleam
@@ -1,4 +1,10 @@
+import gleam/option.{Some}
 import k6/http
+import k6/options.{type Options, Options}
+
+pub fn options() -> Options {
+  Options(duration: Some("10s"), vus: Some(10))
+}
 
 pub fn main() {
   http.get("https://test.k6.io")
diff --git a/src/k6/options.gleam b/src/k6/options.gleam
new file mode 100644
index 0000000..65eafb1
--- /dev/null
+++ b/src/k6/options.gleam
@@ -0,0 +1,5 @@
+import gleam/option.{type Option}
+
+pub type Options {
+  Options(duration: Option(String), vus: Option(Int))
+}

What we need to do next are:

  1. Detecting the options function, if it's present, export the options.
  2. Complete the Options type configuration. We might use types/k6 as a reference.
  3. Port it to runner.js template.

What do you think?

@@ -0,0 +1,5 @@
// Returns runner name including js and gleam file name.
pub fn build_runner_name(name: String) -> #(String, String, String) {
let name = name <> "_runner"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name can be change on both js and gleam side.

@tsloughter
Copy link
Owner

Part of why I wanted an export const options was to get rid of the need for the runner since you can just put exec: 'somefunction' in it.

But this is great if the user can type check options even if they still need a runner.

Another option, that doesn't get type checked but removes the need for a runner, is require the use of a file, like options.json.

@wingyplus
Copy link
Contributor Author

wingyplus commented May 11, 2024

Part of why I wanted an export const options was to get rid of the need for the runner since you can just put exec: 'somefunction' in it.

But this is great if the user can type check options even if they still need a runner.

Another option, that doesn't get type checked but removes the need for a runner, is require the use of a file, like options.json.

I still doubt on how to set it in the library. But yeah, it is interesting. :)

@wingyplus
Copy link
Contributor Author

Or generating the option file when calling k6/new??

@wingyplus
Copy link
Contributor Author

wingyplus commented May 13, 2024

@tsloughter I found the alternative way, embeded gleam into k6 extension and using xk6 to build this extension into k6 binary. I found that the Grafana team build the extension for TypeScript (https://github.com/grafana/xk6-ts), so I apply the same technique to the Gleam project.

The technique behind the scene is use the extension to hook k6 cli arguments, build the gleam project, and bundling it with k6pack (it's wrap around esbuild api). I also add a little javascript before sending to k6pack to export the gleam main function. So the runner script is now eliminate (k6/build also). 🎉

Screenshot 2024-05-13 232517

See #3 for the implementation details. It's a quick hack, it might have a bug somewhere. 😂

@tsloughter
Copy link
Owner

Very nice! Looks like it'll take me a bit more time to review this :)

@tsloughter tsloughter merged commit 953759a into tsloughter:main May 14, 2024
1 check passed
@wingyplus wingyplus deleted the k6-command branch May 14, 2024 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants