Auslastung einzelner Cores in % [Multicore]

Code-Stücke können hier veröffentlicht werden.
Antworten
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Freitag 19. Dezember 2014, 23:56

Hallo liebe Community,
ich bin neu in der Python Welt, um ehrlich zu sein bin ich seit 2Tagen dabei :-).
Da ich mich aber schon in diversen anderen Sprachen heimisch fühle, war das Verstehen recht einfach.... Der Syntax hat mir anfangs nur Probleme gemacht -.-

Ich bin von der Firma SQuote UG (haftungsbeschränkt) und wir nutzen und entwickeln die Software easy-PANEL.
easy-PANEL ist für Hoster bestimmt und erfüllt viele Zwecke. z.B. Verwaltung von Gameservern, vServer, Voiceserver etc....
Ich will hier auch keine Werbung machen, nur eben erklären wie ich dazu kam :-)

Wir haben nun auch angefangen Rootserver Skripte zu schreiben. Diese sollen für den Anfang nur Informationen ausliefern...
Freien und Maximalen HDD Speicher, Ram (Total, Free, Used), uptime etc...

Hier wollte ich auch den Durschnittswert der CPU Auslastung ermitteln und ausgeben. Zunächst habe ich (wie alle bis dato geschriebenen Scripte) das in BASH geschrieben.
Naja ich habe was gefunden und das dann umgemodelt :-) Danke an den Coder :-)

Zurück: Nun haben wir festgestellt das die Ladezeit der Seite sehr lange ist... Wir wissen alle... BASH ist nicht gerade der schnellste :-)
Also habe ich mich umgeschaut und dachte mir mit Python das Ganze zu machen. Python ist Tausendmal schneller und es gefällt mir (hihi)

Ich habe mir also vor Zwei Tagen das ganze angeschaut und habe schon das gesamte Rootscript von BASH in Python umgeschrieben :-)
Jetzt kommen wir zu meinem Problem...

Hier erstmal der Code:

Code: Alles auswählen

import time
import os
import sys
import multiprocessing

def AverageAusgabe():
	wert = Average()
        
        return wert

def Average(tick=""):
	CpuCores = multiprocessing.cpu_count()
	TOTAL_LAST = range(CpuCores)
	BUSY_LAST = range(CpuCores)
	
	for i in range(CpuCores):
		TOTAL_LAST[i] = 0
		BUSY_LAST[i] = 0
	
	print('Anzahl der CPUs: ' + str(CpuCores))	
	while True:
		for i in xrange(CpuCores):
			CPUDATA="grep cpu" + str(i) + " /proc/stat"
			CoreTicks = os.popen(CPUDATA).read()
			CoreTicks = CoreTicks.split()

			BUSY_TICKS = (int(CoreTicks[1]) + int(CoreTicks[2]) + int(CoreTicks[3]) + int(CoreTicks[6]) + int(CoreTicks[7]))
			TOTAL_TICKS = (int(CoreTicks[1]) + int(CoreTicks[2]) + int(CoreTicks[3]) + int(CoreTicks[4]) + int(CoreTicks[5]) + int(CoreTicks[6]) + int(CoreTicks[7]))
			
			
			BUSY_1000 = 1000 *(int(BUSY_TICKS) - int(BUSY_LAST[i])) / (int(TOTAL_TICKS) - int(TOTAL_LAST[i]))
			BUSY_GANZZAHL = BUSY_1000 / 10
			BUSY_ZEHNTEL = BUSY_1000
			
			AVERAGE = str(BUSY_GANZZAHL) + '.' + str(BUSY_ZEHNTEL)

			# return AVERAGE
			print AVERAGE

			
			TOTAL_LAST[i] = TOTAL_TICKS
			BUSY_LAST[i] = BUSY_TICKS
			
		time.sleep(1)
		print
	
if __name__ == "__main__":
	Average()

#AverageAusgabe()
Mein Problem besteht nun darin das ich das in einer For-Schleife habe und meine Variable immer überschreibe, somit bekomme ich statt z.B. 8 Werte nur 1 wert zurück....
Wie bekomme ich das nun in eine Liste/Array das ich die Zahlen in der obriegen Funktionen Addieren kann und dann durch die Anzahl der Cores Zeilen kann um den Durchschnitt zu erhalten...

