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