Skip to content

Conversation

@bmd3k
Copy link
Contributor

@bmd3k bmd3k commented Mar 4, 2020

  • Motivation for features / changes

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.

  • Technical description of changes

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.

  • Screenshots of UI changes

Using an example where "images" and "graphs" are marked as experimental.

No query parameters:
image

"experimentalPlugin=images" query parameter:
image

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

  • Detailed steps to verify changes work correctly (as executed by you)

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.

bmd3k added 2 commits March 4, 2020 11:11
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.
@bmd3k bmd3k requested a review from wchargin March 4, 2020 20:02
Copy link
Contributor

@wchargin wchargin left a 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).


def standard_tensorboard_wsgi(flags, plugin_loaders, assets_zip_provider, experimental_plugins=[]):
def standard_tensorboard_wsgi(
flags, plugin_loaders, assets_zip_provider, experimental_plugins=[]
Copy link
Contributor

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 None as 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.)

Copy link
Contributor Author

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!

PLUGINS_LISTING_ROUTE = "/plugins_listing"
PLUGIN_ENTRY_ROUTE = "/plugin_entry.html"

EXPERIMENTAL_PLUGINS_QUERY_PARAM = "expplugin"
Copy link
Contributor

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.

Copy link
Contributor Author

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.

self._plugins = plugins
self._path_prefix = path_prefix
self._data_provider = data_provider
self._experimental_plugins = experimental_plugins
Copy link
Contributor

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 502 to 510
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)
),
Copy link
Contributor

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
            ...

Copy link
Contributor Author

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):
Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

assets_zip_provider=None,
server_class=None,
subcommands=None,
experimental_plugins=[],
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@bmd3k bmd3k closed this Mar 10, 2020
@wchargin
Copy link
Contributor

(For posterity, this is superseded by #3344.)

@bmd3k bmd3k deleted the experimental-plugins branch March 16, 2020 14:27
@bmd3k bmd3k restored the experimental-plugins branch March 20, 2020 11:49
@bmd3k bmd3k deleted the experimental-plugins branch March 20, 2020 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants