blob: 1ba86c79ab03ee646eacef3ae5d607414e81ec1c [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.
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070032REPO_TRACE = 'REPO_TRACE'
33
Joanna Wanga6c52f52022-11-03 16:51:19 -040034# Temporarily set tracing to always on unless user expicitly sets to 0.
35_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
Joanna Wanga6c52f52022-11-03 16:51:19 -040038_TRACE_FILE_NAME = 'TRACE_FILE'
Joanna Wang0324e432022-12-09 17:49:07 -050039_MAX_SIZE = 70 # in MiB
Joanna Wanga6c52f52022-11-03 16:51:19 -040040_NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++'
41
42
LaMont Jones47020ba2022-11-10 00:11:51 +000043def IsTraceToStderr():
Joanna Wang0324e432022-12-09 17:49:07 -050044 """Whether traces are written to stderr."""
Joanna Wanga6c52f52022-11-03 16:51:19 -040045 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():
Joanna Wang0324e432022-12-09 17:49:07 -050049 """Whether tracing is enabled."""
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070050 return _TRACE
51
David Pursehouse819827a2020-02-12 15:20:19 +090052
Joanna Wanga6c52f52022-11-03 16:51:19 -040053def SetTraceToStderr():
Joanna Wang0324e432022-12-09 17:49:07 -050054 """Enables tracing logging to stderr."""
Joanna Wanga6c52f52022-11-03 16:51:19 -040055 global _TRACE_TO_STDERR
56 _TRACE_TO_STDERR = True
57
58
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070059def SetTrace():
Joanna Wang0324e432022-12-09 17:49:07 -050060 """Enables tracing."""
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070061 global _TRACE
62 _TRACE = True
63
David Pursehouse819827a2020-02-12 15:20:19 +090064
LaMont Jonesed25be52022-11-10 02:31:19 +000065def _SetTraceFile(quiet):
Joanna Wang0324e432022-12-09 17:49:07 -050066 """Sets the trace file location."""
Joanna Wanga6c52f52022-11-03 16:51:19 -040067 global _TRACE_FILE
LaMont Jonesed25be52022-11-10 02:31:19 +000068 _TRACE_FILE = _GetTraceFile(quiet)
Joanna Wanga6c52f52022-11-03 16:51:19 -040069
70
71class Trace(ContextDecorator):
Joanna Wang0324e432022-12-09 17:49:07 -050072 """Used to capture and save git traces."""
Joanna Wanga6c52f52022-11-03 16:51:19 -040073
LaMont Jonesafd76712022-11-10 02:31:19 +000074 def _time(self):
75 """Generate nanoseconds of time in a py3.6 safe way"""
76 return int(time.time() * 1e+9)
Joanna Wanga6c52f52022-11-03 16:51:19 -040077
LaMont Jonesed25be52022-11-10 02:31:19 +000078 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 Jonesafd76712022-11-10 02:31:19 +000087 if not IsTrace():
88 return
89 self._trace_msg = fmt % args
Joanna Wanga6c52f52022-11-03 16:51:19 -040090
LaMont Jonesafd76712022-11-10 02:31:19 +000091 if not _TRACE_FILE:
LaMont Jonesed25be52022-11-10 02:31:19 +000092 _SetTraceFile(quiet)
Joanna Wanga6c52f52022-11-03 16:51:19 -040093
LaMont Jonesafd76712022-11-10 02:31:19 +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
LaMont Jonesafd76712022-11-10 02:31:19 +000098 def __enter__(self):
99 if not IsTrace():
Joanna Wanga6c52f52022-11-03 16:51:19 -0400100 return self
101
LaMont Jonesafd76712022-11-10 02:31:19 +0000102 print_msg = f'PID: {os.getpid()} START: {self._time()} :{self._trace_msg}\n'
Joanna Wanga6c52f52022-11-03 16:51:19 -0400103
LaMont Jonesafd76712022-11-10 02:31:19 +0000104 with open(_TRACE_FILE, 'a') as f:
105 print(print_msg, file=f)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400106
LaMont Jonesafd76712022-11-10 02:31:19 +0000107 if _TRACE_TO_STDERR:
108 print(print_msg, file=sys.stderr)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400109
LaMont Jonesafd76712022-11-10 02:31:19 +0000110 return self
Joanna Wanga6c52f52022-11-03 16:51:19 -0400111
LaMont Jonesafd76712022-11-10 02:31:19 +0000112 def __exit__(self, *exc):
113 if not IsTrace():
Joanna Wanga6c52f52022-11-03 16:51:19 -0400114 return False
115
LaMont Jonesafd76712022-11-10 02:31:19 +0000116 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 Wanga6c52f52022-11-03 16:51:19 -0400126
LaMont Jonesed25be52022-11-10 02:31:19 +0000127def _GetTraceFile(quiet):
Joanna Wanga6c52f52022-11-03 16:51:19 -0400128 """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 Jonesed25be52022-11-10 02:31:19 +0000132 if not quiet:
133 print(f'Trace outputs in {trace_file}', file=sys.stderr)
Joanna Wanga6c52f52022-11-03 16:51:19 -0400134 return trace_file
135
LaMont Jonesafd76712022-11-10 02:31:19 +0000136
Joanna Wanga6c52f52022-11-03 16:51:19 -0400137def _ClearOldTraces():
Joanna Wang0324e432022-12-09 17:49:07 -0500138 """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 Wanga6c52f52022-11-03 16:51:19 -0400146
Joanna Wang0324e432022-12-09 17:49:07 -0500147 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)