Example #1
0
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
Example #2
0
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']
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']
Example #4
0
    # 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
Example #6
0
# 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)
Example #7
0
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
Example #10
0
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
Example #11
0
    # 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)
Example #12
0
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