Выдалась свободная минутка и я решил потрогать немного свой bashui. Там еще трогать не перетрогать но обо всем по порядку. Тех кто не знаком с bashui прошу сюда. А в этой статье я решил затронуть злободневную тему повышения потенциала производительности на примере своего bashui.
Одним из основных элементов bashui является меню (items) — это «табличка» с произвольным количеством строк/столбцов для отображения/выбора какого-то набора элементов. Например списка хостов/команд как в demo_sshto или списка неймспейсов/подов и других k8s элементов как в demo_kubectl, любая текстовая информация которую необходимо как-то вертеть на bashui. Я уже не молод но все хочется какой-то пестрятины, разноцветных свистоперделок каких-то. В меню (items) это есть. Я добавил возможность «раскрашивать» как заголовки так и элементы данных. Но за все приходится платить. И плата порой чрезвычайно высока. За красивую картинку приходится платить потенциалом производительности, мда, никогда такого не было и вот опять.
Давайте посмотрим как это выглядит и по возможности попробуем усилить наш потенциал. Для теста производительности я подготовил вот такой датасет:
data=( #-------------{ first line - column descriptions }-------------------- $red'Item name' $blu'Item description' $grn'Status' #-----------------------{ the data }---------------------------------- 'first' $BLD$ylw'Long description text' 'true' 'second' 'Description 2' 'O_o' 'third' 'description 3' 'false' 'fourth' "${red}Long ${grn}description ${blu}text" 'true' '' '' '' $ylw'fifth' 'Description 2' 'O_o' 'sixth' 'description 3' 'false' 'midle' $grn'Long description text' 'true' 'long name row2' 'Description 2' 'O_o' '' '' '' 'row 3' 'description 3' 'false' 'row1' $blu'Long description text' 'true' 'row1' 'Long description text' 'true' 'last' 'Description 2' 'O_o' 'first' $BLD$ylw'Long description text' 'true' 'second' 'Description 2' 'O_o' 'third' 'description 3' 'false' 'fourth' "${red}Long ${grn}description ${blu}text" 'true' '' '' '' $ylw'fifth' 'Description 2' 'O_o' 'sixth' 'description 3' 'false' 'midle' $grn'Long description text' 'true' 'long name row2' 'Description 2' 'O_o' '' '' '' 'row 3' 'description 3' 'false' 'row1' $blu'Long description text' 'true' 'row1' 'Long description text' 'true' 'last' 'Description 2' 'O_o' )
Примерно 30 строк в 3 столбца, ~90 элементов, попробуем повертеть это на bashui. Запускаю тестовый скрипт и зажимаю кнопку «вниз» чтобы заставить интерфейс постоянно перерисовывать картинку:
Слева меню, справа топ -д1. Обратите внимание на самый жрущий CPU процесс — demo_menu
, почти 70%. Мда, не самый лучший потенциал, да? Да. В чем дело, где я обо… что пошло не так? Давайте попробуем разобраться. Вот код функции items:
items(){ # Main items piker function local x=${1:-1} # X(row) coordinate local y=${2:-1} # Y(line) coordinate local w=${3:-$COLUMNS} # window Width local h=${4:-5} # window Height, min is 5 nclm=($5) # Number of Columns or columns sizes in % of Width local name=$6 # List Name local tc=$7 # Text Color local rc=$8 # boRder Color local gc=$9 # backGround Color shift 9 local data=("$@") local text last c i w z column_size=() [[ $_currentItem_ ]] || _currentItem_=0 ((w-=x)) ((${#nclm[@]}>1)) && { for i in ${nclm[@]}; { i=$((w*i/100)); ((i<_min_culumn_size_)) && i=$_min_culumn_size_; column_size+=($i); } z=${column_size[@]} w=$((${z// /+})) nclm=${#nclm[@]} true } || { column_size=$((w/nclm)) ((column_size<_min_culumn_size_)) && column_size=$_min_culumn_size_ w=$((column_size*nclm)) for ((i=1; i<nclm; i++)); { column_size+=(column_size); } } # Print Heading local c1='┌' c2='┐' [[ $name ]] && { c1='├' c2='┤' XY $x $y "$rc┌$(line '─' $w)┐$DEF"; ((y++)) XY $x $y "$DEF$INV$rc│$DEF$INV$tc$(center_print $w "$name")$DEF$INV$rc│$DEF" ; ((y++)) } # Print column's titles local row=( "${data[@]:0:nclm}" ) for r in "${!row[@]}"; { item=${row[r]} cs=${column_size[r]} text+="$DEF$rc$c1$(center_print $((cs-1)) "{ $item }" '─')$DEF" text=${text//"{ "/"{ $DEF$tc"}; text=${text//" }"/"$DEF$rc }"}; text=${text//".}"/".$DEF$rc}"}; c1='┬' }; last='─'; ((cs==_min_culumn_size_)) && last=''; XY $x $y "$text$rc$last$c2"; ((y++)) # Print data data=( "${data[@]:$nclm}" ) local n=${#data[@]} local rows_avail=$((h-4)) local rows_total=$((n/nclm)) local current_row=$((_currentItem_/nclm)) _page_=$((rows_avail-1)) # pgUP/DOWN jump calculation ((rows_avail>rows_total)) && rows_avail=$rows_total _page_=$((rows_total-1)) j=0; ((current_row>=rows_avail)) && j=$((_currentItem_+nclm-rows_avail*nclm)) for ((i=j; i<rows_avail*nclm+j; i+=nclm)); do row=( "${data[@]:i:nclm}" ) sel= ((i==_currentItem_)) && { sel=$INV decolorizer "${row[0]}" "_target_" _target_=( "$_target_" "${row[@]:1}" ) } text= for r in "${!row[@]}"; { item=${row[r]} cs=${column_size[r]:-column_size} decolorizer "$item" decolorized_item color=$((${#item}-${#decolorized_item})) actual_color=${item:0:$color} ((${#decolorized_item}>=$cs-1)) && decolorized_item="${decolorized_item:0:$[cs-5]}..." item=$decolorized_item color=0 ((r==0)) && { [[ $item ]] || ((color++)) printf -v new_text "$DEF$rc│$DEF$sel$gc %s$DEF$sel$gc$tc%-$((cs-3+color))s" "$INV$BLD${decolorized_item:0:1}" "$actual_color${decolorized_item:1}" } || printf -v new_text "$DEF$rc│$DEF$sel$gc $tc%-$((cs-2+color))s" "$item" text+=$new_text } text="$text $DEF$rc│$DEF\n" XY $x $y "$text"; ((y++)) done # Print last line last_line= for cs in "${column_size[@]}"; { printf -v tmp_line "%$((cs-1))s┴"; tmp_line=${tmp_line// /─} last_line+=$tmp_line } XY $x $y "$DEF$rc└${last_line%┴*}─┘$DEF" # Show current row out of total rows if not all rows displayed ((rows_avail<rows_total)) && { hint="{ $((current_row+1)) of $rows_total }"; XY $((w/2+x-${#hint}/2)) $y "$hint"; } }
Невооруженным взглядом видно что тут используется вложенный цикл, он необходим для правильного отображения данных. Каждый элемент данных обесцвечивается т.к. цвет это просто доп символы из-за них длинна текста определяется неправильно. Затем происходит обрезание (О_о) эм, текста чтобы каждый элемент вписался в рамки таблицы, цвета возвращаются и строка печатается. Это и есть главный bitch бич потенциала, если таблица большая, много строк и столбцов такой алгоритм заставляет мой ноут сильно грустить. Что делать? Резать к чертовой матери. Весь этот вложенный цикл можно заменить одной (почти) командой! Как? Так:
printf -v data -- "$data_template" "${data[@]:$j:$((rows_avail*nclm))}"
Эта команда рисует всю основную таблицу, правда надо немного поколдовать до чтобы собрать $data_template
и после чтобы добавить выделение и от разукрашивания пришлось отказаться в пользу быстродействия. Эх. Так красивенько было с разноцветными строчками. Но полностью выкинуть разукрашивание рука не поднялась, в новой функции я оставил header практически без изменений, это же одна строка, производительность сильно не просаживает. Вот как выглядит новая функция:
items_fast(){ # Main items piker function local x=${1:-1} # X(row) coordinate local y=${2:-1} # Y(line) coordinate local w=${3:-$COLUMNS} # window Width local h=${4:-5} # window Height, min is 5 nclm=($5) # Number of Columns or columns sizes in % of Width local name=$6 # List Name local tc=$7 # Text Color local rc=$8 # boRder Color local gc=$9 # backGround Color shift 9 local text last sel_data sel_dummy c i w z column_size=() [[ $_currentItem_ ]] || _currentItem_=0 ((w-=x)) ((${#nclm[@]}>1)) && { for i in ${nclm[@]}; { i=$((w*i/100)); ((i<_min_culumn_size_)) && i=$_min_culumn_size_; column_size+=($i); } z=${column_size[@]} w=$((${z// /+})) nclm=${#nclm[@]} true } || { column_size=$((w/nclm)) ((column_size<_min_culumn_size_)) && column_size=$_min_culumn_size_ w=$((column_size*nclm)) for ((i=1; i<nclm; i++)); { column_size+=(column_size); } } # Data transformation local titles_items=( "${@:1:$nclm}" ) shift $nclm _target_=( "${@:_currentItem_+1:nclm}" ) local data=( "${@^}" ) sel_data="${data[$_currentItem_]:0:$((column_size-3))}" ((${#sel_data}<column_size)) && printf -v sel_data "$sel_data%$((column_size-${#sel_data}-3))s" printf -v sel_dummy "_SD_%$((column_size-7))s" data[$_currentItem_]="$sel_dummy" local n=$# local rows_avail=$((h-4)) local rows_total=$((n/nclm)) local current_row=$((_currentItem_/nclm)) _page_=$((rows_avail-1)) # pgUP/DOWN jump calculation ((rows_avail>rows_total)) && rows_avail=$rows_total _page_=$((rows_total-1)) j=0; ((current_row>=rows_avail)) && j=$((_currentItem_+nclm-rows_avail*nclm)) # Print Heading local c1='┌' c2='┐' [[ $name ]] && { local c1='├' c2='┤' XY $x $y "$rc┌$(line '─' $w)┐$DEF"; ((y++)) XY $x $y "$DEF$INV$rc│$DEF$INV$tc$(center_print $w "$name")$DEF$INV$rc│$DEF" ; ((y++)) } titles= last_line= printf -v data_template "%$((x-1))s" for i in ${!column_size[@]};{ cs=${column_size[i]:-column_size} # titles preparation titles+="$DEF$rc$c1$(center_print $((cs-1)) "{ ${titles_items[i]} }" '─')$DEF"; c1='┬' # main data template preparation data_template+="$DEF$rc│$DEF$gc$tc %-$((cs-3)).$((cs-3))b " # last line preparation printf -v tmp_line "%$((cs-1))s┴" tmp_line=${tmp_line// /─} last_line+=$tmp_line }; data_template+=" $DEF$rc│$DEF\n" titles=${titles//"{ "/"{ $DEF$tc"} titles=${titles//" }"/"$DEF$rc }"} titles=${titles//".}"/".$DEF$rc}"} # Print titles last='─'; ((cs==_min_culumn_size_)) && last=''; XY $x $y "$titles$rc$last$c2"; ((y++)) # Print data XY 1 $y '' printf -v data -- "$data_template" "${data[@]:$j:$((rows_avail*nclm))}" printf "${data/$sel_dummy/$INV${sel_data}}" ((y+=rows_avail)) # Print last line XY $x $y "$DEF$rc└${last_line%┴*}─┘$DEF" # Show current row out of total rows if not all rows displayed ((rows_avail<rows_total)) && { hint="{ $((current_row+1)) of $rows_total }"; XY $((w/2+x-${#hint}/2)) $y "$hint"; } }
Попробуем повертеть это на bashui, помогло или нет?
Всего то надо было добавить _fast
к названию функции и сразу стало почти в два раза быстрей. Вот справа процесс demo_menu_fast
показывает результат ~33% от CPU. Неплохо, а если продолжать увеличивать размер таблицы, добавить больше строк и столбцов старая функция будет тормозить еще сильней а _fast
функция практически не почувствует изменений. Потенциал заметно вырос.
Но старую функцию я выкидывать не стал, с маленькими менюшкам в которых необходима цветовая дифференциация штанов столбцов она прекрасно справиться а вот для большого объема лучше использовать быструю.
Почему это работает? Рассмотрим поближе команду printf
. Вот выдержка из хелпа:
$ printf --help printf: printf [-v переменная] формат [аргументы] Formats and prints ARGUMENTS under control of the FORMAT. ... The format is re-used as necessary to consume all of the arguments. If there are fewer arguments than the format requires, extra format specifications behave as if a zero value or null string, as appropriate, had been supplied.
т.е. все аргументы быдут выведены согласно указанному формату, простой пример:
$ printf '%s ' one one $ printf '%s ' one two one two $ printf '%s ' one two three one two three $ printf '%s, ' one two three one, two, three, $ printf '%s, %s, %s.' one two three one, two, three.
Модификаторы формата могут быть такие:
%s — строка как она есть
%b — строка с раскрытием ескейпоследовательностей (\n, \t, \r …)
%d — число
%f — число с плавающей точкой
…
Вот тут есть полный список.
Пример поинтересней, зададим вот такой массив:
data=( one two three four five six )
И попробуем вывести его содержимое в виде таблицы из 2х столбцов:
$ printf '%s %s\n' ${data[@]} one two three four five six
Вот такой простой формат, два элемента и переход строки в конце, выводит наш массив в два столбца. Получается что каждый раз берется 2 элемента данных из нашего массива и выводится согласно формату через пробел затем печатается переход строки, таким же образом печатаются следующие 2 элемента и так до конца массива. Кстати визуально удобно когда в коде массив выглядит так как он будет выводится:
data=( one two three four five six )
Добавим в формат выравнивание, самое длинное слово у нас 5 букв, значит надо выровнять все столбцы до 5 символов и палки чтобы это все выглядело как таблица:
$ printf '| %-5s | %-5b |\n' ${data[@]} | one | two | | three | four | | five | six |
А если необходимо ограничить ширину столбцов? Это тоже можно легко сделать так:
$ printf '| %-3.3s | %-3.3b |\n' ${data[@]} | one | two | | thr | fou | | fiv | six |
В этом примере я ограничил ширину столбцов 3 символами. Тоже самое происходит c bashui в этой части:
# main data template preparation data_template+="$DEF$rc│$DEF$gc$tc %-$((cs-3)).$((cs-3))b "
В $data_template
добавляется вот эта вот конструкция N (по кол-ву столбцов) раз, затем этот шаблон используется для обработки массива с данными:
printf -v data -- "$data_template" "${data[@]:$j:$((rows_avail*nclm))}"
Но тут я вывожу не на экран а в переменную $data
для постобработки. Вот так одна (почти) команда может заменить тягомотный цикл. Printf вообще очень удобный инструмент для работы с текстом в bash’е. Вот еще одна полезная возможность printf
. Когда надо добавить какой-то timestamp в ваш скрипт многие используют date
, как-то так:
time=$(date +'%Y-%m-%d') $ echo "bla $time bla" bla 2024-06-14 bla
А с printf
можно сделать так:
$ printf 'bla %(%Y-%m-%d)T bla' bla 2024-06-14 bla
Огромный потенциал.
Благодарности
Нахожусь под сильным (приятным) впечатлением от замечательной поездки в Грузию которую устроила компания Ivinco с которой я в данный момент сотрудничаю. А организовали и сделали по настоящему незабываемым наше пребывание в Грузии ребята из Provodnik‘а молодцы вообще, могут. Всем кто хочет отлично провести время в Грузии (и не только) рекомендую.
Было круто, cпасибо!
Ну вот потенциал приподняли, еще пара фраз и будем прощаться. На просторах github’а наткнулся на интересное bash творчество. Я выкладывал ссылки в своём телеграм-канале, но его читают не только лишь все а штуки, как мне кажется, достойны внимания широкой аудитории, поэтому писну тут в надежде на хабраэффект)
Рисовалка с поддержкой мыши drawin на bash’е. И классическая игра snake на bash’е.
ИМО код заслуживает внимания, посмотрите.
Кстати у меня в репах произошло небольшое изменение. В свое время я долго думал как назвать свою поделку для kubectl. В итоге ничего лучше kube-dialog не придумал, так и назвал. Kube-dialog это обертка kubectl
команд с помощью dialog’а, аналог sshto только для k8s. А недавно меня вштырило, я придумал короткое и ёмкое название — KUI (Kubectl User Interface)! Черт, почему я сразу об этом не подумал?) Но лучше поздно чем никогда, так что вместо kube-dialog’а теперь KUI!
Творите, выдумывайте, пробуйте и не разбулькивайте! 🙂
Лайки, пальцы, на ваше усмотрение.
ссылка на оригинал статьи https://habr.com/ru/articles/818497/
Добавить комментарий