blob: 2f1b180bc899c109d5da2827c5e5329e01ef1e4f [file] [log] [blame]
David Rileye0684ad2017-04-05 00:02:59 -07001#
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
Dylan Denge469a0c2018-06-23 15:02:26 +080021from pyversion import is_python3
22
David Rileye0684ad2017-04-05 00:02:59 -070023TASK_COMMAND = 'command'
24TASK_SYNC_NETWORK = 'sync-network'
25TASK_SYNC_LOCAL = 'sync-local'
26
27class EventLog(object):
28 """Event log that records events that occurred during a repo invocation.
29
30 Events are written to the log as a consecutive JSON entries, one per line.
31 Each entry contains the following keys:
32 - id: A ('RepoOp', ID) tuple, suitable for storing in a datastore.
33 The ID is only unique for the invocation of the repo command.
34 - name: Name of the object being operated upon.
35 - task_name: The task that was performed.
36 - start: Timestamp of when the operation started.
37 - finish: Timestamp of when the operation finished.
38 - success: Boolean indicating if the operation was successful.
39 - try_count: A counter indicating the try count of this task.
40
41 Optionally:
42 - parent: A ('RepoOp', ID) tuple indicating the parent event for nested
43 events.
44
45 Valid task_names include:
46 - command: The invocation of a subcommand.
47 - sync-network: The network component of a sync command.
48 - sync-local: The local component of a sync command.
49
50 Specific tasks may include additional informational properties.
51 """
52
53 def __init__(self):
54 """Initializes the event log."""
55 self._log = []
56 self._next_id = _EventIdGenerator()
57 self._parent = None
58
59 def Add(self, name, task_name, start, finish=None, success=None,
60 try_count=1, kind='RepoOp'):
61 """Add an event to the log.
62
63 Args:
64 name: Name of the object being operated upon.
65 task_name: A sub-task that was performed for name.
66 start: Timestamp of when the operation started.
67 finish: Timestamp of when the operation finished.
68 success: Boolean indicating if the operation was successful.
69 try_count: A counter indicating the try count of this task.
70 kind: The kind of the object for the unique identifier.
71
72 Returns:
73 A dictionary of the event added to the log.
74 """
75 event = {
Dylan Denge469a0c2018-06-23 15:02:26 +080076 'id': (kind, self._next_id.__next__() if is_python3() else self._next_id.next()),
David Rileye0684ad2017-04-05 00:02:59 -070077 'name': name,
78 'task_name': task_name,
79 'start_time': start,
80 'try': try_count,
81 }
82
83 if self._parent:
84 event['parent'] = self._parent['id']
85
86 if success is not None or finish is not None:
87 self.FinishEvent(event, finish, success)
88
89 self._log.append(event)
90 return event
91
92 def AddSync(self, project, task_name, start, finish, success):
93 """Add a event to the log for a sync command.
94
95 Args:
96 project: Project being synced.
97 task_name: A sub-task that was performed for name.
98 One of (TASK_SYNC_NETWORK, TASK_SYNC_LOCAL)
99 start: Timestamp of when the operation started.
100 finish: Timestamp of when the operation finished.
101 success: Boolean indicating if the operation was successful.
102
103 Returns:
104 A dictionary of the event added to the log.
105 """
David Pursehouse685320b2017-12-14 13:38:58 +0900106 event = self.Add(project.relpath, task_name, start, finish, success)
David Rileye0684ad2017-04-05 00:02:59 -0700107 if event is not None:
108 event['project'] = project.name
109 if project.revisionExpr:
110 event['revision'] = project.revisionExpr
111 if project.remote.url:
112 event['project_url'] = project.remote.url
113 if project.remote.fetchUrl:
114 event['remote_url'] = project.remote.fetchUrl
115 try:
116 event['git_hash'] = project.GetCommitRevisionId()
117 except Exception:
118 pass
119 return event
120
121 def GetStatusString(self, success):
122 """Converst a boolean success to a status string.
123
124 Args:
125 success: Boolean indicating if the operation was successful.
126
127 Returns:
128 status string.
129 """
130 return 'pass' if success else 'fail'
131
132 def FinishEvent(self, event, finish, success):
133 """Finishes an incomplete event.
134
135 Args:
136 event: An event that has been added to the log.
137 finish: Timestamp of when the operation finished.
138 success: Boolean indicating if the operation was successful.
139
140 Returns:
141 A dictionary of the event added to the log.
142 """
143 event['status'] = self.GetStatusString(success)
144 event['finish_time'] = finish
145 return event
146
147 def SetParent(self, event):
148 """Set a parent event for all new entities.
149
150 Args:
151 event: The event to use as a parent.
152 """
153 self._parent = event
154
155 def Write(self, filename):
156 """Writes the log out to a file.
157
158 Args:
159 filename: The file to write the log to.
160 """
161 with open(filename, 'w+') as f:
162 for e in self._log:
163 json.dump(e, f, sort_keys=True)
164 f.write('\n')
165
166
167def _EventIdGenerator():
168 """Returns multi-process safe iterator that generates locally unique id.
169
170 Yields:
171 A unique, to this invocation of the program, integer id.
172 """
173 eid = multiprocessing.Value('i', 1)
174
175 while True:
176 with eid.get_lock():
177 val = eid.value
178 eid.value += 1
179 yield val