Timeout setzen?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
BlackJack

@chpo7234: Ähm das ist fast unmöglich weil das Modul im Python (2) Interpreter fest eingebaut ist. Da kann es also nicht einmal Probleme geben mit Pfaden oder Dateien. Kann es sein dass Du einem Python 3 irgendwie versuchst die Pfade zu einer Python 2 Installation als Modulsuchpfad unterzujubeln‽ Ebenfalls komisch ist das `multiprocessing` ein EGG ist, denn das ist eigentlich Bestandteil der Standardbibliothek. Wo kommt das her?
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Sorry, hier noch mal der vollständige Fehlerlog:
Traceback (most recent call last):
File "tpe.py", line 7, in <module>
import concurrent.futures #multithread => Fuehrt PHP nicht aus!
File "/usr/lib/python3.4/concurrent/futures/__init__.py", line 17, in <module>
from concurrent.futures.process import ProcessPoolExecutor
File "/usr/lib/python3.4/concurrent/futures/process.py", line 53, in <module>
import multiprocessing
File "/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/__init__.py", line 64, in <module>
import multiprocessing.patch
File "/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/patch.py", line 31, in <module>
from __builtin__ import property as bltin_property
ImportError: No module named '__builtin__'
Wenn ich das future-Modul in irgendein Python-Script einbinde, denn lässt es sich über die Bash NUR über python3 öffnen. Über PHP funktioniert es über den Befehl "python3" nicht: denn kommt halt der Error-Log. Ich habe soweit eigentlich keine Pfade zugewiesen. Im php führe ich die Datei wie folgt aus: exec("python3 Dateiname parameter1")
BlackJack

@chpo7234: Für Python 2 muss man das `concurrent.futures`-Modul nachträglich installieren, bei Python 3 ist es bereits in der Standardbibliothek.

Deine Python (3) Installation oder die Konfiguration Deines Systems ist kaputt, denn das hier

/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg

sollte nicht existieren. Das ist ganz klar ein Modul/Package für Python 2.7 und hat im Modulsuchpfad von Python 3 nichts zu suchen.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Danke, werde ich mich noch mal mit beschäftigen!

Bis jetzt habe ich Code aus folgendem Artikel abgeleitet: http://www.python-kurs.eu/threads.php (letztes Beispiel)

Irgendwie habe ich jetzt aber nicht das Gefühl, dass die Threads asynchron abgearbeitet werden. Vor allem, da sie mir auch, wie zuvor eingelesen, wieder ausgegeben werden. Das heißt: Ausgabe beginnt mit erster IP und endet mit letzter. Hat hier sonst jemand eine andere Url oder einen anderen Vorschlag für mich?

60 Sekunden für 500 Pings und SNMP-Abfragen im Multithread halte ich irgendwie für zu lange..

Ich bin ein wirklicher Python-Anfänger und mir ist es auch ein wenig unangenehm, aber ich poste trotzdem mal mein Code.

Code: Alles auswählen

import os						# ping
import re						# regular expression
import threading					# Multithreading
import sys						# Parameter-UEbergabe
import socket						# rDNS
import subprocess
import ipconvert as conv
from pysnmp.entity.rfc3413.oneliner import cmdgen	# SNMP

wordwrap = "<br>"
space = " => "

###################################################################################
##
##	Methode: Check SNMP- und rDNS-Status
##
###################################################################################
def info_rDNS_SNMP(ip):
	try:
		rDNS = socket.gethostbyaddr(ip)[0]
	except:
		rDNS = " "

	cmdGen = cmdgen.CommandGenerator()
	errorIndication, errorStatus, errorIndex, varBinding = cmdGen.getCmd(
		cmdgen.CommunityData("public"),
		cmdgen.UdpTransportTarget((ip, 161), timeout=1, retries=0),
		"1.3.6.1.2.1.1.1.0"
	)

	if errorIndication or errorStatus:
		return rDNS
	else:
		return rDNS + space + varBinding[0][1]

#####################################################################################
##
##	Klasse: IP-Check
##
#####################################################################################
class ip_check(threading.Thread):

