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/manager.py ADDED
@@ -0,0 +1,487 @@
1
+ # Copyright 2019 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
+ """Private utilities for managing multiple TensorBoard processes."""
16
+
17
+ import base64
18
+ import dataclasses
19
+ import datetime
20
+ import errno
21
+ import json
22
+ import os
23
+ import subprocess
24
+ import tempfile
25
+ import time
26
+ import typing
27
+
28
+ from typing import Optional
29
+
30
+ from tensorbored import version
31
+ from tensorbored.util import tb_logging
32
+
33
+
34
+ @dataclasses.dataclass(frozen=True)
35
+ class TensorBoardInfo:
36
+ """Holds the information about a running TensorBoard instance.
37
+
38
+ Attributes:
39
+ version: Version of the running TensorBoard.
40
+ start_time: Seconds since epoch.
41
+ pid: ID of the process running TensorBoard.
42
+ port: Port on which TensorBoard is running.
43
+ path_prefix: Relative prefix to the path, may be empty.
44
+ logdir: Data location used by the TensorBoard server, may be empty.
45
+ db: Database connection used by the TensorBoard server, may be empty.
46
+ cache_key: Opaque, as given by `cache_key` below.
47
+ """
48
+
49
+ version: str
50
+ start_time: int
51
+ pid: int
52
+ port: int
53
+ path_prefix: str
54
+ logdir: str
55
+ db: str
56
+ cache_key: str
57
+
58
+
59
+ def data_source_from_info(info):
60
+ """Format the data location for the given TensorBoardInfo.
61
+
62
+ Args:
63
+ info: A TensorBoardInfo value.
64
+
65
+ Returns:
66
+ A human-readable string describing the logdir or database connection
67
+ used by the server: e.g., "logdir /tmp/logs".
68
+ """
69
+ if info.db:
70
+ return "db %s" % info.db
71
+ else:
72
+ return "logdir %s" % info.logdir
73
+
74
+
75
+ def _info_to_string(info):
76
+ """Convert a `TensorBoardInfo` to string form to be stored on disk.
77
+
78
+ The format returned by this function is opaque and should only be
79
+ interpreted by `_info_from_string`.
80
+
81
+ Args:
82
+ info: A valid `TensorBoardInfo` object.
83
+
84
+ Raises:
85
+ ValueError: If any field on `info` is not of the correct type.
86
+
87
+ Returns:
88
+ A string representation of the provided `TensorBoardInfo`.
89
+ """
90
+ field_name_to_type = typing.get_type_hints(TensorBoardInfo)
91
+ for key, field_type in field_name_to_type.items():
92
+ if not isinstance(getattr(info, key), field_type):
93
+ raise ValueError(
94
+ "expected %r of type %s, but found: %r"
95
+ % (key, field_type, getattr(info, key))
96
+ )
97
+ if info.version != version.VERSION:
98
+ raise ValueError(
99
+ "expected 'version' to be %r, but found: %r"
100
+ % (version.VERSION, info.version)
101
+ )
102
+ json_value = dataclasses.asdict(info)
103
+ return json.dumps(json_value, sort_keys=True, indent=4)
104
+
105
+
106
+ def _info_from_string(info_string):
107
+ """Parse a `TensorBoardInfo` object from its string representation.
108
+
109
+ Args:
110
+ info_string: A string representation of a `TensorBoardInfo`, as
111
+ produced by a previous call to `_info_to_string`.
112
+
113
+ Returns:
114
+ A `TensorBoardInfo` value.
115
+
116
+ Raises:
117
+ ValueError: If the provided string is not valid JSON, or if it is
118
+ missing any required fields, or if any field is of incorrect type.
119
+ """
120
+ field_name_to_type = typing.get_type_hints(TensorBoardInfo)
121
+ try:
122
+ json_value = json.loads(info_string)
123
+ except ValueError:
124
+ raise ValueError("invalid JSON: %r" % (info_string,))
125
+ if not isinstance(json_value, dict):
126
+ raise ValueError("not a JSON object: %r" % (json_value,))
127
+ expected_keys = frozenset(field_name_to_type.keys())
128
+ actual_keys = frozenset(json_value)
129
+ missing_keys = expected_keys - actual_keys
130
+ if missing_keys:
131
+ raise ValueError(
132
+ "TensorBoardInfo missing keys: %r" % (sorted(missing_keys),)
133
+ )
134
+ # For forward compatibility, silently ignore unknown keys.
135
+
136
+ # Validate and deserialize fields.
137
+ fields = {}
138
+ for key, field_type in field_name_to_type.items():
139
+ if not isinstance(json_value[key], field_type):
140
+ raise ValueError(
141
+ "expected %r of type %s, but found: %r"
142
+ % (key, field_type, json_value[key])
143
+ )
144
+ fields[key] = json_value[key]
145
+
146
+ return TensorBoardInfo(**fields)
147
+
148
+
149
+ def cache_key(working_directory, arguments, configure_kwargs):
150
+ """Compute a `TensorBoardInfo.cache_key` field.
151
+
152
+ The format returned by this function is opaque. Clients may only
153
+ inspect it by comparing it for equality with other results from this
154
+ function.
155
+
156
+ Args:
157
+ working_directory: The directory from which TensorBoard was launched
158
+ and relative to which paths like `--logdir` and `--db` are
159
+ resolved.
160
+ arguments: The command-line args to TensorBoard, as `sys.argv[1:]`.
161
+ Should be a list (or tuple), not an unparsed string. If you have a
162
+ raw shell command, use `shlex.split` before passing it to this
163
+ function.
164
+ configure_kwargs: A dictionary of additional argument values to
165
+ override the textual `arguments`, with the same semantics as in
166
+ `tensorboard.program.TensorBoard.configure`. May be an empty
167
+ dictionary.
168
+
169
+ Returns:
170
+ A string such that if two (prospective or actual) TensorBoard
171
+ invocations have the same cache key then it is safe to use one in
172
+ place of the other. The converse is not guaranteed: it is often safe
173
+ to change the order of TensorBoard arguments, or to explicitly set
174
+ them to their default values, or to move them between `arguments`
175
+ and `configure_kwargs`, but such invocations may yield distinct
176
+ cache keys.
177
+ """
178
+ if not isinstance(arguments, (list, tuple)):
179
+ raise TypeError(
180
+ "'arguments' should be a list of arguments, but found: %r "
181
+ "(use `shlex.split` if given a string)" % (arguments,)
182
+ )
183
+ datum = {
184
+ "working_directory": working_directory,
185
+ "arguments": arguments,
186
+ "configure_kwargs": configure_kwargs,
187
+ }
188
+ raw = base64.b64encode(
189
+ json.dumps(datum, sort_keys=True, separators=(",", ":")).encode("utf-8")
190
+ )
191
+ # `raw` is of type `bytes`, even though it only contains ASCII
192
+ # characters; we want it to be `str` in both Python 2 and 3.
193
+ return str(raw.decode("ascii"))
194
+
195
+
196
+ def _get_info_dir():
197
+ """Get path to directory in which to store info files.
198
+
199
+ The directory returned by this function is "owned" by this module. If
200
+ the contents of the directory are modified other than via the public
201
+ functions of this module, subsequent behavior is undefined.
202
+
203
+ The directory will be created if it does not exist.
204
+ """
205
+ path = os.path.join(tempfile.gettempdir(), ".tensorboard-info")
206
+ try:
207
+ os.makedirs(path)
208
+ except OSError as e:
209
+ if e.errno == errno.EEXIST and os.path.isdir(path):
210
+ pass
211
+ else:
212
+ raise
213
+ else:
214
+ os.chmod(path, 0o777)
215
+ return path
216
+
217
+
218
+ def _get_info_file_path():
219
+ """Get path to info file for the current process.
220
+
221
+ As with `_get_info_dir`, the info directory will be created if it
222
+ does not exist.
223
+ """
224
+ return os.path.join(_get_info_dir(), "pid-%d.info" % os.getpid())
225
+
226
+
227
+ def write_info_file(tensorboard_info):
228
+ """Write TensorBoardInfo to the current process's info file.
229
+
230
+ This should be called by `main` once the server is ready. When the
231
+ server shuts down, `remove_info_file` should be called.
232
+
233
+ Args:
234
+ tensorboard_info: A valid `TensorBoardInfo` object.
235
+
236
+ Raises:
237
+ ValueError: If any field on `info` is not of the correct type.
238
+ """
239
+ payload = "%s\n" % _info_to_string(tensorboard_info)
240
+ with open(_get_info_file_path(), "w") as outfile:
241
+ outfile.write(payload)
242
+
243
+
244
+ def remove_info_file():
245
+ """Remove the current process's TensorBoardInfo file, if it exists.
246
+
247
+ If the file does not exist, no action is taken and no error is
248
+ raised.
249
+ """
250
+ try:
251
+ os.unlink(_get_info_file_path())
252
+ except OSError as e:
253
+ if e.errno == errno.ENOENT:
254
+ # The user may have wiped their temporary directory or something.
255
+ # Not a problem: we're already in the state that we want to be in.
256
+ pass
257
+ else:
258
+ raise
259
+
260
+
261
+ def get_all():
262
+ """Return TensorBoardInfo values for running TensorBoard processes.
263
+
264
+ This function may not provide a perfect snapshot of the set of running
265
+ processes. Its result set may be incomplete if the user has cleaned
266
+ their /tmp/ directory while TensorBoard processes are running. It may
267
+ contain extraneous entries if TensorBoard processes exited uncleanly
268
+ (e.g., with SIGKILL or SIGQUIT).
269
+
270
+ Entries in the info directory that do not represent valid
271
+ `TensorBoardInfo` values will be silently ignored.
272
+
273
+ Returns:
274
+ A fresh list of `TensorBoardInfo` objects.
275
+ """
276
+ info_dir = _get_info_dir()
277
+ results = []
278
+ for filename in os.listdir(info_dir):
279
+ filepath = os.path.join(info_dir, filename)
280
+ try:
281
+ with open(filepath) as infile:
282
+ contents = infile.read()
283
+ except IOError as e:
284
+ if e.errno == errno.EACCES:
285
+ # May have been written by this module in a process whose
286
+ # `umask` includes some bits of 0o444.
287
+ continue
288
+ else:
289
+ raise
290
+ try:
291
+ info = _info_from_string(contents)
292
+ except ValueError:
293
+ # Ignore unrecognized files, logging at debug only.
294
+ tb_logging.get_logger().debug(
295
+ "invalid info file: %r",
296
+ filepath,
297
+ exc_info=True,
298
+ )
299
+ else:
300
+ results.append(info)
301
+ return results
302
+
303
+
304
+ @dataclasses.dataclass(frozen=True)
305
+ class StartReused:
306
+ """Possible return value of the `start` function.
307
+
308
+ Indicates that a call to `start` was compatible with an existing
309
+ TensorBoard process, which can be reused according to the provided
310
+ info.
311
+
312
+ Attributes:
313
+ info: A `TensorBoardInfo` object.
314
+ """
315
+
316
+ info: TensorBoardInfo
317
+
318
+
319
+ @dataclasses.dataclass(frozen=True)
320
+ class StartLaunched:
321
+ """Possible return value of the `start` function.
322
+
323
+ Indicates that a call to `start` successfully launched a new
324
+ TensorBoard process, which is available with the provided info.
325
+
326
+ Attributes:
327
+ info: A `TensorBoardInfo` object.
328
+ """
329
+
330
+ info: TensorBoardInfo
331
+
332
+
333
+ @dataclasses.dataclass(frozen=True)
334
+ class StartFailed:
335
+ """Possible return value of the `start` function.
336
+
337
+ Indicates that a call to `start` tried to launch a new TensorBoard
338
+ instance, but the subprocess exited with the given exit code and
339
+ output streams. (If the contents of the output streams are no longer
340
+ available---e.g., because the user has emptied /tmp/---then the
341
+ corresponding values will be `None`.)
342
+
343
+ Attributes:
344
+ exit_code: As `Popen.returncode` (negative for signal).
345
+ stdout: Error message to stdout if the stream could not be read.
346
+ stderr: Error message to stderr if the stream could not be read.
347
+ """
348
+
349
+ exit_code: int
350
+ stdout: Optional[str]
351
+ stderr: Optional[str]
352
+
353
+
354
+ @dataclasses.dataclass(frozen=True)
355
+ class StartExecFailed:
356
+ """Possible return value of the `start` function.
357
+
358
+ Indicates that a call to `start` failed to invoke the subprocess.
359
+
360
+ Attributes:
361
+ os_error: `OSError` due to `Popen` invocation.
362
+ explicit_binary: If the TensorBoard executable was chosen via the
363
+ `TENSORBOARD_BINARY` environment variable, then this field contains
364
+ the path to that binary; otherwise `None`.
365
+ """
366
+
367
+ os_error: OSError
368
+ explicit_binary: Optional[str]
369
+
370
+
371
+ @dataclasses.dataclass(frozen=True)
372
+ class StartTimedOut:
373
+ """Possible return value of the `start` function.
374
+
375
+ Indicates that a call to `start` launched a TensorBoard process, but
376
+ that process neither exited nor wrote its info file within the allowed
377
+ timeout period. The process may still be running under the included
378
+ PID.
379
+
380
+ Attributes:
381
+ pid: ID of the process running TensorBoard.
382
+ """
383
+
384
+ pid: int
385
+
386
+
387
+ def start(arguments, timeout=datetime.timedelta(seconds=60)):
388
+ """Start a new TensorBoard instance, or reuse a compatible one.
389
+
390
+ If the cache key determined by the provided arguments and the current
391
+ working directory (see `cache_key`) matches the cache key of a running
392
+ TensorBoard process (see `get_all`), that process will be reused.
393
+
394
+ Otherwise, a new TensorBoard process will be spawned with the provided
395
+ arguments, using the `tensorbored` binary from the system path.
396
+
397
+ Args:
398
+ arguments: List of strings to be passed as arguments to
399
+ `tensorboard`. (If you have a raw command-line string, see
400
+ `shlex.split`.)
401
+ timeout: `datetime.timedelta` object describing how long to wait for
402
+ the subprocess to initialize a TensorBoard server and write its
403
+ `TensorBoardInfo` file. If the info file is not written within
404
+ this time period, `start` will assume that the subprocess is stuck
405
+ in a bad state, and will give up on waiting for it and return a
406
+ `StartTimedOut` result. Note that in such a case the subprocess
407
+ will not be killed. Default value is 60 seconds.
408
+
409
+ Returns:
410
+ A `StartReused`, `StartLaunched`, `StartFailed`, or `StartTimedOut`
411
+ object.
412
+ """
413
+ this_cache_key = cache_key(
414
+ working_directory=os.getcwd(),
415
+ arguments=arguments,
416
+ configure_kwargs={},
417
+ )
418
+ match = _find_matching_instance(this_cache_key)
419
+ if match:
420
+ return StartReused(info=match)
421
+
422
+ stdout_fd, stdout_path = tempfile.mkstemp(prefix=".tensorboard-stdout-")
423
+ stderr_fd, stderr_path = tempfile.mkstemp(prefix=".tensorboard-stderr-")
424
+ start_time_seconds = time.time()
425
+ explicit_tb = os.environ.get("TENSORBOARD_BINARY", None)
426
+ try:
427
+ p = subprocess.Popen(
428
+ ["tensorbored" if explicit_tb is None else explicit_tb] + arguments,
429
+ stdout=stdout_fd,
430
+ stderr=stderr_fd,
431
+ )
432
+ except OSError as e:
433
+ return StartExecFailed(os_error=e, explicit_binary=explicit_tb)
434
+ finally:
435
+ os.close(stdout_fd)
436
+ os.close(stderr_fd)
437
+
438
+ poll_interval_seconds = 0.5
439
+ end_time_seconds = start_time_seconds + timeout.total_seconds()
440
+ while time.time() < end_time_seconds:
441
+ time.sleep(poll_interval_seconds)
442
+ subprocess_result = p.poll()
443
+ if subprocess_result is not None:
444
+ return StartFailed(
445
+ exit_code=subprocess_result,
446
+ stdout=_maybe_read_file(stdout_path),
447
+ stderr=_maybe_read_file(stderr_path),
448
+ )
449
+ info = _find_matching_instance(this_cache_key)
450
+ if info:
451
+ # Don't check that `info.pid == p.pid`, since on Windows that may
452
+ # not be the case: see #4300.
453
+ return StartLaunched(info=info)
454
+ else:
455
+ return StartTimedOut(pid=p.pid)
456
+
457
+
458
+ def _find_matching_instance(cache_key):
459
+ """Find a running TensorBoard instance compatible with the cache key.
460
+
461
+ Returns:
462
+ A `TensorBoardInfo` object, or `None` if none matches the cache key.
463
+ """
464
+ infos = get_all()
465
+ candidates = [info for info in infos if info.cache_key == cache_key]
466
+ for candidate in sorted(candidates, key=lambda x: x.port):
467
+ # TODO(@wchargin): Check here that the provided port is still live.
468
+ return candidate
469
+ return None
470
+
471
+
472
+ def _maybe_read_file(filename):
473
+ """Read the given file, if it exists.
474
+
475
+ Args:
476
+ filename: A path to a file.
477
+
478
+ Returns:
479
+ A string containing the file contents, or `None` if the file does
480
+ not exist.
481
+ """
482
+ try:
483
+ with open(filename) as infile:
484
+ return infile.read()
485
+ except IOError as e:
486
+ if e.errno == errno.ENOENT:
487
+ return None