Ich würde mich sehr um Antwort freuen und danke euch nun schon mal für eure Anteilnahme...
Bitte habt aber Nachsicht mit Fachbegriffen und Funktionen... Ich bin erst seit 2tagen am Zug und kenne nicht mal nen Bruchteil.

Ich lerne aber schnell :-)

Mit freundlichem Gruß
Andre
BlackJack

Samstag 20. Dezember 2014, 01:26

@Nexus633: Du solltest das nicht so 1:1 von Bash in Python umschreiben. Das ist dann kein Python sondern ein eigenartiger Bastard der versucht Bash in Python-Syntax zu programmieren. Da kommt etwas heraus was für Python-Programmierer nicht nach einem Python-Programm aussieht.

Als erstes würde ich einen Blick in den Style Guide for Python Code empfehlen. Durchgehend gross geschriebene Namen für etwas was keine Konstante ist geht ja mal gar nicht. Und auch viele andere Namen entsprechen nicht den Konventionen. Genau wie die Einrückung, die mit vier Leerzeichen pro Ebene gemacht werden sollte.

Listen mit `range()` erstellen ohne dass man die Zahlen tatsächlich benötigt, dann *noch mal* so eine Liste erstellen um die Zahlen darin nur als Index in die Listen mit den Zahlen zu verwenden um die von der jeweiligen Zahl dann auf 0 zu setzen ist so gar nicht Python. Wenn man Werte in einer Liste sammeln möchte dann fängt man mit einer leeren Liste an und hängt dort dann die Werte an. Das gilt dann auch für die Werte vom vorherigen Durchgang. Also nicht die Einträge in einer vorhandenen Liste überschreiben, sondern einfach die alte Liste durch die aktuelle ersetzen und dann eine neue anfangen.

Man sollte zusammengehörige Werte auch nicht auf mehrere ”parallele” Datenstrukturen verteilen. Wenn zu jedem `TOTAL_LAST`-Wert der Wert mit dem gleichen Index in `BUSY_LAST` gehört, dann sollten diese Werte zusammengefasst als *ein* Element in *einer* Liste stehen. Dann muss man die Später nicht wieder über den Index zusammesammeln und es besteht nie die Gefahr das die Listen nicht mehr ”synchron” sind.

In Python 2 ist ``print`` eine Anweisung und keine Funktion, also gehören dort die Klammern nicht hin. Oder man importiert am Anfang des Moduls `print_function` aus dem `__future__`-Modul um aus ``print`` eine Funktion zu machen.

Externe Programme sollte man nur aufrufen wenn man das nicht mit Python-Mitteln lösen kann. Um Werte aus einer Textdatei zu lesen muss man kein ``grep`` starten! Man kann mit Python Dateien öffnen, die zeilenweise verarbeiten, und auch prüfen ob die eine bestimmte Bedingung erfüllen bevor man sie verarbeitet. Letztendlich ist die Frage ob man das in diesem Fall überhaupt machen muss beziehungsweise sollte. Denn hier wird die selbe Datei für jeden Prozessor(kern) erneut geöffnet, komplett durchsucht, um nur *eine* Zeile daraus zu verarbeiten. Und das suchen/filtern auch noch mit einem externen Prozess. *Das* ist es was Programme langsam macht — unnütze Arbeit verrichten. Wenn man sich `/proc/stat` anschaut wäre es doch viel effizienter diese Datei nur einmal zu öffnen, die erste Zeile zu überlesen, und dann alle weiteren der Reihe nach zu verarbeiten, weil ja jede für einen Prozessor steht.

Die Berechnung von `BUSY_TICKS` und `TOTAL_TICKS` liesse sich deutlich kürzer schreiben mit `sum()` und einem Generatorausdruck. Und der Rechenweg zu `AVERAGE` sieht extrem nach Shell-Programmierung aus. Python kennt nicht nur ganze Zahlen sondern auch Gleitkommazahlen. Die muss man nicht mühsam von Hand mit ganzen Zahlen als Festkommazahlen nachbasteln.

Es wäre sinnvoll das Auslesen der CPU-Werte in eine Funktion auszulagern und die *vor* der ``while``-Schleife einmal aufzurufen um dann *in* der Schleife auch beim ersten Durchlauf keine falschen Daten für die Berechnung zu haben.

Das zusammensetzen von Werten und Zeichenketten mittels `str()` und ``+`` ist eher BASIC als Python. In Python verwendet man für so etwas die `format()`-Methode auf Zeichenketten. Das ist dann sogar der Shell-Programmierung etwas ähnlicher mit den Platzhaltern in der Zeichenkette. Das beschränken der Anzeige auf eine Nachkommastelle würde man übrigens auch durch eine entsprechende Formatangabe erreichen. Und mit 100 muss man auch nicht selber multiplizieren wenn man beim Platzhalter angibt, dass die Dezimalbruch als Prozentzahl ausgegeben werden soll. Allerdings nur wenn man auch das %-Zeichen im Ergebnis haben möchte.

Der Durchschnitt von allen Prozessorkernen sollte doch eigentlich auch in `/proc/stat` stehen oder? Den muss man nicht selber berechnen.

Insgesamt würde ich auch mal schauen ob es bessere Alternativen zum lesen und parsen von `/proc` gibt, zum Beispiel das `psutil`-Modul. Das kann auch noch ein paar andere Informationen vom System ermitteln, wie die Partitionen, deren Auslastung, Leistungszähler für IO/Netz, Arbeitsspeicher, …

Ich bezweifle übrigens das bei einem Programm mit einer Endlosschleife eine Implementierung in Python schneller durchläuft als eine als Bash-Skript. ;-)
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Samstag 20. Dezember 2014, 17:02

Hey @BlackJack,
danke für deine Kritik. Ich weiß nicht wie ich das auffassen soll.... Habe gestern Abend doch schon ein wenig gegrübelt und mich doch ein wenig gekränkt gefühlt.
Du gehst mit harten Worten an meinem Beitrag...

Ich gebe dir recht den Standards entspricht es nicht und das da 100% zu verbessern ist auch korrekt. Das war mit mein erstes Python Programm...
Klar kann ich einiges anders machen gerade die unter Prozesse die ich aufrufe brauche ich nicht... Nur ist mein Problem die Unwissenheit und wollte hier mal nachfragen....

Ich werde das Programm definitive Verbessern und werde auch deine Ratschläge befolgen...
Das was ich allerdings nicht verstanden habe, ist die Leere Liste.
Listen mit `range()` erstellen ohne dass man die Zahlen tatsächlich benötigt, dann *noch mal* so eine Liste erstellen um die Zahlen darin nur als Index in die Listen mit den Zahlen zu verwenden um die von der jeweiligen Zahl dann auf 0 zu setzen ist so gar nicht Python

Code: Alles auswählen

TOTAL_LAST = range(CpuCores)
BUSY_LAST = range(CpuCores)
Ich habe fast eine Stunde daran gesessen nur herauszubekommen wie ich es am besten minimalisiere... Leider habe ich es dann so hinbekommen. Es geht anders, freut mich zu hören... Leider wusste ich das bis gestern noch nicht... Wie gesagt 2Tage Python.

Ich habe es zuerst nur 1zu1 nachgebaut... Werde das aber versuchen nach Python Richtlinien zu Coden.
Achja bevor ich das Vergessen... Die Schleife habe ich nicht drinne, die sollte hier nur als Demo dienen... Mit der schleife komme ich nicht weiter... Ich Frage quasi über das Interface ab und alles die Werte 2x berechnen. Die 2te Bewertung möchte ich dann als Rückgabe wert erhalten und dem Webinterface ausliefern... In Bash klappt es einfach mit einer „if“ abfrage

Code: Alles auswählen

if [ $i == 2 ]; then
        echo "$BUSY_GANZZAHL$BUSY_ZEHNTEL" | tr ' ' '\n' | sed -e 's/^0\{0,\}//' | tr '\n' '+' >> tmp/useCPU.XXXXXXX
