Quantcast
Channel: Claus Witt »» Quantified Self
Viewing all articles
Browse latest Browse all 3

Self Surveillance Dashboard

$
0
0

I am trying to closely monitor my productivity while working. For this reason I have a couple of metrics that I use to show on a dashboard on my iPad.

The metrics I am most interested in are:

  • Rescuetime productivity for the week
  • Top apps today (with time, and productivity type)
  • Rescuetime productivity pulse for today, and for the week
  • Keypresses this week
  • Clicks this week
  • Git commits per day
  • Ratio between keypresses and clicks

What does it look like

This is how my dashboard looked last night. Productivity this week has been high in general. The productivity pulse was relatively high yesterday, however the keypress to click ratio is low enough to tell me, that I have been doing a lot of non-coding work yesterday. (Usually on a very productive day, this metric is between 20 and 30, on the graph it is around 12 on average).

How is it done

There are three parts to this dashboard; collection of data, a place to display them, and the actual widgets themselves.

Metrics

The metrics on the dashboard come from only two places (well actually more, keep reading). Rescuetime and Stathat.

Rescuetime metrics are rather simple to understand. The interessting part is how stathat gets the metrics for keypresses, clicks, ratio and commits.

Keypresses and clicks

For keypresses, clicks and the ratio between them, another service comes into play. I use WhatPulse to monitor my computer usage, and it stores its metrics in a sqlite database. The api of stathat is very simple, so it is really just a bash oneliner to add data to any metric:

curl -d "stat=[STATNAME]&ezkey=[STATHAT_KEY]&value=`sqlite3 "[PATH_TO_DB]" "[SELECT_STATEMENT_TO_GET_ONE_VALUE]" http://api.stathat.com/ez

I have made a shell script for each of the three metrics that come from WhatPulse, and each of them runs at a regular interval.

Commits

This proved to be just as easy. As above the call to stathat is just as simple (except this is a counter, not an absolute value)

The interesting part is that it is run as a git hook after each commit. I have placed a post-commit hook in my .git_template/hooks directory

#!/usr/local/bin/zsh
curl -d "stat=commit&ezkey=[STATHAT_KEY]&count=1" http://api.stathat.com/ez

and placed a init template in my git config

[init]
	templatedir = ~/.git_template

This makes git init (and git clone too) use this template dir for projects. This means that the hooks in this template dir are added to all new projects. (And you can always git init an existing project, to get the hook scripts added there as well – so off course I did a shell script for doing just that, on all projects I allready had on my computer).

Thats about it.

Dashboard

The actual dashboard was the easiest part. I worked for some time on a NodeWebkit application for my desktop which could be my dashboard – very inspired by the Telepath mac app that Nich Winter did and showed off in his video The 120-hour Workweek.

However I never really got it working quite like I wanted, and when I learned about the Status Board App by Panic, I knew it would solve the problem more elegantly.

Widgets

RescueTime allready had plugins setup so that was easy. Stathat also had integrations directly. So only the top apps widget required some work.

I decided to create a little ruby (Sinatra) service to run and show the widget on a webserver. Statusboard can show either different graphs from data formatted in a special way – or it can show a simple html page (complete with css and javascript).

The sinatra app is only 44 lines of code:

require 'uri'
require 'net/https'
require 'json'

class Personal < Sinatra::Base
  set :root, File.dirname(__FILE__)
  set :views, Proc.new { File.join(root, "views") } 

  def get_data
    uri = URI.parse("https://www.rescuetime.com/anapi/data?format=json&key=#{ENV['RESCUETIME_KEY']}&resolution_time=day&rk=activity&by=interval")
    http = Net::HTTP.new(uri.host, uri.port)
    http.read_timeout = 30
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
    request = Net::HTTP::Get.new(uri.request_uri)
    response = http.request(request)
    JSON.parse(response.body)["rows"]
  end

  def get_class_name productivity_score
    ["very_unproductive", "unproductive", "neutral", "productive", "very_productive"][productivity_score+2]
  end

  def format_time time
    hours = time/3600.to_i
    minutes = (time/60 - hours * 60).to_i
    seconds = (time - (minutes * 60 + hours * 3600))
    "%02d:%02d:%02d" % [hours, minutes, seconds]
  end

  get '/widget' do
    erb :widget
  end

  get '/content' do
    <a href='https://github.com/data' class='user-mention'>@data</a> = get_data
    erb :content
  end
end

The widget view contains both the javascript and css directly, since it is very simple, and is only loaded once a day, and probably never changes.

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
<meta http-equiv="Cache-control" content="no-cache" />
<style>
table {
  width: 100%;
  font-size: 12px;
}

tr {
  background-color: #ccc;
  color: #000;
  padding: 1px;
}

tr.very_unproductive {
  background-color: #FF0000;

}

tr.unproductive {
  background-color: #F78181;
}

tr.neutral {
  background-color: #F7F8E0;
}


tr.productive {
  background-color: #BCF5A9;
}

tr.very_productive {
  background-color: #2EFE64;
}

table tr td {
  padding: 4px;
  color: #000;
}
</style>

<script type="text/javascript">
function refresh()
{
  var req = new XMLHttpRequest();
  console.log("Refreshing Count...");
  req.onreadystatechange=function() {
    if (req.readyState==4 && req.status==200) {
      document.getElementById('topAppsContainer').innerHTML = req.responseText;
    }
  }
  req.open("GET", '/content', true);
  req.send(null);
}

function init() {
 if (document.location.href.indexOf('desktop') > -1)
  {
    document.getElementById('topAppsContainer').style.backgroundColor = 'black';
  }

  refresh()
    var int=self.setInterval(function(){refresh()},60000);
}
</script>
</head>
<body onload="init();">
<div id="topAppsContainer">
</div>
</body>
</html>

And finally the content view contains the actual table rows

<table>
  <% @data.take(9).each do |row| %>
    <tr class="<%= get_class_name(row[5]) %>">
    <td><%= row[3] %></td>
    <td><%= row[4] %></td>
    <td><%=format_time row[1].to_i %></td>
    </tr>
  <% end %>
</table>

… why take(9)? Thats what can be showed without overflowing!

Thats it.


Viewing all articles
Browse latest Browse all 3

Trending Articles