IRC RSS Feed Bot => Problem: threading: 100% CPU Usage verhindern

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Ein eher ungewöhnliches Problem beschäftigt mich seit einigen Tagen - obgleich ich mir natürlich bewusst bin das 'threading' nicht so schön ist :blush:

    Und zwar schreibe ich aktuell an einem IRC RSS FEED bot, der unterschiedliche RSS Feeds (zZt. 8 Stück) im euIRC announcen soll (und auch schon tut)... Dafür habe ich eine Routine geschrieben die via threading.Timer eine Funktion alle paar Sekunden ausführen soll.
    Soweit auch kein Problem.
    Ursprünglich hatte ich es so gebaut das alle Feeds auf ein mal abgearbeitet werden - also nur ein mal threading.Timer ausgeführt wurde.

    Nun wollte ich aber gerne die Feeds zu unterschiedlichen Zeiten aktualisieren können, also mehrmals threading.Timer ausführen.... Ein Feed nur alle 35s ein anderer aber alle 15s usw

    Jetzt sehe ich natürlich mehrere Prozesse, die aber jeweils 97% CPU Auslastung verursachen und das ist nicht so schön weil dann nicht mehr nur ein Core belastet wird sondern alle, und auf einem 1core Pi bremst das alles andere aus :wallbash:


    Kennt hier jemand eine Möglichkeit über threading eine maximal "nutzbare" CPU Leistung zuzuteilen - oder so? :helpnew:

    Möchte auf time.sleep's an etlichen Stellen gerne verzichten, zumal ich eh glaube das die meiste Last das feedparser Module verursacht...

  • IRC RSS Feed Bot => Problem: threading: 100% CPU Usage verhindern? Schau mal ob du hier fündig wirst!

  • Servus meigrafd,
    ich hab' jetzt zwar keine Antwort auf Deine Frage ... aber: kannst Du es nicht so managen, dass immer nur eine Aktualisierung läuft, also dass der nächste, anstehende Sync (oder wie man das nennen mag) erst mal wartet, bis der Vorgänger beendet ist?

    //EDIT: wobei sich dann die Frage stellt, warum überhaupt mehrere threads ... :denker:

    cu,
    -ds-

  • Wenn ich dich richtig verstehe:

    Ein einziger Thread mit einer while Schleife und einem Queue was abgearbeitet werden soll. 'threading.Timer' fügt den nächsten zu verarbeitenden Feed ins Queue ein und der 'Worker_Thread' arbeitet die dann nacheinander ab. :huh:

    :denker: Wär denk ich eine Möglichkeit - Danke für den Denkanstoß ;)

  • Ich verstehe nichts vom (Python-)Programmieren, aber ich hatte mal ein Problem mit einem Programm, dass mit mehreren Kernen starten sollte.
    Vllt. kannst du dir da was rauspicken:

    Spoiler anzeigen


    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    #
    # $Id: cpuaffinity.py 770 2011-10-19 21:00:15Z Stef $
    # Copyright (C) 2011 Stefan Schwendeler ( kungpfui@gmail.com )

    """ Set CPU affinity and process priority of a new process. """

    __version__ = u'$Revision: 770 $'
    __author__ = u'Stefan Schwendeler'

    import os, sys
    import optparse
    import multiprocessing
    import time
    from ctypes import *


    check_interval = 5.0
    validation_nb = 12


    # some constants of windows os
    priorities = {
    'low': 0x00000040,
    'below_normal': 0x00004000,
    'normal': 0x00000020,
    'above_normal': 0x00008000,
    'high': 0x00000080,
    'realtime': 0x00000100,
    }


    BYTE = c_ubyte
    WORD = c_ushort
    DWORD = c_ulong
    DWORD_PTR = c_ulong if sizeof(c_void_p) == 4 else c_ulonglong
    LPBYTE = POINTER(c_ubyte)
    LPTSTR = POINTER(c_char)
    HANDLE = c_void_p
    PVOID = c_void_p
    LPVOID = c_void_p

    class STARTUPINFO(Structure):
    _fields_ = [("cb", DWORD),
    ("lpReserved", LPTSTR),
    ("lpDesktop", LPTSTR),
    ("lpTitle", LPTSTR),
    ("dwX", DWORD),
    ("dwY", DWORD),
    ("dwXSize", DWORD),
    ("dwYSize", DWORD),
    ("dwXCountChars", DWORD),
    ("dwYCountChars", DWORD),
    ("dwFillAttribute",DWORD),
    ("dwFlags", DWORD),
    ("wShowWindow", WORD),
    ("cbReserved2", WORD),
    ("lpReserved2", LPBYTE),
    ("hStdInput", HANDLE),
    ("hStdOutput", HANDLE),
    ("hStdError", HANDLE),]

    class PROCESS_INFORMATION(Structure):
    _fields_ = [("hProcess", HANDLE),
    ("hThread", HANDLE),
    ("dwProcessId", DWORD),
    ("dwThreadId", DWORD),]


    DETACHED_PROCESS = 0x00000008


    def main():
    """ the main routine :-)"""
    usage = "usage: %prog [options] <program path> [<arg 1> <arg n>]"
    parser = optparse.OptionParser(usage=usage)
    parser.disable_interspersed_args()
    parser.add_option("-c", "--cpuid", default="0", dest="cpuid", help="list of comma separated cpu IDs. E.g. 0,3 or 4,5")
    parser.add_option("-p", "--priority", default="normal", type="choice", choices=priorities.keys(), dest="priority", help="process priority class. [low, normal, high, realtime] - Default: %default")
    options, args = parser.parse_args()

    if len(args) < 1: parser.error("incorrect number of arguments")

    # check cpu IDs
    try:
    cpu_ids = [ int(id) for id in options.cpuid.split(',') ]
    except:
    parser.error("incorrect cpu id parameter")

    cpu_count = multiprocessing.cpu_count()
    if not all([ id < cpu_count for id in cpu_ids ]):
    parser.error("invalid cpu id value")

    # check program path
    if not os.path.exists(args[0]) or not os.path.isfile(args[0]):
    parser.error("incorrect program path")

    cpu_affinity_mask = 0
    for id in cpu_ids:
    cpu_affinity_mask += (1 << id);

    # re-join the commands to a string
    command = ''
    for arg in args:

    if ' ' in arg: arg = '"'+arg+'"'
    if command != '': command += ' '
    command += arg

    startup_dir = os.path.dirname(args[0]) or None

    priority_class = priorities[options.priority]

    # start the process
    si = STARTUPINFO();
    si.cb = sizeof(si)
    pi = PROCESS_INFORMATION();
    if not windll.kernel32.CreateProcessA(None, command, None, None, False, DETACHED_PROCESS | priority_class, None, startup_dir, byref(si), byref(pi)):
    print("Can't create process.");
    sys.exit(1)

    # wait some seconds
    time.sleep(check_interval)

    dwProcessMask = DWORD_PTR(0)
    dwSystemMask = DWORD_PTR(0)
    if windll.kernel32.GetProcessAffinityMask ( pi.hProcess, byref(dwProcessMask), byref(dwSystemMask) ) \
    and (dwSystemMask.value & cpu_affinity_mask):

    validations = 0

    #~ print dwSystemMask.value, dwProcessMask.value, cpu_affinity_mask
    while validations < validation_nb:
    if not windll.kernel32.SetProcessAffinityMask( pi.hProcess, DWORD_PTR(dwSystemMask.value & cpu_affinity_mask) :(
    error_code = windll.kernel32.GetLastError()
    print("Error [%d]: Can't set process affinity mask.", error_code)
    break

    time.sleep(check_interval)
    if not windll.kernel32.GetProcessAffinityMask ( pi.hProcess, byref(dwProcessMask), byref(dwSystemMask) :(
    print("Error Can't set process affinity mask.")
    break
    #~ print dwSystemMask.value, dwProcessMask.value, cpu_affinity_mask
    if ( dwSystemMask.value & cpu_affinity_mask ) != dwProcessMask.value:
    validations = 0
    else:
    validations += 1

    else:
    print("Error: Can't set process affinity mask. CPU ID does not exist.")

    windll.kernel32.CloseHandle(pi.hThread)
    windll.kernel32.CloseHandle(pi.hProcess)

    sys.exit(0)


    if __name__ == "__main__":
    main()

    </code>


  • ... Ein einziger Thread mit einer while Schleife und einem Queue ...


    genau so ...


    ... mit mehreren Kernen starten sollte.


    interessant, was Du da so allesanschleppst ;) ...
    Aber ich glaube, meigrafd wollte genau das Gegenteil ... also nur eine CPU "belasten" (wobei das bei einem Mono-Core-Pi trotzdem blockieren würde).

    //EDIT: ach ja ... ich bin mir nicht sicher, aber ich glaube nicht, dass man auf thread-Ebene Prioritäten und CPUs spezifizieren kann ...

    cu,
    -ds-

  • "CPU affinity" kenn ich, hab ich früher mal mit psutil genutzt
    Genauso wie das FAQ => Nützliche Links / Linksammlung => Prozess/Tasks einem bestimmten Kern zuordnen

    Bringt auf einem 1core System nur nicht so viel :fies:

    Der Bot läuft auf einem Pi1rev1, hat also nur ein Core und zwischendurch sieht das halt schon übel aus:

    Code
    # PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
    19357 bot        39  19  105M 13092  3864 S 93.0  5.5 36:49.99 python feedie.py                                                     
    20908 bot        39  19  105M 13092  3864 R 25.0  5.5  0:00.81 python feedie.py
    20917 bot        39  19  105M 13092  3864 R 25.0  5.5  0:00.62 python feedie.py
    20916 bot        39  19  105M 13092  3864 R 22.0  5.5  0:01.58 python feedie.py
    20909 bot        39  19  105M 13092  3864 R 18.0  5.5  0:00.43 python feedie.py
    20878 root       20   0  5000  1784  1224 R  6.0  0.8  0:09.29 htop

    ..da gibts auch üblere Momente, ist nur schwierig mal einen Moment zu erwischen wo alle 8 Feeds aktiv sind

  • Kannst du nicht evtl. die aktuelle Core-Auslastung (CPU-Load) ermitteln, und einen Schwellwert bestimmen?
    Dann fragt jeder Thread zuvor die Load ab und wird verzögert z.B. um 1sec, (fragt dann wieder) und startet erst, wenn der Schwellwert unterschritten ist?

    Das lässt zwar den konstanten Abstand "verschleifen", aber würde aber eine übermäßige Belastung verhindern...

  • Statt threading solltest du Python3.5 & asyncio verwenden.

    In einer simplen Implementierung kannst du statt print eben deinen Feed abholen. Richtig wird's gemacht, indem man auch das abgreifen der Feeds mit asynchronen Mitteln macht. Ich bin jetzt nicht mehr in den Details zu RSS & Co, aber in https://docs.python.org/3.6/library/asyncio-stream.html finden sich Beispiele, wie man sockets damit abfragt etc.

  • Zentris: Und wenn die Auslastung nie unter den Schwellwert singt?
    Ich glaub da ist dreamshaders Vorschlag weniger Aufwand zu programmieren - oder hast du da schon Code für? ;)

    Manul: Aktuell starte ich das Script mit "nice -n 19 python feedie.py" um das zumindest ein bisschen zu verhindern, aber nein leider reicht das noch nicht :(

    @__deets__: Aktuell nutze ich noch python2, muss aber aufgrund von SSL bzw HTTPS sowieso auf eine höhere Python Version wechseln - mit Raspbian Wheezy gibts nur python2.7.3 aber das unterstützt kein 'ssl.SSLContext' bzw 'ssl.create_default_context()' , wurde erst mit python2.7.9 eingeführt bzw von python3 zurück portiert...
    Mit 'asyncio' hab ich noch nichts gemacht.. sieht kompliziert aus :denker:

  • AFAIK gibt es asyncio auch fuer Python2.7, ab Python 3.5 gibt es spezielle syntax-Unterstuetzung. Und das mit gutem Grund - es ist eine wichtige Technik, die es wert ist, sich mal damit auseinanderzusetzen.

    Doch das Grundprinzip ist nicht schwer. Du hast einen mainloop, wie in der GUI auch. Und du bekommst Ereignisse, wenn es etwas zu tun gibt. Das koennen sowohl IO-Ereignisse (Daten am Stream anliegend), als auch Timer-ereignisse sein. Nur, dass du das nicht so haesslich programmieren musst, wie mit zB tkinter, und einer Reihe von timer-basierten Aktionen. Dein Code sagt einfach in-line "und jetzt lege ich mich fuer n Sekunden schlafen ( await asyncio.sleep(timeout)) ) - und danach geht's halt einfach weiter.

    Genauso mit IO - einfach ein "hier warten, bis Daten kommen", und dann laeuft deine Co-Routine erstmal nicht mehr, bis der Server sich bequemt hat, zu antworten.

    Das ist im Grunde die CPU-schonenste und auch durchsatzstaerkste Art zu programmieren. Nicht umsonst haben NGINX und Node.js in dieser Beziehung fuer viel Furore gesorgt, und zB den Multip-Prozess/Thread-basierten Champion Apache das fuerchten gelehrt.


  • ...Mit 'asyncio' hab ich noch nichts gemacht.. sieht kompliziert aus :denker:

    meinst Du?
    Das sieht für mich wie non-blocking I/O mit timeout aus ... aber ... das ist jetzt nur so eine Vermutung ... das ist -> Parsel <- pythonischer Dialekt ;) ...

    oops ... da war einer schneller :)

    cu,
    -ds-

  • dreamshader das ist zwar wahrscheinlich etwas Haarespalterei, aber ich zumindest wuerde das *nicht* non-blocking nennen. Das ist fuer mich das oeffnen mit O_NONBLK oder aehnlichem, bei dem ein read sofort zurueckkehrt, wenn es nix zu lesen gabt. asyncio is hingegen select/poll/epoll basiert (was auch immer die unten drunter nehmen). Man ruft eben nicht direkt read auf, sondern laesst sich benachrichtigen, wenn man weiss, das read auch etwas liefert. Natuerlich blockiert es dann auch nicht, aber es ist eben auch nicht dediziert non-blocking.

  • Na wie halt asynchroner I/O in C ... war jetzt echt blöd beschrieben von mir ... :blush:
    Mit select() / poll() oder SIGIO bei sockets und gesetztem O_ASYNC oder einfach nur normalem read() mit O_NONBLOCK ...
    Ich denke nämlich, dass blockierende I/Os da Probleme machen. Deshalb lag die Betonung halt auf non-blocking ...
    Aber vielen Dank für die Erklärung ...

    -ds-


  • Zentris: Und wenn die Auslastung nie unter den Schwellwert singt?
    Ich glaub da ist dreamshaders Vorschlag weniger Aufwand zu programmieren - oder hast du da schon Code für? ;)

    Es gibt ja (wie immer) viele Wege nach Rom :)

    Ja, Code mit Multithreading hab ich, der müsste jedoch (massiv) angepasst werden (ist so um die 3 Jahre alt... noch aus meiner "beginner" Phase mit Python...

    "Schwellwert nie sinkt... ": klar, es muss da eine Strategie geben, die damit umgeht (ich würde dann eben den Thread dennoch starten... )

    Mir ist nicht ganz dein Hintergrund klar, warum du die CPU-Auslastung so einschränken willst:
    Wenn es so kritisch ist, dann gib doch dem (Python)-Prozess einen niedrige Ausführungpriorität (nice-Wert):

    also:

    Code
    nice 10 ./Pythonprogram
  • Hallo Meigrafd,

    ich weiß nicht ob mein Ansatz eine große Hilfe für Dich darstellt.

    In einer Auftragsentwicklung hatte ich asynchrone Dateneingänge. Daten kommen zu unbekannten Zeitpunkten herein, Datenumfang variiert und auch während ein Sender sendet, können während eines Datensatzes unbestimmte Pausen eintreten.

    Mein Ansatz bestand darin, dass je Eingangskanal ein Parallelprozess gestartet wurde, der nichts anderes macht, als die eintreffenden Daten (so welche da sind) in eine Datei auf der RAM-Disk anhängt. Für jeden Kanal also eine Datei.

    Die Hauptanwendung macht dann innerhalb der Hauptereignisschleife Überprüfungen, ob sich die einzelnen Dateilängen erhöht haben.
    Falls nicht, ist nichts zu tun.
    Falls doch, wird alles vom letzten eingelesenen Byte +1 bis Dateiende eingelesen. Hier kann man noch einen Timeout einbauen. falls während des Auslesens noch was eingetrudelt ist.

    Dieser Algorithmus hat praktisch keine Auswirkungen auf die CPU-Auslastung. Somit sind auch keine Verrenkungen bzgl. irgendwelcher Core-Auslastungen und Prioritäten-Aktionen erforderlich.

    Meist sind die einfachsten Algorithmen auch die wirkungsvollsten...


    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

  • Ich hab irgendwie noch ein Problem überhaupt den Anfang zu finden...

    Bot wird gestartet und führt direkt eine Funktion - in der dann was drin steht?
    Was wird dann von dieser Funktion verarbeitet?
    Wie wird dann sichergestellt dass es je nach eingestelltem Intervall immer wieder ausgeführt wird?

    Aktuell habe ich eine Funktion die direkt beim starten des Bots die threading.Timer für jeden Feed starten und dann hab ich zusätzlich eine Funktion "feed_refresh()" die sichs ums holen und verarbeiten der Feeds kümmert und eben dem Announce ins irc... Am Ende der "feed_refresh()" wird threading.Timer erneut ausgeführt...

    Ich steh jetzt aber irgendwie auf dem Schlauch die von dreamshader oder __deets__ vorgeschlagene Sache umzusetzen :daumendreh2:

    Hier die aus meiner Sicht wichtigsten Zeilen des Codes damit besser verstanden wird wie es _aktuell_ abläuft: => http://codepad.org/DaNBembL
    'Queue_Manager' kümmert sich um die ausgehenden Nachrichten ins IRC, die nämlich mit etwas Verzögerung sein müssen sonst wird der Bot vom IRC_Server wegen "Excess Flood" gekickt..
    config.py sieht überwiegend so aus:

    Spoiler anzeigen


    [code=php]
    network = {
    'server': 'irc.jen.de.euirc.net',
    'port': 6667,
    'SSL': False,
    'ipv6': False,
    'password': '',
    'bot_nick': 'FEED',
    'bot_name': 'feedie pyBot v1.0',
    'pubmsg_log': False,
    'announce_delay': .5,
    'default_refresh_delay': 35.0,
    'startup_announces': False,
    }

    feeds = [{
    'mydealz': {
    'url': "https://www.mydealz.de/rss/alle",
    'color': 'green',
    'channel': '#FEEDs',
    'channel_key': "",
    'refresh_delay': 25.0,
    'enabled': True,
    },
    'monsterdealz': {
    'url': "http://feeds.feedburner.com/MonsterDealz?format=xml",
    'color': 'green',
    'channel': '#FEEDs',
    'channel_key': "",
    'enabled': True,
    },
    'chillmo': {
    'url': "http://chillmo.com/feed/",
    'color': 'orange',
    'channel': '#FEEDs',
    'channel_key': "",
    'enabled': True,
    },
    'hwluxx': {
    'url': "http://www.hardwareluxx.de/index.php/rss/…x-rss-feed.html",
    'color': 'teal',
    'channel': '#FEEDs',
    'channel_key': "",
    'enabled': True,
    },
    'ht4u': {
    'url': "http://ht4u.net/feeds/news.xml",
    'color': 'purple',
    'channel': '#FEEDs',
    'channel_key': "",
    'enabled': True,
    },
    'pcgames': {
    'url': "http://www.pcgames.de/feed.cfm?menu_alias=home",
    'color': 'pink',
    'channel': '#FEEDs',
    'channel_key': "",
    'enabled': True,
    },
    'pcgames_hw': {
    'url': "http://www.pcgameshardware.de/feed.cfm",
    'color': 'red',
    'channel': '#FEEDs',
    'channel_key': "",
    'enabled': True,
    },
    'forum': {
    'url': "https://www.forum-raspberrypi.de/syndication2.php?limit=10",
    'color': 'red',
    'channel': '#Raspberry-Pi',
    'channel_key': "",
    'refresh_delay': 15.0,
    'enabled': True,
    },
    }]
    [/php]

    Es wird also erst "initFeedRefreshTimers(self)" ausgeführt. Davon werden dann mehrere "threading.Timer" gestartet und die wiederum starten "feed_refresh(self, feed, name, refresh_time)", welche sich dann fortlaufend selbst ausführen...

    ...hoffentlich steigt da jemand durch und kann mir meinen Knoten im Kopf lösen :blush:


    //EDIT: Ich glaub ich habs... Test läuft aber noch ;)


    //EDIT2: Hm ne... Es funktioniert zwar, allerdings verursacht das abrufen der feeds weiterhin nahezu 100% :(
    Um ein Eindruck von dem Problem zu kriegen => http://RaspberryPi.roxxs.org/feedie_prob.mp4
    (das ist jetzt mit dreamshaders Vorschlag)


    //EDIT3: Hab jetzt noch was herausgefunden: http://pythonhosted.org/feedparser/http-etag.html
    Nur leider scheinen die meisten Feeds das nicht zu unterstützen :(

  • Wie wär's denn mit sowas in der Art?


  • ...
    //EDIT2: Hm ne... Es funktioniert zwar, allerdings verursacht das abrufen der feeds weiterhin nahezu 100% :(
    ...

    Das ist aber jetzt ein Mono-Core, oder?
    Vom Prinzip ist das ja so, dass scheinbar tatsächlich nur einer läuft. Auf einem Mono-Core kannst Du da vermutlich nicht viel machen ... wobei ich das jetzt gar nicht mal so schlecht finde (ich hatte jetzt zwischen 40% und 90% beobachtet).
    Wie läuft das denn auf nem Quadcore?
    cu,
    -ds-

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!