TorWAL - tracking work hours with Python

As many other, I found myself primarily working from home all of a sudden. The lines between working and not working was fading. I suspected I worked too many hours (spoiler; 1), and also that real development work got overshadowed by an increasing amount of Slack and video meetings (spoiler; 2). 😬

So I created this Window Activity Logger (WAL, not an overload term at all!) - a tool to collect and make stats of your application use (in Linux). Previously known as window-activity-logger, but as a friend pointed out, TorWAL was more fitting. I only use it for tracking work hours (more on the how later), but it is also applicable tracking and categorizing all your activates.

Example

Looking back at September i.e.:

$ torwal stats --since 2021-09-01 --before 2021-10-01
--- Top 10 uncategories ---
[...]
--- Top 10 active windows ---
[...]
--- Top 10 categories ---
 49h42m (31%) of Slack
 27h55m (18%) of Firefox (uncategorized)
 18h47m (12%) of Video Meeting
 15h19m (10%) of Firefox - Business internal documentaion tool
 10h36m (7%) of Terminal (uncategorized)
 10h13m (6%) of VIM (in dev folders)
 09h52m (6%) of Terminal (in dev folders)
 07h42m (5%) of Firefox - Monitoring stack tools
 04h54m (3%) of Firefox - Google Cloud Platform
 03h55m (2%) of Firefox - GitHub Pull Request
--- Active time (at all hours) ---
 2021-09-01 (Wed):  08h04m ( 00h34m) |                    ▃▃▇▇▇▇▇▇▇▇▇▅▆▇▇▅▄▁▇▇▅       |
 2021-09-02 (Thu):  06h40m (-00h49m) |                    β–†β–‡β–‡β–‡β–‡β–†β–‡β–‡β–‡β–‡β–‡β–‡β–…β–‡β–‡β–‚            |
 2021-09-03 (Fri):  05h43m (-01h46m) |                     β–‡β–‡β–‡β–†β–‡β–…β–‚β–‡β–‡β–‡β–‡β–‡β–„β–ƒ β–…           |
 2021-09-04 (Sat):  00h00m
 2021-09-05 (Sun):  00h23m ( 00h23m) |                                        β–„β–‚      |
 2021-09-06 (Mon):  06h17m (-01h12m) |                     ▅▂▇▇▇▇▇▇▇▇▇▇▇▇▇▇▁          |
 2021-09-07 (Tue):  07h03m (-00h26m) |                      β–„β–‡β–‡β–…β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–ƒβ–†         |
 2021-09-08 (Wed):  06h40m (-00h49m) |                     β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–„          ▁ |
 2021-09-09 (Thu):  06h43m (-00h47m) |                    ▁▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇  β–‚         |
 2021-09-10 (Fri):  06h24m (-01h05m) |                    ▁▇▇▇▇▇▇▇▇▇▇▇▇▇▂             |
 2021-09-11 (Sat):  00h00m
 2021-09-12 (Sun):  00h00m
 2021-09-13 (Mon):  08h26m ( 00h56m) |                    β–ƒβ–‡β–‡β–‡β–…β–‚β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–…β–‡β–‡β–ƒ        |
 2021-09-14 (Tue):  08h07m ( 00h37m) |                    ▁▇▇▇▇▆▇▇▇▇▇▇▇▇▁▆▇▇▇▂▁       |
 2021-09-15 (Wed):  08h56m ( 01h26m) |                      ▁▆▇▇▇▇▇▇▇▇▇▇▅▇▅▇▇▇▄   β–†β–…  |
 2021-09-16 (Thu):  08h08m ( 00h38m) |                    ▁▇▇▇▇▇▇▇▇▇▇▇▇▇▅▇▇▇          |
 2021-09-17 (Fri):  05h03m (-02h26m) |                     ▇▁▂▇▇▇▇▇▇▇▇▇▇▇▂            |
 2021-09-18 (Sat):  00h00m ( 00h00m) |                                       ▁        |
 2021-09-19 (Sun):  00h51m ( 00h51m) |                                             ▆▁ |
 2021-09-20 (Mon):  08h54m ( 01h24m) |    ▇▇▇▁              β–‡β–†β–„β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡ β–„β–‚      |
 2021-09-21 (Tue):  06h59m (-00h30m) |                       β–…β–‡β–‡β–‡β–‡β–‡β–†β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡          |
 2021-09-22 (Wed):  08h13m ( 00h43m) |                      β–„β–‡β–‡β–†β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–†β–…β–‚    ▄▇▇▇▇▆▁▁|
 2021-09-23 (Thu):  08h08m ( 00h38m) |                     β–†β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–…β–‚    ▂▇▁   |
 2021-09-24 (Fri):  07h30m ( 00h00m) |                     ▃▇▇▇▇▇▇▂▆▇▃▇▇▇▇▇▇▇▃▁▁      |
 2021-09-25 (Sat):  02h59m ( 02h59m) |                               β–ƒ            ▄▁▇▇|
 2021-09-26 (Sun):  03h22m ( 03h22m) |    β–‡β–‡β–‡β–‡β–†           β–„β–‡β–„  ▁                      |
 2021-09-27 (Mon):  08h13m ( 00h43m) |                    β–‚β–‡β–‡β–‡β–†β–†β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–†β–‡β–„β–„β–ƒ        |
 2021-09-28 (Tue):  04h53m (-02h36m) |                       β–ƒβ–„β–‡β–ƒβ–‡β–‡β–‡β–‡β–‡β–‡β–‡β–…             |
 2021-09-29 (Wed):  07h49m ( 00h19m) |                      β–…β–‡β–‡β–‡β–†β–‡β–‡β–‡β–‡β–‡β–‚β–ƒβ–†β–†β–‡β–‡β–…     β–ƒβ–‡β–† |
 2021-09-30 (Thu):  09h39m ( 02h09m) |                     β–„β–‡β–‡β–‡β–†β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–‡β–…     |

 170h19m total
 05h19m off balance during this period

