IM Zeitgeist (Linux-Magazin, Mai 2015)

Viele Ubuntu-User wissen gar nicht, dass ihnen der Zeitgeist-Dämon heimlich über die Schulter schaut und ihre Desktop-Aktionen mitprotokolliert. Einige Perl-Skripts bereiten die Lauscherberichte auf und bringen interessante Tatsachen über die Usergewohnheiten ans Tageslicht.

Woher weiß eigentlich der Nautilus-Browser, welche Dateien ein Ubuntu-User in letzter Zeit bearbeitet hat, um sie in der Sparte "Recent" anzuzeigen? In Abbildung 1 listet er zum Beispiel zwei Screenshots auf, die ich kurz vorher zu Kontrollzwecken mit dem Foto-Viewer Eye Of Gnome eog angesehen habe. Da Nautilus und Eye of Gnome zwei völlig getrennte Applikationen sind, die untereinander keine Daten austauschen, hilft auf dem Ubuntu-Desktop ein inoffizieller Mitarbeiter namens zeitgeist mit, Spitzeldaten zu übermitteln. Interessierte können ihn mit

   ps aux | grep zeitgeist

in der Prozessliste als unter ihrer Userid laufend enttarnen. Gnome-Applikationen wie zum Beispiel der Foto-Viewer "eog" stehen über den DBus mit dem Zeitgeist-Dämon in Verbindung, der relevante Vorgänge neugierig in eine Datenbank protokolliert und dort interessierten Anwendungen, wie zum Beispiel dem Nautlius-Browser, zur Verfügung stellt. Als Format für die mitgeschnittenen Desktop-Events nutzt Zeitgeist eine SQLite-Datenbank, die neugierige Perl-Schnüffler ebenfalls auslesen und auswerten können. Wer dem Dämon dabei zuschauen möchte, wie er vom Desktop eintrudelnde Ereignisse aufschnappt, kann selbigen mit

    $ zeitgeist-daemon -r --log-level=DEBUG

im Debug-Modus starten, wobei obiger Aufruf wegen der Option -r auch gleich den bereits laufenden Dämon stoppt und ihn durch den im Vordergrund gestarteten ersetzt.

Rosenlose Zukunft

Das Zeitgeist-Projekt auf Launchpad [2] scheint allerdings seit 2013 im Dornröschenschlaf zu schlummern. Heute bringen Suchanfragen zum Thema Zeitgeist hauptsächlich kritische Stimmen von um Ihren Datenschutz besorgten Usern auf Internetforen zutage, die verzweifelt versuchen, dem Dämon den Garaus zu bereiten, ohne dabei ihren Ubuntu-Desktop lahmzulegen. Auch wenn dem Projekt möglicherweise keine rosige Zukunft bevorsteht, lohnt es sich nichtsdestotrotz, hinter die Kulissen zu schauen und die gesammelten Daten einem kritischen Blick zu unterziehen.

Abbildung 1: Kürzlich angesehene Dateien im Nautilus-Filebrowser.

Respektiert Privates

Weil das ungefragte Mitprotokollieren aller Aktionen auf dem Desktop die Privatsphäre des Users verletzen könnte, erlaubt es der Unity-Desktop dem User, die Sammelwut einzuschränken, ganz abzustellen oder gar alle bislang gesammelten Daten zu verwerfen. In den "System Settings" unter dem Eintrag "Security und Privacy" kann der User den Dämon durch das Deaktivieren einiger Checkboxen in seine Schranken weisen. Dann bekommt Zeitgeist eine Blacklist zugespielt und ignoriert eintrudelnde Events geblockter Applikationen. Abbildung 2 zeigt, dass Zeitgeist sich dazu überreden lässt, wahlweise keine Aktionen betreffs Musik, Videos, Fotos, Chat logs, oder Dokumenten mitzuschneiden. Durch Drücken des Buttons "Clear Data" fordert der User den Dämon gar auf, bislang gesammelte Daten permanent zu löschen. Was der Zeitgeist-Dämon so alles sammelt, zeigt auch der zeitgeist-explorer aus dem gleichnamigen Ubuntu-Paket grafisch an (Abbildung 3).

Abbildung 2: Dieser Dialog in den System Settings unter "Security and Privacy" bestimmt, welche Vorgänge der Zeitgeist-Dämon archiviert.

Auf einer frischen Ubuntu-Installation protokolliert Zeitgeist aber alles Erhältliche mit. Doch nicht alle Applikationen geben sich so geschwätzig wie Eye of Gnome aus dem einleitenden Beispiel. Wünscht sich der User nämlich, dass mittels Rhythmbox abgespielte Musikstücke in die Fänge des Zeitgeists gelangen, muss er das Ubuntu-Paket rhythmbox-plugin-zeitgeist installieren. Für Chrome oder Firefox existieren weitere Plugins, für User, die ihre Webaktivitäten mitverfolgen wollen.

