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

Файл⇨строка или активность работы над файлом

Время на прочтение19 мин
Количество просмотров2.2K
Большинство разработчиков знакома с таким продуктом, как визуализатор code_swarm (на google code). Как минимум каждый третий наверняка выгружал для него лог и создавал видео, которое визуализирует процесс разработки приложения, в котором видно активность программистов. Ну и конечно каждый второй видел видео подобного рода. Практически все эти видео делались на срезе отношения программист⇨файл.
В этой статье будет описан процесс формирования лога в срезе отношения файл⇨строка, то есть с генерированное видео будет демонстрировать активность работы над файлом.

Кому это интересно под прошу под кат.
В статье будет использованы:
  • Git — VCS
  • code_swarm — визуализатор истории репозиториев.
  • gource — визуализатор истории репозиториев.
  • Эмулятор среды linux в Windows или UNIX OS (с git уже идет для win эмулятор msysgit)
  • MEncoder — свободный кодировщик видео
  • ffmpeg — программа для конвертации видео с использованием различных кодеков.
Сразу уточню, описание процесса генерации дифф файла будет для git, но скрипт при желании можно переделать, я же здесь буду делиться своим опытом.
Готовый работающий результат находиться здесь.


Скрипт генерации лога для code_swarm

Для того что бы code_swarm сумел проанализировать историю, ее ему надо подать в определенном формате. Формат файла xml и имеет он следующий вид:
  1. <?xml version="1.0"?>
  2. <file_events>
  3.   <event date="" author="" filename="" action="" comment=""/>
  4. </file_events>
По сути в code_swarm можно демонстрировать любую статистику, которая изменяется по времени и у которой есть объект, который что-то и субъект над которым производиться действие. В классическом случае, когда выгружается лог для code_swarm, допустим таким платформой как showteamwork, объектом является программист, субъектом файл. В нашем случае объектом будет файл а субъектом строка, которую добавили или удалили.
Данные будем брать из diff файла, который большей частью похож на классический файл, но к нему прикрепленный еще и коммиты из репозитория. Файл имеет такой вид:
1142998387000:John Resig<br>&ajax/ajax.js<br>new file mode 100644<br>+// AJAX Plugin<br>+// Docs Here:<br>+// http://jquery.com/docs/ajax/<br>+if ( typeof XMLHttpRequest == 'undefined' && typeof window.ActiveXObject == 'function') {<br>+var XMLHttpRequest = function() {<br>+return new ActiveXObject((navigator.userAgent.toLowerCase().indexOf('msie 5') = 0) ?<br>-Microsoft.XMLHTTP : Msxml2.XMLHTTP);<br>-};<br>-}<br>+.xml = function( type, url, data, ret ) {<br>+var xml = new XMLHttpRequest();<br>+if ( xml ) {<br>+xml.open(type || GET, url, true);<br>+if ( data )<br>+xml.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');<br>+if ( ret )<br>+xml.onreadystatechange = function() {<br>
Данный файл выгружается командой:
git log -U0 --diff-filter=AMD --reverse --pretty="%at000:%cn" -10 | \<br>  grep -v "^\(-\{3\}\|+\{3\}\) " | \<br>  grep -v "^[+-][   ]*$" | \<br>  grep -v "^[+-]$" | \<br>  grep -v "^[   ]*$" | \<br>  sed -e "s/diff .* b\//\&/g" \<br>    -e "s/^+[   ]\+/+/g" \<br>    -e "s/^-[   ]\+/-/g" \<br>    -e "s/[   ]\+$//g" \<br>    -e "s/^$//g" \<br>    -e 's/\\/\\\\/g' \<br>    -e "s/[\"\`<>$]//g"
Думаю стоит описать что и для чего здесь:
  1. log — вывести лог
  2. -U0 — добавить к логу еще и дифф изменении с 0-ым количеством строк контекста, только измененные строки
  3. --diff-filter=AMD — выводить только файлы со статусами A: добавлен, M: изменен, D: удален.
  4. --reverse — обратная сортировка по дате.
  5. --pretty="%at000:%cn" — формат лога %at—дата, %cn—имя коммитера.
  6. -10 — только 10 последних коммитов.
  7. grep запрещаем вывод строк, которые начинаются с 3 + или — с пробелом, все пустые строки, все строки начинающиеся с + или — но они пустые.
  8. sed преобразовываем строки: удаляем все не нужные пробелы после +/-, делаем строки безопасными для shell.
