Spaces:
Running
Running
# 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. | |
# ============================================================================== | |
"""Utilities for using TensorBoard in notebook contexts, like Colab. | |
These APIs are experimental and subject to change. | |
""" | |
import datetime | |
import errno | |
import html | |
import json | |
import os | |
import random | |
import shlex | |
import textwrap | |
import time | |
from tensorboard import manager | |
# Return values for `_get_context` (see that function's docs for | |
# details). | |
_CONTEXT_COLAB = "_CONTEXT_COLAB" | |
_CONTEXT_IPYTHON = "_CONTEXT_IPYTHON" | |
_CONTEXT_NONE = "_CONTEXT_NONE" | |
def _get_context(): | |
"""Determine the most specific context that we're in. | |
Returns: | |
_CONTEXT_COLAB: If in Colab with an IPython notebook context. | |
_CONTEXT_IPYTHON: If not in Colab, but we are in an IPython notebook | |
context (e.g., from running `jupyter notebook` at the command | |
line). | |
_CONTEXT_NONE: Otherwise (e.g., by running a Python script at the | |
command-line or using the `ipython` interactive shell). | |
""" | |
# In Colab, the `google.colab` module is available, but the shell | |
# returned by `IPython.get_ipython` does not have a `get_trait` | |
# method. | |
try: | |
import google.colab # noqa: F401 | |
import IPython | |
except ImportError: | |
pass | |
else: | |
if IPython.get_ipython() is not None: | |
# We'll assume that we're in a Colab notebook context. | |
return _CONTEXT_COLAB | |
# In an IPython command line shell or Jupyter notebook, we can | |
# directly query whether we're in a notebook context. | |
try: | |
import IPython | |
except ImportError: | |
pass | |
else: | |
ipython = IPython.get_ipython() | |
if ipython is not None and ipython.has_trait("kernel"): | |
return _CONTEXT_IPYTHON | |
# Otherwise, we're not in a known notebook context. | |
return _CONTEXT_NONE | |
def load_ipython_extension(ipython): | |
"""Deprecated: use `%load_ext tensorboard` instead. | |
Raises: | |
RuntimeError: Always. | |
""" | |
raise RuntimeError( | |
"Use '%load_ext tensorboard' instead of '%load_ext tensorboard.notebook'." | |
) | |
def _load_ipython_extension(ipython): | |
"""Load the TensorBoard notebook extension. | |
Intended to be called from `%load_ext tensorboard`. Do not invoke this | |
directly. | |
Args: | |
ipython: An `IPython.InteractiveShell` instance. | |
""" | |
_register_magics(ipython) | |
def _register_magics(ipython): | |
"""Register IPython line/cell magics. | |
Args: | |
ipython: An `InteractiveShell` instance. | |
""" | |
ipython.register_magic_function( | |
_start_magic, | |
magic_kind="line", | |
magic_name="tensorboard", | |
) | |
def _start_magic(line): | |
"""Implementation of the `%tensorboard` line magic.""" | |
return start(line) | |
def start(args_string): | |
"""Launch and display a TensorBoard instance as if at the command line. | |
Args: | |
args_string: Command-line arguments to TensorBoard, to be | |
interpreted by `shlex.split`: e.g., "--logdir ./logs --port 0". | |
Shell metacharacters are not supported: e.g., "--logdir 2>&1" will | |
point the logdir at the literal directory named "2>&1". | |
""" | |
context = _get_context() | |
try: | |
import IPython | |
import IPython.display | |
except ImportError: | |
IPython = None | |
if context == _CONTEXT_NONE: | |
handle = None | |
print("Launching TensorBoard...") | |
else: | |
handle = IPython.display.display( | |
IPython.display.Pretty("Launching TensorBoard..."), | |
display_id=True, | |
) | |
def print_or_update(message): | |
if handle is None: | |
print(message) | |
else: | |
handle.update(IPython.display.Pretty(message)) | |
parsed_args = shlex.split(args_string, comments=True, posix=True) | |
start_result = manager.start(parsed_args) | |
if isinstance(start_result, manager.StartLaunched): | |
_display( | |
port=start_result.info.port, | |
print_message=False, | |
display_handle=handle, | |
) | |
elif isinstance(start_result, manager.StartReused): | |
template = ( | |
"Reusing TensorBoard on port {port} (pid {pid}), started {delta} ago. " | |
"(Use '!kill {pid}' to kill it.)" | |
) | |
message = template.format( | |
port=start_result.info.port, | |
pid=start_result.info.pid, | |
delta=_time_delta_from_info(start_result.info), | |
) | |
print_or_update(message) | |
_display( | |
port=start_result.info.port, | |
print_message=False, | |
display_handle=None, | |
) | |
elif isinstance(start_result, manager.StartFailed): | |
def format_stream(name, value): | |
if value == "": | |
return "" | |
elif value is None: | |
return "\n<could not read %s>" % name | |
else: | |
return "\nContents of %s:\n%s" % (name, value.strip()) | |
message = ( | |
"ERROR: Failed to launch TensorBoard (exited with %d).%s%s" | |
% ( | |
start_result.exit_code, | |
format_stream("stderr", start_result.stderr), | |
format_stream("stdout", start_result.stdout), | |
) | |
) | |
print_or_update(message) | |
elif isinstance(start_result, manager.StartExecFailed): | |
the_tensorboard_binary = ( | |
"%r (set by the `TENSORBOARD_BINARY` environment variable)" | |
% (start_result.explicit_binary,) | |
if start_result.explicit_binary is not None | |
else "`tensorboard`" | |
) | |
if start_result.os_error.errno == errno.ENOENT: | |
message = ( | |
"ERROR: Could not find %s. Please ensure that your PATH contains " | |
"an executable `tensorboard` program, or explicitly specify the path " | |
"to a TensorBoard binary by setting the `TENSORBOARD_BINARY` " | |
"environment variable." % (the_tensorboard_binary,) | |
) | |
else: | |
message = "ERROR: Failed to start %s: %s" % ( | |
the_tensorboard_binary, | |
start_result.os_error, | |
) | |
print_or_update(textwrap.fill(message)) | |
elif isinstance(start_result, manager.StartTimedOut): | |
message = ( | |
"ERROR: Timed out waiting for TensorBoard to start. " | |
"It may still be running as pid %d." % start_result.pid | |
) | |
print_or_update(message) | |
else: | |
raise TypeError( | |
"Unexpected result from `manager.start`: %r.\n" | |
"This is a TensorBoard bug; please report it." % start_result | |
) | |
def _time_delta_from_info(info): | |
"""Format the elapsed time for the given TensorBoardInfo. | |
Args: | |
info: A TensorBoardInfo value. | |
Returns: | |
A human-readable string describing the time since the server | |
described by `info` started: e.g., "2 days, 0:48:58". | |
""" | |
delta_seconds = int(time.time()) - info.start_time | |
return str(datetime.timedelta(seconds=delta_seconds)) | |
def display(port=None, height=None): | |
"""Display a TensorBoard instance already running on this machine. | |
Args: | |
port: The port on which the TensorBoard server is listening, as an | |
`int`, or `None` to automatically select the most recently | |
launched TensorBoard. | |
height: The height of the frame into which to render the TensorBoard | |
UI, as an `int` number of pixels, or `None` to use a default value | |
(currently 800). | |
""" | |
_display(port=port, height=height, print_message=True, display_handle=None) | |
def _display(port=None, height=None, print_message=False, display_handle=None): | |
"""Internal version of `display`. | |
Args: | |
port: As with `display`. | |
height: As with `display`. | |
print_message: True to print which TensorBoard instance was selected | |
for display (if applicable), or False otherwise. | |
display_handle: If not None, an IPython display handle into which to | |
render TensorBoard. | |
""" | |
if height is None: | |
height = 800 | |
if port is None: | |
infos = manager.get_all() | |
if not infos: | |
raise ValueError( | |
"Can't display TensorBoard: no known instances running." | |
) | |
else: | |
info = max(manager.get_all(), key=lambda x: x.start_time) | |
port = info.port | |
else: | |
infos = [i for i in manager.get_all() if i.port == port] | |
info = max(infos, key=lambda x: x.start_time) if infos else None | |
if print_message: | |
if info is not None: | |
message = ( | |
"Selecting TensorBoard with {data_source} " | |
"(started {delta} ago; port {port}, pid {pid})." | |
).format( | |
data_source=manager.data_source_from_info(info), | |
delta=_time_delta_from_info(info), | |
port=info.port, | |
pid=info.pid, | |
) | |
print(message) | |
else: | |
# The user explicitly provided a port, and we don't have any | |
# additional information. There's nothing useful to say. | |
pass | |
fn = { | |
_CONTEXT_COLAB: _display_colab, | |
_CONTEXT_IPYTHON: _display_ipython, | |
_CONTEXT_NONE: _display_cli, | |
}[_get_context()] | |
return fn(port=port, height=height, display_handle=display_handle) | |
def _display_colab(port, height, display_handle): | |
"""Display a TensorBoard instance in a Colab output frame. | |
The Colab VM is not directly exposed to the network, so the Colab | |
runtime provides a service worker tunnel to proxy requests from the | |
end user's browser through to servers running on the Colab VM: the | |
output frame may issue requests to https://localhost:<port> (HTTPS | |
only), which will be forwarded to the specified port on the VM. | |
It does not suffice to create an `iframe` and let the service worker | |
redirect its traffic (`<iframe src="https://localhost:6006">`), | |
because for security reasons service workers cannot intercept iframe | |
traffic. Instead, we manually fetch the TensorBoard index page with an | |
XHR in the output frame, and inject the raw HTML into `document.body`. | |
By default, the TensorBoard web app requests resources against | |
relative paths, like `./data/logdir`. Within the output frame, these | |
requests must instead hit `https://localhost:<port>/data/logdir`. To | |
redirect them, we change the document base URI, which transparently | |
affects all requests (XHRs and resources alike). | |
""" | |
import IPython.display | |
shell = """ | |
(async () => { | |
const url = new URL(await google.colab.kernel.proxyPort(%PORT%, {'cache': true})); | |
url.searchParams.set('tensorboardColab', 'true'); | |
const iframe = document.createElement('iframe'); | |
iframe.src = url; | |
iframe.setAttribute('width', '100%'); | |
iframe.setAttribute('height', '%HEIGHT%'); | |
iframe.setAttribute('frameborder', 0); | |
document.body.appendChild(iframe); | |
})(); | |
""" | |
replacements = [ | |
("%PORT%", "%d" % port), | |
("%HEIGHT%", "%d" % height), | |
] | |
for k, v in replacements: | |
shell = shell.replace(k, v) | |
script = IPython.display.Javascript(shell) | |
if display_handle: | |
display_handle.update(script) | |
else: | |
IPython.display.display(script) | |
def _display_ipython(port, height, display_handle): | |
import IPython.display | |
frame_id = "tensorboard-frame-{:08x}".format(random.getrandbits(64)) | |
shell = """ | |
<iframe id="%HTML_ID%" width="100%" height="%HEIGHT%" frameborder="0"> | |
</iframe> | |
<script> | |
(function() { | |
const frame = document.getElementById(%JSON_ID%); | |
const url = new URL(%URL%, window.location); | |
const port = %PORT%; | |
if (port) { | |
url.port = port; | |
} | |
frame.src = url; | |
})(); | |
</script> | |
""" | |
proxy_url = os.environ.get("TENSORBOARD_PROXY_URL") | |
if proxy_url is not None: | |
# Allow %PORT% in $TENSORBOARD_PROXY_URL | |
proxy_url = proxy_url.replace("%PORT%", "%d" % port) | |
replacements = [ | |
("%HTML_ID%", html.escape(frame_id, quote=True)), | |
("%JSON_ID%", json.dumps(frame_id)), | |
("%HEIGHT%", "%d" % height), | |
("%PORT%", "0"), | |
("%URL%", json.dumps(proxy_url)), | |
] | |
else: | |
replacements = [ | |
("%HTML_ID%", html.escape(frame_id, quote=True)), | |
("%JSON_ID%", json.dumps(frame_id)), | |
("%HEIGHT%", "%d" % height), | |
("%PORT%", "%d" % port), | |
("%URL%", json.dumps("/")), | |
] | |
for k, v in replacements: | |
shell = shell.replace(k, v) | |
iframe = IPython.display.HTML(shell) | |
if display_handle: | |
display_handle.update(iframe) | |
else: | |
IPython.display.display(iframe) | |
def _display_cli(port, height, display_handle): | |
del height # unused | |
del display_handle # unused | |
message = "Please visit http://localhost:%d in a web browser." % port | |
print(message) | |
def list(): | |
"""Print a listing of known running TensorBoard instances. | |
TensorBoard instances that were killed uncleanly (e.g., with SIGKILL | |
or SIGQUIT) may appear in this list even if they are no longer | |
running. Conversely, this list may be missing some entries if your | |
operating system's temporary directory has been cleared since a | |
still-running TensorBoard instance started. | |
""" | |
infos = manager.get_all() | |
if not infos: | |
print("No known TensorBoard instances running.") | |
return | |
print("Known TensorBoard instances:") | |
for info in infos: | |
template = ( | |
" - port {port}: {data_source} (started {delta} ago; pid {pid})" | |
) | |
print( | |
template.format( | |
port=info.port, | |
data_source=manager.data_source_from_info(info), | |
delta=_time_delta_from_info(info), | |
pid=info.pid, | |
) | |
) | |