fi
Dann werte ich die tmp Datei aus und gebe den Wert zurück...
In Python kann ich das ja schön direkt auswerten.. Danke für den Tipp sum() :-)

Wie füge ich nun wert a,b,c,d,e,f,g,h (z.b. 8Cores = 8 Werte) in eine Liste... Jedes Mal wenn ich mir den Return gebe bekomme ich statt (Wert: 0,1,2,3,4,5,6,7) nur den Wert 7

Ich danke dir auf jeden Fall für die echt krasse Kritik und würde mich freuen das nächste Mal nicht so harte Sachen an den Kopf geknallt zu bekommen...

Ich bin kein Dummer, nur ein Neuling in diesem Bereich :-)

Mit freundlichem Gruß
Andre
BlackJack

Samstag 20. Dezember 2014, 22:33

@Nexus633: Sorry wenn ich zu unfreundlich rüberkomme. Liest sich wahrscheinlich schlimmer als es gemeint ist.

Ich weiss nicht wie ich das mit der Liste in Worten besser beschreiben soll als: Vor der Schleife eine leere Liste erzeugen und diese dann in der Schleife für jeden Prozessor mit dem entsprechenden Wert(en) zu erweitern.

Für zweimal das Ganze auszuführen würde ich keine Schleife schreiben, zumal da ja eigentlich gar nicht komplett zweimal das gleiche gemacht werden soll denn wenn man die Liste des vorherigen Auslesens ganz am Anfang zum Beispiel mit 0en initialisiert, wie Du das gemacht hast, dann kommt beim ersten Schleifendurchlauf ja sowieso nur Unsinn heraus bei der Berechnung von `BUSY_1000` und den Rechnungen die darauf aufbauen.

Code sagt mehr als 1000 Worte (oder so ähnlich):

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function

from itertools import izip
from time import sleep


def read_cpu_stats():
    with open('/proc/stat') as lines:
        cpu_lines = (line for line in lines if line.startswith('cpu'))
        next(cpu_lines)  # Skip first cpu line (= average of all processors).
        result = list()
        for line in cpu_lines:
            parts = line.split()
            ticks = [int(parts[i]) for i in [1, 2, 3, 6, 7, 4, 5]]
            total_ticks = sum(ticks)
            busy_ticks = total_ticks - sum(ticks[-2:])
            result.append((busy_ticks, total_ticks))
        return result


def get_cpu_percentages(interval=1):
    cpu_stats_a = read_cpu_stats()
    sleep(interval)
    cpu_stats_b = read_cpu_stats()
    return [
        (busy_b - busy_a) / (total_b - total_a)
        for (busy_a, total_a), (busy_b, total_b)
        in izip(cpu_stats_a, cpu_stats_b)
    ]


def main():
    averages = get_cpu_percentages()
    print('+'.join('{0:.1f}'.format(a * 100) for a in averages))


if __name__ == '__main__':
    main()
Ich hoffe ich habe den Shell-Quelltext bezüglich der Ausgabe halbwegs richtig interpretiert.

Und noch mal, nur damit das nicht untergeht: Ich würde das nur selber implementieren wenn das unumgänglich ist, denn mit dem passenden Modul schrumpft das da oben alles zu dem hier zusammen:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function

from psutil import cpu_percent


def main():
    averages = cpu_percent(1, True)
    print('+'.join('{0:.1f}'.format(a) for a in averages))


if __name__ == '__main__':
    main()
BlackJack

Sonntag 21. Dezember 2014, 02:27

Nur mal so zum Spass das ganze ”rückübersetzt” in ein Bash-Skript mit ``awk`` für's Rechnen:

Code: Alles auswählen

#!/bin/bash

read_cpu_stats() {
    awk -f - '/proc/stat' <<"EOF"
    /^cpu[[:digit:]]+/ {
        total = $2 + $3 + $4 + $5 + $6 + $7 + $8
        busy = total - ($5 + $6)
        print busy, total
    }
EOF
}

main() {
    local cpu_stats_a=$(read_cpu_stats)
    sleep 1
    local cpu_stats_b=$(read_cpu_stats)
    awk -f - <(paste <(echo "$cpu_stats_a") <(echo "$cpu_stats_b")) <<"EOF"
    BEGIN { ORS = "+"; OFMT = "%.1f" }
    { print 100 * ($3 - $1) / ($4 - $2) }
EOF
    echo
}

