|  | # Copyright (C) 2017 The Android Open Source Project | 
|  | # | 
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | # you may not use this file except in compliance with the License. | 
|  | # You may obtain a copy of the License at | 
|  | # | 
|  | #      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | # Unless required by applicable law or agreed to in writing, software | 
|  | # distributed under the License is distributed on an "AS IS" BASIS, | 
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | # See the License for the specific language governing permissions and | 
|  | # limitations under the License. | 
|  |  | 
|  | import json | 
|  | import multiprocessing | 
|  |  | 
|  | TASK_COMMAND = 'command' | 
|  | TASK_SYNC_NETWORK = 'sync-network' | 
|  | TASK_SYNC_LOCAL = 'sync-local' | 
|  |  | 
|  |  | 
|  | class EventLog(object): | 
|  | """Event log that records events that occurred during a repo invocation. | 
|  |  | 
|  | Events are written to the log as a consecutive JSON entries, one per line. | 
|  | Each entry contains the following keys: | 
|  | - id: A ('RepoOp', ID) tuple, suitable for storing in a datastore. | 
|  | The ID is only unique for the invocation of the repo command. | 
|  | - name: Name of the object being operated upon. | 
|  | - task_name: The task that was performed. | 
|  | - start: Timestamp of when the operation started. | 
|  | - finish: Timestamp of when the operation finished. | 
|  | - success: Boolean indicating if the operation was successful. | 
|  | - try_count: A counter indicating the try count of this task. | 
|  |  | 
|  | Optionally: | 
|  | - parent: A ('RepoOp', ID) tuple indicating the parent event for nested | 
|  | events. | 
|  |  | 
|  | Valid task_names include: | 
|  | - command: The invocation of a subcommand. | 
|  | - sync-network: The network component of a sync command. | 
|  | - sync-local: The local component of a sync command. | 
|  |  | 
|  | Specific tasks may include additional informational properties. | 
|  | """ | 
|  |  | 
|  | def __init__(self): | 
|  | """Initializes the event log.""" | 
|  | self._log = [] | 
|  | self._parent = None | 
|  |  | 
|  | def Add(self, name, task_name, start, finish=None, success=None, | 
|  | try_count=1, kind='RepoOp'): | 
|  | """Add an event to the log. | 
|  |  | 
|  | Args: | 
|  | name: Name of the object being operated upon. | 
|  | task_name: A sub-task that was performed for name. | 
|  | start: Timestamp of when the operation started. | 
|  | finish: Timestamp of when the operation finished. | 
|  | success: Boolean indicating if the operation was successful. | 
|  | try_count: A counter indicating the try count of this task. | 
|  | kind: The kind of the object for the unique identifier. | 
|  |  | 
|  | Returns: | 
|  | A dictionary of the event added to the log. | 
|  | """ | 
|  | event = { | 
|  | 'id': (kind, _NextEventId()), | 
|  | 'name': name, | 
|  | 'task_name': task_name, | 
|  | 'start_time': start, | 
|  | 'try': try_count, | 
|  | } | 
|  |  | 
|  | if self._parent: | 
|  | event['parent'] = self._parent['id'] | 
|  |  | 
|  | if success is not None or finish is not None: | 
|  | self.FinishEvent(event, finish, success) | 
|  |  | 
|  | self._log.append(event) | 
|  | return event | 
|  |  | 
|  | def AddSync(self, project, task_name, start, finish, success): | 
|  | """Add a event to the log for a sync command. | 
|  |  | 
|  | Args: | 
|  | project: Project being synced. | 
|  | task_name: A sub-task that was performed for name. | 
|  | One of (TASK_SYNC_NETWORK, TASK_SYNC_LOCAL) | 
|  | start: Timestamp of when the operation started. | 
|  | finish: Timestamp of when the operation finished. | 
|  | success: Boolean indicating if the operation was successful. | 
|  |  | 
|  | Returns: | 
|  | A dictionary of the event added to the log. | 
|  | """ | 
|  | event = self.Add(project.relpath, task_name, start, finish, success) | 
|  | if event is not None: | 
|  | event['project'] = project.name | 
|  | if project.revisionExpr: | 
|  | event['revision'] = project.revisionExpr | 
|  | if project.remote.url: | 
|  | event['project_url'] = project.remote.url | 
|  | if project.remote.fetchUrl: | 
|  | event['remote_url'] = project.remote.fetchUrl | 
|  | try: | 
|  | event['git_hash'] = project.GetCommitRevisionId() | 
|  | except Exception: | 
|  | pass | 
|  | return event | 
|  |  | 
|  | def GetStatusString(self, success): | 
|  | """Converst a boolean success to a status string. | 
|  |  | 
|  | Args: | 
|  | success: Boolean indicating if the operation was successful. | 
|  |  | 
|  | Returns: | 
|  | status string. | 
|  | """ | 
|  | return 'pass' if success else 'fail' | 
|  |  | 
|  | def FinishEvent(self, event, finish, success): | 
|  | """Finishes an incomplete event. | 
|  |  | 
|  | Args: | 
|  | event: An event that has been added to the log. | 
|  | finish: Timestamp of when the operation finished. | 
|  | success: Boolean indicating if the operation was successful. | 
|  |  | 
|  | Returns: | 
|  | A dictionary of the event added to the log. | 
|  | """ | 
|  | event['status'] = self.GetStatusString(success) | 
|  | event['finish_time'] = finish | 
|  | return event | 
|  |  | 
|  | def SetParent(self, event): | 
|  | """Set a parent event for all new entities. | 
|  |  | 
|  | Args: | 
|  | event: The event to use as a parent. | 
|  | """ | 
|  | self._parent = event | 
|  |  | 
|  | def Write(self, filename): | 
|  | """Writes the log out to a file. | 
|  |  | 
|  | Args: | 
|  | filename: The file to write the log to. | 
|  | """ | 
|  | with open(filename, 'w+') as f: | 
|  | for e in self._log: | 
|  | json.dump(e, f, sort_keys=True) | 
|  | f.write('\n') | 
|  |  | 
|  |  | 
|  | # An integer id that is unique across this invocation of the program. | 
|  | _EVENT_ID = multiprocessing.Value('i', 1) | 
|  |  | 
|  |  | 
|  | def _NextEventId(): | 
|  | """Helper function for grabbing the next unique id. | 
|  |  | 
|  | Returns: | 
|  | A unique, to this invocation of the program, integer id. | 
|  | """ | 
|  | with _EVENT_ID.get_lock(): | 
|  | val = _EVENT_ID.value | 
|  | _EVENT_ID.value += 1 | 
|  | return val |