• Главная
  • Карта сайта
Не найдено

Дослідження технологічного журналу 1С за допомогою регулярних виразів в блокноті

[Доповнення від 2017.11.23: Для тих, кому не цікаво читати - в репозиторії є готова portable збірка і викладаються готові скрипти.]

підготовка

Для початку підготуємо зручну і комфортне середовище виконання. Для цього будуть потрібні

  • Git Bash - емулятор консолі bash зі многоми додатковими програмами, найважливішою з який для нас є perl
  • Notepad ++ - найзручніший редактор коду, який разом з доповненням NppExec стає повноцінною IDE для наших комфортного виконання завдань аналізу

Після завантаження та установки даних програм необхідно налаштувати. І налаштовувати знадобиться тільки Notepad ++. Йдемо в Plugin Manager і встановлюємо додаток NppExec.

В його налаштуваннях вибираємо формат локалі (ConsoleOutput) - необхідно вибрати UTF-8 як для Input, так і для Output. (Plugins-> NppExec-> Console Output ..)

В налаштуваннях Notepad ++ обов'язково поставте заміну табів на прогалини, інакше нічого не буде нормально працювати. Краще, якщо табів в тексті скрипта взагалі не буде.

Тепер може писати наш перший скрипт.

Напишемо просту команду: ls -l, натиснемо F6 для запуску і налаштуємо його команди:

Напишемо просту команду: ls -l, натиснемо F6 для запуску і налаштуємо його команди:

npp_save - цей макрос збереже поточний скрипт перед виконанням
"C: \ Program Files \ Git \ bin \ bash.exe" "$ (FULL_CURRENT_PATH)" - запускаємо bash і передаємо в якості аргументу ім'я відкритого файлу

Тепер результат виконання операцій буде відображатися внизу, в додатковому вікні самого Notepad ++.

Обробка

Почнемо обробку. Припустимо, нас цікавить сумарна тривалість викликів до СУБД (SDBL) в розрізі контекстів. Вибирати інформацію ми будемо з технологічного журналу 1с (питання його налаштування тут розглядатися не будуть).

Так як повний текст одного події в ТЖ 1с може бути розбитий на кілька рядків, то нам спочатку потрібно склеїти всі рядки, що належать однаковим подіям, щоб в подальшому обробляти їх вже через підрядник.

Я реалізував такий алгоритм склеювання рядків одного події:

  1. Замінюємо всі перенесення рядка на пробіл (в інших випадках це може бути будь-яка необхідна послідовність)
  2. Якщо початок рядка відповідає шаблоном початку рядка запису, то ставимо перенесення рядка перед цим рядком.

perl -ne '{$ _ = ~ s / \ r * \ n / / g; # \ R * \ n в пробіл $ _ = "\ r \ n". $ _ If (/ ^ \ d \ d: \ d \ d \. \ D + /); # Якщо рядок починається з заголовка запису 1с рядок почнеться з перенесення print $ _;} '

Таким чином ми отримаємо список повного тексту всіх подій ТЖ.

Після цього відріжемо всю інформацію від початку події до його тривалості:

$ _ = ~ S / ^ \ d {2}: \ d {2} \. \ D + - //;

і приберемо весь текст від SDBL до контексту

$ _ = ~ S /, SDBL. * Context = / \ @ SDBL \ @ /;

Після цих нехитрих маніпуляцій події на виході придбають такий вигляд:

(тривалість події) @ (назви події-SDBL) @ (і його контекст)

Невелике пояснення по використовуваних в Perl конструкцій:

  • команда perl -ne означає лише тільки виконати програму, зазначену в лапках і передавати їй на вхід рядки на вхід.
  • прийняті рядки потрапляють в системну змінну Perl - $ _
  • операція $ _ = ~ s / 123/321 / g; є ні що інше, як привласнення змінній $ _ результатів заміни вмісту $ _ відповідно до регулярним виразом s / 123/321 / g. В даному контексті 123 змінюється на 321.

Тепер за допомогою AWK можна підсумувати тривалість і кількість подій по контексту.

Результуючий скрипт буде приблизно таким:

