Skip to content

Commit 61df080

Browse files
authored
refactor: slice the two-headed application.py monster in half (#3643)
* split up application.py and branch to data_ingester.py with no content changes * update contents of data_ingester.py and test * remove projector_plugin_test dep on application.DEFAULT_SIZE_GUIDANCE * replace program.py dep on standard_tensorboard_wsgi with LocalDataIngester * lint: black formatting program.py * cr: fix typo in data_ingester.py _parse_event_files_spec * lint: fix variable I forgot to rename * cr: remove dupe import * cr: remove event_processing dep from application, since that's kinda the whole point * cleanup: add more missing event_processing:data_provider deps
1 parent 2d447e4 commit 61df080

File tree

9 files changed

+529
-457
lines changed

9 files changed

+529
-457
lines changed

tensorboard/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ py_library(
167167
"//tensorboard:expect_absl_flags_installed",
168168
"//tensorboard:expect_absl_logging_installed",
169169
"//tensorboard/backend:application",
170+
"//tensorboard/backend/event_processing:data_ingester",
170171
"//tensorboard/backend/event_processing:event_file_inspector",
171172
"//tensorboard/util:argparse_util",
172173
"@org_pocoo_werkzeug",
@@ -189,6 +190,7 @@ py_test(
189190
"//tensorboard/plugins/core:core_plugin",
190191
"@org_pocoo_werkzeug",
191192
"@org_pythonhosted_mock",
193+
"@org_pythonhosted_six",
192194
],
193195
)
194196

tensorboard/backend/BUILD

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,7 @@ py_library(
6565
":security_validator",
6666
"//tensorboard:errors",
6767
"//tensorboard:plugin_util",
68-
"//tensorboard/backend/event_processing:data_provider",
69-
"//tensorboard/backend/event_processing:event_multiplexer",
70-
"//tensorboard/backend/event_processing:tag_types",
7168
"//tensorboard/plugins/core:core_plugin",
72-
"//tensorboard/plugins/histogram:metadata",
73-
"//tensorboard/plugins/image:metadata",
74-
"//tensorboard/plugins/pr_curve:metadata",
75-
"//tensorboard/plugins/scalar:metadata",
7669
"//tensorboard/util:tb_logging",
7770
"@org_pocoo_werkzeug",
7871
"@org_pythonhosted_six",
@@ -83,7 +76,7 @@ py_test(
8376
name = "application_test",
8477
size = "small",
8578
srcs = ["application_test.py"],
86-
srcs_version = "PY2AND3",
79+
srcs_version = "PY3",
8780
tags = ["support_notf"],
8881
deps = [
8982
":application",

tensorboard/backend/application.py

Lines changed: 38 additions & 258 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,12 @@
1717
Provides TensorBoardWSGIApp for building a TensorBoard WSGI app.
1818
"""
1919

20-
from __future__ import absolute_import
21-
from __future__ import division
22-
from __future__ import print_function
23-
24-
import atexit
2520
import base64
2621
import collections
27-
import contextlib
2822
import hashlib
2923
import json
30-
import os
3124
import re
32-
import shutil
33-
import tempfile
3425
import textwrap
35-
import threading
3626
import time
3727

3828
import six
@@ -50,37 +40,11 @@
5040
from tensorboard.backend import http_util
5141
from tensorboard.backend import path_prefix
5242
from tensorboard.backend import security_validator
53-
from tensorboard.backend.event_processing import (
54-
data_provider as event_data_provider,
55-
)
56-
from tensorboard.backend.event_processing import (
57-
plugin_event_multiplexer as event_multiplexer,
58-
)
59-
from tensorboard.backend.event_processing import tag_types
6043
from tensorboard.plugins import base_plugin
61-
from tensorboard.plugins.audio import metadata as audio_metadata
6244
from tensorboard.plugins.core import core_plugin
63-
from tensorboard.plugins.histogram import metadata as histogram_metadata
64-
from tensorboard.plugins.image import metadata as image_metadata
65-
from tensorboard.plugins.pr_curve import metadata as pr_curve_metadata
66-
from tensorboard.plugins.scalar import metadata as scalar_metadata
6745
from tensorboard.util import tb_logging
6846

6947

70-
DEFAULT_SIZE_GUIDANCE = {
71-
tag_types.TENSORS: 10,
72-
}
73-
74-
# TODO(@wchargin): Once SQL mode is in play, replace this with an
75-
# alternative that does not privilege first-party plugins.
76-
DEFAULT_TENSOR_SIZE_GUIDANCE = {
77-
scalar_metadata.PLUGIN_NAME: 1000,
78-
image_metadata.PLUGIN_NAME: 10,
79-
audio_metadata.PLUGIN_NAME: 10,
80-
histogram_metadata.PLUGIN_NAME: 500,
81-
pr_curve_metadata.PLUGIN_NAME: 100,
82-
}
83-
8448
DATA_PREFIX = "/data"
8549
PLUGIN_PREFIX = "/plugin"
8650
PLUGINS_LISTING_ROUTE = "/plugins_listing"
@@ -96,75 +60,6 @@
9660
logger = tb_logging.get_logger()
9761

9862

99-
def _apply_tensor_size_guidance(sampling_hints):
100-
"""Apply user per-summary size guidance overrides."""
101-
tensor_size_guidance = dict(DEFAULT_TENSOR_SIZE_GUIDANCE)
102-
tensor_size_guidance.update(sampling_hints)
103-
return tensor_size_guidance
104-
105-
106-
def standard_tensorboard_wsgi(flags, plugin_loaders, assets_zip_provider):
107-
"""Construct a TensorBoardWSGIApp with standard plugins and multiplexer.
108-
109-
Args:
110-
flags: An argparse.Namespace containing TensorBoard CLI flags.
111-
plugin_loaders: A list of TBLoader instances.
112-
assets_zip_provider: See TBContext documentation for more information.
113-
114-
Returns:
115-
The new TensorBoard WSGI application.
116-
117-
:type plugin_loaders: list[base_plugin.TBLoader]
118-
:rtype: TensorBoardWSGI
119-
"""
120-
data_provider = None
121-
multiplexer = None
122-
reload_interval = flags.reload_interval
123-
# Regular logdir loading mode.
124-
sampling_hints = flags.samples_per_plugin
125-
multiplexer = event_multiplexer.EventMultiplexer(
126-
size_guidance=DEFAULT_SIZE_GUIDANCE,
127-
tensor_size_guidance=_apply_tensor_size_guidance(sampling_hints),
128-
purge_orphaned_data=flags.purge_orphaned_data,
129-
max_reload_threads=flags.max_reload_threads,
130-
event_file_active_filter=_get_event_file_active_filter(flags),
131-
)
132-
data_provider = event_data_provider.MultiplexerDataProvider(
133-
multiplexer, flags.logdir or flags.logdir_spec
134-
)
135-
136-
if reload_interval >= 0:
137-
# We either reload the multiplexer once when TensorBoard starts up, or we
138-
# continuously reload the multiplexer.
139-
if flags.logdir:
140-
path_to_run = {os.path.expanduser(flags.logdir): None}
141-
else:
142-
path_to_run = parse_event_files_spec(flags.logdir_spec)
143-
start_reloading_multiplexer(
144-
multiplexer, path_to_run, reload_interval, flags.reload_task
145-
)
146-
return TensorBoardWSGIApp(
147-
flags, plugin_loaders, data_provider, assets_zip_provider, multiplexer
148-
)
149-
150-
151-
def _handling_errors(wsgi_app):
152-
def wrapper(*args):
153-
(environ, start_response) = (args[-2], args[-1])
154-
try:
155-
return wsgi_app(*args)
156-
except errors.PublicError as e:
157-
request = wrappers.Request(environ)
158-
error_app = http_util.Respond(
159-
request, str(e), "text/plain", code=e.http_code
160-
)
161-
return error_app(environ, start_response)
162-
# Let other exceptions be handled by the server, as an opaque
163-
# internal server error.
164-
165-
return wrapper
166-
167-
16863
def TensorBoardWSGIApp(
16964
flags,
17065
plugins,
@@ -227,6 +122,30 @@ def TensorBoardWSGIApp(
227122
)
228123

229124

125+
def make_plugin_loader(plugin_spec):
126+
"""Returns a plugin loader for the given plugin.
127+
128+
Args:
129+
plugin_spec: A TBPlugin subclass, or a TBLoader instance or subclass.
130+
131+
Returns:
132+
A TBLoader for the given plugin.
133+
134+
:type plugin_spec:
135+
Type[base_plugin.TBPlugin] | Type[base_plugin.TBLoader] |
136+
base_plugin.TBLoader
137+
:rtype: base_plugin.TBLoader
138+
"""
139+
if isinstance(plugin_spec, base_plugin.TBLoader):
140+
return plugin_spec
141+
if isinstance(plugin_spec, type):
142+
if issubclass(plugin_spec, base_plugin.TBLoader):
143+
return plugin_spec()
144+
if issubclass(plugin_spec, base_plugin.TBPlugin):
145+
return base_plugin.BasicLoader(plugin_spec)
146+
raise TypeError("Not a TBLoader or TBPlugin subclass: %r" % (plugin_spec,))
147+
148+
230149
class TensorBoardWSGI(object):
231150
"""The TensorBoard WSGI app that delegates to a set of TBPlugin."""
232151

@@ -598,119 +517,21 @@ def _route_request(self, environ, start_response):
598517
# pylint: enable=too-many-function-args
599518

600519

601-
def parse_event_files_spec(logdir_spec):
602-
"""Parses `logdir_spec` into a map from paths to run group names.
603-
604-
The `--logdir_spec` flag format is a comma-separated list of path
605-
specifications. A path spec looks like 'group_name:/path/to/directory' or
606-
'/path/to/directory'; in the latter case, the group is unnamed. Group names
607-
cannot start with a forward slash: /foo:bar/baz will be interpreted as a spec
608-
with no name and path '/foo:bar/baz'.
609-
610-
Globs are not supported.
611-
612-
Args:
613-
logdir: A comma-separated list of run specifications.
614-
Returns:
615-
A dict mapping directory paths to names like {'/path/to/directory': 'name'}.
616-
Groups without an explicit name are named after their path. If logdir is
617-
None, returns an empty dict, which is helpful for testing things that don't
618-
require any valid runs.
619-
"""
620-
files = {}
621-
if logdir_spec is None:
622-
return files
623-
# Make sure keeping consistent with ParseURI in core/lib/io/path.cc
624-
uri_pattern = re.compile("[a-zA-Z][0-9a-zA-Z.]*://.*")
625-
for specification in logdir_spec.split(","):
626-
# Check if the spec contains group. A spec start with xyz:// is regarded as
627-
# URI path spec instead of group spec. If the spec looks like /foo:bar/baz,
628-
# then we assume it's a path with a colon. If the spec looks like
629-
# [a-zA-z]:\foo then we assume its a Windows path and not a single letter
630-
# group
631-
if (
632-
uri_pattern.match(specification) is None
633-
and ":" in specification
634-
and specification[0] != "/"
635-
and not os.path.splitdrive(specification)[0]
636-
):
637-
# We split at most once so run_name:/path:with/a/colon will work.
638-
run_name, _, path = specification.partition(":")
639-
else:
640-
run_name = None
641-
path = specification
642-
if uri_pattern.match(path) is None:
643-
path = os.path.realpath(os.path.expanduser(path))
644-
files[path] = run_name
645-
return files
646-
647-
648-
def start_reloading_multiplexer(
649-
multiplexer, path_to_run, load_interval, reload_task
650-
):
651-
"""Starts automatically reloading the given multiplexer.
652-
653-
If `load_interval` is positive, the thread will reload the multiplexer
654-
by calling `ReloadMultiplexer` every `load_interval` seconds, starting
655-
immediately. Otherwise, reloads the multiplexer once and never again.
656-
657-
Args:
658-
multiplexer: The `EventMultiplexer` to add runs to and reload.
659-
path_to_run: A dict mapping from paths to run names, where `None` as the run
660-
name is interpreted as a run name equal to the path.
661-
load_interval: An integer greater than or equal to 0. If positive, how many
662-
seconds to wait after one load before starting the next load. Otherwise,
663-
reloads the multiplexer once and never again (no continuous reloading).
664-
reload_task: Indicates the type of background task to reload with.
665-
666-
Raises:
667-
ValueError: If `load_interval` is negative.
668-
"""
669-
if load_interval < 0:
670-
raise ValueError("load_interval is negative: %d" % load_interval)
671-
672-
def _reload():
673-
while True:
674-
start = time.time()
675-
logger.info("TensorBoard reload process beginning")
676-
for path, name in six.iteritems(path_to_run):
677-
multiplexer.AddRunsFromDirectory(path, name)
678-
logger.info(
679-
"TensorBoard reload process: Reload the whole Multiplexer"
680-
)
681-
multiplexer.Reload()
682-
duration = time.time() - start
683-
logger.info(
684-
"TensorBoard done reloading. Load took %0.3f secs", duration
685-
)
686-
if load_interval == 0:
687-
# Only load the multiplexer once. Do not continuously reload.
688-
break
689-
time.sleep(load_interval)
690-
691-
if reload_task == "process":
692-
logger.info("Launching reload in a child process")
693-
import multiprocessing
694-
695-
process = multiprocessing.Process(target=_reload, name="Reloader")
696-
# Best-effort cleanup; on exit, the main TB parent process will attempt to
697-
# kill all its daemonic children.
698-
process.daemon = True
699-
process.start()
700-
elif reload_task in ("thread", "auto"):
701-
logger.info("Launching reload in a daemon thread")
702-
thread = threading.Thread(target=_reload, name="Reloader")
703-
# Make this a daemon thread, which won't block TB from exiting.
704-
thread.daemon = True
705-
thread.start()
706-
elif reload_task == "blocking":
707-
if load_interval != 0:
708-
raise ValueError(
709-
"blocking reload only allowed with load_interval=0"
520+
def _handling_errors(wsgi_app):
521+
def wrapper(*args):
522+
(environ, start_response) = (args[-2], args[-1])
523+
try:
524+
return wsgi_app(*args)
525+
except errors.PublicError as e:
526+
request = wrappers.Request(environ)
527+
error_app = http_util.Respond(
528+
request, str(e), "text/plain", code=e.http_code
710529
)
711-
_reload()
712-
else:
713-
raise ValueError("unrecognized reload_task: %s" % reload_task)
530+
return error_app(environ, start_response)
531+
# Let other exceptions be handled by the server, as an opaque
532+
# internal server error.
533+
534+
return wrapper
714535

715536

716537
def _clean_path(path):
@@ -725,44 +546,3 @@ def _clean_path(path):
725546
if path != "/" and path.endswith("/"):
726547
return path[:-1]
727548
return path
728-
729-
730-
def _get_event_file_active_filter(flags):
731-
"""Returns a predicate for whether an event file load timestamp is active.
732-
733-
Returns:
734-
A predicate function accepting a single UNIX timestamp float argument, or
735-
None if multi-file loading is not enabled.
736-
"""
737-
if not flags.reload_multifile:
738-
return None
739-
inactive_secs = flags.reload_multifile_inactive_secs
740-
if inactive_secs == 0:
741-
return None
742-
if inactive_secs < 0:
743-
return lambda timestamp: True
744-
return lambda timestamp: timestamp + inactive_secs >= time.time()
745-
746-
747-
def make_plugin_loader(plugin_spec):
748-
"""Returns a plugin loader for the given plugin.
749-
750-
Args:
751-
plugin_spec: A TBPlugin subclass, or a TBLoader instance or subclass.
752-
753-
Returns:
754-
A TBLoader for the given plugin.
755-
756-
:type plugin_spec:
757-
Type[base_plugin.TBPlugin] | Type[base_plugin.TBLoader] |
758-
base_plugin.TBLoader
759-
:rtype: base_plugin.TBLoader
760-
"""
761-
if isinstance(plugin_spec, base_plugin.TBLoader):
762-
return plugin_spec
763-
if isinstance(plugin_spec, type):
764-
if issubclass(plugin_spec, base_plugin.TBLoader):
765-
return plugin_spec()
766-
if issubclass(plugin_spec, base_plugin.TBPlugin):
767-
return base_plugin.BasicLoader(plugin_spec)
768-
raise TypeError("Not a TBLoader or TBPlugin subclass: %r" % (plugin_spec,))

0 commit comments

Comments
 (0)