#!/bin/bash

# lperf - скрипт, автоматизирующий анализ блокировок с помощью утилиты dprof.
# Основные функции скрипта:
#  - подсчёт количества различных блокировок;
#  - выявление функций, в которых эти блокировки возникают чаще всего;
#  - получение файлов с инструментированным дизассемблером, по которому
#    можно увидеть, в каких инструкциях блокировки возникают чаще всего.
#
# Запуск скрипта выполняется следующим образом:
#   lperf [options] PROGRAM [ARGS]
# где
# [options] - опции скрипта lperf, их полный список можно получить командой lperf --help ;
# PROGRAM - профилируемая программа;
# [ARGS] - опции профилируемой программы.
#
# Кроме опций, lperf воспринимает также ряд переменных окружения,
# их полный список можно получить командой lperf --env
#
# В процессе своей работы скрипт создаёт каталог вида: analyze.<prog>,
# в который помещает логи, инструментированные дизассемблеры
# и прочие файлы с результатами своей работы.
#
# Copyright (c) 1992-2024 AO "MCST". All rights reserved.


################################################################################
# Секция пользовательских параметров, которые задаются опциями
################################################################################

# Строка запуска основной программы
RUN_PROG_CMD=""

# Команды, которые будут запускаться до/после каждого запуска теста:
INIT_BEFORE_RUN_CMD=""
CLEAR_AFTER_RUN_CMD=""

# Рабочий каталог с логами, дизассемблером и отчётами
WORK_DIR=""

# Интервал сэмплирования (параметр запуска dprof -e)
DPROF_INTERVAL_DEFAULT="1000000"
DPROF_INTERVAL="$DPROF_INTERVAL_DEFAULT"
# Минимальный допустимый интервал сэмплирования для большинства событий
DPROF_INTERVAL_MIN="1000"
# Интервал сэмплирования задан пользователем? {0, 1}
IS_USER_DPROF_INTERVAL=0

# Запускать анализ блокировок BUB_E2 для отдельных каналов?
IS_CHANNEL_ANALYSIS=0

# Автоматический режим анализа? {0, 1}
# В этом режиме скрипт сам определяет, какие блокировки подсчитывать,
# и для каких получать профиль. Это режим по умолчанию.
IS_AUTO_ANALYSIS=1

# Подробные печати на экран? {0, 1}
IS_VERBOSE=0

# Список событий, для которых необходимо подсчитать их кол-во
USER_CNT_EVENTS_LIST=""

# Список событий, для которых необходимо получить профиль
USER_PROF_EVENTS_LIST=""

# На каком языке печатаем сообщения? {0 - русский, 1 - english}
IS_LANG_ENG=1



################################################################################
# Секция внутренних параметров                                                 #
################################################################################

# Краткое название скрипта
SCRIPT_NAME=`basename $0`
SCRIPT_PATH=`dirname $0`
# Версия скрипта
SCRIPT_VERSION="0.8.2"

# Утилиты
DPROF="$SCRIPT_PATH/dprof"
LDIS="/opt/mcst/bin/ldis"
PROF_PARSER="$SCRIPT_PATH/lperf_unite_profiles.pl"
DPROF_TO_PTRACE="$SCRIPT_PATH/dprof2ptrace.sh"
#DPROF="/opt/mcst/bin/dprof"
#EXTRACTOR="$SCRIPT_PATH/extractor.sh"

# Лог работы данного скрипта - должен быть пустым в начале работы
# (чтобы туда не пыталась записать функция die)
ANALYZER_LOG=""
# Файл с выводом основной программы (плюс сообщения от dprof)
RUN_PROG_LOG=
# Лог работы команды INIT_BEFORE_RUN_CMD
INIT_LOG=""
# Лог работы команды CLEAR_AFTER_RUN_CMD
CLEAR_LOG=""
# Файл со счётчиками, заданными пользователем
USER_CNT_EVENTS_FILE=""
# Абсолютный путь к исполняемому файлу
TEST_BIN=""

# Начальные значения внутренних переменных
PERCENT_BUB_E2=0
PERCENT_IB_NO_COMMAND=0
PERCENT_BUB_E2_MEM=0
PERCENT_BUB_E2_OPND=0
PERCENT_BUB_B=0
PERCENT_STALL_AAU=0
# Эти блокировки хранятся в кол-ве тиков:
EVENT_TICKS=0
EVENT_EXEC=0
EVENT_BUB_E2=0
EVENT_IB_NO_COMMAND=0
EVENT_BUB_B=0
EVENT_BUB_B_NOP=0
EVENT_BUB_E2_MEM=0
EVENT_BUB_E2_OPND=0
EVENT_BUB_E2_ALU0_MEM=0
EVENT_BUB_E2_ALU1_MEM=0
EVENT_BUB_E2_ALU2_MEM=0
EVENT_BUB_E2_ALU3_MEM=0
EVENT_BUB_E2_ALU4_MEM=0
EVENT_BUB_E2_ALU5_MEM=0
EVENT_BUB_E2_ALU0_OPND=0
EVENT_BUB_E2_ALU1_OPND=0
EVENT_BUB_E2_ALU2_OPND=0
EVENT_BUB_E2_ALU3_OPND=0
EVENT_BUB_E2_ALU4_OPND=0
EVENT_BUB_E2_ALU5_OPND=0
EVENT_STALL_AAU=0
EVENT_DAM_BELL=0
EVENT_RBRANCH=0
EVENT_BUB_B_RF_SPILLFILL=0
MAX_BUB=""
# Длительность различных событий.
# В случае переменной длительности, используется максимальное значение.
TIME_BUB_E2=4
TIME_STALL_AAU=4
TIME_DAM_BELL=16
TIME_RBRANCH=16
# Признак поддержки событий BUB_E2_MEM и BUB_E2_OPND (появились в Elbrus V3) {0,1}
IS_BUB_E2_MEM=1

# Кол-во запусков через dprof
DPROF_RUNS_NUM=0
DPROF_TOTAL_TIME=0
DPROF_MIN_TIME=0
DPROF_MAX_TIME=0

# Минимальное кол-во событий, для которого будем собирать профиль
MIN_EVENT_NUM=4000


################################################################################
# Секция цветных рюшечек                                                       #
################################################################################

# По умолчанию используем цвета
if [ -z $LPERF_USE_COLORS ] || \
   [ "$LPERF_USE_COLORS" != 0 ]
then
    # Используем цвета
    export BLANK="\033[00m"

    if [ "$GREEN" = "" ]
    then
        export GREEN="\033[01;32m"
    fi

    if [ "$GREEN_NORMAL" = "" ]
    then
        export GREEN_NORMAL="\033[00;32m"
    fi

    if [ "$YELLOW" = "" ]
    then
        export YELLOW="\033[01;33m"
    fi

    if [ "$BLUE" = "" ]
    then
        export BLUE="\033[01;34m"
    fi

    if [ "$RED" = "" ]
    then
        export RED="\033[01;31m"
    fi

    if [ "$WHITE" = "" ]
    then
        export WHITE="\033[01;37m"
    fi
else
    # Не используем цвета
    export BLANK=
    export GREEN=
    export GREEN_NORMAL=
    export YELLOW=
    export BLUE=
    export RED=
    export WHITE=
fi

OK="${GREEN}ok${BLANK}"
FAILED="${RED}failed${BLANK}"
WARNING="${YELLOW}warning${BLANK}"
ERROR="${RED}error${BLANK}"


################################################################################
# Секция внутренних функций                                                    #
################################################################################

# Внутренняя ошибка. Особый случай, когда сообщение выдаётся только на английском.
# Аргументы:
#  $1 - сообщение об ошибке
internal_error()
{
    echo -e "\n$SCRIPT_NAME: internal error: $@"
    exit 2
} # internal_error()

# Фильтр для удаления спецсимволов управления цветом
uncolor()
{
    sed 's/\\033\[01;3.m\|\\033\[00m//g'
} # uncolor()

# Фильтр для преобразования кодировки сообщения
conv_msg_encoding()
{
    if [ "$LPERF_USE_ENCONV" == 0 ]
    then
        tee
    else
        enconv -L russian
    fi
} # conv_msg_encoding()

# Печать сообщения. Все словесные сообщения для пользователей должны выводиться
# через эту функцию, поскольку она выбирает язык и умеет перекодировать сообщения
# в нужную кодировку.
# Аргументы:
#  -log - напечатать сообщение также в лог-файл
#  -n   - не печатать символ перевода на новую строку
#  $1   - сообщение на английском
#  $2   - сообщение на русском
print_msg()
{
    PAR_IS_LOG=0
    PAR_N=

    if [ "$1" == "-log" ]
    then
        PAR_IS_LOG=1
        shift
    fi

    if [ "$1" == "-n" ]
    then
        PAR_N="-n"
        shift
    fi

    PAR_MSG_ENG=$1
    PAR_MSG_RUS=$2
    [ "$PAR_MSG_RUS" == "" ] && internal_error "russian message is not set, english message: '$PAR_MSG_ENG'"

    if [ $IS_LANG_ENG == 1 ]
    then
        echo -e $PAR_N "$PAR_MSG_ENG"
        [ $PAR_IS_LOG == 1 ] && echo $PAR_N "$PAR_MSG_ENG" | uncolor >> $ANALYZER_LOG
    else
        # По умолчанию перекодируем сообщения на русском языке,
        # чтобы они читались при настройках локали, отличных от UTF-8
        echo -e $PAR_N "$PAR_MSG_RUS" | conv_msg_encoding

        # Вывод в лог всегда делаем без перекодировки - чтобы сообщения скрипта
        # и различных утилит в логе были в одной кодировке (UTF-8)
        [ $PAR_IS_LOG == 1 ] && echo $PAR_N "$PAR_MSG_RUS" | uncolor >> $ANALYZER_LOG
    fi
} # print_msg()

# Печать ошибки на экран и в лог
# Аргументы:
#  $1 - сообщение на английском
#  $2 - сообщение на русском
print_error()
{
    PAR_MSG_ENG=$1
    PAR_MSG_RUS=$2

    echo
    print_msg "$SCRIPT_NAME: ${RED}error${BLANK}: $PAR_MSG_ENG" \
              "$SCRIPT_NAME: ${RED}ошибка${BLANK}: $PAR_MSG_RUS" >&2

    if [ "$ANALYZER_LOG" != "" ]
    then
        echo >> $ANALYZER_LOG
        print_msg "error: $PAR_MSG_ENG" \
                  "ошибка: $PAR_MSG_RUS" >> $ANALYZER_LOG
    fi
} # print_error()

# Печать предупреждения на экран и в лог
# Аргументы:
#  $1 - сообщение на английском
#  $2 - сообщение на русском
print_warning()
{
    PAR_MSG_ENG=$1
    PAR_MSG_RUS=$2

    print_msg "$SCRIPT_NAME: ${YELLOW}warning${BLANK}: $PAR_MSG_ENG" \
              "$SCRIPT_NAME: ${YELLOW}предупреждение${BLANK}: $PAR_MSG_RUS"

    if [ "$ANALYZER_LOG" != "" ]
    then
        print_msg "warning: $PAR_MSG_ENG" \
                  "предупреждение: $PAR_MSG_RUS" >> $ANALYZER_LOG
    fi
} # print_warning()

# Аварийное завершение работы скрипта.
# Аргументы:
#  $1 - сообщение на английском
#  $2 - сообщение на русском
die()
{
    print_error "$1" "$2"
    exit 1
} # die()

# Выдача ошибки о неизвестном событии.
# Аргументы:
#  $1 - название события
unknown_event_error()
{
    print_error "unknown event '$1'" \
                "неизвестное событие '$1'"
    print_msg "use option '--list' to see supported events" \
              "используйте опцию '--list' для печати поддерживаемых событий"
    exit 1
} # unknown_event_error()

