xref: /aosp_15_r20/external/tensorflow/tensorflow/python/ops/summary_ops_v2.py (revision b6fb3261f9314811a0f4371741dbb8839866f948)
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
16"""Operations to emit summaries."""
17
18import abc
19import collections
20import functools
21import os
22import re
23import threading
24
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
48
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"
52
53
54class _SummaryState(threading.local):
55
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
63
64
65_summary_state = _SummaryState()
66
67
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.
73
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
79
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
87
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
96
97
98def _should_record_summaries_internal(default_state):
99  """Returns boolean Tensor if summaries should/shouldn't be recorded.
100
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).
106
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)
115
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)
120
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)
127
128
129@tf_export("summary.should_record_summaries", v1=[])
130def should_record_summaries():
131  """Returns boolean Tensor which is True if summaries will be recorded.
132
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)
140
141
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)
146
147
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.
152
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.
162
163  Args:
164    condition: can be True, False, a bool Tensor, or a callable providing such.
165
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
176
177
178def has_default_writer():
179  """Returns a boolean indicating whether a default summary writer exists."""
180  return _summary_state.writer is not None
181
182
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)
193
194
195def always_record_summaries():
196  """Sets the should_record_summaries Tensor to always true."""
197  return record_if(True)
198
199
200def never_record_summaries():
201  """Sets the should_record_summaries Tensor to always false."""
202  return record_if(False)
203
204
205@tf_export("summary.experimental.get_step", v1=[])
206def get_step():
207  """Returns the default summary step for the current thread.
208
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
214
215
216@tf_export("summary.experimental.set_step", v1=[])
217def set_step(step):
218  """Sets the default summary step for the current thread.
219
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()`.
224
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.
228
229  Args:
230    step: An `int64`-castable default step value, or None to unset.
231  """
232  _summary_state.step = step
233
234
235@tf_export("summary.SummaryWriter", v1=[])
236class SummaryWriter(metaclass=abc.ABCMeta):
237  """Interface representing a stateful summary writer object."""
238
239  def set_as_default(self, step=None):
240    """Enables this summary writer for the current thread.
241
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.
246
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.
251
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__()
258
259  def as_default(self, step=None):
260    """Returns a context manager that enables summary writing.
261
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.
266
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.
271
272    For example, `step` can be used as:
273
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    ```
281
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).
287
288    Returns:
289      The context manager.
290    """
291    return _SummaryContextManager(self, step)
292
293  def init(self):
294    """Initializes the summary writer."""
295    raise NotImplementedError()
296
297  def flush(self):
298    """Flushes any buffered data."""
299    raise NotImplementedError()
300
301  def close(self):
302    """Flushes and closes the summary writer."""
303    raise NotImplementedError()
304
305
306class _ResourceSummaryWriter(SummaryWriter):
307  """Implementation of SummaryWriter using a SummaryWriterInterface resource."""
308
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)
317
318  # Extension point to be overridden by subclasses to customize deletion.
319
320  def _set_up_resource_deleter(self):
321    self._resource_deleter = resource_variable_ops.EagerResourceDeleter(
322        handle=self._resource, handle_device="cpu:0")
323
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)
329
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)
335
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
341
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)
348
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
360
361
362class _MultiMetaclass(
363    type(_ResourceSummaryWriter), type(resource.TrackableResource)):
364  pass
365
366
367class _TrackableResourceSummaryWriter(
368    _ResourceSummaryWriter,
369    resource.TrackableResource,
370    metaclass=_MultiMetaclass):
371  """A `_ResourceSummaryWriter` subclass that implements `TrackableResource`."""
372
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)
383
384  # Override for TrackableResource implementation.
385  def _create_resource(self):
386    return self._create_fn()
387
388  # Override for TrackableResource implementation.
389  def _initialize(self):
390    return self._init_op_fn(self.resource_handle)
391
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)
396
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
401
402
403class _LegacyResourceSummaryWriter(SummaryWriter):
404  """Legacy resource-backed SummaryWriter for tf.contrib.summary."""
405
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)
415
416  def init(self):
417    """See `SummaryWriter.init`."""
418    return self._init_op_fn(self._resource)
419
420  def flush(self):
421    """See `SummaryWriter.flush`."""
422    with ops.device("cpu:0"):
423      return gen_summary_ops.flush_summary_writer(self._resource)
424
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)
430
431
432class _NoopSummaryWriter(SummaryWriter):
433  """A summary writer that does nothing, for create_noop_writer()."""
434
435  def set_as_default(self, step=None):
436    pass
437
438  @tf_contextlib.contextmanager
439  def as_default(self, step=None):
440    yield
441
442  def init(self):
443    pass
444
445  def flush(self):
446    pass
447
448  def close(self):
449    pass
450
451
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.
457
458  This operation is a no-op when executing eagerly.
459
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`.
463
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.
466
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`.
473
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})
493
494
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.
503
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.
514
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")
538
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)
548
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)
561
562
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.
569
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.
583
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))
609
610
611@tf_export("summary.create_noop_writer", v1=[])
612def create_noop_writer():
613  """Returns a summary writer that does nothing.
614
615  This is useful as a placeholder in code that expects a context manager.
616  """
617  return _NoopSummaryWriter()
618
619
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)
624
625
626def _nothing():
627  """Convenient else branch for when summaries do not record."""
628  return constant_op.constant(False)
629
630
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.
634
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.
638
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
645
646
647def summary_writer_initializer_op():
648  """Graph-mode only. Returns the list of ops to create all summary writers.
649
650  Returns:
651    The initializer ops.
652
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)
661
662
663_INVALID_SCOPE_CHARACTERS = re.compile(r"[^-_/.A-Za-z0-9]")
664
665
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.
670
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).
677
678  This makes the summary tag more predictable and consistent for the user.
679
680  For example, to define a new summary op called `my_op`:
681
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  ```
688
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.
693
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
705
706
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.
710
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.
714
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.
727
728  Returns:
729    True on success, or false if no summary was written because no default
730    summary writer was available.
731
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
747
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()")
754
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)
768
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
774
775
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.
779
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.
783
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.
790
791  Returns:
792    True on success, or false if no summary was written because no default
793    summary writer was available.
794
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()")
808
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)
820
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
827
828
829def summary_writer_function(name, tensor, function, family=None):
830  """Helper function to write summaries.
831
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
837
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)
850
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
859
860
861def generic(name, tensor, metadata=None, family=None, step=None):
862  """Writes a tensor summary if possible."""
863
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)
880
881
882def scalar(name, tensor, family=None, step=None):
883  """Writes a scalar summary if possible.
884
885  Unlike `tf.contrib.summary.generic` this op may change the dtype
886  depending on the writer, for both practical and efficiency concerns.
887
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`.
896
897  Returns:
898    The created `tf.Operation` or a `tf.no_op` if summary writing has
899    not been enabled for this context.
900  """
901
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)
910
911  return summary_writer_function(name, tensor, function, family=family)
912
913
914def histogram(name, tensor, family=None, step=None):
915  """Writes a histogram summary if possible."""
916
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)
925
926  return summary_writer_function(name, tensor, function, family=family)
927
928
929def image(name, tensor, bad_color=None, max_images=3, family=None, step=None):
930  """Writes an image summary if possible."""
931
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)
944
945  return summary_writer_function(name, tensor, function, family=family)
946
947
948def audio(name, tensor, sample_rate, max_outputs, family=None, step=None):
949  """Writes an audio summary if possible."""
950
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)
961
962  return summary_writer_function(name, tensor, function, family=family)
963
964
965def graph_v1(param, step=None, name=None):
966  """Writes a TensorFlow graph to the summary interface.
967
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.
973
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.
978
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).
987
988  Returns:
989    The created `tf.Operation` or a `tf.no_op` if summary writing has
990    not been enabled for this context.
991
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
1009
1010
1011@tf_export("summary.graph", v1=[])
1012def graph(graph_data):
1013  """Writes a TensorFlow graph summary.
1014
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.
1020
1021  Usage Example:
1022  ```py
1023  writer = tf.summary.create_file_writer("/tmp/mylogs")
1024
1025  @tf.function
1026  def f():
1027    x = constant_op.constant(2)
1028    y = constant_op.constant(3)
1029    return x**y
1030
1031  with writer.as_default():
1032    tf.summary.graph(f.get_concrete_function().graph)
1033
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  ```
1042
1043  Args:
1044    graph_data: The TensorFlow graph to write, as a `tf.Graph` or a
1045      `tf.compat.v1.GraphDef`.
1046
1047  Returns:
1048    True on success, or False if no summary was written because no default
1049    summary writer was available.
1050
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)
1062
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__}.")
1070
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)
1078
1079
1080def import_event(tensor, name=None):
1081  """Writes a `tf.compat.v1.Event` binary proto.
1082
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.
1086
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).
1091
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
1097
1098
1099@tf_export("summary.flush", v1=[])
1100def flush(writer=None, name=None):
1101  """Forces summary writer to send any buffered data to storage.
1102
1103  This operation blocks until that finishes.
1104
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.
1110
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,))
1122
1123
1124def legacy_raw_flush(writer=None, name=None):
1125  """Legacy version of flush() that accepts a raw resource tensor for `writer`.
1126
1127  Do not use this function in any new code. Not supported and not part of the
1128  public TF APIs.
1129
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.
1136
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)
1147
1148
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)
1152
1153
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)
1161
1162
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()
1168
1169
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
1176
1177
1178def _check_create_file_writer_args(inside_function, **kwargs):
1179  """Helper to check the validity of arguments to a create_file_writer() call.
1180
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.
1184
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().")
1200
1201
1202def run_metadata(name, data, step=None):
1203  """Writes entire RunMetadata summary.
1204
1205  A RunMetadata can contain DeviceStats, partition graphs, and function graphs.
1206  Please refer to the proto for definition of each field.
1207
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.
1215
1216  Returns:
1217    True on success, or false if no summary was written because no default
1218    summary writer was available.
1219
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"
1230
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)
1242
1243
1244def run_metadata_graphs(name, data, step=None):
1245  """Writes graphs from a RunMetadata summary.
1246
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.
1254
1255  Returns:
1256    True on success, or false if no summary was written because no default
1257    summary writer was available.
1258
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"
1269
1270  data = config_pb2.RunMetadata(
1271      function_graphs=data.function_graphs,
1272      partition_graphs=data.partition_graphs)
1273
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)
1285
1286
1287_TraceContext = collections.namedtuple("TraceContext", ("graph", "profiler"))
1288_current_trace_context_lock = threading.Lock()
1289_current_trace_context = None
1290
1291
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.
1295
1296  Must be invoked in eager mode.
1297
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.
1301
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`.
1305
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.
1313
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
1321
1322  global _current_trace_context
1323  with _current_trace_context_lock:
1324    if _current_trace_context:
1325      logging.warn("Trace already enabled")
1326      return
1327
1328    if graph and not profiler:
1329      context.context().enable_graph_collection()
1330    if profiler:
1331      context.context().enable_run_metadata()
1332      _profiler.start()
1333
1334    _current_trace_context = _TraceContext(graph=graph, profiler=profiler)
1335
1336
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.
1340
1341  Stops the trace and exports all metadata collected during the trace to the
1342  default SummaryWriter, if one has been set.
1343
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.
1351
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
1359
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
1366
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.")
1374
1375  run_meta = context.context().export_run_metadata()
1376
1377  if graph and not profiler:
1378    run_metadata_graphs(name, run_meta, step)
1379  else:
1380    run_metadata(name, run_meta, step)
1381
1382  if profiler:
1383    _profiler.save(profiler_outdir, _profiler.stop())
1384
1385  trace_off()
1386
1387
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
1397
1398  if graph:
1399    # Disabling run_metadata disables graph collection as well.
1400    context.context().disable_run_metadata()
1401
1402  if profiler:
1403    try:
1404      _profiler.stop()
1405    except _profiler.ProfilerNotRunningError:
1406      pass
1407