Визуализация статистики использования компьютера с R



    Думаю, многим интересно (хотя бы из любопытства), как именно они используют свой компьютер: самые нажимаемые кнопки, пройденное мышью расстояние, среднее время работы и другую информацию. В этой статье я расскажу один из вариантов того, как можно собрать такую информацию и затем представить её в виде интерактивных графиков. Все описанные действия производились на ноутбуке с ОС Debian Wheezy, Python 2.7.3, R 2.15.
    image


    Сбор данных



    Всё началось с того, что этой осенью мне захотелось иметь как можно более полную статистику о моём использовании компьютера. Сказано — сделано: написал простейший кейлоггер, записывающий все нажатия и отпускания клавиш на клавиатуре, кнопок на мыши, а также все перемещения мыши. Также в голову пришла идея делать фото вебкамерой через некоторый промежуток времени, что также было реализовано.
    Изначально все данные писались просто в текстовый файл, но потом решил всё-таки сделать «по хорошему» и перевёл запись всех событий в базу SQLite. Несмотря на то, что размер её по сравнению с сжатым gzip'ом текстовым файлов больше в несколько раз (сейчас база занимает немногим больше 0.5 Гб), это всё равно относительно немного, а делать выборки из базы всё-таки удобнее. Фото с камеры по-прежнему хранятся отдельно и их общий размер сейчас составляет 2.2 Гб (около 30000 файлов).

    Представление информации



    Итак, с записью данных всё вроде бы решено, осталось всё это представить в виде удобных графиков и таблиц. Так как я уже достаточно давно собирался изучить язык R, но всё как-то не было повода начать, то решил его здесь и использовать. В общем-то после других языков начать пользоваться R было достаточно несложно, хотя некоторые непривычные вещи всё-таки есть: например большинство стандартных операторов и функций векторизированы, принят стиль именования с разделением слов точкой (вместо _ или camel case в других языках). Сначала я просто осваивался с самим языком и входящими в стандартную поставку инструментами для обработки и отображения данных (надо сказать, достаточно богатыми), затем нашёл библиотеки ggplot2 для гибкого построения графиков и plyr для операций вида split-apply-combine над массивами данных. Также существует возможность создавать файлы с Markdown разметкой и внедрёнными в неё вычислениями на R (с помощью knitr), что достаточно удобно. Однако, всё это статические графики и таблицы, и для любого изменения их внешнего вида, выбора некоторого подмножества данных требуется каждый раз переписывать код, а хотелось бы чего-то более динамичного, с возможностью ставить элементы управления вроде слайдеров, кнопок и другого.
    Как оказалось, недавно появился удобный способ достичь этого в R, с очень малым количеством дополнительного кода. Наткнулся я на этот инструмент можно сказать случайно, и не пожалел. Смотрите сами: Shiny — «Easy web applications in R». Простейший пример есть прямо на той странице, и он действительно прост в написании. Надо сказать, что Shiny — достаточно новый продукт, разработка (судя по репозиторию) началась в июне прошлого (2012) года и активно продвигается. Каких-либо багов в нём мне не встретилось, так что думаю можно считать проект стабильным. Кстати, на том же сайте можно найти RStudio — удобную IDE для разработки на R.
    Таким образом, отображение статистики я стал реализовывать с использованием Shiny. Некоторые скрины того, что получилось на текущий момент:



    Также можно просмотреть эти страницы (правда в статическом виде, т.е. элементы управления не работают) на shiny-sample.aplavin.ru.

    Видно, что возможности действительно богатые, причём внешний вид страницы можно полностью менять HTML, CSS и JavaScript кодом (у меня везде используется стандартный вариант). Удобно, что для работы Shiny не требуется устанавливать никакой сервер, всё необходимое содержится в самом R-пакете.

    Вкратце о реализации



    Весь код (и кейлоггер, и визуализация) доступен на BitBucket. Сейчас у меня вызывается по крону каждую минуту файл capture, который с вероятностью 50% делает снимок и сохраняет его (из-за того, что камера инициализируется не мгновенно, делается 20 снимков и сохраняется последний из них). Кейлоггер представлен исполняемым файлом keylogger.py, запускаемым из inittab'a (с использованием опции respawn). В папке statistics хранятся файлы keylogger.stats.R и keylogger.stats.Rmd, первый из которых генерирует графики просто в виде картинок, второй — в виде HTML страницы с использованием knitr (оба, разумеется, статичные). Наконец, в папке shiny_page содержатся файлы собственно страницы (ui.R, server.R) и файл compute.data.R, который вычисляет все необходимые данные (сейчас это занимает от 30 секунд до 1 минуты, вынесено в отдельный файл чтобы не вычислять каждый раз при открытии страницы). Для удобства в той же папке есть Makefile, который позволяет запускать приложение командой make run.

    Вычисления статистик



    Изначально все расчёты производились почти полностью запросами к базе SQLite, но затем, сравнив производительность GROUP BY с функциями пакета plyr, я увидел, что SQLite выполняет аналогичные действия намного медленнее, даже с индексами. Единственная (но важная) проблема состоит в том, что для использования этих функций необходимо загружать весь набор данных в память. Сейчас при выполнении compute.data.R используется около 1 Гб памяти, и через некоторое время 4 Гб на моём ноутбуке будет не хватать. В таком случае думаю нужно будет вернуться опять на вычисления средствами базы данных, это хотя и значительно медленнее, но хотя бы будет работать (хотя, конечно, предложения по этому поводу приветствуются). Для сравнения, аналогичный код на SQL и на R с использование plyr:

    SELECT field, COUNT(*) FROM Table GROUP BY field
    


    ddply(dataset, ~field, nrow)


    Также можно делать и более сложные, многоуровневые группировки. Пример из моего compute.data.R (без аналога на SQL, но думаю после предыдущего примера эта двухуровневость должна быть понятна):

    mouse.coords.by.win <- ddply(
      coords,
      ~window,
      function(df) {
        res <- ddply(
          df,
          .(x=as.integer(x/binsize), y=as.integer(y/binsize)),
          .fun=nrow,
          .drop=F)
        res$V1 <- res$V1 / max(res$V1)
        res$cnt <- nrow(df)
        res
      })


    Этот код считает для каждого окна (столбец window) распределение координат мыши по экрану по квадратам со стороной binsize.

    Кейлоггер на Python



    Для перехвата событий X сервера используются Python биндинги для xlib, запись этих событий производится в базу данных SQLite. Стоит заметить, что т.к. скрипты из inittab запускаются от пользователя root, перед обращением к xlib нужно установить переменную окружения: os.environ['XAUTHORITY'] = '/home/USER/.Xauthority'. Затем мы подключаемся к дисплею и создаём recording context для получения нужных нам событий (нажатий клавиш, кнопок и перемещений мыши):

    dpy = display.Display(':0')
    ctx = dpy.record_create_context(
            0,
            [record.AllClients],
            [{
                    'core_requests': (0, 0),
                    'core_replies': (0, 0),
                    'ext_requests': (0, 0, 0, 0),
                    'ext_replies': (0, 0, 0, 0),
                    'delivered_events': (0, 0),
                    'device_events': (2, 6), # в этой строке определяется, какие события мы получаем: в данном случае те, код которых от 2 до 6
                    'errors': (0, 0),
                    'client_started': False,
                    'client_died': False,
            }])
    dpy.record_enable_context(ctx, record_callback) # эта функция возвращает управление только при вызове record_disable_context в callback
    


    Весь остальной код — получение, обработка и запись событий из callback-функции. В связи со сложной структурой полученных от X сервера данных, при обработке используется такой цикл:

    def record_callback(reply):
        data = reply.data
        while len(data):
            event, data = rq.EventField(None).parse_binary_value(data, record_dpy.display, None, None)
            # здесь обработка event
    


    В собственно обработке event'а нет ничего сложного: получаем его тип, дополнительные данные (поле detail) и записываем их. Однако, так как мы хотим записывать также окно (а точнее его класс), где произошло событие, нужно получить его. Это также делается с помощью функций из xlib:

    windowvar = dpy.get_input_focus().focus
    wmclass = windowvar.get_wm_class()
    


    Остальная часть кода — получение нормального названия клавиши из её кода, запись события в соответствующую таблицу в базе и обработка ошибок. С записью в базу связана ещё одна небольшая особенность: если при каждом событии flush'ить результат в файл, то при поступлении большого их количества запись не будет успевать проходить. Поэтому я записываю примерно через каждые 100 событий:

    if randint(0, 100) <= 0:
        dbconn.commit()
    


    Конечно, в таком случае при аварийном завершении процесса небольшое количество последних событий не сохранятся, но в данном случае это не критично.

    Схема базы данных:

    CREATE TABLE KeyEvents(TimeStamp REAL, KeyName TEXT, EventType INTEGER, WindowClass TEXT);
    CREATE TABLE MouseBtnEvents(TimeStamp REAL, KeyName TEXT, EventType INTEGER, WindowClass TEXT);
    CREATE TABLE MouseMoves(TimeStamp REAL, MoveX INTEGER, MoveY INTEGER, WindowClass TEXT);
    


    Заключение



    В итоге можно сказать, что я разобрался с R — действительно удобным языком для подобных вычислений, с прозрачным захватом событий X сервера, ну и конечно получил красивую статистику своего использования компьютера. У меня есть ещё несколько идей по поводу того, какие графики и таблицы ещё стоит добавить в такой «отчёт», но интересно было бы также услышать ваши варианты (если кто-то дочитал до сюда).
    image

    P.S.: посоветуйте пожалуйста хорошую книгу или онлайн-курс по статистике и/или графической визуализации получаемых данных.

    UPD1: значительно дополнена информация о реализации основных частей системы.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 13

      0
      Красиво! Спасибо, мне пригодится. Удачи!
        0
        Красиво и интересно. Спасибо за ссылку на Shiny, проект выглядит довольно любопытным.
          0
          В связи с его новизной и активным развитием некоторые моменты не до конца документированы, хотя общий уровень этой самой документации достаточно хороший. Также, если какой-то фичи определённо не хватает можно написать им issue — отвечают вроде бы быстро. Кстати, ничего не мешает использовать его прямо на сервере, а не локально. Единственно что, всё генерируется на лету и при сложных расчётах или больших графиках может надолго грузить сервер.
          0
          Если кого-то заинтересовала тема, то сейчас идет курс по R для совсем уж новичков на Coursera.
            0
            Я думал пройти это курс, но судя по его программе объём информации как-то не впечатляет, самому эти темы можно намного быстрее изучить. Или это не так? Вы его проходили?
              0
              Я прохожу его сейчас. Если честно, то сложность ни Куиза, ни программного задания совершенно не впечатляет. Может конечно и потому, что синтаксис и идея работы очень на Matlab похожа, а с ним я имел дело достаточно плотно.
            0
            Про python не плохо было бы информации добавить.
              0
              Не думал, что это станет предметом интереса, но раз так, то добавлю более подробное описание :)
            0
            data.table не пробовали? это data.frame с дополнительными наворотами. Например там очень классно что-то вроде GROUP BY писать.
            Можете поподробнее про plyr рассказать? Я что-то не разобрался как с ним работать.

            UPD: да, виду что data.table пробовали.
              0
              Однако нет, data.table не пробовал, но обязательно посмотрю — сейчас поискал, пишут что она быстрее plyr. А в plyr фактически одна основная функция, только для разных типов входа/выхода. Например, приведённая в статье ddply — принимает и возвращает data.frame (на что указывают буквы d). Эта функция — аналог group by по переданной переменной (или нескольким, или по выражению), в возвращаемом data.frame первые столбцы — переменные, по которым была группировка, оставшиеся — результат переданной в ddply функции. В статье есть пара примеров на неё.
              0
              посоветуйте пожалуйста хорошую книгу или онлайн-курс по статистике и/или графической визуализации получаемых данных.

              на coursera.org сейчас идет курс Computing for Data Analysis (основы R), через две недели начнется Data Analysis (это уже про саму статистику), на edx через три недели начнется Introduction to Statistics (The focus of Stat2.1x is on descriptive statistics)
                0
                Какие из этих курсов Вы проходили? Computing for Data Analysis, например, покрывает совсем мало тем, это как поверхностное изучение языка R.

              Only users with full accounts can post comments. Log in, please.