diff options
author | Joanna Wang <jojwang@google.com> | 2022-11-03 16:51:19 -0400 |
---|---|---|
committer | Joanna Wang <jojwang@google.com> | 2022-11-03 21:07:07 +0000 |
commit | a6c52f566acfbff5b0f37158c0d33adf05d250e5 (patch) | |
tree | d79d55b872c3be39c54dcb6ef41749c40d39ccf2 /repo_trace.py | |
parent | 0d130d2da0754c546f654ede99a79aac2b8e6c5f (diff) | |
download | git-repo-a6c52f566acfbff5b0f37158c0d33adf05d250e5.tar.gz |
Set tracing to always on and save to .repo/TRACE_FILE.
- add `--trace_to_stderr` option so stderr will include trace outputs and any other errors that get sent to stderr
- while TRACE_FILE will only include trace outputs
piggy-backing on: https://gerrit-review.googlesource.com/c/git-repo/+/349154
Change-Id: I3895a84de4b2784f17fac4325521cd5e72e645e2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/350114
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Joanna Wang <jojwang@google.com>
Diffstat (limited to 'repo_trace.py')
-rw-r--r-- | repo_trace.py | 110 |
1 files changed, 106 insertions, 4 deletions
diff --git a/repo_trace.py b/repo_trace.py index 7be0c045..0ff3b694 100644 --- a/repo_trace.py +++ b/repo_trace.py | |||
@@ -15,26 +15,128 @@ | |||
15 | """Logic for tracing repo interactions. | 15 | """Logic for tracing repo interactions. |
16 | 16 | ||
17 | Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`. | 17 | Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`. |
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 ...` | ||
18 | """ | 21 | """ |
19 | 22 | ||
20 | import sys | 23 | import sys |
21 | import os | 24 | import os |
25 | import tempfile | ||
26 | import time | ||
27 | from contextlib import ContextDecorator | ||
22 | 28 | ||
23 | # Env var to implicitly turn on tracing. | 29 | # Env var to implicitly turn on tracing. |
24 | REPO_TRACE = 'REPO_TRACE' | 30 | REPO_TRACE = 'REPO_TRACE' |
25 | 31 | ||
26 | _TRACE = os.environ.get(REPO_TRACE) == '1' | 32 | # Temporarily set tracing to always on unless user expicitly sets to 0. |
33 | _TRACE = os.environ.get(REPO_TRACE) != '0' | ||
34 | |||
35 | _TRACE_TO_STDERR = False | ||
36 | |||
37 | _TRACE_FILE = None | ||
38 | |||
39 | _TRACE_FILE_NAME = 'TRACE_FILE' | ||
40 | |||
41 | _MAX_SIZE = 5 # in mb | ||
42 | |||
43 | _NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++' | ||
44 | |||
45 | |||
46 | def IsStraceToStderr(): | ||
47 | return _TRACE_TO_STDERR | ||
27 | 48 | ||
28 | 49 | ||
29 | def IsTrace(): | 50 | def IsTrace(): |
30 | return _TRACE | 51 | return _TRACE |
31 | 52 | ||
32 | 53 | ||
54 | def SetTraceToStderr(): | ||
55 | global _TRACE_TO_STDERR | ||
56 | _TRACE_TO_STDERR = True | ||
57 | |||
58 | |||
33 | def SetTrace(): | 59 | def SetTrace(): |
34 | global _TRACE | 60 | global _TRACE |
35 | _TRACE = True | 61 | _TRACE = True |
36 | 62 | ||
37 | 63 | ||
38 | def Trace(fmt, *args): | 64 | def _SetTraceFile(): |
39 | if IsTrace(): | 65 | global _TRACE_FILE |
40 | print(fmt % args, file=sys.stderr) | 66 | _TRACE_FILE = _GetTraceFile() |
67 | |||
68 | |||
69 | class Trace(ContextDecorator): | ||
70 | |||
71 | def _time(self): | ||
72 | """Generate nanoseconds of time in a py3.6 safe way""" | ||
73 | return int(time.time()*1e+9) | ||
74 | |||
75 | def __init__(self, fmt, *args, first_trace=False): | ||
76 | if not IsTrace(): | ||
77 | return | ||
78 | self._trace_msg = fmt % args | ||
79 | |||
80 | if not _TRACE_FILE: | ||
81 | _SetTraceFile() | ||
82 | |||
83 | if first_trace: | ||
84 | _ClearOldTraces() | ||
85 | self._trace_msg = '%s %s' % (_NEW_COMMAND_SEP, self._trace_msg) | ||
86 | |||
87 | |||
88 | def __enter__(self): | ||
89 | if not IsTrace(): | ||
90 | return self | ||
91 | |||
92 | print_msg = f"PID: {os.getpid()} START: {self._time()} :" + self._trace_msg + '\n' | ||
93 | |||
94 | with open(_TRACE_FILE, 'a') as f: | ||
95 | print(print_msg, file=f) | ||
96 | |||
97 | if _TRACE_TO_STDERR: | ||
98 | print(print_msg, file=sys.stderr) | ||
99 | |||
100 | return self | ||
101 | |||
102 | def __exit__(self, *exc): | ||
103 | if not IsTrace(): | ||
104 | return False | ||
105 | |||
106 | print_msg = f"PID: {os.getpid()} END: {self._time()} :" + self._trace_msg + '\n' | ||
107 | |||
108 | with open(_TRACE_FILE, 'a') as f: | ||
109 | print(print_msg, file=f) | ||
110 | |||
111 | if _TRACE_TO_STDERR: | ||
112 | print(print_msg, file=sys.stderr) | ||
113 | |||
114 | return False | ||
115 | |||
116 | |||
117 | def _GetTraceFile(): | ||
118 | """Get the trace file or create one.""" | ||
119 | # TODO: refactor to pass repodir to Trace. | ||
120 | repo_dir = os.path.dirname(os.path.dirname(__file__)) | ||
121 | trace_file = os.path.join(repo_dir, _TRACE_FILE_NAME) | ||
122 | print('Trace outputs in %s' % trace_file) | ||
123 | return trace_file | ||
124 | |||
125 | def _ClearOldTraces(): | ||
126 | """Clear traces from old commands if trace file is too big. | ||
127 | |||
128 | Note: If the trace file contains output from two `repo` | ||
129 | commands that were running at the same time, this | ||
130 | will not work precisely. | ||
131 | """ | ||
132 | if os.path.isfile(_TRACE_FILE): | ||
133 | while os.path.getsize(_TRACE_FILE)/(1024*1024) > _MAX_SIZE: | ||
134 | temp = tempfile.NamedTemporaryFile(mode='w', delete=False) | ||
135 | with open(_TRACE_FILE, 'r', errors='ignore') as fin: | ||
136 | trace_lines = fin.readlines() | ||
137 | for i , l in enumerate(trace_lines): | ||
138 | if 'END:' in l and _NEW_COMMAND_SEP in l: | ||
139 | temp.writelines(trace_lines[i+1:]) | ||
140 | break | ||
141 | temp.close() | ||
142 | os.replace(temp.name, _TRACE_FILE) | ||