def heartbeat(bucket_name,start_time,activity): client = ActivityWatchClient("aw-watcher-cli", testing=False) bucket_id = "{}_{}".format(bucket_name, client.hostname) event_type = "cli_ping" client.create_bucket(bucket_id, event_type=event_type) # Asynchronous loop with client: heartbeat_data = {"label": "heartbeat"} now = datetime.now(timezone.utc) heartbeat_event = Event(timestamp=now, data=heartbeat_data) sleeptime = 1 #Sending hearbeat until keyboard interrup second = 1 while(True): # The duration between the heartbeats will be less than pulsetime, so they will get merged. rd = relativedelta.relativedelta (now, start_time) print("Doing: {} for {:02}:{:02}:{:02}".format(activity,rd.hours, rd.minutes, rd.seconds),end="\r") client.heartbeat(bucket_id, heartbeat_event, pulsetime=sleeptime+1, queued=True) # Sleep a second until next heartbeat sleep(sleeptime) # Update timestamp for next heartbeat heartbeat_event.timestamp = datetime.now(timezone.utc) # update now now = heartbeat_event.timestamp second += 1
def test_failqueue(): client_name = "aw-test-client-" + str(randint(0, 10000)) bucket_id = "test-bucket-" + str(randint(0, 10000)) bucket_etype = "test" input("Make sure aw-server isn't running, then press enter > ") client1 = ActivityWatchClient(client_name, testing=True) client1.create_bucket(bucket_id, bucket_etype, queued=True) print("Creating events") events = [create_unique_event() for _ in range(3)] for i, e in enumerate(events): e.timestamp += timedelta(seconds=i) client1.heartbeat(bucket_id, e, pulsetime=1, queued=True) print("Trying to send events (should fail)") with client1: time.sleep(1) input("Start aw-server with --testing, then press enter > ") client2 = ActivityWatchClient(client_name, testing=True) client2.create_bucket(bucket_id, bucket_etype, queued=True) # Here the previously queued events should be sent with client2: time.sleep(1) print("Getting events") recv_events = client2.get_events(bucket_id) print("Asserting latest event") pprint(recv_events) pprint(recv_events[0].data['label']) pprint(events[2].data['label']) assert recv_events[0].data['label'] == events[2].data['label']
# This context manager starts the queue dispatcher thread and stops it when done, always use it when setting queued=True. # Alternatively you can use client.connect() and client.disconnect() instead if you prefer that # Create a sample event to send as heartbeat heartbeat_data = {"label": "heartbeat"} now = datetime.now(timezone.utc) heartbeat_event = Event(timestamp=now, data=heartbeat_data) # Now we can send some events via heartbeats # This will send one heartbeat every second 5 times sleeptime = 1 for i in range(5): # The duration between the heartbeats will be less than pulsetime, so they will get merged. # TODO: Make a section with an illustration on how heartbeats work and insert a link here print("Sending heartbeat {}".format(i)) client.heartbeat(bucket_id, heartbeat_event, pulsetime=sleeptime+1, queued=True) # Sleep a second until next heartbeat sleep(sleeptime) # Update timestamp for next heartbeat heartbeat_event.timestamp = datetime.now(timezone.utc) # Give the dispatcher thread some time to complete sending the last events. # If we don't do this the events might possibly queue up and be sent the # next time the client starts instead. sleep(1) # Synchronous example, insert an event event_data = {"label": "non-heartbeat event"} now = datetime.now(timezone.utc)
class AFKWatcher: def __init__(self, testing=False): # Read settings from config configsection = "aw-watcher-afk" if not testing else "aw-watcher-afk-testing" self.settings = Settings(watcher_config[configsection]) self.client = ActivityWatchClient("aw-watcher-afk", testing=testing) self.bucketname = "{}_{}".format(self.client.client_name, self.client.client_hostname) def ping(self, afk: bool, timestamp: datetime, duration: float = 0): data = {"status": "afk" if afk else "not-afk"} e = Event(timestamp=timestamp, duration=duration, data=data) pulsetime = self.settings.timeout + self.settings.poll_time self.client.heartbeat(self.bucketname, e, pulsetime=pulsetime, queued=True) def run(self): logger.info("aw-watcher-afk started") # Initialization sleep(1) eventtype = "afkstatus" self.client.create_bucket(self.bucketname, eventtype, queued=True) # Start afk checking loop with self.client: self.heartbeat_loop() def heartbeat_loop(self): afk = False while True: try: if system in ["Darwin", "Linux"] and os.getppid() == 1: # TODO: This won't work with PyInstaller which starts a bootloader process which will become the parent. # There is a solution however. # See: https://github.com/ActivityWatch/aw-qt/issues/19#issuecomment-316741125 logger.info( "afkwatcher stopped because parent process died") break now = datetime.now(timezone.utc) seconds_since_input = seconds_since_last_input() last_input = now - timedelta(seconds=seconds_since_input) logger.debug( "Seconds since last input: {}".format(seconds_since_input)) # If no longer AFK if afk and seconds_since_input < self.settings.timeout: logger.info("No longer AFK") self.ping(afk, timestamp=last_input) afk = False self.ping(afk, timestamp=last_input) # If becomes AFK elif not afk and seconds_since_input >= self.settings.timeout: logger.info("Became AFK") self.ping(afk, timestamp=last_input) afk = True self.ping(afk, timestamp=last_input, duration=seconds_since_input) # Send a heartbeat if no state change was made else: if afk: self.ping(afk, timestamp=last_input, duration=seconds_since_input) else: self.ping(afk, timestamp=last_input) sleep(self.settings.poll_time) except KeyboardInterrupt: logger.info("aw-watcher-afk stopped by keyboard interrupt") break
# Make sure you've started aw-server with the `--testing` flag as well. client = ActivityWatchClient("test-client", testing=True) bucket_id = "test-bucket" example_data = {"label": "example"} example_event = Event(timestamp=now, data=example_data) # Asynchronous example with client: # This context manager starts the queue dispatcher thread and stops it when done, always use it when setting queued=True. # First we need a bucket to send events/heartbeats to. client.create_bucket(bucket_id, event_type="test", queued=True) # Now we can send some heartbeats. # The duration between them will be less than pulsetime, so they will get merged. client.heartbeat(bucket_id, example_event, pulsetime=10, queued=True) example_event.timestamp += timedelta(seconds=5) client.heartbeat(bucket_id, example_event, pulsetime=10, queued=True) # Give the dispatcher thread some time to complete sending the events sleep(1) # Synchronous example example_event.timestamp += timedelta(minutes=1) inserted_event = client.insert_event(bucket_id, example_event) # The event returned from insert_event has been assigned an id assert inserted_event.id is not None events = client.get_events(bucket_id=bucket_id, limit=10)
class AFKWatcher: def __init__(self, testing=False, settings=None): # Read settings from config configsection = "aw-watcher-afk" if not testing else "aw-watcher-afk-testing" self.settings = Settings(watcher_config[configsection]) self.client = ActivityWatchClient("aw-watcher-afk", testing=testing) self.bucketname = "{}_{}".format(self.client.client_name, self.client.client_hostname) eventtype = "afkstatus" self.client.setup_bucket(self.bucketname, eventtype) self.client.connect() def set_state(self, status, duration, timestamp=None): data = {"status": status} if timestamp is None: timestamp = self.now e = Event(data=data, timestamp=timestamp, duration=duration) self.client.heartbeat(self.bucketname, e, pulsetime=self.settings.timeout, queued=True) def ping(self, afk): data = {"status": "afk" if afk else "not-afk"} e = Event(data=data, timestamp=self.now) self.client.heartbeat(self.bucketname, e, pulsetime=self.settings.timeout, queued=True) def run(self): # TODO: All usage of last_input can probably be replaced the self.seconds_since_last_input equivalent logger.info("afkwatcher started") """ Initialization """ sleep(1) """ Init variables """ self.afk = False self.now = datetime.now(timezone.utc) self.last_check = self.now self.seconds_since_last_input = 0 """ Start afk checking loop """ while True: try: self.last_check = self.now self.now = datetime.now(timezone.utc) self.seconds_since_last_input = get_seconds_since_last_input() self.timedelta_since_last_input = timedelta(seconds=self.seconds_since_last_input) self.last_input = self.now - self.timedelta_since_last_input logger.debug("Time since last input: {}".format(self.timedelta_since_last_input)) # If program is not allowed to run for more than polling_interval+10s it will assume that the computer has gone into suspend/hibernation if self.now > self.last_check + timedelta(seconds=10 + self.settings.polling_interval): logger.info("Woke up from suspend/hibernation") time_since_last_check = self.now - self.last_check self.set_state("hibernating", timedelta(seconds=time_since_last_check.total_seconds()), self.last_check) # If no longer AFK elif self.afk and self.seconds_since_last_input < self.settings.timeout: logger.info("No longer AFK") self.ping(self.afk) # End afk period self.afk = False self.set_state("not-afk", timedelta()) # If becomes AFK elif not self.afk and self.seconds_since_last_input > self.settings.timeout: logger.info("Became AFK") self.afk = True self.set_state("afk", self.timedelta_since_last_input, self.last_input) # Send a heartbeat if no state change was made else: if self.afk: self.ping(self.afk) elif self.seconds_since_last_input < self.settings.polling_interval: self.ping(self.afk) sleep(self.settings.polling_interval) except KeyboardInterrupt: logger.info("afkwatcher stopped by keyboard interrupt") self.ping(self.afk) break
class AFKWatcher: def __init__(self, testing=False): # Read settings from config configsection = "aw-watcher-afk" if not testing else "aw-watcher-afk-testing" self.settings = Settings(watcher_config[configsection]) self.client = ActivityWatchClient("aw-watcher-afk", testing=testing) self.bucketname = "{}_{}".format(self.client.client_name, self.client.client_hostname) def ping(self, afk: bool, timestamp: datetime, duration: float = 0): data = {"status": "afk" if afk else "not-afk"} e = Event(timestamp=timestamp, duration=duration, data=data) pulsetime = self.settings.timeout + self.settings.poll_time self.client.heartbeat(self.bucketname, e, pulsetime=pulsetime, queued=True) def run(self): logger.info("aw-watcher-afk started") # Initialization sleep(1) eventtype = "afkstatus" self.client.create_bucket(self.bucketname, eventtype, queued=True) # Start afk checking loop with self.client: self.heartbeat_loop() def heartbeat_loop(self): afk = False while True: try: if system in ["Darwin", "Linux"] and os.getppid() == 1: # TODO: This won't work with PyInstaller which starts a bootloader process which will become the parent. # There is a solution however. # See: https://github.com/ActivityWatch/aw-qt/issues/19#issuecomment-316741125 logger.info("afkwatcher stopped because parent process died") break now = datetime.now(timezone.utc) seconds_since_input = seconds_since_last_input() last_input = now - timedelta(seconds=seconds_since_input) logger.debug("Seconds since last input: {}".format(seconds_since_input)) # If no longer AFK if afk and seconds_since_input < self.settings.timeout: logger.info("No longer AFK") self.ping(afk, timestamp=last_input) afk = False self.ping(afk, timestamp=last_input) # If becomes AFK elif not afk and seconds_since_input >= self.settings.timeout: logger.info("Became AFK") self.ping(afk, timestamp=last_input) afk = True self.ping(afk, timestamp=last_input, duration=seconds_since_input) # Send a heartbeat if no state change was made else: if afk: self.ping(afk, timestamp=last_input, duration=seconds_since_input) else: self.ping(afk, timestamp=last_input) sleep(self.settings.poll_time) except KeyboardInterrupt: logger.info("aw-watcher-afk stopped by keyboard interrupt") break
class MessageHandler: def __init__(self, testing=False, send_commands=True, send_heartbeats=True): # Create client self._client = ActivityWatchClient(client_id, testing=testing) self._client.connect() self._init_buckets() # Settings self.pulsetime = 10 self.send_commands = send_commands self.send_heartbeats = send_heartbeats # Initialize the EventQueue self._event_queue = EventQueue(callback=self._handle_event, time_buffer=(self.pulsetime / 2)) self._event_handlers = { 'preopen': self._preopen, 'preexec': self._preexec, 'precmd': self._precmd, 'preclose': self._preclose } self._terminal_sessions = {} def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self._client.disconnect() def _init_buckets(self): """Set self._buckets and create these buckets if not existing""" self._buckets = { 'commands': { 'id': "{}-commands_{}".format(client_id, self._client.hostname), 'event_type': 'app.terminal.command' }, 'activity': { 'id': "{}-activity_{}".format(client_id, self._client.hostname), 'event_type': 'app.terminal.activity' } } # Create buckets if not existing for key, bucket in self._buckets.items(): logger.debug("Creating bucket: {}".format(bucket['id'])) self._client.create_bucket(bucket['id'], bucket['event_type'], queued=True) def update_event_queue(self): self._event_queue.update() def handle_fifo_message(self, message): for line in message.split('\n'): if not len(line): continue cli_args = shlex.split(line) args, unknown_args = parser_base.parse_known_args(cli_args) # Make sure that events are called in the right order # (based on args.timestamp) by passing it to the event queue # The event queue will trigger the callback when the time_buffer # is exceeded logger.debug("adding event to event_queue: {}".format(cli_args)) self._event_queue.add_event(cli_args, args.timestamp) def _handle_event(self, cli_args): logger.debug("handling event: {}".format(cli_args)) args, unknown_args = parser_base.parse_known_args(cli_args) # Store terminal session if not already existing pid = args.pid if pid not in self._terminal_sessions: self._terminal_sessions[pid] = TerminalSessionData(pid) if self.send_commands: if args.event not in self._event_handlers: logger.error("Unknown event: {}".format(args.event)) else: self._event_handlers[args.event](cli_args) if self.send_heartbeats: self._heartbeat(cli_args) def _preopen(self, cli_args: list) -> None: """Handle terminal creation""" pass def _preexec(self, cli_args: list) -> None: """Send event containing command execution data""" args, unknown_args = parser_preexec.parse_known_args(cli_args) process = self._terminal_sessions[args.pid] event_data = { 'command': args.command, 'path': args.path, 'shell': args.shell, 'exit_code': 'unknown', 'session_id': process.unique_id } process.event = self._insert_event(data=event_data, timestamp=args.timestamp) def _precmd(self, cli_args: list) -> None: """Update the stored event with duration and exit_code""" args, unknown_args = parser_precmd.parse_known_args(cli_args) process = self._terminal_sessions[args.pid] if process.event is None: return event_data = process.event.data # Calculate time delta between preexec and precmd timestamp = process.event.timestamp cur_time = args.timestamp time_delta = cur_time - timestamp event_data['exit_code'] = args.exit_code self._insert_event(data=event_data, id=process.event.id, timestamp=timestamp, duration=time_delta) process.event = None def _preclose(self, args: argparse.Namespace, args_raw: list) -> None: """Remove pid and related data from terminal_processes_data""" args, unknown_args = parser_preclose.parse_known_args(cli_args) self._terminal_sessions.pop(args.pid) def _heartbeat(self, cli_args: list) -> None: """Send heartbeat to activity bucket""" args, unknown_args = parser_heartbeat.parse_known_args(cli_args) process = self._terminal_sessions[args.pid] event_data = { 'session_id': process.unique_id, 'shell': args.shell, 'path': args.path } event = Event(data=event_data, timestamp=args.timestamp) inserted_heartbeat = self._client.heartbeat( self._buckets['activity']['id'], event, pulsetime=self.pulsetime, queued=True) if inserted_heartbeat and inserted_heartbeat.id: logger.debug('Successfully sent heartbeat') def _insert_event(self, *args, **kwargs) -> Event: """Send event to the aw-server""" event = Event(*args, **kwargs) inserted_event = self._client.insert_event( self._buckets['commands']['id'], event) # aw-server assigned the event an id assert inserted_event.id is not None logger.debug("Successfully sent event") return inserted_event
class AfkRunner: def __init__(self, poll_time: int = 5, timeout: int = 180) -> None: self.client = ActivityWatchClient("aw-watcher-afk", testing=False) self.bucketname = "{}_{}".format( self.client.client_name, self.client.client_hostname ) self.poll_time = poll_time self.timeout = timeout self.initiated_shutdown: bool = False def ping(self, afk: bool, timestamp: datetime, duration: float = 0 ) -> None: data = {"status": "afk" if afk else "not-afk"} e = Event(timestamp=timestamp, duration=duration, data=data) pulsetime = self.timeout + self.poll_time self.client.heartbeat( self.bucketname, e, pulsetime=pulsetime, queued=True ) def run(self) -> None: # Initialization sleep(1) eventtype = "afkstatus" self.client.create_bucket(self.bucketname, eventtype, queued=True) with self.client: self.heartbeat_loop() def stop(self) -> None: self.initiated_shutdown = True def heartbeat_loop(self) -> None: afk = False while True: if self.initiated_shutdown: self.initiated_shutdown = False break try: if system() in ["Darwin", "Linux"] and os.getppid() == 1: break now = datetime.now(timezone.utc) seconds_since_input = seconds_since_last_input() last_input = now - timedelta(seconds=seconds_since_input) # If no longer AFK if afk and seconds_since_input < self.timeout: self.ping(afk, timestamp=last_input) afk = False self.ping(afk, timestamp=last_input) # If becomes AFK elif not afk and seconds_since_input >= self.timeout: self.ping(afk, timestamp=last_input) afk = True self.ping( afk, timestamp=last_input, duration=seconds_since_input ) # Send a heartbeat if no state change was made else: if afk: self.ping( afk, timestamp=last_input, duration=seconds_since_input ) else: self.ping(afk, timestamp=last_input) sleep(self.poll_time) except KeyboardInterrupt: break
# Now we can send some events via heartbeats # This will send one heartbeat every second 5 times sleeptime = 1 for i in range(5): # Create a sample event to send as heartbeat heartbeat_data = {"label": "heartbeat"} now = datetime.now(timezone.utc) heartbeat_event = Event(timestamp=now, data=heartbeat_data) # The duration between the heartbeats will be less than pulsetime, so they will get merged. # The commit_interval=4.0 means that if heartmeats with the same data has a longer duration than 4 seconds it will be fored to be sent to aw-server # TODO: Make a section with an illustration on how heartbeats work and insert a link here print("Sending heartbeat {}".format(i)) client.heartbeat(bucket_id, heartbeat_event, pulsetime=sleeptime + 1, queued=True, commit_interval=4.0) # Sleep a second until next heartbeat sleep(sleeptime) # Give the dispatcher thread some time to complete sending the last events. # If we don't do this the events might possibly queue up and be sent the # next time the client starts instead. sleep(1) # Synchronous example, insert an event event_data = {"label": "non-heartbeat event"} now = datetime.now(timezone.utc) event = Event(timestamp=now, data=event_data)
class TableWatcher: def __init__(self, testing=False): config_section = "aw-watcher-table" if not testing else "aw-watcher-table-testing" self.settings = Settings(watcher_config[config_section]) self.client = ActivityWatchClient("aw-watcher-table", testing=testing) self.bucket_id = "{}_{}".format(self.client.client_name, self.client.client_hostname) def run(self): logger.info("aw-watcher-table started") # Initialization sleep(1) self.client.create_bucket(self.bucket_id, event_type='table_state', queued=True) # Start table checking loop with self.client: self.heartbeat_loop() def ping(self, table_height: Optional[int]): event = Event(timestamp=datetime.datetime.now(datetime.timezone.utc), data={"status": self.get_table_status(table_height)}) # 10 seconds request timeout self.client.heartbeat(self.bucket_id, event, pulsetime=self.settings.poll_time + 1 + 10, queued=True) def get_table_height(self) -> Optional[int]: try: r = requests.get(f'http://{self.settings.ip}/measure') return r.json()['table_height'] except Exception as ex: logger.warning( f'aw-watcher-table: Measurement failed! Please make sure http://{self.settings.ip}/measure ' f'delivers a JSON object which includes the field "table_height"' ) return None def get_table_status(self, table_height): return self.settings.get_height_level(table_height) def heartbeat_loop(self): while True: try: table_height = self.get_table_height() if table_height is None: logger.warning( f'aw-watcher-table: table_height corrected from None to -1!' ) table_height = -1 self.ping(table_height) sleep(self.settings.poll_time) except KeyboardInterrupt: logger.info("aw-watcher-table stopped by keyboard interrupt") break