# Печать всех поддерживаемых опций скрипта
print_supported_options()
{
    print_msg \
"usage: $SCRIPT_NAME [options] program [arguments]

$SCRIPT_NAME options:
--work-dir <dir>  - use work directory <dir>
--interval <n>    - set dprof sampling interval <n> (default $DPROF_INTERVAL_DEFAULT)
--channels        - analyze events by channels, where applicable
--count <events>  - count events <events>
--prof <events>   - make profiles for events <events>
--list            - print all supported events with short description
--init-cmd <cmd>  - set command for initialization before each program run
--clear-cmd <cmd> - set command for cleanup after each program run
--dprof <prog>    - use dprof <prog>
--ldis <prog>     - use ldis <prog>
--ru              - use russian language for output
-v                - verbose mode
--version         - print the version number
--env             - print supported environment variables
--help            - print supported options" \
\
"синтаксис: $SCRIPT_NAME [опции] программа [аргументы]

$SCRIPT_NAME понимает следующие опции:
--work-dir <dir>  - использовать рабочий каталог <dir>
--interval <n>    - установить интервал сэмплирования <n> (по умолчанию $DPROF_INTERVAL_DEFAULT)
--channels        - включить поканальный анализ событий, где возможно
--count <events>  - подсчитать события <events>
--prof <events>   - собрать профили для событий <events>
--list            - напечатать все поддерживаемые события и их краткое описание
--init-cmd <cmd>  - установить команду для исполнения перед каждым запуском программы
--clear-cmd <cmd> - установить команду для исполнения после каждого запуска программы
--dprof <prog>    - использовать dprof <prog>
--ldis <prog>     - использовать ldis <prog>
--ru              - выводить все сообщения на русском языке
-v                - включить выдачу второстепенных комманд
--version         - напечатать версию утилиты
--env             - напечатать поддерживаемые переменные окружения
--help            - напечатать поддерживаемые опции"
    exit 0
} # print_supported_options()

# Печать всех поддерживаемых переменных окружения скрипта
print_supported_env()
{
    print_msg \
"$SCRIPT_NAME environment variables:
LPERF_INTERVAL=<n>    - set dprof sampling interval <n>
LPERF_INIT_CMD=<cmd>  - set command for initialization before each program run
LPERF_CLEAR_CMD=<cmd> - set command for cleanup after each program run
LPERF_DPROF=<prog>    - use dprof <prog>
LPERF_LDIS=<prog>     - use ldis <prog>
LPERF_USE_COLORS=0    - disable color output
LPERF_USE_ENCONV=0    - disable passing russian messages through enconv
LPERF_LANG=<lang>     - set language (en - english, ru - russian)" \
\
"$SCRIPT_NAME понимает следующие переменные окружения:
LPERF_INTERVAL=<n>    - установить интервал сэмплирования <n>
LPERF_INIT_CMD=<cmd>  - установить команду для исполнения перед каждым запуском программы
LPERF_CLEAR_CMD=<cmd> - установить команду для исполнения после каждого запуска программы
LPERF_DPROF=<prog>    - использовать dprof <prog>
LPERF_LDIS=<prog>     - использовать ldis <prog>
LPERF_USE_COLORS=0    - выключить использование цветов в сообщениях
LPERF_USE_ENCONV=0    - выключить фильтрацию сообщений на русском через утилиту enconv
LPERF_LANG=<lang>     - задать язык сообщений (en - английский, ru - русский)"
    exit 0
} # print_supported_env()

# Печать версии утилиты
print_version()
{
    CUR_YEAR=`date '+%Y'`

    if [ "$CUR_YEAR" -le 2021 ]
    then
        CUR_YEAR=2023
    fi

    echo "$SCRIPT_NAME $SCRIPT_VERSION"
    echo "Copyright (C) 2021-$CUR_YEAR AO \"MCST\""
    exit 0
} # print_version()

# Печать всех поддерживаемых событий
print_supported_events()
{
    print_msg \
"$SCRIPT_NAME supported events:
TICKS            - CPU ticks
EXEC             - executed wide instructions
BUB_E2           - phase E2 launches
BUB_E2_MEM       - waiting data from memory (loads and mova)
BUB_E2_OPND      - waiting result of arithmetic instruction
IB_NO_COMMAND    - waiting code from memory
BUB_B            - phase B launches
BUB_B_NOP        - executed empty instructions (NOP)
DAM_BELL         - DAM activations
RBRANCH          - jumps to DAM compensating code
BUB_B_RF_SPILLFILL - spill/fills of register file

The following events are only used in --channels mode:
BUB_E2_ALU0_MEM  - BUB_E2_MEM in channel 0
BUB_E2_ALU1_MEM  - BUB_E2_MEM in channel 1
BUB_E2_ALU2_MEM  - BUB_E2_MEM in channel 2
BUB_E2_ALU3_MEM  - BUB_E2_MEM in channel 3
BUB_E2_ALU4_MEM  - BUB_E2_MEM in channel 4
BUB_E2_ALU5_MEM  - BUB_E2_MEM in channel 5
BUB_E2_ALU_MEM   - all six above

BUB_E2_ALU0_OPND - BUB_E2_OPND in channel 0
BUB_E2_ALU1_OPND - BUB_E2_OPND in channel 1
BUB_E2_ALU2_OPND - BUB_E2_OPND in channel 2
BUB_E2_ALU3_OPND - BUB_E2_OPND in channel 3
BUB_E2_ALU4_OPND - BUB_E2_OPND in channel 4
BUB_E2_ALU5_OPND - BUB_E2_OPND in channel 5
BUB_E2_ALU_OPND  - all six above" \
\
"$SCRIPT_NAME поддерживает следующие события:
TICKS            - такты процессора
EXEC             - выполненные широкие команды
BUB_E2           - запуски фазы E2
BUB_E2_MEM       - ожидание данных из памяти (чтения и mova)
BUB_E2_OPND      - ожидание результата арифметической инструкции
IB_NO_COMMAND    - ожидание кода из памяти
BUB_B            - запуски фазы B
BUB_B_NOP        - выполненные пустые команды (NOP)
DAM_BELL         - звонки DAM
RBRANCH          - переходы на компенсирующий код DAM
BUB_B_RF_SPILLFILL - spill/fills регистрового файла

Следующие события используются только в режиме --channels:
BUB_E2_ALU0_MEM  - BUB_E2_MEM в канале 0
BUB_E2_ALU1_MEM  - BUB_E2_MEM в канале 1
BUB_E2_ALU2_MEM  - BUB_E2_MEM в канале 2
BUB_E2_ALU3_MEM  - BUB_E2_MEM в канале 3
BUB_E2_ALU4_MEM  - BUB_E2_MEM в канале 4
BUB_E2_ALU5_MEM  - BUB_E2_MEM в канале 5
BUB_E2_ALU_MEM   - все 6 событий выше

BUB_E2_ALU0_OPND - BUB_E2_OPND в канале 0
BUB_E2_ALU1_OPND - BUB_E2_OPND в канале 1
BUB_E2_ALU2_OPND - BUB_E2_OPND в канале 2
BUB_E2_ALU3_OPND - BUB_E2_OPND в канале 3
BUB_E2_ALU4_OPND - BUB_E2_OPND в канале 4
BUB_E2_ALU5_OPND - BUB_E2_OPND в канале 5
BUB_E2_ALU_OPND  - все 6 событий выше"

    exit 0
} # print_supported_events()

# Определяет, является ли поданный аргумент целым числом
is_number()
{
    if [ "$1" = "" ] || \
       [ `expr "$1" : '[0-9]*'` != `expr length "$1"` ]
    then
        # Аргумент не является числом
        return 1
    fi

    return 0
} # is_number()

# Печать пустой строки
print_blank_line()
{
    echo | tee -a $ANALYZER_LOG
} # print_blank_line()

# Печать разделительной линии
print_line()
{
    echo "-----------------------------------------------------------------" | tee -a $ANALYZER_LOG
} # print_line()

# Проверка наличия бинарника задачи
# Создание каталога с отчётами и логами
init_work()
{
    # Получаем имя бинарника (обрезаем путь)
    PROGRAM=${RUN_PROG_CMD%% *}
    BIN_NAME=`basename "$PROGRAM"`

    TEST_BIN=`which "$PROGRAM" 2> /dev/null`
    [ "$TEST_BIN" != "" ] || die "program '$PROGRAM' not found" \
                               "программа '$PROGRAM' не найдена"

    # Переделываем ссылку в абсолютный путь
    # Переделываем относительный путь в абсолютный (bug 147701)
    TEST_BIN=`readlink -f $TEST_BIN`
    [ "$TEST_BIN" != "" ] || die "failed to obtain absolute path to program '$PROGRAM'" \
                                 "не удалось получить абсолютный путь к программе '$PROGRAM'"

    which "$TEST_BIN" &> /dev/null || die "program '$PROGRAM' not found" \
                                        "программа '$PROGRAM' не найдена"

    # Проверяем наличие различных утилит
    [ -s "$DPROF" ] || die "dprof '$DPROF' not found" \
                           "утилита dprof '$DPROF' не найдена"
    [ -s "$LDIS" ] || die "ldis '$LDIS' not found" \
                          "утилита ldis '$LDIS' не найдена"
    IS_DYN_LIB_WARNING=0

    # Из-за изменения dprof2ptrace extractor.sh уже не нужен
    # - динлибы профилируются по умолчанию. Закомментируем пока.
    #if [ ! -s "$EXTRACTOR" ]
    #then
    #    print_warning "script '$EXTRACTOR' not found" \
    #                  "скрипт '$EXTRACTOR' не найден"
    #    IS_DYN_LIB_WARNING=1
    #    EXTRACTOR=
    #fi
    # Следующие скрипты являются вспомогательными, поэтому при их отсутствии
    # не ломаемся, а лишь выключаем профилирование динамических библиотек
    if [ ! -s "$PROF_PARSER" ]
    then
        print_warning "script '$PROF_PARSER' not found" \
                      "скрипт '$PROF_PARSER' не найден"
        IS_DYN_LIB_WARNING=1
        PROF_PARSER=
    fi
    if [ $IS_DYN_LIB_WARNING == 1 ]
    then
        print_warning "dynamic libraries cannot be profiled" \
                      "динамические библиотеки не могут быть спрофилированы"
    fi

    # Запоминаем время начала
    SCRIPT_START_TIME=`date "+%s"`

    # Если рабочий каталог не был задан опцией, то задаём его имя автоматически
    if [ "$WORK_DIR" == "" ]
    then
        WORK_DIR_SHORT="analyze.$BIN_NAME"
        WORK_DIR="`pwd`/$WORK_DIR_SHORT"
    else
        WORK_DIR_SHORT="$WORK_DIR"
    fi

    if [ ! -d "$WORK_DIR" ]
    then
        # Создаём рабочий каталог
        mkdir -p "$WORK_DIR" || die "failed to create work dir '$WORK_DIR'" \
                                    "не удалось создать рабочий каталог '$WORK_DIR'"
    fi

    # В строку запуска добавляем перенаправление выдачи в файл
    RUN_PROG_LOG="$WORK_DIR/run_prog.out"
    RUN_PROG_CMD+=" &> $RUN_PROG_LOG"
    echo -n > $RUN_PROG_LOG

    # Полный и короткий путь к основному логу
    ANALYZER_LOG="$WORK_DIR/lperf.log"
    ANALYZER_LOG_SHORT="$WORK_DIR_SHORT/lperf.log"

    # Пишем в лог о начале работы
    CUR_TIME=`date '+%G.%m.%d %k:%M:%S'`
    echo -e "\n\n==================================================" >> $ANALYZER_LOG
    print_msg -log "$SCRIPT_NAME: ANALYZER STARTED $CUR_TIME" \
                   "$SCRIPT_NAME: НАЧИНАЕМ АНАЛИЗ $CUR_TIME"
    echo "analyzing program '$TEST_BIN'" >> $ANALYZER_LOG
    ls -l "$TEST_BIN" >> $ANALYZER_LOG
    print_blank_line

    INIT_LOG="$WORK_DIR/init_before.out"
    CLEAR_LOG="$WORK_DIR/clear_after.out"
    INIT_LOG_SHORT="$WORK_DIR_SHORT/init_before.out"
    CLEAR_LOG_SHORT="$WORK_DIR_SHORT/clear_after.out"
    echo -n > $INIT_LOG
    echo -n > $CLEAR_LOG

} # init_work()

