Runtime eines Bashskriptes optimieren

Hinweis: In dem Thema Runtime eines Bashskriptes optimieren gibt es 17 Antworten auf 2 Seiten. Der letzte Beitrag () befindet sich auf der letzten Seite.
  • Liebe Forenmitglieder,


    wir möchten die Leistungsfähigkeit unseres Internetproviders testen. Dies muss unbedingt in einem Bashskript geschehen. Der hier relevante Teil des Skriptes ist dieser:

    Code
    data=$(ifconfig | grep "eth0 " -A9 | grep "RX bytes")
    rx=$(echo $data | cut -d ":" -f2 | cut -d " " -f1)
    tx=$(echo $data | cut -d ":" -f3 | cut -d " " -f1)


    Die Ausführzeit des Skriptteils auf unserem Rechner (mit "time" gemessen) beträgt 0.062 Sekunden.


    Wir fragen uns nun, ob man den Teil nicht performanter/schneller hin bekommt. Wir haben auch ein speziell dafür geschriebenes C-Programm getestet, das den Aufruf und die Ausgabenfilterung von "ifconfig" übernimmt. Die Ausführzeit sank damit auf 0.046 Sekunden.


    Geht es noch schneller, als mit einem C-Programm?


    Ausserdem muss man den Skript als Root ausführen, da möchten wir gerne drum herum kommen.


    Kann uns jemand helfen?

    Für den Inhalt des Beitrages 55058 haftet ausdrücklich der jeweilige Autor: Easy

  • Ihr könnt einfach aus dem Proc lesen.
    Da kann man direkt Kernelausgaben lesen und braucht weder Rootrechte noch andere Befehle.
    Zur Ausgabe des Gewünschten wären die "InOktets"" zu lesen:

    Code
    sed -n 's/^IpExt: \([[:digit:]]\{1,\} \)\{6\}\([[:digit:]]\{1,\} \).*/\2/p' /proc/net/netstat


    Das ist vielleicht nicht so leicht zu lesen.
    Abba ich geh jetzt mal Einkaufen und erläutere das später.
    Fröhliches Knobeln.

    GRINS


    Erst mal zum Deutschen. Es heißt "das Script". Auch wenn sich viele diese Unsitte mittlerweile angwöhnt haben, ist immer noch "das Script" einzig und allein richtig. Alles andere ist krankes Denglisch.


    "Alles ist eine Datei" ist ein Grundsatz aller Unices und demzufolge auch aller Linuces. (klingt schräg? is abba korrekt.)
    Nach diesem Grundsatz sind auch alle Dinge als Datei abgebildet. Es gibt Devicedateien und dergleichen mehr.
    Zur Laufzeit kann man im Verzeichnis /proc auf viele solcher Pseudodateien normal lesend zugreifen.
    Wir tun das logischerweise, wenn wir Echtzeitdaten über das Netzwerk haben wollen in /proc/net

    Code
    cat /proc/net/netstat

    gibt uns eine Zusammenfassung von "netSTATistics". Genau, das was wir suchen.
    Aber das babbelt ein wenig zu viel. Wir wollen ja nur die bis gerade empfangenen Bytes wissen.


    Dazu ist der sehr schnelle "StreamEDitor" sed hilfreich.
    Dessen prinzipielle Syntax lautet einfach:
    sed Ausdruck File


    Wir brauchen also einen Ausdruck, der uns genau diese Zahl aus der Pseudodatei "netstat" ausliest.
    sed Ausdruck /proc/net/netstat


    Ich verwende für den Ausdruck zwei sed Befehle. s für "Substitute" und pfür print.
    Der sed-Befehl s hat die Syntax
    s/suchausdruck/ersetzungsausdruck/
    wobei der Trenner beliebig wählbar ist.
    s:suchausdruck:ersetungsausdruck:ist also synonym. Sollte im Beispiel mit dem : selbst ein Doppelpunkt in der Suche oder im Ersetzen auftauchen, so muss ich ihn mit einem Backslash \: maskieren, damit er eben nicht für einen Trenner sondern für das Zeichen selbst steht.


    Um die Verwendung von sed klar zu zeigen, werde ich nun Schritt für Schritt die Lösung entwickeln.
    Als Erstes werden nur die Zeilen aus dem Output fischen, die uns interessieren.
    Wir gucken uns an, was ein einfaches cat /proc/net/netstat liefert:

    Code
    user@host~> cat /proc/net/netstat
    TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned OfoPruned OutOfWindowIcmps LockDroppedIcmps ArpFilter TW TWRecycled TWKilled PAWSPassive PAWSActive PAWSEstab DelayedACKs DelayedACKLocked DelayedACKLost ListenOverflows ListenDrops TCPPrequeued TCPDirectCopyFromBacklog TCPDirectCopyFromPrequeue TCPPrequeueDropped TCPHPHits TCPHPHitsToUser TCPPureAcks TCPHPAcks TCPRenoRecovery TCPSackRecovery TCPSACKReneging TCPFACKReorder TCPSACKReorder TCPRenoReorder TCPTSReorder TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo TCPLostRetransmit TCPRenoFailures TCPSackFailures TCPLossFailures TCPFastRetrans TCPForwardRetrans TCPSlowStartRetrans TCPTimeouts TCPRenoRecoveryFail TCPSackRecoveryFail TCPSchedulerFailed TCPRcvCollapsed TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv TCPAbortOnData TCPAbortOnClose TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger TCPAbortFailed TCPMemoryPressures TCPSACKDiscard TCPDSACKIgnoredOld TCPDSACKIgnoredNoUndo TCPSpuriousRTOs TCPMD5NotFound TCPMD5Unexpected TCPSackShifted TCPSackMerged TCPSackShiftFallback TCPBacklogDrop TCPMinTTLDrop TCPDeferAcceptDrop IPReversePathFilter TCPTimeWaitOverflow TCPReqQFullDoCookies TCPReqQFullDrop TCPRetransFail TCPRcvCoalesce TCPOFOQueue TCPOFODrop TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd
    TcpExt: 0 0 0 10 0 0 0 0 0 0 3795 0 0 0 0 0 11600 18 188 0 0 564714 214094 778353936 0 2084557 542775 16818 59616 0 1 0 0 0 0 0 0 0 0 20 0 0 0 0 1 0 0 85 0 0 44 0 297 1 12 0 331 6 0 13 0 0 0 0 0 0 0 0 0 0 0 6 0 0 16 0 0 0 0 0 74018 56261 0 1 302 302 0 0 0 0 0
    IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets
    IpExt: 0 0 237 243 0 0 4028981872 120947939 27043 27283 0 0

    Er liefert genau vier Zeilen. Einige davon sehr lange und sehr unversändlich. Aber es ist schon zu sehen, dass offensichtlich je zwei zusammengehörige Zeilen geliefert werden. Einmal für TCP einmal für IP. Zuerst eine Überschrift, und in der nächsten Zeile die zugehörigen Werte.


    Da wir auf hohe Performance achten, vermeiden wir jeden weiteren Aufruf irgendwelcher Tools und erchlagen die ganze Arbeit mit unserem feinen Substitute-Ausdruck.


    Zuerst fischen wir die Zeile Nummer 4, also die Zeile mit den Werten heraus, die alle empfangenen IP Pakete enthält.


    In REGulärenEXpressions wird immer / als Begrenzer verwendet. Wir können also die Zeile ausgeben lassen mit dem Konstrukt sed '/IpExt: /p' /proc/net/netstat Der Ausdruck sucht also Zeilen die ein IpExt: enthalten (man beachte das Leerzeichen nach dem Doppelpunkt, das AUCH mit gesucht wird) und gibt es mittels des Befehls p aus.

    Code
    user@host:~>sed  '/IpExt/p' /proc/net/netstat
    TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned OfoPruned OutOfWindowIcmps LockDroppedIcmps ArpFilter TW TWRecycled TWKilled PAWSPassive PAWSActive PAWSEstab DelayedACKs DelayedACKLocked DelayedACKLost ListenOverflows ListenDrops TCPPrequeued TCPDirectCopyFromBacklog TCPDirectCopyFromPrequeue TCPPrequeueDropped TCPHPHits TCPHPHitsToUser TCPPureAcks TCPHPAcks TCPRenoRecovery TCPSackRecovery TCPSACKReneging TCPFACKReorder TCPSACKReorder TCPRenoReorder TCPTSReorder TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo TCPLostRetransmit TCPRenoFailures TCPSackFailures TCPLossFailures TCPFastRetrans TCPForwardRetrans TCPSlowStartRetrans TCPTimeouts TCPRenoRecoveryFail TCPSackRecoveryFail TCPSchedulerFailed TCPRcvCollapsed TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv TCPAbortOnData TCPAbortOnClose TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger TCPAbortFailed TCPMemoryPressures TCPSACKDiscard TCPDSACKIgnoredOld TCPDSACKIgnoredNoUndo TCPSpuriousRTOs TCPMD5NotFound TCPMD5Unexpected TCPSackShifted TCPSackMerged TCPSackShiftFallback TCPBacklogDrop TCPMinTTLDrop TCPDeferAcceptDrop IPReversePathFilter TCPTimeWaitOverflow TCPReqQFullDoCookies TCPReqQFullDrop TCPRetransFail TCPRcvCoalesce TCPOFOQueue TCPOFODrop TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd
    TcpExt: 0 0 0 10 0 0 0 0 0 0 3805 0 0 0 0 0 11604 18 188 0 0 564714 214094 778353936 0 2085966 542775 16879 60100 0 1 0 0 0 0 0 0 0 0 20 0 0 0 0 1 0 0 85 0 0 44 0 297 1 12 0 335 6 0 13 0 0 0 0 0 0 0 0 0 0 0 6 0 0 16 0 0 0 0 0 74079 56261 0 1 303 303 0 0 0 0 0
    IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets
    IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets
    IpExt: 0 0 237 243 0 0 4030230542 121118098 27043 27283 0 0
    IpExt: 0 0 237 243 0 0 4030230542 121118098 27043 27283 0 0

    was aussieht, als hätte er nicht funktioniert.
    Aber die Zeilen, die uns interessieren werden zweimal ausgegeben.
    Der Befehl sed gibt JEDE Zeile aus, die er liest, UND die Zeilen, die man durch einen Ausdruck spezifiziert hat.
    Dafür kennt er den Schalter -n was genau dieses Verhalten unterdrückt.
    MERKE: Du willst fast immer -n. Ohne das sind sed Befehle selten sinnvoll.

    Code
    user@host:~>sed -n  '/IpExt/p' /proc/net/netstat
    IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets
    IpExt: 0 0 237 243 0 0 4030658529 121180330 27043 27283 0 0

    Das sieht schon erheblich besser aus.


    Jetzt grenzen wir das ganze ein wenig ein. Wir wollen ja nur einen einzigen Wert ausgeben.


    Ein RegEx kann nach beliebigen Zeichen und Zeichemmustern an beliebigen Stellen suchen. Wir verwenden hier einen sogenannten Anker: ^, was den Zeilenanfang meint. $ meint das Zeilenende.
    Außerdem verwenden wir Zeichenklassen.
    [At3] ist eine Zeichenklasse, die genau EIN EINZIGES Zeichen aus dieser Klasse matcht (to match ist der gängie Fachausdruck für das Zutreffen eines Ausdrucks). Der Ausdruck würde also matchen, wenn der untersuchte String genau ein A oder genau ein t oder genau eine 3 enthalten würde.




    Jetzt ist diese Vorlesung zu lange. Ich schreibe in einem eigenen Post weiter und verlinke hier zu Teil 2

    8 Mal editiert, zuletzt von uhelp ()

    Für den Inhalt des Beitrages 55061 haftet ausdrücklich der jeweilige Autor: uhelp

  • Sieh dir das mal an:

    Code
    time { data=$(</proc/net/dev);data=(${data##*eth0:}); }
    echo rx:${data[0]} tx:${data[8]}


    Bei mir 30 mal schneller als mit ifconfig-grep-cut und Rootrechte braucht es auch nicht. Die äußeren geschweiften Klammern sind nur für die Messung und ansonsten unnötig.

  • Sieh dir das mal an:

    Code
    time { data=$(</proc/net/dev);data=(${data##*eth0:}); }
    echo rx:${data[0]} tx:${data[8]}


    Bei mir 30 mal schneller als mit ifconfig-grep-cut und Rootrechte braucht es auch nicht. Die äußeren geschweiften Klammern sind nur für die Messung und ansonsten unnötig.

    da ist wohl was verrutscht.
    Jedenfalls, wenn man die Gesamtzeit inklusive der Ausgabe messen will:

    Code
    user@host:~>time { data=$(</proc/net/dev);data=(${data##*eth0:}); \
    echo rx:${data[0]} tx:${data[8]} ; }

    Für den Inhalt des Beitrages 55080 haftet ausdrücklich der jeweilige Autor: uhelp

  • @unhelp: Leider liefert dein grinsendes Dings hier schlichtweg nichts. "sed" hatten wir auch längst schon getestet. Du hast offensichtlich nicht bemerkt, daß die Zeit relevant ist, bis 2 Bytewerte in der Bash verfügbar sind, was klar aus meinem Beitrag und Skriptcode hervorgeht. Deine Belehrungen bezüglich Rechtschreibung sind ziemlich daneben, vor allem, wenn ich die Fehler und die Editierungen in deinem Beitrag zähle. Hier ein wirklich nicht böse gemeinter Tipp: dein ergoogeltes Halbwissen hättest du dir sparen können, damit kannst du nur Unwissende beeindrucken, vor den Kundigen machst du dich ziemlich lächerlich.
    Naja, trotzdem danke für den Versuch.



    Persephone: kurz, knackig und genau auf die Zwölf! 0.002 Sekunden, also mehr als 20 mal so schnell wie unser C-Programm! Schön, daß es einen echten Profi im Forum gibt.
    (Natürlich haben wir auch mal das C-Programm auf das Auslesen und Filtern von /proc/net/dev geändert, aber auch so ist es nicht annähernd so schnell, wie deine Lösung ganz ohne Bash-externe Programme.)


    Vielen Dank, auch für die begleitende Erklärung per Mail, unser Programmierer hat deinen Tipp zu "strace" verstanden.


    Dieses Thema ist gelöst.

    Für den Inhalt des Beitrages 55099 haftet ausdrücklich der jeweilige Autor: Easy

  • Easy bei mir liegt die Laufzeit des SED-Befehls mit korrekter Ausgabe zwischen 0.002 und 0.001


    Ich weiß nicht, was ihr daraus gemacht habt, aber bei meinen Maschinen funktioniert es überall.
    Auch eine Erweiterung, so dass er sowohl tr, wie auch rx liest, hat auf die messbare Zeit keinen Einfluß.
    Ich habe die sogar unter verschiedenen Bedingungen in verschiedenen Varianten tausendmal laufen lassen.
    Im Schnitt sind beide Lösungen gleich schnell.
    Außer, wenn die bash sed erst hashen muss.


    Und es heißt immer noch "das Script".


    Wenn du vielleicht posten könntest, welchen Befehl du genau angegeben hast, könnte man das evtl. klären.


    Für beide Werte:

    Code
    sed -n 's/^IpExt: \([[:digit:]]\{1,\} \)\{6\}\([[:digit:]]\{1,\} \)\([[:digit:]]\{1,\}\).*/\2 \3/p' /proc/net/netstat


    Und wenn du mit "in der bash verfügbar" meinst, dass es in einem Array landet, wie bei Persephone, dann:

    Code
    rxtx=( $(sed -n 's/^IpExt: \([[:digit:]]\{1,\} \)\{6\}\([[:digit:]]\{1,\} \)\([[:digit:]]\{1,\}\).*/\2 \3/p' /proc/net/netstat ) )


    Der Befehl -nur am Rande- funktioniert auch in allen möglichen shells und im POSIX Mode der bash.


    Dass ich ausführlich schreibe, liegt nicht daran, dass ich deine Kunden beeindrucken will, sondern, dass andere sehen, dass man mit sed, awk und grep ziemlich schnell effektiv Probleme lösen kann, es sich also lohnt, das zu lernen.
    Ein Forum ist nicht dazu da, für deine Kunden Lösungen zu finden, sondern für alle Wissen zu bieten.


    Wenn du relativ "einfache" sed-Befehle nicht hinkriegst, solltest du vielleicht mehr Lernen, statt Kunden zu bedienen.

    2 Mal editiert, zuletzt von uhelp ()

    Für den Inhalt des Beitrages 55100 haftet ausdrücklich der jeweilige Autor: uhelp

  • Ich kann zwar zum fachlichen Teil nichts beitragen, da ist uhelp sicher besser, aber zum Thema Script möchte ich ihm widersprechen.
    Hier im Duden mit Suchfrage: script
    Nichts für Ungut.
    Noch einen schönen Sonntag :)

    Für den Inhalt des Beitrages 56431 haftet ausdrücklich der jeweilige Autor: slughorn

  • Hallo @uhelp,


    vielen Dank für gute und ausführliche Erläuterung ;) . Und nachfolgend meine "Messwerte".
    Die Ausführungszeit bei mir wurde mit

    Code
    real    0m0.001s
    user    0m0.000s
    sys     0m0.000s


    für Deine Empfehlung und mit

    Code
    real    0m0.004s
    user    0m0.000s
    sys     0m0.003s


    für Persephone Empfehlung ermittelt. :D
    Gruß
    Boreas

    be tolerant - not ignorant
    Alle Hunde sind schwarz.
    Es gibt einen Hund der nicht weiß ist.

    Einmal editiert, zuletzt von Boreas ()

    Für den Inhalt des Beitrages 56435 haftet ausdrücklich der jeweilige Autor: Boreas