############################
## Initialisierung
############################
	def __init__ (self,ip):
		threading.Thread.__init__(self)
		self.ip = ip
		self.__successful_pings = -1

############################
## Multithread
############################
	def run(self):
		ping_out = os.popen("ping -q -c2 -W1 "+self.ip,"r")
		while True:
			line = ping_out.readline()
			if not line: break
			n_received = re.findall(received_packages,line)
			if n_received:
				self.__successful_pings = int(n_received[0])

############################
## Ping-Ergebnis
############################
	def status(self):
		if self.__successful_pings == 0:
			return "no response"
		elif self.__successful_pings == 1:
			return "alive, but 50 % package loss"
		elif self.__successful_pings == 2:		#Host erreichbar
			return info_rDNS_SNMP(self.ip)
		else:
			return "shouldn't occur"

######################################################################################
##
##	Pruefe IP's
##
######################################################################################
received_packages = re.compile(r"(\d) received")

check_results = []
results = []
string_results = ""

###############################
## Checke IP-Addressbereich
###############################
start = int(conv.ip2long(sys.argv[1]))
end = int(conv.ip2long(sys.argv[2]))

for lv in range(start,end+1):
	ip = conv.long2ip(lv)
	current = ip_check(ip)
	check_results.append(current)
	current.start()

################################
## Ergebnisse in Liste speichern
################################
for el in check_results:
	el.join()
	results.append(string_results + el.ip + " : " + el.status())
#results.sort()
#for entry in results:
#	print(entry)
Lieben Gruß
BlackJack

@chpo7234: Doch die werden (teilweise) asynchron abgearbeitet. Die Reihenfolge der Ausgabe kommt dadurch zustande das Du die Ergebnisse von den Thread-Exemplaren in der Reihenfolge abfragst in der sie erstellt und gestartet wurden. Da muss dann die Ausgabe eventuell auf ein Ergebnis warten, aber an der Gesamtlaufzeit ändert das nichts. Allerdings werden SNMP-Abfragen alle nacheinander im Hauptthread ausgeführt statt parallel, denn der Code wird ja erst ausgeführt wenn `status()` aufgerufen wird.

Anmerkungen zum Quelltext: Formatierung und Namensschreibweisen entsprechen teilweise nicht dem Style Guide for Python Code.

Diese umrahmten Kommentare zerstören IMHO den Lesefluss ungemein. Der Inhalt ist zum Teil auch total überflüssig. Man muss vor einer Klasse nicht kommentieren dass das eine Klasse ist oder vor einer `__init__()` dass dort initialisiert wird.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer `main()`-Funktion.

Keine nackten ``except:``\s verwenden. Da wird *alles* behandelt, auch Sachen mit denen Du gar nicht rechnest und für die die Behandlung unpassend ist.

Zwei führende Unterstriche bei Attributen sind dazu da um Namenskollisionen bei Mehrfachvererbung oder tiefen Typhierharchien zu vermeiden. Beides Sachen die weder hier noch allgemein in Python oft gemacht werden. Implementierungsdetails werden mit *einem* führenden Unterstrich gekennzeichnet.

Als ”(noch) nichts”-Wert sollte man keinen speziellen Wert nehmen der vom Typ her auch mit einem tatsächlichen Wert passen würde. In Python gibt es den Wert `None` für so etwas. Ausserdem ist hier ein Fehler. Die Anzahl wird bei Dir mit -1 initialisiert, aber wenn von ``ping`` keine Ausgabe kommt auf die der reguläre Ausdruck zutrifft, dann bleibt der Wert bei -1 und führt dann bei `status()` zu einem Rückgabewert der eher eine Ausnahme sein sollte.

`re.findall()` macht keinen Sinn wenn man nur den ersten Treffer überhaupt verarbeitet.

Namen sollte man erst definieren wenn man sie benötigt und nicht auf Vorrat irgendwo am Anfang einer Funktion. Das ist unübersichtlich und bei Programmänderungen vergisst man gerne mal solche Definitionen und sammelt so Müll an.

Man sollte keine Abkürzungen verwenden die nicht allgemein bekannt sind. Ich rätsele immer noch was `lv` bedeuten sollte. Auch `el` ist nicht schön.

