1 #include <torch/csrc/profiler/combined_traceback.h>
2 #include <torch/csrc/utils/cpp_stacktraces.h>
3
4 namespace torch {
5
6 static std::atomic<CapturedTraceback::Python*> python_support_ = nullptr;
7
gather(bool python,bool script,bool cpp)8 std::shared_ptr<CapturedTraceback> CapturedTraceback::gather(
9 bool python,
10 bool script,
11 bool cpp) {
12 auto r = std::make_shared<CapturedTraceback>();
13 if (python) {
14 auto p = python_support_.load();
15 while (p && r->frames_.empty()) {
16 r->frames_ = p->gather();
17 r->python_ = p;
18 p = p->next_;
19 }
20 }
21 if (script) {
22 r->script_frames_ = torch::jit::currentCallstack();
23 }
24 if (cpp) {
25 r->cpp_frames_ = unwind::unwind();
26 }
27 return r;
28 }
29
traversePython(visitproc visit,void * arg)30 int CapturedTraceback::traversePython(visitproc visit, void* arg) {
31 TORCH_INTERNAL_ASSERT(python_);
32 return python_->traverse(frames_, visit, arg);
33 }
34
clearPython()35 int CapturedTraceback::clearPython() {
36 TORCH_INTERNAL_ASSERT(python_);
37 return python_->clear(frames_);
38 }
39
~CapturedTraceback()40 CapturedTraceback::~CapturedTraceback() {
41 if (!frames_.empty()) {
42 TORCH_INTERNAL_ASSERT(python_);
43 python_->release(frames_);
44 }
45 }
46
47 struct PyFrameHash {
operator ()torch::PyFrameHash48 std::size_t operator()(const CapturedTraceback::PyFrame& f) const {
49 return std::hash<void*>()(f.code) ^ std::hash<int>()(f.lasti);
50 }
51 };
52
53 struct PyFrameEq {
operator ()torch::PyFrameEq54 std::size_t operator()(
55 const CapturedTraceback::PyFrame& lhs,
56 const CapturedTraceback::PyFrame& rhs) const {
57 return lhs.code == rhs.code && lhs.lasti == rhs.lasti;
58 }
59 };
60
symbolize(const std::vector<CapturedTraceback * > & to_symbolize)61 SymbolizedTracebacks symbolize(
62 const std::vector<CapturedTraceback*>& to_symbolize) {
63 SymbolizedTracebacks r;
64
65 std::unordered_map<void*, size_t> ip_to_frame_offset;
66 std::unordered_map<CapturedTraceback::PyFrame, size_t, PyFrameHash, PyFrameEq>
67 py_to_frame_offset;
68 std::vector<void*> all_cpp_ips;
69
70 // dedup and collect any C++ frames that need symbols for
71 for (const auto& e : to_symbolize) {
72 for (void* f : e->cpp_frames_) {
73 if (!ip_to_frame_offset.count(f)) {
74 ip_to_frame_offset[f] = all_cpp_ips.size();
75 all_cpp_ips.push_back(f);
76 }
77 }
78 }
79 // gather symbol names for C++ frames
80 if (!all_cpp_ips.empty()) {
81 r.all_frames = unwind::symbolize(all_cpp_ips, torch::get_symbolize_mode());
82 }
83
84 // batch symbolization requests so we dedup frame objects
85 // however, we might have to request from different python interpreters
86 // make sure we flush requests before switching interpreters;
87 CapturedTraceback::Python* cur_python = nullptr;
88 std::vector<CapturedTraceback::PyFrame> cur_py_frames;
89 size_t py_frames_size_ = 0;
90
91 for (const auto& e : to_symbolize) {
92 if (e->python_) {
93 if (cur_python != e->python_ && !cur_py_frames.empty()) {
94 if (cur_python) {
95 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
96 cur_python->appendSymbolized(cur_py_frames, r);
97 }
98 cur_py_frames.clear();
99 }
100 cur_python = e->python_;
101 for (const auto& f : e->frames_) {
102 if (!py_to_frame_offset.count(f)) {
103 py_to_frame_offset[f] = py_frames_size_++;
104 cur_py_frames.push_back(f);
105 }
106 }
107 }
108 }
109 if (!cur_py_frames.empty()) {
110 if (cur_python) {
111 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
112 cur_python->appendSymbolized(cur_py_frames, r);
113 }
114 cur_py_frames.clear();
115 }
116 std::vector<std::vector<uint64_t>> python_frame_fragments =
117 std::move(r.tracebacks);
118 r.tracebacks = {};
119
120 for (const auto& sc : to_symbolize) {
121 r.tracebacks.emplace_back();
122 auto py_it = sc->frames_.begin();
123 auto py_end = sc->frames_.end();
124
125 bool jit_appended = false;
126
127 auto append_python = [&](const CapturedTraceback::PyFrame& f) {
128 const auto& fragment =
129 python_frame_fragments.at(py_to_frame_offset.at(f));
130 r.tracebacks.back().insert(
131 r.tracebacks.back().end(), fragment.begin(), fragment.end());
132 };
133
134 auto append_jit = [&]() {
135 if (jit_appended) {
136 return;
137 }
138 jit_appended = true;
139 for (const auto& f : sc->script_frames_) {
140 unwind::Frame frame;
141 frame.funcname =
142 f.filename; // sic: torchscript puts funcname in filename field
143 auto flc = f.range.file_line_col();
144 if (flc) {
145 size_t col = 0;
146 std::tie(frame.filename, frame.lineno, col) = *flc;
147 } else {
148 frame.filename = "??";
149 frame.lineno = 0;
150 }
151 r.tracebacks.back().push_back(r.all_frames.size());
152 r.all_frames.emplace_back(std::move(frame));
153 }
154 };
155
156 for (void* f : sc->cpp_frames_) {
157 uint64_t cpp_frame = ip_to_frame_offset.at(f);
158 const unwind::Frame& uf = r.all_frames.at(cpp_frame);
159 if (uf.funcname.find("PyEval_EvalFrame") != std::string::npos) {
160 if (py_it != py_end) {
161 append_python(*py_it++);
162 }
163 } else if (
164 uf.funcname.rfind("torch::jit::InterpreterStateImpl::run", 0) !=
165 std::string::npos) {
166 append_jit();
167 }
168 r.tracebacks.back().push_back(cpp_frame);
169 }
170
171 // add frames if we otherwise haven't seen the C++ frame indicating where
172 // it should go
173 append_jit();
174
175 for (; py_it != py_end; ++py_it) {
176 append_python(*py_it);
177 }
178 }
179 return r;
180 }
181
addPythonUnwinder(CapturedTraceback::Python * p)182 void CapturedTraceback::addPythonUnwinder(CapturedTraceback::Python* p) {
183 CapturedTraceback::Python* old_unwinder = python_support_.load();
184 do {
185 p->next_ = old_unwinder;
186 } while (!python_support_.compare_exchange_strong(old_unwinder, p));
187 }
188
189 } // namespace torch
190