Optimalizálás: awk, grep, bármi?

Fórumok

Van ez a parancs:

awk '/privvmpages/ {print $6}' /proc/user_beancounters

Alapvetően jól működik - nem meglepő, mert nem túl bonyolult -, de mivel sok konténeren, sokszor, sok paraméterre kell lefutnia, ezért érdekelne, hogy van-e valami az awk helyett, ami kevesebb erőforrást eszik, kevesebb libet tölt be, meg egyáltalán.

A beancounters (ha valakinél nincs kéznél):

Version: 2.5
       uid  resource                     held              maxheld              barrier                limit              failcnt
      141:  kmemsize                  6124602              8218054            218060400            218060400                    0
            lockedpages                     0                 4161                  256                  256                   45
            privvmpages                 46060                89367               521151               573266                    0
            shmpages                     4410                 4411               294861               294861                    0
            dummy                           0                    0                    0                    0                    0
            numproc                        26                   37                  240                  240                    0
            physpages                   26407                42805                    0  9223372036854775807                    0
            vmguarpages                     0                    0               294861               294861                    0
            oomguarpages                26407                42805               521151               521151                    0
            numtcpsock                     11                   26                  360                  360                    0
            numflock                        2                    9                  188                  206                    0
            numpty                          1                    2                   16                   16                    0
            numsiginfo                      0                    9                  256                  256                    0
            tcpsndbuf                  204920              1336472              1720320              2703360                    0
            tcprcvbuf                  180224               219200              1720320              2703360                    0
            othersockbuf                    0                49944              1126080              2097152                    0
            dgramrcvbuf                     0                 2312               262144               262144                    0
            numothersock                   41                   53                  360                  360                    0
            dcachesize                 169353               260648              3409920              3624960                    0
            numfile                       523                 1143                 9312                 9312                    0
            dummy                           0                    0                    0                    0                    0
            dummy                           0                    0                    0                    0                    0
            dummy                           0                    0                    0                    0                    0
            numiptent                      27                   27                  128                  128                    0

És a strace -c nálam:


strace -c awk '/privvmpages/ {print $6}' /proc/user_beancounters
0
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  -nan    0.000000           0         4           read
  -nan    0.000000           0         1           write
  -nan    0.000000           0         4           open
  -nan    0.000000           0         6           close
  -nan    0.000000           0         4           fstat
  -nan    0.000000           0        11           mmap
  -nan    0.000000           0         5           mprotect
  -nan    0.000000           0         2           munmap
  -nan    0.000000           0         3           brk
  -nan    0.000000           0         1         1 ioctl
  -nan    0.000000           0         4         4 access
  -nan    0.000000           0         1           execve
  -nan    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000                    47         5 total

A felhasználható eszközök azok, amik a legalapabb debian telepítésben benne vannak.

Fontos, hogy a parancsot a zabbix hívja meg, tehát nem az a cél hogy egy scripten belül lefutva legyen a legkisebb a terhelés, így a shell belső parancsok kiesnek, mert arra tippelek, hogy egy shellt már betölteni is több nyomor mint az awk-ot. De ez csak tipp.

Most megyek mosogatni, addig hátha beugrik valami. Eddig a grep -o és a sed jutott az eszembe.

Hozzászólások

Esetleg egy kisebb awk, ha létezik a jelenleginél kisebb. (ha gawk van felrakva, akkor a mawk szóba jöhet)
A grep-nek hogy adod meg, hogy csak a megfelelő mezőt írja ki?
A -o a teljes illeszkedő mintát kirakja, nem csak a numerikus részét. Vagy az nem gond?

Akit tudja, csinálja, aki nem tudja, tanítja... Hm... igazgatónak talán még jó lennék. :)

Ubuntu 12.04 64bit:
awk 92 call, mawk 48.
Ha ez számít.
A te fájlod nálam nem létezik, helyette a /proc/meminfo egy sorát választottam a próbához.

Viszont elnézve az strace-ed kimenetét, nálad is valami mawk v. hasonló lehet fenn. Ennél (nálam) az összes többi módszer rosszabb eredményt hozott, ha rendszerhívások számát nézzük.

uodate: találtam egy működő debiant. A mawk a default, ennél kisebbet nem nagyon találsz, azt hiszem.

Akit tudja, csinálja, aki nem tudja, tanítja... Hm... igazgatónak talán még jó lennék. :)

Ezt így nem mertem kijelenteni, mert mittudomén hol tart már a tudomány... ;) De kb. így gondolom én is.

Egyébként honnan jött egyáltalán az ötlet?
Miért akarsz ezen még gyorsítani?

Akit tudja, csinálja, aki nem tudja, tanítja... Hm... igazgatónak talán még jó lennék. :)

Az ötlet onnét, hogy tapasztaltam, hogy néha meglepő trükköket lehet csinálni olyan parancsokkal, amikre magamtól nem is gondoltam volna.

Az optimalizációnak nem a gyorsítás a legfőbb célja, hanem az erőforrás-felhasználás minimalizálása, mert ezek a parancsok sokszor fognak lefutni, párhuzamosan, több konténerben, szinte egyszerre, és nem akarom, hogy egy esetleg terhelt rendszert ez vigye a halálba :D

A grep mellé még kell egy cut is, és akkor már nem biztos, hogy megéri. Másrészt, ha a fájlban az elválasztó karakter szóköz, akkor sajnos nem is jó a cut.

Kiegészítés:
Sőt, nálam is (crunchbang) a grep önmagában „költségesebb” mint az mawk. A gawk-nál viszont jobb.

-----
A kockás zakók és a mellészabások tekintetében kérdezze meg úri szabóját.

Nem szorosan a Te problémádra megoldás de másokat is foglalkoztat hasonló dolog. Ill. vannak munin pluginek is amik ezeket az adatokat gyűjtik. A fenti megoldásokat nem néztem végig, de háta van közöttük olyan ami segíthet, ötletet adhat.

Ennél egyszerűbben aligha lehetne megoldani, és ha mawk az az awk*, akkor gyorsabban se.
Illetve igen: megírod C-ben. Te tudod, hogy tényleg fut-e majd annyiszor, hogy megérje az ezzel töltött idő, másfelől viszont max. 20 sor a C változat, amit teszteléssel együtt 1 óra alatt össze lehet hozni.

*: mostanában előkerült ez a $munkahelyen: a mawk konzisztensen 2-5× gyorsabb volt a gawknál, cserébe előfordult, hogy elvileg portolható programnál sem viselkedett úgy, ahogy kellett volna.

Azt kell mondjam, hogy ezen sokat lehet gyorsítani - bár tisztában vagyok vele, hogy a bázis elég kicsi ahhoz, hogy a változás felszín alatt maradjon.

1.
Meg kéne adni, hogy hol található a keresett kifejezés.

Akár a

$1 == "privvmpages"

akár a

/^[[:space:]]+privvmpages/

vagy a

/^(a megfelelő mennyiségű szóköz ide leszámolva)+privvmpages/

megteszi. Érzésem szerint az első gyorsabb, de mind veri azt az esetet, amikor az egész regexppel végig kell túrni az egész sort.

2.
Ha sokszor fog futni, akkor nem biztos, hogy sokszor érdemes meghívni.

A getline ciklusba szervezésével (a close()-ról sem megfeledkezve) összehozható egy, az auto beolvasási mechanizmussal megegyező folyaamat. Már csak időzíteni kell. Nincs mese, erre a system("sleep N") adódik a legkevésbé duhajkodó eszköznek.

1.: igen, erről meg szoktam feledkezni, bár valóban, a jelen esetben aligha van különbség
2.: ez nem az én választásom, a monitoring szerver ledob egy system.run kérést az agentnek (az most kb. midegy hogy hogy), és ebből lesz a cserebogár. Az adott gépen fut N darab konténer, konténerenként M darab figyelt értékkel, amik jó részét az agent tudja magától. Igyekszem a system.run számát alacsonyan tartani.

Normál körülmények között amúgy ez a megoldás nem okoz észrevehető terhelést, de már láttam terhelés alatt vidáman futkosó gépet kicsivel (néha sokkal :D) nagyobb terheléstől leroskadni. Nomeg felmerült bennem hogy íme a remek alkalom új dolgokat tanulni.

Esetleg még olyat lehetne, hogy a konténeren kívül az összes VZ értékét meg tudod nézni egy cat /proc/user_beancounters -el és minden hostról el tudod küldeni az adatokat a monitoring szervernek passive checkként (nagiosnál, máshol lehet máshogy hívják, de szerintem támogatott).

Ez volt az eredeti terv :D
Sajnos felmerültek problémák, a zabbix nem fogad el nem létező hostra nem létező itemeket, mert hová is tenné. Ezért aztán kalapálnom kellett egy scriptet ami a zabbix api-t használva felveszi a hostokhoz az itemeket, triggereket, miegyebeket. A template nem jó, mert egy hostra kellett felvenni az összes konténer adatait. Mire ez megírtam meg minden, úgy döntöttem, hogy akkora gányolás, hogy inkább nem (marha macerás karbantartani, új host, item, trigger, graph esetén megfelelően frissíteni, rajtam kívül senki nem tud érdemben hozzányúlni, sőt, egy év múlva már én se, ehe, stb. stb.).

Az utolsó szöget a koporsóba asszem a vzquota verte, amit megint másképp kell kezelni, és úgy döntöttem, hogy fusson az a nyomorult agent, nem eszik sok erőforrást, és többet ésszel, mint erővel, alacsonyan tartom a monitorozott értékek számát valamit a mintavétel frekvenciáját.

Most automata discovery van, ha felpattintok egy konténert, benne egy agenttel, akkor öröm van és boldogság.

Lehet valamit rosszul nézek, de én grep-pel kevesebb hívást kapok.

strace -c awk '/MemFree/ {print $2}' /proc/meminfo

calls / errors = 92 / 7

strace -c grep MemFree /proc/meminfo | grep -oE "[0-9]+"

calls / errors = 60 / 8

Ja bocs, egyrészt mawk-kal nálam is 47 hívás, másrészt most látom hogy több numerikus oszlop van, azt már drága lenne kikapni - kivéve ha az utolsó szám kell.

strace mutatja a lib betolteseket is, kezdve pl. az /etc/ld.so.conf -fal.

Forditsd statikus awk-ot, ha zavar, az lejjebb viszi.

Masik lehetoseg folyamatos awk invokalas helyett egy folyamatosan futo processzt csinalni. gawk pl. tud getline-t, rakd bele egy '{while(1)}' ciklusba es jo vagy.

Vagy ha mar ugyis egy shell script fut, miert nem csinalod bash-ban? pl.


while read -a A; do
    echo $A[6]
done </proc/user_beancounters

man bash, v4.1 - v4.2 korul igen tapos boviteseket kapott am!

Akkor marad a cut.

cut -d80- /proc/user_beanblabla

odaadja neked a 80. oszloptol minden sort. Hatranya, hogy a space-ek is ott maradnak az elejen, de azt valszeg ez a zabbix lekezeli.

De megintcsak, a strace szerint a teljesitmeny jo resze az ld-ben veszik el, azon meg csak a statikus forditas segit.

BTW, egesz biztos vagy abban, hogy a zabbix a parancsot nem egy shell-ben futtatja? Mert gyakori, hogy ilyet trukkoznek, hogy a kezdo sysadminoknak is mukodjon a a shell-style stdin/stdout redirect.

Akkor egyszeru, siman pakold a fenti while ciklust az sh -c parancssoraba. Nekem itt ubuntu-n megy, de a bubi a /bin/sh -t dash-sel csinalja.

Apropo, dash: kevesebb fuggosege van, emiatt gyorsabb a startup. Van viszont debianban is sash, standalone shell, ami statikusan van forditva. Valszeg ennek a legkisebb az overhead-je. Ha atallitod a /bin/sh -t erre (amugy is jo otlet, ha beut a krach, nem kell libekkel tokolni alapveto funkciokhoz), es a parancsnak egy while loop-ot adsz meg, pl. igy:


sh -c 'while read i; do echo $i; done' </etc/passwd

Akkor mar jo lesz szerintem.

Erosen ketlem, hogy egy statikus sh inditasa valamikor egy mukodo rendszer kellos kozepen kisebb overhead lenne, mint egy dinamikus inditasa. Ugyanis a dinamikusnak mar kb 80%-a benn van a memoriaban, es az adminisztracioval kell (szinten a memoriaban) molyolni, mig ugyanannak a shell-nek a statikus verziojanal az egesz binarist be kell huzni.

A statikusnak meg 100%-a van memoriaban. Egy processz inditasa ugy kezdodik, hogy mmap() -olja a binarist, es a kernel eleg okos ahhoz, hogy ujra felhasznalja a processzek kozott az mmap()-olt page-eket.

Ellenben a dinamikusnak bejatszik minden egyes inditasnal a linkeles, ahhoz meg konfig file-ok olvasgatasa, parse-olasa, stb...

Najó, de ezzel nem megyek semmire.

Először is az sh -c kiesik, mert akkor a végletes parancs sh -c "sh -c 'blabla'" lenne.

Másodszor ha while-read, akkor vagy grep soronként meghívva és utána cut (sokszoros erőforrás) vagy valami bash-dash-sh (only?) trükk, amivel a szövegfájl feldolgozható.

Esetleges megoldás lenne egy sh kompatibilis sor, ami külső parancsok nélkül végez mintaillesztést és stringvágást. A bash ugye tud olyat, hogy ${parameter/pattern/string}, ezzel esetleg lehetne operálni, de egyrészt semmi se garantálja hogy a /bin/sh az bash (és amúgy se az szerintem), illetve továbbra is adott a sorok olvasása a probléma, a read-while nálam riasztóan sok call-t dob.

Mókás, hogy a dash 1546 call-t hív, a bash 269-et, nálam:

strace -c /bin/bash -c 'while read i ; do echo $i ; done < /etc/passwd'
strace -c /bin/dash -c 'while read i ; do echo $i ; done < /etc/passwd'

Teljesen standard mintaillesztes sh-ban

a) case, hatulutoje, hogy nem regexp-et, hanem shell globbingot hasznal
b) modernebb shellekben a [[ sztring == minta ]] formaval is lehet, szinten globbing van regexp helyett

sztringvagas ${valtozo#glob} vagy ${valtozo%glob} (illetve ## es %% formaban) - ez es az a) pont teljesen elterjedt, mindegy h bash/dash/ash/zsh anyamkinja. (De ez itt mar nagyon offtopic)

ls -la | sh -c 'while read a b c d e f g; do echo $f; done'

(sajnos sh nem ismeri a 'read -a' -t, ezert a fenti trukkozes)

bash eleg effektiv interpreter, nem csodalom, hogy jobban optimalizal, mint a dash.
Viszont nagy elony, hogy nem kell awk-ot inditani, zubbix shelljebol megoldhato a dolog.

Igen, erre gondolhattam volna (ha nem használtam ezt százszor, akkor egyszer se, ehe), de sajnos úgy tűnik, hogy ez a drágább megoldás:

strace -c  /bin/dash -c 'while read a b c  d e f; do if [ $a = privvmpages ] ; then echo $f ; fi ; done < /proc/user_beancounters'

3312 syscall

strace -c /bin/dash -c 'echo ok' 

negyven syscall, így a dash bő 3000 sycall-t lő el. Tippre karakterenként olvas, ami elég elkeserítő. Az awk még mindig bőven vezet, a

strace -c /bin/dash -c "/bin/echo ok"

42 syscall plusz a

strace -c awk '/privvmpages/ {print $6}' /proc/user_beancounters

47 syscall, összesen 89.

Ha match esetén dobok egy exit 0-t, akkor a dash read-if lemegy 500-ra kb, de ugye ha a file végén van az egyezés, akkor ez se ér sokat.

Persze az egyes hívások nem egyelő terhelést adnak, de ez alapján még úgy néz ki, hogy az awk a nyerő. Persze jó kérdés, hogy maga az awk betuszkolása az tulajdonképp mekkora overhead a dash karakterenkénti olvasásával szemben.

Viszont elkezdtem azon merengeni, hogy nagy load esetén leállok pár érték (vagy a teljes konténer) monitorozásával, de ezt még átgondolom. Alighanem számolatlanul vannak még fontosabb dolgok :D

Lehet, hogy egy nagy hülyeséget írok, de vagy 10 évvel ezelőtt olvastam olyanról, hogy root jogokkal be lehet állítani, hogy bizonyos fájlok mindig a RAM-ban tárolt pufferben legyenek, onnan ne söpörje ki őket a kernel. Talán ha az awk és a hívott lib-ek ilyen módon lennének tárolva, akkor sosem kellene a merevlemezhez fordulni indításukkor, ami takaríthat meg időt. Persze, ha sűrűn vannak hívogatva, úgysem ürülnek ki a pufferből, úgyhogy nem biztos, hogy ez jó ötlet.

Szerintem az egy kicsit más volt.

Emlékeim szerint - és lehet hogy ez is balgaság - pl. a libeket nem tölti be minden egyes alkalommal a loader, hanem csak egy pointert bök rá, ha már az a ramban van. Nem tudom hogy a libekhez tartozhat-e adatszegmens, esetleg ahhoz kellhet külön művelet.

Hasonló megoldás rémlik a végrehajthaó binárisokkal kapcsolatban is.

Az mlock-kal kevertem, bocs. Az persze itt nem használható.

Amiről írtam, arra tényleg nincs ilyen megoldás, csak rosszul emlékeztem. Ha bármit is számítana az awk és a libek betöltési ideje, akkor azokar RAM-diskre kellene kitenni, de ez egy hülye erőltetett megoldás lenne, ami feltehetően keveset hoz a konyhára.

[De legalább jól sejtettem, hogy félrebeszélek. :-) ]

miert akarsz egy merhetetlen ideju taskot "optimalizalni"?

--
NetBSD - Simplicity is prerequisite for reliability


// fawk.c : minimalist awk

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

//returns index of a non-nul terminated substring within a non-nul terminated string
int str_idx(char* str, int str_len, char* substr, int substr_len)
{
    char* orig_str = str;

    while( substr_len <= str_len )
    {
        int i;
        for(i=0; i<substr_len; ++i)
        {
            if( str[i] != substr[i] )
                break;
        }

        if( i == substr_len )
            return str - orig_str;

        --str_len;
        ++str;
    }

    return -1;
}

int usage(int ret_code)
{
    printf(
        "usage: fawk <pattern> <column # to print> <input> [options]\n"
        "where\n"
        "   <pattern>             a word in quotes to search for in the columns - no regexp allowed\n"
        "   <column # to print>   serial of word to be printed\n"
        "   <input>               input file to read\n"
        " options:\n"
        "   --first_match_only    stops searching after the first match\n"
        "   --substring_match     the word is used as substring (default=exact match)\n"
        "\n"
        "example: fawk \"privvmpages\" 6 /proc/user_beancounters \n"
        );

    return ret_code;
}

int main(int argc, char* argv[])
{
    FILE* fi;
    int i;
    char line[4096+1];
    char* endp;

    int column_to_output;
    char* word_to_search_for;
    size_t word_to_search_len;
    int first_match_only = 0;
    int substring_match = 0;

    if( argc < 3 ) {
        printf("error: improper number of parameters\n");
        return usage(1);
    }

    word_to_search_for = argv[1];
    word_to_search_len = strlen(word_to_search_for);
    if( word_to_search_len <= 0 ) {
        printf("error: invalid word to search for: '%s'\n", argv[1]);
        return usage(2);
    }

    column_to_output = strtoul(argv[2], &endp, 10);
    if( endp == argv[2] || column_to_output < 0 ) {
        printf("error: invalid column number: '%s'\n", argv[2]);
        return usage(3);
    }

    for(i=4; i < argc; ++i)
    {
        if( strcmp(argv[i], "--first_match_only")==0 )
            first_match_only = 1;
        else if( strcmp(argv[i], "--substring_match")==0 )
            substring_match = 1;
    }

    fi = fopen(argv[3], "rt");
    if( !fi ) {
        printf("error: could not open input file '%s'\n", argv[3]);
        return usage(4);
    }

    while( fgets(line, sizeof(line), fi) )
    {
        char* p;
        size_t len;
        int word_num, match_found;
        char* word_to_output = NULL;
        size_t word_to_output_len = 0;

        line[sizeof(line)-1] = '\0';
        len = strlen(line);

        if( len > 0 && line[len-1] == '\n' )
            line[--len] = '\0';

        //if 0'th column requested, this means the whole line to be outputted
        if( column_to_output == 0 )
        {
            word_to_output = line;
            word_to_output_len = len;
        }

        //splitting row into words; finding the specified pattern in words
        match_found = 0;
        word_num = 0; 
        p = line;
        while(!match_found || word_to_output_len==0)
        {
            char* word_start_p;
            size_t word_len;

            //skip spaces before next word
            while( isspace(*p) )
                ++p;

            if( !*p )
                break;  //line end, no more words

            //word starting char found
            ++word_num;
            word_start_p = p++;

            //find word end
            while( *p && !isspace(*p) )
                ++p;

            word_len = p - word_start_p;

            //if we are at the N'th column, store the word to be outputted
            if( column_to_output == word_num )
            {
                word_to_output = word_start_p;
                word_to_output_len = word_len;
            }

            //check if this word matches the pattern
            if( !match_found && word_to_search_len <= word_len )
            {
                if( substring_match )
                    match_found = str_idx(word_start_p, word_len, word_to_search_for, word_to_search_len) >=0;
                else
                    match_found = strncmp(word_start_p, word_to_search_for, word_to_search_len) == 0;
            }
        }

        if(match_found && word_to_output_len > 0)
        {
            printf("%s\n", word_to_output);

            if( first_match_only )
                break;
        }
    }

    fclose(fi); fi = NULL;

    return 0;
}

Ehhez kb a legbutább fordító is elég kell legyen, és nincs benne semmi OS-specifikus. De ha már terjedni akar, akkor

- minden warning/error printf( ... ) lecserélendő fprintf( stderr, ... ) -re
- meg pl. az utolsó fi=NULL ; utasítást optimalizáld ki te magad, ne bízd a fordítóra

meg lehetne még hasogatni a szőrszálakat

Szerk: ja, és szerintem copy and paste-tel együtt is hamarabb kiderült volna, ha kipróbálod, mint várni a válaszra :-)

BTW, goto serverfault.com ilyen kerdesekkel batran.