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"""Library for arbitrary expression evaluation based on a debugger data dump."""
16from __future__ import absolute_import
17from __future__ import division
18from __future__ import print_function
19
20import re
21
22import numpy as np  # pylint: disable=unused-import
23
24from tensorflow.python.debug.lib import debug_data
25
26_DUMP_TENSOR_PATTERN = re.compile(r"`.*?`")
27_DEVICE_NAME_PREFIX_PATTERN = re.compile(
28    r"/job:(\w)+/replica:(\d)+/task:(\d)+/(\w)+:(\d)+:")
29_EXEC_INDEX_SUFFIX_PATTERN = re.compile(r"\[(\d)*\]$")
30
31_DEFAULT_DEBUG_OP = "DebugIdentity"
32
33
34def _parse_debug_tensor_name(debug_tensor_name):
35  # pylint: disable=line-too-long
36  """Parse a debug tensor name in a to-be-evaluated expression.
37
38  Args:
39    debug_tensor_name: name of the debug tensor, with or without
40      device name as a prefix, with or without debug op, with or
41      without '[<exec_index>]' as a suffix.
42      E.g., without device name prefix, without debug op suffix:
43        "hidden_0/MatMul:0"
44      E.g., with device name prefix:
45        "/job:worker/replica:0/task:1/gpu:0:hidden_0/MatMul:0"
46      E.g., with debug op suffix:
47        "hidden_0/MatMul:0:DebugNumericSummary"
48      E.g., with device name prefix and debug op suffix:
49        "/job:worker/replica:0/task:1/gpu:0:hidden_0/MatMul:0:DebugNumericSummary"
50      E.g., with device name prefix, debug op and an exec index:
51        "/job:worker/replica:0/task:1/gpu:0:hidden_0/MatMul:0:DebugNumericSummary[1]"
52
53  Returns:
54    device_name: If device name prefix exists, the device name; otherwise,
55      `None`.
56    node_name: Name of the node.
57    output_slot: Output slot index as an `int`.
58    debug_op: If the debug op suffix exists, the debug op name; otheriwse,
59      `None`.
60    exec_index: Execution index (applicable to cases in which a debug tensor
61      is computed multiple times in a `tf.Session.run` call, e.g., due to
62      `tf.while_loop`). If the exec_index suffix does not exist, this value
63      defaults to `0`.
64
65  Raises:
66    ValueError: If the input `debug_tensor_name` is malformed.
67  """
68  # pylint: enable=line-too-long
69  device_prefix_match = re.match(_DEVICE_NAME_PREFIX_PATTERN, debug_tensor_name)
70  if device_prefix_match:
71    device_name = debug_tensor_name[
72        device_prefix_match.start() : device_prefix_match.end() - 1]
73    debug_tensor_name = debug_tensor_name[device_prefix_match.end():]
74  else:
75    device_name = None
76
77  split_items = debug_tensor_name.split(":")
78  if len(split_items) not in (2, 3):
79    raise ValueError(
80        "The debug tensor name in the to-be-evaluated expression is malformed: "
81        "'%s'" % debug_tensor_name)
82    # TODO(cais): Provide examples of good debug tensor names in the error
83    # message.
84
85  exec_index_match = re.search(_EXEC_INDEX_SUFFIX_PATTERN, split_items[-1])
86  if exec_index_match:
87    exec_index = int(split_items[-1][
88        exec_index_match.start() + 1 : exec_index_match.end() - 1])
89    split_items[-1] = split_items[-1][:exec_index_match.start()]
90  else:
91    exec_index = 0
92
93  if len(split_items) == 2:
94    node_name = split_items[0]
95    output_slot = int(split_items[1])
96    debug_op = _DEFAULT_DEBUG_OP
97  else:
98    split_items = debug_tensor_name.split(":")
99    node_name = split_items[0]
100    output_slot = int(split_items[1])
101    debug_op = split_items[2]
102
103  return device_name, node_name, output_slot, debug_op, exec_index
104
105
106class ExpressionEvaluator(object):
107  """Evaluates Python expressions using debug tensor values from a dump."""
108
109  def __init__(self, dump):
110    """Constructor of ExpressionEvaluator.
111
112    Args:
113      dump: an instance of `DebugDumpDir`.
114    """
115    self._dump = dump
116    self._cached_tensor_values = dict()
117
118  def evaluate(self, expression):
119    """Parse an expression.
120
121    Args:
122      expression: the expression to be parsed.
123
124    Returns:
125      The result of the evaluation.
126
127    Raises:
128      ValueError: If the value of one or more of the debug tensors in the
129        expression are not available.
130    """
131    dump_tensors_iter = re.finditer(_DUMP_TENSOR_PATTERN, expression)
132    rewritten_expression = expression
133    for match in reversed(list(dump_tensors_iter)):
134      tensor_name = match.group(0)[1:-1].strip()
135      device_name, node_name, output_slot, debug_op, exec_index = (
136          _parse_debug_tensor_name(tensor_name))
137      if tensor_name not in self._cached_tensor_values:
138        try:
139          value = self._dump.get_tensors(
140              node_name, output_slot, debug_op,
141              device_name=device_name)[exec_index]
142        except debug_data.WatchKeyDoesNotExistInDebugDumpDirError:
143          raise ValueError(
144              "Eval failed due to the value of %s:%d:DebugIdentity being "
145              "unavailable" % (node_name, output_slot))
146        self._cached_tensor_values[tensor_name] = value
147      rewritten_expression = (
148          rewritten_expression[:match.start(0)] +
149          "self._cached_tensor_values['" + tensor_name + "']" +
150          rewritten_expression[match.end(0):])
151
152    return eval(rewritten_expression)  # pylint: disable=eval-used
153