-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Summary callback changes #1768
Summary callback changes #1768
Conversation
Here is a more complete example that also demonstrates the fixed summary columns even in the old backwards-compatible |
I'm working on creating an end-of-test summary for functional/integration tests. The example posted above is hitting a bug/mistake in the
I can fix/work around it, so it's not a problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! 👏
The code LGTM, besides the minor comments. I'll take another look next year ;)
Just some notes:
- Strong 👍 for splitting this into separate commits/PRs. It would've been easier to review like that as well. 😅
- I like the
GetResolversForTrendColumns()
refactor, despite of thoseSink
pain points. 👍
I have 2 concept/UX/big issues:
import http from 'k6/http';
import { JUnit, TextSummary } from 'https://jslib.k6.io/k6-summary/1.0.0/index.js' // or something like that
// ...
export function handleSummary(data, api) { // Worst name ever I know :)
// save everything to files and send it to a server when the test finishes or gets aborted
http.post('https://example.com/testResults', JSON.stringify(data));
api.writeToStdout(TextSummary(data));
// or TextSummary(api.stdout, data);
var f = api.openFile("./results/junit.xml");
f.write(JUnit(data));
// or JUnit(f, data);
} This will (without generators that are currently not supported, last I checked) let us write big responses without being memory hungry. I haven't looked into it, but I expect there is a somewhat common "interface" for this in the JS world that can be used.
I think it is worth trying to figure out if we can actually have a way that this is prevented. My first thought (which works great for the summary-trend-stats) is to completely change the API to something like (all names of stuff are bad and only for illustrative purposes): import summary from "k6/summary";
summary.register(function(data) {<same as previously>}, ["p(23)"]); Where the first argument is the function as before and the second is something that says which summaryTrendStats it wants. edit; this second syntax also makes it easy to have multiple handleSummary functions making the different types instead of having a single one which makes all. |
@mstoykov, I think we've previously discussed most of these ideas in the private issue, but to rehash things:
If you recall, having some sort of a file writing API was port of my original proposal for this functionality. I later retracted this, because that API would have been a deep and bikesheddy rabbit hole which didn't (and still doesn't) seem worth getting into. I didn't want to have to figure out a nice JS API for file opening/closing, permissions, appending, binary data, etc. This feature is big enough without having to deal with that from scratch as well... The currently returned Besides, any file writing API will probably be better off as something generic, and better of being polished as an xk6 extension for a while, not directly in core. cc @sniku @imiric what are your thoughts here? I am not opposed to a proper API, but that would mean delaying the feature again and not getting it in v0.30 for sure.
True, that's a risk, but you can say the same thing about I don't think the UX of passing the required percentiles ( That said, I like the approach of relying on an imported module and calling a method on it ( import events from "k6/events";
events.registerHandler('endOfTestSummary', someHandler); // or maybe addEventListener :D
events.registerHandler('midTestSummary', someOtherHandler);
events.registerHandler('vuInit', perVUInit);
events.registerHandler('abortedByThreshold', whatever);
// ... But again, this seems like it can grow in complexity and needs careful design because of the k6 multi-JS-runtime arhitecture. It also seems like something that can be added in the future, in a backwards compatible way. So, for now, starting with an exported |
I like the flexibility of @mstoykov's proposal, but also feel that it would cause a delay of v0.30.0 to implement it. Since we can add a proper API in a backwards compatible way in the future (new users simply won't have to return an object), my vote is to merge this as is and discuss and design the API proposal in a subsequent issue/PR. I also like the event-based registration idea for summary handlers, but that also feels out of scope for this MVP. |
Codecov Report
@@ Coverage Diff @@
## master #1768 +/- ##
=======================================
Coverage 71.49% 71.50%
=======================================
Files 180 181 +1
Lines 13851 13939 +88
=======================================
+ Hits 9903 9967 +64
- Misses 3325 3340 +15
- Partials 623 632 +9
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
I tried (successfully I would say) port https://github.com/Mattihew/k6-to-junit to this new change here |
if err := s.SummarizeMetricsJSON(f, data); err != nil { | ||
logger.WithError(err).Error("failed to make summary export file") | ||
} | ||
logger.WithError(err).Error("failed to handle the end-of-test summary") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that in a sequential release we will fallback to a default representation or the dumping the lib.Summary
as JSON to the disk?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what you mean by "a sequential release", but we'll get an error here only if something goes majorly wrong... Both initRunner.HandleSummary()
and handleSummaryResult()
try to continue working even if there are errors. handleSummaryResult()
tries to save all files, even if some of them fail (because of wrong names, lack of permissions, etc.). And initRunner.HandleSummary()
has internal try / catch
in the JS wrapper code: https://github.com/loadimpact/k6/blob/0838813db6edead0f6faa3cdaadf3b5eb070a1ab/js/summary.go#L97-L104
Though I should add tests for both of these things...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I missed this code at the time ...
This is probably my biggest issue reviewing the PR, the code is all over the place, although I don't know how to fix that if it's at all possible given what the code tries to do, so I don't have some concrete feedback. I hope that when we rewrite even more of the whole metric and threshold (which specifically are also over the place) this might get more streamlined
switch v := resolver(sink).(type) { | ||
case float64: | ||
v := resolver(sink) | ||
if tc != "count" { // sigh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick ... but why are you checking for the reverse here instead of for the equal one? ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤷♂️ probably just because that was the case order in the old switch
, but yeah, doesn't makes sense, so I'll change it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the JS variant of this though, no need to further mess with the Go code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
I had some more comments, but even the runPart
thing seems likely to be more involved with little actual benefit, so I guess it can be even be left for a later PR or not done at all.
js/runner.go
Outdated
fn := exported.Get(consts.HandleSummaryFn) | ||
if _, ok := goja.AssertFunction(fn); ok { | ||
handleSummaryFn = fn | ||
} else if fn != nil && !goja.IsUndefined(fn) && !goja.IsNull(fn) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked and Get
returns nil
if it doesn't exist. Did you intend for exports.handleSummary = undefined;
(or null
) to be a valid thing that won't raise an error or were you over zealous in the checks?
I am not particularly certain it matters either way, to be honest. If someone has defined handleSummary
it is probably better to use delete exports.handleSummary
, on the other hand, I don't think we will be hitting it. So the question is mostly questing what was the idea behind it, not changing it one way or another
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
were you over zealous in the checks?
Likely that, and it makes sense to leave only the nil
check 👍
} | ||
rawResult, _, _, err := vu.runFn(ctx, false, handleSummaryWrapper, wrapperArgs...) | ||
|
||
// TODO: refactor the whole JS runner to avoid copy-pasting these complicated bits... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am pretty sure you can just use runPart
for most of the above.
You will need to keep the check for whether exports.handleSummary
is a function, but everything else seems to be done by runPart
either way ... I think ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I guess you will need to move https://github.com/loadimpact/k6/blob/0838813db6edead0f6faa3cdaadf3b5eb070a1ab/js/summary.go#L95-L118 to golang code after the runPart
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I initially tried using runPart
, but it was an even bigger mess. We can figure out some way to refactor the whole js.Runner
internally in the future to reduce the boilerplate, but for now I don't want to mess with it more if I don't need to... 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nice work!
This is a WIP attempt at fixing a bunch of issues (fixes #1316, fixes #1611, closes #817, closes #647, closes #351) and making possible the implementation of a bunch of other ones (#1315, #1675, #1319)...
The way we do that is by supporting a new
handleSummary()
exported function that allows users to customize the test summary (or summaries) however the want. The final goal is something like this:With the current state of the PR,
handleSummary()
is supported and its results are saved to stderr/stdout/files. I've also heavily refactored the--summary-export
(implemented it through the new system in a backwards compatible way) and fixed a bunch of--sumary-trend-stats
bugs and group/check order. But we don't have a JS equivalent of the old summary (TextSummary()
in the example above) yet orJUnit()
. For now, I've exposed the old Go code that generated the test summary as a second parameter tohandleSummary()
, but I'll remove that in the final version. So, to test this, you might use something like this:The following things remain to be done:
Breaking changes and other bugfixes
--no-summary
now disables even--summary-export
value
forRate
metrics in the old--summary-export
JSON file was was always0
, regardless of thepass/(pass+fail)
ratio