А вот теперь самое интересное, есть очень полезная утилита для bash называется awk — это язык построчного разбора и обработки входного потока. В нем весь смак. Я сначала идею реализовал на стандартных sed, сut и while, но производительность была ужасная, но когда переделал все для Awk скорость генерации удвоилась если не утроилась. Собственно, хватит лишних слов, вот код скрипт полностью (наиболее актуальная версия файла):
#!/bin/sh

generate() {
    
if test -t 1; then
        
exec >$logfile
    
fi

    
echo -e "<?xml version=\"1.0\"?>\n<file_events>"
    
echo "generating ..." >&2

    
awk -v typegen=$1 '
        BEGIN {
            split("\b\b\b\b\b. . . . . \b- \b\b- \b\b- \b\b- \b\b- \b= = = = =", st, " ")
            ist=0
            _ord_init()

            typehash=0
            if( typegen == "ch_code") {
                typehash=1
            }
            else if( typegen == "crypt" ) {
                typehash=2
            }
        }

        function _ord_init(low, high, i, t)
        {
            low = sprintf("%c", 7)
            if (low == "\a") {
                low = 0
                high = 127
            } else if (sprintf("%c", 128 + 7) == "\a") {
                low = 128
                high = 255
            } else {
                low = 0
                high = 255
            }

            for (i = low; i <= high; i++) {
                t = sprintf("%c", i)
                _ord_[t] = i
            }
        }

        function ord(str, c) {
            c = substr(str, 1, 1);
            return _ord_[c];
        }

        /^[0-9]/ { 
            sub(/:.*/, "");
            d=$0;
            next;
        }
        /^&/ {
            sub(/&/, "");
            f=$0
            substr($0, 2, length($0) - 1);
            next;
        }
        /^\+/ { a="A"; }
        /^-/ { a="D"; }
        /^[\+-]/ {
            sub(/[\+-]/, "")
            str=""
            if( typehash == 1) {
                for(i=1; i<length($0); i++){
                    str = str "" ord(substr($0, i, 1))
                }
                gsub(/32|16/, "/sd", str)
                str = substr(str, 0, length(str)-2) "." substr(str, length(str)-1, 2);
            }
            else {
                cmd="echo \"" $0 "\" | md5sum | cut -f1 -d \" \" | sed -e \"s@[32|16]@/sd@g;\" -e \"s/\\(..\\)\$/.\\1/\"" 
                if ( typehash == 2 )
                    cmd="C:/Perl/bin/perl -e \"print crypt($ARGV[0], $ARGV[1])\" \"" $0 "\" \"1/5l58j/jk\"" 
                cmd | getline str;
                close(cmd);
            }

            if (str != "")
                print "<event date=\""d"\" author=\""f"\" filename=\""str"\" action=\""a"\"  comment=\"\"/>"
                
            system("echo -ne \"" st[ist++] "\" >&2")
            if (ist > 16) ist=0
        }
    '
 $gitdiff

    
echo -ne "\b\b\b\b\b\b\b\b\b\b\b\bcompleted!" >&2
    
echo "</file_events>"
    
rm $gitdiff
}

prepare_git() {
git log -U0 --diff-filter=AMD --reverse --pretty="%at000:%cn" $1 | \
    
grep -v "^\(-\{3\}\|+\{3\}\) " | \
    
grep -v "^[+-][     ]*$" | \
    
grep -v "^[+-]$" | \
    
grep -v "^[     ]*$" | \
    
sed -e "s/diff .* b\//\&/g" \
        
-e "s/^+[     ]\+/+/g" \
        
-e "s/^-[   ]\+/-/g" \
        
-e "s/[     ]\+$//g" \
        
-e "s/^$//g" \
        
-e 's/\\/\\\\/g' \
        
-e "s/[\"\`<>$]//g" > $gitdiff
}

fileaction="$(date +%j%H%M%s)"

typehash=md5
[ -n "$1" ] && typehash=$1 || echo -e "первым параметром передается тип функции для" + \
    
"генерации хеша строки\nдоступны следующие значения:\n" + \
    
