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.
Files changed (271) hide show
  1. tensorbored/__init__.py +112 -0
  2. tensorbored/_vendor/__init__.py +0 -0
  3. tensorbored/_vendor/bleach/__init__.py +125 -0
  4. tensorbored/_vendor/bleach/_vendor/__init__.py +0 -0
  5. tensorbored/_vendor/bleach/_vendor/html5lib/__init__.py +35 -0
  6. tensorbored/_vendor/bleach/_vendor/html5lib/_ihatexml.py +289 -0
  7. tensorbored/_vendor/bleach/_vendor/html5lib/_inputstream.py +918 -0
  8. tensorbored/_vendor/bleach/_vendor/html5lib/_tokenizer.py +1735 -0
  9. tensorbored/_vendor/bleach/_vendor/html5lib/_trie/__init__.py +5 -0
  10. tensorbored/_vendor/bleach/_vendor/html5lib/_trie/_base.py +40 -0
  11. tensorbored/_vendor/bleach/_vendor/html5lib/_trie/py.py +67 -0
  12. tensorbored/_vendor/bleach/_vendor/html5lib/_utils.py +159 -0
  13. tensorbored/_vendor/bleach/_vendor/html5lib/constants.py +2946 -0
  14. tensorbored/_vendor/bleach/_vendor/html5lib/filters/__init__.py +0 -0
  15. tensorbored/_vendor/bleach/_vendor/html5lib/filters/alphabeticalattributes.py +29 -0
  16. tensorbored/_vendor/bleach/_vendor/html5lib/filters/base.py +12 -0
  17. tensorbored/_vendor/bleach/_vendor/html5lib/filters/inject_meta_charset.py +73 -0
  18. tensorbored/_vendor/bleach/_vendor/html5lib/filters/lint.py +93 -0
  19. tensorbored/_vendor/bleach/_vendor/html5lib/filters/optionaltags.py +207 -0
  20. tensorbored/_vendor/bleach/_vendor/html5lib/filters/sanitizer.py +916 -0
  21. tensorbored/_vendor/bleach/_vendor/html5lib/filters/whitespace.py +38 -0
  22. tensorbored/_vendor/bleach/_vendor/html5lib/html5parser.py +2795 -0
  23. tensorbored/_vendor/bleach/_vendor/html5lib/serializer.py +409 -0
  24. tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/__init__.py +30 -0
  25. tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/genshi.py +54 -0
  26. tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/sax.py +50 -0
  27. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/__init__.py +88 -0
  28. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/base.py +417 -0
  29. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/dom.py +239 -0
  30. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/etree.py +343 -0
  31. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/etree_lxml.py +392 -0
  32. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/__init__.py +154 -0
  33. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/base.py +252 -0
  34. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/dom.py +43 -0
  35. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/etree.py +131 -0
  36. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/etree_lxml.py +215 -0
  37. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/genshi.py +69 -0
  38. tensorbored/_vendor/bleach/_vendor/parse.py +1078 -0
  39. tensorbored/_vendor/bleach/callbacks.py +32 -0
  40. tensorbored/_vendor/bleach/html5lib_shim.py +757 -0
  41. tensorbored/_vendor/bleach/linkifier.py +633 -0
  42. tensorbored/_vendor/bleach/parse_shim.py +1 -0
  43. tensorbored/_vendor/bleach/sanitizer.py +638 -0
  44. tensorbored/_vendor/bleach/six_shim.py +19 -0
  45. tensorbored/_vendor/webencodings/__init__.py +342 -0
  46. tensorbored/_vendor/webencodings/labels.py +231 -0
  47. tensorbored/_vendor/webencodings/mklabels.py +59 -0
  48. tensorbored/_vendor/webencodings/x_user_defined.py +325 -0
  49. tensorbored/assets.py +36 -0
  50. tensorbored/auth.py +102 -0
  51. tensorbored/backend/__init__.py +0 -0
  52. tensorbored/backend/application.py +604 -0
  53. tensorbored/backend/auth_context_middleware.py +38 -0
  54. tensorbored/backend/client_feature_flags.py +113 -0
  55. tensorbored/backend/empty_path_redirect.py +46 -0
  56. tensorbored/backend/event_processing/__init__.py +0 -0
  57. tensorbored/backend/event_processing/data_ingester.py +276 -0
  58. tensorbored/backend/event_processing/data_provider.py +535 -0
  59. tensorbored/backend/event_processing/directory_loader.py +142 -0
  60. tensorbored/backend/event_processing/directory_watcher.py +272 -0
  61. tensorbored/backend/event_processing/event_accumulator.py +950 -0
  62. tensorbored/backend/event_processing/event_file_inspector.py +463 -0
  63. tensorbored/backend/event_processing/event_file_loader.py +292 -0
  64. tensorbored/backend/event_processing/event_multiplexer.py +521 -0
  65. tensorbored/backend/event_processing/event_util.py +68 -0
  66. tensorbored/backend/event_processing/io_wrapper.py +223 -0
  67. tensorbored/backend/event_processing/plugin_asset_util.py +104 -0
  68. tensorbored/backend/event_processing/plugin_event_accumulator.py +721 -0
  69. tensorbored/backend/event_processing/plugin_event_multiplexer.py +522 -0
  70. tensorbored/backend/event_processing/reservoir.py +266 -0
  71. tensorbored/backend/event_processing/tag_types.py +29 -0
  72. tensorbored/backend/experiment_id.py +71 -0
  73. tensorbored/backend/experimental_plugin.py +51 -0
  74. tensorbored/backend/http_util.py +263 -0
  75. tensorbored/backend/json_util.py +70 -0
  76. tensorbored/backend/path_prefix.py +67 -0
  77. tensorbored/backend/process_graph.py +74 -0
  78. tensorbored/backend/security_validator.py +202 -0
  79. tensorbored/compat/__init__.py +69 -0
  80. tensorbored/compat/proto/__init__.py +0 -0
  81. tensorbored/compat/proto/allocation_description_pb2.py +35 -0
  82. tensorbored/compat/proto/api_def_pb2.py +82 -0
  83. tensorbored/compat/proto/attr_value_pb2.py +80 -0
  84. tensorbored/compat/proto/cluster_pb2.py +58 -0
  85. tensorbored/compat/proto/config_pb2.py +271 -0
  86. tensorbored/compat/proto/coordination_config_pb2.py +45 -0
  87. tensorbored/compat/proto/cost_graph_pb2.py +87 -0
  88. tensorbored/compat/proto/cpp_shape_inference_pb2.py +70 -0
  89. tensorbored/compat/proto/debug_pb2.py +65 -0
  90. tensorbored/compat/proto/event_pb2.py +149 -0
  91. tensorbored/compat/proto/full_type_pb2.py +74 -0
  92. tensorbored/compat/proto/function_pb2.py +157 -0
  93. tensorbored/compat/proto/graph_debug_info_pb2.py +111 -0
  94. tensorbored/compat/proto/graph_pb2.py +41 -0
  95. tensorbored/compat/proto/histogram_pb2.py +39 -0
  96. tensorbored/compat/proto/meta_graph_pb2.py +254 -0
  97. tensorbored/compat/proto/node_def_pb2.py +61 -0
  98. tensorbored/compat/proto/op_def_pb2.py +81 -0
  99. tensorbored/compat/proto/resource_handle_pb2.py +48 -0
  100. tensorbored/compat/proto/rewriter_config_pb2.py +93 -0
  101. tensorbored/compat/proto/rpc_options_pb2.py +35 -0
  102. tensorbored/compat/proto/saved_object_graph_pb2.py +193 -0
  103. tensorbored/compat/proto/saver_pb2.py +38 -0
  104. tensorbored/compat/proto/step_stats_pb2.py +116 -0
  105. tensorbored/compat/proto/struct_pb2.py +144 -0
  106. tensorbored/compat/proto/summary_pb2.py +111 -0
  107. tensorbored/compat/proto/tensor_description_pb2.py +38 -0
  108. tensorbored/compat/proto/tensor_pb2.py +68 -0
  109. tensorbored/compat/proto/tensor_shape_pb2.py +46 -0
  110. tensorbored/compat/proto/tfprof_log_pb2.py +307 -0
  111. tensorbored/compat/proto/trackable_object_graph_pb2.py +90 -0
  112. tensorbored/compat/proto/types_pb2.py +105 -0
  113. tensorbored/compat/proto/variable_pb2.py +62 -0
  114. tensorbored/compat/proto/verifier_config_pb2.py +38 -0
  115. tensorbored/compat/proto/versions_pb2.py +35 -0
  116. tensorbored/compat/tensorflow_stub/__init__.py +38 -0
  117. tensorbored/compat/tensorflow_stub/app.py +124 -0
  118. tensorbored/compat/tensorflow_stub/compat/__init__.py +131 -0
  119. tensorbored/compat/tensorflow_stub/compat/v1/__init__.py +20 -0
  120. tensorbored/compat/tensorflow_stub/dtypes.py +692 -0
  121. tensorbored/compat/tensorflow_stub/error_codes.py +169 -0
  122. tensorbored/compat/tensorflow_stub/errors.py +507 -0
  123. tensorbored/compat/tensorflow_stub/flags.py +124 -0
  124. tensorbored/compat/tensorflow_stub/io/__init__.py +17 -0
  125. tensorbored/compat/tensorflow_stub/io/gfile.py +1011 -0
  126. tensorbored/compat/tensorflow_stub/pywrap_tensorflow.py +285 -0
  127. tensorbored/compat/tensorflow_stub/tensor_shape.py +1035 -0
  128. tensorbored/context.py +129 -0
  129. tensorbored/data/__init__.py +0 -0
  130. tensorbored/data/grpc_provider.py +365 -0
  131. tensorbored/data/ingester.py +46 -0
  132. tensorbored/data/proto/__init__.py +0 -0
  133. tensorbored/data/proto/data_provider_pb2.py +517 -0
  134. tensorbored/data/proto/data_provider_pb2_grpc.py +374 -0
  135. tensorbored/data/provider.py +1365 -0
  136. tensorbored/data/server_ingester.py +301 -0
  137. tensorbored/data_compat.py +159 -0
  138. tensorbored/dataclass_compat.py +224 -0
  139. tensorbored/default.py +124 -0
  140. tensorbored/errors.py +130 -0
  141. tensorbored/lazy.py +99 -0
  142. tensorbored/main.py +48 -0
  143. tensorbored/main_lib.py +62 -0
  144. tensorbored/manager.py +487 -0
  145. tensorbored/notebook.py +441 -0
  146. tensorbored/plugin_util.py +266 -0
  147. tensorbored/plugins/__init__.py +0 -0
  148. tensorbored/plugins/audio/__init__.py +0 -0
  149. tensorbored/plugins/audio/audio_plugin.py +229 -0
  150. tensorbored/plugins/audio/metadata.py +69 -0
  151. tensorbored/plugins/audio/plugin_data_pb2.py +37 -0
  152. tensorbored/plugins/audio/summary.py +230 -0
  153. tensorbored/plugins/audio/summary_v2.py +124 -0
  154. tensorbored/plugins/base_plugin.py +367 -0
  155. tensorbored/plugins/core/__init__.py +0 -0
  156. tensorbored/plugins/core/core_plugin.py +981 -0
  157. tensorbored/plugins/custom_scalar/__init__.py +0 -0
  158. tensorbored/plugins/custom_scalar/custom_scalars_plugin.py +320 -0
  159. tensorbored/plugins/custom_scalar/layout_pb2.py +85 -0
  160. tensorbored/plugins/custom_scalar/metadata.py +35 -0
  161. tensorbored/plugins/custom_scalar/summary.py +79 -0
  162. tensorbored/plugins/debugger_v2/__init__.py +0 -0
  163. tensorbored/plugins/debugger_v2/debug_data_multiplexer.py +631 -0
  164. tensorbored/plugins/debugger_v2/debug_data_provider.py +634 -0
  165. tensorbored/plugins/debugger_v2/debugger_v2_plugin.py +504 -0
  166. tensorbored/plugins/distribution/__init__.py +0 -0
  167. tensorbored/plugins/distribution/compressor.py +158 -0
  168. tensorbored/plugins/distribution/distributions_plugin.py +116 -0
  169. tensorbored/plugins/distribution/metadata.py +19 -0
  170. tensorbored/plugins/graph/__init__.py +0 -0
  171. tensorbored/plugins/graph/graph_util.py +129 -0
  172. tensorbored/plugins/graph/graphs_plugin.py +336 -0
  173. tensorbored/plugins/graph/keras_util.py +328 -0
  174. tensorbored/plugins/graph/metadata.py +42 -0
  175. tensorbored/plugins/histogram/__init__.py +0 -0
  176. tensorbored/plugins/histogram/histograms_plugin.py +144 -0
  177. tensorbored/plugins/histogram/metadata.py +63 -0
  178. tensorbored/plugins/histogram/plugin_data_pb2.py +34 -0
  179. tensorbored/plugins/histogram/summary.py +234 -0
  180. tensorbored/plugins/histogram/summary_v2.py +292 -0
  181. tensorbored/plugins/hparams/__init__.py +14 -0
  182. tensorbored/plugins/hparams/_keras.py +93 -0
  183. tensorbored/plugins/hparams/api.py +130 -0
  184. tensorbored/plugins/hparams/api_pb2.py +208 -0
  185. tensorbored/plugins/hparams/backend_context.py +606 -0
  186. tensorbored/plugins/hparams/download_data.py +158 -0
  187. tensorbored/plugins/hparams/error.py +26 -0
  188. tensorbored/plugins/hparams/get_experiment.py +71 -0
  189. tensorbored/plugins/hparams/hparams_plugin.py +206 -0
  190. tensorbored/plugins/hparams/hparams_util_pb2.py +69 -0
  191. tensorbored/plugins/hparams/json_format_compat.py +38 -0
  192. tensorbored/plugins/hparams/list_metric_evals.py +57 -0
  193. tensorbored/plugins/hparams/list_session_groups.py +1040 -0
  194. tensorbored/plugins/hparams/metadata.py +125 -0
  195. tensorbored/plugins/hparams/metrics.py +41 -0
  196. tensorbored/plugins/hparams/plugin_data_pb2.py +69 -0
  197. tensorbored/plugins/hparams/summary.py +205 -0
  198. tensorbored/plugins/hparams/summary_v2.py +597 -0
  199. tensorbored/plugins/image/__init__.py +0 -0
  200. tensorbored/plugins/image/images_plugin.py +232 -0
  201. tensorbored/plugins/image/metadata.py +65 -0
  202. tensorbored/plugins/image/plugin_data_pb2.py +34 -0
  203. tensorbored/plugins/image/summary.py +159 -0
  204. tensorbored/plugins/image/summary_v2.py +130 -0
  205. tensorbored/plugins/mesh/__init__.py +14 -0
  206. tensorbored/plugins/mesh/mesh_plugin.py +292 -0
  207. tensorbored/plugins/mesh/metadata.py +152 -0
  208. tensorbored/plugins/mesh/plugin_data_pb2.py +37 -0
  209. tensorbored/plugins/mesh/summary.py +251 -0
  210. tensorbored/plugins/mesh/summary_v2.py +214 -0
  211. tensorbored/plugins/metrics/__init__.py +0 -0
  212. tensorbored/plugins/metrics/metadata.py +17 -0
  213. tensorbored/plugins/metrics/metrics_plugin.py +623 -0
  214. tensorbored/plugins/pr_curve/__init__.py +0 -0
  215. tensorbored/plugins/pr_curve/metadata.py +75 -0
  216. tensorbored/plugins/pr_curve/plugin_data_pb2.py +34 -0
  217. tensorbored/plugins/pr_curve/pr_curves_plugin.py +241 -0
  218. tensorbored/plugins/pr_curve/summary.py +574 -0
  219. tensorbored/plugins/profile_redirect/__init__.py +0 -0
  220. tensorbored/plugins/profile_redirect/profile_redirect_plugin.py +49 -0
  221. tensorbored/plugins/projector/__init__.py +67 -0
  222. tensorbored/plugins/projector/metadata.py +26 -0
  223. tensorbored/plugins/projector/projector_config_pb2.py +54 -0
  224. tensorbored/plugins/projector/projector_plugin.py +795 -0
  225. tensorbored/plugins/projector/tf_projector_plugin/index.js +32 -0
  226. tensorbored/plugins/projector/tf_projector_plugin/projector_binary.html +524 -0
  227. tensorbored/plugins/projector/tf_projector_plugin/projector_binary.js +15536 -0
  228. tensorbored/plugins/scalar/__init__.py +0 -0
  229. tensorbored/plugins/scalar/metadata.py +60 -0
  230. tensorbored/plugins/scalar/plugin_data_pb2.py +34 -0
  231. tensorbored/plugins/scalar/scalars_plugin.py +181 -0
  232. tensorbored/plugins/scalar/summary.py +109 -0
  233. tensorbored/plugins/scalar/summary_v2.py +124 -0
  234. tensorbored/plugins/text/__init__.py +0 -0
  235. tensorbored/plugins/text/metadata.py +62 -0
  236. tensorbored/plugins/text/plugin_data_pb2.py +34 -0
  237. tensorbored/plugins/text/summary.py +114 -0
  238. tensorbored/plugins/text/summary_v2.py +124 -0
  239. tensorbored/plugins/text/text_plugin.py +288 -0
  240. tensorbored/plugins/wit_redirect/__init__.py +0 -0
  241. tensorbored/plugins/wit_redirect/wit_redirect_plugin.py +49 -0
  242. tensorbored/program.py +910 -0
  243. tensorbored/summary/__init__.py +35 -0
  244. tensorbored/summary/_output.py +124 -0
  245. tensorbored/summary/_tf/__init__.py +14 -0
  246. tensorbored/summary/_tf/summary/__init__.py +178 -0
  247. tensorbored/summary/_writer.py +105 -0
  248. tensorbored/summary/v1.py +51 -0
  249. tensorbored/summary/v2.py +25 -0
  250. tensorbored/summary/writer/__init__.py +13 -0
  251. tensorbored/summary/writer/event_file_writer.py +291 -0
  252. tensorbored/summary/writer/record_writer.py +50 -0
  253. tensorbored/util/__init__.py +0 -0
  254. tensorbored/util/encoder.py +116 -0
  255. tensorbored/util/grpc_util.py +311 -0
  256. tensorbored/util/img_mime_type_detector.py +40 -0
  257. tensorbored/util/io_util.py +20 -0
  258. tensorbored/util/lazy_tensor_creator.py +110 -0
  259. tensorbored/util/op_evaluator.py +104 -0
  260. tensorbored/util/platform_util.py +20 -0
  261. tensorbored/util/tb_logging.py +24 -0
  262. tensorbored/util/tensor_util.py +617 -0
  263. tensorbored/util/timing.py +122 -0
  264. tensorbored/version.py +21 -0
  265. tensorbored/webfiles.zip +0 -0
  266. tensorbored-2.21.0rc1769983804.dist-info/METADATA +49 -0
  267. tensorbored-2.21.0rc1769983804.dist-info/RECORD +271 -0
  268. tensorbored-2.21.0rc1769983804.dist-info/WHEEL +5 -0
  269. tensorbored-2.21.0rc1769983804.dist-info/entry_points.txt +6 -0
  270. tensorbored-2.21.0rc1769983804.dist-info/licenses/LICENSE +739 -0
  271. tensorbored-2.21.0rc1769983804.dist-info/top_level.txt +1 -0