`string_results` hat keinen Effekt. Eine leere Zeichenkette kann man so oft ”addieren” wie man lustig ist, das ändert nichts am Ergebnis. Der Name lässt auch vermuten, dass das eigentlich etwas anderes sein/werden sollte.

Wenn das Ergebnis an ein anderes Programm übermittelt werden soll, würde ich keinen Text verwenden und auch nicht krude HTML-Teile ausgeben sondern ein verbreitetes Serialisierungsformat verwenden wie beispielsweise JSON.

Ich lande dann bei so etwas (ungetestet):

Code: Alles auswählen

import json
import re
import socket
import sys
from collections import OrderedDict
from subprocess import check_output
from threading import Thread

import ipconvert
from pysnmp.entity.rfc3413.oneliner import cmdgen
 
ARROW = '=>'


def check_snmp(ip):
    try:
        reverse_dns = socket.gethostbyaddr(ip)[0]
    except socket.error:
        reverse_dns = ' '

    command_generator = cmdgen.CommandGenerator()
    error_indication, error_status, _, var_binding = command_generator.getCmd(
        cmdgen.CommunityData('public'),
        cmdgen.UdpTransportTarget((ip, 161), timeout=1, retries=0),
        '1.3.6.1.2.1.1.1.0'
    )

    if error_indication or error_status:
        return reverse_dns
    else:
        return '{0} {1} {2}'.format(reverse_dns, ARROW, var_binding[0][1])


class IpCheck(Thread):
    
    RECEIVED_PACKAGES_RE = re.compile(r'(\d+) received')

    def __init__(self, ip):
        Thread.__init__(self)
        self.ip = ip
        self.result = None

    def run(self):
        match = self.RECEIVED_PACKAGES_RE.search(
            check_output(['ping', '-q', '-c2', '-W1', self.ip])
        )
        successful_ping_count = int(match.group(1)) if match else 0

        if successful_ping_count == 0:
            self.result = 'no response'
        elif successful_ping_count == 1:
            self.result = 'alive, but 50% package loss'
        elif successful_ping_count == 2:
            self.result = check_snmp(self.ip)
        else:
            assert False


def ip_range(start, end):
    return (
        ipconvert.long2ip(i)
        for i in range(ipconvert.ip2long(start), ipconvert.ip2long(end) + 1)
    )


def main():
    start = sys.argv[1]
    end = sys.argv[2]

    ip_checks = []
    for ip in ip_range(start, end):
        ip_check = IpCheck(ip)
        ip_check.start()
        ip_checks.append(ip_check)

    result = OrderedDict()
    for ip_check in ip_checks:
        ip_check.join()
        result[ip_check.ip] = ip_check.result

    print(json.dumps(result))


if __name__ == '__main__':
    main()
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Hey!

Vielen Dank für deine ausführliche Hilfestellung! Überwältigend! So eine Hilfsbereitschaft findet man selten..

Deine Tipps werde ich natürlich beherzigen - mir die Code-Richtlinien auch noch mal genauer anschauen!

Die Kommentare waren eigentlich nur für mich gedacht.. Auf der Linux-Konsole bzw. im Nano-Editor habe ich teilweise die Übersicht verloren, aber du hast natürlich Recht.

Freue mich sehr, dass ich nun auf die main-Funktion aufmerksam gemacht wurde! Das habe ich irgendwie schon vermisst..

Der Fehler mit den nackten Excepts ist mir eigentlich aus anderen Programmiersprachen bekannt.. Ich Faulpelz..

lv sollte eigentlich Laufvariable bedeuten, sorry..

Mit dem ergebnislosen string_results hatte ich wohl wirklich etwas anderes vor...Wie der Name teilweise vermuten lässt, wollte ich die Ergebnisse in einen string speichern. Mit dem hätte ich vorzeitig erst mal an PHP übermittelt. Später hatte ich vor, eine Liste / ein Array zu übergeben, aber nun habe ich ja die Abhilfe mit json. Da werde ich mich denn mal reinfuchsen!

Eine Rückmeldung zu dem Code habe ich leider noch: ich verstehe überhaupt nichts mit der ip_range()-Methode anzufangen..

Code: Alles auswählen

