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

    Большинство разработчиков знакома с таким продуктом, как визуализатор 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 ругнулся! =)
    Поделиться публикацией

    Комментарии 12

      +2
      круто!)
        0
        А есть готовый скрипт выгрузки из svn?
          0
          тут вопрос только в том, чтоб выгрузить дифф из svn в нужном формате. С svn я не работал, к сожалению подсказать не могу. Если время появиться попробую поковырять в сторону svn.
            0
            На странице по продукту code.google.com/p/codeswarm/wiki/GeneratingAVideo рассказано как это сделать.
              0
              там показано как сделать в отношении юзер-файл.
              но спасибо посмотрю.
            –4
            Стоило бы употребить дефис, а не длинное тире. Порой, люди, начитавшись Лебедева, до конца не понимают прочитанного. И делают вот такие ошибки.
              0
              Это конечно не значительно, но в данном случае вы правы. Поправлю по всему тексту.
                0
                Информация полезная, с исправленной мелочью она стала еще более близка к идеалу.
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                ну да в gourсe добавили обработку xml, раньше не было.
                но то что у вас рисует gource — это классический вариант где отображается как пользователи работают над проектом оказывая воздействие на файлы, а у меня показано как в файлы добавляются строки.
                0
                Интересно было бы посмотреть на развитие ядра linux к примеру или другого проекта с большой историей разработки
                  0
                  сейчас обрабатываю mc
                  потом ядро линукса попробую
                  но сразу скажу в gource долгого видео не получиться, файл ppm для jquery с начала проекта и до 2007 занимал 60Гб. А вот в Code_swarm всю историю можно будет просмотреть.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое