Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2316
Wohnort: Meiningen
|
Ah, verstehe. Kann man die if-Bedingungen irgendwie schachteln oder per UND verknüpfen, und wenn ja, würde das die Laufzeit reduzieren? In diesem Zusammenhang habe ich leider festgestellt, dass auch in den log.*.old Dateien aktuelle Einträge vorhanden sein können. Wenn ich awk alle Logs durchsuchen lasse, steigt die Ausführungszeit auf 20 Sekunden (vorher hatte ich 5 Sekunden, weil nur log.*.NT1 und log.*.SMB* berücksichtigt wurden). Grüße
Dalai
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12802
|
Dalai schrieb: Ah, verstehe. Kann man die if-Bedingungen irgendwie schachteln oder per UND verknüpfen,
Generell, ja. Aber hier sind die beiden Bedingungen ja in verschiedenen Pattern-Actions. Man kann natürlich eine Variable definieren, die dann Teil der Bedingung der zweiten Action wird.
und wenn ja, würde das die Laufzeit reduzieren?
Das kann man so allgemein nicht sagen. In diesem Fall wird es wohl keine oder nur wenige Fehlerzeilen geben, so dass Dir das nicht wirklich etwas bringt.
In diesem Zusammenhang habe ich leider festgestellt, dass auch in den log.*.old Dateien aktuelle Einträge vorhanden sein können. Wenn ich awk alle Logs durchsuchen lasse, steigt die Ausführungszeit auf 20 Sekunden (vorher hatte ich 5 Sekunden, weil nur log.*.NT1 und log.*.SMB* berücksichtigt wurden).
Sind 20 Sekunden ein Problem? (Du könntest übrigens mal einen reinen Lese-Vergleich mittels wc machen, um zu sehen, ob wirklich awk das Problem ist oder IO.)
|
tomtomtom
Supporter
Anmeldungsdatum: 22. August 2008
Beiträge: 53485
Wohnort: Berlin
|
Dalai schrieb: ich brauche mal wieder die Hilfe der Experten. Ich hätte gerne eine Auswertung der Samba-Logs, genauer gesagt die der Loginversuche.
Mit einem Samba aus 14.04 hast du ganz andere Probleme als die Auswertung der Login-Versuche. Mit dem Rest des Systems natürlich erst recht...
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Bei Logfiles kann man in der Regel ein festes Muster voraussetzen. Demnach hier ein Ansatz, der sich auf die einzelnen Positionen verlässt:
$ awk '/FAILED with error/ {print "["ARGV[ARGIND]"]", "["date, time"]", "user", $5, "->", $7, $11} {date=substr($1,2,10); time=substr($2,1,8)}' test.log
[test.log] [2019/11/11 01:29:14] user [administrator] -> [administrator] NT_STATUS_WRONG_PASSWORD
[test.log] [2019/11/11 01:47:16] user [administrator] -> [administrator] NT_STATUS_WRONG_PASSWORD
[test.log] [2019/11/11 01:54:48] user [admin] -> [administrator] NT_STATUS_WRONG_PASSWORD
[test.log] [2019/11/11 17:07:52] user [hans] -> [hans] NT_STATUS_NO_SUCH_USER Alternativ kann man die substr()-Aufrufe auch nur bei Bedarf machen:
awk '/FAILED with error/ {d=substr(_[1],2,10); t=substr(_[2],1,8); print "["ARGV[ARGIND]"]", "["d,t"]", "user", $5, "->", $7, $11} {_[1]=$1; _[2]=$2}' test.log Ist wahrscheinlich noch etwas schneller, aber auch komplexer. Wenn einem das zu kompakt bzw unleserlich ist, sind natürlich längere Variablennamen und einen Aufsplittung auf mehrere Zeilen denkbar...
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Dalai schrieb: Ich habe den RegEx noch etwas optimiert und verändert: awk '/^\[/ {if (match($0, /^\[([0-9]{4}(\/[0-9]{2}){2} ([0-9]{2}:){2}[0-9]{2}).*auth_check_ntlm_password/, matches)) time=matches[1]}
/^ / {if (match($0, /Authentication for (user .*) FAILED with error ([a-zA-Z_]+)/, matches))
print "[" ARGV[ARGIND] "] [" time "] " matches[1] " " matches[2]}' logfiles Die Angabe auth_check_ntlm_passwort in der ersten Zeile hat die Ausführungszeit um ca. 40% reduziert (von ~8,5 auf ~5 Sekunden bei um die 400 Logdateien), weil nicht mehr jede zweite Logzeile matcht sondern nur noch die mit Loginereignissen (erfolgreich oder nicht). Und die detailierte Angabe des Usernamens habe ich verallgemeinert, sowie die Optionalität des FAILED entfernt.
Regex-Aufrufe sind halt trotzdem recht teuer in der Laufzeit. Die beste Optimierung ist, möglichst auf reguläre Ausdrücke zu verzichten und an den Stellen die mitgebrachten Funktion für String-Manipulationen der Programmiersprache zu verwenden. Die sind oft leserlicher und performanter, weil sie keine Alleskönner sein müssen, sondern quasi gemäß Unix-Philosophie eine ganz bestimmte Aufgabe erledigen. Dazu muss man sich dann aber auch "trauen", begründete Annahmen über die Beschaffenheit der vorliegenden Daten zu treffen.
|
Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2316
Wohnort: Meiningen
|
rklm schrieb: Sind 20 Sekunden ein Problem? (Du könntest übrigens mal einen reinen Lese-Vergleich mittels wc machen, um zu sehen, ob wirklich awk das Problem ist oder IO.)
Ja, es liegt am awk . Laut top lastet awk während der gesamten Zeit einen CPU-Kern komplett aus, und ein time vor dem Befehl sagt: real 0m20.877s
user 0m20.197s
sys 0m0.670s Und natürlich sind 20 Sekunden ein Problem, wenn man so lange darauf warten muss, bis auf der Konsole die Ausgabe erscheint (und der Prompt wiederkommt). snafu1 schrieb: Bei Logfiles kann man in der Regel ein festes Muster voraussetzen. Demnach hier ein Ansatz, der sich auf die einzelnen Positionen verlässt:
Dankeschön! Vorteil: nur ~6 Sekunden Laufzeit. Nachteil: bei erfolgreichen Logins würde nicht die komplette Gruppe [user1] → [user2] → [user3] ausgegeben. Ist aktuell kein Problem, weil es nur um die fehlgeschlagenen Logins geht.
Ist wahrscheinlich noch etwas schneller, aber auch komplexer.
Es ist ein wenig schneller: ~4,7 statt ~6 Sekunden. Was ich nicht verstehe: Wo findet die Prüfung auf "Bedarf" statt? In welcher Reihenfolge wertet awk die Blöcke aus, von vorn nach hinten oder umgekehrt? snafu1 schrieb: Regex-Aufrufe sind halt trotzdem recht teuer in der Laufzeit.
Das hab ich gemerkt. Wobei grep das um Faktoren schneller erledigt (weniger als 1 Sekunde), auch wenn mir bewusst ist, dass awk deutlich mächtiger ist. Grüße
Dalai
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Hier mal als ordentliches Skript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #!/usr/bin/env -S awk -f
FNR == 1 {
filename = "[" ARGV[ARGIND] "]"
}
/FAILED with error/ {
date = substr(oldline[1], 2, 10)
time = substr(oldline[2], 1, 8)
userchain = $5
for (i = 6; $i == "->"; i += 2)
userchain = userchain " " $i " " $(i+1)
print filename, "[" date, time "]", "user", userchain, $NF
}
{
oldline[1] = $1
oldline[2] = $2
}
|
Ausführbar nach dem Schema:
./parselog.awk test1.log test2.log usw. NF steht für Number of Fields, d.h. NF ist die letzte Spalte und $NF holt deren Inhalt. NR meint Number of Rows (insgesamt) und FNR steht für die Zeilen der aktuellen Datei (File). Dies setze ich ein, damit die Formatierung für den Dateinamen nur zusammengesetzt wird, wenn eine neue Datei beginnt. Das Ergebnis wird dann für die print-Aufrufe wiederverwendet.
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Dalai schrieb: Dankeschön! Vorteil: nur ~6 Sekunden Laufzeit. Nachteil: bei erfolgreichen Logins würde nicht die komplette Gruppe [user1] → [user2] → [user3] ausgegeben. Ist aktuell kein Problem, weil es nur um die fehlgeschlagenen Logins geht.
Die jetzige Lösung dürfte damit klar kommen. Du kannst den Ansatz gerne bei Bedarf übernehmen bzw anpassen. Dalai schrieb: Was ich nicht verstehe: Wo findet die Prüfung auf "Bedarf" statt? In welcher Reihenfolge wertet awk die Blöcke aus, von vorn nach hinten oder umgekehrt?
Mit "Bedarf" meinte ich, dass er es dann nicht für jede Zeile macht, sondern nur dann, wenn das Pattern passt. awk wertet von links nach rechts und von oben nach unten aus. Daher ist es auch wichtig, dass sich die Spalten der aktuellen Zeile erst zum Schluss gemerkt werden. Somit "sieht" die nächste Zeile noch den vorherigen Stand, was halt auch Sinn der Sache ist.
snafu1 schrieb: Regex-Aufrufe sind halt trotzdem recht teuer in der Laufzeit.
Das hab ich gemerkt. Wobei grep das um Faktoren schneller erledigt (weniger als 1 Sekunde), auch wenn mir bewusst ist, dass awk deutlich mächtiger ist.
Das mag sein. Aber grep kann weniger, wie du schon geschrieben hast. Du müsstest es dann mit anderen Tools kombinieren, wenn die Aufgabe komplexer wird. Das heißt, Pipes einsetzen und womöglich grep mehrfach aufrufen müssen. Dann relativiert sich der Unterschied wahrscheinlich. EDIT: Nochmal überarbeitet zwecks besserer Darstellung:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #!/usr/bin/env -S awk -f
FNR == 1 {
print "\n" ARGV[ARGIND] ":"
}
/FAILED with error/ {
date = substr(oldfields[1], 2, 10)
time = substr(oldfields[2], 1, 8)
userchain = $5
for (i = 6; $i == "->"; i += 2)
userchain = userchain " -> " $(i+1)
print "[" date, time "]", $NF, userchain
}
{
oldfields[1] = $1
oldfields[2] = $2
}
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12802
|
Dalai schrieb:
Ja, es liegt am awk . Laut top lastet awk während der gesamten Zeit einen CPU-Kern komplett aus, und ein time vor dem Befehl sagt: real 0m20.877s
user 0m20.197s
sys 0m0.670s
Iiih! Rein aus Neugier, kannst Du mal diese Ruby-Version meines awk -Skriptes probieren und uns wissen lassen, wie lange die auf der selben Eingabe braucht? 1
2
3
4
5
6
7
8
9
10
11
12 | #!/usr/bin/ruby
time = nil
ARGF.each_line do |line|
case line
when /^\[([0-9]{4}(\/[0-9]{2}){2} ([0-9]{2}:){2}[0-9]{2})/
time = $1
when /[aA]uthentication for (user \[[^\]]+\] -> \[[^\]]+\]) (?:FAILED with error ([a-zA-Z_]+))?/
puts "[#{ARGF.filename}] [#{time}] #$1 #$2"
end
end
|
Einfach normal aufrufen und die Namen der Logfiles als Argumente übergeben. Edit: time wäre natürlich schick.
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Ebenfalls aus Neugier: Wird es schneller, wenn du im AWK-Skript den Teil mit den "oldfields" wie folgt ersetzt?
| /^\[/ {
oldfields[1] = $1
oldfields[2] = $2
}
|
Das trifft alle Zeilen, die mit "[" beginnen. Sieht ein bißchen aus wie ein verkrüppelter ASCII-Fisch. Die Schreibweise ist aber notwendig, weil die eckige Klammer sonst eine andere Bedeutung für AWK hätte.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12802
|
|
Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2316
Wohnort: Meiningen
|
Sorry, dass ich mich jetzt erst wieder melde, aber ich hatte die vergangenen Tage noch andere Baustellen zu beackern. snafu1 schrieb: Mit "Bedarf" meinte ich, dass er es dann nicht für jede Zeile macht, sondern nur dann, wenn das Pattern passt.
Das Pattern ist doch das 'FAILED with error', oder sehe ich das falsch? Das wird doch aber in beiden Fällen gesucht.
awk wertet von links nach rechts und von oben nach unten aus. Daher ist es auch wichtig, dass sich die Spalten der aktuellen Zeile erst zum Schluss gemerkt werden. Somit "sieht" die nächste Zeile noch den vorherigen Stand, was halt auch Sinn der Sache ist.
Verstehe.
EDIT: Nochmal überarbeitet zwecks besserer Darstellung:
Das gibt die Namen aller Logdateien aus, auch wenn in dieser keine Logins zu finden sind. Laufzeitmäßig macht es keinen Unterschied: zwischen drei und vier Sekunden für alle deiner awk -Varianten (meist so ~3,5s). Wichtig: Weil ich ein paar alte Logs weggeräumt habe, sind die neuen Laufzeiten nicht mit den alten vergleichbar! Vor waren's ~6 Sekunden. rklm schrieb: Iiih! Rein aus Neugier, kannst Du mal diese Ruby-Version meines awk -Skriptes probieren und uns wissen lassen, wie lange die auf der selben Eingabe braucht?
Ruby ist nicht installiert, und das werde ich auf diesem Produktivsystem nur zum Testen auch nicht machen. Python und Perl sind vorhanden. Grüße
Dalai
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
In Python natürlich auch machbar:
| #!/usr/bin/env python3
import fileinput
import re
for line in fileinput.input():
fields = line.split()
if "FAILED with error" in line:
timestamp = f"[{oldfields[0][1:11]} {oldfields[1][:8]}]"
userchain = re.search(r"(\[.*]( -> )?)+", line).group()
print(fileinput.filename(), timestamp, fields[-1], userchain)
oldfields = fields
|
Auch hier können wieder die Dateinamen als Argumente mitgegeben werden. Und auch AWK kann das natürlich kürzer:
| #!/usr/bin/env -S awk -f
/FAILED with error/ {
match($0, /(\[.*]( -> )?)+/, usermatch)
print(ARGV[ARGIND], timestamp, $NF, usermatch[0])
}
/^\[/ {timestamp = substr($0, 1, 20) "]"}
|
EDIT:
Wer's mag, kann das Matchen auch vor den Block schieben:
| #!/usr/bin/env -S awk -f
match($0, /(\[.+]( -> )?)+ FAILED with error/, m) {
print(ARGV[ARGIND], timestamp, $NF, m[1])
}
/^\[/ {timestamp = substr($0, 1, 20) "]"}
|
|
Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2316
Wohnort: Meiningen
|
snafu1 schrieb: In Python natürlich auch machbar:
Das läuft bei mir nicht: | File "/tmp/a.py", line 8
timestamp = f"[{oldfields[0][1:11]} {oldfields[1][:8]}]"
^
SyntaxError: invalid syntax
|
Und auch AWK kann das natürlich kürzer:
| #!/usr/bin/env -S awk -f
/FAILED with error/ {
match($0, /(\[.*]( -> )?)+/, usermatch)
print(ARGV[ARGIND], timestamp, $NF, usermatch[0])
}
/^\[/ {timestamp = substr($0, 1, 20) "]"}
|
Laufzeit auch um die 3,5 Sekunden. Nur die Spalten werden in der falschen Reihenfolge ausgegeben (was aber trivial zu beheben ist/war) 😉.
Wer's mag, kann das Matchen auch vor den Block schieben:
| #!/usr/bin/env -S awk -f
match($0, /(\[.+]( -> )?)+ FAILED with error/, m) {
print(ARGV[ARGIND], timestamp, $NF, m[1])
}
/^\[/ {timestamp = substr($0, 1, 20) "]"}
|
Laufzeit knapp 8 Sekunden. Da matchen wohl viel mehr Zeilen, nicht nur die die 'FAILED with error' enthalten. Grüße
Dalai
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Dalai schrieb: Das läuft bei mir nicht: | File "/tmp/a.py", line 8
timestamp = f"[{oldfields[0][1:11]} {oldfields[1][:8]}]"
^
SyntaxError: invalid syntax
|
Hatte ich schon befürchtet. Dann läuft da eine ältere Python-Version, die noch nicht die relativ neuen f-Strings kennt. Man muss dann stattdessen format() benutzen. Ist aber im Grunde auch egal, da das Vorhaben mit AWK eh leichter zu lösen ist.
Laufzeit auch um die 3,5 Sekunden. Nur die Spalten werden in der falschen Reihenfolge ausgegeben (was aber trivial zu beheben ist/war) 😉.
Okay, ich wollte mal kreativ sein. Bau dir die Spalten halt in der Reihenfolge, die für dich am besten ist. ☺
Laufzeit knapp 8 Sekunden. Da matchen wohl viel mehr Zeilen, nicht nur die die 'FAILED with error' enthalten.
Normalerweise nicht. Oder übersehe ich was? Ich vermute eher, dass AWK so optimiert ist, dass reiner Text schnell erkannt wird und dass es bei komplexeren Pattern erheblich Laufzeit kostet. Das Beispiel hatten wir ja schon auf Seite 1 hier im Thread. Na, dann lass ich's jetzt auch mal gut sein. Du solltest nun genug Code vorliegen haben. 😉 EDIT:
Bezüglich der schlechten Performance beim letzen Skript: Das liegt wohl daran, wie Text im Falle von regulären Ausdrücken verarbeitet wird. In dem Fall wird nämlich alles "näher angeschaut", was eckige Klammern hat. Und dann wird zum Schluss erkannt, dass kein "FAILED with error" im Anschluss vorkommt, sodass die ganze Vorarbeit wieder verworfen werden muss. Dies kostet in der Summe (also bei vielen Zeilen bzw vielen "Misses") spürbar zusätzliche Zeit. Somit ist es auch kein spezielles AWK-Phänomen, sondern eher ein Problem, wenn man das Regex-Pattern entsprechend ungünstig formuliert. So wie ich es tat, da ich dies nicht auf dem Schirm hatte...
|