1717Provides 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
2520import base64
2621import collections
27- import contextlib
2822import hashlib
2923import json
30- import os
3124import re
32- import shutil
33- import tempfile
3425import textwrap
35- import threading
3626import time
3727
3828import six
5040from tensorboard .backend import http_util
5141from tensorboard .backend import path_prefix
5242from 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
6043from tensorboard .plugins import base_plugin
61- from tensorboard .plugins .audio import metadata as audio_metadata
6244from 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
6745from 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-
8448DATA_PREFIX = "/data"
8549PLUGIN_PREFIX = "/plugin"
8650PLUGINS_LISTING_ROUTE = "/plugins_listing"
9660logger = 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-
16863def 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+
230149class 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
716537def _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