main
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Sonntag 21. Dezember 2014, 13:03

Hey @BlackJack,
danke für deine Antworten, nur sind beide ausgaben fehlerhaft. Ich erhalte keine Nachkommastellen.
Meins mag zwar nicht schön und auch nicht im Code Stil liegen, aber es funktioniert und ich erhalte werte die mit HTOP verglichen werden können (htop ist schneller als ich :-( xD)

Bei deinem erhalte ich immer ,0 sobald der wert über 2,5 ist erhalte ich bei dir 3,0. (als bespiel)
Was ich aber genial finde ist das es doch relative einfach umzusetzen ist das gesamte in Python Stil zu schreiben. Ich werde mir deins auch annehmen und ein wenig verändern um die richtigen Werte zu ermitteln.

Ich glaube das du eine falsche Berechnung hast. Um die CPU Auslastung zu erhalten braucht man 2 Werte. von diesen 2 werten die Differenz ist die Angabe in Prozent. (Natürlich nur mit der zuvor berechneten Arbeit)

In deinem Skript berechnest du 2 mal den selben wert mit dem neuen wert. Ich berechne aber 2 unterschiedliche werte.
Klar hast du recht, man kann auch aus der Datei /proc/stat den ersten CPU wert nehmen (da das der Durschnitt ist), dieser ist aber nicht genau. Das wird in der man beschrieben.

Um eine recht genaue Auslastung zu erhalten sollen alle Werte berechnet und durch die Anzahl der Cores geteilt werden.
Meine werte kann man mit den werten in HTOP vergleichen und man sieht das meine genauer sind.

Dein Bash Skript läuft auch nur bedingt. Hier wird mit teils 1 Wert und teils 2 Werte ausgeben diese sind aber Identisch. Getestet habe ich deine Skripte unter 2 Core, 8Core und 16Core.
Wie gesagt danke ich dir aber für deine Hilfe und dein Python hilft mir sehr weiter. Ich lerne noch und werde es auf jeden Fall berücksichtigen.

Achja bevor ich das vergesse: das mit psutil habe ich gewusst, nur kann ich nicht von meinen Kunden Verlangen das diese die lib installiert haben, somit laufe ich auf gefahr das es garnicht funktioniert. Auf unseren Mascinen ist z.B. die Lib nicht Installiert. Gibt es eine Möglichkeit diese zu Implementieren ohne das der Kunde diese Installieren muss.

Ein Beispiel: Ich lege die Lib in den Ordner Libs und Importiere Sie dann in mein Programm. Ist das Möglich ?
In anderen Sprachen ist das kein Thema ;-)


EDIT: ICH NEHME DAS MIT DEM PYTHON ZURÜCK... WEITERE TESTS HABNE ERGEBEN DAS ES MIT NACHKOMMASTELLE FUNKTIONIERT... (Warum auch immer nicht bei den anderen Servern....)
BlackJack

Sonntag 21. Dezember 2014, 15:28

@Nexus633: Also ich erhalte Nachkommastellen. Hast Du auch wirklich *alles* übernommen, insbesondere auch den Import aus `__future__` damit der ``/``-Operator auch bei zwei Ganzzahlen eine Gleitkommazahl als Ergebnis liefert?

Das mit der Berechnung verstehe ich nicht. Ich berechne 2 mal den selben Wert mit dem Neuen Wert? Häh? Es gibt zwei Messungen. Jede Messung gibt zwei Werte: die „busy ticks” und die „total ticks” jeweils gemessen vom Systemstart. Die jeweiligen Werte der beiden Messungen ziehe ich voneinander ab, damit habe ich dann die Deltawerte für „busy“ und „total” von der Sekunde zwischen den beiden Messungen, und davon wird dann das Verhältnis berechnet. Also wieviel Anteil „busy” an „total” in der betrachteten Sekunde hat. Diese Berechnung passiert in Zeile 28 beziehungsweise in der „list comprehension” für alle Kerne.

