blob: 49462174784bc7a414ae0dcf2a196362b74bdb7c [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
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070023import sys
24import os
Joanna Wanga6c52f52022-11-03 16:51:19 -040025import time
Joanna Wang0324e432022-12-09 17:49:07 -050026import tempfile
Joanna Wanga6c52f52022-11-03 16:51:19 -040027from contextlib import ContextDecorator
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040028
Joanna Wang24c63142022-11-08 18:56:52 -050029import platform_utils
30
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040031# Env var to implicitly turn on tracing.
Gavin Makea2e3302023-03-11 06:46:20 +000032REPO_TRACE = "REPO_TRACE"
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070033
Joanna Wanga6c52f52022-11-03 16:51:19 -040034# Temporarily set tracing to always on unless user expicitly sets to 0.
Gavin Makea2e3302023-03-11 06:46:20 +000035_TRACE = os.environ.get(REPO_TRACE) != "0"
Joanna Wanga6c52f52022-11-03 16:51:19 -040036_TRACE_TO_STDERR = False
Joanna Wanga6c52f52022-11-03 16:51:19 -040037_TRACE_FILE = None
Gavin Makea2e3302023-03-11 06:46:20 +000038_TRACE_FILE_NAME = "TRACE_FILE"
Joanna Wang0324e432022-12-09 17:49:07 -050039_MAX_SIZE = 70 # in MiB
Gavin Makea2e3302023-03-11 06:46:20 +000040_NEW_COMMAND_SEP = "+++++++++++++++NEW COMMAND+++++++++++++++++++"
Joanna Wanga6c52f52022-11-03 16:51:19 -040041
42
LaMont Jones47020ba2022-11-10 00:11:51 +000043def IsTraceToStderr():
Gavin Makea2e3302023-03-11 06:46:20 +000044 """Whether traces are written to stderr."""
45 return _TRACE_TO_STDERR
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070046
David Pursehouse819827a2020-02-12 15:20:19 +090047
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070048def IsTrace():
Gavin Makea2e3302023-03-11 06:46:20 +000049 """Whether tracing is enabled."""
50 return _TRACE
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070051
David Pursehouse819827a2020-02-12 15:20:19 +090052
Joanna Wanga6c52f52022-11-03 16:51:19 -040053def SetTraceToStderr():
Gavin Makea2e3302023-03-11 06:46:20 +000054 """Enables tracing logging to stderr."""
55 global _TRACE_TO_STDERR
56 _TRACE_TO_STDERR = True
Joanna Wanga6c52f52022-11-03 16:51:19 -040057
58
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070059def SetTrace():
Gavin Makea2e3302023-03-11 06:46:20 +000060 """Enables tracing."""
61 global _TRACE
62 _TRACE = True
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070063
David Pursehouse819827a2020-02-12 15:20:19 +090064
LaMont Jonesed25be52022-11-10 02:31:19 +000065def _SetTraceFile(quiet):
Gavin Makea2e3302023-03-11 06:46:20 +000066 """Sets the trace file location."""
67 global _TRACE_FILE
68 _TRACE_FILE = _GetTraceFile(quiet)
Joanna Wanga6c52f52022-11-03 16:51:19 -040069
70
71class Trace(ContextDecorator):
Gavin Makea2e3302023-03-11 06:46:20 +000072 """Used to capture and save git traces."""
Joanna Wanga6c52f52022-11-03 16:51:19 -040073
Gavin Makea2e3302023-03-11 06:46:20 +000074 def _time(self):
75 """Generate nanoseconds of time in a py3.6 safe way"""
76 return int(time.time() * 1e9)
Joanna Wanga6c52f52022-11-03 16:51:19 -040077
Gavin Makea2e3302023-03-11 06:46:20 +000078 def __init__(self, fmt, *args, first_trace=False, quiet=True):
79 """Initialize the object.
LaMont Jonesed25be52022-11-10 02:31:19 +000080
Gavin Makea2e3302023-03-11 06:46:20 +000081 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 """
87 if not IsTrace():
88 return
89 self._trace_msg = fmt % args
Joanna Wanga6c52f52022-11-03 16:51:19 -040090
Gavin Makea2e3302023-03-11 06:46:20 +000091 if not _TRACE_FILE:
92 _SetTraceFile(quiet)
Joanna Wanga6c52f52022-11-03 16:51:19 -040093
Gavin Makea2e3302023-03-11 06:46:20 +000094 if first_trace:
95 _ClearOldTraces()
96 self._trace_msg = f"{_NEW_COMMAND_SEP} {self._trace_msg}"
Joanna Wanga6c52f52022-11-03 16:51:19 -040097
Gavin Makea2e3302023-03-11 06:46:20 +000098 def __enter__(self):
99 if not IsTrace():
100 return self
Joanna Wanga6c52f52022-11-03 16:51:19 -0400101
Gavin Makea2e3302023-03-11 06:46:20 +0000102 print_msg = (
103 f"PID: {os.getpid()} START: {self._time()} :{self._trace_msg}\n"
104 )
Joanna Wanga6c52f52022-11-03 16:51:19 -0400105
Gavin Makea2e3302023-03-11 06:46:20 +0000106 with open(_TRACE_FILE, "a") as f:
107 print(print_msg, file=f)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400108
Gavin Makea2e3302023-03-11 06:46:20 +0000109 if _TRACE_TO_STDERR:
110 print(print_msg, file=sys.stderr)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400111
Gavin Makea2e3302023-03-11 06:46:20 +0000112 return self
Joanna Wanga6c52f52022-11-03 16:51:19 -0400113
Gavin Makea2e3302023-03-11 06:46:20 +0000114 def __exit__(self, *exc):
115 if not IsTrace():
116 return False
Joanna Wanga6c52f52022-11-03 16:51:19 -0400117
Gavin Makea2e3302023-03-11 06:46:20 +0000118 print_msg = (
119 f"PID: {os.getpid()} END: {self._time()} :{self._trace_msg}\n"
120 )
LaMont Jonesafd76712022-11-10 02:31:19 +0000121
Gavin Makea2e3302023-03-11 06:46:20 +0000122 with open(_TRACE_FILE, "a") as f:
123 print(print_msg, file=f)
LaMont Jonesafd76712022-11-10 02:31:19 +0000124
Gavin Makea2e3302023-03-11 06:46:20 +0000125 if _TRACE_TO_STDERR:
126 print(print_msg, file=sys.stderr)
LaMont Jonesafd76712022-11-10 02:31:19 +0000127
Gavin Makea2e3302023-03-11 06:46:20 +0000128 return False
LaMont Jonesafd76712022-11-10 02:31:19 +0000129
Joanna Wanga6c52f52022-11-03 16:51:19 -0400130
LaMont Jonesed25be52022-11-10 02:31:19 +0000131def _GetTraceFile(quiet):
Gavin Makea2e3302023-03-11 06:46:20 +0000132 """Get the trace file or create one."""
133 # TODO: refactor to pass repodir to Trace.
134 repo_dir = os.path.dirname(os.path.dirname(__file__))
135 trace_file = os.path.join(repo_dir, _TRACE_FILE_NAME)
136 if not quiet:
137 print(f"Trace outputs in {trace_file}", file=sys.stderr)
138 return trace_file
Joanna Wanga6c52f52022-11-03 16:51:19 -0400139
LaMont Jonesafd76712022-11-10 02:31:19 +0000140
Joanna Wanga6c52f52022-11-03 16:51:19 -0400141def _ClearOldTraces():
Gavin Makea2e3302023-03-11 06:46:20 +0000142 """Clear the oldest commands if trace file is too big."""
143 try:
144 with open(_TRACE_FILE, "r", errors="ignore") as f:
145 if os.path.getsize(f.name) / (1024 * 1024) <= _MAX_SIZE:
146 return
147 trace_lines = f.readlines()
148 except FileNotFoundError:
Joanna Wang0324e432022-12-09 17:49:07 -0500149 return
Joanna Wanga6c52f52022-11-03 16:51:19 -0400150
Gavin Makea2e3302023-03-11 06:46:20 +0000151 while sum(len(x) for x in trace_lines) / (1024 * 1024) > _MAX_SIZE:
152 for i, line in enumerate(trace_lines):
153 if "END:" in line and _NEW_COMMAND_SEP in line:
154 trace_lines = trace_lines[i + 1 :]
155 break
156 else:
157 # The last chunk is bigger than _MAX_SIZE, so just throw everything
158 # away.
159 trace_lines = []
Joanna Wang0324e432022-12-09 17:49:07 -0500160
Gavin Makea2e3302023-03-11 06:46:20 +0000161 while trace_lines and trace_lines[-1] == "\n":
162 trace_lines = trace_lines[:-1]
163 # Write to a temporary file with a unique name in the same filesystem
164 # before replacing the original trace file.
165 temp_dir, temp_prefix = os.path.split(_TRACE_FILE)
166 with tempfile.NamedTemporaryFile(
167 "w", dir=temp_dir, prefix=temp_prefix, delete=False
168 ) as f:
169 f.writelines(trace_lines)
170 platform_utils.rename(f.name, _TRACE_FILE)