Abbildung 3: Die Utility zeitgeist-explorer zeigt eintrudelnde Ereignisse in Echtzeit.

Perl wühlt

Die nachfolgend vorgestellten Perlskripts wühlen nach Herzenslust in den gesammelten Daten herum. Um zum Beispiel festzustellen, welche neuen Fotos der User zuletzt angesehen hat, liest Listing 1 die Tabelle uri der SQLite-Datenbank activity.sqlite im Pfad .local/share/zeitgeist unter dem Home-Verzeichnis des Users aus. Die SQL-Abfrage SELECT * from uri listet einfach alle Datenreihen aus der Tabelle uri auf, in denen Zeitgeist neu gefundenen Pfaden oder URLs Indexnummern zuweist. Das CPAN-Modul DBD::SQLite enthält nicht nur den Perl-Wrapper zum Ansteuern von SQLite-Datenbanken, sondern auch den C-Code des SQLite-Projekts selbst. Wer das Ubuntu-Paket sqlite3 installiert hat, kann mit dem Kommando

    $ sqlite3 activity.sqlite .schema

im Verzeichnis der Zeitgeistdatenbank erst einmal nachsehen, welche Tabellen in der Datenbank liegen und später mit interaktiv abgesetzten SQL-Kommandos deren Inhalt erforschen.

Abbildung 4: Der Zeitgeist-Dämon speichert alle angesehenen Abbildungen in einer SQLite-Datenbank.

Listing 1: urls-recent

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use DBD::SQLite;
    04 
    05 my( $home ) = glob "~";
    06 my $dbfile = "$home/.local/share/" .
    07   "zeitgeist/activity.sqlite";
    08 my $dbh = DBI->connect(
    09   "dbi:SQLite:dbname=$dbfile", "", "" );
    10 
    11 my $sth = $dbh->prepare( 
    12   "SELECT * from uri" );
    13 $sth->execute();
    14 
    15 while( my( $id, $uri ) = 
    16   $sth->fetchrow_array() ) {
    17   print "$uri\n";
    18 }

Listing 1 nutzt die Methode prepare() des DBI-Moduls mit dem geladenen SQLite-Driver, um das SQL-Kommando vorzubereiten, und execute() auf das zurückkommende Statement-Handle, um es dem SQLite-Engine zu übermitteln. Wie in DBI üblich, schnappen sich wiederholte Aufrufe von fetchrow_array() die vom Datenbank-Engine zurückkommenden Ergebnisreihen, deren jede (im Fall der Tabelle uri) zwei Spaltenwerte, nämlich id und value enthalten, die das Skript zwei gleichnamigen Perl-Variablen zuweist. Abbildung 4 zeigt, dass von Listing 1 nicht nur Foto-Pfade zurückkommen, sondern auch seltsam anmutende URLs wie application://firefox.desktop, die von Events zeugen, bei denen der User bestimmte Applikationen, wie zum Beispiel hier den Firefox-Browser, aufgerufen hat.

Abbildung 5: Die Tabelle event enthält protokollierte Aktionen mit Zeitstempel und Referenzen auf Daten in anderen Tabellen.

Immer ein Ereignis

Die Datenbanktabelle event hingegen speichert die zeitliche Abfolge der vom Zeitgeist-Dämon aufgeschnappten Ereignisse. Der SQL-Query in Abbildung 5 zeigt einige Beispiel-Records von Events mit zugeordneten Zeitstempeln, die mit numerischen Schlüsseln auf andere Tabellen verweisen wie zum Beispiel uri, in denen dann der Schlüssel einem Texteintrag zugeordnet ist.

Das Zeitstempelformat der Spalte timestamp ist allerdings nicht ein direkt von SQLite unterstütztes, sondern die Unixzeit seit 1970 in Sekunden gefolgt von drei weiteren Ziffern, die tausenstel Sekunden anzeigen. Listing 2 extrahiert aus der Tabelle event die Desktop-Aktionen der letzten 12 Stunden und muss das Zeitgeist-Datum im abgesetzten SQL mit

    date(substr(timestamp,1,10),'unixepoch')

in ein SQLite-Datum umwandeln, bevor es den Zeitstempel mit dem im SQLite-Dialekt praktischerweise mittels date('now', '-12 hours') verfügbaren Zeitfenster vergleichen kann.