# Завершение работы анализатора
finish_work()
{
    print_blank_line
    print_line

    # Подсчитываем общее время работы утилиты
    SCRIPT_END_TIME=`date "+%s"`
    SCRIPT_DIFF_TIME=$((SCRIPT_END_TIME - SCRIPT_START_TIME))

    if [ $DPROF_RUNS_NUM -gt 1 ]
    then
        # Печатаем статистику по запускам задачи через dprof
        DPROF_AVG_TIME=`echo $DPROF_TOTAL_TIME $DPROF_RUNS_NUM | awk '{printf( "%.1f", 1.0 * $1 / $2);}'`
        print_msg -log \
"total $DPROF_RUNS_NUM program runs performed in $SCRIPT_DIFF_TIME s:
    average time $DPROF_AVG_TIME s
    minimum time $DPROF_MIN_TIME s
    maximum time $DPROF_MAX_TIME s" \
\
"выполнено $DPROF_RUNS_NUM запусков программы за $SCRIPT_DIFF_TIME с:
       среднее время $DPROF_AVG_TIME с
   минимальное время $DPROF_MIN_TIME c
  максимальное время $DPROF_MAX_TIME с"

        # Печатаем предупреждение в случае слишком большой дисперсии
        if [ $DPROF_MAX_TIME -gt 20 ]
        then
            # %.0f делает округление к ближайшему целому, как и требуется
            MIN_GOOD_TIME=`echo $DPROF_AVG_TIME | awk '{printf( "%.0f", 0.9 * $1);}'`
            MAX_GOOD_TIME=`echo $DPROF_AVG_TIME | awk '{printf( "%.0f", 1.1 * $1);}'`

            if [ $DPROF_MIN_TIME -lt $MIN_GOOD_TIME ] || \
               [ $DPROF_MAX_TIME -gt $MAX_GOOD_TIME ]
            then
                # Слишком большой разброс времени исполнения команды
                print_warning "dispersion is too high, results may be inconsistent" \
                              "разброс времени исполнения слишком велик, результаты могут быть противоречивыми"
            fi
        fi
    else
        print_msg -log "total analysis time $SCRIPT_DIFF_TIME s" \
                       "общее время анализа $SCRIPT_DIFF_TIME с"
    fi

    CUR_TIME=`date '+%G.%m.%d %k:%M:%S'`
    print_msg -log "$SCRIPT_NAME: ANALYZER FINISHED $CUR_TIME" \
                   "$SCRIPT_NAME: АНАЛИЗ ЗАВЕРШЕН $CUR_TIME"
} # finish_work()

# Запуск основной команды через dprof с выводом команды на экран и в лог,
# с проверкой возвращаемого значения.
# Аргументы:
#  $1 - запускаемая команда и её параметры
run_cmd()
{
    CMD_WITH_ARGS="$1"

    # Печатаем команду на экран (убираем перенаправление вывода и абсолютные пути) и в лог
    SHORT_CMD=`echo -n "$CMD_WITH_ARGS " | sed 's#/[^ ]\+/##g'`
    CMD_BIN=`echo "$SHORT_CMD" | awk '{print $1;}'`
    echo -n "$SHORT_CMD"
    echo "$CMD_WITH_ARGS" >> $ANALYZER_LOG

    # Запоминаем время начала
    CMD_START_TIME=`date "+%s"`

    # Запускаем инициализацию
    if [ "$INIT_BEFORE_RUN_CMD" != "" ]
    then
        eval "$INIT_BEFORE_RUN_CMD >& $INIT_LOG" || die "init before run failed, see log '$INIT_LOG_SHORT'" \
                                                        "инициализатор завершился с ошибкой, см. лог '$INIT_LOG_SHORT'"
    fi

    # Запускаем команду
    eval "$CMD_WITH_ARGS"
    RES=$?

    if [ $RES != 0 ]
    then
        # Показываем пользователю содержимое файла с выводом команды:
        if [ -s "$RUN_PROG_LOG" ]
        then
            print_error "'$CMD_BIN' output from file $RUN_PROG_LOG :" \
                        "вывод '$CMD_BIN' из файла $RUN_PROG_LOG :"
            tail "$RUN_PROG_LOG" | conv_msg_encoding
        fi
        die "'$CMD_BIN' failed ($RES), see $ANALYZER_LOG_SHORT" \
            "запуск '$CMD_BIN' завершился с ошибкой ($RES), см. лог $ANALYZER_LOG_SHORT"
    fi

    # Запускаем чистку
    if [ "$CLEAR_AFTER_RUN_CMD" != "" ]
    then
        eval "$CLEAR_AFTER_RUN_CMD >& $CLEAR_LOG" || die "clear after run failed, see '$CLEAR_LOG_SHORT'" \
                                                         "чистильщик завершился с ошибкой, см. лог '$CLEAR_LOG_SHORT'"
    fi

    # Печатаем потраченное время
    CMD_END_TIME=`date "+%s"`
    CMD_DIFF_TIME=$((CMD_END_TIME - CMD_START_TIME))

    if [ "$CMD_DIFF_TIME" -gt 2 ]
    then
        echo -e "$OK ($CMD_DIFF_TIME s)"
    else
        echo -e "$OK"
    fi

    # Подсчитываем статистику по запускам задачи через dprof
    if [ $DPROF_RUNS_NUM == 0 ]
    then
        DPROF_MIN_TIME=$CMD_DIFF_TIME
        DPROF_MAX_TIME=$CMD_DIFF_TIME
    else
        [ $DPROF_MIN_TIME -gt $CMD_DIFF_TIME ] && DPROF_MIN_TIME=$CMD_DIFF_TIME
        [ $DPROF_MAX_TIME -lt $CMD_DIFF_TIME ] && DPROF_MAX_TIME=$CMD_DIFF_TIME
    fi

    let "DPROF_RUNS_NUM++"
    let "DPROF_TOTAL_TIME += CMD_DIFF_TIME"
} # run_cmd()

# Запуск вспомогательной команды с выводом команды на экран и в лог,
# с проверкой возвращаемого значения.
# Аргументы:
#  $1 - запускаемая команда и её параметры
run_aux_cmd()
{
    CMD_WITH_ARGS="$1"

    # Печатаем команду на экран (убираем перенаправление вывода и абсолютные пути) и в лог
    SHORT_CMD=`echo -n "$CMD_WITH_ARGS " | sed 's#/[^ ]\+/##g'`
    CMD_BIN=`echo "$SHORT_CMD" | awk '{print $1;}'`
    [ $IS_VERBOSE == 1 ] && echo -n "$SHORT_CMD"
    echo "$CMD_WITH_ARGS" >> $ANALYZER_LOG

    # Запускаем команду
    RUN_CMD_LOG="$WORK_DIR/aux_cmd.out"
    echo -n > $RUN_CMD_LOG
    eval "$CMD_WITH_ARGS" 2>> $RUN_CMD_LOG
    RES=$?
    cat $RUN_CMD_LOG >> $ANALYZER_LOG

    if [ $RES != 0 ]
    then
        if [ -s "$RUN_CMD_LOG" ]
        then
            print_error "auxiliary command '$CMD_BIN' failed ($RES):" \
                   "вспомогательный запуск '$CMD_BIN' завершился с ошибкой ($RES):"
            tail $RUN_CMD_LOG | conv_msg_encoding
        fi

        if [ $IS_VERBOSE == 0 ]
        then
            echo
            print_msg "$SCRIPT_NAME: hint: use -v option to see auxiliary commands" \
                      "$SCRIPT_NAME: совет: используйте опцию -v для печати запускаемых вспомогательных команд"
        fi

        die "'$CMD_BIN' failed, see log $ANALYZER_LOG_SHORT" \
            "запуск '$CMD_BIN' завершился с ошибкой, см. лог $ANALYZER_LOG_SHORT"
    fi

    [ $IS_VERBOSE == 1 ] && echo -e "$OK"
} # run_aux_cmd()

# Преобразование профиля из формата dprof в формат lcsim.
# Это копия скрипта event2ptrace.sh
# Аргументы:
#  $1 - файл *.IP с профилем
event_to_ptrace()
{
    # Формируем такой же заголовок, как и lcsim
    echo "# CYCLE # by $SCRIPT_NAME $SCRIPT_VERSION `date '+%G.%m.%d %k:%M:%S'`"
    echo "# Address Width Executed Trapped Consumed NOPs R-stalls FPU-stalls CODE-stalls SPILL-stalls MOVA-stalls MMU-stalls Unloaded BRANCH-stalls Wait-stalls Trap-stalls"
    awk '/^\[[0-9]+\] event \w+ \(\w*:?\w+\) at 0[0-9a-fA-F]+/ {
      count[$6]++;
    }
    END {
      for (i in count) {
        print "0x" i " 4 " count[i] " 0 " count[i] " 0 0 0 0 0 0 0 0 0 0 0 "
      }
    }' $1 | sort
} # event_to_ptrace()

# Получение кол-ва событий по логу dprof
# Аргументы:
#  $1 - путь к логу dprof
#  $2 - название события (например, TICKS)
#  $3 - название переменной, в которую запишем результат
#  $4 - длительность блокировки
get_event_counter()
{
    CUR_DPROF_LOG="$1"
    CUR_EVENT="$2"
    VAR_NAME="$3"
    BUB_TIME="$4"

    # Проверяем параметры
    [ "$CUR_DPROF_LOG" != "" ] || internal_error "get_event_counter(): dprof log not specified (param 1)"
    [ "$CUR_EVENT" != "" ]     || internal_error "get_event_counter(): event not specified (param 2)"
    [ "$VAR_NAME" != "" ]      || internal_error "get_event_counter(): var name not specified (param 3)"
    [ "$BUB_TIME" != "" ]      || internal_error "get_event_counter(): bub time not specified (param 4)"
    [ "$BUB_TIME" -ge 1 ]      || internal_error "get_event_counter(): invalid bub time value '$BUB_TIME' (param 4)"
    [ -s "$CUR_DPROF_LOG" ]    || internal_error "get_event_counter(): dprof log '$CUR_DPROF_LOG' not found"

    # Распознаём строку лога вида (нам нужно последнее число в ней):
    # [4229] event TICKS (2): 27831472462
    # Учитываем, что для разных потоков будет свой счётчик - суммируем счётчики потоков
    CUR_EVENT_COUNTER=`grep " event[ ]\+$CUR_EVENT " "$CUR_DPROF_LOG" | awk '{s+=$5;} END{print s;}'`

    # Проверяем, что полученная строка является числом и больше нуля
    is_number "$CUR_EVENT_COUNTER" || die "failed to get event '$CUR_EVENT' counter from dprof log '$CUR_DPROF_LOG'" \
                                          "не удалось извлечь кол-во событий '$CUR_EVENT' из лога dprof '$CUR_DPROF_LOG'"
    [ "$CUR_EVENT_COUNTER" -ge 0 ] || die "invalid event '$CUR_EVENT' counter ($CUR_EVENT_COUNTER)" \
                                          "некорректный счётчик события '$CUR_EVENT' ($CUR_EVENT_COUNTER)"

    # Присваиваем значение переменной с учётом длительности блокировки
    let "$VAR_NAME=$CUR_EVENT_COUNTER*$BUB_TIME"

} # get_event_counter()

