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

Prebid core: automatic dynamic loading of debugging module #8106

Merged
merged 10 commits into from
Jun 14, 2022

Conversation

dgirardi
Copy link
Collaborator

@dgirardi dgirardi commented Feb 23, 2022

Type of change

  • Feature

Description of change

This adds a "standalone" version of the debugging module to the build, and automatically loads it when debugging configuration is passed to setConfig or loaded from session storage. All other debugging logic is moved to the "debugging" module.

By "standalone" I mean a version that does not rely on having a specific build of "prebid-core.js" available, as is the case for all other modules; this is to enable it to work with third party builds (such as any that was done by consuming Prebid from npm).

@dgirardi dgirardi marked this pull request as ready for review February 24, 2022 20:59
@Rothalack
Copy link
Collaborator

I'm not opposed to this change. It appears to be working for me in testing. I have apprehensions of the wider implications of this change, but that's mostly because I don't feel very confident of my understanding of what other implications it may have. I don't want to be the final say on that. Would like more eyes on it.

@robertrmartinez
Copy link
Collaborator

@ChrisHuie I do not think I have enough time to get to this this week unfortunately. Sorry for the lag.

I can try and get back to this one middle to end of next week

@robertrmartinez robertrmartinez removed their request for review March 31, 2022 20:21
@robertrmartinez robertrmartinez removed their assignment Mar 31, 2022
Copy link
Contributor

@ncolletti ncolletti left a comment

Choose a reason for hiding this comment

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

Nice work here, the PR looks good as-is and my tests are passing. However I share the same sentiments as the previous reviewer that it may be best to have another look from a reviewer who may be more knowledgeable of edge cases to consider before introducing these changes.

Comment on lines +7 to +9
Because of this, **this module cannot freely import symbols from core**: anything that depends on Prebid global state (which includes, but is not limited to, `config`, `auctionManager`, `adapterManager`, etc) would *not* work as expected.

Imports must be limited to logic that is stateless and free of side effects; symbols from `utils.js` are mostly OK, with the notable exception of logging functions (which have a dependency on `config`).
Copy link
Contributor

Choose a reason for hiding this comment

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

If there are no import guards that can be setup at build time, then this may be something to mention in the PR_REVIEW doc for others to be on the look out for.

@ChrisHuie ChrisHuie self-requested a review April 22, 2022 18:28
@patmmccann patmmccann requested a review from allanjun May 11, 2022 12:24
@patmmccann
Copy link
Collaborator

@allanjun this should integrate nicely into tools, we're very interested in your feedback

@allanjun
Copy link
Collaborator

hi @patmmccann thanks for tagging me. Maybe I didn't understand it right, but does this mean we need publishers to also host the file debugging-standalone.js ? Or we plan to host this at jsdelivr ? Should this remote file url be configurable at runtime via setconfig ?

@dgirardi
Copy link
Collaborator Author

dgirardi commented May 11, 2022

@allanjun, debugging-standalone is pulled from jsdelivr by default, configurable at build time - the intent is to allow debugging of production sites without any extra setup work (and without the weight of debugging code until it's needed).

I decided to not make it configurable at runtime because to get the effect of "reload the page to see your mock ads" the URL would need to be stored locally (in many cases we will need it before any call to setConfig). That's essentially the same as doing eval(localStorage.sourceCode) - it's a potential security issue.

We could allow a runtime setConfig({debugUrl}) that would work only if it's called before requestBids - that is very similar to a build time config, because it needs to be set pre-emptively and changing it requires re-deployment. But if the interface is nicer I can make the change.

@allanjun
Copy link
Collaborator

thanks for the explanation @dgirardi all makes sense. Professor Prebid could ensure that setConfig runs before requestBids (so would be for the other hooks that intercept the bidResponses) but I think that won't be needed, the debugging file on jsdlvr would do enough. We can include that in the future if really needed.

@dgirardi
Copy link
Collaborator Author

@allanjun I am not that familiar with what extensions can do nowadays, but I'd be surprised if that's true in general - setConfig and requestBids could be bundled together in one javascript file and run before you get control back. Even a pre-set pbjs.que could technically be prepended with stuff that breaks the flow, although granted that's unlikely. IMO if we need that feature setConfig is not a good place for it, but we can discuss this if/when we get there.

@snapwich snapwich self-assigned this Jun 8, 2022
Copy link
Collaborator

@snapwich snapwich left a comment

Choose a reason for hiding this comment

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

just some small feedback. if the things i suggested are not possible for whatever reason then it looks good as-is.

src/prebid.js Outdated
bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null))
}
return startAuction({bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ortb2Fragments});
ready().then(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

i feel like this should use the existing startAuction hook below since it already exists and since hooks execute quickly through synchronous callbacks rather than a deferred promise. by wrapping this in a then it subjects all requestBids calls to the performance penalty of using a promise, even those that are not using debugging.

is there any reason why this can't use the startAuction hook?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm taking that to mean that we should hook debugging logic only when we need it - i.e. add/remove hooks when it's toggled - because I don't see how requestBids vs startAuction would have any difference in performance.

Copy link
Collaborator

Choose a reason for hiding this comment

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

add/remove hooks when it's toggled

that wasn't what i was suggesting although now that you mention it you probably could do that.

i was referencing the using existing hook (startAuction) because it already exists at almost the same point where you implemented the debugging ready logic, and if startAuction is sufficient (i.e. not too late) might as well use what already exists.

the performance impact i reference is that by implementing the wait logic with promises you defer the start of the auction to the next execution of the micro-task queue. the hook uses synchronous callbacks to continue execution which means if there are no hooks it will execute immediately.

Copy link
Collaborator Author

@dgirardi dgirardi Jun 10, 2022

Choose a reason for hiding this comment

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

I'm going the add/remove hook route so we don't need to fight over this, but I'll start anyway: if you mean "go synchronous when you can", with patterns that I saw elsewhere like if (timeout === 0) callback() else setTimeout(callback, timeout), I don't think it's a good idea.

My sense is the performance impact of waiting for the queue to clear is significant only when there's a lot of stuff on the queue (expecting the VM scheduler to be blazingly fast compared to interpreted code), and holding on to control is not the right thing to do in those cases, because the browser freezing for a split second - or, god forbid, the "page is not responding" popup - is a lot more noticeable than some delay in rendering.

Not to mention that at some point we're always going to need to wait for the network, so is shaving (I'm guessing) 1-2ms from 200 or so worth it? but I'll admit that I have not attempted to measure this, so I'm curious why you think it is.

