summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--repo_trace.py55
-rw-r--r--tests/test_repo_trace.py56
2 files changed, 93 insertions, 18 deletions
diff --git a/repo_trace.py b/repo_trace.py
index d79408d9..1ba86c79 100644
--- a/repo_trace.py
+++ b/repo_trace.py
@@ -23,6 +23,7 @@ To also include trace outputs in stderr do `repo --trace_to_stderr ...`
23import sys 23import sys
24import os 24import os
25import time 25import time
26import tempfile
26from contextlib import ContextDecorator 27from contextlib import ContextDecorator
27 28
28import platform_utils 29import platform_utils
@@ -35,34 +36,40 @@ _TRACE = os.environ.get(REPO_TRACE) != '0'
35_TRACE_TO_STDERR = False 36_TRACE_TO_STDERR = False
36_TRACE_FILE = None 37_TRACE_FILE = None
37_TRACE_FILE_NAME = 'TRACE_FILE' 38_TRACE_FILE_NAME = 'TRACE_FILE'
38_MAX_SIZE = 70 # in mb 39_MAX_SIZE = 70 # in MiB
39_NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++' 40_NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++'
40 41
41 42
42def IsTraceToStderr(): 43def IsTraceToStderr():
44 """Whether traces are written to stderr."""
43 return _TRACE_TO_STDERR 45 return _TRACE_TO_STDERR
44 46
45 47
46def IsTrace(): 48def IsTrace():
49 """Whether tracing is enabled."""
47 return _TRACE 50 return _TRACE
48 51
49 52
50def SetTraceToStderr(): 53def SetTraceToStderr():
54 """Enables tracing logging to stderr."""
51 global _TRACE_TO_STDERR 55 global _TRACE_TO_STDERR
52 _TRACE_TO_STDERR = True 56 _TRACE_TO_STDERR = True
53 57
54 58
55def SetTrace(): 59def SetTrace():
60 """Enables tracing."""
56 global _TRACE 61 global _TRACE
57 _TRACE = True 62 _TRACE = True
58 63
59 64
60def _SetTraceFile(quiet): 65def _SetTraceFile(quiet):
66 """Sets the trace file location."""
61 global _TRACE_FILE 67 global _TRACE_FILE
62 _TRACE_FILE = _GetTraceFile(quiet) 68 _TRACE_FILE = _GetTraceFile(quiet)
63 69
64 70
65class Trace(ContextDecorator): 71class Trace(ContextDecorator):
72 """Used to capture and save git traces."""
66 73
67 def _time(self): 74 def _time(self):
68 """Generate nanoseconds of time in a py3.6 safe way""" 75 """Generate nanoseconds of time in a py3.6 safe way"""
@@ -128,20 +135,32 @@ def _GetTraceFile(quiet):
128 135
129 136
130def _ClearOldTraces(): 137def _ClearOldTraces():
131 """Clear the oldest commands if trace file is too big. 138 """Clear the oldest commands if trace file is too big."""
132 139 try:
133 Note: If the trace file contains output from two `repo` 140 with open(_TRACE_FILE, 'r', errors='ignore') as f:
134 commands that were running at the same time, this 141 if os.path.getsize(f.name) / (1024 * 1024) <= _MAX_SIZE:
135 will not work precisely. 142 return
136 """ 143 trace_lines = f.readlines()
137 if os.path.isfile(_TRACE_FILE): 144 except FileNotFoundError:
138 while os.path.getsize(_TRACE_FILE) / (1024 * 1024) > _MAX_SIZE: 145 return
139 temp_file = _TRACE_FILE + '.tmp' 146
140 with open(_TRACE_FILE, 'r', errors='ignore') as fin: 147 while sum(len(x) for x in trace_lines) / (1024 * 1024) > _MAX_SIZE:
141 with open(temp_file, 'w') as tf: 148 for i, line in enumerate(trace_lines):
142 trace_lines = fin.readlines() 149 if 'END:' in line and _NEW_COMMAND_SEP in line:
143 for i, l in enumerate(trace_lines): 150 trace_lines = trace_lines[i + 1:]
144 if 'END:' in l and _NEW_COMMAND_SEP in l: 151 break
145 tf.writelines(trace_lines[i + 1:]) 152 else:
146 break 153 # The last chunk is bigger than _MAX_SIZE, so just throw everything away.
147 platform_utils.rename(temp_file, _TRACE_FILE) 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)
diff --git a/tests/test_repo_trace.py b/tests/test_repo_trace.py
new file mode 100644
index 00000000..5faf2938
--- /dev/null
+++ b/tests/test_repo_trace.py
@@ -0,0 +1,56 @@
1# Copyright 2022 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
15"""Unittests for the repo_trace.py module."""
16
17import os
18import unittest
19from unittest import mock
20
21import repo_trace
22
23
24class TraceTests(unittest.TestCase):
25 """Check Trace behavior."""
26
27 def testTrace_MaxSizeEnforced(self):
28 content = 'git chicken'
29
30 with repo_trace.Trace(content, first_trace=True):
31 pass
32 first_trace_size = os.path.getsize(repo_trace._TRACE_FILE)
33
34 with repo_trace.Trace(content):
35 pass
36 self.assertGreater(
37 os.path.getsize(repo_trace._TRACE_FILE), first_trace_size)
38
39 # Check we clear everything is the last chunk is larger than _MAX_SIZE.
40 with mock.patch('repo_trace._MAX_SIZE', 0):
41 with repo_trace.Trace(content, first_trace=True):
42 pass
43 self.assertEqual(first_trace_size,
44 os.path.getsize(repo_trace._TRACE_FILE))
45
46 # Check we only clear the chunks we need to.
47 repo_trace._MAX_SIZE = (first_trace_size + 1) / (1024 * 1024)
48 with repo_trace.Trace(content, first_trace=True):
49 pass
50 self.assertEqual(first_trace_size * 2,
51 os.path.getsize(repo_trace._TRACE_FILE))
52
53 with repo_trace.Trace(content, first_trace=True):
54 pass
55 self.assertEqual(first_trace_size * 2,
56 os.path.getsize(repo_trace._TRACE_FILE))