Jun 07, 2024 5:00 AM
Aug 27, 2024 10:32 PM

ActivityWatch

Open source application that automatically tracks how you spend time on your devices.

· Website · Docs · GitHub ·

Data Model

Buckets

Each bucket contains a series of events and metadata for those events (such as their type and what collected them).
It is recommended to have one bucket per watcher and host. A bucket should always receive data from the same source.
bucket = {
  "id": "aw-watcher-test_myhostname",
  "created": "2017-05-16T13:37:00.000000",
  "name": "A short but descriptive human readable bucketname",
// Type of events in bucket
  "type": "com.example.test",       
  // Identifier of client software used to report data
  "client": "example-watcher-test",  
  // Hostname of device where data was collected
  "hostname": "myhostname",
}
Buckets...
Heartbeats

Heartbeats is a method that merges adjacent events with identical data (within a pulsetime window). This is useful to save on storage space and disk IO, and it is recommended that watchers use it when sending events to the server.

A merge of two events A and B is done if their data is identical and their timestamps are within the pulsetime window. The resulting event will have the earlier timestamp, and a duration to match the difference between the timestamps.
Heartbeats...
Events

The event model used by ActivityWatch is pretty simple, here is the JSON representation:
event = {
/// ISO8601 formatted timestamp
"timestamp": "2016-04-27T15:23:55Z",
// Duration in seconds
"duration": 3.14,        
// A JSON object, the schema of this depends on the event type
"data": {"key": "value"},
}
All timestamps are stored as UTC. Timezone information (UTC offset) is currently discarded.
Events...
Event types

To separate different types of data in ActivityWatch there is the event type. A buckets event type specified the schema of the vents in the bucket.

By creating standards for watchers to use we enable easier transformation and visualization.
Event types...
web.tab.current
{
    url: string,
    title: string,
    audible: bool,
    incognito: bool,
}
web.tab.current...
app.editor.activity
{
    // full path to file, separated by forward slash
    file: string,
    // full path of cwd, separated by forward slash
    project: string,
    // name of language of the file
    language: string,
}
app.editor.activity...
currentwindow 
{
    app: string,
    title: string,
}
currentwindow...
afkstatus
{
    // "afk" or "not-afk"
    status: string
}
afkstatus...
Text is not SVG - cannot display

Querying data

Query by window title keywords

events = flood(query_bucket(find_bucket("aw-watcher-window_")));
not_afk = flood(query_bucket(find_bucket("aw-watcher-afk_")));
not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);
events = filter_period_intersect(events, not_afk);
events = merge_events_by_keys(events, ["app", "title"]);
events = filter_keyvals_regex(events, "title", "(?i)strong.*heart");
events = merge_events_by_keys(events, ["title"]);
RETURN = sort_by_duration(events);