Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 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 | |
Mike Frysinger | 8a11f6f | 2019-08-27 00:26:15 -0400 | [diff] [blame] | 15 | """Logic for tracing repo interactions. |
| 16 | |
| 17 | Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`. |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 18 | |
| 19 | Temporary: Tracing is always on. Set `REPO_TRACE=0` to turn off. |
| 20 | To also include trace outputs in stderr do `repo --trace_to_stderr ...` |
Mike Frysinger | 8a11f6f | 2019-08-27 00:26:15 -0400 | [diff] [blame] | 21 | """ |
| 22 | |
Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 23 | import sys |
| 24 | import os |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 25 | import time |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 26 | import tempfile |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 27 | from contextlib import ContextDecorator |
Mike Frysinger | 8a11f6f | 2019-08-27 00:26:15 -0400 | [diff] [blame] | 28 | |
Joanna Wang | 24c6314 | 2022-11-08 18:56:52 -0500 | [diff] [blame] | 29 | import platform_utils |
| 30 | |
Mike Frysinger | 8a11f6f | 2019-08-27 00:26:15 -0400 | [diff] [blame] | 31 | # Env var to implicitly turn on tracing. |
Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 32 | REPO_TRACE = 'REPO_TRACE' |
| 33 | |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 34 | # Temporarily set tracing to always on unless user expicitly sets to 0. |
| 35 | _TRACE = os.environ.get(REPO_TRACE) != '0' |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 36 | _TRACE_TO_STDERR = False |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 37 | _TRACE_FILE = None |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 38 | _TRACE_FILE_NAME = 'TRACE_FILE' |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 39 | _MAX_SIZE = 70 # in MiB |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 40 | _NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++' |
| 41 | |
| 42 | |
LaMont Jones | 47020ba | 2022-11-10 00:11:51 +0000 | [diff] [blame] | 43 | def IsTraceToStderr(): |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 44 | """Whether traces are written to stderr.""" |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 45 | return _TRACE_TO_STDERR |
Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 46 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 47 | |
Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 48 | def IsTrace(): |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 49 | """Whether tracing is enabled.""" |
Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 50 | return _TRACE |
| 51 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 52 | |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 53 | def SetTraceToStderr(): |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 54 | """Enables tracing logging to stderr.""" |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 55 | global _TRACE_TO_STDERR |
| 56 | _TRACE_TO_STDERR = True |
| 57 | |
| 58 | |
Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 59 | def SetTrace(): |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 60 | """Enables tracing.""" |
Shawn O. Pearce | ad3193a | 2009-04-18 09:54:51 -0700 | [diff] [blame] | 61 | global _TRACE |
| 62 | _TRACE = True |
| 63 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 64 | |
LaMont Jones | ed25be5 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 65 | def _SetTraceFile(quiet): |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 66 | """Sets the trace file location.""" |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 67 | global _TRACE_FILE |
LaMont Jones | ed25be5 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 68 | _TRACE_FILE = _GetTraceFile(quiet) |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 69 | |
| 70 | |
| 71 | class Trace(ContextDecorator): |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 72 | """Used to capture and save git traces.""" |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 73 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 74 | def _time(self): |
| 75 | """Generate nanoseconds of time in a py3.6 safe way""" |
| 76 | return int(time.time() * 1e+9) |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 77 | |
LaMont Jones | ed25be5 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 78 | def __init__(self, fmt, *args, first_trace=False, quiet=True): |
| 79 | """Initialize the object. |
| 80 | |
| 81 | Args: |
| 82 | fmt: The format string for the trace. |
| 83 | *args: Arguments to pass to formatting. |
| 84 | first_trace: Whether this is the first trace of a `repo` invocation. |
| 85 | quiet: Whether to suppress notification of trace file location. |
| 86 | """ |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 87 | if not IsTrace(): |
| 88 | return |
| 89 | self._trace_msg = fmt % args |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 90 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 91 | if not _TRACE_FILE: |
LaMont Jones | ed25be5 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 92 | _SetTraceFile(quiet) |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 93 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 94 | if first_trace: |
| 95 | _ClearOldTraces() |
| 96 | self._trace_msg = f'{_NEW_COMMAND_SEP} {self._trace_msg}' |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 97 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 98 | def __enter__(self): |
| 99 | if not IsTrace(): |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 100 | return self |
| 101 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 102 | print_msg = f'PID: {os.getpid()} START: {self._time()} :{self._trace_msg}\n' |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 103 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 104 | with open(_TRACE_FILE, 'a') as f: |
| 105 | print(print_msg, file=f) |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 106 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 107 | if _TRACE_TO_STDERR: |
| 108 | print(print_msg, file=sys.stderr) |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 109 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 110 | return self |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 111 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 112 | def __exit__(self, *exc): |
| 113 | if not IsTrace(): |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 114 | return False |
| 115 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 116 | print_msg = f'PID: {os.getpid()} END: {self._time()} :{self._trace_msg}\n' |
| 117 | |
| 118 | with open(_TRACE_FILE, 'a') as f: |
| 119 | print(print_msg, file=f) |
| 120 | |
| 121 | if _TRACE_TO_STDERR: |
| 122 | print(print_msg, file=sys.stderr) |
| 123 | |
| 124 | return False |
| 125 | |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 126 | |
LaMont Jones | ed25be5 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 127 | def _GetTraceFile(quiet): |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 128 | """Get the trace file or create one.""" |
| 129 | # TODO: refactor to pass repodir to Trace. |
| 130 | repo_dir = os.path.dirname(os.path.dirname(__file__)) |
| 131 | trace_file = os.path.join(repo_dir, _TRACE_FILE_NAME) |
LaMont Jones | ed25be5 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 132 | if not quiet: |
| 133 | print(f'Trace outputs in {trace_file}', file=sys.stderr) |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 134 | return trace_file |
| 135 | |
LaMont Jones | afd7671 | 2022-11-10 02:31:19 +0000 | [diff] [blame] | 136 | |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 137 | def _ClearOldTraces(): |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 138 | """Clear the oldest commands if trace file is too big.""" |
| 139 | try: |
| 140 | with open(_TRACE_FILE, 'r', errors='ignore') as f: |
| 141 | if os.path.getsize(f.name) / (1024 * 1024) <= _MAX_SIZE: |
| 142 | return |
| 143 | trace_lines = f.readlines() |
| 144 | except FileNotFoundError: |
| 145 | return |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 146 | |
Joanna Wang | 0324e43 | 2022-12-09 17:49:07 -0500 | [diff] [blame] | 147 | while sum(len(x) for x in trace_lines) / (1024 * 1024) > _MAX_SIZE: |
| 148 | for i, line in enumerate(trace_lines): |
| 149 | if 'END:' in line and _NEW_COMMAND_SEP in line: |
| 150 | trace_lines = trace_lines[i + 1:] |
| 151 | break |
| 152 | else: |
| 153 | # The last chunk is bigger than _MAX_SIZE, so just throw everything away. |
| 154 | trace_lines = [] |
| 155 | |
| 156 | while trace_lines and trace_lines[-1] == '\n': |
| 157 | trace_lines = trace_lines[:-1] |
| 158 | # Write to a temporary file with a unique name in the same filesystem |
| 159 | # before replacing the original trace file. |
| 160 | temp_dir, temp_prefix = os.path.split(_TRACE_FILE) |
| 161 | with tempfile.NamedTemporaryFile('w', |
| 162 | dir=temp_dir, |
| 163 | prefix=temp_prefix, |
| 164 | delete=False) as f: |
| 165 | f.writelines(trace_lines) |
| 166 | platform_utils.rename(f.name, _TRACE_FILE) |