# см. комментарий к print_event_counter()
print_event_cnt_internal()
{
    if [ "$1" == "bubs" ]
    then
        # Печатаем это слово отдельно, поскольку %s в printf некорректно работает с UTF-8
        print_msg -log -n "              bubs" \
                          "        блокировки"
        printf     " %15d"      "$2" | tee -a "$ANALYZER_LOG"
    else
        printf "%18s %15d" "$1" "$2" | tee -a "$ANALYZER_LOG"
    fi

    if [ "$3" -gt 0 ]
    then
        CUR_PERCENT=`echo "$2 $3" | awk '{printf( "%f\n", 100.0 * $1 / $2);}'`
        MSG_ENG=`printf "  (%4.1f%% bubs)" "$CUR_PERCENT"`
        MSG_RUS=`printf "  (%4.1f%% блокировок)" "$CUR_PERCENT"`
        print_msg -log -n "$MSG_ENG" "$MSG_RUS"
    fi

    if [ "$4" -gt 0 ]
    then
        CUR_PERCENT=`echo "$2 $4" | awk '{printf( "%f\n", 100.0 * $1 / $2);}'`
        MSG_ENG=`printf "  (%4.1f%% ticks)" "$CUR_PERCENT"`
        MSG_RUS=`printf "  (%4.1f%% тактов)" "$CUR_PERCENT"`
        print_msg -log -n "$MSG_ENG" "$MSG_RUS"
    fi

    echo | tee -a "$ANALYZER_LOG"
} # print_event_cnt_internal()

# Печать на экран и в лог счётчика событий.
# Аргументы:
#  $1 - название события
#  $2 - счётчик события
#  $3 - общее кол-во блокировок (или 0, если не считаем % от этого числа)
#  $4 - общее кол-во тиков (или 0, если не считаем % от этого числа)
#  $5 - -skip-zero - если опция подана, то ничего не печатаем в случае нулевого счётчика
print_event_counter()
{
    [ "$1" != "" ] || internal_error "print_event_counter(): param 1 not set"
    [ "$2" != "" ] || internal_error "print_event_counter(): param 2 not set"
    [ "$3" != "" ] || internal_error "print_event_counter(): param 3 not set"
    [ "$4" != "" ] || internal_error "print_event_counter(): param 4 not set"

    if [ "$5" == "-skip-zero" ] && [ "$2" -le 0 ]
    then
        return
    fi

    print_event_cnt_internal "$1" "$2" "$3" "$4"

} # print_event_counter()

# Получение процента кол-ва событий для определённой блокировки
# Аргументы:
#  $1 - счётчик событий
#  $2 - счётчик событий, относительно которого считается %,
#       может быть пустым, тогда берётся общее кол-во блокировок
#
# Результат печатается в stdout
get_event_percent()
{
    CUR_EVENTS="$1"
    ALL_EVENTS="$2"

    if [ "$ALL_EVENTS" == "" ]
    then
        ALL_EVENTS="$BUB_TICKS"
    fi

    echo "$CUR_EVENTS $ALL_EVENTS" | awk '{if( $2 > 0 ) { printf( "%.0f\n", 100.0 * $1 / $2);} else { print 0 } }'

} # get_event_percent()

# Печать предупреждения
print_eblrus_e3s_warning()
{
    print_warning "BUB_E2_MEM and BUB_E2_OPND are not supported by Elbrus E3S cpu" \
                  "BUB_E2_MEM и BUB_E2_OPND не поддерживаются процессором Elbrus E3S"
} # print_eblrus_e3s_warning()

# Добавление события в очередь событий EVENTS_QUEQUE[channel][i] для последующего их подсчёта.
# Аргументы:
#  $1 - название события
#  $2 - канал1
#  $3 - канал2 (опционально)
push_event_to_queque()
{
    PAR_EVENT="$1"
    PAR_CH1="$2"
    PAR_CH2="$3"

    # Проверяем, поддерживает ли аппаратура требуемые события
    if [ $IS_BUB_E2_MEM == 0 ] && \
       [[ "$PAR_EVENT" == "BUB_E2_"* ]]
    then
        # В Elbrus E3S аппаратура не поддерживает эти события
        print_eblrus_e3s_warning
        return
    fi

    I=0
    while /bin/true
    do
        if [ "${EVENTS_QUEQUE[$PAR_CH1,$I]}" == "" ]
        then
            EVENTS_QUEQUE[$PAR_CH1,$I]=$PAR_EVENT
            return
        fi

        if [ "$PAR_CH2" != "" ] && \
           [ "${EVENTS_QUEQUE[$PAR_CH2,$I]}" == "" ]
        then
            EVENTS_QUEQUE[$PAR_CH2,$I]=$PAR_EVENT
            return
        fi
        let 'I++'
    done
} # push_event_to_queque()

# Запуск задачи на исполнение с подсчётом кол-ва заданных событий
# Аргументы:
#  $1 - список событий
dprof_count_user_events()
{
    PAR_EVENTS_LIST="$1"
    DPROF_LOG="$WORK_DIR/dprof_user_event.out"
    DPROF_ARGS=

    for USER_EVENT in $PAR_EVENTS_LIST
    do
        DPROF_ARGS+=" -m $USER_EVENT"
    done

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q$DPROF_ARGS -o $DPROF_LOG $RUN_PROG_CMD"
    print_blank_line

    for USER_EVENT in $PAR_EVENTS_LIST
    do
        # Вычисляем длительность события
        # UPD: Для пользователя выдаём кол-во событий в чистом виде
        EVENT_LEN=1
#       [[ "$USER_EVENT" == "BUB_E2"*   ]] && EVENT_LEN=$TIME_BUB_E2
#       [[ "$USER_EVENT" == "DAM_BELL"* ]] && EVENT_LEN=$TIME_DAM_BELL

        # Вытягиваем кол-во собранных событий из лога
        get_event_counter $DPROF_LOG "$USER_EVENT" "USER_EVENT_CNT" "$EVENT_LEN"

        # Печатаем полученное значение
        print_event_counter "$USER_EVENT" "$USER_EVENT_CNT" 0 0 | tee -a $USER_CNT_EVENTS_FILE
    done

    print_blank_line

} # dprof_count_user_events()

# Запуск задачи на исполнение с подсчётом событий TICKS и EXEC
# Вычтя одно из другого мы получим общее кол-во блокировок
dprof_count_ticks_exec()
{
    DPROF_LOG="$WORK_DIR/dprof_ticks_exec.out"

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -m TICKS -m EXEC -o $DPROF_LOG $RUN_PROG_CMD"

    # Вытягиваем кол-во событий из лога
    get_event_counter $DPROF_LOG "TICKS" "EVENT_TICKS" 1
    get_event_counter $DPROF_LOG "EXEC"  "EVENT_EXEC"  1

    # Печатаем полученные значения
    print_blank_line
    print_event_counter "TICKS" "$EVENT_TICKS" 0 0
    print_event_counter "EXEC"  "$EVENT_EXEC"  0 0
    BUB_TICKS=$((EVENT_TICKS - EVENT_EXEC))
    print_event_counter "bubs"  "$BUB_TICKS"   0 "$EVENT_TICKS"
    print_blank_line

    # Проверяем, что счётчики не нулевые
    [ "$EVENT_TICKS" -gt 0 ] || die "invalid TICKS counter ($EVENT_TICKS)" \
                                    "некорректный счётчик события TICKS ($EVENT_TICKS)"
    [ "$EVENT_EXEC"  -gt 0 ] || die "invalid EXEC counter ($EVENT_EXEC)" \
                                    "некорректный счётчик события EXEC ($EVENT_EXEC)"

} # dprof_count_ticks_exec()

# Запуск задачи на исполнение с подсчётом событий BUB_E2, IB_NO_COMMAND, DAM_BELL
dprof_count_bube2_ibnocmd()
{
    DPROF_LOG="$WORK_DIR/dprof_bube2_ibnocmd.out"
    CLEAR_LOG="$WORK_DIR/check_results.out"

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -m DAM_BELL -m BUB_E2 -m IB_NO_COMMAND -o $DPROF_LOG $RUN_PROG_CMD"

    # Вытягиваем кол-во событий из лога
    get_event_counter $DPROF_LOG "BUB_E2"        "EVENT_BUB_E2"        $TIME_BUB_E2
    get_event_counter $DPROF_LOG "IB_NO_COMMAND" "EVENT_IB_NO_COMMAND" 1
    get_event_counter $DPROF_LOG "DAM_BELL"      "EVENT_DAM_BELL"      $TIME_DAM_BELL

    # Печатаем полученные значения
    print_blank_line
    print_event_counter "BUB_E2"        "$EVENT_BUB_E2"        "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "IB_NO_COMMAND" "$EVENT_IB_NO_COMMAND" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "DAM_BELL"      "$EVENT_DAM_BELL"      "$BUB_TICKS" "$EVENT_TICKS"
    print_blank_line

    # Считаем процент BUB_E2 - эта блокировка наиболее распространённая,
    # и если она доминирует, то нет особого смысла смотреть что-то другое
    PERCENT_BUB_E2=`get_event_percent $EVENT_BUB_E2`
    PERCENT_IB_NO_COMMAND=`get_event_percent $EVENT_IB_NO_COMMAND`

} # dprof_count_bube2_ibnocmd()

# Запуск задачи на исполнение с подсчётом событий BUB_E2_MEM и BUB_E2_OPND
dprof_count_bube2_mem_opnd()
{
    DPROF_LOG="$WORK_DIR/dprof_bube2_mem_opnd.out"
    CLEAR_LOG="$WORK_DIR/check_results.out"

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -m DTLB_DEC_STALL_AAU -m BUB_E2_MEM -m BUB_E2_OPND -o $DPROF_LOG $RUN_PROG_CMD"

    # Вытягиваем кол-во событий из лога
    get_event_counter $DPROF_LOG "BUB_E2_MEM"           "EVENT_BUB_E2_MEM"  $TIME_BUB_E2
    get_event_counter $DPROF_LOG "BUB_E2_OPND"          "EVENT_BUB_E2_OPND" $TIME_BUB_E2
    get_event_counter $DPROF_LOG "DTLB_DEC_STALL_AAU"   "EVENT_STALL_AAU"   $TIME_STALL_AAU

    # Печатаем полученные значения
    print_blank_line
    print_event_counter "BUB_E2_MEM"         "$EVENT_BUB_E2_MEM"  "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_OPND"        "$EVENT_BUB_E2_OPND" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "DTLB_DEC_STALL_AAU" "$EVENT_STALL_AAU"   "$BUB_TICKS" "$EVENT_TICKS"
    print_blank_line

    # Считаем процент блокировок
    PERCENT_BUB_E2_MEM=`get_event_percent $EVENT_BUB_E2_MEM`
    PERCENT_BUB_E2_OPND=`get_event_percent $EVENT_BUB_E2_OPND`
    PERCENT_STALL_AAU=`get_event_percent $EVENT_STALL_AAU`

} # dprof_count_bube2_mem_opnd()

# Запуск задачи на исполнение с подсчётом событий BUB_E2_ALU[0-5]_MEM
dprof_count_bube2_mem()
{
    DPROF_LOG01="$WORK_DIR/dprof_bube2_mem01.out"
    DPROF_LOG23="$WORK_DIR/dprof_bube2_mem23.out"
    DPROF_LOG45="$WORK_DIR/dprof_bube2_mem45.out"
    CLEAR_LOG="$WORK_DIR/check_results.out"

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -m BUB_E2_ALU0_MEM -m BUB_E2_ALU1_MEM -o $DPROF_LOG01 $RUN_PROG_CMD"
    run_cmd "$DPROF -q -m BUB_E2_ALU2_MEM -m BUB_E2_ALU3_MEM -o $DPROF_LOG23 $RUN_PROG_CMD"
    run_cmd "$DPROF -q -m BUB_E2_ALU4_MEM -m BUB_E2_ALU5_MEM -o $DPROF_LOG45 $RUN_PROG_CMD"

    # Вытягиваем кол-во событий из лога
    get_event_counter $DPROF_LOG01 "BUB_E2_ALU0_MEM" "EVENT_BUB_E2_ALU0_MEM" $TIME_BUB_E2
    get_event_counter $DPROF_LOG01 "BUB_E2_ALU1_MEM" "EVENT_BUB_E2_ALU1_MEM" $TIME_BUB_E2
    get_event_counter $DPROF_LOG23 "BUB_E2_ALU2_MEM" "EVENT_BUB_E2_ALU2_MEM" $TIME_BUB_E2
    get_event_counter $DPROF_LOG23 "BUB_E2_ALU3_MEM" "EVENT_BUB_E2_ALU3_MEM" $TIME_BUB_E2
    get_event_counter $DPROF_LOG45 "BUB_E2_ALU4_MEM" "EVENT_BUB_E2_ALU4_MEM" $TIME_BUB_E2
    get_event_counter $DPROF_LOG45 "BUB_E2_ALU5_MEM" "EVENT_BUB_E2_ALU5_MEM" $TIME_BUB_E2

    # Печатаем полученные значения
    print_blank_line
    print_event_counter "BUB_E2_ALU0_MEM" "$EVENT_BUB_E2_ALU0_MEM" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU1_MEM" "$EVENT_BUB_E2_ALU1_MEM" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU2_MEM" "$EVENT_BUB_E2_ALU2_MEM" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU3_MEM" "$EVENT_BUB_E2_ALU3_MEM" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU4_MEM" "$EVENT_BUB_E2_ALU4_MEM" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU5_MEM" "$EVENT_BUB_E2_ALU5_MEM" "$BUB_TICKS" "$EVENT_TICKS"
    print_blank_line

} # dprof_count_bube2_mem()

# Запуск задачи на исполнение с подсчётом событий BUB_E2_ALU[0-5]_OPND
dprof_count_bube2_opnd()
{
    DPROF_LOG01="$WORK_DIR/dprof_bube2_opnd01.out"
    DPROF_LOG23="$WORK_DIR/dprof_bube2_opnd23.out"
    DPROF_LOG45="$WORK_DIR/dprof_bube2_opnd45.out"
    CLEAR_LOG="$WORK_DIR/check_results.out"

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -m BUB_E2_ALU0_OPND -m BUB_E2_ALU1_OPND -o $DPROF_LOG01 $RUN_PROG_CMD"
    run_cmd "$DPROF -q -m BUB_E2_ALU2_OPND -m BUB_E2_ALU3_OPND -o $DPROF_LOG23 $RUN_PROG_CMD"
    run_cmd "$DPROF -q -m BUB_E2_ALU4_OPND -m BUB_E2_ALU5_OPND -o $DPROF_LOG45 $RUN_PROG_CMD"

    # Вытягиваем кол-во событий из лога
    get_event_counter $DPROF_LOG01 "BUB_E2_ALU0_OPND" "EVENT_BUB_E2_ALU0_OPND" $TIME_BUB_E2
    get_event_counter $DPROF_LOG01 "BUB_E2_ALU1_OPND" "EVENT_BUB_E2_ALU1_OPND" $TIME_BUB_E2
    get_event_counter $DPROF_LOG23 "BUB_E2_ALU2_OPND" "EVENT_BUB_E2_ALU2_OPND" $TIME_BUB_E2
    get_event_counter $DPROF_LOG23 "BUB_E2_ALU3_OPND" "EVENT_BUB_E2_ALU3_OPND" $TIME_BUB_E2
    get_event_counter $DPROF_LOG45 "BUB_E2_ALU4_OPND" "EVENT_BUB_E2_ALU4_OPND" $TIME_BUB_E2
    get_event_counter $DPROF_LOG45 "BUB_E2_ALU5_OPND" "EVENT_BUB_E2_ALU5_OPND" $TIME_BUB_E2

    # Печатаем полученные значения
    print_blank_line
    print_event_counter "BUB_E2_ALU0_OPND" "$EVENT_BUB_E2_ALU0_OPND" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU1_OPND" "$EVENT_BUB_E2_ALU1_OPND" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU2_OPND" "$EVENT_BUB_E2_ALU2_OPND" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU3_OPND" "$EVENT_BUB_E2_ALU3_OPND" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU4_OPND" "$EVENT_BUB_E2_ALU4_OPND" "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_E2_ALU5_OPND" "$EVENT_BUB_E2_ALU5_OPND" "$BUB_TICKS" "$EVENT_TICKS"
    print_blank_line

} # dprof_count_bube2_opnd()

# Запуск задачи на исполнение с подсчётом событий BUB_B и BUB_B_NOP
dprof_count_bubb_bubbnop()
{
    DPROF_LOG="$WORK_DIR/dprof_bubb_bubbnop.out"
    CLEAR_LOG="$WORK_DIR/check_results.out"

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -m BUB_B -m BUB_B_NOP -o $DPROF_LOG $RUN_PROG_CMD"

    # Вытягиваем кол-во событий из лога
    get_event_counter $DPROF_LOG "BUB_B"     "EVENT_BUB_B"     1
    get_event_counter $DPROF_LOG "BUB_B_NOP" "EVENT_BUB_B_NOP" 1

    # Печатаем полученные значения
    print_blank_line
    print_event_counter "BUB_B"     "$EVENT_BUB_B"     "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_B_NOP" "$EVENT_BUB_B_NOP" "$BUB_TICKS" "$EVENT_TICKS"
    print_blank_line

    # Считаем процент BUB_B - нужен как косвенный признак срабатывания DAM
    # Считаем процент BUB_B_NOP относительно BUB_B - нужен как косвенный признак
    # аппаратного spill/fill регистрового файла
    PERCENT_BUB_B=`get_event_percent $EVENT_BUB_B`
    PERCENT_BUB_B_NOP=`get_event_percent $EVENT_BUB_B_NOP $EVENT_BUB_B`

} # dprof_count_bubb_bubbnop()

# Запуск задачи на исполнение с подсчётом событий RBRANCH и BUB_B_RF_SPILLFILL
dprof_count_rbranch_rf_spillfill()
{
    DPROF_LOG="$WORK_DIR/dprof_dam_bell.out"
    CLEAR_LOG="$WORK_DIR/check_results.out"

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -m RBRANCH -m BUB_B_RF_SPILLFILL -o $DPROF_LOG $RUN_PROG_CMD"

    # Вытягиваем кол-во событий из лога
    get_event_counter $DPROF_LOG "RBRANCH"            "EVENT_RBRANCH"            $TIME_RBRANCH
    get_event_counter $DPROF_LOG "BUB_B_RF_SPILLFILL" "EVENT_BUB_B_RF_SPILLFILL" 1

    # Печатаем полученные значения
    print_blank_line
    print_event_counter "RBRANCH"            "$EVENT_RBRANCH"            "$BUB_TICKS" "$EVENT_TICKS"
    print_event_counter "BUB_B_RF_SPILLFILL" "$EVENT_BUB_B_RF_SPILLFILL" "$BUB_TICKS" "$EVENT_TICKS"
    print_blank_line

} # dprof_count_rbranch_rf_spillfill()

# Печатаем сводную таблицу по всем блокировкам
print_all_bubs()
{
    print_line
    print_msg -log "             event         counter" \
                   "           событие         счётчик"
    print_event_counter "TICKS" "$EVENT_TICKS" 0 0
    print_event_counter "EXEC"  "$EVENT_EXEC"  0 0
    print_event_counter "bubs"  "$BUB_TICKS"   0 "$EVENT_TICKS"
    print_blank_line
    print_msg -log "                             ticks (estimated)" \
                   "                             такты (оценка)"
    print_event_counter "BUB_E2"             "$EVENT_BUB_E2"             "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_MEM"         "$EVENT_BUB_E2_MEM"         "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU0_MEM"    "$EVENT_BUB_E2_ALU0_MEM"    "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU1_MEM"    "$EVENT_BUB_E2_ALU1_MEM"    "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU2_MEM"    "$EVENT_BUB_E2_ALU2_MEM"    "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU3_MEM"    "$EVENT_BUB_E2_ALU3_MEM"    "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU4_MEM"    "$EVENT_BUB_E2_ALU4_MEM"    "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU5_MEM"    "$EVENT_BUB_E2_ALU5_MEM"    "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_OPND"        "$EVENT_BUB_E2_OPND"        "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU0_OPND"   "$EVENT_BUB_E2_ALU0_OPND"   "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU1_OPND"   "$EVENT_BUB_E2_ALU1_OPND"   "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU2_OPND"   "$EVENT_BUB_E2_ALU2_OPND"   "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU3_OPND"   "$EVENT_BUB_E2_ALU3_OPND"   "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU4_OPND"   "$EVENT_BUB_E2_ALU4_OPND"   "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_E2_ALU5_OPND"   "$EVENT_BUB_E2_ALU5_OPND"   "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "DTLB_DEC_STALL_AAU" "$EVENT_STALL_AAU"          "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "IB_NO_COMMAND"      "$EVENT_IB_NO_COMMAND"      "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_B"              "$EVENT_BUB_B"              "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_B_NOP"          "$EVENT_BUB_B_NOP"          "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "DAM_BELL"           "$EVENT_DAM_BELL"           "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "RBRANCH"            "$EVENT_RBRANCH"            "$BUB_TICKS" "$EVENT_TICKS" -skip-zero
    print_event_counter "BUB_B_RF_SPILLFILL" "$EVENT_BUB_B_RF_SPILLFILL" "$BUB_TICKS" "$EVENT_TICKS" -skip-zero

    # Сумма блокировок может оказаться более 100%, поскольку в аппаратуре длительность блокировок
    # не является фиксированной, а lperf в своих подсчётах всегда использует максимальную длительность.
    # Не печатаем её, чтобы не ломать мозг пользователям. Эта печать нужна только на этапе отладки скрипта.
#   SUMM_BUBS=$((EVENT_BUB_E2 + EVENT_IB_NO_COMMAND + EVENT_BUB_B))
#   print_event_counter "counted bubs"       "$SUMM_BUBS"                "$BUB_TICKS" 0              -skip-zero

    print_line

} # print_all_bubs()

# Находим самую большую блокировку (MAX_BUB)
find_biggest_bub()
{
    MAX_TICKS=0
    MAX_BUB=""

    # FIXME что-то здесь мало событий обрабатывается
    if [ "$MAX_TICKS" -lt "$EVENT_BUB_E2" ]
    then
        MAX_TICKS=$EVENT_BUB_E2
        MAX_BUB="BUB_E2"
    fi

    if [ "$MAX_TICKS" -lt "$EVENT_IB_NO_COMMAND" ]
    then
        MAX_TICKS=$EVENT_IB_NO_COMMAND
        MAX_BUB="IB_NO_COMMAND"
    fi

    if [ "$MAX_TICKS" -lt "$EVENT_BUB_B" ]
    then
        MAX_TICKS=$EVENT_BUB_B
        MAX_BUB="BUB_B"
    fi

    if [ "$MAX_TICKS" -lt "$EVENT_BUB_B_NOP" ]
    then
        MAX_TICKS=$EVENT_BUB_B_NOP
        MAX_BUB="BUB_B_NOP"
    fi

    if [ "$MAX_BUB" == "" ]
    then
        print_msg -log "failed: biggest bub not found" \
                       "failed: событие с максимальным счётчиком не найдено"
    fi
} # find_biggest_bub()

# Выдача первых (наиболее горячих) функций из файла с профилем.
# Аргументы:
#  $1 - название входного файла (с профилем)
#  $2 - название выходного файла (куда запишем результат)
#
# Формат входного файла:
# %time      %summ      time    calls  time/call  name
# 72.50%     72.50%    21330        1    21330    LBM_performStreamCollide (470.lbm)
#
# Формат выходного файла:
# 1  72.50%    LBM_performStreamCollide (470.lbm)
# 2  15.00%    LBM_showGridStatistics (470.lbm)
# 3  12.50%    LBM_handleInOutFlow (470.lbm)
parse_hot_functions()
{
    PAR_IN_FILE="$1"
    PAR_OUT_FILE="$2"

    # Здесь единственное место, где текст не перевели на русский.
    # Однако, сделать это непросто, поэтому пока просто забьём.
    cat $PAR_IN_FILE \
        | grep '^[ ]*[0-9.%]\+ ' \
        | sed 's/%//' \
        | awk 'BEGIN{ nprinted=0; events_num=0; } \
               { \
                 if( $1 > 2.0 || nprinted == 0 || \
                     (nprinted < 40 && $1 > 0.5) || \
                     (nprinted < 10 && $1 > 0.1) ) { \
                   nprinted++; \
                   events_num += $3; \
                   printf( "%2d  %5.2f%%    %s %s\n", nprinted, $1, $6, $7); \
                 } \
               } \
               END{ printf( "total %d events in %d functions\n", events_num, nprinted); }' \
        > $PAR_OUT_FILE
} # parse_hot_functions()

# Корректируем интервал сэмплирования (DPROF_INTERVAL) для коротких задач.
# Аргументы:
#  $1 - название события, которое мы будем профилировать
set_dprof_interval()
{
    PAR_EVENT_NAME="$1"

    # Менять интервал можно только в том случае, если он не задан пользователем
    if [ $IS_USER_DPROF_INTERVAL == 1 ]
    then
        return 0
    fi

    DPROF_INTERVAL="$DPROF_INTERVAL_DEFAULT"

    # Получаем счётчик событий, полученный в предыдущих запусках dprof -m
    let "CUR_EVENT_COUNTER = EVENT_$PAR_EVENT_NAME"
    # Если запуска dprof -m для данного события не было (такое возможно, например,
    # в режиме --prof), то менять частоту не будем
    [ "$CUR_EVENT_COUNTER" -gt 0 ] || return 0

    # Некоторые события происходят очень редко,
    # только для них допустимо использовать минимальную частоту
    if [ "$PAR_EVENT_NAME" == "DAM_BELL" ] || \
       [ "$PAR_EVENT_NAME" == "RBRANCH" ] || \
       [ "$PAR_EVENT_NAME" == "BUB_B_RF_SPILLFILL" ]
    then
        let "CUR_INTERVAL_MIN = DPROF_INTERVAL_MIN * 10"

    # Профиль для TICKS,IB_NO_COMMAND собирается некорректно для interval=1000,
    # для них повысим нижний порог сильнее
    elif [ "$PAR_EVENT_NAME" == "TICKS" ] || \
         [ "$PAR_EVENT_NAME" == "IB_NO_COMMAND" ]
    then
        let "CUR_INTERVAL_MIN = DPROF_INTERVAL_MIN * 100"
    else
        let "CUR_INTERVAL_MIN = DPROF_INTERVAL_MIN * 10"
    fi

    # Уменьшаем интервал таким образом, чтобы dprof собрал хотя бы MIN_EVENT_NUM событий для профиля
    while [ $DPROF_INTERVAL -gt $CUR_INTERVAL_MIN ]
    do
        if [ "$CUR_EVENT_COUNTER" -ge "$((MIN_EVENT_NUM * DPROF_INTERVAL))" ]
        then
            return
        fi

        let "DPROF_INTERVAL = DPROF_INTERVAL / 10"
    done
} # set_dprof_interval()

# Запуск задачи на исполнение со снятием профиля по конкретному событию.
# Сохраняются файлы:
#  - с общим профилем события (по функциям)
#  - с дизассемблером наиболее горячих функций (со счётчиками)
# Аргументы:
#  $1 - название события (например, BUB_E2)
dprof_make_event_prof()
{
    EVENT_NAME=$1

    if [ "$EVENT_NAME" == "" ]
    then
        return 1
    fi

    # Проверяем, поддерживает ли аппаратура требуемое событие
    if [ $IS_BUB_E2_MEM == 0 ] && \
       [[ "$EVENT_NAME" == "BUB_E2_"* ]]
    then
        # В Elbrus E3S аппаратура не поддерживает эти события
        print_eblrus_e3s_warning
        return
    fi

    IP_FILE="$WORK_DIR/${BIN_NAME}.${EVENT_NAME}.IP"
    DPROF_TO_PTRACE_OUT="$WORK_DIR/${BIN_NAME}.dprof2ptrace.out"
    PTRACE_FILE="$WORK_DIR/${BIN_NAME}.ptrace"
    TEST_LINK="$WORK_DIR/${BIN_NAME}"
    PROF_FILE="$WORK_DIR/${BIN_NAME}.${EVENT_NAME}.prof"
    FUPR_FILE="$WORK_DIR/${BIN_NAME}.${EVENT_NAME}.full.prof"
    HOTF_FILE="$WORK_DIR/${BIN_NAME}.${EVENT_NAME}.hot_func"

    print_blank_line
    print_msg -log "$GREEN> make profile for event $EVENT_NAME$BLANK" \
                   "$GREEN> получаем профиль для события $EVENT_NAME$BLANK"


    # Удаляем залипшие файлы от предыдущих запусков скрипта
    run_aux_cmd "rm -f $IP_FILE"
    run_aux_cmd "rm -f $DPROF_TO_PTRACE_OUT"
    run_aux_cmd "rm -f $PTRACE_FILE"
    run_aux_cmd "rm -f $PROF_FILE"
    run_aux_cmd "rm -f $FUPR_FILE"
    run_aux_cmd "rm -f $HOTF_FILE"
    run_aux_cmd "rm -f *.ptrace *.dynamic_libs"
    run_aux_cmd "rm -f $WORK_DIR/*.ptrace $WORK_DIR/*.dynamic_libs"

    # Корректируем интервал сэмплирования (DPROF_INTERVAL) для коротких задач
    set_dprof_interval $EVENT_NAME

    # Запускаем задачу на исполнение
    run_cmd "$DPROF -q -e ${EVENT_NAME}:$DPROF_INTERVAL -o $IP_FILE $RUN_PROG_CMD"

    # Создаём символьную ссылку на исполняемый файл
    if [ "$TEST_LINK" != "$TEST_BIN" ]
    then
        if [ -f "$TEST_LINK" ] || [ -L "$TEST_LINK" ]
        then
            run_aux_cmd "rm -f $TEST_LINK"
        fi

        run_aux_cmd "ln -sf $TEST_BIN $TEST_LINK"
    fi

    # Получаем все функции бинарника, в которых сидят блокировки
    BASE_DIR=`pwd`
    cd $WORK_DIR
    run_aux_cmd "$DPROF_TO_PTRACE $IP_FILE > $DPROF_TO_PTRACE_OUT"
    cd $BASE_DIR
    if [ -f $PTRACE_FILE ]
    then
        run_aux_cmd "cp -f $PTRACE_FILE ."
        run_aux_cmd "$LDIS -P $TEST_LINK > $PROF_FILE"

        # Получаем наиболее горячие функции
        parse_hot_functions "$PROF_FILE" "$HOTF_FILE"
        HOT_FUNC_NUM=`head -n -1 $HOTF_FILE | wc -l`

        # Горячими могут оказаться только функции библиотек, так что не прерываемся.
        #if [ "$HOT_FUNC_NUM" -le 0 ]
        #then
        #    # Не нашли ни одной горячей функции
        #    print_msg -log "failed: no hot functions found" \
        #                   "failed: горячие функции не найдены"
        #    return 1
        #fi
    fi

    # Это не надо - dprof2ptrace уже всё сделал, осталось только склеить.
    # Определяем, есть ли в профиле библиотечные функции
    #IS_HOT_LIB_FUNC=0
    #HOT_FUNC_LIST=`head -n -1 $HOTF_FILE | awk '{print $3;}'`

    #for FUNC in $HOT_FUNC_LIST
    #do
    #    if [[ "$FUNC" == *"*"* ]]
    #    then
    #        # Нашли библиотечную функцию
    #        IS_HOT_LIB_FUNC=1
    #        break
    #    fi
    #done

    # Вынесли из условия в связи с переносом функциональности из extractor.sh
    # в dprof2ptrace.sh
    # [ $IS_HOT_LIB_FUNC == 1 ] && \
    # [ "$EXTRACTOR" != "" ] && \
    if [ "$PROF_PARSER" != "" ]
    then
        # Дополняем профиль библиотечными функциям.
        # Поскольку ldis не умеет печатать их подобно perf'у,
        # приходится делать это руками с помощью дополнительных утилит
        # и такой-то матери.
        # run_aux_cmd "$EXTRACTOR $TEST_LINK $IP_FILE >> $ANALYZER_LOG"

        # Получаем список использованных библиотек
        LIBS_LIST=`cat $IP_FILE | perl -e 'while(<>){
        chomp;
        if( m/DPROF_PROC_MAPS NEW.+ (\/.+)$/ &&
            $1 ne "'$BIN_NAME'" ){
            print $1 . " ";
        }}'`

        # Добавляем профиль библиотек в файл с профилем
        for LIB in $LIBS_LIST
        do
            LIB_NAME=`basename $LIB`
            [ "$LIB_NAME" == "$BIN_NAME" ] && continue

            # Библиотека может быть упомянута в файле с трассой, но не иметь попавших
            # сэмплов - в этом случае dprof2ptrace не создаст <lib_name>.ptrace.
            LIB_PTRACE_FILE="$WORK_DIR/$LIB_NAME.ptrace"
            if [ -f $LIB_PTRACE_FILE ]
            then
                run_aux_cmd "ln -sf $LIB ."
                run_aux_cmd "cp -f $LIB_PTRACE_FILE ."
                run_aux_cmd "$LDIS -P $LIB_NAME >> $PROF_FILE"
            fi
        done

        # Объединяем профили программы и её библиотек в один сквозной профиль
        run_aux_cmd "$PROF_PARSER $PROF_FILE > $FUPR_FILE"
        run_aux_cmd "mv -f $FUPR_FILE $PROF_FILE"

        # Получаем наиболее горячие функции
        parse_hot_functions "$PROF_FILE" "$HOTF_FILE"
        HOT_FUNC_NUM=`head -n -1 $HOTF_FILE | wc -l`

        if [ "$HOT_FUNC_NUM" -le 0 ]
        then
            # Не нашли ни одной горячей функции
            print_msg -log "failed: no hot functions found (with dynamic libraries)" \
                           "failed: горячие функции не найдены (с динамическими библиотеками)"
            return 1
        fi

        # Получаем простой список функций(бинарников)
        HOT_FUNC_LIST=`head -n -1 $HOTF_FILE | awk '{printf( "%s%s ", $3, $4);}'`
    fi

    # Печатаем наиболее горячие функции
    print_msg -log "found $HOT_FUNC_NUM hot functions:" \
                 "найдено $HOT_FUNC_NUM горячих функций:"
    print_blank_line
    print_msg -log " #   time%    function" \
                   " #  время%    функция"
    cat $HOTF_FILE | tee -a "$ANALYZER_LOG"
    print_blank_line
    FUNC_ID=0

    # Получаем дизассемблеры со счётчиками для горячих функций
    for FUNC in $HOT_FUNC_LIST
    do
        let 'FUNC_ID+=1'

        # Учитываем, что dprof может выдать функцию с названием (*)
        if [[ "$FUNC" == *"*"* ]]
        then
            print_msg -log "skip function '$FUNC'" \
                           "пропускаем функцию '$FUNC'"
            continue
        fi

        if [[ "$FUNC" == *")"* ]]
        then
            # Разделяем строку "func (bin)" на две части: имя функции и имя бинарника,
            # в котором эта функция живёт
            FUNC_NAME=`echo "$FUNC" | sed -r 's/(.+)\((.*)\)/\1/g'`
            CUR_BIN_NAME=`echo "$FUNC" | sed -r 's/(.+)\((.*)\)/\2/g'`
            [ "$CUR_BIN_NAME" == "" ] && CUR_BIN_NAME="$TEST_LINK"
            [ "$CUR_BIN_NAME" == "$BIN_NAME" ] && CUR_BIN_NAME="$TEST_LINK"
            [ "$CUR_BIN_NAME" == "$FUNC" ] && die "failed to get function name" \
                                                  "не удалось извлечь имя функции"
        else
            # Строка содержит только имя функции. Бинарник - профилируемая программа.
            FUNC_NAME="$FUNC"
            CUR_BIN_NAME="$TEST_LINK"
        fi

        # Трамплины игнорируем, всё равно ldis не умеет получать для них дизассемблер
        if [[ "$FUNC_NAME" == *"@plt" ]]
        then
            continue
        fi

        # Функции нумеруем, и в названиях файлов используем номера, а не имена функций;
        # это актуально для C++, где названия функций бывают очень длинные.
        FUNC_ID_STR=`printf "%02d" $FUNC_ID`
        DIS_FUNC_FILE="$WORK_DIR/${BIN_NAME}.${EVENT_NAME}.${FUNC_ID_STR}.dis"
        TCK_FUNC_FILE="$WORK_DIR/${BIN_NAME}.${EVENT_NAME}.${FUNC_ID_STR}.ticks"

        # Получаем дизассемблер горячей функции со счётчиками блокировок
        run_aux_cmd "$LDIS -I $CUR_BIN_NAME $FUNC_NAME > $DIS_FUNC_FILE"

        if [ -s "$DIS_FUNC_FILE" ]
        then
            # Выделяем из этого файла только тики.
            # Вначале печатаем тики, которые составляют хотя бы 1% от максимального.
            # Потом печатаем все тики, чтобы удобно было смотреть.
            echo "HOT TICKS:" > "$TCK_FUNC_FILE"
            grep '[0-9] \-> [0-9]' "$DIS_FUNC_FILE" \
                | sort -rnk 1 \
                | awk 'BEGIN{ max=0; n=0 } \
                       { n = n+1; \
                         if( n > 32 ) exit 0; \
                         if( $1 > max ) max=$1; \
                         if( ($1*100) > max) { \
                           print $_ \
                         } else { \
                           exit 0 \
                         } \
                       }' >> "$TCK_FUNC_FILE"
            echo -e "\n\n\nALL TICKS:" >> "$TCK_FUNC_FILE"
            grep '[0-9] \-> [0-9]' "$DIS_FUNC_FILE" >> "$TCK_FUNC_FILE"
        fi
    done

    # Удаляем временные файлы. Делать это нужно ниже цикла,
    # т.к. в нём неявно используется файл PTRACE_FILE
    rm -f $HOTF_FILE $PTRACE_FILE

} # dprof_make_event_prof()