cat //server/c$/1clog/sdbl/rphost_*/*.log | \ Perl -ne '{$ _ = ~ s / \ xef \ xbb \ xbf // g; #BOM $ _ = ~ s / \ r * \ n / / g; # \ R * \ n в пробіл $ _ = ~ s / \ s + / / g; # І багато прогалин - в 1 $ _ = "\ r \ n". $ _ If (/ ^ \ d \ d: \ d \ d \. \ D + /); # Якщо рядок починається з заголовка запису 1с рядок почнеться з перенесення print $ _;} '| \ Perl -ne 'if ((/,SDBL.*Context =. {3} /)) {# щодо подій SDBL $ _ = ~ s / ^ \ d {2}: \ d {2} \. \ D + - //; # Прибрати все початку рядка до тривалості операції $ _ = ~ s /, SDBL. * Context = / \ @ SDBL \ @ /; # Забираю непотрібну інформацію між, SDBL і Context = $ _ = ~ s / \ x27 // g; # Апострофи теж все в сад print $ _; } '| \ Awk -F '@' '{sum [$ 3] + = $ 1; # Підсумовують тривалість ttl + = $ 1; # Загальна тривалість cnt [$ 3] + = 1; # Підсумовують кількість ct + = 1; # І загальна кількість збільшується} END {printf "% d% s% '\' 'd% s%' \ '' d% s% d \ n", ttl, "===== SDBL TOTAL (ms)" , ttl / 1000, "COUNT:", ct, "AVG (ms):", ttl / ct / 1000 for (i in sum) {printf "% d% s% '\' 'd% s%' \ ' '.2f% s%' \ '' d% s% '\' '. 2f% s%' \ '' d% s \ n ", sum [i]," TOTAL (ms): ", sum [i ] / 1000, "TOTAL (ms)%:", sum [i] / ttl * 100, "COUNT:", cnt [i], "COUNT%:", cnt [i] / ct * 100, "AVG ( ms): ", (cnt [i]> 0? sum [i] / cnt [i] / 1000: sum [i] / 1000)," CONTEXT = "i}} '| \ Sort -rnb | \ Head -n 11 | \ Perl -pe 's / \ d + \ s / \ r \ n /; # Перше число - тривалість для сортування міняємо на переноси рядків для зручності сприйняття '

Невелике пояснення по використовуваних в AWK конструкціям:

  • -F '@' - означає що як роздільник полів виступає символ @. Роздільником можна призначити як символ, так і послідовність.
  • значення між роздільниками в awk потрапляють в змінні службові змінні $ 1, $ 2, $ 3 ітд. (В залежності від кількості полів). В $ 0 приходить вся рядок, без розбивки по розділювачам.
  • команда printf в першому параметрі визначаємо типи виведених значень і їх форматування, так:
    % s - рядок
    % D - ціле,% '\' 'd означає угруповання по 3 частин цілого числа (123456 = 123 456, для зручності сприйняття)
    % F - float,% '\' '. 2f означає угруповання по 3 частин числа з округленням до двох знаків після коми (+123456,1234 = 123 456,12 для зручності сприйняття)

2f означає угруповання по 3 частин числа з округленням до двох знаків після коми (+123456,1234 = 123 456,12 для зручності сприйняття)

Таким нехитрим чином, ми можемо побачити, що 2,46% звернень до СУБД контексту "ДінаміческійСпісок.ПолучітьДанние: ЖурналДокументов.КадровиеДокументи.Форма.ФормаСпіска.Реквізіт.Спісок" створили 60,36% відсотків навантаження.

пірнаємо глибше

Так як в записах журналів реєстрації 1с не міститься інформація про годині і датою, а мені дуже б хотілося використовувати для відборів і їх, то спробуємо отримати і ці дані:

