QProcess zu lahm oder Code ineffizient?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

Hi

Ich hab ein kleines Problem mit QProcess und zwar schreib ich gerade ne kleine Ui für AVG Antivirus und starte avgscan per QProcess.
Nach dem der avgscan gestartet ist lese ich Prozentstatus und Verzeichnis aus und genau hier ist mein Problem!

Starte ich "avgscan /home/foobar/Bilder" in der Konsole sehe ich 0 - 100% und alle 547 Verzeichnisnamen.
Starte ich avgscan über QProcess und Lese % und Verzeichnis aus, zählt der Counter gerade mal 10 Ausgaben und der ProgressBar bleibt bei 90% stecken.

Code ineffizient oder QProcess zu lahm?

Code: Alles auswählen

 def onScan(self):
        cmd = []
        cmd.append(self.checkScanMode())
        cmd = cmd + self.checkSelectedFolder()
        
        # Start process and setup signal/slot
        self.process = QtCore.QProcess()
        #self.process.setProcessChannelMode(2)
        self.process.readyRead.connect(self.output)
        self.process.finished.connect(self.onFinish)
        self.process.start('avgscan', QtCore.QStringList(cmd))

Code: Alles auswählen

    def output(self):
        output = self.process.readAllStandardOutput().replace('[2K','')
        try:
            self.count +=1
            a = re.findall('\[(.*)\..%\] (/.*)', output)
            p = int(a[0][0].replace('~', ''))
            d = a[0][1]
            print d
            self.progressBar.setValue(p)
            self.currentDirectory.setText(str(self.count))
        except IndexError:
            pass


Ausgabe von self.output()

Code: Alles auswählen

/home/foobar/Bilder/CBR/IMG_20120821_130923.jpg
/home/foobar/Bilder/Transformers/tf_1_optimus_large.jpg
/home/foobar/Bilder/Foobar/Hamsterbacken.jpg
/home/foobar/Bilder/DCIM/Calibra/
/home/foobar/Bilder/DCIM/Camera/IMG_20120615_181118.jpg
12
The End

Selbst wenn ich den Code verkürze in z.B. (Siehe unten), kommt die QProcess bzw die Ui nicht nach -.-'

Code: Alles auswählen

    def output(self):
        #output = self.process.readAllStandardOutput()#.replace('[2K','')
        try:
            self.count +=1
            #a = re.findall('\[(.*)\..%\] (/.*)', output)
            p = int(re.findall('\[~(.*)\..%\]', self.process.readAllStandardOutput())[0])
            #p = int(a[0][0].replace('~', ''))
            #d = a[0][1]
            #print d
            self.progressBar.setValue(p)
            self.currentDirectory.setText(str(self.count))
        except IndexError:
            pass
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

Ich hab jetzt noch was am Code gespielt und ich denke es liegt echt an QProcess, falls es QProcess ist, wie kann ich dann QProcess beschleunigen oder gibts ne alternative Lösung die schneller ist?

Selbes Problem :(

Code: Alles auswählen

   def output(self):
        print self.process.readAllStandardOutput()#.replace('[2K','')
#        try:
#            #a = re.findall('\[(.*)\..%\] (/.*)', output)
#            p = int(re.findall('\[~([\d]*)\.', self.process.readAllStandardOutput())[0])#[\d]%\]
#            #p = int(a[0][0].replace('~', ''))
#            #d = a[0][1]
#            self.progressBar.setValue(p)
#            self.currentDirectory.setText(str(self.count))
#        except IndexError:
#            pass
#        self.count +=1
BlackJack

@AngelusNoctis: Dir ist klar, dass das da nicht Zeilenweise ankommt, sondern in `output` durchaus mehr als eine %-Angabe vorkommen kann? `self.count` zählt also nicht die Dateinamen sondern wie oft Blöcke von Daten gelesen wurden. Wenn also die erste Prozentangabe im letzten gelesenen Block 90% ist, dann hast Du zwar 100% gelesen, zeigst aber nur die 90 an. Des weiteren können die Blöcke irgendwo getrennt werden, also auch mitten in einem Teil den Du mit dem regulären Ausdruck erfassen willst, was dazu führen kann, dass das nicht funktioniert weil ein Teil in einem Block steckt und ein anderer im nächsten Block.

Kannst ja zum Testen einfach mal `output` komplett per ``print`` ausgeben. Die ganzen Ausgaben zusammen sollten die komplette Ausgabe bilden.

Übrigens ist auch Deiner Problembeschreibung nicht erkennbar was das alles mit Geschwindigkeit zu tun haben soll. Wenn das langsam ist, wäre das noch einmal ein ganz anderes Problem.
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

BlackJack hat geschrieben: Kannst ja zum Testen einfach mal `output` komplett per ``print`` ausgeben. Die ganzen Ausgaben zusammen sollten die komplette Ausgabe bilden.
Genau, das hab ich getan. Siehe zweites Posting.

Edit: Ich versuch es mal mit canReadLine() und readLine()
Übrigens ist auch Deiner Problembeschreibung nicht erkennbar was das alles mit Geschwindigkeit zu tun haben soll. Wenn das langsam ist, wäre das noch einmal ein ganz anderes Problem.
Als ich bisschen gegoogelt habe, fand ich einige Bug-Reports von 2007/2010 in denen andere Dev's genau das selbe Problem hatten und da hiess es das QProcess nur alle 100ms reagiert o.ä.

Deshalb dachte ich es liegt an der Geschwindgkeit von QProcess.
lunar

@AngelusNoctis Die Ausgabe eines Prozesses wird gepuffert. Auf dem Terminal zeilenweise, bei Unterprozessen dagegen nur blockweise. Du musst die Pufferung auf beiden Seiten der Pipe ausschalten, wenn Du die Ausgabe eines Prozesses in Echtzeit lesen möchtest.

Auf der Seite von QProcess sollte das gehen, indem Du "QIODevice.Unbuffered | QIODevice.ReadWrite" als "openMode" an "QProcess.start()" übergibst. Auf der Seite von avgscan musst Du halt nachschauen, ob das Programm ein Argument zur Deaktivierung des Puffers hat. Wenn ja, musst Du dieses übergeben, wenn nein, hast Du Pech gehabt.

Und bitte, übergibt keine magischen Zahlen an Qt-Funktion. ".setProcessChannelMode()" erwartet ein Element der "ProcessChannelMode"-Aufzählung, also übergebe auch ein solches, in diesem Fall wäre das "QProcess.ForwardedChannels". Was allerdings überhaupt keinen Sinn ergibt, weswegen Du die Zeile wohl auch auskommentiert hast, was aber die Frage aufwirft, warum das da überhaupt steht.

Edit: Dir muss auch klar sein, dass „echte“ Echtzeitausgabe sowieso nicht geht, da es immer die Latenz der Ereignisschleife gibt. Eine kleine Verzögerung gibt es also immer, systembedingt.
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

lunar hat geschrieben:@AngelusNoctis Die Ausgabe eines Prozesses wird gepuffert. Auf dem Terminal zeilenweise, bei Unterprozessen dagegen nur blockweise. Du musst die Pufferung auf beiden Seiten der Pipe ausschalten, wenn Du die Ausgabe eines Prozesses in Echtzeit lesen möchtest.

Auf der Seite von QProcess sollte das gehen, indem Du "QIODevice.Unbuffered | QIODevice.ReadWrite" als "openMode" an "QProcess.start()" übergibst. Auf der Seite von avgscan musst Du halt nachschauen, ob das Programm ein Argument zur Deaktivierung des Puffers hat. Wenn ja, musst Du dieses übergeben, wenn nein, hast Du Pech gehabt.

Und bitte, übergibt keine magischen Zahlen an Qt-Funktion. ".setProcessChannelMode()" erwartet ein Element der "ProcessChannelMode"-Aufzählung, also übergebe auch ein solches, in diesem Fall wäre das "QProcess.ForwardedChannels". Was allerdings überhaupt keinen Sinn ergibt, weswegen Du die Zeile wohl auch auskommentiert hast, was aber die Frage aufwirft, warum das da überhaupt steht.

Edit: Dir muss auch klar sein, dass „echte“ Echtzeitausgabe sowieso nicht geht, da es immer die Latenz der Ereignisschleife gibt. Eine kleine Verzögerung gibt es also immer, systembedingt.
Hi auch dir ein Danke für die Antwort

Ich versuch es jetzt mal mit QIODevice.Unbuffered, ansonsten hab ich wirklich Pech :(


PS. Das mit ForwardedChannels war ne Verzweiflungstat, mitten in der Nacht als ich alle möglichen Optionen reingeballert habe ^^
Sirius3
User
Beiträge: 18229
Registriert: Sonntag 21. Oktober 2012, 17:20

lunar hat geschrieben:Auf der Seite von avgscan musst Du halt nachschauen, ob das Programm ein Argument zur Deaktivierung des Puffers hat. Wenn ja, musst Du dieses übergeben, wenn nein, hast Du Pech gehabt.
avgscan wird höchstwahrscheinlich nach jeder Zeile die Ausgabe flushen, weil sonst eine Prozentanzeige keinen Sinn machen würde. Zumindest nach Programm-Ende sollten alle Ausgaben auch wirklich geschrieben sein.

@AngelusNoctis: um es nochmal deutlich zu sagen: warum suchst Du nach allen Vorkommen von [xx%] gibst aber immer nur die erste Fundstelle aus.

Falls

Code: Alles auswählen

print self.process.readAllStandardOutput()
nicht alles ausgibt, kann es ja nur daran liegen, dass ein zweiter Prozess auch den Output liest oder der letzte Leseaufruf irgendwo abgebrochen wird.
lunar

@Sirius3 Nicht zwangsläufig… Terminalausgabe wird ohnehin nur zeilenweise gepuffert, insofern ist eine Prozentanzeige auch sinnvoll, wenn die Puffer nicht explizit geleert werden.
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

Moin :)

Danke für die Tipps.

Da ich bisher aber kein Erfolg hatte, werde ich es jetzt mal anders probieren und meld mich dann :)
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