"\t\tmd5 — по-умолчанию\n\t\tcrypt\n\t\tch_code\nusing: $0 crypt" >&2
echo "Будет применена функция: "$typehash >&2

[ -n "$2" ] && countcommit=$2 || echo -e "вторым параметром передается лимит коммитов\n" + \
    
"git log --help\nпараметер:\t-<n>\n\t\tLimits the number of commits to show.\nusing: $0 crypt -10" >&2
echo -n "Будет использован лимит: " >&2
[ -n "$2" ] && echo $2' коммитов' >&2 || echo "все коммиты" >&2

gitdiff=$fileaction".temp"
logfile=$fileaction"actions.xml"

prepare_git $countcommit
generate $typehash


Подробно объяснять не буду, как работает программа на awk. Скажу только в общих черта:
  • Если строка начинается на цифру то значит, это время коммита, извлекаем и запоминаем это значение и сразу переходим к следующей строке
  • Если строка начинается со знака & значит следующие символы это имя файла, извлекаем и переходим к следующей строке
  • Если строка начинается со знака +, значит действие типа A, продолжаем анализ строки
  • Если строка начинается со знака -, значит действие типа D, продолжаем анализ строки
  • Если строка начинается со знака -/+, то обработать строку и вывести ее в STDOUT, перейти к следующей строке
Для того что бы строки (субъекты) были перевариваемые визуализаторами, они преобразуются несколькими способами:
  1. md5 — с использованием утилиты md5sum, затем в сумме все числа 32 или 16 заменяются символами /sd, а перед двумя последними добавляется символ точка. Это делается что бы в визуализаторе gource строилось дерево
  2. crypt — с использованием функции perl crypt которая шифрует входящую по ключу и возвращает результат.
  3. ch_code — просто преобразует все символы в цифровое значение и заменяет все числа 32 или 16 символами /sd.
Скрипт может принимать 2 параметра:
  1. тип преобразования строк — этот параметр отвечает за преобразование строки, принимает значения приведенные выше, без его указания по умолчанию будет использоваться тип md5.
  2. количество коммитов — этот параметр передается в функцию генерации дифа, чтоб ограничить количество выводимых коммитов, передавать надо следующую конструкцию -num, где num число коммитов. Если его не, то берутся все коммиты.
Вывод данных происходит в файл созданный автоматически. Но при желании вывод можно сделать в любой другой файл. Для того, чтоб запускать функцию генерации лога активности из sh любого вашего репоситория выполните следующую команду:
$ echo "{путь к файлу скрипта генерации лога} \$@" > /bin/genlogcs
Собственно по генерации активности все.


Конфиг для code_swarm

Теперь поговорим о конфиге для code_swarm.Для начала я собрал code_swarm из исходников, полученный файл можно скачать от сюда. Ложите его себе в директорию, где находиться code_swarm в каталог dist.
Создайте файл с названием my.conf со следующим содержимым:
#Цвет для элементов заканчивающихся на число и букву
ColorAssign1="DigitLetter", ".*[0-9][a-z]", 43, 170, 215, 43, 170, 215
#Для элементов заканчивающихся на букву и цифру
ColorAssign2="LetterDigit", ".*[a-z][0-9]", 255, 134, 51, 255, 134, 51
#Букву и букву
ColorAssign3="LetterLetter", ".*[a-z][a-z]", 43, 110, 214, 43, 110, 214
#Цифру и цифру
ColorAssign4="DigitDigit", ".*[0-9][0-9]", 41, 242, 185, 41, 242, 185

Width=1280
Height=720
InputFile=data/my/data/actions.xml
PhysicsEngineConfigDir=physics_engine
PhysicsEngineSelection=PhysicsEngineOrderly
ParticleSpriteFile=src/particle.png
Font=Helvetica
FontSize=16
BoldFontSize=16
#MillisecondsPerFrame=2254085
MaxThreads=4
Background=0,0,0
TakeSnapshots=true
SnapshotLocation=data/my/png/cs-#####.png
DrawNamesSharp=true
DrawNamesHalos=true
DrawFilesSharp=false
DrawFilesFuzzy=true
DrawFilesJelly=false
ShowLegend=true
ShowHistory=true
ShowDate=true
ShowEdges=false
ShowDebug=false
EdgeLength=36
EdgeDecrement=-2
FileDecrement=-1
PersonDecrement=-1
FileSpeed=7.0
PersonSpeed=2.0
FileMass=2.0
PersonMass=10.0
EdgeLife=250
FileLife=200
PersonLife=255
HighlightPct=5
UseOpenGL=false
ShowUserName=true
IsInputSorted=false
Этот файл нам пригодится в дальнейшем.


Скрипт генерации видео визуализации активности

Чтоб долго не объяснять что и где создавать и какая структура каталога должна быть, скажу просто в директории с code_swarm, в каталоге data создайте каталог my со структурой которая приведена здесь. Взять надо следующее:
  • Каталог data со всем содержимым
  • Каталог tools со всем содержимым
  • Каталог generator_logs, в который мы положим скрипт для генерации файла активности для code_swarm. Собственно, он там и находиться.
  • Файл gen_log, о нем речь пойдет дальше, этот файл генерирует лог для gource из лога для code_swarm
  • my.config, который описан выше
  • sort_log, это скрипт для сортировки лога для gource
  • run.bat, о нем речь пойдет дальше
И надо еще создать 2 каталога png и results.

Скрипт gen_log

Так как интересно посмотреть результат работы над файлами не только в code_swarm, но и в gource я сделал скрипт, который генерирует лог для него. Данный скрип называется gen_log (наиболее актуальная версия файла):
#!/bin/sh
uses(){
    echo -e 'using\n$0 file_codeswarm.xml'
}

generatelog(){
    echo "genereting... "
    state=("\\" "|" "/" "—")
    i=0
    if [ -f "$1" ]; then
        result=${1%.*}'.log'
        echo -n > $result
        #Выводим только строки в которых есть слово event
        grep -e "event " $1 | \
        #удаляем все символы табуляции и пробелы
        #удаляем все <event и />
        sed -e "s/^[    ]*//;s/^<event //g;s|/>$||g" | \
        while read line
        do
            date=""
            #валидируем строку, появляются 4 переменные
            eval $line;
            #проверяем не пустали переменная date, если нет производим вывод
            [ -n "$date" ] && [ "`echo -n $data | wc -c`" -gt "10" ] && date=`echo $data | sed -e "s/^\(.\{10\}\).*/\1/"`
            [ -n "$date" ] && echo "$date|$author|$action|$filename" >> $result
            #отображение процесса генерации.
            echo -ne "\b${state[$i]}"
            (( i += 1 ))
            [[ $i -eq 5 ]] && i=0
        done
        echo -ne "\bcompleted!"
    else
        echo -e "file log code_swarm not exsits!\n$1"
    fi
}

[ -n "$1" ] && generatelog $1 || uses 
В этом скрипте используется полезная функция eval. Она исполняет текст как будто вы его ввели в командную строку. Такой подход удобен в нашем случае так как входная строка имеет следующий вид:
date="1142998387000" author="ajax/ajax.js" filename="c9/sd/sd9db4/sd/sd/sdb945/sdb89a/sd/sd7/sd/sdfbfdf.04" action="A" comment=""
Как вы понимаете система обработает эту строчку и у нас появиться 5 переменных date, author, filename, action, comment (спасибо bliznezz). Эти переменные заливаются в файл со следующим форматом:
date|author|action|filename
Правда у gource можно уже настроить этот формат. Формат обработки файла находиться в файле {gource_home}/data/gource.style

Скрипт run.bat

Теперь соберем все в общий файл, который будет обрабатывать сгенерированный вами, при помощи команды genlogcs, файл активности, который вы положили в каталог {code_swarm_home}/data/my/data/actions.xml.
Вот его содержимое (наиболее актуальная версия файла):
call sh gen_log ./data/actions.xml

call sh sort_log ./data/actions.log > data\gource.log

pushd png
del *.png
popd

pushd ..\..
call run.bat data\my\my.config
popd

pushd png
call "..\tools\nt\mencoder" mf://*.png -mf fps=19:type=png -ovc x264 -x264encopts pass=1:bitrate=1000 -oac copy -audiofile "..\data\audio.wav" -o "..\results\result.avi"
popd

pushd "tools\gource"
call gource.exe --hide filenames,dirnames --user-scale 2 --output-framerate 25 --stop-position 1 --highlight-all-users --seconds-per-day 1 --output-ppm-stream "..\..\results\resultgource.ppm" "..\..\data\gource.log"
popd

