summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--command.py2
-rw-r--r--event_log.py177
-rwxr-xr-xmain.py15
-rw-r--r--manifest_xml.py6
-rw-r--r--project.py18
-rw-r--r--subcmds/sync.py31
6 files changed, 236 insertions, 13 deletions
diff --git a/command.py b/command.py
index 2ff0a344..971f968b 100644
--- a/command.py
+++ b/command.py
@@ -19,6 +19,7 @@ import platform
19import re 19import re
20import sys 20import sys
21 21
22from event_log import EventLog
22from error import NoSuchProjectError 23from error import NoSuchProjectError
23from error import InvalidProjectGroupsError 24from error import InvalidProjectGroupsError
24 25
@@ -28,6 +29,7 @@ class Command(object):
28 """ 29 """
29 30
30 common = False 31 common = False
32 event_log = EventLog()
31 manifest = None 33 manifest = None
32 _optparse = None 34 _optparse = None
33 35
diff --git a/event_log.py b/event_log.py
new file mode 100644
index 00000000..d73511da
--- /dev/null
+++ b/event_log.py
@@ -0,0 +1,177 @@
1#
2# Copyright (C) 2017 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from __future__ import print_function
17
18import json
19import multiprocessing
20
21TASK_COMMAND = 'command'
22TASK_SYNC_NETWORK = 'sync-network'
23TASK_SYNC_LOCAL = 'sync-local'
24
25class EventLog(object):
26 """Event log that records events that occurred during a repo invocation.
27
28 Events are written to the log as a consecutive JSON entries, one per line.
29 Each entry contains the following keys:
30 - id: A ('RepoOp', ID) tuple, suitable for storing in a datastore.
31 The ID is only unique for the invocation of the repo command.
32 - name: Name of the object being operated upon.
33 - task_name: The task that was performed.
34 - start: Timestamp of when the operation started.
35 - finish: Timestamp of when the operation finished.
36 - success: Boolean indicating if the operation was successful.
37 - try_count: A counter indicating the try count of this task.
38
39 Optionally:
40 - parent: A ('RepoOp', ID) tuple indicating the parent event for nested
41 events.
42
43 Valid task_names include:
44 - command: The invocation of a subcommand.
45 - sync-network: The network component of a sync command.
46 - sync-local: The local component of a sync command.
47
48 Specific tasks may include additional informational properties.
49 """
50
51 def __init__(self):
52 """Initializes the event log."""
53 self._log = []
54 self._next_id = _EventIdGenerator()
55 self._parent = None
56
57 def Add(self, name, task_name, start, finish=None, success=None,
58 try_count=1, kind='RepoOp'):
59 """Add an event to the log.
60
61 Args:
62 name: Name of the object being operated upon.
63 task_name: A sub-task that was performed for name.
64 start: Timestamp of when the operation started.
65 finish: Timestamp of when the operation finished.
66 success: Boolean indicating if the operation was successful.
67 try_count: A counter indicating the try count of this task.
68 kind: The kind of the object for the unique identifier.
69
70 Returns:
71 A dictionary of the event added to the log.
72 """
73 event = {
74 'id': (kind, self._next_id.next()),
75 'name': name,
76 'task_name': task_name,
77 'start_time': start,
78 'try': try_count,
79 }
80
81 if self._parent:
82 event['parent'] = self._parent['id']
83
84 if success is not None or finish is not None:
85 self.FinishEvent(event, finish, success)
86
87 self._log.append(event)
88 return event
89
90 def AddSync(self, project, task_name, start, finish, success):
91 """Add a event to the log for a sync command.
92
93 Args:
94 project: Project being synced.
95 task_name: A sub-task that was performed for name.
96 One of (TASK_SYNC_NETWORK, TASK_SYNC_LOCAL)
97 start: Timestamp of when the operation started.
98 finish: Timestamp of when the operation finished.
99 success: Boolean indicating if the operation was successful.
100
101 Returns:
102 A dictionary of the event added to the log.
103 """
104 event = self.Add(project.relpath, success, start, finish, task_name)
105 if event is not None:
106 event['project'] = project.name
107 if project.revisionExpr:
108 event['revision'] = project.revisionExpr
109 if project.remote.url:
110 event['project_url'] = project.remote.url
111 if project.remote.fetchUrl:
112 event['remote_url'] = project.remote.fetchUrl
113 try:
114 event['git_hash'] = project.GetCommitRevisionId()
115 except Exception:
116 pass
117 return event
118
119 def GetStatusString(self, success):
120 """Converst a boolean success to a status string.
121
122 Args:
123 success: Boolean indicating if the operation was successful.
124
125 Returns:
126 status string.
127 """
128 return 'pass' if success else 'fail'
129
130 def FinishEvent(self, event, finish, success):
131 """Finishes an incomplete event.
132
133 Args:
134 event: An event that has been added to the log.
135 finish: Timestamp of when the operation finished.
136 success: Boolean indicating if the operation was successful.
137
138 Returns:
139 A dictionary of the event added to the log.
140 """
141 event['status'] = self.GetStatusString(success)
142 event['finish_time'] = finish
143 return event
144
145 def SetParent(self, event):
146 """Set a parent event for all new entities.
147
148 Args:
149 event: The event to use as a parent.
150 """
151 self._parent = event
152
153 def Write(self, filename):
154 """Writes the log out to a file.
155
156 Args:
157 filename: The file to write the log to.
158 """
159 with open(filename, 'w+') as f:
160 for e in self._log:
161 json.dump(e, f, sort_keys=True)
162 f.write('\n')
163
164
165def _EventIdGenerator():
166 """Returns multi-process safe iterator that generates locally unique id.
167
168 Yields:
169 A unique, to this invocation of the program, integer id.
170 """
171 eid = multiprocessing.Value('i', 1)
172
173 while True:
174 with eid.get_lock():
175 val = eid.value
176 eid.value += 1
177 yield val
diff --git a/main.py b/main.py
index f965d7e1..386b4f13 100755
--- a/main.py
+++ b/main.py
@@ -37,6 +37,7 @@ except ImportError:
37 kerberos = None 37 kerberos = None
38 38
39from color import SetDefaultColoring 39from color import SetDefaultColoring
40import event_log
40from trace import SetTrace 41from trace import SetTrace
41from git_command import git, GitCommand 42from git_command import git, GitCommand
42from git_config import init_ssh, close_ssh 43from git_config import init_ssh, close_ssh
@@ -85,6 +86,9 @@ global_options.add_option('--time',
85global_options.add_option('--version', 86global_options.add_option('--version',
86 dest='show_version', action='store_true', 87 dest='show_version', action='store_true',
87 help='display this version of repo') 88 help='display this version of repo')
89global_options.add_option('--event-log',
90 dest='event_log', action='store',
91 help='filename of event log to append timeline to')
88 92
89class _Repo(object): 93class _Repo(object):
90 def __init__(self, repodir): 94 def __init__(self, repodir):
@@ -176,6 +180,8 @@ class _Repo(object):
176 RunPager(config) 180 RunPager(config)
177 181
178 start = time.time() 182 start = time.time()
183 cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start)
184 cmd.event_log.SetParent(cmd_event)
179 try: 185 try:
180 result = cmd.Execute(copts, cargs) 186 result = cmd.Execute(copts, cargs)
181 except (DownloadError, ManifestInvalidRevisionError, 187 except (DownloadError, ManifestInvalidRevisionError,
@@ -203,7 +209,8 @@ class _Repo(object):
203 result = e.code 209 result = e.code
204 raise 210 raise
205 finally: 211 finally:
206 elapsed = time.time() - start 212 finish = time.time()
213 elapsed = finish - start
207 hours, remainder = divmod(elapsed, 3600) 214 hours, remainder = divmod(elapsed, 3600)
208 minutes, seconds = divmod(remainder, 60) 215 minutes, seconds = divmod(remainder, 60)
209 if gopts.time: 216 if gopts.time:
@@ -213,6 +220,12 @@ class _Repo(object):
213 print('real\t%dh%dm%.3fs' % (hours, minutes, seconds), 220 print('real\t%dh%dm%.3fs' % (hours, minutes, seconds),
214 file=sys.stderr) 221 file=sys.stderr)
215 222
223 cmd.event_log.FinishEvent(cmd_event, finish,
224 result is None or result == 0)
225 if gopts.event_log:
226 cmd.event_log.Write(os.path.abspath(
227 os.path.expanduser(gopts.event_log)))
228
216 return result 229 return result
217 230
218 231
diff --git a/manifest_xml.py b/manifest_xml.py
index 4f0eb64d..55d25a79 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -110,7 +110,8 @@ class _XmlRemote(object):
110 return url 110 return url
111 111
112 def ToRemoteSpec(self, projectName): 112 def ToRemoteSpec(self, projectName):
113 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName 113 fetchUrl = self.resolvedFetchUrl.rstrip('/')
114 url = fetchUrl + '/' + projectName
114 remoteName = self.name 115 remoteName = self.name
115 if self.remoteAlias: 116 if self.remoteAlias:
116 remoteName = self.remoteAlias 117 remoteName = self.remoteAlias
@@ -118,7 +119,8 @@ class _XmlRemote(object):
118 url=url, 119 url=url,
119 pushUrl=self.pushUrl, 120 pushUrl=self.pushUrl,
120 review=self.reviewUrl, 121 review=self.reviewUrl,
121 orig_name=self.name) 122 orig_name=self.name,
123 fetchUrl=self.fetchUrl)
122 124
123class XmlManifest(object): 125class XmlManifest(object):
124 """manages the repo configuration file""" 126 """manages the repo configuration file"""
diff --git a/project.py b/project.py
index 45ecfe36..269fd7e5 100644
--- a/project.py
+++ b/project.py
@@ -323,13 +323,15 @@ class RemoteSpec(object):
323 pushUrl=None, 323 pushUrl=None,
324 review=None, 324 review=None,
325 revision=None, 325 revision=None,
326 orig_name=None): 326 orig_name=None,
327 fetchUrl=None):
327 self.name = name 328 self.name = name
328 self.url = url 329 self.url = url
329 self.pushUrl = pushUrl 330 self.pushUrl = pushUrl
330 self.review = review 331 self.review = review
331 self.revision = revision 332 self.revision = revision
332 self.orig_name = orig_name 333 self.orig_name = orig_name
334 self.fetchUrl = fetchUrl
333 335
334 336
335class RepoHook(object): 337class RepoHook(object):
@@ -2876,13 +2878,14 @@ class SyncBuffer(object):
2876 2878
2877 self.detach_head = detach_head 2879 self.detach_head = detach_head
2878 self.clean = True 2880 self.clean = True
2881 self.recent_clean = True
2879 2882
2880 def info(self, project, fmt, *args): 2883 def info(self, project, fmt, *args):
2881 self._messages.append(_InfoMessage(project, fmt % args)) 2884 self._messages.append(_InfoMessage(project, fmt % args))
2882 2885
2883 def fail(self, project, err=None): 2886 def fail(self, project, err=None):
2884 self._failures.append(_Failure(project, err)) 2887 self._failures.append(_Failure(project, err))
2885 self.clean = False 2888 self._MarkUnclean()
2886 2889
2887 def later1(self, project, what): 2890 def later1(self, project, what):
2888 self._later_queue1.append(_Later(project, what)) 2891 self._later_queue1.append(_Later(project, what))
@@ -2896,6 +2899,15 @@ class SyncBuffer(object):
2896 self._PrintMessages() 2899 self._PrintMessages()
2897 return self.clean 2900 return self.clean
2898 2901
2902 def Recently(self):
2903 recent_clean = self.recent_clean
2904 self.recent_clean = True
2905 return recent_clean
2906
2907 def _MarkUnclean(self):
2908 self.clean = False
2909 self.recent_clean = False
2910
2899 def _RunLater(self): 2911 def _RunLater(self):
2900 for q in ['_later_queue1', '_later_queue2']: 2912 for q in ['_later_queue1', '_later_queue2']:
2901 if not self._RunQueue(q): 2913 if not self._RunQueue(q):
@@ -2904,7 +2916,7 @@ class SyncBuffer(object):
2904 def _RunQueue(self, queue): 2916 def _RunQueue(self, queue):
2905 for m in getattr(self, queue): 2917 for m in getattr(self, queue):
2906 if not m.Run(self): 2918 if not m.Run(self):
2907 self.clean = False 2919 self._MarkUnclean()
2908 return False 2920 return False
2909 setattr(self, queue, []) 2921 setattr(self, queue, [])
2910 return True 2922 return True
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 82056f33..ef023274 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -64,6 +64,7 @@ try:
64except ImportError: 64except ImportError:
65 multiprocessing = None 65 multiprocessing = None
66 66
67import event_log
67from git_command import GIT, git_require 68from git_command import GIT, git_require
68from git_config import GetUrlCookieFile 69from git_config import GetUrlCookieFile
69from git_refs import R_HEADS, HEAD 70from git_refs import R_HEADS, HEAD
@@ -304,9 +305,10 @@ later is required to fix a server side protocol bug.
304 # - We always set err_event in the case of an exception. 305 # - We always set err_event in the case of an exception.
305 # - We always make sure we call sem.release(). 306 # - We always make sure we call sem.release().
306 # - We always make sure we unlock the lock if we locked it. 307 # - We always make sure we unlock the lock if we locked it.
308 start = time.time()
309 success = False
307 try: 310 try:
308 try: 311 try:
309 start = time.time()
310 success = project.Sync_NetworkHalf( 312 success = project.Sync_NetworkHalf(
311 quiet=opt.quiet, 313 quiet=opt.quiet,
312 current_branch_only=opt.current_branch_only, 314 current_branch_only=opt.current_branch_only,
@@ -345,6 +347,9 @@ later is required to fix a server side protocol bug.
345 finally: 347 finally:
346 if did_lock: 348 if did_lock:
347 lock.release() 349 lock.release()
350 finish = time.time()
351 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
352 start, finish, success)
348 353
349 return success 354 return success
350 355
@@ -720,16 +725,24 @@ later is required to fix a server side protocol bug.
720 _PostRepoUpgrade(self.manifest, quiet=opt.quiet) 725 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
721 726
722 if not opt.local_only: 727 if not opt.local_only:
723 mp.Sync_NetworkHalf(quiet=opt.quiet, 728 start = time.time()
724 current_branch_only=opt.current_branch_only, 729 success = mp.Sync_NetworkHalf(quiet=opt.quiet,
725 no_tags=opt.no_tags, 730 current_branch_only=opt.current_branch_only,
726 optimized_fetch=opt.optimized_fetch, 731 no_tags=opt.no_tags,
727 submodules=self.manifest.HasSubmodules) 732 optimized_fetch=opt.optimized_fetch,
733 submodules=self.manifest.HasSubmodules)
734 finish = time.time()
735 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
736 start, finish, success)
728 737
729 if mp.HasChanges: 738 if mp.HasChanges:
730 syncbuf = SyncBuffer(mp.config) 739 syncbuf = SyncBuffer(mp.config)
740 start = time.time()
731 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules) 741 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
732 if not syncbuf.Finish(): 742 clean = syncbuf.Finish()
743 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
744 start, time.time(), clean)
745 if not clean:
733 sys.exit(1) 746 sys.exit(1)
734 self._ReloadManifest(manifest_name) 747 self._ReloadManifest(manifest_name)
735 if opt.jobs is None: 748 if opt.jobs is None:
@@ -823,7 +836,10 @@ later is required to fix a server side protocol bug.
823 for project in all_projects: 836 for project in all_projects:
824 pm.update() 837 pm.update()
825 if project.worktree: 838 if project.worktree:
839 start = time.time()
826 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync) 840 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
841 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
842 start, time.time(), syncbuf.Recently())
827 pm.end() 843 pm.end()
828 print(file=sys.stderr) 844 print(file=sys.stderr)
829 if not syncbuf.Finish(): 845 if not syncbuf.Finish():
@@ -907,6 +923,7 @@ def _VerifyTag(project):
907 return False 923 return False
908 return True 924 return True
909 925
926
910class _FetchTimes(object): 927class _FetchTimes(object):
911 _ALPHA = 0.5 928 _ALPHA = 0.5
912 929