diff options
Diffstat (limited to 'repo_trace.py')
-rw-r--r-- | repo_trace.py | 184 |
1 files changed, 94 insertions, 90 deletions
diff --git a/repo_trace.py b/repo_trace.py index 1ba86c79..49462174 100644 --- a/repo_trace.py +++ b/repo_trace.py | |||
@@ -29,138 +29,142 @@ from contextlib import ContextDecorator | |||
29 | import platform_utils | 29 | import platform_utils |
30 | 30 | ||
31 | # Env var to implicitly turn on tracing. | 31 | # Env var to implicitly turn on tracing. |
32 | REPO_TRACE = 'REPO_TRACE' | 32 | REPO_TRACE = "REPO_TRACE" |
33 | 33 | ||
34 | # Temporarily set tracing to always on unless user expicitly sets to 0. | 34 | # Temporarily set tracing to always on unless user expicitly sets to 0. |
35 | _TRACE = os.environ.get(REPO_TRACE) != '0' | 35 | _TRACE = os.environ.get(REPO_TRACE) != "0" |
36 | _TRACE_TO_STDERR = False | 36 | _TRACE_TO_STDERR = False |
37 | _TRACE_FILE = None | 37 | _TRACE_FILE = None |
38 | _TRACE_FILE_NAME = 'TRACE_FILE' | 38 | _TRACE_FILE_NAME = "TRACE_FILE" |
39 | _MAX_SIZE = 70 # in MiB | 39 | _MAX_SIZE = 70 # in MiB |
40 | _NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++' | 40 | _NEW_COMMAND_SEP = "+++++++++++++++NEW COMMAND+++++++++++++++++++" |
41 | 41 | ||
42 | 42 | ||
43 | def IsTraceToStderr(): | 43 | def IsTraceToStderr(): |
44 | """Whether traces are written to stderr.""" | 44 | """Whether traces are written to stderr.""" |
45 | return _TRACE_TO_STDERR | 45 | return _TRACE_TO_STDERR |
46 | 46 | ||
47 | 47 | ||
48 | def IsTrace(): | 48 | def IsTrace(): |
49 | """Whether tracing is enabled.""" | 49 | """Whether tracing is enabled.""" |
50 | return _TRACE | 50 | return _TRACE |
51 | 51 | ||
52 | 52 | ||
53 | def SetTraceToStderr(): | 53 | def SetTraceToStderr(): |
54 | """Enables tracing logging to stderr.""" | 54 | """Enables tracing logging to stderr.""" |
55 | global _TRACE_TO_STDERR | 55 | global _TRACE_TO_STDERR |
56 | _TRACE_TO_STDERR = True | 56 | _TRACE_TO_STDERR = True |
57 | 57 | ||
58 | 58 | ||
59 | def SetTrace(): | 59 | def SetTrace(): |
60 | """Enables tracing.""" | 60 | """Enables tracing.""" |
61 | global _TRACE | 61 | global _TRACE |
62 | _TRACE = True | 62 | _TRACE = True |
63 | 63 | ||
64 | 64 | ||
65 | def _SetTraceFile(quiet): | 65 | def _SetTraceFile(quiet): |
66 | """Sets the trace file location.""" | 66 | """Sets the trace file location.""" |
67 | global _TRACE_FILE | 67 | global _TRACE_FILE |
68 | _TRACE_FILE = _GetTraceFile(quiet) | 68 | _TRACE_FILE = _GetTraceFile(quiet) |
69 | 69 | ||
70 | 70 | ||
71 | class Trace(ContextDecorator): | 71 | class Trace(ContextDecorator): |
72 | """Used to capture and save git traces.""" | 72 | """Used to capture and save git traces.""" |
73 | 73 | ||
74 | def _time(self): | 74 | def _time(self): |
75 | """Generate nanoseconds of time in a py3.6 safe way""" | 75 | """Generate nanoseconds of time in a py3.6 safe way""" |
76 | return int(time.time() * 1e+9) | 76 | return int(time.time() * 1e9) |
77 | 77 | ||
78 | def __init__(self, fmt, *args, first_trace=False, quiet=True): | 78 | def __init__(self, fmt, *args, first_trace=False, quiet=True): |
79 | """Initialize the object. | 79 | """Initialize the object. |
80 | 80 | ||
81 | Args: | 81 | Args: |
82 | fmt: The format string for the trace. | 82 | fmt: The format string for the trace. |
83 | *args: Arguments to pass to formatting. | 83 | *args: Arguments to pass to formatting. |
84 | first_trace: Whether this is the first trace of a `repo` invocation. | 84 | first_trace: Whether this is the first trace of a `repo` invocation. |
85 | quiet: Whether to suppress notification of trace file location. | 85 | quiet: Whether to suppress notification of trace file location. |
86 | """ | 86 | """ |
87 | if not IsTrace(): | 87 | if not IsTrace(): |
88 | return | 88 | return |
89 | self._trace_msg = fmt % args | 89 | self._trace_msg = fmt % args |
90 | 90 | ||
91 | if not _TRACE_FILE: | 91 | if not _TRACE_FILE: |
92 | _SetTraceFile(quiet) | 92 | _SetTraceFile(quiet) |
93 | 93 | ||
94 | if first_trace: | 94 | if first_trace: |
95 | _ClearOldTraces() | 95 | _ClearOldTraces() |
96 | self._trace_msg = f'{_NEW_COMMAND_SEP} {self._trace_msg}' | 96 | self._trace_msg = f"{_NEW_COMMAND_SEP} {self._trace_msg}" |
97 | 97 | ||
98 | def __enter__(self): | 98 | def __enter__(self): |
99 | if not IsTrace(): | 99 | if not IsTrace(): |
100 | return self | 100 | return self |
101 | 101 | ||
102 | print_msg = f'PID: {os.getpid()} START: {self._time()} :{self._trace_msg}\n' | 102 | print_msg = ( |
103 | f"PID: {os.getpid()} START: {self._time()} :{self._trace_msg}\n" | ||
104 | ) | ||
103 | 105 | ||
104 | with open(_TRACE_FILE, 'a') as f: | 106 | with open(_TRACE_FILE, "a") as f: |
105 | print(print_msg, file=f) | 107 | print(print_msg, file=f) |
106 | 108 | ||
107 | if _TRACE_TO_STDERR: | 109 | if _TRACE_TO_STDERR: |
108 | print(print_msg, file=sys.stderr) | 110 | print(print_msg, file=sys.stderr) |
109 | 111 | ||
110 | return self | 112 | return self |
111 | 113 | ||
112 | def __exit__(self, *exc): | 114 | def __exit__(self, *exc): |
113 | if not IsTrace(): | 115 | if not IsTrace(): |
114 | return False | 116 | return False |
115 | 117 | ||
116 | print_msg = f'PID: {os.getpid()} END: {self._time()} :{self._trace_msg}\n' | 118 | print_msg = ( |
119 | f"PID: {os.getpid()} END: {self._time()} :{self._trace_msg}\n" | ||
120 | ) | ||
117 | 121 | ||
118 | with open(_TRACE_FILE, 'a') as f: | 122 | with open(_TRACE_FILE, "a") as f: |
119 | print(print_msg, file=f) | 123 | print(print_msg, file=f) |
120 | 124 | ||
121 | if _TRACE_TO_STDERR: | 125 | if _TRACE_TO_STDERR: |
122 | print(print_msg, file=sys.stderr) | 126 | print(print_msg, file=sys.stderr) |
123 | 127 | ||
124 | return False | 128 | return False |
125 | 129 | ||
126 | 130 | ||
127 | def _GetTraceFile(quiet): | 131 | def _GetTraceFile(quiet): |
128 | """Get the trace file or create one.""" | 132 | """Get the trace file or create one.""" |
129 | # TODO: refactor to pass repodir to Trace. | 133 | # TODO: refactor to pass repodir to Trace. |
130 | repo_dir = os.path.dirname(os.path.dirname(__file__)) | 134 | repo_dir = os.path.dirname(os.path.dirname(__file__)) |
131 | trace_file = os.path.join(repo_dir, _TRACE_FILE_NAME) | 135 | trace_file = os.path.join(repo_dir, _TRACE_FILE_NAME) |
132 | if not quiet: | 136 | if not quiet: |
133 | print(f'Trace outputs in {trace_file}', file=sys.stderr) | 137 | print(f"Trace outputs in {trace_file}", file=sys.stderr) |
134 | return trace_file | 138 | return trace_file |
135 | 139 | ||
136 | 140 | ||
137 | def _ClearOldTraces(): | 141 | def _ClearOldTraces(): |
138 | """Clear the oldest commands if trace file is too big.""" | 142 | """Clear the oldest commands if trace file is too big.""" |
139 | try: | 143 | try: |
140 | with open(_TRACE_FILE, 'r', errors='ignore') as f: | 144 | with open(_TRACE_FILE, "r", errors="ignore") as f: |
141 | if os.path.getsize(f.name) / (1024 * 1024) <= _MAX_SIZE: | 145 | if os.path.getsize(f.name) / (1024 * 1024) <= _MAX_SIZE: |
146 | return | ||
147 | trace_lines = f.readlines() | ||
148 | except FileNotFoundError: | ||
142 | return | 149 | return |
143 | trace_lines = f.readlines() | 150 | |
144 | except FileNotFoundError: | 151 | while sum(len(x) for x in trace_lines) / (1024 * 1024) > _MAX_SIZE: |
145 | return | 152 | for i, line in enumerate(trace_lines): |
146 | 153 | if "END:" in line and _NEW_COMMAND_SEP in line: | |
147 | while sum(len(x) for x in trace_lines) / (1024 * 1024) > _MAX_SIZE: | 154 | trace_lines = trace_lines[i + 1 :] |
148 | for i, line in enumerate(trace_lines): | 155 | break |
149 | if 'END:' in line and _NEW_COMMAND_SEP in line: | 156 | else: |
150 | trace_lines = trace_lines[i + 1:] | 157 | # The last chunk is bigger than _MAX_SIZE, so just throw everything |
151 | break | 158 | # away. |
152 | else: | 159 | trace_lines = [] |
153 | # The last chunk is bigger than _MAX_SIZE, so just throw everything away. | 160 | |
154 | trace_lines = [] | 161 | while trace_lines and trace_lines[-1] == "\n": |
155 | 162 | trace_lines = trace_lines[:-1] | |
156 | while trace_lines and trace_lines[-1] == '\n': | 163 | # Write to a temporary file with a unique name in the same filesystem |
157 | trace_lines = trace_lines[:-1] | 164 | # before replacing the original trace file. |
158 | # Write to a temporary file with a unique name in the same filesystem | 165 | temp_dir, temp_prefix = os.path.split(_TRACE_FILE) |
159 | # before replacing the original trace file. | 166 | with tempfile.NamedTemporaryFile( |
160 | temp_dir, temp_prefix = os.path.split(_TRACE_FILE) | 167 | "w", dir=temp_dir, prefix=temp_prefix, delete=False |
161 | with tempfile.NamedTemporaryFile('w', | 168 | ) as f: |
162 | dir=temp_dir, | 169 | f.writelines(trace_lines) |
163 | prefix=temp_prefix, | 170 | platform_utils.rename(f.name, _TRACE_FILE) |
164 | delete=False) as f: | ||
165 | f.writelines(trace_lines) | ||
166 | platform_utils.rename(f.name, _TRACE_FILE) | ||