blob: ee224ea7c8b528e946d7847cf24a96fd8a1ff52d [file] [log] [blame]
Shawn O. Pearcead3193a2009-04-18 09:54:51 -07001# 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 Frysinger8a11f6f2019-08-27 00:26:15 -040015"""Logic for tracing repo interactions.
16
17Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`.
Joanna Wanga6c52f52022-11-03 16:51:19 -040018
19Temporary: Tracing is always on. Set `REPO_TRACE=0` to turn off.
20To also include trace outputs in stderr do `repo --trace_to_stderr ...`
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040021"""
22
Mike Frysinger06ddc8c2023-08-21 21:26:51 -040023import contextlib
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070024import os
Mike Frysinger64477332023-08-21 21:20:32 -040025import sys
Joanna Wang0324e432022-12-09 17:49:07 -050026import tempfile
Mike Frysinger64477332023-08-21 21:20:32 -040027import time
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040028
Joanna Wang24c63142022-11-08 18:56:52 -050029import platform_utils
30
Mike Frysinger64477332023-08-21 21:20:32 -040031
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040032# Env var to implicitly turn on tracing.
Gavin Makea2e3302023-03-11 06:46:20 +000033REPO_TRACE = "REPO_TRACE"
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070034
Joanna Wanga6c52f52022-11-03 16:51:19 -040035# Temporarily set tracing to always on unless user expicitly sets to 0.
Gavin Makea2e3302023-03-11 06:46:20 +000036_TRACE = os.environ.get(REPO_TRACE) != "0"
Joanna Wanga6c52f52022-11-03 16:51:19 -040037_TRACE_TO_STDERR = False
Joanna Wanga6c52f52022-11-03 16:51:19 -040038_TRACE_FILE = None
Gavin Makea2e3302023-03-11 06:46:20 +000039_TRACE_FILE_NAME = "TRACE_FILE"
Joanna Wang0324e432022-12-09 17:49:07 -050040_MAX_SIZE = 70 # in MiB
Gavin Makea2e3302023-03-11 06:46:20 +000041_NEW_COMMAND_SEP = "+++++++++++++++NEW COMMAND+++++++++++++++++++"
Joanna Wanga6c52f52022-11-03 16:51:19 -040042
43
LaMont Jones47020ba2022-11-10 00:11:51 +000044def IsTraceToStderr():
Gavin Makea2e3302023-03-11 06:46:20 +000045 """Whether traces are written to stderr."""
46 return _TRACE_TO_STDERR
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070047
David Pursehouse819827a2020-02-12 15:20:19 +090048
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070049def IsTrace():
Gavin Makea2e3302023-03-11 06:46:20 +000050 """Whether tracing is enabled."""
51 return _TRACE
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070052
David Pursehouse819827a2020-02-12 15:20:19 +090053
Joanna Wanga6c52f52022-11-03 16:51:19 -040054def SetTraceToStderr():
Gavin Makea2e3302023-03-11 06:46:20 +000055 """Enables tracing logging to stderr."""
56 global _TRACE_TO_STDERR
57 _TRACE_TO_STDERR = True
Joanna Wanga6c52f52022-11-03 16:51:19 -040058
59
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070060def SetTrace():
Gavin Makea2e3302023-03-11 06:46:20 +000061 """Enables tracing."""
62 global _TRACE
63 _TRACE = True
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070064
David Pursehouse819827a2020-02-12 15:20:19 +090065
LaMont Jonesed25be52022-11-10 02:31:19 +000066def _SetTraceFile(quiet):
Gavin Makea2e3302023-03-11 06:46:20 +000067 """Sets the trace file location."""
68 global _TRACE_FILE
69 _TRACE_FILE = _GetTraceFile(quiet)
Joanna Wanga6c52f52022-11-03 16:51:19 -040070
71
Mike Frysinger06ddc8c2023-08-21 21:26:51 -040072class Trace(contextlib.ContextDecorator):
Gavin Makea2e3302023-03-11 06:46:20 +000073 """Used to capture and save git traces."""
Joanna Wanga6c52f52022-11-03 16:51:19 -040074
Gavin Makea2e3302023-03-11 06:46:20 +000075 def _time(self):
76 """Generate nanoseconds of time in a py3.6 safe way"""
77 return int(time.time() * 1e9)
Joanna Wanga6c52f52022-11-03 16:51:19 -040078
Gavin Makea2e3302023-03-11 06:46:20 +000079 def __init__(self, fmt, *args, first_trace=False, quiet=True):
80 """Initialize the object.
LaMont Jonesed25be52022-11-10 02:31:19 +000081
Gavin Makea2e3302023-03-11 06:46:20 +000082 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 Wanga6c52f52022-11-03 16:51:19 -040091
Gavin Makea2e3302023-03-11 06:46:20 +000092 if not _TRACE_FILE:
93 _SetTraceFile(quiet)
Joanna Wanga6c52f52022-11-03 16:51:19 -040094
Gavin Makea2e3302023-03-11 06:46:20 +000095 if first_trace:
96 _ClearOldTraces()
97 self._trace_msg = f"{_NEW_COMMAND_SEP} {self._trace_msg}"
Joanna Wanga6c52f52022-11-03 16:51:19 -040098
Gavin Makea2e3302023-03-11 06:46:20 +000099 def __enter__(self):
100 if not IsTrace():
101 return self
Joanna Wanga6c52f52022-11-03 16:51:19 -0400102
Gavin Makea2e3302023-03-11 06:46:20 +0000103 print_msg = (
104 f"PID: {os.getpid()} START: {self._time()} :{self._trace_msg}\n"
105 )
Joanna Wanga6c52f52022-11-03 16:51:19 -0400106
Gavin Makea2e3302023-03-11 06:46:20 +0000107 with open(_TRACE_FILE, "a") as f:
108 print(print_msg, file=f)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400109
Gavin Makea2e3302023-03-11 06:46:20 +0000110 if _TRACE_TO_STDERR:
111 print(print_msg, file=sys.stderr)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400112
Gavin Makea2e3302023-03-11 06:46:20 +0000113 return self
Joanna Wanga6c52f52022-11-03 16:51:19 -0400114
Gavin Makea2e3302023-03-11 06:46:20 +0000115 def __exit__(self, *exc):
116 if not IsTrace():
117 return False
Joanna Wanga6c52f52022-11-03 16:51:19 -0400118
Gavin Makea2e3302023-03-11 06:46:20 +0000119 print_msg = (
120 f"PID: {os.getpid()} END: {self._time()} :{self._trace_msg}\n"
121 )
LaMont Jonesafd76712022-11-10 02:31:19 +0000122
Gavin Makea2e3302023-03-11 06:46:20 +0000123 with open(_TRACE_FILE, "a") as f:
124 print(print_msg, file=f)
LaMont Jonesafd76712022-11-10 02:31:19 +0000125
Gavin Makea2e3302023-03-11 06:46:20 +0000126 if _TRACE_TO_STDERR:
127 print(print_msg, file=sys.stderr)
LaMont Jonesafd76712022-11-10 02:31:19 +0000128
Gavin Makea2e3302023-03-11 06:46:20 +0000129 return False
LaMont Jonesafd76712022-11-10 02:31:19 +0000130
Joanna Wanga6c52f52022-11-03 16:51:19 -0400131
LaMont Jonesed25be52022-11-10 02:31:19 +0000132def _GetTraceFile(quiet):
Gavin Makea2e3302023-03-11 06:46:20 +0000133 """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 Wanga6c52f52022-11-03 16:51:19 -0400140
LaMont Jonesafd76712022-11-10 02:31:19 +0000141
Joanna Wanga6c52f52022-11-03 16:51:19 -0400142def _ClearOldTraces():
Gavin Makea2e3302023-03-11 06:46:20 +0000143 """Clear the oldest commands if trace file is too big."""
144 try:
Jason R. Coombs034950b2023-10-20 23:32:02 +0545145 with open(_TRACE_FILE, errors="ignore") as f:
Gavin Makea2e3302023-03-11 06:46:20 +0000146 if os.path.getsize(f.name) / (1024 * 1024) <= _MAX_SIZE:
147 return
148 trace_lines = f.readlines()
149 except FileNotFoundError:
Joanna Wang0324e432022-12-09 17:49:07 -0500150 return
Joanna Wanga6c52f52022-11-03 16:51:19 -0400151
Gavin Makea2e3302023-03-11 06:46:20 +0000152 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 Wang0324e432022-12-09 17:49:07 -0500161
Gavin Makea2e3302023-03-11 06:46:20 +0000162 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)