def ip_range(start, end):
    return (
        ipconvert.long2ip(i)
        for i in range(ipconvert.ip2long(start), ipconvert.ip2long(end) +1)
    )

Code: Alles auswählen

start = int(conv.ip2long(sys.argv[1]))
end = int(conv.ip2long(sys.argv[2]))
Wieso hast du für den Zweizeiler extra eine neue Methode geschrieben? Ist die zweifache Typumwandlung schlecht? Oder ist es einfach besser lesbar, wenn man ip_range(n,m) statt range(n,m) abfragt?

Und irgendwie verstehe ich die funktionsweise nicht... Ich verstehe darunter folgendes:
Eine bisher nicht zugewiesene Variable "i" wird in eine IP konvertiert. Mit dem Ergebnis wird nichts gemacht, es wird in keiner Variablen gespeichert. Oder nimmt "i" nun dieses Wert an?
In der nächsten Zeile beginnt eine Schleife, die nichts macht.

Entschuldige, dass ich in die Methode nun etwas verständnislos reinblicke.

Lieben Gruß
BlackJack

@chpo7234: Für die Konsole gibt's ja auch Editoren mit Syntaxhervorhebung und allerlei Unterstützung für's Programmieren, wie auflisten und auswählen von Funktion, Klassen, und Methoden. Ansonsten mag ich meinen ”grafischen” Texteditor nicht mehr missen und mounte oft ein Verzeichnis auf einem Server in dem ich mehr als nur ein kleines bisschen in Quelltext editieren muss, lokal mit sshfs.

Das in `ip_range()` ist ein Generatorausdruck. Alles in den Klammern gehört zusammen. Die funktionieren wie „list comprehemsions“ (LCs), nur ”lazy”, dass heisst es werden nicht alle Ergebnisse sofort erzeugt und in einer Liste gespeichert, sondern jedes Element wird erst dann berechnet wenn es von dem Generator/Iterator abgefragt wird.

Die zweifache Umwandlung ist nicht schlecht, die habe ich da ja auch drin, aber ich wollte die gerne an einem Ort weggekapselt haben um das iterieren über einen IP-Bereich über die Angabe von zwei IPs in der üblichen Notation in einer Funktion zu haben. Ich denke halt viel in solchen ”Datenströmen” mit Quellen, Abbildungen, Filtern, und Senken. Und das ist dann eine Quelle von IPs aus einem gegebenen Bereich. Ausserdem hatte ich noch im Hinterkopf das es in Python 3 wohl ein Modul für IP-Adressen gibt und das eventuell auch so etwas bietet, und dann kann man das leichter austauschen. Eigentlich fehlt da noch ein `map()` für meinen üblichen Programmierstil:

Code: Alles auswählen

    ip_checks = []
    for ip_check in map(IpCheck, ip_range(start, end)):
        ip_check.start()
        ip_checks.append(ip_check)
Und im Grunde würde ich das `start()` und das `join()` da noch raushaben, damit man keine ``for``-Schleifen sondern LCs und Generatorausdrücke verwenden kann, dann würde ich aber letztendlich „futures“ selber nachprogrammieren, dafür gibt's ja aber schon ein Modul/Package.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Moin moin BlackJack,

ich habe die run-Methode mal überarbeitet. Bei Hosts, die nicht verfügbar oder nur teilweise verfügbar waren, tauchte stets folgende Exception auf:
CalledProcessError: Command '[ping', '-q', '-c2', '-W1', '10.81.3.80 ']' returned non-zero exit status 1
Das habe ich nun wie folgt gelöst:

Code: Alles auswählen

import subprocess
[...]

def run(self):
    try:
        output = check_output(['ping', '-q', '-c2',, '-W1', self.ip])
        self.result = check_snmp(self.ip)
    except subprocess.CalledProcessError:
        self.result = 'no response'

[...]
Dadurch werden zwar jene Hosts ausgeblendet, die nur teilweise verfügbar sind, aber ich denke, darüber kann hinweg gesehen werden.

Lieben Gruß
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Dann finde doch mal heraus, was ein von 0 abweichender Returncode bedeutet und werte das entsprechend aus.
Antworten