I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,632 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""A wrapper around DebugDataReader used for retrieving tfdbg v2 data."""
import threading
from tensorboard import errors
# Dummy run name for the debugger.
# Currently, the `DebuggerV2ExperimentMultiplexer` class is tied to a single
# logdir, which holds at most one DebugEvent file set in the tfdbg v2 (tfdbg2
# for short) format.
# TODO(cais): When tfdbg2 allows there to be multiple DebugEvent file sets in
# the same logdir, replace this magic string with actual run names.
DEFAULT_DEBUGGER_RUN_NAME = "__default_debugger_run__"
# Default number of alerts per monitor type.
# Limiting the number of alerts is based on the consideration that usually
# only the first few alerting events are the most critical and the subsequent
# ones are either repetitions of the earlier ones or caused by the earlier ones.
DEFAULT_PER_TYPE_ALERT_LIMIT = 1000
# Default interval between successive calls to `DebugDataReader.update()``.
DEFAULT_RELOAD_INTERVAL_SEC = 30
def run_repeatedly_in_background(target, interval_sec):
"""Run a target task repeatedly in the background.
In the context of this module, `target` is the `update()` method of the
underlying reader for tfdbg2-format data.
This method is mocked by unit tests for deterministic behaviors during
testing.
Args:
target: The target task to run in the background, a callable with no args.
interval_sec: Time interval between repeats, in seconds.
Returns:
- A `threading.Event` object that can be used to interrupt an ongoing
waiting interval between successive runs of `target`. To interrupt the
interval, call the `set()` method of the object.
- The `threading.Thread` object on which `target` is run repeatedly.
"""
event = threading.Event()
def _run_repeatedly():
while True:
target()
event.wait(interval_sec)
event.clear()
# Use `daemon=True` to make sure the thread doesn't block program exit.
thread = threading.Thread(target=_run_repeatedly, daemon=True)
thread.start()
return event, thread
def _alert_to_json(alert):
# TODO(cais): Replace this with Alert.to_json() when supported by the
# backend.
from tensorflow.python.debug.lib import debug_events_monitors
if isinstance(alert, debug_events_monitors.InfNanAlert):
return {
"alert_type": "InfNanAlert",
"op_type": alert.op_type,
"output_slot": alert.output_slot,
# TODO(cais): Once supported by backend, add 'op_name' key
# for intra-graph execution events.
"size": alert.size,
"num_neg_inf": alert.num_neg_inf,
"num_pos_inf": alert.num_pos_inf,
"num_nan": alert.num_nan,
"execution_index": alert.execution_index,
"graph_execution_trace_index": alert.graph_execution_trace_index,
}
else:
raise TypeError("Unrecognized alert subtype: %s" % type(alert))
def parse_tensor_name(tensor_name):
"""Helper function that extracts op name and slot from tensor name."""
output_slot = 0
if ":" in tensor_name:
op_name, output_slot = tensor_name.split(":")
output_slot = int(output_slot)
else:
op_name = tensor_name
return op_name, output_slot
class DebuggerV2EventMultiplexer:
"""A class used for accessing tfdbg v2 DebugEvent data on local filesystem.
This class is a short-term hack, mirroring the EventMultiplexer for the main
TensorBoard plugins (e.g., scalar, histogram and graphs.) As such, it only
implements the methods relevant to the Debugger V2 pluggin.
TODO(cais): Integrate it with EventMultiplexer and use the integrated class
from MultiplexerDataProvider for a single path of accessing debugger and
non-debugger data.
"""
def __init__(self, logdir):
"""Constructor for the `DebugEventMultiplexer`.
Args:
logdir: Path to the directory to load the tfdbg v2 data from.
"""
self._logdir = logdir
self._reader = None
self._reader_lock = threading.Lock()
self._reload_needed_event = None
# Create the reader for the tfdbg2 data in the lodir as soon as
# the backend of the debugger-v2 plugin is created, so it doesn't need
# to wait for the first request from the FE to start loading data.
self._tryCreateReader()
def _tryCreateReader(self):
"""Try creating reader for tfdbg2 data in the logdir.
If the reader has already been created, a new one will not be created and
this function is a no-op.
If a reader has not been created, create it and start periodic calls to
`update()` on a separate thread.
"""
if self._reader:
return
with self._reader_lock:
if not self._reader:
try:
# TODO(cais): Avoid conditional imports and instead use
# plugin loader to gate the loading of this entire plugin.
from tensorflow.python.debug.lib import debug_events_reader
from tensorflow.python.debug.lib import (
debug_events_monitors,
)
except ImportError:
# This ensures graceful behavior when tensorflow install is
# unavailable or when the installed tensorflow version does not
# contain the required modules.
return
try:
self._reader = debug_events_reader.DebugDataReader(
self._logdir
)
except AttributeError:
# Gracefully fail for users without the required API changes to
# debug_events_reader.DebugDataReader introduced in
# TF 2.1.0.dev20200103. This should be safe to remove when
# TF 2.2 is released.
return
except ValueError:
# When no DebugEvent file set is found in the logdir, a
# `ValueError` is thrown.
return
self._monitors = [
debug_events_monitors.InfNanMonitor(
self._reader, limit=DEFAULT_PER_TYPE_ALERT_LIMIT
)
]
self._reload_needed_event, _ = run_repeatedly_in_background(
self._reader.update, DEFAULT_RELOAD_INTERVAL_SEC
)
def _reloadReader(self):
"""If a reader exists and has started period updating, unblock the update.
The updates are performed periodically with a sleep interval between
successive calls to the reader's update() method. Calling this method
interrupts the sleep immediately if one is ongoing.
"""
if self._reload_needed_event:
self._reload_needed_event.set()
def FirstEventTimestamp(self, run):
"""Return the timestamp of the first DebugEvent of the given run.
This may perform I/O if no events have been loaded yet for the run.
Args:
run: A string name of the run for which the timestamp is retrieved.
This currently must be hardcoded as `DEFAULT_DEBUGGER_RUN_NAME`,
as each logdir contains at most one DebugEvent file set (i.e., a
run of a tfdbg2-instrumented TensorFlow program.)
Returns:
The wall_time of the first event of the run, which will be in seconds
since the epoch as a `float`.
"""
if self._reader is None:
raise ValueError("No tfdbg2 runs exists.")
if run != DEFAULT_DEBUGGER_RUN_NAME:
raise ValueError(
"Expected run name to be %s, but got %s"
% (DEFAULT_DEBUGGER_RUN_NAME, run)
)
return self._reader.starting_wall_time()
def PluginRunToTagToContent(self, plugin_name):
raise NotImplementedError(
"DebugDataMultiplexer.PluginRunToTagToContent() has not been "
"implemented yet."
)
def Runs(self):
"""Return all the tfdbg2 run names in the logdir watched by this instance.
The `Run()` method of this class is specialized for the tfdbg2-format
DebugEvent files.
As a side effect, this method unblocks the underlying reader's period
reloading if a reader exists. This lets the reader update at a higher
frequency than the default one with 30-second sleeping period between
reloading when data is being queried actively from this instance.
Note that this `Runs()` method is used by all other public data-access
methods of this class (e.g., `ExecutionData()`, `GraphExecutionData()`).
Hence calls to those methods will lead to accelerated data reloading of
the reader.
Returns:
If tfdbg2-format data exists in the `logdir` of this object, returns:
```
{runName: { "debugger-v2": [tag1, tag2, tag3] } }
```
where `runName` is the hard-coded string `DEFAULT_DEBUGGER_RUN_NAME`
string. This is related to the fact that tfdbg2 currently contains
at most one DebugEvent file set per directory.
If no tfdbg2-format data exists in the `logdir`, an empty `dict`.
"""
# Call `_tryCreateReader()` here to cover the possibility of tfdbg2
# data start being written to the logdir after the tensorboard backend
# starts.
self._tryCreateReader()
if self._reader:
# If a _reader exists, unblock its reloading (on a separate thread)
# immediately.
self._reloadReader()
return {
DEFAULT_DEBUGGER_RUN_NAME: {
# TODO(cais): Add the semantically meaningful tag names such as
# 'execution_digests_book', 'alerts_book'
"debugger-v2": []
}
}
else:
return {}
def _checkBeginEndIndices(self, begin, end, total_count):
if begin < 0:
raise errors.InvalidArgumentError(
"Invalid begin index (%d)" % begin
)
if end > total_count:
raise errors.InvalidArgumentError(
"end index (%d) out of bounds (%d)" % (end, total_count)
)
if end >= 0 and end < begin:
raise errors.InvalidArgumentError(
"end index (%d) is unexpectedly less than begin index (%d)"
% (end, begin)
)
if end < 0: # This means all digests.
end = total_count
return end
def Alerts(self, run, begin, end, alert_type_filter=None):
"""Get alerts from the debugged TensorFlow program.
Args:
run: The tfdbg2 run to get Alerts from.
begin: Beginning alert index.
end: Ending alert index.
alert_type_filter: Optional filter string for alert type, used to
restrict retrieved alerts data to a single type. If used,
`begin` and `end` refer to the beginning and ending indices within
the filtered alert type.
"""
from tensorflow.python.debug.lib import debug_events_monitors
runs = self.Runs()
if run not in runs:
# TODO(cais): This should generate a 400 response instead.
return None
alerts = []
alerts_breakdown = dict()
alerts_by_type = dict()
for monitor in self._monitors:
monitor_alerts = monitor.alerts()
if not monitor_alerts:
continue
alerts.extend(monitor_alerts)
# TODO(cais): Replace this with Alert.to_json() when
# monitor.alert_type() is available.
if isinstance(monitor, debug_events_monitors.InfNanMonitor):
alert_type = "InfNanAlert"
else:
alert_type = "__MiscellaneousAlert__"
alerts_breakdown[alert_type] = len(monitor_alerts)
alerts_by_type[alert_type] = monitor_alerts
num_alerts = len(alerts)
if alert_type_filter is not None:
if alert_type_filter not in alerts_breakdown:
raise errors.InvalidArgumentError(
"Filtering of alerts failed: alert type %s does not exist"
% alert_type_filter
)
alerts = alerts_by_type[alert_type_filter]
end = self._checkBeginEndIndices(begin, end, len(alerts))
return {
"begin": begin,
"end": end,
"alert_type": alert_type_filter,
"num_alerts": num_alerts,
"alerts_breakdown": alerts_breakdown,
"per_type_alert_limit": DEFAULT_PER_TYPE_ALERT_LIMIT,
"alerts": [_alert_to_json(alert) for alert in alerts[begin:end]],
}
def ExecutionDigests(self, run, begin, end):
"""Get ExecutionDigests.
Args:
run: The tfdbg2 run to get `ExecutionDigest`s from.
begin: Beginning execution index.
end: Ending execution index.
Returns:
A JSON-serializable object containing the `ExecutionDigest`s and
related meta-information
"""
runs = self.Runs()
if run not in runs:
return None
# TODO(cais): For scalability, use begin and end kwargs when available in
# `DebugDataReader.execution()`.`
execution_digests = self._reader.executions(digest=True)
end = self._checkBeginEndIndices(begin, end, len(execution_digests))
return {
"begin": begin,
"end": end,
"num_digests": len(execution_digests),
"execution_digests": [
digest.to_json() for digest in execution_digests[begin:end]
],
}
def ExecutionData(self, run, begin, end):
"""Get Execution data objects (Detailed, non-digest form).
Args:
run: The tfdbg2 run to get `ExecutionDigest`s from.
begin: Beginning execution index.
end: Ending execution index.
Returns:
A JSON-serializable object containing the `ExecutionDigest`s and
related meta-information
"""
runs = self.Runs()
if run not in runs:
return None
execution_digests = self._reader.executions(digest=True)
end = self._checkBeginEndIndices(begin, end, len(execution_digests))
execution_digests = execution_digests[begin:end]
executions = self._reader.executions(digest=False, begin=begin, end=end)
return {
"begin": begin,
"end": end,
"executions": [execution.to_json() for execution in executions],
}
def GraphExecutionDigests(self, run, begin, end, trace_id=None):
"""Get `GraphExecutionTraceDigest`s.
Args:
run: The tfdbg2 run to get `GraphExecutionTraceDigest`s from.
begin: Beginning graph-execution index.
end: Ending graph-execution index.
Returns:
A JSON-serializable object containing the `ExecutionDigest`s and
related meta-information
"""
runs = self.Runs()
if run not in runs:
return None
# TODO(cais): Implement support for trace_id once the joining of eager
# execution and intra-graph execution is supported by DebugDataReader.
if trace_id is not None:
raise NotImplementedError(
"trace_id support for GraphExecutionTraceDigest is "
"not implemented yet."
)
graph_exec_digests = self._reader.graph_execution_traces(digest=True)
end = self._checkBeginEndIndices(begin, end, len(graph_exec_digests))
return {
"begin": begin,
"end": end,
"num_digests": len(graph_exec_digests),
"graph_execution_digests": [
digest.to_json() for digest in graph_exec_digests[begin:end]
],
}
def GraphExecutionData(self, run, begin, end, trace_id=None):
"""Get `GraphExecutionTrace`s.
Args:
run: The tfdbg2 run to get `GraphExecutionTrace`s from.
begin: Beginning graph-execution index.
end: Ending graph-execution index.
Returns:
A JSON-serializable object containing the `ExecutionDigest`s and
related meta-information
"""
runs = self.Runs()
if run not in runs:
return None
# TODO(cais): Implement support for trace_id once the joining of eager
# execution and intra-graph execution is supported by DebugDataReader.
if trace_id is not None:
raise NotImplementedError(
"trace_id support for GraphExecutionTraceData is "
"not implemented yet."
)
digests = self._reader.graph_execution_traces(digest=True)
end = self._checkBeginEndIndices(begin, end, len(digests))
graph_executions = self._reader.graph_execution_traces(
digest=False, begin=begin, end=end
)
return {
"begin": begin,
"end": end,
"graph_executions": [
graph_exec.to_json() for graph_exec in graph_executions
],
}
def GraphInfo(self, run, graph_id):
"""Get the information regarding a TensorFlow graph.
Args:
run: Name of the run.
graph_id: Debugger-generated ID of the graph in question.
This information is available in the return values
of `GraphOpInfo`, `GraphExecution`, etc.
Returns:
A JSON-serializable object containing the information regarding
the TensorFlow graph.
Raises:
NotFoundError if the graph_id is not known to the debugger.
"""
runs = self.Runs()
if run not in runs:
return None
try:
graph = self._reader.graph_by_id(graph_id)
except KeyError:
raise errors.NotFoundError(
'There is no graph with ID "%s"' % graph_id
)
return graph.to_json()
def GraphOpInfo(self, run, graph_id, op_name):
"""Get the information regarding a graph op's creation.
Args:
run: Name of the run.
graph_id: Debugger-generated ID of the graph that contains
the op in question. This ID is available from other methods
of this class, e.g., the return value of `GraphExecutionDigests()`.
op_name: Name of the op.
Returns:
A JSON-serializable object containing the information regarding
the op's creation and its immediate inputs and consumers.
Raises:
NotFoundError if the graph_id or op_name does not exist.
"""
runs = self.Runs()
if run not in runs:
return None
try:
graph = self._reader.graph_by_id(graph_id)
except KeyError:
raise errors.NotFoundError(
'There is no graph with ID "%s"' % graph_id
)
try:
op_creation_digest = graph.get_op_creation_digest(op_name)
except KeyError:
raise errors.NotFoundError(
'There is no op named "%s" in graph with ID "%s"'
% (op_name, graph_id)
)
data_object = self._opCreationDigestToDataObject(
op_creation_digest, graph
)
# Populate data about immediate inputs.
for input_spec in data_object["inputs"]:
try:
input_op_digest = graph.get_op_creation_digest(
input_spec["op_name"]
)
except KeyError:
input_op_digest = None
if input_op_digest:
input_spec["data"] = self._opCreationDigestToDataObject(
input_op_digest, graph
)
# Populate data about immediate consuming ops.
for slot_consumer_specs in data_object["consumers"]:
for consumer_spec in slot_consumer_specs:
try:
digest = graph.get_op_creation_digest(
consumer_spec["op_name"]
)
except KeyError:
digest = None
if digest:
consumer_spec["data"] = self._opCreationDigestToDataObject(
digest, graph
)
return data_object
def _opCreationDigestToDataObject(self, op_creation_digest, graph):
if op_creation_digest is None:
return None
json_object = op_creation_digest.to_json()
del json_object["graph_id"]
json_object["graph_ids"] = self._getGraphStackIds(
op_creation_digest.graph_id
)
# TODO(cais): "num_outputs" should be populated in to_json() instead.
json_object["num_outputs"] = op_creation_digest.num_outputs
del json_object["input_names"]
json_object["inputs"] = []
for input_tensor_name in op_creation_digest.input_names or []:
input_op_name, output_slot = parse_tensor_name(input_tensor_name)
json_object["inputs"].append(
{"op_name": input_op_name, "output_slot": output_slot}
)
json_object["consumers"] = []
for _ in range(json_object["num_outputs"]):
json_object["consumers"].append([])
for src_slot, consumer_op_name, dst_slot in graph.get_op_consumers(
json_object["op_name"]
):
json_object["consumers"][src_slot].append(
{"op_name": consumer_op_name, "input_slot": dst_slot}
)
return json_object
def _getGraphStackIds(self, graph_id):
"""Retrieve the IDs of all outer graphs of a graph.
Args:
graph_id: Id of the graph being queried with respect to its outer
graphs context.
Returns:
A list of graph_ids, ordered from outermost to innermost, including
the input `graph_id` argument as the last item.
"""
graph_ids = [graph_id]
graph = self._reader.graph_by_id(graph_id)
while graph.outer_graph_id:
graph_ids.insert(0, graph.outer_graph_id)
graph = self._reader.graph_by_id(graph.outer_graph_id)
return graph_ids
def SourceFileList(self, run):
runs = self.Runs()
if run not in runs:
return None
return self._reader.source_file_list()
def SourceLines(self, run, index):
runs = self.Runs()
if run not in runs:
return None
try:
host_name, file_path = self._reader.source_file_list()[index]
except IndexError:
raise errors.NotFoundError(
"There is no source-code file at index %d" % index
)
return {
"host_name": host_name,
"file_path": file_path,
"lines": self._reader.source_lines(host_name, file_path),
}
def StackFrames(self, run, stack_frame_ids):
runs = self.Runs()
if run not in runs:
return None
stack_frames = []
for stack_frame_id in stack_frame_ids:
if stack_frame_id not in self._reader._stack_frame_by_id:
raise errors.NotFoundError(
"Cannot find stack frame with ID %s" % stack_frame_id
)
# TODO(cais): Use public method (`stack_frame_by_id()`) when
# available.
# pylint: disable=protected-access
stack_frames.append(self._reader._stack_frame_by_id[stack_frame_id])
# pylint: enable=protected-access
return {"stack_frames": stack_frames}

View File

@ -0,0 +1,635 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""An implementation of DataProvider that serves tfdbg v2 data.
This implementation is:
1. Based on reading data from a DebugEvent file set on the local filesystem.
2. Implements only the relevant methods for the debugger v2 plugin, including
- list_runs()
- read_blob_sequences()
- read_blob()
This class is a short-term hack. To be used in production, it awaits integration
with a more complete implementation of DataProvider such as
MultiplexerDataProvider.
"""
import json
from tensorboard.data import provider
from tensorboard.plugins.debugger_v2 import debug_data_multiplexer
PLUGIN_NAME = "debugger-v2"
ALERTS_BLOB_TAG_PREFIX = "alerts"
EXECUTION_DIGESTS_BLOB_TAG_PREFIX = "execution_digests"
EXECUTION_DATA_BLOB_TAG_PREFIX = "execution_data"
GRAPH_EXECUTION_DIGESTS_BLOB_TAG_PREFIX = "graphexec_digests"
GRAPH_EXECUTION_DATA_BLOB_TAG_PREFIX = "graphexec_data"
GRAPH_INFO_BLOB_TAG_PREFIX = "graph_info"
GRAPH_OP_INFO_BLOB_TAG_PREFIX = "graph_op_info"
SOURCE_FILE_LIST_BLOB_TAG = "source_file_list"
SOURCE_FILE_BLOB_TAG_PREFIX = "source_file"
STACK_FRAMES_BLOB_TAG_PREFIX = "stack_frames"
def alerts_run_tag_filter(run, begin, end, alert_type=None):
"""Create a RunTagFilter for Alerts.
Args:
run: tfdbg2 run name.
begin: Beginning index of alerts.
end: Ending index of alerts.
alert_type: Optional alert type, used to restrict retrieval of alerts
data to a single type of alerts.
Returns:
`RunTagFilter` for the run and range of Alerts.
"""
tag = "%s_%d_%d" % (ALERTS_BLOB_TAG_PREFIX, begin, end)
if alert_type is not None:
tag += "_%s" % alert_type
return provider.RunTagFilter(runs=[run], tags=[tag])
def _parse_alerts_blob_key(blob_key):
"""Parse the BLOB key for Alerts.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
- `${ALERTS_BLOB_TAG_PREFIX}_${begin}_${end}.${run_id}` when there is no
alert type filter.
- `${ALERTS_BLOB_TAG_PREFIX}_${begin}_${end}_${alert_filter}.${run_id}`
when there is an alert type filter.
Returns:
- run ID
- begin index
- end index
- alert_type: alert type string used to filter retrieved alert data.
`None` if no filtering is used.
"""
key_body, run = blob_key.split(".", 1)
key_body = key_body[len(ALERTS_BLOB_TAG_PREFIX) :]
key_items = key_body.split("_", 3)
begin = int(key_items[1])
end = int(key_items[2])
alert_type = None
if len(key_items) > 3:
alert_type = key_items[3]
return run, begin, end, alert_type
def execution_digest_run_tag_filter(run, begin, end):
"""Create a RunTagFilter for ExecutionDigests.
This differs from `execution_data_run_tag_filter()` in that it is for
the small-size digest objects for execution debug events, instead of the
full-size data objects.
Args:
run: tfdbg2 run name.
begin: Beginning index of ExecutionDigests.
end: Ending index of ExecutionDigests.
Returns:
`RunTagFilter` for the run and range of ExecutionDigests.
"""
return provider.RunTagFilter(
runs=[run],
tags=["%s_%d_%d" % (EXECUTION_DIGESTS_BLOB_TAG_PREFIX, begin, end)],
)
def _parse_execution_digest_blob_key(blob_key):
"""Parse the BLOB key for ExecutionDigests.
This differs from `_parse_execution_data_blob_key()` in that it is for
the small-size digest objects for execution debug events, instead of the
full-size data objects.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${EXECUTION_DIGESTS_BLOB_TAG_PREFIX}_${begin}_${end}.${run_id}`
Returns:
- run ID
- begin index
- end index
"""
key_body, run = blob_key.split(".", 1)
key_body = key_body[len(EXECUTION_DIGESTS_BLOB_TAG_PREFIX) :]
begin = int(key_body.split("_")[1])
end = int(key_body.split("_")[2])
return run, begin, end
def execution_data_run_tag_filter(run, begin, end):
"""Create a RunTagFilter for Execution data objects.
This differs from `execution_digest_run_tag_filter()` in that it is
for the detailed data objects for execution, instead of the digests.
Args:
run: tfdbg2 run name.
begin: Beginning index of Execution.
end: Ending index of Execution.
Returns:
`RunTagFilter` for the run and range of ExecutionDigests.
"""
return provider.RunTagFilter(
runs=[run],
tags=["%s_%d_%d" % (EXECUTION_DATA_BLOB_TAG_PREFIX, begin, end)],
)
def _parse_execution_data_blob_key(blob_key):
"""Parse the BLOB key for Execution data objects.
This differs from `_parse_execution_digest_blob_key()` in that it is
for the deatiled data objects for execution, instead of the digests.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${EXECUTION_DATA_BLOB_TAG_PREFIX}_${begin}_${end}.${run_id}`
Returns:
- run ID
- begin index
- end index
"""
key_body, run = blob_key.split(".", 1)
key_body = key_body[len(EXECUTION_DATA_BLOB_TAG_PREFIX) :]
begin = int(key_body.split("_")[1])
end = int(key_body.split("_")[2])
return run, begin, end
def graph_execution_digest_run_tag_filter(run, begin, end, trace_id=None):
"""Create a RunTagFilter for GraphExecutionTraceDigests.
This differs from `graph_execution_data_run_tag_filter()` in that it is for
the small-size digest objects for intra-graph execution debug events, instead
of the full-size data objects.
Args:
run: tfdbg2 run name.
begin: Beginning index of GraphExecutionTraceDigests.
end: Ending index of GraphExecutionTraceDigests.
Returns:
`RunTagFilter` for the run and range of GraphExecutionTraceDigests.
"""
# TODO(cais): Implement support for trace_id once joining of eager
# execution and intra-graph execution is supported by DebugDataReader.
if trace_id is not None:
raise NotImplementedError(
"trace_id support for graph_execution_digest_run_tag_filter() is "
"not implemented yet."
)
return provider.RunTagFilter(
runs=[run],
tags=[
"%s_%d_%d" % (GRAPH_EXECUTION_DIGESTS_BLOB_TAG_PREFIX, begin, end)
],
)
def _parse_graph_execution_digest_blob_key(blob_key):
"""Parse the BLOB key for GraphExecutionTraceDigests.
This differs from `_parse_graph_execution_data_blob_key()` in that it is for
the small-size digest objects for intra-graph execution debug events,
instead of the full-size data objects.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${GRAPH_EXECUTION_DIGESTS_BLOB_TAG_PREFIX}_${begin}_${end}.${run_id}`
Returns:
- run ID
- begin index
- end index
"""
# TODO(cais): Support parsing trace_id when it is supported.
key_body, run = blob_key.split(".", 1)
key_body = key_body[len(GRAPH_EXECUTION_DIGESTS_BLOB_TAG_PREFIX) :]
begin = int(key_body.split("_")[1])
end = int(key_body.split("_")[2])
return run, begin, end
def graph_execution_data_run_tag_filter(run, begin, end, trace_id=None):
"""Create a RunTagFilter for GraphExecutionTrace.
This method differs from `graph_execution_digest_run_tag_filter()` in that
it is for full-sized data objects for intra-graph execution events.
Args:
run: tfdbg2 run name.
begin: Beginning index of GraphExecutionTrace.
end: Ending index of GraphExecutionTrace.
Returns:
`RunTagFilter` for the run and range of GraphExecutionTrace.
"""
# TODO(cais): Implement support for trace_id once joining of eager
# execution and intra-graph execution is supported by DebugDataReader.
if trace_id is not None:
raise NotImplementedError(
"trace_id support for graph_execution_data_run_tag_filter() is "
"not implemented yet."
)
return provider.RunTagFilter(
runs=[run],
tags=["%s_%d_%d" % (GRAPH_EXECUTION_DATA_BLOB_TAG_PREFIX, begin, end)],
)
def _parse_graph_execution_data_blob_key(blob_key):
"""Parse the BLOB key for GraphExecutionTrace.
This method differs from `_parse_graph_execution_digest_blob_key()` in that
it is for full-sized data objects for intra-graph execution events.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${GRAPH_EXECUTION_DATA_BLOB_TAG_PREFIX}_${begin}_${end}.${run_id}`
Returns:
- run ID
- begin index
- end index
"""
# TODO(cais): Support parsing trace_id when it is supported.
key_body, run = blob_key.split(".", 1)
key_body = key_body[len(GRAPH_EXECUTION_DATA_BLOB_TAG_PREFIX) :]
begin = int(key_body.split("_")[1])
end = int(key_body.split("_")[2])
return run, begin, end
def graph_op_info_run_tag_filter(run, graph_id, op_name):
"""Create a RunTagFilter for graph op info.
Args:
run: tfdbg2 run name.
graph_id: Debugger-generated ID of the graph. This is assumed to
be the ID of the graph that immediately encloses the op in question.
op_name: Name of the op in question. (e.g., "Dense_1/MatMul")
Returns:
`RunTagFilter` for the run and range of graph op info.
"""
if not graph_id:
raise ValueError("graph_id must not be None or empty.")
return provider.RunTagFilter(
runs=[run],
tags=["%s_%s_%s" % (GRAPH_OP_INFO_BLOB_TAG_PREFIX, graph_id, op_name)],
)
def _parse_graph_op_info_blob_key(blob_key):
"""Parse the BLOB key for graph op info.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${GRAPH_OP_INFO_BLOB_TAG_PREFIX}_${graph_id}_${op_name}.${run_name}`,
wherein
- `graph_id` is a UUID
- op_name conforms to the TensorFlow spec:
`^[A-Za-z0-9.][A-Za-z0-9_.\\/>-]*$`
- `run_name` is assumed to contain no dots (`'.'`s).
Returns:
- run name
- graph_id
- op name
"""
# NOTE: the op_name itself may include dots, this is why we use `rindex()`
# instead of `split()`.
last_dot_index = blob_key.rindex(".")
run = blob_key[last_dot_index + 1 :]
key_body = blob_key[:last_dot_index]
key_body = key_body[len(GRAPH_OP_INFO_BLOB_TAG_PREFIX) :]
_, graph_id, op_name = key_body.split("_", 2)
return run, graph_id, op_name
def graph_info_run_tag_filter(run, graph_id):
"""Create a RunTagFilter for graph info.
Args:
run: tfdbg2 run name.
graph_id: Debugger-generated ID of the graph in question.
Returns:
`RunTagFilter` for the run and range of graph info.
"""
if not graph_id:
raise ValueError("graph_id must not be None or empty.")
return provider.RunTagFilter(
runs=[run],
tags=["%s_%s" % (GRAPH_INFO_BLOB_TAG_PREFIX, graph_id)],
)
def _parse_graph_info_blob_key(blob_key):
"""Parse the BLOB key for graph info.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${GRAPH_INFO_BLOB_TAG_PREFIX}_${graph_id}.${run_name}`,
Returns:
- run name
- graph_id
"""
key_body, run = blob_key.split(".")
graph_id = key_body[len(GRAPH_INFO_BLOB_TAG_PREFIX) + 1 :]
return run, graph_id
def source_file_list_run_tag_filter(run):
"""Create a RunTagFilter for listing source files.
Args:
run: tfdbg2 run name.
Returns:
`RunTagFilter` for listing the source files in the tfdbg2 run.
"""
return provider.RunTagFilter(runs=[run], tags=[SOURCE_FILE_LIST_BLOB_TAG])
def _parse_source_file_list_blob_key(blob_key):
"""Parse the BLOB key for source file list.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${SOURCE_FILE_LIST_BLOB_TAG}.${run_id}`
Returns:
- run ID
"""
return blob_key[blob_key.index(".") + 1 :]
def source_file_run_tag_filter(run, index):
"""Create a RunTagFilter for listing source files.
Args:
run: tfdbg2 run name.
index: The index for the source file of which the content is to be
accessed.
Returns:
`RunTagFilter` for accessing the content of the source file.
"""
return provider.RunTagFilter(
runs=[run],
tags=["%s_%d" % (SOURCE_FILE_BLOB_TAG_PREFIX, index)],
)
def _parse_source_file_blob_key(blob_key):
"""Parse the BLOB key for accessing the content of a source file.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${SOURCE_FILE_BLOB_TAG_PREFIX}_${index}.${run_id}`
Returns:
- run ID, as a str.
- File index, as an int.
"""
key_body, run = blob_key.split(".", 1)
index = int(key_body[len(SOURCE_FILE_BLOB_TAG_PREFIX) + 1 :])
return run, index
def stack_frames_run_tag_filter(run, stack_frame_ids):
"""Create a RunTagFilter for querying stack frames.
Args:
run: tfdbg2 run name.
stack_frame_ids: The stack_frame_ids being requested.
Returns:
`RunTagFilter` for accessing the content of the source file.
"""
return provider.RunTagFilter(
runs=[run],
# The stack-frame IDS are UUIDs, which do not contain underscores.
# Hence it's safe to concatenate them with underscores.
tags=[STACK_FRAMES_BLOB_TAG_PREFIX + "_" + "_".join(stack_frame_ids)],
)
def _parse_stack_frames_blob_key(blob_key):
"""Parse the BLOB key for source file list.
Args:
blob_key: The BLOB key to parse. By contract, it should have the format:
`${STACK_FRAMES_BLOB_TAG_PREFIX}_` +
`${stack_frame_id_0}_..._${stack_frame_id_N}.${run_id}`
Returns:
- run ID
- The stack frame IDs as a tuple of strings.
"""
key_body, run = blob_key.split(".", 1)
key_body = key_body[len(STACK_FRAMES_BLOB_TAG_PREFIX) + 1 :]
stack_frame_ids = key_body.split("_")
return run, stack_frame_ids
class LocalDebuggerV2DataProvider(provider.DataProvider):
"""A DataProvider implementation for tfdbg v2 data on local filesystem.
In this implementation, `experiment_id` is assumed to be the path to the
logdir that contains the DebugEvent file set.
"""
def __init__(self, logdir):
"""Constructor of LocalDebuggerV2DataProvider.
Args:
logdir: Path to the directory from which the tfdbg v2 data will be
loaded.
"""
super().__init__()
self._multiplexer = debug_data_multiplexer.DebuggerV2EventMultiplexer(
logdir
)
def list_runs(self, ctx=None, *, experiment_id):
"""List runs available.
Args:
experiment_id: currently unused, because the backing
DebuggerV2EventMultiplexer does not accommodate multiple experiments.
Returns:
Run names as a list of str.
"""
return [
provider.Run(
run_id=run, # use names as IDs
run_name=run,
start_time=self._get_first_event_timestamp(run),
)
for run in self._multiplexer.Runs()
]
def _get_first_event_timestamp(self, run_name):
try:
return self._multiplexer.FirstEventTimestamp(run_name)
except ValueError as e:
return None
def list_scalars(
self, ctx=None, *, experiment_id, plugin_name, run_tag_filter=None
):
del experiment_id, plugin_name, run_tag_filter # Unused.
raise TypeError("Debugger V2 DataProvider doesn't support scalars.")
def read_scalars(
self,
ctx=None,
*,
experiment_id,
plugin_name,
downsample=None,
run_tag_filter=None,
):
del experiment_id, plugin_name, downsample, run_tag_filter
raise TypeError("Debugger V2 DataProvider doesn't support scalars.")
def read_last_scalars(
self,
ctx=None,
*,
experiment_id,
plugin_name,
run_tag_filter=None,
):
del experiment_id, plugin_name, run_tag_filter
raise TypeError("Debugger V2 DataProvider doesn't support scalars.")
def list_blob_sequences(
self, ctx=None, *, experiment_id, plugin_name, run_tag_filter=None
):
del experiment_id, plugin_name, run_tag_filter # Unused currently.
# TODO(cais): Implement this.
raise NotImplementedError()
def read_blob_sequences(
self,
ctx=None,
*,
experiment_id,
plugin_name,
downsample=None,
run_tag_filter=None,
):
del experiment_id, downsample # Unused.
if plugin_name != PLUGIN_NAME:
raise ValueError("Unsupported plugin_name: %s" % plugin_name)
if run_tag_filter.runs is None:
raise ValueError(
"run_tag_filter.runs is expected to be specified, but is not."
)
if run_tag_filter.tags is None:
raise ValueError(
"run_tag_filter.tags is expected to be specified, but is not."
)
output = dict()
existing_runs = self._multiplexer.Runs()
for run in run_tag_filter.runs:
if run not in existing_runs:
continue
output[run] = dict()
for tag in run_tag_filter.tags:
if tag.startswith(
(
ALERTS_BLOB_TAG_PREFIX,
EXECUTION_DIGESTS_BLOB_TAG_PREFIX,
EXECUTION_DATA_BLOB_TAG_PREFIX,
GRAPH_EXECUTION_DIGESTS_BLOB_TAG_PREFIX,
GRAPH_EXECUTION_DATA_BLOB_TAG_PREFIX,
GRAPH_INFO_BLOB_TAG_PREFIX,
GRAPH_OP_INFO_BLOB_TAG_PREFIX,
SOURCE_FILE_BLOB_TAG_PREFIX,
STACK_FRAMES_BLOB_TAG_PREFIX,
)
) or tag in (SOURCE_FILE_LIST_BLOB_TAG,):
output[run][tag] = [
provider.BlobReference(blob_key="%s.%s" % (tag, run))
]
return output
def read_blob(self, ctx=None, *, blob_key):
if blob_key.startswith(ALERTS_BLOB_TAG_PREFIX):
run, begin, end, alert_type = _parse_alerts_blob_key(blob_key)
return json.dumps(
self._multiplexer.Alerts(
run, begin, end, alert_type_filter=alert_type
)
)
elif blob_key.startswith(EXECUTION_DIGESTS_BLOB_TAG_PREFIX):
run, begin, end = _parse_execution_digest_blob_key(blob_key)
return json.dumps(
self._multiplexer.ExecutionDigests(run, begin, end)
)
elif blob_key.startswith(EXECUTION_DATA_BLOB_TAG_PREFIX):
run, begin, end = _parse_execution_data_blob_key(blob_key)
return json.dumps(self._multiplexer.ExecutionData(run, begin, end))
elif blob_key.startswith(GRAPH_EXECUTION_DIGESTS_BLOB_TAG_PREFIX):
run, begin, end = _parse_graph_execution_digest_blob_key(blob_key)
return json.dumps(
self._multiplexer.GraphExecutionDigests(run, begin, end)
)
elif blob_key.startswith(GRAPH_EXECUTION_DATA_BLOB_TAG_PREFIX):
run, begin, end = _parse_graph_execution_data_blob_key(blob_key)
return json.dumps(
self._multiplexer.GraphExecutionData(run, begin, end)
)
elif blob_key.startswith(GRAPH_INFO_BLOB_TAG_PREFIX):
run, graph_id = _parse_graph_info_blob_key(blob_key)
return json.dumps(self._multiplexer.GraphInfo(run, graph_id))
elif blob_key.startswith(GRAPH_OP_INFO_BLOB_TAG_PREFIX):
run, graph_id, op_name = _parse_graph_op_info_blob_key(blob_key)
return json.dumps(
self._multiplexer.GraphOpInfo(run, graph_id, op_name)
)
elif blob_key.startswith(SOURCE_FILE_LIST_BLOB_TAG):
run = _parse_source_file_list_blob_key(blob_key)
return json.dumps(self._multiplexer.SourceFileList(run))
elif blob_key.startswith(SOURCE_FILE_BLOB_TAG_PREFIX):
run, index = _parse_source_file_blob_key(blob_key)
return json.dumps(self._multiplexer.SourceLines(run, index))
elif blob_key.startswith(STACK_FRAMES_BLOB_TAG_PREFIX):
run, stack_frame_ids = _parse_stack_frames_blob_key(blob_key)
return json.dumps(
self._multiplexer.StackFrames(run, stack_frame_ids)
)
else:
raise ValueError("Unrecognized blob_key: %s" % blob_key)

