Skip to content

Commit e085051

Browse files
authored
Dump remote tracebacks to make local ones more friendly (#3028)
1 parent 44ed47b commit e085051

File tree

8 files changed

+252
-103
lines changed

8 files changed

+252
-103
lines changed

mars/lib/tbcode.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright 1999-2021 Alibaba Group Holding Ltd.
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+
This utility module dumps code of remote traceback and loads them
17+
into local linecache. This enables displaying codes of remote
18+
tracebacks correctly.
19+
"""
20+
21+
import linecache
22+
import os
23+
import types
24+
from collections import defaultdict
25+
26+
27+
def dump_traceback_code(
28+
tb: types.TracebackType, number_of_lines_of_context: int = 5
29+
):
30+
"""
31+
Dump codes before and after lines of tracebacks.
32+
33+
Parameters
34+
----------
35+
tb: types.TracebackType
36+
Traceback object
37+
number_of_lines_of_context: int
38+
Total number of lines around the code
39+
Returns
40+
-------
41+
result: dict
42+
Dumped code lines of traceback
43+
"""
44+
results = defaultdict(lambda: dict(fragments=[]))
45+
46+
while tb:
47+
file_name = tb.tb_frame.f_code.co_filename
48+
if linecache.getline(file_name, tb.tb_lineno): # pragma: no branch
49+
code_lines = linecache.cache[file_name][2]
50+
left_range = max(tb.tb_lineno - number_of_lines_of_context // 2 - 1, 0)
51+
right_range = min(left_range + number_of_lines_of_context, len(code_lines))
52+
53+
cache_data = linecache.cache[file_name]
54+
fragment = cache_data[2][left_range:right_range]
55+
results[file_name]["fragments"].append(
56+
dict(left=left_range, right=right_range, code=fragment)
57+
)
58+
results[file_name].update(
59+
dict(
60+
size=cache_data[0], lines=len(cache_data[2])
61+
)
62+
)
63+
tb = tb.tb_next
64+
return dict(results)
65+
66+
67+
def load_traceback_code(code_frags: dict, cache: dict = None):
68+
"""
69+
Load dumped codes for remote tracebacks.
70+
71+
Parameters
72+
----------
73+
code_frags: dict
74+
Dumped codes for remote traceback.
75+
cache: dict
76+
Target for codes to be dumped, for test purpose only.
77+
Production code should keep this field as None.
78+
"""
79+
if cache is not None:
80+
real_cache = False
81+
else:
82+
real_cache = True
83+
cache = linecache.cache
84+
85+
for file_name, profile in code_frags.items():
86+
if real_cache and os.path.exists(file_name):
87+
# skip rewriting caches of existing files
88+
continue
89+
90+
if file_name not in cache:
91+
# keep field 1 (mtime) as None to ensure lazy cache
92+
cache[file_name] = (
93+
profile["size"], None, [""] * profile["lines"], file_name
94+
)
95+
for fragment in profile["fragments"]:
96+
left_range, right_range = fragment["left"], fragment["right"]
97+
cache[file_name][2][left_range:right_range] = fragment["code"]

0 commit comments

Comments
 (0)