grep -r ". *" -H /c/rphost_*/*.log

В такому форматі команда grep буде виводити перед кожним рядком ім'я файлу, в дорозі і імені якого міститься додаткова інформація:

Але, ім'я файлу буде виводитися і перед склеюваними рядками. Значить потрібно прибрати його з склеюваних рядків. І, для початку, змінити формат рядка події на більш читабельний.

Тут на допомогу знову приходить Perl зі своїми потужними регулярними виразами.

grep -r ". *" -H /c/rphost_*/*.log | \ Perl -ne '{$ _ = ~ s / \ xef \ xbb \ xbf // g; #BOM $ _ = ~ s / \ r * \ n / / g; # \ R * \ n $ _ = ~ s / \ x27 // g; # Прибираю апострофи $ _ = ~ s / \ s + / / g; # Повертаю послідовності прогалин в один if (/ \ d \ d: \ d \ d \. \ D + /) {while (s /^.*_ (\ d +) \ / (\ d {2}) (\ d { 2}) (\ d {2}) (\ d {2}) \. log \: (\ d +: \ d + \. \ d +) \ - (\ d +), (\ w +), (\ d +) / /) {$ _ = "\ r \ n". "dt = 20". $ 2. ".". $ 3. ".". $ 4. ", time =". $ 5. ":". $ 6. ", pid = ". $ 1.", dur = ". $ 7.", evnt = ". $ 8.", ukn = ". $ 9. $ _; }} Else {$ _ = ~ s /^.* log: //;} # з перенесених рядків просто витираю початок print $ _;} '| \ Head -n 150

Результатом такої обробки рядків буде зручний і читається вигляд:

Результатом такої обробки рядків буде зручний і читається вигляд:

Тепер стає можливим розділити поля по разделителю (тобто виводити тільки ті поля, що нам реально потрібні). Наприклад, необхідно вивести тільки дату, час і контексти всіх SCALL'ов

grep -r ". *" -H /c/rphost_*/*.log | \ Perl -ne '{$ _ = ~ s / \ xef \ xbb \ xbf // g; #BOM $ _ = ~ s / \ r * \ n / / g; # \ R * \ n $ _ = ~ s / \ x27 // g; # Прибираю апострофи $ _ = ~ s / \ s + / / g; # Повертаю багато прогалин в один if (/ \ d \ d: \ d \ d \. \ D + /) {while (s /^.*_ (\ d +) \ / (\ d {2}) (\ d { 2}) (\ d {2}) (\ d {2}) \. log \: (\ d +: \ d + \. \ d +) \ - (\ d +), (\ w +), (\ d +) / /) {$ _ = "\ r \ n". "dt = 20". $ 2. ".". $ 3. ".". $ 4. ", time =". $ 5. ":". $ 6. ", pid = ". $ 1.", dur = ". $ 7.", evnt = ". $ 8.", ukn = ". $ 9. $ _; }} Else {$ _ = ~ s /^.* log: //;} # з перенесених рядків просто витираю початок print $ _;} '| \ Perl -ne '{if (/ time =. * Evnt = SCALL. * Context = /) {for (split / (? = \, \ W +: * \ w * =) /, $ _) {$ _ = ~ s /, // g; print $ _. "[] [] []" if (/ dt = /); print $ _. "[] [] []" if (/ time = /); print $ _. "[] [] []" if (/ evnt = /); print $ _ if (/ Context = /); }}} '| \ Head -n 150

Результат буде вельми наочним:

Результат буде вельми наочним:

бритва Оккама

У представлених вище угрупованнях по полях все ще використовується AWK, хоча в самому Perl є не менше потужні можливості для оперування даними.

Можна прибрати і AWK, і сортування за допомогою команди sort -rnb, і висновок тільки 10 рядків за допомогою head.

Спробуємо ж тільки засобами Perl підрахувати кількість і тривалість подій SDBLв розрізі контекстів:

time grep -r ". *" -H /c/rphost_*/*.log | \ Perl -ne '{$ _ = ~ s / \ xef \ xbb \ xbf // g; #BOM $ _ = ~ s / \ r * \ n / / g; # \ R * \ n $ _ = ~ s / \ x27 // g; # Прибираю апострофи $ _ = ~ s / \ s + / / g; # Повертаю багато прогалин в один if (/ \ d \ d: \ d \ d \. \ D + /) {while (s /^.*_ (\ d +) \ / (\ d {2}) (\ d { 2}) (\ d {2}) (\ d {2}) \. log \: (\ d +: \ d + \. \ d +) \ - (\ d +), (\ w +), (\ d +) / /) {$ _ = "\ r \ n". "dt = 20". $ 2. ".". $ 3. ".". $ 4. ", time =". $ 5. ":". $ 6. ", pid = ". $ 1.", dur = ". $ 7.", evnt = ". $ 8.", ukn = ". $ 9. $ _; }} Else {$ _ = ~ s /^.* log: //;} # з перенесених рядків просто витираю початок print $ _;} '| \ Perl -ne '#perl вміє працювати як AWK if (/ dur = (\ d +), evnt = SDBL. * Context = (. *) /) {$ Dur_ttl + = $ 1/1000; $ Dur {$ 2} + = $ 1/1000; $ Cnt_ttl + = 1; $ Cnt {$ 2} + = 1; } END {printf ( "===== TIME TOTAL (ms):%. 2f COUNT:% d AVG (ms):%. 2f \ r \ n", $ dur_ttl, $ cnt_ttl, $ dur_ttl / $ cnt_ttl) ; # Формую заголовок foreach $ k (sort {$ dur {$ b} <=> $ dur {$ a}} keys% dur) {printf "[] [] [] TIME (ms):% d [] [] [ ] TIME (%):%. 2f [] [] [] COUNT:% d [] [] [] COUNT (%):%. 2f [] [] [] BY:% s \ r \ n ", $ dur {$ k}, $ dur {$ k} / $ dur_ttl * 100, $ cnt {$ k}, $ cnt {$ k} / $ cnt_ttl * 100, $ k; # Сортую масив по спадаючій тривалості і виводжу його last if ($ _ + = 1)> 10; # Але тільки перші 10 рядків}} '

