1 /* Copyright 2020 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 #ifndef TENSORFLOW_PYTHON_UTIL_STACK_TRACE_H_
17 #define TENSORFLOW_PYTHON_UTIL_STACK_TRACE_H_
18
19 #include <Python.h>
20 #include <frameobject.h>
21
22 #include <array>
23 #include <limits>
24 #include <sstream>
25 #include <string>
26
27 #include "absl/base/attributes.h"
28 #include "absl/base/optimization.h"
29 #include "absl/container/flat_hash_map.h"
30 #include "absl/container/flat_hash_set.h"
31 #include "absl/container/inlined_vector.h"
32 #include "absl/types/optional.h"
33 #include "tensorflow/core/platform/status.h"
34 #include "tensorflow/core/util/managed_stack_trace.h"
35
36 namespace tensorflow {
37
38 // Assert that Python GIL is held.
39 // TODO(cheshire): Fix duplication vs. py_util.h
DCheckPyGilStateForStackTrace()40 inline void DCheckPyGilStateForStackTrace() {
41 #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 4
42 DCHECK(PyGILState_Check());
43 #endif
44 }
45
46 // A class for capturing Python stack trace.
47 class StackTrace final {
48 public:
49 static constexpr int kStackTraceInitialSize = 30;
50
StackTrace()51 StackTrace() {}
52
53 // Returns `StackTrace` object that captures the current Python stack trace.
54 // `limit` determines how many stack frames at most are returned: set to -1
55 // for "no limit".
56 // Python GIL must be acquired beforehand.
57 ABSL_MUST_USE_RESULT
58 ABSL_ATTRIBUTE_HOT
Capture(int limit)59 static StackTrace Capture(int limit) {
60 DCheckPyGilStateForStackTrace();
61 if (limit == -1) limit = std::numeric_limits<int>::max();
62
63 StackTrace result;
64 const PyFrameObject* frame = PyThreadState_GET()->frame;
65 int i = 0;
66 for (; i < limit && frame != nullptr; frame = frame->f_back, ++i) {
67 PyCodeObject* code_obj = frame->f_code;
68 DCHECK(code_obj != nullptr);
69
70 Py_INCREF(code_obj);
71 result.code_objs_.push_back(std::make_pair(code_obj, frame->f_lasti));
72 }
73 return result;
74 }
75
76 // Python GIL must be acquired beforehand.
77 ABSL_ATTRIBUTE_HOT
~StackTrace()78 ~StackTrace() { Clear(); }
79
StackTrace(StackTrace && other)80 StackTrace(StackTrace&& other) { std::swap(code_objs_, other.code_objs_); }
81
82 // Python GIL must be acquired beforehand.
83 ABSL_ATTRIBUTE_HOT
84 StackTrace& operator=(StackTrace&& other) {
85 Clear();
86 std::swap(code_objs_, other.code_objs_);
87 return *this;
88 }
89
90 // Returns a structured representation of the captured stack trace.
91 // `mapper` provides a custom mapping for translating stack frames, `filter`
92 // returns `true` for the stack frames which should be omitted.
93 //
94 // `reverse_traversal` changes the traversal order of the stack trace, and
95 // `limit` bounds the number of returned frames (after filtering).
96 std::vector<StackFrame> ToStackFrames(const StackTraceMap& mapper = {},
97 const StackTraceFilter& filtered = {},
98 bool reverse_traversal = false,
99 int limit = -1) const;
100
101 // Python GIL must be acquired beforehand.
102 ABSL_ATTRIBUTE_HOT
Clear()103 void Clear() {
104 if (!code_objs_.empty()) DCheckPyGilStateForStackTrace();
105 for (const auto& p : code_objs_) Py_DECREF(p.first);
106 code_objs_.clear();
107 }
108
109 private:
110 absl::InlinedVector<std::pair<PyCodeObject*, int>, kStackTraceInitialSize>
111 code_objs_;
112
113 StackTrace(const StackTrace&) = delete;
114 StackTrace& operator=(const StackTrace&) = delete;
115 };
116
117 // A class that manages Python stack traces in a circular buffer. Users can
118 // insert stack trace entries and retrive them by ids.
119 class StackTraceManager {
120 public:
121 static constexpr int kStackTraceCircularBufferSize = 1024;
122
123 // Captures the current Python stack trace and returns an id.
124 // Python GIL must be acquired beforehand.
125 ABSL_MUST_USE_RESULT
126 ABSL_ATTRIBUTE_HOT
Capture(int limit)127 int Capture(int limit) {
128 DCheckPyGilStateForStackTrace();
129 const int id = next_id_++;
130 const int index = id & (kStackTraceCircularBufferSize - 1);
131 stack_traces_[index] = StackTrace::Capture(limit);
132 return id;
133 }
134
135 // Retrieve captured Python stack trace by id. Returns `nullptr` if the
136 // requested stack trace is evicted from the circular buffer.
137 // Python GIL must be acquired beforehand.
138 ABSL_MUST_USE_RESULT
139 StackTrace* Get(int id);
140
141 private:
142 int next_id_ = 0;
143 std::array<StackTrace, kStackTraceCircularBufferSize> stack_traces_;
144 };
145
146 // Singleton StackTraceManager.
147 extern StackTraceManager* const stack_trace_manager;
148
149 // Converts the ManagedStackTrace (identified by ID) to a vector of stack
150 // frames.
ManagedStackTraceToStackFrames(int id,const StackTraceMap & mapper,const StackTraceFilter & filtered,bool reverse_traversal,int limit)151 inline std::vector<StackFrame> ManagedStackTraceToStackFrames(
152 int id, const StackTraceMap& mapper, const StackTraceFilter& filtered,
153 bool reverse_traversal, int limit) {
154 PyGILState_STATE gstate = PyGILState_Ensure();
155 std::vector<StackFrame> result = stack_trace_manager->Get(id)->ToStackFrames(
156 mapper, filtered, reverse_traversal, limit);
157 PyGILState_Release(gstate);
158 return result;
159 }
160
161 // Returns Python stack trace object that can be converted to string.
162 // Note that the actual stack trace is kept in a circular buffer for string
163 // conversion could fail if it's evicted before.
164 // Python GIL must be acquired beforehand.
GetStackTrace(int limit)165 inline ManagedStackTrace GetStackTrace(int limit) {
166 DCheckPyGilStateForStackTrace();
167 return ManagedStackTrace(stack_trace_manager->Capture(limit),
168 &ManagedStackTraceToStackFrames);
169 }
170
171 } // namespace tensorflow
172
173 #endif // TENSORFLOW_PYTHON_UTIL_STACK_TRACE_H_
174