Listing 2: apps-recent

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use DBD::SQLite;
    04 
    05 my( $home ) = glob "~";
    06 my $dbfile = "$home/.local/share/" .
    07   "zeitgeist/activity.sqlite";
    08 my $dbh = DBI->connect(
    09   "dbi:SQLite:dbname=$dbfile", "", "" );
    10 
    11 my $sth = $dbh->prepare(
    12   "select uri.value from event,uri where 
    13    subj_id = uri.id and interpretation = 1
    14    AND 
    15    date(substr(timestamp,1,10),'unixepoch') 
    16    >= date('now', '-12 hours')" );
    17 $sth->execute();
    18 
    19 my %apps = ();
    20 
    21 while( my( $uri ) = 
    22     $sth->fetchrow_array() ) {
    23     if( $uri =~ /^application:/ ) {
    24         $apps{ $uri }++;
    25     }
    26 }
    27 
    28 for my $app ( sort 
    29     { $apps{ $b } <=> $apps{ $a } } 
    30     keys %apps ) {
    31     print "$app: $apps{ $app }\n";
    32 }

Abbildung 6: Wie oft hat der User welche Applikation in den letzten 6 Stunden aufgerufen?

Die while-Schleife ab Zeile 21 arbeitet alle gefundenen Ergeignis-Records ab, und kumuliert Applikations-URLs im Hash %apps auf. Die for-Schleife ab Zeile 28 gruppiert die Einträge dann absteigend nach Zählerstand und gibt das Ergebnis aus. Abbildung 6 zeigt die Ausgabe von Listing 2, und es stellt sich heraus, dass ich in der untersuchten Zeitspanne 18 mal ein XTerm-Fenster geöffnet und insgesamt sechs Screenshots gezogen habe.

Überwacher-Stechuhr

Wie fleißig war der User in den letzten 24 Stunden, hat er auch ordentlich auf dem Desktop herumgeklickt? Das findet Listing 3 heraus, in dem es die Activity-Tabelle event nach Einträgen des vergangenen Tages durchforstet, und pro laufender Stunde jedes Mal einen Zähler hochzählt, falls ein Desktop-Ereignis in diesen Zeitrahmen fällt.

Listing 3: last-24-hours

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use DBD::SQLite;
    04 use DateTime::Format::SQLite;
    05 
    06 my( $home ) = glob "~";
    07 my $dbfile = "$home/.local/share/" .
    08   "zeitgeist/activity.sqlite";
    09 my $dbh = DBI->connect(
    10   "dbi:SQLite:dbname=$dbfile", "", "" );
    11 
    12 my $sth = $dbh->prepare(
    13   "SELECT 
    14    datetime(substr(timestamp,1,10),
    15             'unixepoch') 
    16    AS time FROM event WHERE
    17    time >= datetime('now', '-24 hours')" );
    18 $sth->execute();
    19 
    20 my %activity = ();
    21 
    22 while( my( $time ) = 
    23   $sth->fetchrow_array() ) {
    24 
    25   my $dt = DateTime::Format::SQLite->
    26     parse_datetime( $time );
    27   $dt->set_time_zone( "local" );
    28   $activity{ $dt->hour() }++;
    29 }
    30 
    31 my $now = DateTime->now( 
    32     time_zone => "local" );
    33 my $dt  = 
    34   $now->clone->subtract( days =>  1 );
    35 
    36 while( $dt < $now ) {
    37   my $hour = $dt->hour();
    38   printf "%02d:00 %d\n",
    39     $hour, $activity{ $hour } || 0;
    40   $dt = $dt->add( hours => 1 );
    41 }

Abbildung 7: Ereignisse auf dem Desktop über die letzten 24 Stunden.

Die SQL-Abfrage ab Zeile 13 holt Ereignisse mit Zeitstempeln der letzten 24 Stunden aus der Datenbank, und das CPAN-Modul DateTime::Format::SQLite wandelt das herauskommende Datumsformat in das von Datums-Tausendsassa DateTime benutzte um.

Datums-Tausendsassa

Damit die aus dem Zeitstempel extrahierten Stunden nicht die der UTC-Zeitzone sondern der lokalen entsprechen, setzt Zeile 27 dieselbe mit dem Kennwort "local". Der Hash %activity zählt in Zeile 28 jeweils um eins hoch, falls ein Ereignis in eine bestimmte Stunde fällt und abschließend braucht die while-Schleife ab Zeile 36 nur noch vom Datum des gestrigen Tages bis heute stundenweise hochzuzählen und jeweils die Anzahl der Ereignisse mit printf auszudrucken.

Die Ausgabe in Abbildung 7 bestätigt, dass der User nach dem Motto "Am Abend wird der Faule fleißig" nur nach 19 Uhr bis etwa 23 Uhr aktiv war, während unter Tags absolut nichts geschah. Oder war es etwa so, dass der User tagsüber im Büro arbeitete und sich abends und nachts an seinen Heimcomputer setzte, um Artikel fürs Linux-Magazin zu schreiben? Dank Zeitgeist besteht nun Erklärungsbedarf.

Infos

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2015/05/Perl

[2]

Zeitgeist-Projekt auf Launchpad: https://launchpad.net/zeitgeist

Michael Schilli

arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.