Как стать автором
Обновить

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

Время на прочтение6 мин
Количество просмотров21K


Думаю, многим интересно (хотя бы из любопытства), как именно они используют свой компьютер: самые нажимаемые кнопки, пройденное мышью расстояние, среднее время работы и другую информацию. В этой статье я расскажу один из вариантов того, как можно собрать такую информацию и затем представить её в виде интерактивных графиков. Все описанные действия производились на ноутбуке с ОС 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: значительно дополнена информация о реализации основных частей системы.
Теги:
Хабы:
+39
Комментарии13

Публикации

Истории

Работа

Python разработчик
141 вакансия
Data Scientist
63 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн