Skip to content

Commit

Permalink
plugins: add sample dynamically loaded plugin (#2354)
Browse files Browse the repository at this point in the history
Summary:
This plugin can eventually replace the `tensorboard-plugin-example`
repository; it’ll be easier for us to keep it up to date if it’s in the
same repository as main TensorBoard. And it has tests!

Test Plan:
Run `bazel test //tensorboard/plugins/example:smoke_test` to start.
Then, modify `setup.py` to remove the `entry_points` entry, and note
that the test fails. Separately, modify line 42 of `__init__.py` to
serve the JS under `/not_index.js` instead; the test should again fail.

Test the development workflow: Follow the instructions in `README.md`.
Then, edit `widgets.js` (say, add another exclamation point) and refresh
to see changes live without restarting TensorBoard:

![screenshot of “Hello TensorBoard!” in example plugin][1]

[1]: https://user-images.githubusercontent.com/4317806/59641012-cb700380-9114-11e9-8323-c811388df31c.png

wchargin-branch: example-dynamic-plugin
  • Loading branch information
wchargin authored Jun 24, 2019
1 parent 6399b85 commit afecb68
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 0 deletions.
21 changes: 21 additions & 0 deletions tensorboard/plugins/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

# Generated by running `python setup.py develop`.
tensorboard_plugin_example.egg-info/

# Generated by running `python setup.py bdist_wheel`.
build/
dist/
25 changes: 25 additions & 0 deletions tensorboard/plugins/example/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Description:
# TensorBoard example plugin

# This plugin is not primarily built with Bazel (intentionally, to
# demonstrate that Bazel is not required); we use this BUILD file only
# to define integration tests for TensorBoard itself.

package(default_visibility = ["//tensorboard:internal"])

licenses(["notice"]) # Apache 2.0

sh_test(
name = "smoke_test",
size = "large",
timeout = "short",
srcs = ["smoke_test.sh"],
# Don't just `glob(["**"])` because `setup.py` creates artifacts
# like wheels and a `tensorboard_plugin_example.egg-info` directory,
# and we want to make sure that those don't interfere with the test.
data = [
"setup.py",
"tensorboard_plugin_example/__init__.py",
"//tensorboard/pip_package",
] + glob(["tensorboard_plugin_example/static/**"]),
)
48 changes: 48 additions & 0 deletions tensorboard/plugins/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Example plugin

## Overview

A sample plugin using TensorBoard’s dynamic plugin loading mechanism. It
doesn’t do anything useful at the moment, but a stock TensorBoard binary
(1.14+) will automatically load and display it.

This code happens to live in the TensorBoard repository, but is intended
to stand in for a third-party plugin developer’s separate repository. It
is not built with Bazel, and it only depends on TensorBoard through its
public APIs (under the assumption that TensorBoard is installed in an
active virtualenv).

## Usage

In this directory (`tensorboard/plugins/example`), in a virtualenv with
TensorBoard installed, run:

```
python setup.py develop
```

This will link the plugin into your virtualenv. You can edit the
plugin’s source code and data files and see changes reflected without
having to reinstall the plugin.

Then, just run

```
tensorboard --logdir /tmp/whatever
```

and open TensorBoard to see the plugin’s “hello world” screen.

After making changes to the Python code, you must restart TensorBoard
for your changes to take effect. If your plugin reads data files at
runtime, you can edit those and see changes reflected immediately.

You can run

```
python setup.py develop --uninstall
```

to unlink the plugin from your virtualenv, after which you can also
delete the `tensorboard_plugin_example.egg-info/` directory that the
original `setup.py` invocation created.
36 changes: 36 additions & 0 deletions tensorboard/plugins/example/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import setuptools


setuptools.setup(
name="tensorboard_plugin_example",
version="0.1.0",
description="Sample TensorBoard plugin.",
packages=["tensorboard_plugin_example"],
package_data={
"tensorboard_plugin_example": ["static/**"],
},
entry_points={
"tensorboard_plugins": [
"example = tensorboard_plugin_example:ExamplePlugin",
],
},
)
61 changes: 61 additions & 0 deletions tensorboard/plugins/example/smoke_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/sh
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eux
command -v virtualenv >/dev/null

workdir="$(mktemp -d)"
cd "${workdir}"

cleanup() {
rm -r "${workdir}"
}
trap cleanup EXIT

cp -LR "${TEST_SRCDIR}/org_tensorflow_tensorboard/tensorboard/plugins/example/" \
./example-plugin/

mkdir tensorboard-wheels
tar xzvf \
"${TEST_SRCDIR}/org_tensorflow_tensorboard/tensorboard/pip_package/pip_packages.tar.gz" \
-C ./tensorboard-wheels/

virtualenv venv
export VIRTUAL_ENV=venv
export PATH="${PWD}/venv/bin:${PATH}"
unset PYTHON_HOME

# Require wheel for bdist_wheel command, and setuptools 36.2.0+ so that
# env markers are handled (https://github.com/pypa/setuptools/pull/1081)
pip install -qU wheel 'setuptools>=36.2.0'

(cd ./example-plugin && python setup.py bdist_wheel)
[ -f ./example-plugin/dist/*.whl ] # just one wheel

# This plugin doesn't require TensorFlow, so we don't install it.
py_major_version="$(python -c 'import sys; print(sys.version_info[0])')"
pip install ./tensorboard-wheels/*py"${py_major_version}"*.whl
pip install ./example-plugin/dist/*.whl

# Test tensorboard + tensorboard_plugin_example integration.
mkfifo pipe
tensorboard --port=0 --logdir=smokedir 2>pipe &
perl -ne 'print STDERR;/http:.*:(\d+)/ and print $1.v10 and exit 0' <pipe >port
curl -fs "http://localhost:$(cat port)/data/plugins_listing" >plugins_listing
grep -F '"example":' plugins_listing
grep -F '"/data/plugin/example/index.js"' plugins_listing
curl -fs "http://localhost:$(cat port)/data/plugin/example/index.js" >index.js
diff -u example-plugin/tensorboard_plugin_example/static/index.js index.js
kill $!
60 changes: 60 additions & 0 deletions tensorboard/plugins/example/tensorboard_plugin_example/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""A sample plugin to demonstrate dynamic loading."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os

from tensorboard.plugins import base_plugin
import werkzeug
from werkzeug import wrappers


class ExamplePlugin(base_plugin.TBPlugin):
plugin_name = 'example'

def __init__(self, context):
# A real plugin would likely save the `context.multiplexer` and/or
# `context.db_connection_provider` attributes for later use, but we
# don't actually need any of that.
pass

def is_active(self):
return True

def get_plugin_apps(self):
return {
"/index.js": self._serve_js,
}

def frontend_metadata(self):
return super(ExamplePlugin, self).frontend_metadata()._replace(
es_module_path="/index.js",
)

@wrappers.Request.application
def _serve_js(self, request):
del request # unused
filepath = os.path.join(os.path.dirname(__file__), "static", "index.js")
with open(filepath) as infile:
contents = infile.read()
return werkzeug.Response(
contents,
status=200,
content_type="application/javascript",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ==============================================================================

export function render() {
const msg = document.createElement("span");
msg.innerText = "Hello TensorBoard!";
document.body.appendChild(msg);
}

0 comments on commit afecb68

Please sign in to comment.