-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Experimental plugins #3326
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
Experimental plugins #3326
Conversation
Adds initial experimental plugins support in application layer. A list of experimental_plugins can be specified (by plugin name). When handling plugins_listing requests, experimental_plugins are included if they are specified using the expplugin query parameter. Note: This commit does not yet include the work to pass the query parameters in the plugins_listing request.
Add support to pass expplugin query parameters from the page's URL to the plugins_listing request.
wchargin
left a comment
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.
Comments modulo agreement about whether the args to TensorBoardWSGIApp
are okay with us (cc @nfelt).
tensorboard/backend/application.py
Outdated
|
|
||
| def standard_tensorboard_wsgi(flags, plugin_loaders, assets_zip_provider, experimental_plugins=[]): | ||
| def standard_tensorboard_wsgi( | ||
| flags, plugin_loaders, assets_zip_provider, experimental_plugins=[] |
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.
Use experimental_plugins=None instead and do the resolution inside the
function body, because:
- default argument values are evaluated at function definition time,
not invocation time, so this introduces a globally shared mutable
list object—not what you want! (cf. style guide) - using
Noneas a default argument value in general is a common
Python pattern enabling callers to specify “use default”
without having to duplicate the actual value of the default
(Likewise below.)
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.
Done.
Wow, mutable shared default values.
I'm not sure if the way I now initialize self._experimental_plugins is idiomatic python - let me know!
tensorboard/backend/application.py
Outdated
| PLUGINS_LISTING_ROUTE = "/plugins_listing" | ||
| PLUGIN_ENTRY_ROUTE = "/plugin_entry.html" | ||
|
|
||
| EXPERIMENTAL_PLUGINS_QUERY_PARAM = "expplugin" |
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.
Could we call this experimentalPlugin instead? exp is ambiguous in
general, and especially so given that TensorBoard has a notion of
experiments that’s not what’s meant here.
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.
Done.
Yes, I think it's fine to be a bit wordy for now assuming this isn't used very frequently. If this proves to be a popular feature, though, we may find ourselves adding something very concise later on. Previous systems I've worked with use "e=" to trigger experiments. We might want to add something like "ep" in the future. Maybe.
tensorboard/backend/application.py
Outdated
| self._plugins = plugins | ||
| self._path_prefix = path_prefix | ||
| self._data_provider = data_provider | ||
| self._experimental_plugins = experimental_plugins |
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.
Since this is only used for membership tests, best to convert it to a
frozenset for faster queries and more idiomatic set operations (see
suggestion below).
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.
Done.
tensorboard/backend/application.py
Outdated
| plugins_to_consider = filter( | ||
| # Filter out experimental plugins that were not activated using the query param. | ||
| lambda plugin: ( | ||
| plugin.plugin_name not in self._experimental_plugins | ||
| ) | ||
| or ( | ||
| plugin.plugin_name | ||
| in request.args.getlist(EXPERIMENTAL_PLUGINS_QUERY_PARAM) | ||
| ), |
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.
Instead of filter(lambda: ..., ...), use a list comprehension, per the
style guide:
https://google.github.io/styleguide/pyguide.html#215-deprecated-language-features
But probably simpler here to just conditionally continue at the top of
the for-loop? Certainly shorter:
plugins_to_skip = self._experimental_plugins - frozenset(
request.args.getlist(EXPERIMENTAL_PLUGINS_QUERY_PARAM)
)
for plugin in self._plugins:
if p.plugin_name in plugins_to_skip:
continue
...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.
Done. (2nd option)
| self.assertEqual(parsed_object["foo"]["enabled"], False) | ||
| self.assertEqual(parsed_object["baz"]["enabled"], True) | ||
|
|
||
| def testPluginsListingWithExperimentalPlugin(self): |
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 tests; thanks!
| export function createRouter(dataDir = 'data'): Router { | ||
| export function createRouter( | ||
| dataDir = 'data', | ||
| windowLocationUrl = new URL(window.location.href) |
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.
This is a little scary; in the past, we’ve had issues with code like
this breaking --path_prefix handling. It’s fine here because you’re
only using it for the search params rather than the path, but I wonder
if we can make that clearer—something like
export function createRouter(
dataDir = 'data',
searchParams = new URLSearchParams(window.location.search)
): Router {
// ...
}such that future changes to the function don’t accidentally rely on
path?
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.
Done. Though I decided to call it urlSearchParams.
tensorboard/program.py
Outdated
| assets_zip_provider=None, | ||
| server_class=None, | ||
| subcommands=None, | ||
| experimental_plugins=[], |
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.
Hmm; it would be great if we could avoid exposing this on program;
unlike backend.application, program is a public API and I’m not sure
that we want to commit to supporting this forever.
Do you have any thoughts about the best way to do this? The internal
clients that need this don’t call through program (instead calling
TensorBoardWSGIApp directly), but I see that tensorboard.main does.
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.
It's true that for my particular use case I do not need to call through program.py. I made the changes here just to be complete. I'm ok reverting the changes to this file and deciding later whether to expose the experimentalPlugin functionality more broadly.
|
(For posterity, this is superseded by #3344.) |
Allow an administrator of a Tensorboard to mark certain plugins as "experimental" and hidden to users by default. Users would then need to add query parameter "experimentalPlugin=" to the URL of their experiment to see the experimental plugin.
standard_tensorboard_wsgi and TensorBoardWSGIApp include an argument "experimental_plugins" that lists plugins, by name, that are in an experimental state.
There is logic in _serve_plugins_listing() that filters out plugins that are included in experimental_plugins but are not specified as a value for an experimentalPlugin query parameter.
There is logic in createRouter() that forward "experimentalPlugin" query parameter values from the original TensorBoard request to the call to the plugins_listing endpoint.
Using an example where "images" and "graphs" are marked as experimental.
No query parameters:

"experimentalPlugin=images" query parameter:

"experimentalPlugin=images&experimentalPlugin=graphs" query parameters:

Modify your favorite instance of Tensorboard to include experimental_plugins = ["images", "graphs"] in call to standard_tensorboard_wsgi() or TensorBoardWSGIApp().
Start server.
Open "http://localhost:6006/" without query parameters. Note that "Images" and "Graphs" do not appear as plugins.
Open "http://localhost:6006/?experimentalPlugin=images&experimentalPlugin=graphs". Note that "Images" and "Graphs" appear as plugins.