# 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: """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, to be set # by the first Add event. We can't set it here since it results in leaked # resources (see: https://issues.gerritcodereview.com/353656374). _EVENT_ID = None def _NextEventId(): """Helper function for grabbing the next unique id. Returns: A unique, to this invocation of the program, integer id. """ global _EVENT_ID if _EVENT_ID is None: # There is a small chance of race condition - two parallel processes # setting up _EVENT_ID. However, we expect TASK_COMMAND to happen before # mp kicks in. _EVENT_ID = multiprocessing.Value("i", 1) with _EVENT_ID.get_lock(): val = _EVENT_ID.value _EVENT_ID.value += 1 return val