def pause_tracking_session(session): ''' Close session and make a note of the session if it is open so that it can be resumed. ''' if session.is_open(): try: sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'a') except IOError as err: logger.error('Error opening the paused sessions file: {}'.format(err)) else: with sessions_f: sessions_f.write( '{}\n'.format(session.dumps()) ) session_end(session.path) closed_session = TrackingSession(name=session.name, pid=999999) shutil.move( session.path, '-{}'.format(time.time()).join( os.path.splitext(closed_session.path) ) )
def get_tracker_events(old_only=False): """ Read the events log and return a dictionary with all of them. :param old_only: Don't return events from the current boot. :type old_only: boolean :returns: A dictionary suitable to be sent to the tracker endpoint. :rtype: dict """ data = {'events': []} try: rf = open_locked(tracker_events_file, 'r') except IOError as e: logger.error("Error opening the tracker events file {}".format(e)) else: with rf: for event_line in rf.readlines(): try: event = json.loads(event_line) except: logger.warn("Found a corrupted event, skipping.") if _validate_event(event) and event['token'] != TOKEN: data['events'].append(event) return data
def _open_uuids(): """Helper function to open the uuids file and load the JSON inside. Returns: file, dict: The opened uuids file and its JSON data. """ newly_created = False uuids_file = None data = dict() if not os.path.exists(TRACKER_UUIDS_PATH): touch(TRACKER_UUIDS_PATH) newly_created = True try: uuids_file = open_locked(TRACKER_UUIDS_PATH, 'r+') except (IOError, OSError) as error: logger.error('Error while opening uuids file: {}'.format(error)) else: if newly_created: uuids_file.write(json.dumps(data)) uuids_file.seek(0) try: data = json.load(uuids_file) except ValueError as error: logger.error('File uuids does not contain valid JSON: {}'.format(error)) return uuids_file, data
def get_tracker_events(old_only=False): """ Read the events log and return a dictionary with all of them. :param old_only: Don't return events from the current boot. :type old_only: boolean :returns: A dictionary suitable to be sent to the tracker endpoint. :rtype: dict """ data = {'events': []} try: rf = open_locked(tracker_events_file, 'r') except IOError as e: logger.error("Error opening the tracker events file {}".format(e)) else: with rf: for event_line in rf.readlines(): try: event = json.loads(event_line) except: logger.warn("Found a corrupted event, skipping.") if _validate_event(event) and event['token'] != TOKEN: data['events'].append(event) return data
def generate_tracker_token(): """ Generating the token is a simple md5hash of the current time. The token is saved to the `tracker_token_file`. :returns: The token. :rtype: str """ token = hashlib.md5(str(time.time())).hexdigest() ensure_dir(tracker_dir) try: f = open_locked(tracker_token_file, 'w') except IOError as e: logger.error( "Error opening tracker token file (generate) {}".format(e)) else: with f: f.write(token) if 'SUDO_USER' in os.environ: chown_path(tracker_token_file) # Make sure that the events file exist try: f = open(tracker_events_file, 'a') except IOError as e: logger.error("Error opening tracker events file {}".format(e)) else: f.close() if 'SUDO_USER' in os.environ: chown_path(tracker_events_file) return token
def clear_tracker_events(old_only=True): """ Truncate the events file, removing all the cached data. :param old_only: Don't remove data from the current boot. :type old_only: boolean """ try: rf = open_locked(tracker_events_file, 'r') except IOError as e: logger.error("Error opening tracking events file {}".format(e)) else: with rf: events = [] for event_line in rf.readlines(): try: event = json.loads(event_line) if 'token' in event and event['token'] == TOKEN: events.append(event_line) except: logger.warn("Found a corrupted event, skipping.") with open(tracker_events_file, 'w') as wf: for event_line in events: wf.write(event_line) if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def get_paused_sessions(): if not os.path.exists(PAUSED_SESSIONS_FILE): return [] try: sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'r') except IOError as err: logger.error('Error opening the paused sessions file: {}'.format(err)) return [] else: with sessions_f: paused_sessions = [] for session in sessions_f: if not session: continue try: new_session = TrackingSession.loads(session) except TypeError: logger.warn( 'Failed to process session: {}'.format(session)) continue paused_sessions.append(new_session) return paused_sessions
def track_data(name, data): """ Track arbitrary data. Calling this function will generate a data tracking event. :param name: The identifier of the data. :type name: str :param data: Arbitrary data, must be compatible with JSON. :type data: dict, list, str, int, float, None """ event = { 'type': 'data', 'time': int(time.time()), 'timezone_offset': get_utc_offset(), 'os_version': OS_VERSION, 'cpu_id': CPU_ID, 'token': TOKEN, 'language': LANGUAGE, 'name': str(name), 'data': data } try: af = open_locked(tracker_events_file, 'a') except IOError as e: logger.error("Error opening tracker events file {}".format(e)) else: with af: af.write(json.dumps(event) + "\n") if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def clear_tracker_events(old_only=True): """ Truncate the events file, removing all the cached data. :param old_only: Don't remove data from the current boot. :type old_only: boolean """ try: rf = open_locked(tracker_events_file, 'r') except IOError as e: logger.error("Error opening tracking events file {}".format(e)) else: with rf: events = [] for event_line in rf.readlines(): try: event = json.loads(event_line) if 'token' in event and event['token'] == TOKEN: events.append(event_line) except: logger.warn("Found a corrupted event, skipping.") with open(tracker_events_file, 'w') as wf: for event_line in events: wf.write(event_line) if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def session_log(name, started, length): """ Log a session that was tracked outside of the tracker. :param name: The identifier of the session. :type name: str :param started: When was the session started (UTC unix timestamp). :type started: int :param length: Length of the session in seconds. :param started: int """ try: af = open_locked(tracker_events_file, 'a') except IOError as e: logger.error("Error while opening events file: {}".format(e)) else: with af: session = { 'name': name, 'started': int(started), 'elapsed': int(length) } event = get_session_event(session) af.write(json.dumps(event) + "\n") if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def session_end(session_file): if not os.path.exists(session_file): msg = "Someone removed the tracker file, the runtime of this " \ "app will not be logged" logger.warn(msg) return try: rf = open_locked(session_file, 'r') except IOError as e: logger.error("Error opening the tracker session file {}".format(e)) else: with rf: data = json.load(rf) data['elapsed'] = int(time.time()) - data['started'] data['finished'] = True try: wf = open(session_file, 'w') except IOError as e: logger.error( "Error opening the tracker session file {}".format(e)) else: with wf: json.dump(data, wf) if 'SUDO_USER' in os.environ: chown_path(session_file)
def open(self, mode): try: session_f = open_locked(self.path, mode) except IOError as exc: logger.error( 'Error opening the tracking session file "{f}": {err}'.format( f=self.path, err=exc)) else: yield session_f
def session_start(name, pid=None, ignore_pause=False): if not pid: pid = os.getpid() pid = int(pid) if not ignore_pause and is_tracking_paused(): session = TrackingSession(name=name, pid=pid) try: paused_sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'a') except IOError as err: logger.error( 'Error while opening the paused sessions file: {}'.format(err) ) else: paused_sessions_f.write( '{}\n'.format(session.dumps()) ) return session.path data = { 'pid': pid, 'name': name, 'started': int(time.time()), 'elapsed': 0, 'app_session_id': str(uuid5(uuid1(), name + str(pid))), 'finished': False, 'token-system': TOKEN } path = get_session_file_path(data['name'], data['pid']) try: f = open_locked(path, 'w') except IOError as e: logger.error("Error opening tracker session file {}".format(e)) else: with f: json.dump(data, f) if 'SUDO_USER' in os.environ: chown_path(path) return path
def open(self, mode): try: session_f = open_locked(self.path, mode) except IOError as exc: logger.error( 'Error opening the tracking session file "{f}": {err}' .format(f=self.path, err=exc) ) else: yield session_f
def get_session_unique_id(name, pid): data = {} tracker_session_file = get_session_file_path(name, pid) try: af = open_locked(tracker_session_file, 'r') except (IOError, OSError) as e: logger.error("Error while opening session file: {}".format(e)) else: with af: try: data = json.load(af) except ValueError as e: logger.error("Session file is not a valid JSON") return data.get('app_session_id', "")
def track_action(name): """ Trigger an action tracking event. :param name: The identifier of the action. :type name: str """ try: af = open_locked(tracker_events_file, 'a') except IOError as e: logger.error("Error opening tracker events file {}".format(e)) else: with af: event = get_action_event(name) af.write(json.dumps(event) + "\n") if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def track_action(name): """ Trigger an action tracking event. :param name: The identifier of the action. :type name: str """ try: af = open_locked(tracker_events_file, 'a') except IOError as e: logger.error("Error opening tracker events file {}".format(e)) else: with af: event = get_action_event(name) af.write(json.dumps(event) + "\n") if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def pause_tracking_sessions(): ''' Loop through all the alive sessions, pausing each one. ''' open_sessions = get_open_sessions() # Mark paused in the event of no sessions if len(open_sessions) == 0: try: paused_sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'a') except IOError as err: logger.error( 'Error while opening the paused sessions file: {}'.format(err)) else: with paused_sessions_f: pass for session in open_sessions: pause_tracking_session(session)
def unpause_tracking_session(session): ''' Restart the session if the process is still alive and remove record of this paused session. ''' if session.is_open(): session_start(session.name, session.pid, ignore_pause=True) paused_sessions = get_paused_sessions() try: paused_sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'w') except IOError as e: logger.error("Error while opening events file: {}".format(e)) else: with paused_sessions_f: for paused_session in paused_sessions: if paused_session != session: paused_sessions_f.write('{}\n'.format( paused_session.dumps()))
def pause_tracking_sessions(): ''' Loop through all the alive sessions, pausing each one. ''' open_sessions = get_open_sessions() # Mark paused in the event of no sessions if len(open_sessions) == 0: try: paused_sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'a') except IOError as err: logger.error( 'Error while opening the paused sessions file: {}'.format(err) ) else: with paused_sessions_f: pass for session in open_sessions: pause_tracking_session(session)
def track_data(name, data): """ Track arbitrary data. Calling this function will generate a data tracking event. :param name: The identifier of the data. :type name: str :param data: Arbitrary data, must be compatible with JSON. :type data: dict, list, str, int, float, None """ try: af = open_locked(tracker_events_file, 'a') except IOError as e: logger.error("Error opening tracker events file {}".format(e)) else: with af: event = get_data_event(name, data) af.write(json.dumps(event) + "\n") if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def track_data(name, data): """ Track arbitrary data. Calling this function will generate a data tracking event. :param name: The identifier of the data. :type name: str :param data: Arbitrary data, must be compatible with JSON. :type data: dict, list, str, int, float, None """ try: af = open_locked(tracker_events_file, 'a') except IOError as e: logger.error("Error opening tracker events file {}".format(e)) else: with af: event = get_data_event(name, data) af.write(json.dumps(event) + "\n") if 'SUDO_USER' in os.environ: chown_path(tracker_events_file)
def unpause_tracking_session(session): ''' Restart the session if the process is still alive and remove record of this paused session. ''' if session.is_open(): session_start(session.name, session.pid, ignore_pause=True) paused_sessions = get_paused_sessions() try: paused_sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'w') except IOError as e: logger.error("Error while opening events file: {}".format(e)) else: with paused_sessions_f: for paused_session in paused_sessions: if paused_session != session: paused_sessions_f.write( '{}\n'.format(paused_session.dumps()) )
def load_token(): """ Reads the tracker token from the token file. If the file doesn't exists, it regenerates it. The token is regenerated on each boot and is inserted into every event to link together events that happened during the same start of the OS. :returns: The token. :rtype: str """ if os.path.exists(tracker_token_file): try: f = open_locked(tracker_token_file, 'r') except IOError as e: logger.error("Error opening tracker token file {}".format(e)) else: with f: return f.read().strip() else: return generate_tracker_token()
def get_paused_sessions(): if not os.path.exists(PAUSED_SESSIONS_FILE): return [] try: sessions_f = open_locked(PAUSED_SESSIONS_FILE, 'r') except IOError as err: logger.error('Error opening the paused sessions file: {}'.format(err)) return [] else: with sessions_f: paused_sessions = [] for session in sessions_f: if not session: continue try: new_session = TrackingSession.loads(session) except TypeError: logger.warn('Failed to process session: {}'.format(session)) continue paused_sessions.append(new_session) return paused_sessions
def add_runtime_to_app(app, runtime): """ Saves the tracking data for a given application. Appends a time period to a given app's runtime stats and raises starts by one. Apart from the total values, it also updates the weekly stats. This function uses advisory file locks (see flock(2)) to avoid races between different applications saving their tracking data at the same time. :param app: The name of the application. :type app: str :param runtime: For how long was the app running. :type runtime: number """ if not app or app == 'kano-tracker': return if not is_number(runtime): return runtime = float(runtime) app = app.replace('.', '_') # Make sure no one else is accessing this file app_state_file = get_app_state_file('kano-tracker') try: tracker_store = open_locked(app_state_file, 'r') except IOError as e: logger.error("Error opening app state file {}".format(e)) else: app_stats = load_app_state_variable('kano-tracker', 'app_stats') if not app_stats: app_stats = dict() try: app_stats[app]['starts'] += 1 app_stats[app]['runtime'] += runtime except Exception: app_stats[app] = { 'starts': 1, 'runtime': runtime, } # Record usage data on per-week basis if 'weekly' not in app_stats[app]: app_stats[app]['weekly'] = {} week = str(get_nearest_previous_monday()) if week not in app_stats[app]['weekly']: app_stats[app]['weekly'][week] = { 'starts': 0, 'runtime': 0 } app_stats[app]['weekly'][week]['starts'] += 1 app_stats[app]['weekly'][week]['runtime'] += runtime save_app_state_variable('kano-tracker', 'app_stats', app_stats) # Close the lock tracker_store.close()
def add_runtime_to_app(app, runtime): """ Saves the tracking data for a given application. Appends a time period to a given app's runtime stats and raises starts by one. Apart from the total values, it also updates the weekly stats. This function uses advisory file locks (see flock(2)) to avoid races between different applications saving their tracking data at the same time. :param app: The name of the application. :type app: str :param runtime: For how long was the app running. :type runtime: number """ if not app or app == 'kano-tracker': return if not is_number(runtime): return runtime = float(runtime) app = app.replace('.', '_') # Make sure no one else is accessing this file app_state_file = get_app_state_file('kano-tracker') try: tracker_store = open_locked(app_state_file, 'r') except IOError as e: logger.error("Error opening app state file {}".format(e)) else: app_stats = load_app_state_variable('kano-tracker', 'app_stats') if not app_stats: app_stats = dict() try: app_stats[app]['starts'] += 1 app_stats[app]['runtime'] += runtime except Exception: app_stats[app] = { 'starts': 1, 'runtime': runtime, } # Record usage data on per-week basis if 'weekly' not in app_stats[app]: app_stats[app]['weekly'] = {} week = str(get_nearest_previous_monday()) if week not in app_stats[app]['weekly']: app_stats[app]['weekly'][week] = { 'starts': 0, 'runtime': 0 } app_stats[app]['weekly'][week]['starts'] += 1 app_stats[app]['weekly'][week]['runtime'] += runtime save_app_state_variable('kano-tracker', 'app_stats', app_stats) # Close the lock tracker_store.close()