GrowthForecast.plで自分ロギングしてみた

こちらのブログではお久しぶりです。

定期的に数字を投げておけば勝手にいい感じでグラフ化してくれるツールが欲しい…!と思っていたら、GrowthForecastというのがPerl界隈で流行ってると教えてもらいました。

早速使ってみようと思ったのですが、MacではRRDtoolが落ちるため、色々やった挙句諦めてVirtualBoxにインストールしてようやく使えました。

使い方については既に色々書かれているので省略しますが、グラフを作り始める設定とかも不要でHTTPでPOSTし始めれば今すぐにでも数値ログを取れるという、まさに思った通りのツールでした。


ところで最近はライフログにハマっています。

パソコンを触っている時間を記録しておいて、何時に眠りに落ちたのか分かるようにしたかったので、GrowthForecastでキータイプとマウスイベントの回数をロギングしてみました。

Macでキーイベントを取得するにはObjective-Cだろうと思って書き始めたのですが、XCodeに慣れなくて結局Vimで書きました。

かなりCっぽいObjective-Cのソース置いときます。

これでコンパイルできるはずです。

clang sample.m -o sample -framework Cocoa -lcurl


GrowthForecastに投げる部分はlibcurlを使ってこんな感じになります。

#include <curl/curl.h>

...

size_t curl_write_callback_func(char* ptr, size_t size, size_t nmemb, void* nothing) {
    return 0;
}

int post(char *name, int num) {
    char url[MAXLINE];
    sprintf(url, "http://localhost:5126/api/me/log/%s", name);

    char postdata[MAXLINE];
    sprintf(postdata, "number=%d", num);

    printf("%s: %d\n", name, num);

    CURL *curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback_func);
    CURLcode res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);

    return res == CURLE_OK;
}


Macのイベント取得部分はCGEventTapというAPIを使って書けるそうです。

#import <Cocoa/Cocoa.h>
#include <time.h>

...

CGEventRef catchEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* nothing) {

    // http://stackoverflow.com/questions/4727149/application-randomly-stops-receiving-key-presses-cgeventtaps
    if (type == kCGEventTapDisabledByTimeout) {
        CGEventTapEnable(eventTap, true);
    }

    if (type == kCGEventKeyDown) { /* to listen for key events, run this program as root */
        int key = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

        printf("%d\n", key);
        count_key++;
    }
    else if (type == kCGEventMouseMoved || type == kCGEventScrollWheel) {
        count_mouse++;
    }

    return event;
}

int main() {
    CFRunLoopSourceRef runLoopSource;

    eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, 0, kCGEventMaskForAllEvents, catchEventCallback, NULL);

    runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);

    CGEventTapEnable(eventTap, true);
 
    ...

    CFRunLoopRun();

    return 0;
}

キーイベントを取得するにはこのプログラムをroot権限で実行する必要があります。

それから、普通にキーイベントだけを取得すると、何故か突然イベントを取得できなくなることがあるのですが、どうやらイベントが連続して起こった時などに CGEventTap が外されてしまうそうです。StackOverflowでSnow Leopardのバグだとかいう書き込みを見ましたが真相は不明です。その時に kCGEventTapDisabledByTimeout というイベントが出るので、それを捕まえて CGEventTap をまた有効にしてあげないといけません。


定期的にGrowthForecastにPOSTする部分はCFRunLoopTimerRefというのを使いました。

#include <time.h>
#include <dispatch/dispatch.h>

...

void postEventsAsync(int num_key, int num_mouse) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        post("key", num_key);
        post("mouse", num_mouse);
    });
}

void recordCountsCallback(CFRunLoopTimerRef timer, void* nothing) {
    time_t this_time;
    struct tm * timeinfo;

    time(&this_time);
    timeinfo = localtime(&this_time);
    timeinfo->tm_sec = 0;
    this_time = mktime(timeinfo);

    if (this_time == prev_time) {
        // within less than 60 sec (do nothing)
        return;
    } else if (prev_time == 0) {
        // first time (set prev_time to this_time)
    } else if (difftime(this_time, prev_time) >= 120.0) {
        // come back from sleep (discard)
        count_key = 0;
        count_mouse = 0;

        postEventsAsync(count_key, count_mouse);
    } else {
        // more than 60 sec (record)
        postEventsAsync(count_key, count_mouse);

        count_key = 0;
        count_mouse = 0;
    }
    prev_time = this_time;
}

int main() {

    ...

    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1.0, 0, 0, recordCountsCallback, NULL);
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);

    CFRunLoopRun();

    return 0;
}

Grand Central Dispatch を使ってみました。こうしないと、HTTP通信待ちの間に後続のキーイベント等をブロックしちゃうので、パソコンがプチフリーズしたみたいになってしまうためです。


こんなところです。それではみなさんもHappy Life Logging!