# Снимаем профиль по наибольшим блокировкам
dprof_make_hot_events_prof()
{
    print_blank_line
    print_msg -log "$GREEN> MAKE HOT EVENTS PROFILE$BLANK" \
                   "$GREEN> ПОЛУЧАЕМ ПРОФИЛЬ ДЛЯ ЗНАЧИМЫХ СОБЫТИЙ$BLANK"
    IS_PROFILED=0

    # Определяем минимальное кол-во тиков события для сбора профиля (5%)
    MIN_TICKS=$((BUB_TICKS / 20))

    if [ "$MIN_TICKS" -le "$MIN_EVENT_NUM" ]
    then
        # Слишком мало событий (будет большая погрешность)
        print_msg -log "failed: too few bubs collected ($BUB_TICKS)" \
                       "failed: собрано слишком мало событий ($BUB_TICKS)"
        return 1
    fi

    EVENTS_LIST="\
    BUB_E2 \
    BUB_E2_MEM \
    BUB_E2_OPND \
    BUB_B \
    BUB_B_NOP \
    IB_NO_COMMAND \
    DAM_BELL \
    RBRANCH \
    DTLB_DEC_STALL_AAU \
    BUB_B_RF_SPILLFILL"

    if [ "$IS_CHANNEL_ANALYSIS" == 1 ]
    then
        EVENTS_LIST="$EVENTS_LIST \
        BUB_E2_ALU0_MEM \
        BUB_E2_ALU1_MEM \
        BUB_E2_ALU2_MEM \
        BUB_E2_ALU3_MEM \
        BUB_E2_ALU4_MEM \
        BUB_E2_ALU5_MEM \
        BUB_E2_ALU0_OPND \
        BUB_E2_ALU1_OPND \
        BUB_E2_ALU2_OPND \
        BUB_E2_ALU3_OPND \
        BUB_E2_ALU4_OPND \
        BUB_E2_ALU5_OPND"
    fi

    # Профилируем все события из списка
    for EVENT in $EVENTS_LIST
    do
        if [ "$EVENT" == "DTLB_DEC_STALL_AAU" ]
        then
            EVENT_COUNTER="$EVENT_STALL_AAU"
        else
            let "EVENT_COUNTER=EVENT_$EVENT"
        fi

        # Не профилируем слишком редкие события
        if [ "$EVENT_COUNTER" -lt "$MIN_TICKS" ]
        then
            print_msg -log "skip profiling $EVENT: event counter is too low" \
                           "не профилируем $EVENT: счётчик слишком мал"
            continue
        fi

        # Не профилируем BUB_E2, если в нём доминирует BUB_E2_MEM (>80%)
        if [ "$EVENT" == "BUB_E2" ] && \
           [ "$EVENT_BUB_E2_MEM" -ge "$((EVENT_BUB_E2 * 4 / 5))" ] && \
           [ "$EVENT_BUB_E2_MEM" -gt "$MIN_TICKS" ]
        then
            print_msg -log "skip profiling BUB_E2: event BUB_E2_MEM is dominating" \
                           "не профилируем BUB_E2: событие BUB_E2_MEM доминирует"
            continue
        fi

        # Не профилируем BUB_E2, если в нём доминируют BUB_E2_MEM и EVENT_BUB_E2_OPND (>90% в сумме)
        if [ "$EVENT" == "BUB_E2" ] && \
           [ "$((EVENT_BUB_E2_MEM + EVENT_BUB_E2_OPND))" -ge "$((EVENT_BUB_E2 * 9 / 10))" ] && \
           [ "$EVENT_BUB_E2_MEM"  -gt "$MIN_TICKS" ] && \
           [ "$EVENT_BUB_E2_OPND" -gt "$MIN_TICKS" ]
        then
            print_msg -log "skip profiling BUB_E2: events BUB_E2_MEM + BUB_E2_OPND are dominating" \
                           "не профилируем BUB_E2: события BUB_E2_MEM + BUB_E2_OPND доминируют"
            continue
        fi

        # Не профилируем BUB_B, если в нём доминирует BUB_B_NOP (>80%)
        if [ "$EVENT" == "BUB_B" ] && \
           [ "$EVENT_BUB_B_NOP" -ge "$((EVENT_BUB_B * 4 / 5))" ] && \
           [ "$EVENT_BUB_B_NOP" -gt "$MIN_TICKS" ]
        then
            print_msg -log "skip profiling BUB_B: event BUB_B_NOP is dominating" \
                           "не профилируем BUB_B: событие BUB_B_NOP доминирует"
            continue
        fi

        # Не профилируем RBRANCH, если DAM_BELL сопоставим с ним
        if [ "$EVENT" == "RBRANCH" ] && \
           [ "$EVENT_RBRANCH" -ge "$((EVENT_DAM_BELL * 3 / 5))" ]
        then
            print_msg -log "skip profiling RBRANCH: event DAM_BELL is comparable" \
                           "не профилируем RBRANCH: событие DAM_BELL имеет сравнимый счётчик"
            continue
        fi

        # Получаем интервал сэмплирования (DPROF_INTERVAL), чтобы оценить, сколько событий мы сможем собрать.
        # Если слишком мало, то не будем запускать профилирование (кому нужен профиль из 20 событий?).
        # При этом нужно понимать, что EVENT_COUNTER - это оценка тактов, а не количество событий.
        set_dprof_interval $EVENT
        MIN_TICKS2=$((1000 * DPROF_INTERVAL))

        if [ "$EVENT_COUNTER" -lt "$MIN_TICKS2" ]
        then
            print_msg -log "skip profiling $EVENT: event counter is too low" \
                           "не профилируем $EVENT: счётчик события слишком мал"
            continue
        fi

        # Собственно, запускаем профилирование события
        dprof_make_event_prof "$EVENT"
        IS_PROFILED=1
    done

    if [ "$IS_PROFILED" == 0 ]
    then
        print_msg -log "failed: no hot events found (program run is too short)" \
                       "failed: значимые события не найдены (запуск программы слишком короткий)"
        return 1
    fi
} # dprof_make_hot_events_prof()





################################################################################
# |> Секция кода скрипта                                                       #
################################################################################

# Проверяем наличие утилиты enconv.
# Делаем это в первую очередь, поскольку эта утилита используется при выдаче сообщений.
[ -z $LPERF_USE_ENCONV ]  && LPERF_USE_ENCONV=1
which enconv &> /dev/null || LPERF_USE_ENCONV=0

# Читаем переменные окружения
if [ ! -z $LPERF_LANG ]
then
    case "$LPERF_LANG" in
      "en") IS_LANG_ENG=1 ;;
      "ru") IS_LANG_ENG=0 ;;
      *) die "invalid value '$LPERF_LANG' [LPERF_LANG]" \
             "некорректное значение '$LPERF_LANG' [LPERF_LANG]"
    esac
fi

if [ ! -z $LPERF_INTERVAL ]
then
    is_number "$LPERF_INTERVAL" || \
            die "value '$LPERF_INTERVAL' is not a number [LPERF_INTERVAL]" \
             "значение '$LPERF_INTERVAL' не является числом [LPERF_INTERVAL]"
    [ "$LPERF_INTERVAL" -ge "$DPROF_INTERVAL_MIN" ] || \
            die "value '$LPERF_INTERVAL' is too low [LPERF_INTERVAL]" \
             "значение '$LPERF_INTERVAL' слишком мало [LPERF_INTERVAL]"
    DPROF_INTERVAL=$LPERF_INTERVAL
    IS_USER_DPROF_INTERVAL=1
fi

if [ ! -z $LPERF_INIT_CMD ]
then
    INIT_BEFORE_RUN_CMD=$LPERF_INIT_CMD
fi

if [ ! -z $LPERF_CLEAR_CMD ]
then
    CLEAR_AFTER_RUN_CMD=$LPERF_CLEAR_CMD
fi

if [ ! -z $LPERF_DPROF ]
then
    DPROF=$LPERF_DPROF
    [ -s "$DPROF" ] || die "specified dprof '$DPROF' not found [LPERF_DPROF]" \
                           "указанный dprof '$DPROF' не найден [LPERF_DPROF]"
fi

if [ ! -z $LPERF_LDIS ]
then
    LDIS=$LPERF_LDIS
    [ -s "$LDIS" ] || die "specified ldis '$LDIS' not found [LPERF_LDIS]" \
                          "указанный ldis '$LDIS' не найден [LPERF_LDIS]"
fi


# Устанавливаем переменную окружения LC_NUMERIC, чтобы у нас работал printf
export LC_NUMERIC="en_US.UTF-8"


