Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tensorflow/python/ops/summary_ops_v2.py: 28%
502 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
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# ==============================================================================
16"""Operations to emit summaries."""
18import abc
19import collections
20import functools
21import os
22import re
23import threading
25from tensorflow.core.framework import graph_pb2
26from tensorflow.core.framework import summary_pb2
27from tensorflow.core.protobuf import config_pb2
28from tensorflow.python.eager import context
29from tensorflow.python.eager import profiler as _profiler
30from tensorflow.python.framework import constant_op
31from tensorflow.python.framework import dtypes
32from tensorflow.python.framework import ops
33from tensorflow.python.framework import smart_cond
34from tensorflow.python.framework import tensor_util
35from tensorflow.python.ops import array_ops
36from tensorflow.python.ops import control_flow_ops
37from tensorflow.python.ops import gen_resource_variable_ops
38from tensorflow.python.ops import gen_summary_ops
39from tensorflow.python.ops import math_ops
40from tensorflow.python.ops import resource_variable_ops
41from tensorflow.python.ops import summary_op_util
42from tensorflow.python.platform import tf_logging as logging
43from tensorflow.python.trackable import resource
44from tensorflow.python.training import training_util
45from tensorflow.python.util import deprecation
46from tensorflow.python.util import tf_contextlib
47from tensorflow.python.util.tf_export import tf_export
49# Name for graph collection of summary writer init ops, which is only exposed
50# as a legacy API for tf.contrib.summary in TF 1.x.
51_SUMMARY_WRITER_INIT_COLLECTION_NAME = "_SUMMARY_WRITER_V2"
54class _SummaryState(threading.local):
56 def __init__(self):
57 super(_SummaryState, self).__init__()
58 self.is_recording = None
59 # TODO(slebedev): why a separate flag for DS and is it on by default?
60 self.is_recording_distribution_strategy = True
61 self.writer = None
62 self.step = None
65_summary_state = _SummaryState()
68class _SummaryContextManager:
69 """Context manager to implement SummaryWriter.as_default()."""
70 # Note: this is a class so that it's possible to implement `set_as_default()`
71 # simply via `as_default().__enter__()`. We can't do that with @contextmanager
72 # because the `finally` block will be executed when the generator is GCed.
74 def __init__(self, writer, step=None):
75 self._writer = writer
76 self._step = step
77 self._old_writer = None
78 self._old_step = None
80 def __enter__(self):
81 self._old_writer = _summary_state.writer
82 _summary_state.writer = self._writer
83 if self._step is not None:
84 self._old_step = _summary_state.step
85 _summary_state.step = self._step
86 return self._writer
88 def __exit__(self, *exc):
89 # Flushes the summary writer in eager mode or in graph functions, but
90 # not in legacy graph mode (you're on your own there).
91 _summary_state.writer.flush()
92 _summary_state.writer = self._old_writer
93 if self._step is not None:
94 _summary_state.step = self._old_step
95 return False
98def _should_record_summaries_internal(default_state):
99 """Returns boolean Tensor if summaries should/shouldn't be recorded.
101 Now the summary condition is decided by logical "and" of below conditions:
102 First, summary writer must be set. Given this constraint is met,
103 ctx.summary_recording and ctx.summary_recording_distribution_strategy.
104 The former one is usually set by user, and the latter one is controlled
105 by DistributionStrategy (tf.distribute.ReplicaContext).
107 Args:
108 default_state: can be True or False. The default summary behavior when
109 summary writer is set and the user does not specify
110 ctx.summary_recording and ctx.summary_recording_distribution_strategy
111 is True.
112 """
113 if _summary_state.writer is None:
114 return constant_op.constant(False)
116 if not callable(_summary_state.is_recording):
117 static_cond = tensor_util.constant_value(_summary_state.is_recording)
118 if static_cond is not None and not static_cond:
119 return constant_op.constant(False)
121 resolve = lambda x: x() if callable(x) else x
122 cond_distributed = resolve(_summary_state.is_recording_distribution_strategy)
123 cond = resolve(_summary_state.is_recording)
124 if cond is None:
125 cond = default_state
126 return math_ops.logical_and(cond_distributed, cond)
129@tf_export("summary.should_record_summaries", v1=[])
130def should_record_summaries():
131 """Returns boolean Tensor which is True if summaries will be recorded.
133 If no default summary writer is currently registered, this always returns
134 False. Otherwise, this reflects the recording condition has been set via
135 `tf.summary.record_if()` (except that it may return False for some replicas
136 when using `tf.distribute.Strategy`). If no recording condition is active,
137 it defaults to True.
138 """
139 return _should_record_summaries_internal(default_state=True)
142# Legacy symbol used by tf.contrib.summary.should_record_summaries.
143def _legacy_contrib_should_record_summaries():
144 """Returns boolean Tensor which is true if summaries should be recorded."""
145 return _should_record_summaries_internal(default_state=False)
148@tf_export("summary.record_if", v1=[])
149@tf_contextlib.contextmanager
150def record_if(condition):
151 """Sets summary recording on or off per the provided boolean value.
153 The provided value can be a python boolean, a scalar boolean Tensor, or
154 or a callable providing such a value; if a callable is passed it will be
155 invoked on-demand to determine whether summary writing will occur. Note that
156 when calling record_if() in an eager mode context, if you intend to provide a
157 varying condition like `step % 100 == 0`, you must wrap this in a
158 callable to avoid immediate eager evaluation of the condition. In particular,
159 using a callable is the only way to have your condition evaluated as part of
160 the traced body of an @tf.function that is invoked from within the
161 `record_if()` context.
163 Args:
164 condition: can be True, False, a bool Tensor, or a callable providing such.
166 Yields:
167 Returns a context manager that sets this value on enter and restores the
168 previous value on exit.
169 """
170 old = _summary_state.is_recording
171 try:
172 _summary_state.is_recording = condition
173 yield
174 finally:
175 _summary_state.is_recording = old
178def has_default_writer():
179 """Returns a boolean indicating whether a default summary writer exists."""
180 return _summary_state.writer is not None
183# TODO(apassos) consider how to handle local step here.
184def record_summaries_every_n_global_steps(n, global_step=None):
185 """Sets the should_record_summaries Tensor to true if global_step % n == 0."""
186 if global_step is None:
187 global_step = training_util.get_or_create_global_step()
188 with ops.device("cpu:0"):
189 should = lambda: math_ops.equal(global_step % n, 0)
190 if not context.executing_eagerly():
191 should = should()
192 return record_if(should)
195def always_record_summaries():
196 """Sets the should_record_summaries Tensor to always true."""
197 return record_if(True)
200def never_record_summaries():
201 """Sets the should_record_summaries Tensor to always false."""
202 return record_if(False)
205@tf_export("summary.experimental.get_step", v1=[])
206def get_step():
207 """Returns the default summary step for the current thread.
209 Returns:
210 The step set by `tf.summary.experimental.set_step()` if one has been set,
211 otherwise None.
212 """
213 return _summary_state.step
216@tf_export("summary.experimental.set_step", v1=[])
217def set_step(step):
218 """Sets the default summary step for the current thread.
220 For convenience, this function sets a default value for the `step` parameter
221 used in summary-writing functions elsewhere in the API so that it need not
222 be explicitly passed in every such invocation. The value can be a constant
223 or a variable, and can be retrieved via `tf.summary.experimental.get_step()`.
225 Note: when using this with @tf.functions, the step value will be captured at
226 the time the function is traced, so changes to the step outside the function
227 will not be reflected inside the function unless using a `tf.Variable` step.
229 Args:
230 step: An `int64`-castable default step value, or None to unset.
231 """
232 _summary_state.step = step
235@tf_export("summary.SummaryWriter", v1=[])
236class SummaryWriter(metaclass=abc.ABCMeta):
237 """Interface representing a stateful summary writer object."""
239 def set_as_default(self, step=None):
240 """Enables this summary writer for the current thread.
242 For convenience, if `step` is not None, this function also sets a default
243 value for the `step` parameter used in summary-writing functions elsewhere
244 in the API so that it need not be explicitly passed in every such
245 invocation. The value can be a constant or a variable.
247 Note: when setting `step` in a @tf.function, the step value will be
248 captured at the time the function is traced, so changes to the step outside
249 the function will not be reflected inside the function unless using
250 a `tf.Variable` step.
252 Args:
253 step: An `int64`-castable default step value, or `None`. When not `None`,
254 the current step is modified to the given value. When `None`, the
255 current step is not modified.
256 """
257 self.as_default(step).__enter__()
259 def as_default(self, step=None):
260 """Returns a context manager that enables summary writing.
262 For convenience, if `step` is not None, this function also sets a default
263 value for the `step` parameter used in summary-writing functions elsewhere
264 in the API so that it need not be explicitly passed in every such
265 invocation. The value can be a constant or a variable.
267 Note: when setting `step` in a @tf.function, the step value will be
268 captured at the time the function is traced, so changes to the step outside
269 the function will not be reflected inside the function unless using
270 a `tf.Variable` step.
272 For example, `step` can be used as:
274 ```python
275 with writer_a.as_default(step=10):
276 tf.summary.scalar(tag, value) # Logged to writer_a with step 10
277 with writer_b.as_default(step=20):
278 tf.summary.scalar(tag, value) # Logged to writer_b with step 20
279 tf.summary.scalar(tag, value) # Logged to writer_a with step 10
280 ```
282 Args:
283 step: An `int64`-castable default step value, or `None`. When not `None`,
284 the current step is captured, replaced by a given one, and the original
285 one is restored when the context manager exits. When `None`, the current
286 step is not modified (and not restored when the context manager exits).
288 Returns:
289 The context manager.
290 """
291 return _SummaryContextManager(self, step)
293 def init(self):
294 """Initializes the summary writer."""
295 raise NotImplementedError()
297 def flush(self):
298 """Flushes any buffered data."""
299 raise NotImplementedError()
301 def close(self):
302 """Flushes and closes the summary writer."""
303 raise NotImplementedError()
306class _ResourceSummaryWriter(SummaryWriter):
307 """Implementation of SummaryWriter using a SummaryWriterInterface resource."""
309 def __init__(self, create_fn, init_op_fn):
310 self._resource = create_fn()
311 self._init_op = init_op_fn(self._resource)
312 self._closed = False
313 if context.executing_eagerly():
314 self._set_up_resource_deleter()
315 else:
316 ops.add_to_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME, self._init_op)
318 # Extension point to be overridden by subclasses to customize deletion.
320 def _set_up_resource_deleter(self):
321 self._resource_deleter = resource_variable_ops.EagerResourceDeleter(
322 handle=self._resource, handle_device="cpu:0")
324 def set_as_default(self, step=None):
325 """See `SummaryWriter.set_as_default`."""
326 if context.executing_eagerly() and self._closed:
327 raise RuntimeError(f"SummaryWriter {self!r} is already closed")
328 super().set_as_default(step)
330 def as_default(self, step=None):
331 """See `SummaryWriter.as_default`."""
332 if context.executing_eagerly() and self._closed:
333 raise RuntimeError(f"SummaryWriter {self!r} is already closed")
334 return super().as_default(step)
336 def init(self):
337 """See `SummaryWriter.init`."""
338 if context.executing_eagerly() and self._closed:
339 raise RuntimeError(f"SummaryWriter {self!r} is already closed")
340 return self._init_op
342 def flush(self):
343 """See `SummaryWriter.flush`."""
344 if context.executing_eagerly() and self._closed:
345 return
346 with ops.device("cpu:0"):
347 return gen_summary_ops.flush_summary_writer(self._resource)
349 def close(self):
350 """See `SummaryWriter.close`."""
351 if context.executing_eagerly() and self._closed:
352 return
353 try:
354 with ops.control_dependencies([self.flush()]):
355 with ops.device("cpu:0"):
356 return gen_summary_ops.close_summary_writer(self._resource)
357 finally:
358 if context.executing_eagerly():
359 self._closed = True
362class _MultiMetaclass(
363 type(_ResourceSummaryWriter), type(resource.TrackableResource)):
364 pass
367class _TrackableResourceSummaryWriter(
368 _ResourceSummaryWriter,
369 resource.TrackableResource,
370 metaclass=_MultiMetaclass):
371 """A `_ResourceSummaryWriter` subclass that implements `TrackableResource`."""
373 def __init__(self, create_fn, init_op_fn):
374 # Resolve multiple inheritance via explicit calls to __init__() on parents.
375 resource.TrackableResource.__init__(self, device="/CPU:0")
376 self._create_fn = create_fn
377 self._init_op_fn = init_op_fn
378 # Pass .resource_handle into _ResourceSummaryWriter parent class rather than
379 # create_fn, to ensure it accesses the resource handle only through the
380 # cached property so that everything is using a single resource handle.
381 _ResourceSummaryWriter.__init__(
382 self, create_fn=lambda: self.resource_handle, init_op_fn=init_op_fn)
384 # Override for TrackableResource implementation.
385 def _create_resource(self):
386 return self._create_fn()
388 # Override for TrackableResource implementation.
389 def _initialize(self):
390 return self._init_op_fn(self.resource_handle)
392 # Override for TrackableResource implementation.
393 def _destroy_resource(self):
394 gen_resource_variable_ops.destroy_resource_op(
395 self.resource_handle, ignore_lookup_error=True)
397 def _set_up_resource_deleter(self):
398 # Override to suppress ResourceSummaryWriter implementation; we don't need
399 # the deleter since TrackableResource.__del__() handles it for us.
400 pass
403class _LegacyResourceSummaryWriter(SummaryWriter):
404 """Legacy resource-backed SummaryWriter for tf.contrib.summary."""
406 def __init__(self, resource, init_op_fn):
407 self._resource = resource
408 self._init_op_fn = init_op_fn
409 init_op = self.init()
410 if context.executing_eagerly():
411 self._resource_deleter = resource_variable_ops.EagerResourceDeleter(
412 handle=self._resource, handle_device="cpu:0")
413 else:
414 ops.add_to_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME, init_op)
416 def init(self):
417 """See `SummaryWriter.init`."""
418 return self._init_op_fn(self._resource)
420 def flush(self):
421 """See `SummaryWriter.flush`."""
422 with ops.device("cpu:0"):
423 return gen_summary_ops.flush_summary_writer(self._resource)
425 def close(self):
426 """See `SummaryWriter.close`."""
427 with ops.control_dependencies([self.flush()]):
428 with ops.device("cpu:0"):
429 return gen_summary_ops.close_summary_writer(self._resource)
432class _NoopSummaryWriter(SummaryWriter):
433 """A summary writer that does nothing, for create_noop_writer()."""
435 def set_as_default(self, step=None):
436 pass
438 @tf_contextlib.contextmanager
439 def as_default(self, step=None):
440 yield
442 def init(self):
443 pass
445 def flush(self):
446 pass
448 def close(self):
449 pass
452@tf_export(v1=["summary.initialize"])
453def initialize(
454 graph=None, # pylint: disable=redefined-outer-name
455 session=None):
456 """Initializes summary writing for graph execution mode.
458 This operation is a no-op when executing eagerly.
460 This helper method provides a higher-level alternative to using
461 `tf.contrib.summary.summary_writer_initializer_op` and
462 `tf.contrib.summary.graph`.
464 Most users will also want to call `tf.compat.v1.train.create_global_step`
465 which can happen before or after this function is called.
467 Args:
468 graph: A `tf.Graph` or `tf.compat.v1.GraphDef` to output to the writer.
469 This function will not write the default graph by default. When
470 writing to an event log file, the associated step will be zero.
471 session: So this method can call `tf.Session.run`. This defaults
472 to `tf.compat.v1.get_default_session`.
474 Raises:
475 RuntimeError: If the current thread has no default
476 `tf.contrib.summary.SummaryWriter`.
477 ValueError: If session wasn't passed and no default session.
478 """
479 if context.executing_eagerly():
480 return
481 if _summary_state.writer is None:
482 raise RuntimeError("No default tf.contrib.summary.SummaryWriter found")
483 if session is None:
484 session = ops.get_default_session()
485 if session is None:
486 raise ValueError("Argument `session must be passed if no default "
487 "session exists")
488 session.run(summary_writer_initializer_op())
489 if graph is not None:
490 data = _serialize_graph(graph)
491 x = array_ops.placeholder(dtypes.string)
492 session.run(graph_v1(x, 0), feed_dict={x: data})
495@tf_export("summary.create_file_writer", v1=[])
496def create_file_writer_v2(logdir,
497 max_queue=None,
498 flush_millis=None,
499 filename_suffix=None,
500 name=None,
501 experimental_trackable=False):
502 """Creates a summary file writer for the given log directory.
504 Args:
505 logdir: a string specifying the directory in which to write an event file.
506 max_queue: the largest number of summaries to keep in a queue; will
507 flush once the queue gets bigger than this. Defaults to 10.
508 flush_millis: the largest interval between flushes. Defaults to 120,000.
509 filename_suffix: optional suffix for the event file name. Defaults to `.v2`.
510 name: a name for the op that creates the writer.
511 experimental_trackable: a boolean that controls whether the returned writer
512 will be a `TrackableResource`, which makes it compatible with SavedModel
513 when used as a `tf.Module` property.
515 Returns:
516 A SummaryWriter object.
517 """
518 if logdir is None:
519 raise ValueError("Argument `logdir` cannot be None")
520 inside_function = ops.inside_function()
521 with ops.name_scope(name, "create_file_writer") as scope, ops.device("cpu:0"):
522 # Run init inside an init_scope() to hoist it out of tf.functions.
523 with ops.init_scope():
524 if context.executing_eagerly():
525 _check_create_file_writer_args(
526 inside_function,
527 logdir=logdir,
528 max_queue=max_queue,
529 flush_millis=flush_millis,
530 filename_suffix=filename_suffix)
531 logdir = ops.convert_to_tensor(logdir, dtype=dtypes.string)
532 if max_queue is None:
533 max_queue = constant_op.constant(10)
534 if flush_millis is None:
535 flush_millis = constant_op.constant(2 * 60 * 1000)
536 if filename_suffix is None:
537 filename_suffix = constant_op.constant(".v2")
539 def create_fn():
540 # Use unique shared_name to prevent resource sharing in eager mode, but
541 # otherwise use a fixed shared_name to allow SavedModel TF 1.x loading.
542 if context.executing_eagerly():
543 shared_name = context.anonymous_name()
544 else:
545 shared_name = ops.name_from_scope_name(scope) # pylint: disable=protected-access
546 return gen_summary_ops.summary_writer(
547 shared_name=shared_name, name=name)
549 init_op_fn = functools.partial(
550 gen_summary_ops.create_summary_file_writer,
551 logdir=logdir,
552 max_queue=max_queue,
553 flush_millis=flush_millis,
554 filename_suffix=filename_suffix)
555 if experimental_trackable:
556 return _TrackableResourceSummaryWriter(
557 create_fn=create_fn, init_op_fn=init_op_fn)
558 else:
559 return _ResourceSummaryWriter(
560 create_fn=create_fn, init_op_fn=init_op_fn)
563def create_file_writer(logdir,
564 max_queue=None,
565 flush_millis=None,
566 filename_suffix=None,
567 name=None):
568 """Creates a summary file writer in the current context under the given name.
570 Args:
571 logdir: a string, or None. If a string, creates a summary file writer
572 which writes to the directory named by the string. If None, returns
573 a mock object which acts like a summary writer but does nothing,
574 useful to use as a context manager.
575 max_queue: the largest number of summaries to keep in a queue; will
576 flush once the queue gets bigger than this. Defaults to 10.
577 flush_millis: the largest interval between flushes. Defaults to 120,000.
578 filename_suffix: optional suffix for the event file name. Defaults to `.v2`.
579 name: Shared name for this SummaryWriter resource stored to default
580 Graph. Defaults to the provided logdir prefixed with `logdir:`. Note: if a
581 summary writer resource with this shared name already exists, the returned
582 SummaryWriter wraps that resource and the other arguments have no effect.
584 Returns:
585 Either a summary writer or an empty object which can be used as a
586 summary writer.
587 """
588 if logdir is None:
589 return _NoopSummaryWriter()
590 logdir = str(logdir)
591 with ops.device("cpu:0"):
592 if max_queue is None:
593 max_queue = constant_op.constant(10)
594 if flush_millis is None:
595 flush_millis = constant_op.constant(2 * 60 * 1000)
596 if filename_suffix is None:
597 filename_suffix = constant_op.constant(".v2")
598 if name is None:
599 name = "logdir:" + logdir
600 resource = gen_summary_ops.summary_writer(shared_name=name)
601 return _LegacyResourceSummaryWriter(
602 resource=resource,
603 init_op_fn=functools.partial(
604 gen_summary_ops.create_summary_file_writer,
605 logdir=logdir,
606 max_queue=max_queue,
607 flush_millis=flush_millis,
608 filename_suffix=filename_suffix))
611@tf_export("summary.create_noop_writer", v1=[])
612def create_noop_writer():
613 """Returns a summary writer that does nothing.
615 This is useful as a placeholder in code that expects a context manager.
616 """
617 return _NoopSummaryWriter()
620def _cleanse_string(name, pattern, value):
621 if isinstance(value, str) and pattern.search(value) is None:
622 raise ValueError(f"{name} ({value}) must match {pattern.pattern}")
623 return ops.convert_to_tensor(value, dtypes.string)
626def _nothing():
627 """Convenient else branch for when summaries do not record."""
628 return constant_op.constant(False)
631@tf_export(v1=["summary.all_v2_summary_ops"])
632def all_v2_summary_ops():
633 """Returns all V2-style summary ops defined in the current default graph.
635 This includes ops from TF 2.0 tf.summary and TF 1.x tf.contrib.summary (except
636 for `tf.contrib.summary.graph` and `tf.contrib.summary.import_event`), but
637 does *not* include TF 1.x tf.summary ops.
639 Returns:
640 List of summary ops, or None if called under eager execution.
641 """
642 if context.executing_eagerly():
643 return None
644 return ops.get_collection(ops.GraphKeys._SUMMARY_COLLECTION) # pylint: disable=protected-access
647def summary_writer_initializer_op():
648 """Graph-mode only. Returns the list of ops to create all summary writers.
650 Returns:
651 The initializer ops.
653 Raises:
654 RuntimeError: If in Eager mode.
655 """
656 if context.executing_eagerly():
657 raise RuntimeError(
658 "tf.contrib.summary.summary_writer_initializer_op is only "
659 "supported in graph mode.")
660 return ops.get_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME)
663_INVALID_SCOPE_CHARACTERS = re.compile(r"[^-_/.A-Za-z0-9]")
666@tf_export("summary.experimental.summary_scope", v1=[])
667@tf_contextlib.contextmanager
668def summary_scope(name, default_name="summary", values=None):
669 """Experimental context manager for use when defining a custom summary op.
671 This behaves similarly to `tf.name_scope`, except that it returns a generated
672 summary tag in addition to the scope name. The tag is structurally similar to
673 the scope name - derived from the user-provided name, prefixed with enclosing
674 name scopes if any - but we relax the constraint that it be uniquified, as
675 well as the character set limitation (so the user-provided name can contain
676 characters not legal for scope names; in the scope name these are removed).
678 This makes the summary tag more predictable and consistent for the user.
680 For example, to define a new summary op called `my_op`:
682 ```python
683 def my_op(name, my_value, step):
684 with tf.summary.summary_scope(name, "MyOp", [my_value]) as (tag, scope):
685 my_value = tf.convert_to_tensor(my_value)
686 return tf.summary.write(tag, my_value, step=step)
687 ```
689 Args:
690 name: string name for the summary.
691 default_name: Optional; if provided, used as default name of the summary.
692 values: Optional; passed as `values` parameter to name_scope.
694 Yields:
695 A tuple `(tag, scope)` as described above.
696 """
697 name = name or default_name
698 current_scope = ops.get_name_scope()
699 tag = current_scope + "/" + name if current_scope else name
700 # Strip illegal characters from the scope name, and if that leaves nothing,
701 # use None instead so we pick up the default name.
702 name = _INVALID_SCOPE_CHARACTERS.sub("", name) or None
703 with ops.name_scope(name, default_name, values, skip_on_eager=False) as scope:
704 yield tag, scope
707@tf_export("summary.write", v1=[])
708def write(tag, tensor, step=None, metadata=None, name=None):
709 """Writes a generic summary to the default SummaryWriter if one exists.
711 This exists primarily to support the definition of type-specific summary ops
712 like scalar() and image(), and is not intended for direct use unless defining
713 a new type-specific summary op.
715 Args:
716 tag: string tag used to identify the summary (e.g. in TensorBoard), usually
717 generated with `tf.summary.summary_scope`
718 tensor: the Tensor holding the summary data to write or a callable that
719 returns this Tensor. If a callable is passed, it will only be called when
720 a default SummaryWriter exists and the recording condition specified by
721 `record_if()` is met.
722 step: Explicit `int64`-castable monotonic step value for this summary. If
723 omitted, this defaults to `tf.summary.experimental.get_step()`, which must
724 not be None.
725 metadata: Optional SummaryMetadata, as a proto or serialized bytes
726 name: Optional string name for this op.
728 Returns:
729 True on success, or false if no summary was written because no default
730 summary writer was available.
732 Raises:
733 ValueError: if a default writer exists, but no step was provided and
734 `tf.summary.experimental.get_step()` is None.
735 """
736 with ops.name_scope(name, "write_summary") as scope:
737 if _summary_state.writer is None:
738 return constant_op.constant(False)
739 if step is None:
740 step = get_step()
741 if metadata is None:
742 serialized_metadata = b""
743 elif hasattr(metadata, "SerializeToString"):
744 serialized_metadata = metadata.SerializeToString()
745 else:
746 serialized_metadata = metadata
748 def record():
749 """Record the actual summary and return True."""
750 if step is None:
751 raise ValueError("No step set. Please specify one either through the "
752 "`step` argument or through "
753 "tf.summary.experimental.set_step()")
755 # Note the identity to move the tensor to the CPU.
756 with ops.device("cpu:0"):
757 summary_tensor = tensor() if callable(tensor) else array_ops.identity(
758 tensor)
759 write_summary_op = gen_summary_ops.write_summary(
760 _summary_state.writer._resource, # pylint: disable=protected-access
761 step,
762 summary_tensor,
763 tag,
764 serialized_metadata,
765 name=scope)
766 with ops.control_dependencies([write_summary_op]):
767 return constant_op.constant(True)
769 op = smart_cond.smart_cond(
770 should_record_summaries(), record, _nothing, name="summary_cond")
771 if not context.executing_eagerly():
772 ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op) # pylint: disable=protected-access
773 return op
776@tf_export("summary.experimental.write_raw_pb", v1=[])
777def write_raw_pb(tensor, step=None, name=None):
778 """Writes a summary using raw `tf.compat.v1.Summary` protocol buffers.
780 Experimental: this exists to support the usage of V1-style manual summary
781 writing (via the construction of a `tf.compat.v1.Summary` protocol buffer)
782 with the V2 summary writing API.
784 Args:
785 tensor: the string Tensor holding one or more serialized `Summary` protobufs
786 step: Explicit `int64`-castable monotonic step value for this summary. If
787 omitted, this defaults to `tf.summary.experimental.get_step()`, which must
788 not be None.
789 name: Optional string name for this op.
791 Returns:
792 True on success, or false if no summary was written because no default
793 summary writer was available.
795 Raises:
796 ValueError: if a default writer exists, but no step was provided and
797 `tf.summary.experimental.get_step()` is None.
798 """
799 with ops.name_scope(name, "write_raw_pb") as scope:
800 if _summary_state.writer is None:
801 return constant_op.constant(False)
802 if step is None:
803 step = get_step()
804 if step is None:
805 raise ValueError("No step set. Please specify one either through the "
806 "`step` argument or through "
807 "tf.summary.experimental.set_step()")
809 def record():
810 """Record the actual summary and return True."""
811 # Note the identity to move the tensor to the CPU.
812 with ops.device("cpu:0"):
813 raw_summary_op = gen_summary_ops.write_raw_proto_summary(
814 _summary_state.writer._resource, # pylint: disable=protected-access
815 step,
816 array_ops.identity(tensor),
817 name=scope)
818 with ops.control_dependencies([raw_summary_op]):
819 return constant_op.constant(True)
821 with ops.device("cpu:0"):
822 op = smart_cond.smart_cond(
823 should_record_summaries(), record, _nothing, name="summary_cond")
824 if not context.executing_eagerly():
825 ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op) # pylint: disable=protected-access
826 return op
829def summary_writer_function(name, tensor, function, family=None):
830 """Helper function to write summaries.
832 Args:
833 name: name of the summary
834 tensor: main tensor to form the summary
835 function: function taking a tag and a scope which writes the summary
836 family: optional, the summary's family
838 Returns:
839 The result of writing the summary.
840 """
841 name_scope = ops.get_name_scope()
842 if name_scope:
843 # Add a slash to allow reentering the name scope.
844 name_scope += "/"
845 def record():
846 with ops.name_scope(name_scope), summary_op_util.summary_scope(
847 name, family, values=[tensor]) as (tag, scope):
848 with ops.control_dependencies([function(tag, scope)]):
849 return constant_op.constant(True)
851 if _summary_state.writer is None:
852 return control_flow_ops.no_op()
853 with ops.device("cpu:0"):
854 op = smart_cond.smart_cond(
855 _legacy_contrib_should_record_summaries(), record, _nothing, name="")
856 if not context.executing_eagerly():
857 ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op) # pylint: disable=protected-access
858 return op
861def generic(name, tensor, metadata=None, family=None, step=None):
862 """Writes a tensor summary if possible."""
864 def function(tag, scope):
865 if metadata is None:
866 serialized_metadata = constant_op.constant("")
867 elif hasattr(metadata, "SerializeToString"):
868 serialized_metadata = constant_op.constant(metadata.SerializeToString())
869 else:
870 serialized_metadata = metadata
871 # Note the identity to move the tensor to the CPU.
872 return gen_summary_ops.write_summary(
873 _summary_state.writer._resource, # pylint: disable=protected-access
874 _choose_step(step),
875 array_ops.identity(tensor),
876 tag,
877 serialized_metadata,
878 name=scope)
879 return summary_writer_function(name, tensor, function, family=family)
882def scalar(name, tensor, family=None, step=None):
883 """Writes a scalar summary if possible.
885 Unlike `tf.contrib.summary.generic` this op may change the dtype
886 depending on the writer, for both practical and efficiency concerns.
888 Args:
889 name: An arbitrary name for this summary.
890 tensor: A `tf.Tensor` Must be one of the following types:
891 `float32`, `float64`, `int32`, `int64`, `uint8`, `int16`,
892 `int8`, `uint16`, `half`, `uint32`, `uint64`.
893 family: Optional, the summary's family.
894 step: The `int64` monotonic step variable, which defaults
895 to `tf.compat.v1.train.get_global_step`.
897 Returns:
898 The created `tf.Operation` or a `tf.no_op` if summary writing has
899 not been enabled for this context.
900 """
902 def function(tag, scope):
903 # Note the identity to move the tensor to the CPU.
904 return gen_summary_ops.write_scalar_summary(
905 _summary_state.writer._resource, # pylint: disable=protected-access
906 _choose_step(step),
907 tag,
908 array_ops.identity(tensor),
909 name=scope)
911 return summary_writer_function(name, tensor, function, family=family)
914def histogram(name, tensor, family=None, step=None):
915 """Writes a histogram summary if possible."""
917 def function(tag, scope):
918 # Note the identity to move the tensor to the CPU.
919 return gen_summary_ops.write_histogram_summary(
920 _summary_state.writer._resource, # pylint: disable=protected-access
921 _choose_step(step),
922 tag,
923 array_ops.identity(tensor),
924 name=scope)
926 return summary_writer_function(name, tensor, function, family=family)
929def image(name, tensor, bad_color=None, max_images=3, family=None, step=None):
930 """Writes an image summary if possible."""
932 def function(tag, scope):
933 bad_color_ = (constant_op.constant([255, 0, 0, 255], dtype=dtypes.uint8)
934 if bad_color is None else bad_color)
935 # Note the identity to move the tensor to the CPU.
936 return gen_summary_ops.write_image_summary(
937 _summary_state.writer._resource, # pylint: disable=protected-access
938 _choose_step(step),
939 tag,
940 array_ops.identity(tensor),
941 bad_color_,
942 max_images,
943 name=scope)
945 return summary_writer_function(name, tensor, function, family=family)
948def audio(name, tensor, sample_rate, max_outputs, family=None, step=None):
949 """Writes an audio summary if possible."""
951 def function(tag, scope):
952 # Note the identity to move the tensor to the CPU.
953 return gen_summary_ops.write_audio_summary(
954 _summary_state.writer._resource, # pylint: disable=protected-access
955 _choose_step(step),
956 tag,
957 array_ops.identity(tensor),
958 sample_rate=sample_rate,
959 max_outputs=max_outputs,
960 name=scope)
962 return summary_writer_function(name, tensor, function, family=family)
965def graph_v1(param, step=None, name=None):
966 """Writes a TensorFlow graph to the summary interface.
968 The graph summary is, strictly speaking, not a summary. Conditions
969 like `tf.summary.should_record_summaries` do not apply. Only
970 a single graph can be associated with a particular run. If multiple
971 graphs are written, then only the last one will be considered by
972 TensorBoard.
974 When not using eager execution mode, the user should consider passing
975 the `graph` parameter to `tf.compat.v1.summary.initialize` instead of
976 calling this function. Otherwise special care needs to be taken when
977 using the graph to record the graph.
979 Args:
980 param: A `tf.Tensor` containing a serialized graph proto. When
981 eager execution is enabled, this function will automatically
982 coerce `tf.Graph`, `tf.compat.v1.GraphDef`, and string types.
983 step: The global step variable. This doesn't have useful semantics
984 for graph summaries, but is used anyway, due to the structure of
985 event log files. This defaults to the global step.
986 name: A name for the operation (optional).
988 Returns:
989 The created `tf.Operation` or a `tf.no_op` if summary writing has
990 not been enabled for this context.
992 Raises:
993 TypeError: If `param` isn't already a `tf.Tensor` in graph mode.
994 """
995 if not context.executing_eagerly() and not isinstance(param, ops.Tensor):
996 raise TypeError("graph() needs a argument `param` to be tf.Tensor "
997 "(e.g. tf.placeholder) in graph mode, but received "
998 f"param={param} of type {type(param).__name__}.")
999 writer = _summary_state.writer
1000 if writer is None:
1001 return control_flow_ops.no_op()
1002 with ops.device("cpu:0"):
1003 if isinstance(param, (ops.Graph, graph_pb2.GraphDef)):
1004 tensor = ops.convert_to_tensor(_serialize_graph(param), dtypes.string)
1005 else:
1006 tensor = array_ops.identity(param)
1007 return gen_summary_ops.write_graph_summary(
1008 writer._resource, _choose_step(step), tensor, name=name) # pylint: disable=protected-access
1011@tf_export("summary.graph", v1=[])
1012def graph(graph_data):
1013 """Writes a TensorFlow graph summary.
1015 Write an instance of `tf.Graph` or `tf.compat.v1.GraphDef` as summary only
1016 in an eager mode. Please prefer to use the trace APIs (`tf.summary.trace_on`,
1017 `tf.summary.trace_off`, and `tf.summary.trace_export`) when using
1018 `tf.function` which can automatically collect and record graphs from
1019 executions.
1021 Usage Example:
1022 ```py
1023 writer = tf.summary.create_file_writer("/tmp/mylogs")
1025 @tf.function
1026 def f():
1027 x = constant_op.constant(2)
1028 y = constant_op.constant(3)
1029 return x**y
1031 with writer.as_default():
1032 tf.summary.graph(f.get_concrete_function().graph)
1034 # Another example: in a very rare use case, when you are dealing with a TF v1
1035 # graph.
1036 graph = tf.Graph()
1037 with graph.as_default():
1038 c = tf.constant(30.0)
1039 with writer.as_default():
1040 tf.summary.graph(graph)
1041 ```
1043 Args:
1044 graph_data: The TensorFlow graph to write, as a `tf.Graph` or a
1045 `tf.compat.v1.GraphDef`.
1047 Returns:
1048 True on success, or False if no summary was written because no default
1049 summary writer was available.
1051 Raises:
1052 ValueError: `graph` summary API is invoked in a graph mode.
1053 """
1054 if not context.executing_eagerly():
1055 raise ValueError("graph() cannot be invoked inside a graph context.")
1056 writer = _summary_state.writer
1057 if writer is None:
1058 return constant_op.constant(False)
1059 with ops.device("cpu:0"):
1060 if not should_record_summaries():
1061 return constant_op.constant(False)
1063 if isinstance(graph_data, (ops.Graph, graph_pb2.GraphDef)):
1064 tensor = ops.convert_to_tensor(
1065 _serialize_graph(graph_data), dtypes.string)
1066 else:
1067 raise ValueError("Argument 'graph_data' is not tf.Graph or "
1068 "tf.compat.v1.GraphDef. Received graph_data="
1069 f"{graph_data} of type {type(graph_data).__name__}.")
1071 gen_summary_ops.write_graph_summary(
1072 writer._resource, # pylint: disable=protected-access
1073 # Graph does not have step. Set to 0.
1074 0,
1075 tensor,
1076 )
1077 return constant_op.constant(True)
1080def import_event(tensor, name=None):
1081 """Writes a `tf.compat.v1.Event` binary proto.
1083 This can be used to import existing event logs into a new summary writer sink.
1084 Please note that this is lower level than the other summary functions and
1085 will ignore the `tf.summary.should_record_summaries` setting.
1087 Args:
1088 tensor: A `tf.Tensor` of type `string` containing a serialized
1089 `tf.compat.v1.Event` proto.
1090 name: A name for the operation (optional).
1092 Returns:
1093 The created `tf.Operation`.
1094 """
1095 return gen_summary_ops.import_event(
1096 _summary_state.writer._resource, tensor, name=name) # pylint: disable=protected-access
1099@tf_export("summary.flush", v1=[])
1100def flush(writer=None, name=None):
1101 """Forces summary writer to send any buffered data to storage.
1103 This operation blocks until that finishes.
1105 Args:
1106 writer: The `tf.summary.SummaryWriter` to flush. If None, the current
1107 default writer will be used instead; if there is no current writer, this
1108 returns `tf.no_op`.
1109 name: Ignored legacy argument for a name for the operation.
1111 Returns:
1112 The created `tf.Operation`.
1113 """
1114 del name # unused
1115 if writer is None:
1116 writer = _summary_state.writer
1117 if writer is None:
1118 return control_flow_ops.no_op()
1119 if isinstance(writer, SummaryWriter):
1120 return writer.flush()
1121 raise ValueError("Invalid argument to flush(): %r" % (writer,))
1124def legacy_raw_flush(writer=None, name=None):
1125 """Legacy version of flush() that accepts a raw resource tensor for `writer`.
1127 Do not use this function in any new code. Not supported and not part of the
1128 public TF APIs.
1130 Args:
1131 writer: The `tf.summary.SummaryWriter` to flush. If None, the current
1132 default writer will be used instead; if there is no current writer, this
1133 returns `tf.no_op`. For this legacy version only, also accepts a raw
1134 resource tensor pointing to the underlying C++ writer resource.
1135 name: Ignored legacy argument for a name for the operation.
1137 Returns:
1138 The created `tf.Operation`.
1139 """
1140 if writer is None or isinstance(writer, SummaryWriter):
1141 # Forward to the TF2 implementation of flush() when possible.
1142 return flush(writer, name)
1143 else:
1144 # Legacy fallback in case we were passed a raw resource tensor.
1145 with ops.device("cpu:0"):
1146 return gen_summary_ops.flush_summary_writer(writer, name=name)
1149def eval_dir(model_dir, name=None):
1150 """Construct a logdir for an eval summary writer."""
1151 return os.path.join(model_dir, "eval" if not name else "eval_" + name)
1154@deprecation.deprecated(date=None,
1155 instructions="Renamed to create_file_writer().")
1156def create_summary_file_writer(*args, **kwargs):
1157 """Please use `tf.contrib.summary.create_file_writer`."""
1158 logging.warning("Deprecation Warning: create_summary_file_writer was renamed "
1159 "to create_file_writer")
1160 return create_file_writer(*args, **kwargs)
1163def _serialize_graph(arbitrary_graph):
1164 if isinstance(arbitrary_graph, ops.Graph):
1165 return arbitrary_graph.as_graph_def(add_shapes=True).SerializeToString()
1166 else:
1167 return arbitrary_graph.SerializeToString()
1170def _choose_step(step):
1171 if step is None:
1172 return training_util.get_or_create_global_step()
1173 if not isinstance(step, ops.Tensor):
1174 return ops.convert_to_tensor(step, dtypes.int64)
1175 return step
1178def _check_create_file_writer_args(inside_function, **kwargs):
1179 """Helper to check the validity of arguments to a create_file_writer() call.
1181 Args:
1182 inside_function: whether the create_file_writer() call is in a tf.function
1183 **kwargs: the arguments to check, as kwargs to give them names.
1185 Raises:
1186 ValueError: if the arguments are graph tensors.
1187 """
1188 for arg_name, arg in kwargs.items():
1189 if not isinstance(arg, ops.EagerTensor) and tensor_util.is_tf_type(arg):
1190 if inside_function:
1191 raise ValueError(
1192 f"Invalid graph Tensor argument '{arg_name}={arg}' to "
1193 "create_file_writer() inside an @tf.function. The create call will "
1194 "be lifted into the outer eager execution context, so it cannot "
1195 "consume graph tensors defined inside the function body.")
1196 else:
1197 raise ValueError(
1198 f"Invalid graph Tensor argument '{arg_name}={arg}' to eagerly "
1199 "executed create_file_writer().")
1202def run_metadata(name, data, step=None):
1203 """Writes entire RunMetadata summary.
1205 A RunMetadata can contain DeviceStats, partition graphs, and function graphs.
1206 Please refer to the proto for definition of each field.
1208 Args:
1209 name: A name for this summary. The summary tag used for TensorBoard will be
1210 this name prefixed by any active name scopes.
1211 data: A RunMetadata proto to write.
1212 step: Explicit `int64`-castable monotonic step value for this summary. If
1213 omitted, this defaults to `tf.summary.experimental.get_step()`, which must
1214 not be None.
1216 Returns:
1217 True on success, or false if no summary was written because no default
1218 summary writer was available.
1220 Raises:
1221 ValueError: if a default writer exists, but no step was provided and
1222 `tf.summary.experimental.get_step()` is None.
1223 """
1224 summary_metadata = summary_pb2.SummaryMetadata()
1225 # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for
1226 # the rationale.
1227 summary_metadata.plugin_data.plugin_name = "graph_run_metadata"
1228 # version number = 1
1229 summary_metadata.plugin_data.content = b"1"
1231 with summary_scope(name,
1232 "graph_run_metadata_summary",
1233 [data, step]) as (tag, _):
1234 with ops.device("cpu:0"):
1235 tensor = constant_op.constant(data.SerializeToString(),
1236 dtype=dtypes.string)
1237 return write(
1238 tag=tag,
1239 tensor=tensor,
1240 step=step,
1241 metadata=summary_metadata)
1244def run_metadata_graphs(name, data, step=None):
1245 """Writes graphs from a RunMetadata summary.
1247 Args:
1248 name: A name for this summary. The summary tag used for TensorBoard will be
1249 this name prefixed by any active name scopes.
1250 data: A RunMetadata proto to write.
1251 step: Explicit `int64`-castable monotonic step value for this summary. If
1252 omitted, this defaults to `tf.summary.experimental.get_step()`, which must
1253 not be None.
1255 Returns:
1256 True on success, or false if no summary was written because no default
1257 summary writer was available.
1259 Raises:
1260 ValueError: if a default writer exists, but no step was provided and
1261 `tf.summary.experimental.get_step()` is None.
1262 """
1263 summary_metadata = summary_pb2.SummaryMetadata()
1264 # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for
1265 # the rationale.
1266 summary_metadata.plugin_data.plugin_name = "graph_run_metadata_graph"
1267 # version number = 1
1268 summary_metadata.plugin_data.content = b"1"
1270 data = config_pb2.RunMetadata(
1271 function_graphs=data.function_graphs,
1272 partition_graphs=data.partition_graphs)
1274 with summary_scope(name,
1275 "graph_run_metadata_graph_summary",
1276 [data, step]) as (tag, _):
1277 with ops.device("cpu:0"):
1278 tensor = constant_op.constant(data.SerializeToString(),
1279 dtype=dtypes.string)
1280 return write(
1281 tag=tag,
1282 tensor=tensor,
1283 step=step,
1284 metadata=summary_metadata)
1287_TraceContext = collections.namedtuple("TraceContext", ("graph", "profiler"))
1288_current_trace_context_lock = threading.Lock()
1289_current_trace_context = None
1292@tf_export("summary.trace_on", v1=[])
1293def trace_on(graph=True, profiler=False): # pylint: disable=redefined-outer-name
1294 """Starts a trace to record computation graphs and profiling information.
1296 Must be invoked in eager mode.
1298 When enabled, TensorFlow runtime will collect information that can later be
1299 exported and consumed by TensorBoard. The trace is activated across the entire
1300 TensorFlow runtime and affects all threads of execution.
1302 To stop the trace and export the collected information, use
1303 `tf.summary.trace_export`. To stop the trace without exporting, use
1304 `tf.summary.trace_off`.
1306 Args:
1307 graph: If True, enables collection of executed graphs. It includes ones from
1308 tf.function invocation and ones from the legacy graph mode. The default
1309 is True.
1310 profiler: If True, enables the advanced profiler. Enabling profiler
1311 implicitly enables the graph collection. The profiler may incur a high
1312 memory overhead. The default is False.
1314 """
1315 if ops.inside_function():
1316 logging.warn("Cannot enable trace inside a tf.function.")
1317 return
1318 if not context.executing_eagerly():
1319 logging.warn("Must enable trace in eager mode.")
1320 return
1322 global _current_trace_context
1323 with _current_trace_context_lock:
1324 if _current_trace_context:
1325 logging.warn("Trace already enabled")
1326 return
1328 if graph and not profiler:
1329 context.context().enable_graph_collection()
1330 if profiler:
1331 context.context().enable_run_metadata()
1332 _profiler.start()
1334 _current_trace_context = _TraceContext(graph=graph, profiler=profiler)
1337@tf_export("summary.trace_export", v1=[])
1338def trace_export(name, step=None, profiler_outdir=None):
1339 """Stops and exports the active trace as a Summary and/or profile file.
1341 Stops the trace and exports all metadata collected during the trace to the
1342 default SummaryWriter, if one has been set.
1344 Args:
1345 name: A name for the summary to be written.
1346 step: Explicit `int64`-castable monotonic step value for this summary. If
1347 omitted, this defaults to `tf.summary.experimental.get_step()`, which must
1348 not be None.
1349 profiler_outdir: Output directory for profiler. It is required when profiler
1350 is enabled when trace was started. Otherwise, it is ignored.
1352 Raises:
1353 ValueError: if a default writer exists, but no step was provided and
1354 `tf.summary.experimental.get_step()` is None.
1355 """
1356 # TODO(stephanlee): See if we can remove profiler_outdir and infer it from
1357 # the SummaryWriter's logdir.
1358 global _current_trace_context
1360 if ops.inside_function():
1361 logging.warn("Cannot export trace inside a tf.function.")
1362 return
1363 if not context.executing_eagerly():
1364 logging.warn("Can only export trace while executing eagerly.")
1365 return
1367 with _current_trace_context_lock:
1368 if _current_trace_context is None:
1369 raise ValueError("Must enable trace before export through "
1370 "tf.summary.trace_on.")
1371 graph, profiler = _current_trace_context # pylint: disable=redefined-outer-name
1372 if profiler and profiler_outdir is None:
1373 raise ValueError("Argument `profiler_outdir` is not specified.")
1375 run_meta = context.context().export_run_metadata()
1377 if graph and not profiler:
1378 run_metadata_graphs(name, run_meta, step)
1379 else:
1380 run_metadata(name, run_meta, step)
1382 if profiler:
1383 _profiler.save(profiler_outdir, _profiler.stop())
1385 trace_off()
1388@tf_export("summary.trace_off", v1=[])
1389def trace_off():
1390 """Stops the current trace and discards any collected information."""
1391 global _current_trace_context
1392 with _current_trace_context_lock:
1393 if _current_trace_context is None:
1394 return # tracing already off
1395 graph, profiler = _current_trace_context # pylint: disable=redefined-outer-name, unpacking-non-sequence
1396 _current_trace_context = None
1398 if graph:
1399 # Disabling run_metadata disables graph collection as well.
1400 context.context().disable_run_metadata()
1402 if profiler:
1403 try:
1404 _profiler.stop()
1405 except _profiler.ProfilerNotRunningError:
1406 pass