View File

@ -0,0 +1,505 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""The TensorBoard Debugger V2 plugin."""
import threading
from werkzeug import wrappers
from tensorboard import errors
from tensorboard import plugin_util
from tensorboard.plugins import base_plugin
from tensorboard.plugins.debugger_v2 import debug_data_provider
from tensorboard.backend import http_util
def _error_response(request, error_message):
return http_util.Respond(
request,
{"error": error_message},
"application/json",
code=400,
)
def _missing_run_error_response(request):
return _error_response(request, "run parameter is not provided")
class DebuggerV2Plugin(base_plugin.TBPlugin):
"""Debugger V2 Plugin for TensorBoard."""
plugin_name = debug_data_provider.PLUGIN_NAME
def __init__(self, context):
"""Instantiates Debugger V2 Plugin via TensorBoard core.
Args:
context: A base_plugin.TBContext instance.
"""
super().__init__(context)
self._logdir = context.logdir
self._underlying_data_provider = None
# Held while initializing `_underlying_data_provider` for the first
# time, to make sure that we only construct one.
self._data_provider_init_lock = threading.Lock()
@property
def _data_provider(self):
if self._underlying_data_provider is not None:
return self._underlying_data_provider
with self._data_provider_init_lock:
if self._underlying_data_provider is not None:
return self._underlying_data_provider
# TODO(cais): Implement factory for DataProvider that takes into account
# the settings.
dp = debug_data_provider.LocalDebuggerV2DataProvider(self._logdir)
self._underlying_data_provider = dp
return dp
def get_plugin_apps(self):
# TODO(cais): Add routes as they are implemented.
return {
"/runs": self.serve_runs,
"/alerts": self.serve_alerts,
"/execution/digests": self.serve_execution_digests,
"/execution/data": self.serve_execution_data,
"/graph_execution/digests": self.serve_graph_execution_digests,
"/graph_execution/data": self.serve_graph_execution_data,
"/graphs/graph_info": self.serve_graph_info,
"/graphs/op_info": self.serve_graph_op_info,
"/source_files/list": self.serve_source_files_list,
"/source_files/file": self.serve_source_file,
"/stack_frames/stack_frames": self.serve_stack_frames,
}
def is_active(self):
"""The Debugger V2 plugin must be manually selected."""
return False
def frontend_metadata(self):
return base_plugin.FrontendMetadata(
is_ng_component=True, tab_name="Debugger V2", disable_reload=False
)
@wrappers.Request.application
def serve_runs(self, request):
experiment = plugin_util.experiment_id(request.environ)
runs = self._data_provider.list_runs(experiment_id=experiment)
run_listing = dict()
for run in runs:
run_listing[run.run_id] = {"start_time": run.start_time}
return http_util.Respond(request, run_listing, "application/json")
@wrappers.Request.application
def serve_alerts(self, request):
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
begin = int(request.args.get("begin", "0"))
end = int(request.args.get("end", "-1"))
alert_type = request.args.get("alert_type", None)
run_tag_filter = debug_data_provider.alerts_run_tag_filter(
run, begin, end, alert_type=alert_type
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.InvalidArgumentError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_execution_digests(self, request):
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
begin = int(request.args.get("begin", "0"))
end = int(request.args.get("end", "-1"))
run_tag_filter = debug_data_provider.execution_digest_run_tag_filter(
run, begin, end
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.InvalidArgumentError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_execution_data(self, request):
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
begin = int(request.args.get("begin", "0"))
end = int(request.args.get("end", "-1"))
run_tag_filter = debug_data_provider.execution_data_run_tag_filter(
run, begin, end
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.InvalidArgumentError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_graph_execution_digests(self, request):
"""Serve digests of intra-graph execution events.
As the names imply, this route differs from `serve_execution_digests()`
in that it is for intra-graph execution, while `serve_execution_digests()`
is for top-level (eager) execution.
"""
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
begin = int(request.args.get("begin", "0"))
end = int(request.args.get("end", "-1"))
run_tag_filter = (
debug_data_provider.graph_execution_digest_run_tag_filter(
run, begin, end
)
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.InvalidArgumentError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_graph_execution_data(self, request):
"""Serve detailed data objects of intra-graph execution events.
As the names imply, this route differs from `serve_execution_data()`
in that it is for intra-graph execution, while `serve_execution_data()`
is for top-level (eager) execution.
Unlike `serve_graph_execution_digests()`, this method serves the
full-sized data objects for intra-graph execution events.
"""
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
begin = int(request.args.get("begin", "0"))
end = int(request.args.get("end", "-1"))
run_tag_filter = (
debug_data_provider.graph_execution_data_run_tag_filter(
run, begin, end
)
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.InvalidArgumentError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_graph_info(self, request):
"""Serve basic information about a TensorFlow graph.
The request specifies the debugger-generated ID of the graph being
queried.
The response contains a JSON object with the following fields:
- graph_id: The debugger-generated ID (echoing the request).
- name: The name of the graph (if any). For TensorFlow 2.x
Function Graphs (FuncGraphs), this is typically the name of
the underlying Python function, optionally prefixed with
TensorFlow-generated prefixed such as "__inference_".
Some graphs (e.g., certain outermost graphs) may have no names,
in which case this field is `null`.
- outer_graph_id: Outer graph ID (if any). For an outermost graph
without an outer graph context, this field is `null`.
- inner_graph_ids: Debugger-generated IDs of all the graphs
nested inside this graph. For a graph without any graphs nested
inside, this field is an empty array.
"""
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
graph_id = request.args.get("graph_id")
run_tag_filter = debug_data_provider.graph_info_run_tag_filter(
run, graph_id
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.NotFoundError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_graph_op_info(self, request):
"""Serve information for ops in graphs.
The request specifies the op name and the ID of the graph that
contains the op.
The response contains a JSON object with the following fields:
- op_type
- op_name
- graph_ids: Stack of graph IDs that the op is located in, from
outermost to innermost. The length of this array is always >= 1.
The length is 1 if and only if the graph is an outermost graph.
- num_outputs: Number of output tensors.
- output_tensor_ids: The debugger-generated number IDs for the
symbolic output tensors of the op (an array of numbers).
- host_name: Name of the host on which the op is created.
- stack_trace: Stack frames of the op's creation.
- inputs: Specifications of all inputs to this op.
Currently only immediate (one level of) inputs are provided.
This is an array of length N_in, where N_in is the number of
data inputs received by the op. Each element of the array is an
object with the following fields:
- op_name: Name of the op that provides the input tensor.
- output_slot: 0-based output slot index from which the input
tensor emits.
- data: A recursive data structure of this same schema.
This field is not populated (undefined) at the leaf nodes
of this recursive data structure.
In the rare case wherein the data for an input cannot be
retrieved properly (e.g., special internal op types), this
field will be unpopulated.
This is an empty list for an op with no inputs.
- consumers: Specifications for all the downstream consuming ops of
this. Currently only immediate (one level of) consumers are provided.
This is an array of length N_out, where N_out is the number of
symbolic tensors output by this op.
Each element of the array is an array of which the length equals
the number of downstream ops that consume the corresponding symbolic
tensor (only data edges are tracked).
Each element of the array is an object with the following fields:
- op_name: Name of the op that receives the output tensor as an
input.
- input_slot: 0-based input slot index at which the downstream
op receives this output tensor.
- data: A recursive data structure of this very schema.
This field is not populated (undefined) at the leaf nodes
of this recursive data structure.
In the rare case wherein the data for a consumer op cannot be
retrieved properly (e.g., special internal op types), this
field will be unpopulated.
If this op has no output tensors, this is an empty array.
If one of the output tensors of this op has no consumers, the
corresponding element is an empty array.
"""
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
graph_id = request.args.get("graph_id")
op_name = request.args.get("op_name")
run_tag_filter = debug_data_provider.graph_op_info_run_tag_filter(
run, graph_id, op_name
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.NotFoundError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_source_files_list(self, request):
"""Serves a list of all source files involved in the debugged program."""
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
run_tag_filter = debug_data_provider.source_file_list_run_tag_filter(
run
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
@wrappers.Request.application
def serve_source_file(self, request):
"""Serves the content of a given source file.
The source file is referred to by the index in the list of all source
files involved in the execution of the debugged program, which is
available via the `serve_source_files_list()` serving route.
Args:
request: HTTP request.
Returns:
Response to the request.
"""
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
index = request.args.get("index")
# TOOD(cais): When the need arises, support serving a subset of a
# source file's lines.
if index is None:
return _error_response(
request, "index is not provided for source file content"
)
index = int(index)
run_tag_filter = debug_data_provider.source_file_run_tag_filter(
run, index
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.NotFoundError as e:
return _error_response(request, str(e))
@wrappers.Request.application
def serve_stack_frames(self, request):
"""Serves the content of stack frames.
The source frames being requested are referred to be UUIDs for each of
them, separated by commas.
Args:
request: HTTP request.
Returns:
Response to the request.
"""
experiment = plugin_util.experiment_id(request.environ)
run = request.args.get("run")
if run is None:
return _missing_run_error_response(request)
stack_frame_ids = request.args.get("stack_frame_ids")
if stack_frame_ids is None:
return _error_response(request, "Missing stack_frame_ids parameter")
if not stack_frame_ids:
return _error_response(request, "Empty stack_frame_ids parameter")
stack_frame_ids = stack_frame_ids.split(",")
run_tag_filter = debug_data_provider.stack_frames_run_tag_filter(
run, stack_frame_ids
)
blob_sequences = self._data_provider.read_blob_sequences(
experiment_id=experiment,
plugin_name=self.plugin_name,
run_tag_filter=run_tag_filter,
)
tag = next(iter(run_tag_filter.tags))
try:
return http_util.Respond(
request,
self._data_provider.read_blob(
blob_key=blob_sequences[run][tag][0].blob_key
),
"application/json",
)
except errors.NotFoundError as e:
return _error_response(request, str(e))