Copy link
Collaborator

Choose a reason for hiding this comment

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

i don't think a if (timeout === 0) callback() else setTimeout(callback, timeout) style is a good practice in general but i think it makes perfect sense in our case. the whole concept of header bidding is to hold an auction in the browser before the page loads with the goal of rendering with the page, and anytime we defer execution we work against that goal.

i don't think this is a case of greedily holding onto resources. requestBids was called which means the publisher meant to start the auction and we've been given our time, so to just immediately defer without cause is:

  1. not being responsible with the time we were given
  2. not an easily quantifiable performance hit. as you said it depends on the size of the queue, that could be nothing or it could be everything. considering we execute at page load we're definitely competing with a lot of other resources though.
  3. is not helpful from a UX perspective (browser freezing, etc). the auction involves an asynchronous request to the bidder endpoints which means we're going to be deferring execution whether we want to or not as soon as we make the requests. why give up our execution time slice early when we know we're just going to be required to defer a few lines of code later?

i'm not anti-promises or anything and i've mentioned with others that i'm comfortable using them where they make sense: with asynconous execution. however i'm very against using them in the critical code path in situations that are expected to be behave synchronously.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I can turn your argument around and say "why not give up control now since we'll be forced to do it as soon as we need the network anyway"?

you could and i'd say because then we'd be giving up control twice instead of the required once. it's like stopping 1 exit early to fill up with gas when you know you need to stop at the next city just a few minutes down the road anyways...

Copy link
Collaborator

@patmmccann patmmccann Jun 11, 2022

Choose a reason for hiding this comment

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

Do we care if debugging is performant?

Or is this modifying the normal workflow

Copy link
Collaborator

Choose a reason for hiding this comment

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

no i don't think it matters too much if debugging is performant (although it is nice to emulate the environment as well as possible)

my main concern is that this impacts the non-debugging code path as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just to confirm - are you referring to the original approach, or the last revision as well? (promises now should only come into the picture when debugging is enabled, and I'm using loadExternalScript as well).

Copy link
Collaborator

Choose a reason for hiding this comment

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

original approach. new revision looks good and i think promises in the debugging flow is acceptable.

return false;
}
return true;
function loadScript(url) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can this use loadExternalScript in src/adloader.js ?

@patmmccann patmmccann merged commit da46bf2 into prebid:master Jun 14, 2022
renebaudisch pushed a commit to renebaudisch/Prebid.js that referenced this pull request Jun 28, 2022
* WIP: move all debugging logic to the debugging module

* Dynamic loading of modules

* Move all debugging logic to the debugging module

* Automatically load debugging module when needed

* Build and dynamically load "standalone" version of debugging module

* add note to PR_REVIEW

* use loadExternalScript; only add debugging hooks when needed
bwhisp pushed a commit to bwhisp/Prebid.js that referenced this pull request Jul 13, 2022
* WIP: move all debugging logic to the debugging module

* Dynamic loading of modules

* Move all debugging logic to the debugging module

* Automatically load debugging module when needed

* Build and dynamically load "standalone" version of debugging module

* add note to PR_REVIEW

* use loadExternalScript; only add debugging hooks when needed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

7 participants