pushd "tools\nt"
call ffmpeg -y -b 9000K -f image2pipe -vcodec ppm -i "..\..\results\resultgource.ppm" -fpre "..\ll.ffpreset" -i "..\..\results\resultgource.ppm" -vcodec libx264 "..\..\results\resultgource.avi"

call mencoder "..\..\results\resultgource.avi" -ovc x264 -x264encopts pass=1:bitrate=10000 -ofps 19 -speed 2 -o "..\..\results\resultgource.fps"

call mencoder "..\..\results\resultgource.fps" -ovc x264 -x264encopts pass=1:bitrate=10000 -oac copy -audiofile "..\..\data\audio.wav" -o "..\..\results\resultgource.avi"
popd

del results\resultgource.ppm
del results\resultgource.fps
del data\actions.log
Данный скрипт выполняет следующие действия:
  1. сортирует его командой sort -k1 -t "|", но так как выполняем под виндой я ее в другой файл поместил, а то ругается в винде. Сортировать необходимо, так как gource работает корректно только с отсортированными данными.
  2. Запускает code_swarm с конфигом, который мы описали в my.config. В результате работы code_swarm генерирует большое колво png файлов
  3. Превращаем png файлы в видео по средствам mencoder, при этом прикрепляем звуковую дорожку, продолжительность видео можно регулировать параметром -mf fps=19:type=png, где 19 это по идее должно быть отношение между колвом файлов png и продолжительностью аудио трека в секундах. Но это мне не нравиться и я по этому использую приемлемое для меня значение.
  4. Потом запускается gource и выгружает результат в ppm. Предупреждаю файл будет очень большой в несколько гигабайт, так что путь выгрузки указывайте учитывая это.
  5. Затем перегоняем этот ppm файл при помощи утилиты ffmpeg в файл avi, но он получается очень большой и долгий. Ускоряем при помощи mencoder до тех же 19 fps. И затем снова запускаем mencoder для того чтоб прикрепить к нему звуковую дорожку. В результате получаем файл всего в несколько 10 мегабайт.
Конвертацию видео я подглядел во фреймворке showteamwork. После выполнения всех этапов в каталоге, который вы указали для резултатов работы (в моем случае это {code_swarm_home}/data/my/results), два видео ролика с визуализированной активностью.


Результат выполнения, с типом генерации md5

Вот мои результаты с репозитория jquery.

code_swarm



Что это значит (модель «притяжение»):
  • Объект притягивает к себе субъекты.
  • Объекты отталкивают друг дргуга
  • Объекты притягиваются друг к другу если используют один и тотже субъект
  • Субъекты которые часто используется увеличивается в размере и в яркости.
Тут самые яркие объекты это скорей всего фигурные скобочки которые часто встречаются в js скриптах. и как правило закрывающая бывает одна в строке, md5sum для них всех одинаковая. Так же md5sum может совпадать у нескольких различных строк. Но с этим можно смириться. Если вам нужна наиболее объективная картина то используйте тип генерации ch_code.

gource



Что это значит (модель «пчелы и соты»):
  • Объект — пчела
  • Субъект — соты
  • Пчелы строят соты, Зеленый луч — добавление, Красный — удаление.
Интересная картина получатся напоминает фрактал. Каждое ответвление это как будто каталог каждый листик на дереве это файл.


Итоги

Эта статья в первую очередь подчеркивает то, что с помощью визуализаторов code_swarm и gource можно обработать любую статистику, у которой есть переменная время, главное в правильном виде подать им эту статистику.
Все это конечно больше напоминает игру. Для меня по крайней мере этим оно и является. Скажем так, что данные вещи добавляют разнообразия в труд программиста.
Делайте клон моего репа
$ git clone git://github.com/artzub/code_swarm-gource-my-conf.git test
и выкладывайте свои результаты мне очень интересно.


Литература

upd: Все молчат, а в скрипте генерации лога для code_swarm есть ошибка, или опечатка даже не знаю как правильней сказать. В регулярном выражении /^+/ ошибка так как + надо экранировать вот так /^\+/. Странно то что в винде все отрабатывает запустил под debian awk ругнулся! =)
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 44: ↑42 и ↓2+40
Комментарии12

Публикации

Истории

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

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань