Heute habe ich mich (unter anderem) mit einem Problem beschäftigt das auf den ersten Blick trivial aussieht. Ich zumindest habe bislang keine Lösung gefunden die mir wirklich gefällt. Mal sehen ob was dabei rauskommt wenn ich das hier ‚crowdsource’…
Aufgabenstellung: Zähle, wie viele Prozesse eines bestimmten Programmes schon länger als 30 Minuten laufen.
Klingt einfach, oder?
Die Ausgaben von ps zu parsen erscheint mir dabei aber zu fehleranfällig. Da müsste man zu viel berücksichtigen: Prozesse die vielleicht länger als einen Tag laufen, Prozesse die über Mitternacht gelaufen sind… ich habe nicht getestet wie es aussieht wenn man an der Zeitzone rumspielt… Nein, das kann nicht der richtige Ansatz sein.
Meine erste echte Lösung sieht zwar hässlich originell aus, funktionierte aber im Test (zu Demozwecken suche ich mal nach meinem Firefox):
echo | killall -i --older-than 30m firefox 2> /dev/null | tr -cd '?' | wc -c
Mit -i ist killall ‚interaktiv‘, fragt also zu jedem gefundenen Prozess freundlich nach ob es zuschlagen soll. Mit dem reingepipten echo beantwortet es sich diese Frage immer negativ. Der Rest der Zeile zählt im Prinzip wie oft killall gefragt hat ob es töten soll.
Blöd ist, dass die auf einem etwas älteren RHEL laufen soll. Anders als bei Ubuntu kennt killall da die Option –older-than noch nicht.
Der zweite Lösungsansatz funktioniert leider nur auf den ersten Blick, dafür aber auch auf dem alten Red Hat:
find /proc/[0-9]* -maxdepth 1 -name status -mmin +30 | xargs egrep "^Name:\sfirefox$" | wc -l
Ich suche also alle Prozesse die schon länger als 30 Minuten laufen, und suche in deren status-Datei nach dem Namen meines Prozesses. Die Ergebnisse werden dann wieder gezählt. Alternativ könnte man statt in der status- auch in der cmdline-Datei suchen, in meinem echten Einsatz übersehe ich damit dann auch Zombies (die werden separat betrachtet). Leider funktioniert dieser Ansatz nicht wirklich zuverlässig. Es sieht so aus als ob der Kernel den Dateien im Proc-Verzeichnis bisweilen einen neuen Timestamp gibt — obwohl der Prozess nicht gestartet oder beendet wurde. Ein Schuss in den Ofen, also.
Wie macht man sowas? Ich meine, wenn man eine zuverlässige Lösung mit Bordmitteln haben will? Gibt es da ein passendes Tool das ich noch nicht kenne? Oder hilfreiche Optionen für gängige Tools? Wenn ich die Suchmaschine meiner Wahl frage finde ich eine Menge Lösungen, aber die sind entweder auch wackelig, oder sie bestehen darauf direkt alles zu töten…
Hmm.
Hier mal geschaut ? (zweite Antwort, die mit dem ps)
http://stackoverflow.com/questions/6134/how-do-you-find-the-age-of-a-long-running-linux-process
@joerg: Danke fuer den Ansatz. Jetzt muss ich zwar auch noch parsen, aber mit der Option etime gibt ps zumindest halbwegs zuverlaessig die passende Zeit aus. Ich habe auch meine Herangehensweise geaendert: ich gebe nicht mehr die Anzahl der Prozesse aus die laenger als eine bestimmte Zeit laufen, sondern die Zeit die der aelteste Prozess schon unterwegs ist (passt besser zur dem was ich eigentlich machen will):
ps h -o etime -C firefox | awk 'BEGIN{rm=0}{rt=substr($1,length($1)-4,2)*60+substr($1,length($1)-1,2);if(rt>rm){rm=rt}}END{print rm}'
Das scheint zu gehen.
Ach ja, schoen ist das aber immer noch nicht. Bessere Vorschlaege nehme ich gerne an!
Versuch doch mal:
ps -o pid,lstart,comm -e
9759 Tue Jan 22 17:53:34 2013 php-cgi
11636 Tue Jan 22 17:59:33 2013 php-cgi
12046 Tue Jan 22 18:01:19 2013 sshd
Die zweiten Werte müsste man nur filtern, durch folgendes jagen:
date -d "$extracted" +%s
und dann kann man sortieren und vergleichen, etwa:
now=date +%s
max=$(echo "$now 30 60 * - p" | dc)
und dann alle awk $2 rausfiltern, die größer/kleiner sind.
Ichj denke das wichtigste wäre, hier erstmal eine immer schöne Spalte „lstart“ zu haben, die man in Unixtime/seconds since epoch umwandeln kann. Danach wird es einfach, da man mit sekunden gut rechnen kann.
PS: Meine Lösung sollte einen Pluspunkt für umgekehrt polnische Notation kriegen!
@Ben: Den Punkt fuer RPN hast Du Dir redlich verdient.
Fuer einen 30-Minuten-Timestamp wuerde ich aber dc nicht bemuehen, das kann date machen. Wenn ich den Rest in einen Einzeiler giesse sieht das so aus:
t30=$(date -d "30 minutes ago" +%s); ps -o lstart h -C zsh | while read d; do [ $(date -d "$d" +%s) -lt $t30 ] && echo $d || continue; done
Wenn ich das richtig sehe werden bis auf ps und date nur interne Shell-Kommandos benutzt. Somit koennte das trotz Schleife ziemlich schnell sein. Auch ein interessanter Ansatz… Danke!