What you see is a list of categories I spend my time in. Slack is shockingly high. I think it is a result of poor async communication adaptation and the supporting nature of being a Reliability Engineer. Slack is the new open landscape. 🀷

Secondly, you get a list of days. Each with the sum of all active time, and weather or not I've hit my target of 7h30m. It also holds a nice ASCII histogram of the full day, where as one full bar is 30 min of activity.

As you can see, the second last week, I had on-call duties. πŸ€™

How does it work?

You'll find the code here https://github.com/torvald/TorWAL.

Each 10 seconds, or for each tick, I save a row in a SQLite3 database with the title of the currently active window on and for how long I've been inactive.

A tick counts as active time if you have been idle for less then a idle threshold (I use 5 min). If I attend a virtual meeting I use slightly higher thresholds, as virtual meetings tend to lead to some inactivity (from your window managers point of view).

IDLE_TIME_GENERAL = 5 * 60  # Five min
IDLE_TIME_VIDEO_CONFERENCING = 20 * 60  # Twenty min
VIDEO_CONFERENCING_APP_PATTERN = "%<insert tool here>%"

Now, after working from the office more and more, I added this post-pandemic feature where I also track which SSID I'm connected to. This way, I know exactly how long I've been to the office, and I count all of that as active time.

SSIDS_PATTERNS = ['%office_SSID%', '%old_office_SSID%']

I personally check if Slack is running, as a signal to whether I'm Β«workingΒ» or not. If I close Slack, I don't register active time. This is configurable. This has the side effect of making me more mindful about closing work related programs when I'm actually not working.

ACTIVITY_FILTER_CMD = "ps aux | grep 'usr/lib/slac[k]/'"

To create an incentive of not overspending time on activities that are not strictly work, I can add these to the list of IGNORE_PATTERNS. That also works well as an escape hatch if I suddenly find myself head deep into YouTube videos of Honda Monkeys.

IGNORE_PATTERNS = ["%WeeChat%", "%Reddit%", "%1,000 Miles in Baja on Honda Monkeys%"]

Bottom line

If not fully restored, my work-life balanced has at least improved drastically. I've become way more mindful about where I spend my time. Also, as of before, I could pull through a couple days with some extra hours – but not knowing exactly how many, it's hard to justify taking a day of. That is now justified by a quick torwal stats, without even touching my conscience.


  1. I did! 

  2. I did!