Wie siehst Du das Deine Werte genauer sind? Du kannst doch höchstens sehen ob die voneinander Abweichen. Falls ja ist dann die Frage ob die Werte eines lange existierenden Systemwerkzeugs oder Dein selbst geschriebenes Programm genauer oder überhaupt richtig ist. Ich würde da eher Fehler vermuten und die auch eher nicht in ``htop`` suchen. ;-) Ausserdem ist die ”Ungenauigkeit” bei der ersten CPU-Zeile für praktische Zwecke sehr wahrscheinlich vernachlässigbar.

Das Bash-Skript läuft bei mir und gibt immer so viele Werte aus wie 'cpu'-Zeilen in der ``/proc/stat`` stehen die nach dem 'cpu' noch eine Ziffer stehen haben. Wie sieht denn bei Dir eine typische Ausgabe von ``grep '^cpu' /proc/stat`` aus?

Wenn man sich nur auf die Standardbibliothek beschränkt wird man, trotz Umfang, früher oder später auf Probleme stossen für die man das Rad neu erfinden muss. Und das dann weniger umfangreich und schlechter getestet. `psutil` ist ein reines Python-Package, das lässt sich also problemlos mit `pip` installieren. Falls die Lizenz von Deinem Code sich mit der BSD-Lizenz verträgt, kannst Du das notfalls auch in Dein Package mit aufnehmen.
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Sonntag 21. Dezember 2014, 15:54

Hey @BlackJack,
ich schrieb dich "EDIT: ICH NEHME DAS MIT DEM PYTHON ZURÜCK... WEITERE TESTS HABNE ERGEBEN DAS ES MIT NACHKOMMASTELLE FUNKTIONIERT... (Warum auch immer nicht bei den anderen Servern....)"
Ich nehme das was ich sagte zurück... Es funktioniert! Danke dir :-)

Das mit den werten war auf den vor EDIT Eintrag bezogen und somit Schwachsinn... Sorry :-)
ich habe ein wenig voreilig und vor allem vorlaut gehandelt. Das bin leider ICH.... Ich bin aber derzeit am studieren deines Programmes und sehe nach und nach wie du was machst.
Es sind sehr nützliche Funktionen, so macht es noch mehr Spaß zu üben...

Das BASH Skript gib mir leider nur Teilwerte aus... Soll aber nicht das Problem sein da ich mit Python arbeiten und üben will... Ich bringe mir das halt selber bei und versuche so gut wie ich nur kann das zu übernehmen was mir z.B. hier übermittelt wird (Also nicht die Skripte, sondern das geschriebene (Tipps, Tricks und Kritik :-)))

Ich werde noch weiter Studieren und mir weiter Python aneignen, lernen.

Mir wurde halt ans Herz gelegt den Statistischen Wert der CPU aus ``/proc/stat`` nicht zu nehmen. Daher werde ich das auch nicht und berechne es lieber selber... :-)
das mit pip ist das nächste was ich machen und probieren werde...

Ich suche zunächst nach der Bibliothek `psutil`, ist diese nicht vorhanden lasse ich Sie installieren. :-) Ich habe mir angeschaut was es alles kann und bin sehr erfreut über dessen Funktionen :-)
Ich würde mich schonwieder mit `danke dir` bei dir bedanken aber ich glaube es wird lästig und zu viel :-) hihi.

Ich werde deinen Rat aber beherzigen.
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Mittwoch 24. Dezember 2014, 17:36

Hey@BlackJack,
es tut mir leid das ich dich nochmal stören muss....
Ich habe jetzt alles versucht um die summe aus den einzelnen werten zu ermitteln...

Ich bekomme egal was ich mache nur Fehler....
Ich bekomme es nicht geschi...en die werte zusammenzurechnen....

Magst mir nur einen anstupser geben ?
Nicht die Lösung hinschreiben bitte :-)

Ich wäre dir sehr dankbar :-)
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Mittwoch 24. Dezember 2014, 20:22

Hey@BlackJack,
hat sich erledigt. Habs hinbekommen :-)
Nun habe ich den wert den ich haben wollte :-)

Danke danke danke
Antworten