-
Notifications
You must be signed in to change notification settings - Fork 44
More reliable response body size and data_received
metric emission
#117
Conversation
46d16b5
to
e6d688f
Compare
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'll let @inancgumus comment on the synchronization part :)
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.
Wow, a lot of work, good job. Minor requests.
2f50e4d
to
fa39ea5
Compare
e6d688f
to
8718977
Compare
8718977
to
405ee49
Compare
After some more research into this, for a reason I'm unable to explain, we just don't receive a We get So it's not a matter of synchronizing how events are processed (thankfully! 😅), but the fact we just don't get the I did a hacky fallback so that we at least don't panic when this happens, but metrics for these requests will be less useful. I'll pick this back up when I'm back from vacation, but in the meantime @inancgumus, if you have some time, please test the Vistaprint script and any others you have on this branch and compare it to |
It was displayed 16 times on the main branch:
I'm no longer observing these error log messages on this branch:
Using Tom's original script, I see the errors three times on the main branch:
However, I see them twice on this branch:
I wrote an improved version of Tom's script that reaches almost to the checkout page:
Here's the modified script.import { sleep, group } from 'k6';
import launcher from 'k6/x/browser';
import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.1.0/index.js';
export const options = {};
let browser, context, page;
const pauseMin = 0;
const pauseMax = 1;
export default function() {
initialize();
navigateToHomepage();
clickBusinessCards();
clickBrowseDesigns();
clickDesign();
clickCustomizeDesign();
clickNext();
continueWithoutBack();
customerReviewed();
addToCart();
continuePastRecommendations();
continueToCart();
goToCheckout();
}
function initialize() {
browser = launcher.launch('chromium', {
args: [],
debug: false,
devtools: false,
env: {},
executablePath: null,
headless: false,
ignoreDefaultArgs: [],
proxy: {},
slowMo: '500ms',
timeout: '30s',
});
context = browser.newContext({
ignoreHTTPSErrors: true,
screen: {width: 1920, height: 1080},
viewport: {width: 1920, height: 1080},
});
page = context.newPage();
}
function navigateToHomepage() {
console.log('Starting navigateToHomepage...');
group("Navigate to Homepage", () => {
page.goto('https://vistaprint.ca', { waitUntil: 'networkidle' });
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function clickBusinessCards() {
console.log('Starting clickBusinessCards...');
group("Click Business Cards", () => {
page.click("//span[text()='Business Cards']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function clickBrowseDesigns() {
console.log('Starting clickBrowseDesigns...');
group("Click Browse Designs", () => {
page.waitForSelector("//a[text()='Browse designs']");
page.click("//a[text()='Browse designs']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function clickDesign() {
console.log('Starting clickDesign...');
const designOption = page.waitForSelector(`li.design-tile >> nth=0`);
console.log(designOption.textContent());
group("Select Design Option", () => {
page.click(`li.design-tile >> nth=0`)
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function clickCustomizeDesign() {
console.log('Starting clickCustomizeDesign...');
group("Click Customize Design", () => {
page.click("//a[text()='Customize this design']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function clickNext() {
console.log('Starting clickNext...');
group("Click Next", () => {
page.click("//button[text()='Next']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function continueWithoutBack() {
console.log('Starting continueWithoutBack...');
group("Click Continue Without Back", () => {
page.click("//button[text()='Continue without back']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function customerReviewed() {
console.log('Starting customerReviewed...');
page.click("//input[@value='customerReviewed']");
sleep(5);
group("Click Customer Reviewed", () => {
page.click("//button[text()='Continue']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function addToCart() {
console.log('Starting addToCart...');
group("Click Add to Cart", () => {
page.click("//button[text()='Add to cart']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function continuePastRecommendations() {
console.log('Starting continuePastRecommendations...');
group("Click Continue (Recommendations)", () => {
page.waitForSelector("//a[text()='Continue']");
page.click("//a[text()='Continue']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function continueToCart() {
console.log('Starting continueToCart...');
group("Click Continue to Cart", () => {
page.click("//a[text()='Continue to cart']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
}
function goToCheckout() {
console.log('Starting goToCheckout...');
group("Click Checkout", () => {
page.click("//a[text()='Checkout']");
});
sleep(randomIntBetween(pauseMin, pauseMax));
} |
@inancgumus It's difficult to judge the improvement based on that Vistaprint script since it's a very dynamic site and we use random sleeps in the script you're testing with, so the amount of requests will be different each time. Moreover, I see the This PR isn't meant to completely eliminate the E.g. on
And on this branch rebased on current
That said, it's surprising that you get a ~4x increase of the error on this branch, or that you have that many to begin with. My difference is +/- ~5, and usually not more than 10 in total on either branch. I'll run some more tests and add ignoring of blob URLs. |
I agree 👍 I was measuring this in an earlier version of the main branch, things probably have changed since then 🤷
👍 |
405ee49
to
a9d9868
Compare
@inancgumus This is ready for review. Please test again when compared to Like I mentioned above, comparing the amount of Instead of focusing on the message count, you can confirm that I'm frankly unsure if we should merge this as it's difficult to judge the improvement or whether it's not breaking anything else, but from a metrics standpoint it seems like an improvement. |
@inancgumus WDYT about merging this? |
See ChromeDevTools/devtools-protocol#44 (comment) There is a race condition here where sometimes we receive the LoadingFinished event before we finish processing ResponseReceived, so we don't set the Response on the Request and req.response is nil in onLoadingFinished resulting in a nil pointer panic, but I wasn't able to synchronize this properly.
Ideally we shouldn't need to do this, and with the previous change of emitting response metrics on LoadingFinished, fetchBody() is more reliable.
650cc5f
to
3295ede
Compare
I pushed a rebase on A smaller test that shows the improvement is the When running on current $ XK6_BROWSER_LOG=info XK6_HEADLESS=1 xk6 run examples/fillform.js
[...]
WARN[0001] error fetching response body for 'https://test.k6.io/': No data found for resource with given identifier (-32000) category=cdp elapsed="0 ms" goroutine=75
ERRO[0003] unknown command or event "Page.frameScheduledNavigation"
ERRO[0003] unknown command or event "Page.frameClearedScheduledNavigation"
WARN[0003] error fetching response body for 'https://test.k6.io/my_messages.php': No data found for resource with given identifier (-32000) category=cdp elapsed="0 ms" goroutine=75
ERRO[0007] unknown command or event "Page.frameScheduledNavigation"
ERRO[0007] unknown command or event "Page.frameClearedScheduledNavigation"
WARN[0007] error fetching response body for 'https://test.k6.io/login.php': No data found for resource with given identifier (-32000) category=cdp elapsed="0 ms" goroutine=75 And on this branch (3295ede): $ XK6_BROWSER_LOG=info XK6_HEADLESS=1 xk6 run examples/fillform.js
[...]
ERRO[0003] unknown command or event "Page.frameScheduledNavigation"
ERRO[0003] unknown command or event "Page.frameClearedScheduledNavigation"
ERRO[0007] unknown command or event "Page.frameScheduledNavigation"
ERRO[0007] unknown command or event "Page.frameClearedScheduledNavigation" |
OK let me review this today. |
And to confirm that the // simple.js
import launcher from 'k6/x/browser';
import { sleep } from 'k6';
export default function() {
const browser = launcher.launch('chromium', {
headless: true,
});
const context = browser.newContext();
const page = context.newPage();
page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
sleep(1); // waitFor workaround
page.close();
browser.close();
} Running this on $ jq -c '. | select(.type=="Point" and .metric == "data_received" and .data.tags.url)' out-main.json
{"type":"Point","data":{"time":"2022-02-04T06:44:31.082861+01:00","value":174,"tags":{"from_cache":"false","from_prefetch_cache":"false","from_service_worker":"false","group":"","method":"GET","proto":"http/1.1","scenario":"default","status":"200","url":"https://test.k6.io/"}},"metric":"data_received"}
{"type":"Point","data":{"time":"2022-02-04T06:44:31.287852+01:00","value":5076,"tags":{"from_cache":"false","from_prefetch_cache":"false","from_service_worker":"false","group":"","method":"GET","proto":"http/1.1","scenario":"default","status":"200","url":"https://test.k6.io/static/css/site.css"}},"metric":"data_received"}
{"type":"Point","data":{"time":"2022-02-04T06:44:31.87362+01:00","value":10112,"tags":{"from_cache":"false","from_prefetch_cache":"false","from_service_worker":"false","group":"","method":"GET","proto":"http/1.1","scenario":"default","status":"200","url":"https://test.k6.io/static/js/prisms.js"}},"metric":"data_received"} Only the sizes: $ jq -c '. | select(.type=="Point" and .metric == "data_received" and .data.tags.url) | {url: .data.tags.url, received: .data.value}' out-main.json
{"url":"https://test.k6.io/","received":174}
{"url":"https://test.k6.io/static/css/site.css","received":5076}
{"url":"https://test.k6.io/static/js/prisms.js","received":10112} Summing up: $ jq -c '. | select(.type=="Point" and .metric == "data_received" and .data.tags.url) | .data.value' out-main.json | paste -sd+ | bc
15362 Comparing this to $ jq -c '. | select(.type=="Point" and .metric == "data_received" and .data.tags.url) | {url: .data.tags.url, received: .data.value}' out-117.json
{"url":"https://test.k6.io/","received":11277}
{"url":"https://test.k6.io/static/css/site.css","received":5076}
{"url":"https://test.k6.io/static/js/prisms.js","received":10112} Sum: $ jq -c '. | select(.type=="Point" and .metric == "data_received" and .data.tags.url) | .data.value' out-117.json | paste -sd+ | bc
26465 To confirm which one is correct: $ jq -c '. | select(.type=="Point" and .metric == "data_received" and .data.tags.url) | .data.tags.url' out-main.json | xargs -I{} sh -c 'echo {}; curl -is {} | wc --bytes'
https://test.k6.io/
11277
https://test.k6.io/static/css/site.css
5076
https://test.k6.io/static/js/prisms.js
10112 😄 |
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.
Overall, it's good. Glad that we can finally get rid of these ancient error messages!
But I'd like to separate k6 concerns from the browser concerns as much as we can.
sampleTags := k6stats.IntoSampleTags(&tags)
k6stats.PushIfNotDone(m.ctx, state.Samples, k6stats.ConnectedSamples{
...
})
So, would you like to move this part of the code to another function like:
func (m *NetworkManager) emitResponseMetrics(resp *Response) {
...
emitK6Metrics(...)
...
}
func emitK6Metrics(...) {
sampleTags := k6stats.IntoSampleTags(&tags)
k6stats.PushIfNotDone( ... )
...
k6stats.PushIfNotDone( ... )
...
}
I know what you're going to say: "I thought you were against creating functions that are not used by multiple functions". I'm not against them as long as they are contextual and serve as a chance to make things better. I see it as a first step in refactoring and simplifying the extension code later.
Thanks for the short |
Btw, are we going to have a test for this? |
I'm not sure how to test this TBH without an E2E test like that script. Hhmm though we might be able to get the metric for some I'll look into it. |
It sounds like a plan! |
3295ede
to
3369bfc
Compare
3369bfc
to
49ae1bd
Compare
I think that makes sense, but this function is not the place to do it. All of these metrics are "k6 metrics". The custom ones we emit is mostly done from |
I don't mean that but never mind, no big deal. |
49ae1bd
to
286eafa
Compare
286eafa
to
f29064e
Compare
@inancgumus A test for this would be similar to e6db54c, and we decided to revert that because of Let me know if there's anything pending here, otherwise let's merge it. (Sorry for the last force push... It was only for adding a test in f29064e.) |
Fair enough, let's merge it 🚀 Maybe create an issue for testing both, so we don't forget? And maybe add a note next to grafana/k6#4469. |
This is related to #90, but doesn't really fix the issue as the current behavior is correct. See this comment.
Essentially
Frame
being nil is a valid state for responses to preflight/OPTIONS
requests, so the fix in b2052bb is all we need to do for handling this case.What this PR does is:
move the
data_received
metric emission to theLoadingFinished
event handler, which according to this comment is more reliable. This results in more accuratedata_received
metric values.Before, on
main
(54cd293) with the script from this comment:After, on a9d9868:
I confirmed this is more accurate by comparing with the Chrome DevTools Network tab.
This change also produces less
error fetching response body
warning events, though it doesn't eliminate them entirely, except when running the test suite.enhance the logging so that we can see the type of request when
Frame
is nil. In all cases I've seen with the script from Tom these are preflight/OPTIONS
requests which don't have an associatedFrameID
.