From e0684addeeb920ddf83168d5fbbbe50cb9287abd Mon Sep 17 00:00:00 2001 From: David Riley Date: Wed, 5 Apr 2017 00:02:59 -0700 Subject: sync: Add support to dump a JSON event log of all sync events. Change-Id: Id4852968ac1b2bf0093007cf2e5ca951ddab8b3b --- event_log.py | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 event_log.py (limited to 'event_log.py') 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 @@ +# +# 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. + +from __future__ import print_function + +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._next_id = _EventIdGenerator() + 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, self._next_id.next()), + '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, success, start, finish, task_name) + 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') + + +def _EventIdGenerator(): + """Returns multi-process safe iterator that generates locally unique id. + + Yields: + A unique, to this invocation of the program, integer id. + """ + eid = multiprocessing.Value('i', 1) + + while True: + with eid.get_lock(): + val = eid.value + eid.value += 1 + yield val -- cgit v1.2.3-54-g00ecf