# Читаем опции, поданные скрипту
until [ -z "$1" ]
do
  case "$1" in
    "--work-dir")
      shift
      WORK_DIR="$1"

      # Проверяем, что указанный каталог существует:
      [ -d "$WORK_DIR" ] || die "specified work dir '$WORK_DIR' not found [--work-dir]" \
                                "указанный рабочий каталог '$WORK_DIR' не найден [--work-dir]"

      # Превращаем относительный путь в абсолютный
      if [ "${WORK_DIR:0:1}" != "/" ]
      then
          WORK_DIR="`pwd`/$WORK_DIR"

          # Проверяем, что каталог по абсолютному пути существует:
          [ -d "$WORK_DIR" ] || internal_error "work dir '$WORK_DIR' not found by absolute path [--work-dir]"
      fi
      ;;

    "--interval")
      shift
      DPROF_INTERVAL="$1"
      is_number "$DPROF_INTERVAL" || \
            die "value '$DPROF_INTERVAL' is not a number [--interval]" \
             "значение '$DPROF_INTERVAL' не является числом [--interval]"
      [ "$DPROF_INTERVAL" -ge "$DPROF_INTERVAL_MIN" ] || \
            die "value '$DPROF_INTERVAL' is too low [--interval]" \
             "значение '$DPROF_INTERVAL' слишком мало [--interval]"
      IS_USER_DPROF_INTERVAL=1
      ;;
    "--channels") IS_CHANNEL_ANALYSIS=1 ;;
    "--list") print_supported_events ;;
    "--count") shift ; USER_CNT_EVENTS_LIST="$1" ; IS_AUTO_ANALYSIS=0 ;;
    "--prof") shift ; USER_PROF_EVENTS_LIST="$1" ; IS_AUTO_ANALYSIS=0 ;;
    "--init-cmd")  shift ; INIT_BEFORE_RUN_CMD="$1" ;;
    "--clear-cmd") shift ; CLEAR_AFTER_RUN_CMD="$1" ;;
    "--dprof")
      shift
      DPROF=$1
      [ -s "$DPROF" ] || die "specified dprof '$DPROF' not found [--dprof]" \
                             "указанный dprof '$DPROF' не найден [--dprof]"
      ;;
    "--ldis")
      shift
      LDIS=$1
      [ -s "$LDIS" ] || die "specified ldis '$LDIS' not found [--ldis]" \
                            "указанный ldis '$LDIS' не найден [--ldis]"
      ;;
    "--ru") IS_LANG_ENG=0 ;;
    "-v") IS_VERBOSE=1 ;;
    "--version") print_version ;;
    "--env")  print_supported_env ;;
    "-h")     print_supported_options ;;
    "-help")  print_supported_options ;;
    "--help") print_supported_options ;;
    "--") shift ; RUN_PROG_CMD="$@" ;;
    *) RUN_PROG_CMD="$@" ;;
  esac

  # За командой запуска теста опций скрипта быть не может
  [ "$RUN_PROG_CMD" == "" ] || break

  shift
done

[ "$RUN_PROG_CMD" != "" ] || die "no command specified" \
                                 "не указана исполняемая команда"

# Проверяем бинарник, создаём каталоги
init_work

# Проверяем поддержку события BUB_E2_MEM
TMP_STR=`$DPROF --mlist | grep BUB_E2_MEM`

if [ "$TMP_STR" == "" ]
then
    IS_BUB_E2_MEM=0
fi

if [ "$IS_AUTO_ANALYSIS" == 1 ]
then
    ##
    # АВТОМАТИЧЕСКИЙ РЕЖИМ РАБОТЫ (РЕЖИМ ПО УМОЛЧАНИЮ)
    #
    # Подсчитываем различные события
    ##
    print_msg -log "$GREEN> COUNT EVENTS$BLANK" \
                   "$GREEN> ПОДСЧИТЫВАЕМ СОБЫТИЯ$BLANK"

    # Считаем TICKS и EXEC
    dprof_count_ticks_exec

    # Считаем BUB_E2, IB_NO_COMMAND, DAM_BELL
    dprof_count_bube2_ibnocmd

    # Считаем BUB_B и BUB_B_NOP
    dprof_count_bubb_bubbnop

    if [ "$PERCENT_BUB_E2" -ge 10 ]
    then
        if [ $IS_BUB_E2_MEM == 0 ]
        then
            # В Elbrus E3S аппаратура не поддерживает эти события
            print_eblrus_e3s_warning
        else
            # Считаем BUB_E2_MEM, BUB_E2_OPND, DTLB_DEC_STALL_AAU
            dprof_count_bube2_mem_opnd

            if [ "$IS_CHANNEL_ANALYSIS" == 1 ]
            then
                if [ "$PERCENT_BUB_E2_MEM" -ge 10 ]
                then
                    # Считаем BUB_E2_MEM для различных каналов
                    dprof_count_bube2_mem
                fi

                if [ "$PERCENT_BUB_E2_OPND" -ge 10 ]
                then
                    # Считаем BUB_E2_OPND для различных каналов
                    dprof_count_bube2_opnd
                fi
            fi
        fi
    fi

    if [ "$PERCENT_BUB_B" -ge 10 ]
    then
      if [ "$PERCENT_IB_NO_COMMAND" -ge 10 ] || \
         [ "$PERCENT_BUB_B_NOP" -le 40 ] || \
         [ $((4*PERCENT_BUB_B_NOP)) -le $((3*PERCENT_BUB_B)) ]
      then
        # Считаем RBRANCH и BUB_B_RF_SPILLFILL
        dprof_count_rbranch_rf_spillfill
      fi
    fi

    # Печатаем сводную таблицу по всем блокировкам
    print_all_bubs



    ##
    # Снимаем профили по различным событиям
    ##

    # Снимаем профиль по времени работы
    dprof_make_event_prof "TICKS"

    # Снимаем профиль по всем значительным блокировкам
    dprof_make_hot_events_prof
    RES=$?

    if [ $RES != 0 ]
    then
        # Получаем самую большую блокировку (-> MAX_BUB)
        find_biggest_bub

        # Снимаем профиль по наибольшей блокировке
        dprof_make_event_prof "$MAX_BUB"
    fi
fi


if [ "$USER_CNT_EVENTS_LIST" != "" ]
then
    ##
    # РУЧНОЙ РЕЖИМ РАБОТЫ - ПОЛУЧАЕМ ПРОФИЛИ ДЛЯ ЗАДАННЫХ СОБЫТИЙ
    ##

    # Создаём лог, в который будем складывать счётчики событий
    USER_CNT_EVENTS_FILE="$WORK_DIR/user_events_cnt.txt"
    echo -n > $USER_CNT_EVENTS_FILE

    # Заменяем запятые на пробелы
    # Заменяем фиктивные события списком реальных событий
    CNT_EVENTS_LIST=`echo "$USER_CNT_EVENTS_LIST" \
        | sed 's/,/ /g' \
        | sed 's/BUB_E2_ALU_MEM/BUB_E2_ALU0_MEM BUB_E2_ALU1_MEM BUB_E2_ALU2_MEM BUB_E2_ALU3_MEM BUB_E2_ALU4_MEM BUB_E2_ALU5_MEM/g' \
        | sed 's/BUB_E2_ALU_OPND/BUB_E2_ALU0_OPND BUB_E2_ALU1_OPND BUB_E2_ALU2_OPND BUB_E2_ALU3_OPND BUB_E2_ALU4_OPND BUB_E2_ALU5_OPND/g'`
    declare -A EVENTS_QUEQUE

    # По всем заданным пользователем событиям - заполняем массив EVENTS_QUEQUE[channel][i]
    for USER_EVENT in $CNT_EVENTS_LIST
    do
      # WARNING! При добавлении новых событий, их необходимо также
      #          добавлять в функцию print_supported_events
      case "$USER_EVENT" in
        "TICKS")         push_event_to_queque "$USER_EVENT" 2 3 ;;
        "EXEC")          push_event_to_queque "$USER_EVENT" 2 3 ;;
        "BUB_E2"*)       push_event_to_queque "$USER_EVENT" 2 3 ;;
        "BUB_B"*)        push_event_to_queque "$USER_EVENT" 2 3 ;;
        "IB_NO_COMMAND") push_event_to_queque "$USER_EVENT" 2 3 ;;
        "DAM_BELL")      push_event_to_queque "$USER_EVENT" 1   ;;
        "RBRANCH")       push_event_to_queque "$USER_EVENT" 2 3 ;;
        *) unknown_event_error "$USER_EVENT" ;;
      esac
    done

    I=0

    # По всем событиям из очереди EVENTS_QUEQUE - запускаем подсчёт событий через dprof
    while /bin/true
    do
        USER_EVENTS_LIST=

        for PAR_CH in 0 1 2 3
        do
            USER_EVENT="${EVENTS_QUEQUE[$PAR_CH,$I]}"
            [ "$USER_EVENT" != "" ] && USER_EVENTS_LIST+=" $USER_EVENT"
        done

        [ "$USER_EVENTS_LIST" == "" ] && break
        dprof_count_user_events "$USER_EVENTS_LIST"
        let 'I++'
    done

    # Выводим сводную таблицу по всем счётчикам
    if [ -s "$USER_CNT_EVENTS_FILE" ]
    then
        print_blank_line
        print_line
        print_msg -log "             event         counter" \
                       "           событие         счётчик"
        cat $USER_CNT_EVENTS_FILE | sort -rnk 2 | tee -a $ANALYZER_LOG
        print_line
    fi
fi


if [ "$USER_PROF_EVENTS_LIST" != "" ]
then
    ##
    # РУЧНОЙ РЕЖИМ РАБОТЫ - ПОЛУЧАЕМ ПРОФИЛИ ДЛЯ ЗАДАННЫХ СОБЫТИЙ
    ##

    # Заменяем запятые на пробелы
    PROF_EVENTS_LIST=`echo "$USER_PROF_EVENTS_LIST" | sed 's/,/ /g'`

    # По всем заданным пользователем событиям
    for USER_EVENT in $PROF_EVENTS_LIST
    do
      # WARNING! При добавлении новых событий, их необходимо также
      #          добавлять в функцию print_supported_events
      case "$USER_EVENT" in
        "TICKS")              dprof_make_event_prof "$USER_EVENT" ;;
        "EXEC")               dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2")             dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_MEM")         dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_OPND")        dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU_MEM")
                              dprof_make_event_prof "BUB_E2_ALU0_MEM"
                              dprof_make_event_prof "BUB_E2_ALU1_MEM"
                              dprof_make_event_prof "BUB_E2_ALU2_MEM"
                              dprof_make_event_prof "BUB_E2_ALU3_MEM"
                              dprof_make_event_prof "BUB_E2_ALU4_MEM"
                              dprof_make_event_prof "BUB_E2_ALU5_MEM"
                              ;;
        "BUB_E2_ALU0_MEM")    dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU1_MEM")    dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU2_MEM")    dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU3_MEM")    dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU4_MEM")    dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU5_MEM")    dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU_OPND")
                              dprof_make_event_prof "BUB_E2_ALU0_OPND"
                              dprof_make_event_prof "BUB_E2_ALU1_OPND"
                              dprof_make_event_prof "BUB_E2_ALU2_OPND"
                              dprof_make_event_prof "BUB_E2_ALU3_OPND"
                              dprof_make_event_prof "BUB_E2_ALU4_OPND"
                              dprof_make_event_prof "BUB_E2_ALU5_OPND"
                              ;;
        "BUB_E2_ALU0_OPND")   dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU1_OPND")   dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU2_OPND")   dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU3_OPND")   dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU4_OPND")   dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_E2_ALU5_OPND")   dprof_make_event_prof "$USER_EVENT" ;;
        "IB_NO_COMMAND")      dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_B")              dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_B_NOP")          dprof_make_event_prof "$USER_EVENT" ;;
        "DAM_BELL")           dprof_make_event_prof "$USER_EVENT" ;;
        "RBRANCH")            dprof_make_event_prof "$USER_EVENT" ;;
        "BUB_B_RF_SPILLFILL") dprof_make_event_prof "$USER_EVENT" ;;
        *) unknown_event_error "$USER_EVENT" ;;
      esac
    done
fi



# Завершаем работу
finish_work

