def test_heartbeat_merge_fail(): """Merge should not happen""" now = datetime.now() td_1s = timedelta(seconds=1) # timestamp of heartbeat more than pulsetime away last_event, heartbeat = Event(timestamp=now, data={"label": "test"}), Event(timestamp=now + 3*td_1s, data={"label": "test"}) merged = heartbeat_merge(last_event, heartbeat, pulsetime=2) assert merged is None # labels not identical last_event, heartbeat = Event(timestamp=now, data={"label": "test"}), Event(timestamp=now + td_1s, data={"label": "test2"}) merged = heartbeat_merge(last_event, heartbeat, pulsetime=2) assert merged is None
def test_heartbeat_merge(): """Events should merge""" now = datetime.now() td_1s = timedelta(seconds=1) last_event, heartbeat = Event(timestamp=now), Event(timestamp=now + td_1s) merged = heartbeat_merge(last_event, heartbeat, pulsetime=2) assert merged is not None
def heartbeat(self, bucket_id: str, heartbeat: Event, pulsetime: float) -> Event: """ Heartbeats are useful when implementing watchers that simply keep track of a state, how long it's in that state and when it changes. A single heartbeat always has a duration of zero. If the heartbeat was identical to the last (apart from timestamp), then the last event has its duration updated. If the heartbeat differed, then a new event is created. Such as: - Active application and window title - Example: aw-watcher-window - Currently open document/browser tab/playing song - Example: wakatime - Example: aw-watcher-web - Example: aw-watcher-spotify - Is the user active/inactive? Send an event on some interval indicating if the user is active or not. - Example: aw-watcher-afk Inspired by: https://wakatime.com/developers#heartbeats """ logger.debug( "Received heartbeat in bucket '{}'\n\ttimestamp: {}, duration: {}, pulsetime: {}\n\tdata: {}" .format( bucket_id, heartbeat.timestamp, heartbeat.duration, pulsetime, heartbeat.data, )) # The endtime here is set such that in the event that the heartbeat is older than an # existing event we should try to merge it with the last event before the heartbeat instead. # FIXME: This (the endtime=heartbeat.timestamp) gets rid of the "heartbeat was older than last event" # warning and also causes a already existing "newer" event to be overwritten in the # replace_last call below. This is problematic. # Solution: This could be solved if we were able to replace arbitrary events. # That way we could double check that the event has been applied # and if it hasn't we simply replace it with the updated counterpart. last_event = None if bucket_id not in self.last_event: last_events = self.db[bucket_id].get(limit=1) if len(last_events) > 0: last_event = last_events[0] else: last_event = self.last_event[bucket_id] if last_event: if last_event.data == heartbeat.data: merged = heartbeat_merge(last_event, heartbeat, pulsetime) if merged is not None: # Heartbeat was merged into last_event logger.debug( "Received valid heartbeat, merging. (bucket: {})". format(bucket_id)) self.last_event[bucket_id] = merged self.db[bucket_id].replace_last(merged) return merged else: logger.info( "Received heartbeat after pulse window, inserting as new event. (bucket: {})" .format(bucket_id)) else: logger.debug( "Received heartbeat with differing data, inserting as new event. (bucket: {})" .format(bucket_id)) else: logger.info( "Received heartbeat, but bucket was previously empty, inserting as new event. (bucket: {})" .format(bucket_id)) self.db[bucket_id].insert(heartbeat) self.last_event[bucket_id] = heartbeat return heartbeat
def heartbeat(self, bucket_id: str, heartbeat: Event, pulsetime: float) -> Event: """ Heartbeats are useful when implementing watchers that simply keep track of a state, how long it's in that state and when it changes. A single heartbeat always has a duration of zero. If the heartbeat was identical to the last (apart from timestamp), then the last event has its duration updated. If the heartbeat differed, then a new event is created. Such as: - Active application and window title - Example: aw-watcher-window - Currently open document/browser tab/playing song - Example: wakatime - Example: aw-watcher-web - Example: aw-watcher-spotify - Is the user active/inactive? Send an event on some interval indicating if the user is active or not. - Example: aw-watcher-afk Inspired by: https://wakatime.com/developers#heartbeats """ logger.debug("Received heartbeat in bucket '{}'\n\ttimestamp: {}, duration: {}, pulsetime: {}\n\tdata: {}".format( bucket_id, heartbeat.timestamp, heartbeat.duration, pulsetime, heartbeat.data)) # The endtime here is set such that in the event that the heartbeat is older than an # existing event we should try to merge it with the last event before the heartbeat instead. # FIXME: This (the endtime=heartbeat.timestamp) gets rid of the "heartbeat was older than last event" # warning and also causes a already existing "newer" event to be overwritten in the # replace_last call below. This is problematic. # Solution: This could be solved if we were able to replace arbitrary events. # That way we could double check that the event has been applied # and if it hasn't we simply replace it with the updated counterpart. last_event = None if bucket_id not in self.last_event: last_events = self.db[bucket_id].get(limit=1) if len(last_events) > 0: last_event = last_events[0] else: last_event = self.last_event[bucket_id] if last_event: if last_event.data == heartbeat.data: merged = heartbeat_merge(last_event, heartbeat, pulsetime) if merged is not None: # Heartbeat was merged into last_event logger.debug("Received valid heartbeat, merging. (bucket: {})".format(bucket_id)) self.last_event[bucket_id] = merged self.db[bucket_id].replace_last(merged) return merged else: logger.info("Received heartbeat after pulse window, inserting as new event. (bucket: {})".format(bucket_id)) else: logger.debug("Received heartbeat with differing data, inserting as new event. (bucket: {})".format(bucket_id)) else: logger.info("Received heartbeat, but bucket was previously empty, inserting as new event. (bucket: {})".format(bucket_id)) self.db[bucket_id].insert(heartbeat) self.last_event[bucket_id] = heartbeat return heartbeat