So, ich hab das Zeilen zählen jetzt mit ner ganz einfachen Lösung, gelöst...

Und zwar ruf ich "avgscan -o" auf, das mir dann auch saubere Dateien aufführt (clean) und zähl dann mit output.count(), wie oft "clean" vorkommt.
Das selbe dann noch mit "failed", "healed", "infected" etc pp.

Das einzige Problem das noch besteht, ist der ProgressBar.setMaximum().

Zwar wollte ich mit os.walk() alle Dateien vorabzählen und in ProgressBar.setMaximum() knallen, aber das dauert relativ lange.

Hätte da jemand ne Idee oder weiss wie das solche Scanner, die Dateien innert wenigen Sekunden ermitteln?

Ansonsten würde ich zu C oder gleich ls und wc greifen :)
BlackJack

@AngelusNoctis: Solange die Daten nicht auf einer SSD oder etwas anderem sehr schnellen liegen, glaube ich nicht das Python hier der Flaschenhals ist, sondern die Datenübertragung von der Festplatte.
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

BlackJack hat geschrieben:@AngelusNoctis: Solange die Daten nicht auf einer SSD oder etwas anderem sehr schnellen liegen, glaube ich nicht das Python hier der Flaschenhals ist, sondern die Datenübertragung von der Festplatte.
Jap, habs gerade bemerkt... ls mit wc benötigt auch um die 50 sek

Aber auch doof wenn jedesmal, 1 minute gewartet werden muss bis es los geht :(
BlackJack

@AngelusNoctis: Man könnte das zählen der Dateien parallel zum Scannen starten und falls das Programm öfter auf die selben Verzeichnisstrukturen losgelassen wird, könnte man das Ergebnis für's nächste mal cachen und solange als Grundlage verwenden bis man erneut mit dem Zählen fertig ist.
Sirius3
User
Beiträge: 18229
Registriert: Sonntag 21. Oktober 2012, 17:20

@AngelusNoctis: da nach Programmende vonavgscan auf jedenfall die 100% nach output geschrieben sein müssen, hast Du in Deinem ersten Ansatz noch irgendwo anders einen Fehler. Dieser Fehler wird dir auch bei Deinem neuen Ansatz auf die Füße fallen, da Du auch dort den ein oder anderen clean überlesen könntest, nur fällt Dir das bei der Menge der Daten vielleicht nicht sofort auf.
AngelusNoctis
User
Beiträge: 92
Registriert: Sonntag 16. Dezember 2007, 20:03

Sirius3 hat geschrieben:@AngelusNoctis: da nach Programmende vonavgscan auf jedenfall die 100% nach output geschrieben sein müssen, hast Du in Deinem ersten Ansatz noch irgendwo anders einen Fehler. Dieser Fehler wird dir auch bei Deinem neuen Ansatz auf die Füße fallen, da Du auch dort den ein oder anderen clean überlesen könntest, nur fällt Dir das bei der Menge der Daten vielleicht nicht sofort auf.
Also das mit count('clean') hab ich jetzt mit ~, / und wahlweise irgendwelchen anderen Verzeichnissen probiert, die Ausgabe war am Ende immer 1:1 die selbe wie bei avgscan...

Das Problem beim alten Ansatz war das readLine() ab und an zwei oder drei Zeilen als eine Ausgegeben hatte und da ging dann was verloren, mit der neuen Methode hat es bisher geklappt.

BlackJack hat geschrieben:@AngelusNoctis: Man könnte das zählen der Dateien parallel zum Scannen starten und falls das Programm öfter auf die selben Verzeichnisstrukturen losgelassen wird, könnte man das Ergebnis für's nächste mal cachen und solange als Grundlage verwenden bis man erneut mit dem Zählen fertig ist.
Jop, werde nen QThread reinhauen :)


N8 ihrs :)
Antworten