tensorbored/program.py ADDED
@@ -0,0 +1,910 @@
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
+ """Utilities for TensorBoard command line program.
16
+
17
+ This is a lightweight module for bringing up a TensorBoard HTTP server
18
+ or emulating the `tensorboard` shell command.
19
+
20
+ Those wishing to create custom builds of TensorBoard can use this module
21
+ by swapping out `tensorboard.main` with the custom definition that
22
+ modifies the set of plugins and static assets.
23
+
24
+ This module does not depend on first-party plugins or the default web
25
+ server assets. Those are defined in `tensorboard.default`.
26
+ """
27
+
28
+ from abc import ABCMeta
29
+ from abc import abstractmethod
30
+ import argparse
31
+ import atexit
32
+ from collections import defaultdict
33
+ import errno
34
+ import logging
35
+ import mimetypes
36
+ import os
37
+ import shlex
38
+ import signal
39
+ import socket
40
+ import sys
41
+ import threading
42
+ import time
43
+ import urllib.parse
44
+
45
+ from absl import flags as absl_flags
46
+ from absl.flags import argparse_flags
47
+ from werkzeug import serving
48
+
49
+ from tensorbored import manager
50
+ from tensorbored import version
51
+ from tensorbored.backend import application
52
+ from tensorbored.backend.event_processing import data_ingester as local_ingester
53
+ from tensorbored.backend.event_processing import event_file_inspector as efi
54
+ from tensorbored.data import server_ingester
55
+ from tensorbored.plugins.core import core_plugin
56
+ from tensorbored.util import tb_logging
57
+
58
+ logger = tb_logging.get_logger()
59
+
60
+ # Default subcommand name. This is a user-facing CLI and should not change.
61
+ _SERVE_SUBCOMMAND_NAME = "serve"
62
+ # Internal flag name used to store which subcommand was invoked.
63
+ _SUBCOMMAND_FLAG = "__tensorboard_subcommand"
64
+
65
+ # Message printed when we actually use the data server, so that users are not
66
+ # caught terribly by surprise.
67
+ _DATA_SERVER_ADVISORY_MESSAGE = """
68
+ NOTE: Using experimental fast data loading logic. To disable, pass
69
+ "--load_fast=false" and report issues on GitHub. More details:
70
+ https://github.com/tensorflow/tensorboard/issues/4784
71
+
72
+ """
73
+
74
+ # Message printed with `--load_fast=true` if the data server could not start up.
75
+ # To be formatted with one `DataServerStartupError` interpoland.
76
+ _DATA_SERVER_STARTUP_ERROR_MESSAGE_TEMPLATE = """\
77
+ Could not start data server: %s.
78
+ Try with --load_fast=false and report issues on GitHub. Details:
79
+ https://github.com/tensorflow/tensorboard/issues/4784
80
+ """
81
+
82
+
83
+ class TensorBoard:
84
+ """Class for running TensorBoard.
85
+
86
+ Fields:
87
+ plugin_loaders: Set from plugins passed to constructor.
88
+ assets_zip_provider: Set by constructor.
89
+ server_class: Set by constructor.
90
+ flags: An argparse.Namespace set by the configure() method.
91
+ cache_key: As `manager.cache_key`; set by the configure() method.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ plugins=None,
97
+ assets_zip_provider=None,
98
+ server_class=None,
99
+ subcommands=None,
100
+ ):
101
+ """Creates new instance.
102
+
103
+ Args:
104
+ plugins: A list of TensorBoard plugins to load, as TBPlugin classes or
105
+ TBLoader instances or classes. If not specified, defaults to first-party
106
+ plugins.
107
+ assets_zip_provider: A function that provides a zip file containing
108
+ assets to the application. If `None`, the default TensorBoard web
109
+ assets will be used. (If building from source, your binary must
110
+ explicitly depend on `//tensorbored:assets_lib` if you pass `None`.)
111
+ server_class: An optional factory for a `TensorBoardServer` to use
112
+ for serving the TensorBoard WSGI app. If provided, its callable
113
+ signature should match that of `TensorBoardServer.__init__`.
114
+ subcommands: An optional list of TensorBoardSubcommand objects, which
115
+ extend the functionality of the CLI.
116
+
117
+ :type plugins:
118
+ list[
119
+ base_plugin.TBLoader | Type[base_plugin.TBLoader] |
120
+ Type[base_plugin.TBPlugin]
121
+ ]
122
+ """
123
+ if plugins is None:
124
+ from tensorbored import default
125
+
126
+ plugins = default.get_plugins()
127
+ if assets_zip_provider is None:
128
+ try:
129
+ from tensorbored import assets
130
+ except ImportError as e:
131
+ # `tensorboard.assets` is not a strict Bazel dep; clients are
132
+ # required to either depend on `//tensorbored:assets_lib` or
133
+ # pass a valid assets provider.
134
+ raise ImportError(
135
+ "No `assets_zip_provider` given, but `tensorboard.assets` "
136
+ "could not be imported to resolve defaults"
137
+ ) from e
138
+ assets_zip_provider = assets.get_default_assets_zip_provider()
139
+ if server_class is None:
140
+ server_class = create_port_scanning_werkzeug_server
141
+ if subcommands is None:
142
+ subcommands = []
143
+ self.plugin_loaders = [
144
+ application.make_plugin_loader(p) for p in plugins
145
+ ]
146
+ self.assets_zip_provider = assets_zip_provider
147
+ self.server_class = server_class
148
+ self.subcommands = {}
149
+ for subcommand in subcommands:
150
+ name = subcommand.name()
151
+ if name in self.subcommands or name == _SERVE_SUBCOMMAND_NAME:
152
+ raise ValueError("Duplicate subcommand name: %r" % name)
153
+ self.subcommands[name] = subcommand
154
+ self.flags = None
155
+
156
+ def configure(self, argv=("",), **kwargs):
157
+ """Configures TensorBoard behavior via flags.
158
+
159
+ This method will populate the "flags" property with an argparse.Namespace
160
+ representing flag values parsed from the provided argv list, overridden by
161
+ explicit flags from remaining keyword arguments.
162
+
163
+ Args:
164
+ argv: Can be set to CLI args equivalent to sys.argv; the first arg is
165
+ taken to be the name of the path being executed.
166
+ kwargs: Additional arguments will override what was parsed from
167
+ argv. They must be passed as Python data structures, e.g.
168
+ `foo=1` rather than `foo="1"`.
169
+
170
+ Returns:
171
+ Either argv[:1] if argv was non-empty, or [''] otherwise, as a mechanism
172
+ for absl.app.run() compatibility.
173
+
174
+ Raises:
175
+ ValueError: If flag values are invalid.
176
+ """
177
+
178
+ base_parser = argparse_flags.ArgumentParser(
179
+ prog="tensorbored",
180
+ description=(
181
+ "TensorBoard is a suite of web applications for "
182
+ "inspecting and understanding your TensorFlow runs "
183
+ "and graphs. https://github.com/tensorflow/tensorboard "
184
+ ),
185
+ )
186
+ subparsers = base_parser.add_subparsers(
187
+ help="TensorBoard subcommand (defaults to %r)"
188
+ % _SERVE_SUBCOMMAND_NAME
189
+ )
190
+
191
+ serve_subparser = subparsers.add_parser(
192
+ _SERVE_SUBCOMMAND_NAME,
193
+ help="start local TensorBoard server (default subcommand)",
194
+ )
195
+ serve_subparser.set_defaults(
196
+ **{_SUBCOMMAND_FLAG: _SERVE_SUBCOMMAND_NAME}
197
+ )
198
+
199
+ if len(argv) < 2 or argv[1].startswith("-"):
200
+ # This invocation, if valid, must not use any subcommands: we
201
+ # don't permit flags before the subcommand name.
202
+ serve_parser = base_parser
203
+ else:
204
+ # This invocation, if valid, must use a subcommand: we don't take
205
+ # any positional arguments to `serve`.
206
+ serve_parser = serve_subparser
207
+
208
+ for name, subcommand in self.subcommands.items():
209
+ subparser = subparsers.add_parser(
210
+ name,
211
+ help=subcommand.help(),
212
+ description=subcommand.description(),
213
+ )
214
+ subparser.set_defaults(**{_SUBCOMMAND_FLAG: name})
215
+ subcommand.define_flags(subparser)
216
+
217
+ for loader in self.plugin_loaders:
218
+ loader.define_flags(serve_parser)
219
+
220
+ arg0 = argv[0] if argv else ""
221
+
222
+ flags = base_parser.parse_args(argv[1:]) # Strip binary name from argv.
223
+ if getattr(flags, _SUBCOMMAND_FLAG, None) is None:
224
+ # Manually assign default value rather than using `set_defaults`
225
+ # on the base parser to work around Python bug #9351 on old
226
+ # versions of `argparse`: <https://bugs.python.org/issue9351>
227
+ setattr(flags, _SUBCOMMAND_FLAG, _SERVE_SUBCOMMAND_NAME)
228
+
229
+ self.cache_key = manager.cache_key(
230
+ working_directory=os.getcwd(),
231
+ arguments=argv[1:],
232
+ configure_kwargs=kwargs,
233
+ )
234
+ if arg0:
235
+ # Only expose main module Abseil flags as TensorBoard native flags.
236
+ # This is the same logic Abseil's ArgumentParser uses for determining
237
+ # which Abseil flags to include in the short helpstring.
238
+ for flag in set(absl_flags.FLAGS.get_key_flags_for_module(arg0)):
239
+ if hasattr(flags, flag.name):
240
+ raise ValueError("Conflicting Abseil flag: %s" % flag.name)
241
+ setattr(flags, flag.name, flag.value)
242
+ for k, v in kwargs.items():
243
+ if not hasattr(flags, k):
244
+ raise ValueError("Unknown TensorBoard flag: %s" % k)
245
+ setattr(flags, k, v)
246
+ if getattr(flags, _SUBCOMMAND_FLAG) == _SERVE_SUBCOMMAND_NAME:
247
+ for loader in self.plugin_loaders:
248
+ loader.fix_flags(flags)
249
+ self.flags = flags
250
+ return [arg0]
251
+
252
+ def main(self, ignored_argv=("",)):
253
+ """Blocking main function for TensorBoard.
254
+
255
+ This method is called by `tensorboard.main.run_main`, which is the
256
+ standard entrypoint for the tensorboard command line program. The
257
+ configure() method must be called first.
258
+
259
+ Args:
260
+ ignored_argv: Do not pass. Required for Abseil compatibility.
261
+
262
+ Returns:
263
+ Process exit code, i.e. 0 if successful or non-zero on failure. In
264
+ practice, an exception will most likely be raised instead of
265
+ returning non-zero.
266
+
267
+ :rtype: int
268
+ """
269
+ self._install_signal_handler(signal.SIGTERM, "SIGTERM")
270
+ self._fix_mime_types()
271
+ subcommand_name = getattr(self.flags, _SUBCOMMAND_FLAG)
272
+ if subcommand_name == _SERVE_SUBCOMMAND_NAME:
273
+ runner = self._run_serve_subcommand
274
+ else:
275
+ runner = self.subcommands[subcommand_name].run
276
+ return runner(self.flags) or 0
277
+
278
+ def _run_serve_subcommand(self, flags):
279
+ # TODO(#2801): Make `--version` a flag on only the base parser, not `serve`.
280
+ if flags.version_tb:
281
+ print(version.VERSION)
282
+ return 0
283
+ if flags.inspect:
284
+ # TODO(@wchargin): Convert `inspect` to a normal subcommand?
285
+ logger.info(
286
+ "Not bringing up TensorBoard, but inspecting event files."
287
+ )
288
+ event_file = os.path.expanduser(flags.event_file)
289
+ efi.inspect(flags.logdir, event_file, flags.tag)
290
+ return 0
291
+ try:
292
+ server = self._make_server()
293
+ server.print_serving_message()
294
+ self._register_info(server)
295
+ server.serve_forever()
296
+ return 0
297
+ except TensorBoardServerException as e:
298
+ logger.error(e.msg)
299
+ sys.stderr.write("ERROR: %s\n" % e.msg)
300
+ sys.stderr.flush()
301
+ return -1
302
+
303
+ def launch(self):
304
+ """Python API for launching TensorBoard.
305
+
306
+ This method is the same as main() except it launches TensorBoard in
307
+ a separate permanent thread. The configure() method must be called
308
+ first.
309
+
310
+ Returns:
311
+ The URL of the TensorBoard web server.
312
+
313
+ :rtype: str
314
+ """
315
+ # Make it easy to run TensorBoard inside other programs, e.g. Colab.
316
+ server = self._make_server()
317
+ thread = threading.Thread(
318
+ target=server.serve_forever, name="TensorBoard"
319
+ )
320
+ thread.daemon = True
321
+ thread.start()
322
+ return server.get_url()
323
+
324
+ def _register_info(self, server):
325
+ """Write a TensorBoardInfo file and arrange for its cleanup.
326
+
327
+ Args:
328
+ server: The result of `self._make_server()`.
329
+ """
330
+ server_url = urllib.parse.urlparse(server.get_url())
331
+ info = manager.TensorBoardInfo(
332
+ version=version.VERSION,
333
+ start_time=int(time.time()),
334
+ port=server_url.port,
335
+ pid=os.getpid(),
336
+ path_prefix=self.flags.path_prefix,
337
+ logdir=self.flags.logdir or self.flags.logdir_spec,
338
+ db=self.flags.db,
339
+ cache_key=self.cache_key,
340
+ )
341
+ atexit.register(manager.remove_info_file)
342
+ manager.write_info_file(info)
343
+
344
+ def _install_signal_handler(self, signal_number, signal_name):
345
+ """Set a signal handler to gracefully exit on the given signal.
346
+
347
+ When this process receives the given signal, it will run `atexit`
348
+ handlers and then exit with `0`.
349
+
350
+ Args:
351
+ signal_number: The numeric code for the signal to handle, like
352
+ `signal.SIGTERM`.
353
+ signal_name: The human-readable signal name.
354
+ """
355
+ # Note to maintainers: Google-internal code overrides this
356
+ # method (cf. cl/334534610). Double-check changes before
357
+ # modifying API.
358
+
359
+ old_signal_handler = None # set below
360
+
361
+ def handler(handled_signal_number, frame):
362
+ # In case we catch this signal again while running atexit
363
+ # handlers, take the hint and actually die.
364
+ signal.signal(signal_number, signal.SIG_DFL)
365
+ sys.stderr.write(
366
+ "TensorBoard caught %s; exiting...\n" % signal_name
367
+ )
368
+ # The main thread is the only non-daemon thread, so it suffices to
369
+ # exit hence.
370
+ if old_signal_handler not in (signal.SIG_IGN, signal.SIG_DFL):
371
+ old_signal_handler(handled_signal_number, frame)
372
+ sys.exit(0)
373
+
374
+ old_signal_handler = signal.signal(signal_number, handler)
375
+
376
+ def _fix_mime_types(self):
377
+ """Fix incorrect entries in the `mimetypes` registry.
378
+
379
+ On Windows, the Python standard library's `mimetypes` reads in
380
+ mappings from file extension to MIME type from the Windows
381
+ registry. Other applications can and do write incorrect values
382
+ to this registry, which causes `mimetypes.guess_type` to return
383
+ incorrect values, which causes TensorBoard to fail to render on
384
+ the frontend.
385
+
386
+ This method hard-codes the correct mappings for certain MIME
387
+ types that are known to be either used by TensorBoard or
388
+ problematic in general.
389
+ """
390
+ # Known to be problematic when Visual Studio is installed:
391
+ # <https://github.com/tensorflow/tensorboard/issues/3120>
392
+ mimetypes.add_type("text/javascript", ".js")
393
+ # Not known to be problematic, but used by TensorBoard:
394
+ mimetypes.add_type("font/woff2", ".woff2")
395
+ mimetypes.add_type("text/html", ".html")
396
+
397
+ def _start_subprocess_data_ingester(self):
398
+ """Creates, starts, and returns a `SubprocessServerDataIngester`."""
399
+ flags = self.flags
400
+ server_binary = server_ingester.get_server_binary()
401
+ ingester = server_ingester.SubprocessServerDataIngester(
402
+ server_binary=server_binary,
403
+ logdir=flags.logdir,
404
+ reload_interval=flags.reload_interval,
405
+ channel_creds_type=flags.grpc_creds_type,
406
+ samples_per_plugin=flags.samples_per_plugin,
407
+ extra_flags=shlex.split(flags.extra_data_server_flags),
408
+ )
409
+ ingester.start()
410
+ return ingester
411
+
412
+ def _make_data_ingester(self):
413
+ """Determines the right data ingester, starts it, and returns it."""
414
+ flags = self.flags
415
+ if flags.grpc_data_provider:
416
+ ingester = server_ingester.ExistingServerDataIngester(
417
+ flags.grpc_data_provider,
418
+ channel_creds_type=flags.grpc_creds_type,
419
+ )
420
+ ingester.start()
421
+ return ingester
422
+
423
+ if flags.load_fast == "true":
424
+ try:
425
+ return self._start_subprocess_data_ingester()
426
+ except server_ingester.NoDataServerError as e:
427
+ msg = "Option --load_fast=true not available: %s\n" % e
428
+ sys.stderr.write(msg)
429
+ sys.exit(1)
430
+ except server_ingester.DataServerStartupError as e:
431
+ msg = _DATA_SERVER_STARTUP_ERROR_MESSAGE_TEMPLATE % e
432
+ sys.stderr.write(msg)
433
+ sys.exit(1)
434
+
435
+ if flags.load_fast == "auto" and _should_use_data_server(flags):
436
+ try:
437
+ ingester = self._start_subprocess_data_ingester()
438
+ sys.stderr.write(_DATA_SERVER_ADVISORY_MESSAGE)
439
+ sys.stderr.flush()
440
+ return ingester
441
+ except server_ingester.NoDataServerError as e:
442
+ logger.info("No data server: %s", e)
443
+ except server_ingester.DataServerStartupError as e:
444
+ logger.info(
445
+ "Data server error: %s; falling back to multiplexer", e
446
+ )
447
+
448
+ ingester = local_ingester.LocalDataIngester(flags)
449
+ ingester.start()
450
+ return ingester
451
+
452
+ def _make_data_provider(self):
453
+ """Returns `(data_provider, deprecated_multiplexer)`."""
454
+ ingester = self._make_data_ingester()
455
+ # Stash ingester so that it can avoid GCing Windows file handles.
456
+ # (See comment in `SubprocessServerDataIngester.start` for details.)
457
+ self._ingester = ingester
458
+
459
+ deprecated_multiplexer = None
460
+ if isinstance(ingester, local_ingester.LocalDataIngester):
461
+ deprecated_multiplexer = ingester.deprecated_multiplexer
462
+ return (ingester.data_provider, deprecated_multiplexer)
463
+
464
+ def _make_server(self):
465
+ """Constructs the TensorBoard WSGI app and instantiates the server."""
466
+ data_provider, deprecated_multiplexer = self._make_data_provider()
467
+ app = application.TensorBoardWSGIApp(
468
+ self.flags,
469
+ self.plugin_loaders,
470
+ data_provider,
471
+ self.assets_zip_provider,
472
+ deprecated_multiplexer,
473
+ )
474
+ return self.server_class(app, self.flags)
475
+
476
+
477
+ def _should_use_data_server(flags):
478
+ if flags.logdir_spec and not flags.logdir:
479
+ logger.info(
480
+ "Note: --logdir_spec is not supported with --load_fast behavior; "
481
+ "falling back to slower Python-only load path. To use the data "
482
+ "server, replace --logdir_spec with --logdir."
483
+ )
484
+ return False
485
+ if not flags.logdir:
486
+ # Using some other legacy mode; not supported.
487
+ return False
488
+ if "://" in flags.logdir and not flags.logdir.startswith("gs://"):
489
+ logger.info(
490
+ "Note: --load_fast behavior only supports local and GCS (gs://) "
491
+ "paths; falling back to slower Python-only load path."
492
+ )
493
+ return False
494
+ if flags.detect_file_replacement is True:
495
+ logger.info(
496
+ "Note: --detect_file_replacement=true is not supported with "
497
+ "--load_fast behavior; falling back to slower Python-only load "
498
+ "path."
499
+ )
500
+ return False
501
+ return True
502
+
503
+
504
+ class TensorBoardSubcommand(metaclass=ABCMeta):
505
+ """Experimental private API for defining subcommands for tensorboard.
506
+
507
+ The intended use is something like:
508
+
509
+ `tensorboard <sub_cmd_name> <additional_args...>`
510
+
511
+ Since our hosted service at http://tensorbored.dev has been shut down, this
512
+ functionality is no longer used, but the support for subcommands remains,
513
+ in case it is ever useful again.
514
+ """
515
+
516
+ @abstractmethod
517
+ def name(self):
518
+ """Name of this subcommand, as specified on the command line.
519
+
520
+ This must be unique across all subcommands.
521
+
522
+ Returns:
523
+ A string.
524
+ """
525
+ pass
526
+
527
+ @abstractmethod
528
+ def define_flags(self, parser):
529
+ """Configure an argument parser for this subcommand.
530
+
531
+ Flags whose names start with two underscores (e.g., `__foo`) are
532
+ reserved for use by the runtime and must not be defined by
533
+ subcommands.
534
+
535
+ Args:
536
+ parser: An `argparse.ArgumentParser` scoped to this subcommand,
537
+ which this function should mutate.
538
+ """
539
+ pass
540
+
541
+ @abstractmethod
542
+ def run(self, flags):
543
+ """Execute this subcommand with user-provided flags.
544
+
545
+ Args:
546
+ flags: An `argparse.Namespace` object with all defined flags.
547
+
548
+ Returns:
549
+ An `int` exit code, or `None` as an alias for `0`.
550
+ """
551
+ pass
552
+
553
+ def help(self):
554
+ """Short, one-line help text to display on `tensorbored --help`."""
555
+ return None
556
+
557
+ def description(self):
558
+ """Description to display on `tensorboard SUBCOMMAND --help`."""
559
+ return None
560
+
561
+
562
+ class TensorBoardServer(metaclass=ABCMeta):
563
+ """Class for customizing TensorBoard WSGI app serving."""
564
+
565
+ @abstractmethod
566
+ def __init__(self, wsgi_app, flags):
567
+ """Create a flag-configured HTTP server for TensorBoard's WSGI app.
568
+
569
+ Args:
570
+ wsgi_app: The TensorBoard WSGI application to create a server for.
571
+ flags: argparse.Namespace instance of TensorBoard flags.
572
+ """
573
+ raise NotImplementedError()
574
+
575
+ @abstractmethod
576
+ def serve_forever(self):
577
+ """Blocking call to start serving the TensorBoard server."""
578
+ raise NotImplementedError()
579
+
580
+ @abstractmethod
581
+ def get_url(self):
582
+ """Returns a URL at which this server should be reachable."""
583
+ raise NotImplementedError()
584
+
585
+ def print_serving_message(self):
586
+ """Prints a user-friendly message prior to server start.
587
+
588
+ This will be called just before `serve_forever`.
589
+ """
590
+ sys.stderr.write(
591
+ "TensorBoard %s at %s (Press CTRL+C to quit)\n"
592
+ % (version.VERSION, self.get_url())
593
+ )
594
+ sys.stderr.flush()
595
+
596
+
597
+ class TensorBoardServerException(Exception):
598
+ """Exception raised by TensorBoardServer for user-friendly errors.
599
+
600
+ Subclasses of TensorBoardServer can raise this exception in order to
601
+ generate a clean error message for the user rather than a
602
+ stacktrace.
603
+ """
604
+
605
+ def __init__(self, msg):
606
+ self.msg = msg
607
+
608
+
609
+ class TensorBoardPortInUseError(TensorBoardServerException):
610
+ """Error raised when attempting to bind to a port that is in use.
611
+
612
+ This should be raised when it is expected that binding to another
613
+ similar port would succeed. It is used as a signal to indicate that
614
+ automatic port searching should continue rather than abort.
615
+ """
616
+
617
+ pass
618
+
619
+
620
+ def with_port_scanning(cls):
621
+ """Create a server factory that performs port scanning.
622
+
623
+ This function returns a callable whose signature matches the
624
+ specification of `TensorBoardServer.__init__`, using `cls` as an
625
+ underlying implementation. It passes through `flags` unchanged except
626
+ in the case that `flags.port is None`, in which case it repeatedly
627
+ instantiates the underlying server with new port suggestions.
628
+
629
+ Args:
630
+ cls: A valid implementation of `TensorBoardServer`. This class's
631
+ initializer should raise a `TensorBoardPortInUseError` upon
632
+ failing to bind to a port when it is expected that binding to
633
+ another nearby port might succeed.
634
+
635
+ The initializer for `cls` will only ever be invoked with `flags`
636
+ such that `flags.port is not None`.
637
+
638
+ Returns:
639
+ A function that implements the `__init__` contract of
640
+ `TensorBoardServer`.
641
+ """
642
+
643
+ def init(wsgi_app, flags):
644
+ # base_port: what's the first port to which we should try to bind?
645
+ # should_scan: if that fails, shall we try additional ports?
646
+ # max_attempts: how many ports shall we try?
647
+ should_scan = flags.port is None
648
+ base_port = (
649
+ core_plugin.DEFAULT_PORT if flags.port is None else flags.port
650
+ )
651
+
652
+ if base_port > 0xFFFF:
653
+ raise TensorBoardServerException(
654
+ "TensorBoard cannot bind to port %d > %d" % (base_port, 0xFFFF)
655
+ )
656
+ max_attempts = 100 if should_scan else 1
657
+ base_port = min(base_port + max_attempts, 0x10000) - max_attempts
658
+
659
+ for port in range(base_port, base_port + max_attempts):
660
+ subflags = argparse.Namespace(**vars(flags))
661
+ subflags.port = port
662
+ try:
663
+ return cls(wsgi_app=wsgi_app, flags=subflags)
664
+ except TensorBoardPortInUseError:
665
+ if not should_scan:
666
+ raise
667
+ # All attempts failed to bind.
668
+ raise TensorBoardServerException(
669
+ "TensorBoard could not bind to any port around %s "
670
+ "(tried %d times)" % (base_port, max_attempts)
671
+ )
672
+
673
+ return init
674
+
675
+
676
+ class _WSGIRequestHandler(serving.WSGIRequestHandler):
677
+ """Custom subclass of Werkzeug request handler to use HTTP/1.1."""
678
+
679
+ # The default on the http.server is HTTP/1.0 for legacy reasons:
680
+ # https://docs.python.org/3/library/http.server.html#http.server.BaseHTTPRequestHandler.protocol_version
681
+ # Override here to use HTTP/1.1 to avoid needing a new TCP socket and Python
682
+ # thread for each HTTP request. The tradeoff is we must always specify the
683
+ # Content-Length header, or do chunked encoding for streaming.
684
+ protocol_version = "HTTP/1.1"
685
+
686
+
687
+ class WerkzeugServer(serving.ThreadedWSGIServer, TensorBoardServer):
688
+ """Implementation of TensorBoardServer using the Werkzeug dev server."""
689
+
690
+ # ThreadedWSGIServer handles this in werkzeug 0.12+ but we allow 0.11.x.
691
+ daemon_threads = True
692
+
693
+ def __init__(self, wsgi_app, flags):
694
+ self._flags = flags
695
+ host = flags.host
696
+ port = flags.port
697
+
698
+ self._auto_wildcard = flags.bind_all
699
+ if self._auto_wildcard:
700
+ # Serve on all interfaces, and attempt to serve both IPv4 and IPv6
701
+ # traffic through one socket.
702
+ host = self._get_wildcard_address(port)
703
+ elif host is None:
704
+ host = "localhost"
705
+
706
+ self._host = host
707
+ self._url = None # Will be set by get_url() below
708
+
709
+ self._fix_werkzeug_logging()
710
+
711
+ def is_port_in_use(port):
712
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
713
+ return s.connect_ex(("localhost", port)) == 0
714
+
715
+ try:
716
+ if is_port_in_use(port):
717
+ raise TensorBoardPortInUseError(
718
+ "TensorBoard could not bind to port %d, it was already in use"
719
+ % port
720
+ )
721
+ super().__init__(host, port, wsgi_app, _WSGIRequestHandler)
722
+ except socket.error as e:
723
+ if hasattr(errno, "EACCES") and e.errno == errno.EACCES:
724
+ raise TensorBoardServerException(
725
+ "TensorBoard must be run as superuser to bind to port %d"
726
+ % port
727
+ )
728
+ elif hasattr(errno, "EADDRINUSE") and e.errno == errno.EADDRINUSE:
729
+ if port == 0:
730
+ raise TensorBoardServerException(
731
+ "TensorBoard unable to find any open port"
732
+ )
733
+ else:
734
+ raise TensorBoardPortInUseError(
735
+ "TensorBoard could not bind to port %d, it was already in use"
736
+ % port
737
+ )
738
+ elif (
739
+ hasattr(errno, "EADDRNOTAVAIL")
740
+ and e.errno == errno.EADDRNOTAVAIL
741
+ ):
742
+ raise TensorBoardServerException(
743
+ "TensorBoard could not bind to unavailable address %s"
744
+ % host
745
+ )
746
+ elif (
747
+ hasattr(errno, "EAFNOSUPPORT") and e.errno == errno.EAFNOSUPPORT
748
+ ):
749
+ raise TensorBoardServerException(
750
+ "Tensorboard could not bind to unsupported address family %s"
751
+ % host
752
+ )
753
+ # Raise the raw exception if it wasn't identifiable as a user error.
754
+ raise
755
+
756
+ def _get_wildcard_address(self, port):
757
+ """Returns a wildcard address for the port in question.
758
+
759
+ This will attempt to follow the best practice of calling
760
+ getaddrinfo() with a null host and AI_PASSIVE to request a
761
+ server-side socket wildcard address. If that succeeds, this
762
+ returns the first IPv6 address found, or if none, then returns
763
+ the first IPv4 address. If that fails, then this returns the
764
+ hardcoded address "::" if socket.has_ipv6 is True, else
765
+ "0.0.0.0".
766
+ """
767
+ fallback_address = "::" if socket.has_ipv6 else "0.0.0.0"
768
+ if hasattr(socket, "AI_PASSIVE"):
769
+ try:
770
+ addrinfos = socket.getaddrinfo(
771
+ None,
772
+ port,
773
+ socket.AF_UNSPEC,
774
+ socket.SOCK_STREAM,
775
+ socket.IPPROTO_TCP,
776
+ socket.AI_PASSIVE,
777
+ )
778
+ except socket.gaierror as e:
779
+ logger.warning(
780
+ "Failed to auto-detect wildcard address, assuming %s: %s",
781
+ fallback_address,
782
+ str(e),
783
+ )
784
+ return fallback_address
785
+ addrs_by_family = defaultdict(list)
786
+ for family, _, _, _, sockaddr in addrinfos:
787
+ # Format of the "sockaddr" socket address varies by address family,
788
+ # but [0] is always the IP address portion.
789
+ addrs_by_family[family].append(sockaddr[0])
790
+ if hasattr(socket, "AF_INET6") and addrs_by_family[socket.AF_INET6]:
791
+ return addrs_by_family[socket.AF_INET6][0]
792
+ if hasattr(socket, "AF_INET") and addrs_by_family[socket.AF_INET]:
793
+ return addrs_by_family[socket.AF_INET][0]
794
+ logger.warning(
795
+ "Failed to auto-detect wildcard address, assuming %s",
796
+ fallback_address,
797
+ )
798
+ return fallback_address
799
+
800
+ def server_bind(self):
801
+ """Override to set custom options on the socket."""
802
+ if self._flags.reuse_port:
803
+ try:
804
+ socket.SO_REUSEPORT
805
+ except AttributeError:
806
+ raise TensorBoardServerException(
807
+ "TensorBoard --reuse_port option is not supported on this platform"
808
+ )
809
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
810
+
811
+ # Enable IPV4 mapping for IPV6 sockets when desired.
812
+ # The main use case for this is so that when no host is specified,
813
+ # TensorBoard can listen on all interfaces for both IPv4 and IPv6
814
+ # connections, rather than having to choose v4 or v6 and hope the
815
+ # browser didn't choose the other one.
816
+ socket_is_v6 = (
817
+ hasattr(socket, "AF_INET6")
818
+ and self.socket.family == socket.AF_INET6
819
+ )
820
+ has_v6only_option = hasattr(socket, "IPPROTO_IPV6") and hasattr(
821
+ socket, "IPV6_V6ONLY"
822
+ )
823
+ if self._auto_wildcard and socket_is_v6 and has_v6only_option:
824
+ try:
825
+ self.socket.setsockopt(
826
+ socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0
827
+ )
828
+ except socket.error as e:
829
+ # Log a warning on failure to dual-bind, except for EAFNOSUPPORT
830
+ # since that's expected if IPv4 isn't supported at all (IPv6-only).
831
+ if (
832
+ hasattr(errno, "EAFNOSUPPORT")
833
+ and e.errno != errno.EAFNOSUPPORT
834
+ ):
835
+ logger.warning(
836
+ "Failed to dual-bind to IPv4 wildcard: %s", str(e)
837
+ )
838
+ super().server_bind()
839
+
840
+ def handle_error(self, request, client_address):
841
+ """Override to get rid of noisy EPIPE errors."""
842
+ del request # unused
843
+ # Kludge to override a SocketServer.py method so we can get rid of noisy
844
+ # EPIPE errors. They're kind of a red herring as far as errors go. For
845
+ # example, `curl -N http://localhost:6006/ | head` will cause an EPIPE.
846
+ exc_info = sys.exc_info()
847
+ e = exc_info[1]
848
+ if isinstance(e, IOError) and e.errno == errno.EPIPE:
849
+ logger.warning(
850
+ "EPIPE caused by %s in HTTP serving" % str(client_address)
851
+ )
852
+ else:
853
+ logger.error("HTTP serving error", exc_info=exc_info)
854
+
855
+ def get_url(self):
856
+ if not self._url:
857
+ if self._auto_wildcard:
858
+ display_host = socket.getfqdn()
859
+ # Confirm that the connection is open, otherwise change to `localhost`
860
+ try:
861
+ socket.create_connection(
862
+ (display_host, self.server_port), timeout=1
863
+ )
864
+ except socket.error as e:
865
+ display_host = "localhost"
866
+
867
+ else:
868
+ host = self._host
869
+ display_host = (
870
+ "[%s]" % host
871
+ if ":" in host and not host.startswith("[")
872
+ else host
873
+ )
874
+ self._url = "http://%s:%d%s/" % (
875
+ display_host,
876
+ self.server_port,
877
+ self._flags.path_prefix.rstrip("/"),
878
+ )
879
+ return self._url
880
+
881
+ def print_serving_message(self):
882
+ if self._flags.host is None and not self._flags.bind_all:
883
+ sys.stderr.write(
884
+ "Serving TensorBoard on localhost; to expose to the network, "
885
+ "use a proxy or pass --bind_all\n"
886
+ )
887
+ sys.stderr.flush()
888
+ super().print_serving_message()
889
+
890
+ def _fix_werkzeug_logging(self):
891
+ """Fix werkzeug logging setup so it inherits TensorBoard's log level.
892
+
893
+ This addresses a change in werkzeug 0.15.0+ [1] that causes it set its own
894
+ log level to INFO regardless of the root logger configuration. We instead
895
+ want werkzeug to inherit TensorBoard's root logger log level (set via absl
896
+ to WARNING by default).
897
+
898
+ [1]: https://github.com/pallets/werkzeug/commit/4cf77d25858ff46ac7e9d64ade054bf05b41ce12
899
+ """
900
+ # Log once at DEBUG to force werkzeug to initialize its singleton logger,
901
+ # which sets the logger level to INFO it if is unset, and then access that
902
+ # object via logging.getLogger('werkzeug') to durably revert the level to
903
+ # unset (and thus make messages logged to it inherit the root logger level).
904
+ self.log(
905
+ "debug", "Fixing werkzeug logger to inherit TensorBoard log level"
906
+ )
907
+ logging.getLogger("werkzeug").setLevel(logging.NOTSET)
908
+
909
+
910
+ create_port_scanning_werkzeug_server = with_port_scanning(WerkzeugServer)