tensorbored 2.21.0rc1769983804__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tensorbored/__init__.py +112 -0
- tensorbored/_vendor/__init__.py +0 -0
- tensorbored/_vendor/bleach/__init__.py +125 -0
- tensorbored/_vendor/bleach/_vendor/__init__.py +0 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/__init__.py +35 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/_ihatexml.py +289 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/_inputstream.py +918 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/_tokenizer.py +1735 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/_trie/__init__.py +5 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/_trie/_base.py +40 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/_trie/py.py +67 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/_utils.py +159 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/constants.py +2946 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/__init__.py +0 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/alphabeticalattributes.py +29 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/base.py +12 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/inject_meta_charset.py +73 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/lint.py +93 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/optionaltags.py +207 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/sanitizer.py +916 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/filters/whitespace.py +38 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/html5parser.py +2795 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/serializer.py +409 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/__init__.py +30 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/genshi.py +54 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/sax.py +50 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/__init__.py +88 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/base.py +417 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/dom.py +239 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/etree.py +343 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/etree_lxml.py +392 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/__init__.py +154 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/base.py +252 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/dom.py +43 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/etree.py +131 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/etree_lxml.py +215 -0
- tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/genshi.py +69 -0
- tensorbored/_vendor/bleach/_vendor/parse.py +1078 -0
- tensorbored/_vendor/bleach/callbacks.py +32 -0
- tensorbored/_vendor/bleach/html5lib_shim.py +757 -0
- tensorbored/_vendor/bleach/linkifier.py +633 -0
- tensorbored/_vendor/bleach/parse_shim.py +1 -0
- tensorbored/_vendor/bleach/sanitizer.py +638 -0
- tensorbored/_vendor/bleach/six_shim.py +19 -0
- tensorbored/_vendor/webencodings/__init__.py +342 -0
- tensorbored/_vendor/webencodings/labels.py +231 -0
- tensorbored/_vendor/webencodings/mklabels.py +59 -0
- tensorbored/_vendor/webencodings/x_user_defined.py +325 -0
- tensorbored/assets.py +36 -0
- tensorbored/auth.py +102 -0
- tensorbored/backend/__init__.py +0 -0
- tensorbored/backend/application.py +604 -0
- tensorbored/backend/auth_context_middleware.py +38 -0
- tensorbored/backend/client_feature_flags.py +113 -0
- tensorbored/backend/empty_path_redirect.py +46 -0
- tensorbored/backend/event_processing/__init__.py +0 -0
- tensorbored/backend/event_processing/data_ingester.py +276 -0
- tensorbored/backend/event_processing/data_provider.py +535 -0
- tensorbored/backend/event_processing/directory_loader.py +142 -0
- tensorbored/backend/event_processing/directory_watcher.py +272 -0
- tensorbored/backend/event_processing/event_accumulator.py +950 -0
- tensorbored/backend/event_processing/event_file_inspector.py +463 -0
- tensorbored/backend/event_processing/event_file_loader.py +292 -0
- tensorbored/backend/event_processing/event_multiplexer.py +521 -0
- tensorbored/backend/event_processing/event_util.py +68 -0
- tensorbored/backend/event_processing/io_wrapper.py +223 -0
- tensorbored/backend/event_processing/plugin_asset_util.py +104 -0
- tensorbored/backend/event_processing/plugin_event_accumulator.py +721 -0
- tensorbored/backend/event_processing/plugin_event_multiplexer.py +522 -0
- tensorbored/backend/event_processing/reservoir.py +266 -0
- tensorbored/backend/event_processing/tag_types.py +29 -0
- tensorbored/backend/experiment_id.py +71 -0
- tensorbored/backend/experimental_plugin.py +51 -0
- tensorbored/backend/http_util.py +263 -0
- tensorbored/backend/json_util.py +70 -0
- tensorbored/backend/path_prefix.py +67 -0
- tensorbored/backend/process_graph.py +74 -0
- tensorbored/backend/security_validator.py +202 -0
- tensorbored/compat/__init__.py +69 -0
- tensorbored/compat/proto/__init__.py +0 -0
- tensorbored/compat/proto/allocation_description_pb2.py +35 -0
- tensorbored/compat/proto/api_def_pb2.py +82 -0
- tensorbored/compat/proto/attr_value_pb2.py +80 -0
- tensorbored/compat/proto/cluster_pb2.py +58 -0
- tensorbored/compat/proto/config_pb2.py +271 -0
- tensorbored/compat/proto/coordination_config_pb2.py +45 -0
- tensorbored/compat/proto/cost_graph_pb2.py +87 -0
- tensorbored/compat/proto/cpp_shape_inference_pb2.py +70 -0
- tensorbored/compat/proto/debug_pb2.py +65 -0
- tensorbored/compat/proto/event_pb2.py +149 -0
- tensorbored/compat/proto/full_type_pb2.py +74 -0
- tensorbored/compat/proto/function_pb2.py +157 -0
- tensorbored/compat/proto/graph_debug_info_pb2.py +111 -0
- tensorbored/compat/proto/graph_pb2.py +41 -0
- tensorbored/compat/proto/histogram_pb2.py +39 -0
- tensorbored/compat/proto/meta_graph_pb2.py +254 -0
- tensorbored/compat/proto/node_def_pb2.py +61 -0
- tensorbored/compat/proto/op_def_pb2.py +81 -0
- tensorbored/compat/proto/resource_handle_pb2.py +48 -0
- tensorbored/compat/proto/rewriter_config_pb2.py +93 -0
- tensorbored/compat/proto/rpc_options_pb2.py +35 -0
- tensorbored/compat/proto/saved_object_graph_pb2.py +193 -0
- tensorbored/compat/proto/saver_pb2.py +38 -0
- tensorbored/compat/proto/step_stats_pb2.py +116 -0
- tensorbored/compat/proto/struct_pb2.py +144 -0
- tensorbored/compat/proto/summary_pb2.py +111 -0
- tensorbored/compat/proto/tensor_description_pb2.py +38 -0
- tensorbored/compat/proto/tensor_pb2.py +68 -0
- tensorbored/compat/proto/tensor_shape_pb2.py +46 -0
- tensorbored/compat/proto/tfprof_log_pb2.py +307 -0
- tensorbored/compat/proto/trackable_object_graph_pb2.py +90 -0
- tensorbored/compat/proto/types_pb2.py +105 -0
- tensorbored/compat/proto/variable_pb2.py +62 -0
- tensorbored/compat/proto/verifier_config_pb2.py +38 -0
- tensorbored/compat/proto/versions_pb2.py +35 -0
- tensorbored/compat/tensorflow_stub/__init__.py +38 -0
- tensorbored/compat/tensorflow_stub/app.py +124 -0
- tensorbored/compat/tensorflow_stub/compat/__init__.py +131 -0
- tensorbored/compat/tensorflow_stub/compat/v1/__init__.py +20 -0
- tensorbored/compat/tensorflow_stub/dtypes.py +692 -0
- tensorbored/compat/tensorflow_stub/error_codes.py +169 -0
- tensorbored/compat/tensorflow_stub/errors.py +507 -0
- tensorbored/compat/tensorflow_stub/flags.py +124 -0
- tensorbored/compat/tensorflow_stub/io/__init__.py +17 -0
- tensorbored/compat/tensorflow_stub/io/gfile.py +1011 -0
- tensorbored/compat/tensorflow_stub/pywrap_tensorflow.py +285 -0
- tensorbored/compat/tensorflow_stub/tensor_shape.py +1035 -0
- tensorbored/context.py +129 -0
- tensorbored/data/__init__.py +0 -0
- tensorbored/data/grpc_provider.py +365 -0
- tensorbored/data/ingester.py +46 -0
- tensorbored/data/proto/__init__.py +0 -0
- tensorbored/data/proto/data_provider_pb2.py +517 -0
- tensorbored/data/proto/data_provider_pb2_grpc.py +374 -0
- tensorbored/data/provider.py +1365 -0
- tensorbored/data/server_ingester.py +301 -0
- tensorbored/data_compat.py +159 -0
- tensorbored/dataclass_compat.py +224 -0
- tensorbored/default.py +124 -0
- tensorbored/errors.py +130 -0
- tensorbored/lazy.py +99 -0
- tensorbored/main.py +48 -0
- tensorbored/main_lib.py +62 -0
- tensorbored/manager.py +487 -0
- tensorbored/notebook.py +441 -0
- tensorbored/plugin_util.py +266 -0
- tensorbored/plugins/__init__.py +0 -0
- tensorbored/plugins/audio/__init__.py +0 -0
- tensorbored/plugins/audio/audio_plugin.py +229 -0
- tensorbored/plugins/audio/metadata.py +69 -0
- tensorbored/plugins/audio/plugin_data_pb2.py +37 -0
- tensorbored/plugins/audio/summary.py +230 -0
- tensorbored/plugins/audio/summary_v2.py +124 -0
- tensorbored/plugins/base_plugin.py +367 -0
- tensorbored/plugins/core/__init__.py +0 -0
- tensorbored/plugins/core/core_plugin.py +981 -0
- tensorbored/plugins/custom_scalar/__init__.py +0 -0
- tensorbored/plugins/custom_scalar/custom_scalars_plugin.py +320 -0
- tensorbored/plugins/custom_scalar/layout_pb2.py +85 -0
- tensorbored/plugins/custom_scalar/metadata.py +35 -0
- tensorbored/plugins/custom_scalar/summary.py +79 -0
- tensorbored/plugins/debugger_v2/__init__.py +0 -0
- tensorbored/plugins/debugger_v2/debug_data_multiplexer.py +631 -0
- tensorbored/plugins/debugger_v2/debug_data_provider.py +634 -0
- tensorbored/plugins/debugger_v2/debugger_v2_plugin.py +504 -0
- tensorbored/plugins/distribution/__init__.py +0 -0
- tensorbored/plugins/distribution/compressor.py +158 -0
- tensorbored/plugins/distribution/distributions_plugin.py +116 -0
- tensorbored/plugins/distribution/metadata.py +19 -0
- tensorbored/plugins/graph/__init__.py +0 -0
- tensorbored/plugins/graph/graph_util.py +129 -0
- tensorbored/plugins/graph/graphs_plugin.py +336 -0
- tensorbored/plugins/graph/keras_util.py +328 -0
- tensorbored/plugins/graph/metadata.py +42 -0
- tensorbored/plugins/histogram/__init__.py +0 -0
- tensorbored/plugins/histogram/histograms_plugin.py +144 -0
- tensorbored/plugins/histogram/metadata.py +63 -0
- tensorbored/plugins/histogram/plugin_data_pb2.py +34 -0
- tensorbored/plugins/histogram/summary.py +234 -0
- tensorbored/plugins/histogram/summary_v2.py +292 -0
- tensorbored/plugins/hparams/__init__.py +14 -0
- tensorbored/plugins/hparams/_keras.py +93 -0
- tensorbored/plugins/hparams/api.py +130 -0
- tensorbored/plugins/hparams/api_pb2.py +208 -0
- tensorbored/plugins/hparams/backend_context.py +606 -0
- tensorbored/plugins/hparams/download_data.py +158 -0
- tensorbored/plugins/hparams/error.py +26 -0
- tensorbored/plugins/hparams/get_experiment.py +71 -0
- tensorbored/plugins/hparams/hparams_plugin.py +206 -0
- tensorbored/plugins/hparams/hparams_util_pb2.py +69 -0
- tensorbored/plugins/hparams/json_format_compat.py +38 -0
- tensorbored/plugins/hparams/list_metric_evals.py +57 -0
- tensorbored/plugins/hparams/list_session_groups.py +1040 -0
- tensorbored/plugins/hparams/metadata.py +125 -0
- tensorbored/plugins/hparams/metrics.py +41 -0
- tensorbored/plugins/hparams/plugin_data_pb2.py +69 -0
- tensorbored/plugins/hparams/summary.py +205 -0
- tensorbored/plugins/hparams/summary_v2.py +597 -0
- tensorbored/plugins/image/__init__.py +0 -0
- tensorbored/plugins/image/images_plugin.py +232 -0
- tensorbored/plugins/image/metadata.py +65 -0
- tensorbored/plugins/image/plugin_data_pb2.py +34 -0
- tensorbored/plugins/image/summary.py +159 -0
- tensorbored/plugins/image/summary_v2.py +130 -0
- tensorbored/plugins/mesh/__init__.py +14 -0
- tensorbored/plugins/mesh/mesh_plugin.py +292 -0
- tensorbored/plugins/mesh/metadata.py +152 -0
- tensorbored/plugins/mesh/plugin_data_pb2.py +37 -0
- tensorbored/plugins/mesh/summary.py +251 -0
- tensorbored/plugins/mesh/summary_v2.py +214 -0
- tensorbored/plugins/metrics/__init__.py +0 -0
- tensorbored/plugins/metrics/metadata.py +17 -0
- tensorbored/plugins/metrics/metrics_plugin.py +623 -0
- tensorbored/plugins/pr_curve/__init__.py +0 -0
- tensorbored/plugins/pr_curve/metadata.py +75 -0
- tensorbored/plugins/pr_curve/plugin_data_pb2.py +34 -0
- tensorbored/plugins/pr_curve/pr_curves_plugin.py +241 -0
- tensorbored/plugins/pr_curve/summary.py +574 -0
- tensorbored/plugins/profile_redirect/__init__.py +0 -0
- tensorbored/plugins/profile_redirect/profile_redirect_plugin.py +49 -0
- tensorbored/plugins/projector/__init__.py +67 -0
- tensorbored/plugins/projector/metadata.py +26 -0
- tensorbored/plugins/projector/projector_config_pb2.py +54 -0
- tensorbored/plugins/projector/projector_plugin.py +795 -0
- tensorbored/plugins/projector/tf_projector_plugin/index.js +32 -0
- tensorbored/plugins/projector/tf_projector_plugin/projector_binary.html +524 -0
- tensorbored/plugins/projector/tf_projector_plugin/projector_binary.js +15536 -0
- tensorbored/plugins/scalar/__init__.py +0 -0
- tensorbored/plugins/scalar/metadata.py +60 -0
- tensorbored/plugins/scalar/plugin_data_pb2.py +34 -0
- tensorbored/plugins/scalar/scalars_plugin.py +181 -0
- tensorbored/plugins/scalar/summary.py +109 -0
- tensorbored/plugins/scalar/summary_v2.py +124 -0
- tensorbored/plugins/text/__init__.py +0 -0
- tensorbored/plugins/text/metadata.py +62 -0
- tensorbored/plugins/text/plugin_data_pb2.py +34 -0
- tensorbored/plugins/text/summary.py +114 -0
- tensorbored/plugins/text/summary_v2.py +124 -0
- tensorbored/plugins/text/text_plugin.py +288 -0
- tensorbored/plugins/wit_redirect/__init__.py +0 -0
- tensorbored/plugins/wit_redirect/wit_redirect_plugin.py +49 -0
- tensorbored/program.py +910 -0
- tensorbored/summary/__init__.py +35 -0
- tensorbored/summary/_output.py +124 -0
- tensorbored/summary/_tf/__init__.py +14 -0
- tensorbored/summary/_tf/summary/__init__.py +178 -0
- tensorbored/summary/_writer.py +105 -0
- tensorbored/summary/v1.py +51 -0
- tensorbored/summary/v2.py +25 -0
- tensorbored/summary/writer/__init__.py +13 -0
- tensorbored/summary/writer/event_file_writer.py +291 -0
- tensorbored/summary/writer/record_writer.py +50 -0
- tensorbored/util/__init__.py +0 -0
- tensorbored/util/encoder.py +116 -0
- tensorbored/util/grpc_util.py +311 -0
- tensorbored/util/img_mime_type_detector.py +40 -0
- tensorbored/util/io_util.py +20 -0
- tensorbored/util/lazy_tensor_creator.py +110 -0
- tensorbored/util/op_evaluator.py +104 -0
- tensorbored/util/platform_util.py +20 -0
- tensorbored/util/tb_logging.py +24 -0
- tensorbored/util/tensor_util.py +617 -0
- tensorbored/util/timing.py +122 -0
- tensorbored/version.py +21 -0
- tensorbored/webfiles.zip +0 -0
- tensorbored-2.21.0rc1769983804.dist-info/METADATA +49 -0
- tensorbored-2.21.0rc1769983804.dist-info/RECORD +271 -0
- tensorbored-2.21.0rc1769983804.dist-info/WHEEL +5 -0
- tensorbored-2.21.0rc1769983804.dist-info/entry_points.txt +6 -0
- tensorbored-2.21.0rc1769983804.dist-info/licenses/LICENSE +739 -0
- tensorbored-2.21.0rc1769983804.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""TensorBoard core plugin package."""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import functools
|
|
19
|
+
import gzip
|
|
20
|
+
import io
|
|
21
|
+
import json
|
|
22
|
+
import mimetypes
|
|
23
|
+
import os
|
|
24
|
+
import posixpath
|
|
25
|
+
import re
|
|
26
|
+
import zipfile
|
|
27
|
+
|
|
28
|
+
from werkzeug import utils
|
|
29
|
+
from werkzeug import wrappers
|
|
30
|
+
|
|
31
|
+
from tensorbored import plugin_util
|
|
32
|
+
from tensorbored.backend import http_util
|
|
33
|
+
from tensorbored.plugins import base_plugin
|
|
34
|
+
from tensorbored.util import grpc_util
|
|
35
|
+
from tensorbored.util import tb_logging
|
|
36
|
+
from tensorbored import version
|
|
37
|
+
|
|
38
|
+
logger = tb_logging.get_logger()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# If no port is specified, try to bind to this port. See help for --port
|
|
42
|
+
# for more details.
|
|
43
|
+
DEFAULT_PORT = 6006
|
|
44
|
+
# Valid javascript mimetypes that we have seen configured, in practice.
|
|
45
|
+
# Historically (~2020-2022) we saw "application/javascript" exclusively but with
|
|
46
|
+
# RFC 9239 (https://www.rfc-editor.org/rfc/rfc9239) we saw some systems quickly
|
|
47
|
+
# transition to 'text/javascript'.
|
|
48
|
+
JS_MIMETYPES = ["text/javascript", "application/javascript"]
|
|
49
|
+
JS_CACHE_EXPIRATION_IN_SECS = 86400
|
|
50
|
+
|
|
51
|
+
_RUN_COLOR_HEX_RE = re.compile(r"^#[0-9a-fA-F]{6}$")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CorePlugin(base_plugin.TBPlugin):
|
|
55
|
+
"""Core plugin for TensorBoard.
|
|
56
|
+
|
|
57
|
+
This plugin serves runs, configuration data, and static assets. This
|
|
58
|
+
plugin should always be present in a TensorBoard WSGI application.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
plugin_name = "core"
|
|
62
|
+
|
|
63
|
+
def __init__(self, context, include_debug_info=None):
|
|
64
|
+
"""Instantiates CorePlugin.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
context: A base_plugin.TBContext instance.
|
|
68
|
+
include_debug_info: If true, `/data/environment` will include some
|
|
69
|
+
basic information like the TensorBoard server version. Disabled by
|
|
70
|
+
default to prevent surprising information leaks in custom builds of
|
|
71
|
+
TensorBoard.
|
|
72
|
+
"""
|
|
73
|
+
self._flags = context.flags
|
|
74
|
+
logdir_spec = context.flags.logdir_spec if context.flags else ""
|
|
75
|
+
self._logdir = context.logdir or logdir_spec
|
|
76
|
+
self._window_title = context.window_title
|
|
77
|
+
self._path_prefix = context.flags.path_prefix if context.flags else None
|
|
78
|
+
self._assets_zip_provider = context.assets_zip_provider
|
|
79
|
+
self._data_provider = context.data_provider
|
|
80
|
+
self._include_debug_info = bool(include_debug_info)
|
|
81
|
+
|
|
82
|
+
def is_active(self):
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
def get_plugin_apps(self):
|
|
86
|
+
apps = {
|
|
87
|
+
"/___rPc_sWiTcH___": self._send_404_without_logging,
|
|
88
|
+
"/audio": self._redirect_to_index,
|
|
89
|
+
"/data/environment": self._serve_environment,
|
|
90
|
+
"/data/logdir": self._serve_logdir,
|
|
91
|
+
"/data/runs": self._serve_runs,
|
|
92
|
+
"/data/run_colors": self._serve_run_colors,
|
|
93
|
+
"/data/profile": self._serve_profile,
|
|
94
|
+
"/data/experiments": self._serve_experiments,
|
|
95
|
+
"/data/experiment_runs": self._serve_experiment_runs,
|
|
96
|
+
"/data/notifications": self._serve_notifications,
|
|
97
|
+
"/data/window_properties": self._serve_window_properties,
|
|
98
|
+
"/events": self._redirect_to_index,
|
|
99
|
+
"/favicon.ico": self._send_404_without_logging,
|
|
100
|
+
"/graphs": self._redirect_to_index,
|
|
101
|
+
"/histograms": self._redirect_to_index,
|
|
102
|
+
"/images": self._redirect_to_index,
|
|
103
|
+
}
|
|
104
|
+
apps.update(self.get_resource_apps())
|
|
105
|
+
return apps
|
|
106
|
+
|
|
107
|
+
def get_resource_apps(self):
|
|
108
|
+
apps = {}
|
|
109
|
+
if not self._assets_zip_provider:
|
|
110
|
+
return apps
|
|
111
|
+
|
|
112
|
+
with self._assets_zip_provider() as fp:
|
|
113
|
+
with zipfile.ZipFile(fp) as zip_:
|
|
114
|
+
for path in zip_.namelist():
|
|
115
|
+
content = zip_.read(path)
|
|
116
|
+
# Opt out of gzipping index.html
|
|
117
|
+
if path == "index.html":
|
|
118
|
+
apps["/" + path] = functools.partial(
|
|
119
|
+
self._serve_index, content
|
|
120
|
+
)
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
gzipped_asset_bytes = _gzip(content)
|
|
124
|
+
wsgi_app = functools.partial(
|
|
125
|
+
self._serve_asset, path, gzipped_asset_bytes
|
|
126
|
+
)
|
|
127
|
+
apps["/" + path] = wsgi_app
|
|
128
|
+
apps["/"] = apps["/index.html"]
|
|
129
|
+
return apps
|
|
130
|
+
|
|
131
|
+
@wrappers.Request.application
|
|
132
|
+
def _send_404_without_logging(self, request):
|
|
133
|
+
return http_util.Respond(request, "Not found", "text/plain", code=404)
|
|
134
|
+
|
|
135
|
+
@wrappers.Request.application
|
|
136
|
+
def _redirect_to_index(self, unused_request):
|
|
137
|
+
return utils.redirect("/")
|
|
138
|
+
|
|
139
|
+
@wrappers.Request.application
|
|
140
|
+
def _serve_asset(self, path, gzipped_asset_bytes, request):
|
|
141
|
+
"""Serves a pre-gzipped static asset from the zip file."""
|
|
142
|
+
mimetype = mimetypes.guess_type(path)[0] or "application/octet-stream"
|
|
143
|
+
|
|
144
|
+
# Cache JS resources while keep others do not cache.
|
|
145
|
+
expires = (
|
|
146
|
+
JS_CACHE_EXPIRATION_IN_SECS
|
|
147
|
+
if request.args.get("_file_hash") and mimetype in JS_MIMETYPES
|
|
148
|
+
else 0
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return http_util.Respond(
|
|
152
|
+
request,
|
|
153
|
+
gzipped_asset_bytes,
|
|
154
|
+
mimetype,
|
|
155
|
+
content_encoding="gzip",
|
|
156
|
+
expires=expires,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@wrappers.Request.application
|
|
160
|
+
def _serve_index(self, index_asset_bytes, request):
|
|
161
|
+
"""Serves index.html content.
|
|
162
|
+
|
|
163
|
+
Note that we opt out of gzipping index.html to write preamble before the
|
|
164
|
+
resource content. This inflates the resource size from 2x kiB to 1xx
|
|
165
|
+
kiB, but we require an ability to flush preamble with the HTML content.
|
|
166
|
+
"""
|
|
167
|
+
relpath = (
|
|
168
|
+
posixpath.relpath(self._path_prefix, request.script_root)
|
|
169
|
+
if self._path_prefix
|
|
170
|
+
else "."
|
|
171
|
+
)
|
|
172
|
+
meta_header = (
|
|
173
|
+
'<!doctype html><meta name="tb-relative-root" content="%s/">'
|
|
174
|
+
% relpath
|
|
175
|
+
)
|
|
176
|
+
content = meta_header.encode("utf-8") + index_asset_bytes
|
|
177
|
+
# By passing content_encoding, disallow gzipping. Bloats the content
|
|
178
|
+
# from ~25 kiB to ~120 kiB but reduces CPU usage and avoid 3ms worth of
|
|
179
|
+
# gzipping.
|
|
180
|
+
return http_util.Respond(
|
|
181
|
+
request, content, "text/html", content_encoding="identity"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@wrappers.Request.application
|
|
185
|
+
def _serve_environment(self, request):
|
|
186
|
+
"""Serve a JSON object describing the TensorBoard parameters."""
|
|
187
|
+
ctx = plugin_util.context(request.environ)
|
|
188
|
+
experiment = plugin_util.experiment_id(request.environ)
|
|
189
|
+
md = self._data_provider.experiment_metadata(
|
|
190
|
+
ctx, experiment_id=experiment
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
environment = {
|
|
194
|
+
"version": version.VERSION,
|
|
195
|
+
"data_location": md.data_location,
|
|
196
|
+
"window_title": self._window_title,
|
|
197
|
+
"experiment_name": md.experiment_name,
|
|
198
|
+
"experiment_description": md.experiment_description,
|
|
199
|
+
"creation_time": md.creation_time,
|
|
200
|
+
}
|
|
201
|
+
if self._include_debug_info:
|
|
202
|
+
environment["debug"] = {
|
|
203
|
+
"data_provider": str(self._data_provider),
|
|
204
|
+
"flags": self._render_flags(),
|
|
205
|
+
}
|
|
206
|
+
return http_util.Respond(
|
|
207
|
+
request,
|
|
208
|
+
environment,
|
|
209
|
+
"application/json",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def _render_flags(self):
|
|
213
|
+
"""Return a JSON-and-human-friendly version of `self._flags`.
|
|
214
|
+
|
|
215
|
+
Like `json.loads(json.dumps(self._flags, default=str))` but
|
|
216
|
+
without the wasteful serialization overhead.
|
|
217
|
+
"""
|
|
218
|
+
if self._flags is None:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
def go(x):
|
|
222
|
+
if isinstance(x, (type(None), str, int, float)):
|
|
223
|
+
return x
|
|
224
|
+
if isinstance(x, (list, tuple)):
|
|
225
|
+
return [go(v) for v in x]
|
|
226
|
+
if isinstance(x, dict):
|
|
227
|
+
return {str(k): go(v) for (k, v) in x.items()}
|
|
228
|
+
return str(x)
|
|
229
|
+
|
|
230
|
+
return go(vars(self._flags))
|
|
231
|
+
|
|
232
|
+
@wrappers.Request.application
|
|
233
|
+
def _serve_logdir(self, request):
|
|
234
|
+
"""Respond with a JSON object containing this TensorBoard's logdir."""
|
|
235
|
+
# TODO(chihuahua): Remove this method once the frontend instead uses the
|
|
236
|
+
# /data/environment route (and no deps throughout Google use the
|
|
237
|
+
# /data/logdir route).
|
|
238
|
+
return http_util.Respond(
|
|
239
|
+
request, {"logdir": self._logdir}, "application/json"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@wrappers.Request.application
|
|
243
|
+
def _serve_window_properties(self, request):
|
|
244
|
+
"""Serve a JSON object containing this TensorBoard's window
|
|
245
|
+
properties."""
|
|
246
|
+
# TODO(chihuahua): Remove this method once the frontend instead uses the
|
|
247
|
+
# /data/environment route.
|
|
248
|
+
return http_util.Respond(
|
|
249
|
+
request, {"window_title": self._window_title}, "application/json"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
@wrappers.Request.application
|
|
253
|
+
def _serve_runs(self, request):
|
|
254
|
+
"""Serve a JSON array of run names, ordered by run started time.
|
|
255
|
+
|
|
256
|
+
Sort order is by started time (aka first event time) with empty
|
|
257
|
+
times sorted last, and then ties are broken by sorting on the
|
|
258
|
+
run name.
|
|
259
|
+
"""
|
|
260
|
+
ctx = plugin_util.context(request.environ)
|
|
261
|
+
experiment = plugin_util.experiment_id(request.environ)
|
|
262
|
+
runs = sorted(
|
|
263
|
+
self._data_provider.list_runs(ctx, experiment_id=experiment),
|
|
264
|
+
key=lambda run: (
|
|
265
|
+
run.start_time if run.start_time is not None else float("inf"),
|
|
266
|
+
run.run_name,
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
run_names = [run.run_name for run in runs]
|
|
270
|
+
return http_util.Respond(request, run_names, "application/json")
|
|
271
|
+
|
|
272
|
+
def _run_colors_path(self):
|
|
273
|
+
"""Returns a path to the run color overrides file, or None if unavailable.
|
|
274
|
+
|
|
275
|
+
We only support writing when `--logdir` points to a local filesystem
|
|
276
|
+
directory. For `--logdir_spec` or non-local data providers, this is not
|
|
277
|
+
guaranteed.
|
|
278
|
+
"""
|
|
279
|
+
logdir = self._logdir or ""
|
|
280
|
+
# Basic guard against `--logdir_spec`/multi-logdir strings.
|
|
281
|
+
if not logdir or ("," in logdir) or (":" in logdir):
|
|
282
|
+
return None
|
|
283
|
+
if not os.path.isdir(logdir):
|
|
284
|
+
return None
|
|
285
|
+
return os.path.join(logdir, "runs", "run_colors.json")
|
|
286
|
+
|
|
287
|
+
def _read_run_colors(self):
|
|
288
|
+
path = self._run_colors_path()
|
|
289
|
+
if not path or not os.path.exists(path):
|
|
290
|
+
return {}
|
|
291
|
+
try:
|
|
292
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
293
|
+
data = json.load(f)
|
|
294
|
+
return data if isinstance(data, dict) else {}
|
|
295
|
+
except Exception:
|
|
296
|
+
return {}
|
|
297
|
+
|
|
298
|
+
def _write_run_colors(self, mapping):
|
|
299
|
+
path = self._run_colors_path()
|
|
300
|
+
if not path:
|
|
301
|
+
return False
|
|
302
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
303
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
304
|
+
json.dump(mapping, f, indent=2, sort_keys=True)
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
@wrappers.Request.application
|
|
308
|
+
def _serve_run_colors(self, request):
|
|
309
|
+
"""Serve and update run color overrides.
|
|
310
|
+
|
|
311
|
+
GET returns a JSON object mapping run name -> hex color string (e.g. "#aabbcc").
|
|
312
|
+
|
|
313
|
+
POST accepts either:
|
|
314
|
+
- {"run": "<run_name>", "color": "#aabbcc"}
|
|
315
|
+
- {"overrides": {"run1": "#aabbcc", ...}}
|
|
316
|
+
|
|
317
|
+
The mapping is stored as JSON under `<logdir>/runs/run_colors.json` when possible.
|
|
318
|
+
"""
|
|
319
|
+
if request.method == "GET":
|
|
320
|
+
return http_util.Respond(
|
|
321
|
+
request, self._read_run_colors(), "application/json"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if request.method != "POST":
|
|
325
|
+
return http_util.Respond(
|
|
326
|
+
request, "Method not allowed", "text/plain", code=405
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
payload = json.loads(request.data or b"{}")
|
|
331
|
+
except Exception:
|
|
332
|
+
return http_util.Respond(
|
|
333
|
+
request, "Invalid JSON", "text/plain", code=400
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if not isinstance(payload, dict):
|
|
337
|
+
return http_util.Respond(
|
|
338
|
+
request, "Invalid request", "text/plain", code=400
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
mapping = self._read_run_colors()
|
|
342
|
+
if not isinstance(mapping, dict):
|
|
343
|
+
mapping = {}
|
|
344
|
+
|
|
345
|
+
if "overrides" in payload:
|
|
346
|
+
overrides = payload.get("overrides")
|
|
347
|
+
if not isinstance(overrides, dict):
|
|
348
|
+
return http_util.Respond(
|
|
349
|
+
request, "Invalid overrides", "text/plain", code=400
|
|
350
|
+
)
|
|
351
|
+
for run_name, color in overrides.items():
|
|
352
|
+
if not isinstance(run_name, str) or not isinstance(color, str):
|
|
353
|
+
continue
|
|
354
|
+
if not _RUN_COLOR_HEX_RE.match(color):
|
|
355
|
+
continue
|
|
356
|
+
mapping[run_name] = color
|
|
357
|
+
else:
|
|
358
|
+
run_name = payload.get("run")
|
|
359
|
+
color = payload.get("color")
|
|
360
|
+
if not isinstance(run_name, str) or not isinstance(color, str):
|
|
361
|
+
return http_util.Respond(
|
|
362
|
+
request, "Invalid request", "text/plain", code=400
|
|
363
|
+
)
|
|
364
|
+
if not _RUN_COLOR_HEX_RE.match(color):
|
|
365
|
+
return http_util.Respond(
|
|
366
|
+
request, "Invalid color", "text/plain", code=400
|
|
367
|
+
)
|
|
368
|
+
mapping[run_name] = color
|
|
369
|
+
|
|
370
|
+
if not self._write_run_colors(mapping):
|
|
371
|
+
return http_util.Respond(
|
|
372
|
+
request,
|
|
373
|
+
"Run color persistence is unavailable for this logdir",
|
|
374
|
+
"text/plain",
|
|
375
|
+
code=400,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
return http_util.Respond(request, mapping, "application/json")
|
|
379
|
+
|
|
380
|
+
def _profile_path(self):
|
|
381
|
+
"""Returns a path to the default profile file, or None if unavailable.
|
|
382
|
+
|
|
383
|
+
We only support reading/writing when `--logdir` points to a local
|
|
384
|
+
filesystem directory. For `--logdir_spec` or non-local data providers,
|
|
385
|
+
this is not guaranteed.
|
|
386
|
+
"""
|
|
387
|
+
logdir = self._logdir or ""
|
|
388
|
+
# Basic guard against `--logdir_spec`/multi-logdir strings.
|
|
389
|
+
if not logdir or ("," in logdir) or (":" in logdir):
|
|
390
|
+
return None
|
|
391
|
+
if not os.path.isdir(logdir):
|
|
392
|
+
return None
|
|
393
|
+
return os.path.join(logdir, ".tensorboard", "default_profile.json")
|
|
394
|
+
|
|
395
|
+
def _read_profile(self):
|
|
396
|
+
"""Reads the default profile from the logdir.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
The profile data as a dict, or None if no profile exists or is
|
|
400
|
+
invalid.
|
|
401
|
+
"""
|
|
402
|
+
path = self._profile_path()
|
|
403
|
+
if not path or not os.path.exists(path):
|
|
404
|
+
return None
|
|
405
|
+
try:
|
|
406
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
407
|
+
data = json.load(f)
|
|
408
|
+
# Basic validation - must be a dict with version and data fields
|
|
409
|
+
if not isinstance(data, dict):
|
|
410
|
+
return None
|
|
411
|
+
if "version" not in data:
|
|
412
|
+
return None
|
|
413
|
+
return data
|
|
414
|
+
except Exception:
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
def _write_profile(self, profile_data):
|
|
418
|
+
"""Writes a profile to the default profile file.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
profile_data: The profile data dict to write.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
True if successful, False otherwise.
|
|
425
|
+
"""
|
|
426
|
+
path = self._profile_path()
|
|
427
|
+
if not path:
|
|
428
|
+
return False
|
|
429
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
430
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
431
|
+
json.dump(profile_data, f, indent=2)
|
|
432
|
+
return True
|
|
433
|
+
|
|
434
|
+
@wrappers.Request.application
|
|
435
|
+
def _serve_profile(self, request):
|
|
436
|
+
"""Serve and update the default dashboard profile.
|
|
437
|
+
|
|
438
|
+
GET returns the default profile JSON object, or 404 if none exists.
|
|
439
|
+
The profile contains pinned cards, run colors, superimposed cards,
|
|
440
|
+
and other dashboard settings.
|
|
441
|
+
|
|
442
|
+
POST accepts a profile JSON object to save as the default profile.
|
|
443
|
+
The profile is stored under `<logdir>/.tensorboard/default_profile.json`.
|
|
444
|
+
|
|
445
|
+
This endpoint enables training harnesses to set default dashboard
|
|
446
|
+
configurations that users will see when they first load TensorBoard.
|
|
447
|
+
"""
|
|
448
|
+
if request.method == "GET":
|
|
449
|
+
profile = self._read_profile()
|
|
450
|
+
if profile is None:
|
|
451
|
+
return http_util.Respond(
|
|
452
|
+
request, "No default profile found", "text/plain", code=404
|
|
453
|
+
)
|
|
454
|
+
return http_util.Respond(request, profile, "application/json")
|
|
455
|
+
|
|
456
|
+
if request.method != "POST":
|
|
457
|
+
return http_util.Respond(
|
|
458
|
+
request, "Method not allowed", "text/plain", code=405
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
payload = json.loads(request.data or b"{}")
|
|
463
|
+
except Exception:
|
|
464
|
+
return http_util.Respond(
|
|
465
|
+
request, "Invalid JSON", "text/plain", code=400
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if not isinstance(payload, dict):
|
|
469
|
+
return http_util.Respond(
|
|
470
|
+
request, "Invalid profile format", "text/plain", code=400
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
# Basic validation - require version field
|
|
474
|
+
if "version" not in payload:
|
|
475
|
+
return http_util.Respond(
|
|
476
|
+
request,
|
|
477
|
+
"Invalid profile: missing version field",
|
|
478
|
+
"text/plain",
|
|
479
|
+
code=400,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
if not self._write_profile(payload):
|
|
483
|
+
return http_util.Respond(
|
|
484
|
+
request,
|
|
485
|
+
"Profile persistence is unavailable for this logdir",
|
|
486
|
+
"text/plain",
|
|
487
|
+
code=400,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
return http_util.Respond(request, payload, "application/json")
|
|
491
|
+
|
|
492
|
+
@wrappers.Request.application
|
|
493
|
+
def _serve_experiments(self, request):
|
|
494
|
+
"""Serve a JSON array of experiments.
|
|
495
|
+
|
|
496
|
+
Experiments are ordered by experiment started time (aka first
|
|
497
|
+
event time) with empty times sorted last, and then ties are
|
|
498
|
+
broken by sorting on the experiment name.
|
|
499
|
+
"""
|
|
500
|
+
results = self.list_experiments_impl()
|
|
501
|
+
return http_util.Respond(request, results, "application/json")
|
|
502
|
+
|
|
503
|
+
def list_experiments_impl(self):
|
|
504
|
+
return []
|
|
505
|
+
|
|
506
|
+
@wrappers.Request.application
|
|
507
|
+
def _serve_experiment_runs(self, request):
|
|
508
|
+
"""Serve a JSON runs of an experiment, specified with query param
|
|
509
|
+
`experiment`, with their nested data, tag, populated.
|
|
510
|
+
|
|
511
|
+
Runs returned are ordered by started time (aka first event time)
|
|
512
|
+
with empty times sorted last, and then ties are broken by
|
|
513
|
+
sorting on the run name. Tags are sorted by its name,
|
|
514
|
+
displayName, and lastly, inserted time.
|
|
515
|
+
"""
|
|
516
|
+
results = []
|
|
517
|
+
return http_util.Respond(request, results, "application/json")
|
|
518
|
+
|
|
519
|
+
@wrappers.Request.application
|
|
520
|
+
def _serve_notifications(self, request):
|
|
521
|
+
"""Serve JSON payload of notifications to show in the UI."""
|
|
522
|
+
response = utils.redirect("../notifications_note.json")
|
|
523
|
+
# Disable Werkzeug's automatic Location header correction routine, which
|
|
524
|
+
# absolutizes relative paths "to be RFC conformant" [1], but this is
|
|
525
|
+
# based on an outdated HTTP/1.1 RFC; the current one allows them:
|
|
526
|
+
# https://tools.ietf.org/html/rfc7231#section-7.1.2
|
|
527
|
+
response.autocorrect_location_header = False
|
|
528
|
+
return response
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class CorePluginLoader(base_plugin.TBLoader):
|
|
532
|
+
"""CorePlugin factory."""
|
|
533
|
+
|
|
534
|
+
def __init__(self, include_debug_info=None):
|
|
535
|
+
self._include_debug_info = include_debug_info
|
|
536
|
+
|
|
537
|
+
def define_flags(self, parser):
|
|
538
|
+
"""Adds standard TensorBoard CLI flags to parser."""
|
|
539
|
+
parser.add_argument(
|
|
540
|
+
"--logdir",
|
|
541
|
+
metavar="PATH",
|
|
542
|
+
type=str,
|
|
543
|
+
default="",
|
|
544
|
+
help="""\
|
|
545
|
+
Directory where TensorBoard will look to find TensorFlow event files
|
|
546
|
+
that it can display. TensorBoard will recursively walk the directory
|
|
547
|
+
structure rooted at logdir, looking for .*tfevents.* files.
|
|
548
|
+
|
|
549
|
+
A leading tilde will be expanded with the semantics of Python's
|
|
550
|
+
os.expanduser function.
|
|
551
|
+
""",
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
parser.add_argument(
|
|
555
|
+
"--logdir_spec",
|
|
556
|
+
metavar="PATH_SPEC",
|
|
557
|
+
type=str,
|
|
558
|
+
default="",
|
|
559
|
+
help="""\
|
|
560
|
+
Like `--logdir`, but with special interpretation for commas and colons:
|
|
561
|
+
commas separate multiple runs, where a colon specifies a new name for a
|
|
562
|
+
run. For example:
|
|
563
|
+
`tensorbored --logdir_spec=name1:/path/to/logs/1,name2:/path/to/logs/2`.
|
|
564
|
+
|
|
565
|
+
This flag is discouraged and can usually be avoided. TensorBoard walks
|
|
566
|
+
log directories recursively; for finer-grained control, prefer using a
|
|
567
|
+
symlink tree. Some features may not work when using `--logdir_spec`
|
|
568
|
+
instead of `--logdir`.
|
|
569
|
+
""",
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
parser.add_argument(
|
|
573
|
+
"--host",
|
|
574
|
+
metavar="ADDR",
|
|
575
|
+
type=str,
|
|
576
|
+
default=None, # like localhost, but prints a note about `--bind_all`
|
|
577
|
+
help="""\
|
|
578
|
+
What host to listen to (default: localhost). To serve to the entire local
|
|
579
|
+
network on both IPv4 and IPv6, see `--bind_all`, with which this option is
|
|
580
|
+
mutually exclusive.
|
|
581
|
+
""",
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
parser.add_argument(
|
|
585
|
+
"--bind_all",
|
|
586
|
+
action="store_true",
|
|
587
|
+
help="""\
|
|
588
|
+
Serve on all public interfaces. This will expose your TensorBoard instance to
|
|
589
|
+
the network on both IPv4 and IPv6 (where available). Mutually exclusive with
|
|
590
|
+
`--host`.
|
|
591
|
+
""",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
parser.add_argument(
|
|
595
|
+
"--port",
|
|
596
|
+
metavar="PORT",
|
|
597
|
+
type=lambda s: (None if s == "default" else int(s)),
|
|
598
|
+
default="default",
|
|
599
|
+
help="""\
|
|
600
|
+
Port to serve TensorBoard on. Pass 0 to request an unused port selected
|
|
601
|
+
by the operating system, or pass "default" to try to bind to the default
|
|
602
|
+
port (%s) but search for a nearby free port if the default port is
|
|
603
|
+
unavailable. (default: "default").\
|
|
604
|
+
"""
|
|
605
|
+
% DEFAULT_PORT,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
parser.add_argument(
|
|
609
|
+
"--reuse_port",
|
|
610
|
+
metavar="BOOL",
|
|
611
|
+
# Custom str-to-bool converter since regular bool() doesn't work.
|
|
612
|
+
type=lambda v: {"true": True, "false": False}.get(v.lower(), v),
|
|
613
|
+
choices=[True, False],
|
|
614
|
+
default=False,
|
|
615
|
+
help="""\
|
|
616
|
+
Enables the SO_REUSEPORT option on the socket opened by TensorBoard's HTTP
|
|
617
|
+
server, for platforms that support it. This is useful in cases when a parent
|
|
618
|
+
process has obtained the port already and wants to delegate access to the
|
|
619
|
+
port to TensorBoard as a subprocess.(default: %(default)s).\
|
|
620
|
+
""",
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
parser.add_argument(
|
|
624
|
+
"--load_fast",
|
|
625
|
+
type=str,
|
|
626
|
+
default="auto",
|
|
627
|
+
choices=["false", "auto", "true"],
|
|
628
|
+
help="""\
|
|
629
|
+
Use alternate mechanism to load data. Typically 100x faster or more, but only
|
|
630
|
+
available on some platforms and invocations. Defaults to "auto" to use this new
|
|
631
|
+
mode only if available, otherwise falling back to the legacy loading path. Set
|
|
632
|
+
to "true" to suppress the advisory note and hard-fail if the fast codepath is
|
|
633
|
+
not available. Set to "false" to always fall back. Feedback/issues:
|
|
634
|
+
https://github.com/tensorflow/tensorboard/issues/4784
|
|
635
|
+
(default: %(default)s)
|
|
636
|
+
""",
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
parser.add_argument(
|
|
640
|
+
"--extra_data_server_flags",
|
|
641
|
+
type=str,
|
|
642
|
+
default="",
|
|
643
|
+
help="""\
|
|
644
|
+
Experimental. With `--load_fast`, pass these additional command-line flags to
|
|
645
|
+
the data server. Subject to POSIX word splitting per `shlex.split`. Meant for
|
|
646
|
+
debugging; not officially supported.
|
|
647
|
+
""",
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
parser.add_argument(
|
|
651
|
+
"--grpc_creds_type",
|
|
652
|
+
type=grpc_util.ChannelCredsType,
|
|
653
|
+
default=grpc_util.ChannelCredsType.LOCAL,
|
|
654
|
+
choices=grpc_util.ChannelCredsType.choices(),
|
|
655
|
+
help="""\
|
|
656
|
+
Experimental. The type of credentials to use to connect to the data server.
|
|
657
|
+
(default: %(default)s)
|
|
658
|
+
""",
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
parser.add_argument(
|
|
662
|
+
"--grpc_data_provider",
|
|
663
|
+
metavar="PORT",
|
|
664
|
+
type=str,
|
|
665
|
+
default="",
|
|
666
|
+
help="""\
|
|
667
|
+
Experimental. Address of a gRPC server exposing a data provider. Set to empty
|
|
668
|
+
string to disable. (default: %(default)s)
|
|
669
|
+
""",
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
parser.add_argument(
|
|
673
|
+
"--purge_orphaned_data",
|
|
674
|
+
metavar="BOOL",
|
|
675
|
+
# Custom str-to-bool converter since regular bool() doesn't work.
|
|
676
|
+
type=lambda v: {"true": True, "false": False}.get(v.lower(), v),
|
|
677
|
+
choices=[True, False],
|
|
678
|
+
default=True,
|
|
679
|
+
help="""\
|
|
680
|
+
Whether to purge data that may have been orphaned due to TensorBoard
|
|
681
|
+
restarts. Setting --purge_orphaned_data=False can be used to debug data
|
|
682
|
+
disappearance. (default: %(default)s)\
|
|
683
|
+
""",
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
parser.add_argument(
|
|
687
|
+
"--db",
|
|
688
|
+
metavar="URI",
|
|
689
|
+
type=str,
|
|
690
|
+
default="",
|
|
691
|
+
help="""\
|
|
692
|
+
[experimental] sets SQL database URI and enables DB backend mode, which is
|
|
693
|
+
read-only unless --db_import is also passed.\
|
|
694
|
+
""",
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
parser.add_argument(
|
|
698
|
+
"--db_import",
|
|
699
|
+
action="store_true",
|
|
700
|
+
help="""\
|
|
701
|
+
[experimental] enables DB read-and-import mode, which in combination with
|
|
702
|
+
--logdir imports event files into a DB backend on the fly. The backing DB is
|
|
703
|
+
temporary unless --db is also passed to specify a DB path to use.\
|
|
704
|
+
""",
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
parser.add_argument(
|
|
708
|
+
"--inspect",
|
|
709
|
+
action="store_true",
|
|
710
|
+
help="""\
|
|
711
|
+
Prints digests of event files to command line.
|
|
712
|
+
|
|
713
|
+
This is useful when no data is shown on TensorBoard, or the data shown
|
|
714
|
+
looks weird.
|
|
715
|
+
|
|
716
|
+
Must specify one of `logdir` or `event_file` flag.
|
|
717
|
+
|
|
718
|
+
Example usage:
|
|
719
|
+
`tensorbored --inspect --logdir mylogdir --tag loss`
|
|
720
|
+
|
|
721
|
+
See tensorbored/backend/event_processing/event_file_inspector.py for more info.\
|
|
722
|
+
""",
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
# This flag has a "_tb" suffix to avoid conflicting with an internal flag
|
|
726
|
+
# named --version. Note that due to argparse auto-expansion of unambiguous
|
|
727
|
+
# flag prefixes, you can still invoke this as `tensorbored --version`.
|
|
728
|
+
parser.add_argument(
|
|
729
|
+
"--version_tb",
|
|
730
|
+
action="store_true",
|
|
731
|
+
help="Prints the version of Tensorboard",
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
parser.add_argument(
|
|
735
|
+
"--tag",
|
|
736
|
+
metavar="TAG",
|
|
737
|
+
type=str,
|
|
738
|
+
default="",
|
|
739
|
+
help="tag to query for; used with --inspect",
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
parser.add_argument(
|
|
743
|
+
"--event_file",
|
|
744
|
+
metavar="PATH",
|
|
745
|
+
type=str,
|
|
746
|
+
default="",
|
|
747
|
+
help="""\
|
|
748
|
+
The particular event file to query for. Only used if --inspect is
|
|
749
|
+
present and --logdir is not specified.\
|
|
750
|
+
""",
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
parser.add_argument(
|
|
754
|
+
"--path_prefix",
|
|
755
|
+
metavar="PATH",
|
|
756
|
+
type=str,
|
|
757
|
+
default="",
|
|
758
|
+
help="""\
|
|
759
|
+
An optional, relative prefix to the path, e.g. "/path/to/tensorboard".
|
|
760
|
+
resulting in the new base url being located at
|
|
761
|
+
localhost:6006/path/to/tensorboard under default settings. A leading
|
|
762
|
+
slash is required when specifying the path_prefix. A trailing slash is
|
|
763
|
+
optional and has no effect. The path_prefix can be leveraged for path
|
|
764
|
+
based routing of an ELB when the website base_url is not available e.g.
|
|
765
|
+
"example.site.com/path/to/tensorboard/".\
|
|
766
|
+
""",
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
parser.add_argument(
|
|
770
|
+
"--window_title",
|
|
771
|
+
metavar="TEXT",
|
|
772
|
+
type=str,
|
|
773
|
+
default="",
|
|
774
|
+
help="changes title of browser window",
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
parser.add_argument(
|
|
778
|
+
"--max_reload_threads",
|
|
779
|
+
metavar="COUNT",
|
|
780
|
+
type=int,
|
|
781
|
+
default=1,
|
|
782
|
+
help="""\
|
|
783
|
+
The max number of threads that TensorBoard can use to reload runs. Not
|
|
784
|
+
relevant for db read-only mode. Each thread reloads one run at a time.
|
|
785
|
+
(default: %(default)s)\
|
|
786
|
+
""",
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
parser.add_argument(
|
|
790
|
+
"--reload_interval",
|
|
791
|
+
metavar="SECONDS",
|
|
792
|
+
type=_nonnegative_float,
|
|
793
|
+
default=5.0,
|
|
794
|
+
help="""\
|
|
795
|
+
How often the backend should load more data, in seconds. Set to 0 to
|
|
796
|
+
load just once at startup. Must be non-negative. (default: %(default)s)\
|
|
797
|
+
""",
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
parser.add_argument(
|
|
801
|
+
"--reload_task",
|
|
802
|
+
metavar="TYPE",
|
|
803
|
+
type=str,
|
|
804
|
+
default="auto",
|
|
805
|
+
choices=["auto", "thread", "process", "blocking"],
|
|
806
|
+
help="""\
|
|
807
|
+
[experimental] The mechanism to use for the background data reload task.
|
|
808
|
+
The default "auto" option will conditionally use threads for legacy reloading
|
|
809
|
+
and a child process for DB import reloading. The "process" option is only
|
|
810
|
+
useful with DB import mode. The "blocking" option will block startup until
|
|
811
|
+
reload finishes, and requires --load_interval=0. (default: %(default)s)\
|
|
812
|
+
""",
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
parser.add_argument(
|
|
816
|
+
"--reload_multifile",
|
|
817
|
+
metavar="BOOL",
|
|
818
|
+
# Custom str-to-bool converter since regular bool() doesn't work.
|
|
819
|
+
type=lambda v: {"true": True, "false": False}.get(v.lower(), v),
|
|
820
|
+
choices=[True, False],
|
|
821
|
+
default=None,
|
|
822
|
+
help="""\
|
|
823
|
+
[experimental] If true, this enables experimental support for continuously
|
|
824
|
+
polling multiple event files in each run directory for newly appended data
|
|
825
|
+
(rather than only polling the last event file). Event files will only be
|
|
826
|
+
polled as long as their most recently read data is newer than the threshold
|
|
827
|
+
defined by --reload_multifile_inactive_secs, to limit resource usage. Beware
|
|
828
|
+
of running out of memory if the logdir contains many active event files.
|
|
829
|
+
(default: false)\
|
|
830
|
+
""",
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
parser.add_argument(
|
|
834
|
+
"--reload_multifile_inactive_secs",
|
|
835
|
+
metavar="SECONDS",
|
|
836
|
+
type=int,
|
|
837
|
+
default=86400,
|
|
838
|
+
help="""\
|
|
839
|
+
[experimental] Configures the age threshold in seconds at which an event file
|
|
840
|
+
that has no event wall time more recent than that will be considered an
|
|
841
|
+
inactive file and no longer polled (to limit resource usage). If set to -1,
|
|
842
|
+
no maximum age will be enforced, but beware of running out of memory and
|
|
843
|
+
heavier filesystem read traffic. If set to 0, this reverts to the older
|
|
844
|
+
last-file-only polling strategy (akin to --reload_multifile=false).
|
|
845
|
+
(default: %(default)s - intended to ensure an event file remains active if
|
|
846
|
+
it receives new data at least once per 24 hour period)\
|
|
847
|
+
""",
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
parser.add_argument(
|
|
851
|
+
"--generic_data",
|
|
852
|
+
metavar="TYPE",
|
|
853
|
+
type=str,
|
|
854
|
+
default="auto",
|
|
855
|
+
choices=["false", "auto", "true"],
|
|
856
|
+
help="""\
|
|
857
|
+
[experimental] Hints whether plugins should read from generic data
|
|
858
|
+
provider infrastructure. For plugins that support only the legacy
|
|
859
|
+
multiplexer APIs or only the generic data APIs, this option has no
|
|
860
|
+
effect. The "auto" option enables this only for plugins that are
|
|
861
|
+
considered to have stable support for generic data providers. (default:
|
|
862
|
+
%(default)s)\
|
|
863
|
+
""",
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
parser.add_argument(
|
|
867
|
+
"--samples_per_plugin",
|
|
868
|
+
type=_parse_samples_per_plugin,
|
|
869
|
+
default="",
|
|
870
|
+
help="""\
|
|
871
|
+
An optional comma separated list of plugin_name=num_samples pairs to
|
|
872
|
+
explicitly specify how many samples to keep per tag for that plugin. For
|
|
873
|
+
unspecified plugins, TensorBoard randomly downsamples logged summaries
|
|
874
|
+
to reasonable values to prevent out-of-memory errors for long running
|
|
875
|
+
jobs. This flag allows fine control over that downsampling. Note that if a
|
|
876
|
+
plugin is not specified in this list, a plugin-specific default number of
|
|
877
|
+
samples will be enforced. (for example, 10 for images, 500 for histograms,
|
|
878
|
+
and 1000 for scalars). Most users should not need to set this flag.\
|
|
879
|
+
""",
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
parser.add_argument(
|
|
883
|
+
"--detect_file_replacement",
|
|
884
|
+
metavar="BOOL",
|
|
885
|
+
# Custom str-to-bool converter since regular bool() doesn't work.
|
|
886
|
+
type=lambda v: {"true": True, "false": False}.get(v.lower(), v),
|
|
887
|
+
choices=[True, False],
|
|
888
|
+
default=None,
|
|
889
|
+
help="""\
|
|
890
|
+
[experimental] If true, this enables experimental support for detecting when
|
|
891
|
+
event files are replaced with new versions that contain additional data. This is
|
|
892
|
+
not needed in the normal case where new data is either appended to an existing
|
|
893
|
+
file or written to a brand new file, but it arises, for example, when using
|
|
894
|
+
rsync without the --inplace option, in which new versions of the original file
|
|
895
|
+
are first written to a temporary file, then swapped into the final location.
|
|
896
|
+
|
|
897
|
+
This option is currently incompatible with --load_fast=true, and if passed will
|
|
898
|
+
disable fast-loading mode. (default: false)\
|
|
899
|
+
""",
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
def fix_flags(self, flags):
|
|
903
|
+
"""Fixes standard TensorBoard CLI flags to parser."""
|
|
904
|
+
FlagsError = base_plugin.FlagsError
|
|
905
|
+
if flags.version_tb:
|
|
906
|
+
pass
|
|
907
|
+
elif flags.inspect:
|
|
908
|
+
if flags.logdir_spec:
|
|
909
|
+
raise FlagsError(
|
|
910
|
+
"--logdir_spec is not supported with --inspect."
|
|
911
|
+
)
|
|
912
|
+
if flags.logdir and flags.event_file:
|
|
913
|
+
raise FlagsError(
|
|
914
|
+
"Must specify either --logdir or --event_file, but not both."
|
|
915
|
+
)
|
|
916
|
+
if not (flags.logdir or flags.event_file):
|
|
917
|
+
raise FlagsError(
|
|
918
|
+
"Must specify either --logdir or --event_file."
|
|
919
|
+
)
|
|
920
|
+
elif flags.logdir and flags.logdir_spec:
|
|
921
|
+
raise FlagsError("May not specify both --logdir and --logdir_spec")
|
|
922
|
+
elif (
|
|
923
|
+
not flags.db
|
|
924
|
+
and not flags.logdir
|
|
925
|
+
and not flags.logdir_spec
|
|
926
|
+
and not flags.grpc_data_provider
|
|
927
|
+
):
|
|
928
|
+
raise FlagsError(
|
|
929
|
+
"A logdir or db must be specified. "
|
|
930
|
+
"For example `tensorbored --logdir mylogdir` "
|
|
931
|
+
"or `tensorbored --db sqlite:~/.tensorboard.db`. "
|
|
932
|
+
"Run `tensorbored --helpfull` for details and examples."
|
|
933
|
+
)
|
|
934
|
+
elif flags.host is not None and flags.bind_all:
|
|
935
|
+
raise FlagsError("Must not specify both --host and --bind_all.")
|
|
936
|
+
elif (
|
|
937
|
+
flags.load_fast == "true" and flags.detect_file_replacement is True
|
|
938
|
+
):
|
|
939
|
+
raise FlagsError(
|
|
940
|
+
"Must not specify both --load_fast=true and"
|
|
941
|
+
"--detect_file_replacement=true"
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
flags.path_prefix = flags.path_prefix.rstrip("/")
|
|
945
|
+
if flags.path_prefix and not flags.path_prefix.startswith("/"):
|
|
946
|
+
raise FlagsError(
|
|
947
|
+
"Path prefix must start with slash, but got: %r."
|
|
948
|
+
% flags.path_prefix
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
def load(self, context):
|
|
952
|
+
"""Creates CorePlugin instance."""
|
|
953
|
+
return CorePlugin(context, include_debug_info=self._include_debug_info)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _gzip(bytestring):
|
|
957
|
+
out = io.BytesIO()
|
|
958
|
+
# Set mtime to zero for deterministic results across TensorBoard launches.
|
|
959
|
+
with gzip.GzipFile(fileobj=out, mode="wb", compresslevel=3, mtime=0) as f:
|
|
960
|
+
f.write(bytestring)
|
|
961
|
+
return out.getvalue()
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def _parse_samples_per_plugin(value):
|
|
965
|
+
"""Parses `value` as a string-to-int dict in the form `foo=12,bar=34`."""
|
|
966
|
+
result = {}
|
|
967
|
+
for token in value.split(","):
|
|
968
|
+
if token:
|
|
969
|
+
k, v = token.strip().split("=")
|
|
970
|
+
result[k] = int(v)
|
|
971
|
+
return result
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def _nonnegative_float(v):
|
|
975
|
+
try:
|
|
976
|
+
v = float(v)
|
|
977
|
+
except ValueError:
|
|
978
|
+
raise argparse.ArgumentTypeError("invalid float: %r" % v)
|
|
979
|
+
if not (v >= 0): # no NaNs, please
|
|
980
|
+
raise argparse.ArgumentTypeError("must be non-negative: %r" % v)
|
|
981
|
+
return v
|