Результатом буде дуже швидка обробка всього масиву ТЖ для отримання цікавить нас інформації.

Оцінити швидкість виконання завжди можна командою time на самому початку виконання.

Пояснення щодо конструкція Perl:

if (/ dur = (\ d +), evnt = SDBL. * Context = (. *) /) {# якщо у рядки є dur = \ d +, його evnt = SDBL І у нього є контекст $ dur_ttl + = $ 1/1000; $ Dur {$ 2} + = $ 1/1000; # Підсумовуємо тривалість - $ 1 (перші круглі дужки (\ d +)) по контексту - $ 2 (Context = (. *)) $ Cnt_ttl + = 1; $ Cnt {$ 2} + = 1; # Підсумовуємо кількість контекстів - $ 2 (Context = (. *))} Foreach $ k (sort {$ dur {$ b} <=> $ dur {$ a}} keys% dur) {# сортування масиву і обхід по всіх його ключам last if ($ _ + = 1)> 10; # Кожен прохід ми збільшуємо на 1 змінну $ _. Виходимо, якщо число проходів перевищило 10

оптимізація

Обробка (парсинг) ТЖ - надзвичайно ресурсоємних заняття. ТЖ запросто можуть досягати декількох десятків (а то й сотень) гігабайт. І вибірка інформації з них може займати ДУЖЕ тривалий час.

Тому весь код для парсинга повинен бути максимально ефективним.

У попередньому прикладі в першому виклику Perl склеюються всі рядки, навіть не відбираються в другому виклику (підрахунок). Тобто ми обробляємо в першому проході ті рядки, які не знадобляться в другому.

Трохи модифікуємо відбір для того, щоб склеювати рядки тільки по потрібному події. Справедливості заради варто помітити, що відразу все необхідне - SDBL з контекстом - ми відібрати не зможемо, тому що перед склеюванням рядків контекст може перебувати на різних рядках з подією.

echo "Тривалість подій SDBL в резрезе контекстів" time grep -r ". *" -H /c/v8/logs/*/*.log | \ Perl -ne 's / \ xef \ xbb \ xbf //; #BOM - обов'язково на початку, інакше з певой рядком будуть проблеми if (/ log: \ d \ d: \ d \ d \. \ D + - \ d +, (\ w +), /) {# якщо в рядку є ідентифікатор початку рядки і це наш тип події if ($ 1 eq "SDBL") {# первинний відбір щодо подій s / \ s + / / g; # Повертаю багато прогалин в один, і перенесення рядка теж тут полетить в пробіл if (s /^.*_ (\ d +) \ / (\ d {2}) (\ d {2}) (\ d {2}) (\ d {2}) \. log \: \ s * (\ d +: \ d + \. \ d +) \ - (\ d +), (\ w +), (\ d +) //) {$ _ = " \ r \ n "." dt = 20 ". $ 2.". ". $ 3.". ". $ 4.", time = ". $ 5.": ". $ 6.", pid = ". $ 1.", dur = ". $ 7.", evnt = ". $ 8.", ukn = ". $ 9. $ _; } $ F = 1; } Else {$ f = 0}; } Elsif ($ f) {# якщо наше подія, то обробляємо цю висячу рядок s /^.* log: //; # З перенесених рядків просто витираю початок s / \ s + / / g; # Повертаю багато прогалин в один, і перенесення рядка теж тут полетить в пробіл} if ($ f) {s / \ x27 // g; # Прибираю апострофи print; } END {print "\ r \ n"} # треба поставити, щоб останній рядок в обробку потрапила '| \ Perl -ne '#perl вміє працювати як AWK if (/ dur = (\ d +), evnt = SDBL. * Context = (. *) $ /) {$ Dur_ttl + = $ 1/1000; $ Dur {$ 2} + = $ 1/1000; $ Cnt_ttl + = 1; $ Cnt {$ 2} + = 1; } END {printf ( "===== TIME TOTAL (ms):%. 2f COUNT:% d AVG (ms):%. 2f \ r \ n", $ dur_ttl, $ cnt_ttl, $ dur_ttl / $ cnt_ttl) ; # Формую заголовок foreach $ k (sort {$ dur {$ b} <=> $ dur {$ a}} keys% dur) {last if ($ _ + = 1)> 10; # Але тільки перші 10 рядків printf "$ _: [] [] [] TIME (ms):% d [] [] [] TIME (%):%. 2f [] [] [] COUNT:% d [] [] [] COUNT (%):%. 2f [] [] [] BY: $ k \ r \ n ", $ dur {$ k}, $ dur {$ k} / ($ dur_ttl> 0? $ dur_ttl : 1) * 100, $ cnt {$ k}, $ cnt {$ k} / ($ cnt_ttl> 0? $ cnt_ttl: 1) * 100; # Сортую масив по спадаючій тривалості і виводжу його}} '

Тепер оцінимо швидкість виконання:

Тепер оцінимо швидкість виконання:

Швидкість відбору тієї ж самої інформації збільшилася в 5 разів!

замість висновку

Сподіваюся, дана стаття допоможе Вам розкрити нові можливості та вимірювання аналізу ТЖ 1с.

Якщо якісь моменти залишилися незрозумілими, то напишіть про них в коментарях. Я постараюся описати їх більш детально.

Робочі приклади скриптів парсинга ТЖ за описаними вище методиками доступні в відкритому репозиторії Bash1C . Приєднуйтесь!

Cnt [i]> 0?
Dur_ttl> 0?
Cnt_ttl> 0?
Провайдеры:
  • 08.09.2015

    Batyevka.NET предоставляет услуги доступа к сети Интернет на территории Соломенского района г. Киева.Наша миссия —... 
    Читать полностью

  • 08.09.2015
    IPNET

    Компания IPNET — это крупнейший оператор и технологический лидер на рынке телекоммуникаций Киева. Мы предоставляем... 
    Читать полностью

  • 08.09.2015
    Boryspil.Net

    Интернет-провайдер «Boryspil.net» начал свою работу в 2008 году и на данный момент является одним из крупнейших поставщиков... 
    Читать полностью

  • 08.09.2015
    4OKNET

    Наша компания работает в сфере телекоммуникационных услуг, а именно — предоставлении доступа в сеть интернет.Уже... 
    Читать полностью

  • 08.09.2015
    Телегруп

    ДП «Телегруп-Украина» – IT-компания с 15-летним опытом работы на рынке телекоммуникационных услуг, а также официальный... 
    Читать полностью

  • 08.09.2015
    Софтлинк

    Высокая скоростьМы являемся участником Украинского центра обмена трафиком (UA — IX) с включением 10 Гбит / сек... 
    Читать полностью