{"id":134,"date":"2005-03-20T18:19:24","date_gmt":"2005-03-20T16:19:24","guid":{"rendered":"http:\/\/blogs.bl0rg.net\/netzstaub\/?p=134"},"modified":"2005-03-20T18:19:24","modified_gmt":"2005-03-20T16:19:24","slug":"process-watch","status":"publish","type":"post","link":"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/03\/20\/process-watch\/","title":{"rendered":"Process Watch"},"content":{"rendered":"<p>\nDividuum war diese Woche ganz sch&#246;n fleissig, und ich werde hier mal kurz &#252;ber die Software bloggen die er geschrieben hat. Wir wollten demn&#228;chst die Welt mit unser Streaming-Software &#252;bernehmen. Doch dazu braucht man auch Software, die den Zustand der ganzen Tools, die f&#252;r erfolgreiches Streaming notwendig sind, &#252;berwacht. Wie &#252;berwacht man am besten Encoder, HTTP-Server, Mitschneide-Prozesse, Umkodiersoftware, usw&#8230;? Am Besten mit eigener Process-Watch Software, den anderen L&#246;sungen ist ja nicht zu trauen. Klar, es gibt schon init-Skripte. Doch wer will sowas h&#228;ssliches schreiben wie start-stop-daemon zu verwenden? Und wie pr&#252;ft man nachher nach, das die Software auch l&#228;uft? Integrieren von Pipe-basierten Shellskripten ist da auch nicht wirklich einfach. Genauso ist es mit supervise von djb z.B. Da ist die User-Schnittstelle ganz sch&#246;n anstrengend, selbst die Logs kann man nicht als Mensch parsen.\n<\/p>\n<p>\nDie Software von Dividuum heisst &#8220;Process-Watch&#8221;, abgek&#252;rzt &#8220;pw.rb&#8221;, und ist nat&#252;rlich in Ruby geschrieben. Sie liest ein Config-File in YAML ein, und verwendet den Webserver Webrick zur Darstellung des Prozess-status. Die Software ist in unserem Subversion Repository unter svn:\/\/bl0rg.net\/pw\/ zu finden. Zuerst eine kleine &#220;bersicht von YAML.\n<\/p>\n<p>\n<strong>YAML<\/strong>\n<\/p>\n<p>\n<a href=\"http:\/\/yaml.org\/\">YAML<\/a> heisst YAML Ain&#8217;t Markup Language, und ist so eine Art Sprache, die man benutzen kann, um Daten zu serialisieren. Im Gegensatz zu XML, mit der YAML oft verglichen wird, ist das explizite Ziel von YAML auch noch von Menschen lesbar und editierbar zu sein. Dazu verwendet YAML eine Mischung von speziellen Zeichen und Indentierung. Weiterhin kann man YAML auch in einem Stream-Context verwenden. Wenn man also nur eine Kommunikationspipe hat, kann man dar&#252;ber mit YAML getrennte Dokumente &#252;bermitteln. In dem Fall von Process-Watch werden nur die &#8220;einfachen&#8221; Features von YAML unterst&#252;tzt. Eine Beispielskonfigurationsdatei sieht so aus:\n<\/p>\n<pre>Sleeper:\n    command: sleep 5\n\nTest:\n    command: sleep 10\n    graceTime: 2\n    \nTop:\n    command: top 1&gt;&#38;2\n    graceTime: 3\n    activateOnStartup: false\n<\/pre>\n<p>\nDiese Konfigurationsdatei l&#228;sst sich ungef&#228;hr so lesen: Es gibt 3 Knoten in der Datei. Die Knoten heissen &#8220;Sleeper&#8221;, &#8220;Test&#8221; und &#8220;Top&#8221;, und sind Kommandos, die Process-Watch &#252;berwachen soll. Jeder Knoten ist ein Mapping, d.h. z.B. eine Hashtabelle. Der Key &#8220;command&#8221; ist jeweils das Kommando, das ausgef&#252;hrt werden muss. Der Parameter &#8220;graceTime&#8221; ist dazu da, einem Prozess bevor er gekillt wird ein bisschen Zeit zu geben. Ein Prozess wird gekillt, wenn in der Steueroberfl&#228;che ein Stop-Kommando abgesetzt wird, oder Process-Watch allgemein gestoppt wird. Der Parameter &#8220;activateOnStartup&#8221; ist dazu da, um ein Prozess nicht gleich beim Starten von Process-Watch zu starten. Wie man sehen kann, sind die &#8220;command&#8221;-Parameter vollwertige Shellkommandos, mit Pipes, usw&#8230; Daher kann man z.B. Programme wie unseren FEC-Streamclient gleich mit mpg123 &#252;ber Process-Watch starten:\n<\/p>\n<pre>poc:\n    command: ~\/progs\/poc\/pob-fec | mpg123 -\n    maxLogItems: 30\n    graceTime: 5\n<\/pre>\n<p>\nHier sieht man auch gleich an dem &#8220;maxLogItems&#8221;-Parameter, dass Process-Watch nicht nur Programme ausf&#252;hren kann, sondern auch ihre Ausgaben mitloggen kann, und &#252;ber die Kontrollschnittstelle anzeigen kann.\n<\/p>\n<p>\n<strong>Process-Watch im Einsatz<\/strong>\n<\/p>\n<p>\nGanz &#8220;user&#8221;-friendly ist Process-Watch noch nicht, und im Moment ist der Name der Konfigurationsdatei (&#8220;config.yaml&#8221;) noch fest verdrahtet. Wenn man jetzt Process-Watch mit der obigen Datei startet, bekommt man folgende Konfigurationsoberfl&#228;che auf dem Port 8080:\n<\/p>\n<p>\n<img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogs.bl0rg.net\/netzstaub\/images\/pw1-1.png\" height=\"224\" width=\"471\" border=\"1\" align=\"middle\" hspace=\"4\" vspace=\"4\" alt=\"Pw1-1\" \/>\n<\/p>\n<p>Mit &#8220;start&#8221; und &#8220;stop&#8221; kann man hier die Programme starten und stoppen, mit &#8220;log&#8221; kann man den Output des Programms einsehen. Unten kann man auch  noch die Konfiguration neuladen (wenn man z.B. neue Programme eingef&#252;gt hat). Process-Watch geht dabei recht klever vor, indem es nicht nur die Konfiguration dumm neul&#228;dt, sondern erkennt, welche Programme dazu gekommen sind, und welche gekillt werden m&#252;ssen, und bei welchen sich die Parameter ge&#228;ndert haben.<\/p>\n<h3>Process-Watch Internals<\/h3>\n<p>Intern ist Process-Watch recht klar aufgebaut. Es gibt ein Master-Objekt, dass die laufenden Prozesse kontrolliert, und auch Anfragen von dem Webserver abarbeitet. F&#252;r die einzeln gestartete Prozess sind &#8220;WatchProcess&#8221;-Objekte zust&#228;ndig. Das Hauptprogramm (die &#8220;main&#8221;-Routine, sozusagen) instanziiert ein &#8220;Master&#8221;-Objekt, einen HTTP Server, und biegt das &#8220;INT&#8221; Signal um, um Process-Watch sauber zu stoppen. Nachdem der Webserver instanziiert wurde, wird f&#252;r die Url &#8220;\/&#8221; ein Handler angegeben, der die Anfrage an das Master-Objekt weiterleitet, indem es bei ihm die Methode &#8220;statusRequest&#8221; aufruft. Anschliessend wird der Webserver-thread gestartet, und alle 0.5 Sekunden wird dann der Master gepollt. Bei diesem Schritt &#252;berpr&#252;ft er dass jeder Prozess richtig l&#228;uft, usw&#8230; Hier dieses Hauptprogramm:<\/p>\n<pre>done = false\n\ntrap(\"INT\") do \n    done = true\nend\n\nmaster = Master.new(\"config.yaml\")\nhttp   = WEBrick::HTTPServer.new(:Port =&gt; 8080)\n\nhttp.mount_proc(\"\/\") do |req, res|\n    master.statusRequest(req, res)\nend\n\nhttpThread = Thread.new do \n    http.start\nend\n\nuntil done do\n    master.iteration\n    sleep 0.5\nend\n\nhttp.shutdown \nhttpThread.join\nmaster.activateNewConfig({})\n<\/pre>\n<p>Wie man sieht, wird hier die &#8220;eigentliche&#8221; Arbeit in der Methode &#8220;iteration&#8221; von dem Masterobjekt gemacht. Diese Routine sieht wie folgt aus:<\/p>\n<pre>    def iteration\n        @masterMutex.synchronize do \n            fdlist = {}\n            @processes.values.each do |process|\n                fdlist[process.stderr] = process if process.stderr\n            end\n            readable, _, _ = *IO.select(fdlist.keys, nil, nil, 0)\n            if readable\n                readable.each do |fd| \n                    fdlist[fd].onStderrReadable\n                end\n            end\n            \n            collectDeadChilds\n            checkProcessConfig\n        end\n    end\n<\/pre>\n<p>Process-Watch benutzt 2 Threads (der Master-thread, und der Webserver-thread), und deswegen wird zur Synchronisation hier in &#8220;iteration&#8221; das Mastermutex reserviert. Innerhalb des Mutex geht dann der Masterprozesse alle gestarteten Prozesse durch (die in dem Array &#8220;@processes&#8221; gespeichert sind), und checkt, ob bei einem Prozess was von Stderr gelesen werden kann. Ist dies der Fall, dann stehen Daten f&#252;r das Log bereit, und die Methode &#8220;onStderrReadable&#8221; wird bei dem &#8220;WatchProcess&#8221; Objekt aufgerufen. Nachdem die Logfiles aller gestarteten Prozesse aufgefrischt wurden, wird in der Method &#8220;collectDeadChilds&#8221; nachgeguckt, welche Subprozesse gestorben sind, und sammelt ihre Leichen auf. Schliesslich wird verifiziert, ob die Konfiguration neugeladen werden muss, was durch die &#8220;checkProcessConfig&#8221; Methode erledigt wird. Bei dem ersten Ausf&#252;hren von &#8220;iteration&#8221; ist noch gar keine Konfiguration geladen worden, so dass diese Methode auf jedem Fall aufgerufen wird.<\/p>\n<p>&#8220;checkProcessConfig&#8221; tut nichts anderes, als die YAML Konfigurationsdatei zu laden, und nachher &#8220;activateNewConfig&#8221; aufzurufen, die sich darum k&#252;mmert, die neuen Prozesse zu erkennen, die nicht-mehr existierenden zu identifizieren, und die modifizierten Parameter der gleich-bleibenden Prozesse zu &#252;bernehmen. Der YAML-Reader benutzt die YAML-Library f&#252;r Ruby, und ist verheerend einfach \ud83d\ude42<\/p>\n<pre>def readYaml(file)\n    begin \n        cfgfile = File.open(file)\n        config  = YAML::load(cfgfile.read)\n    rescue SystemCallError =&gt; e\n        puts \"error while reading configuration file #{file}: #{e}\"\n    rescue ArgumentError =&gt; e\n        puts \"error while parsing configuration file #{file}: #{e}\"\n    ensure\n        cfgfile.close\n    end\n    config\nend\n\ndef checkProcessConfig(reload = @mustReload)\n    return unless reload\n    @mustReload = false\n\n    config = readYaml(@config)\n    unless config\n        log \"could not read configuration\"\n        return false\n    end\n    activateNewConfig(config)\nend\n<\/pre>\n<p>Die neuen und alten Prozesse werden &#252;ber ihren Namen identifiziert, also die Keys vom ersten Mapping in der YAML-Datei. F&#252;r jeden neuen Prozess wird ein neues &#8220;WatchProcess&#8221; Objekt instanziiert, was sich dann auch darum k&#252;mmert, zu Forken und den neuen Prozess auszuf&#252;hren (und auch Stderr umzuleiten, damit die Nachrichten, die dar&#252;ber kommen mitloggen zu k&#246;nnen). Das ganze Unix-Handling ist ziemlich ekelhaft, und ich werde hier nicht n&#228;her drauf eingehen. Vermerken, dass Unix wirklich stinkt, muss ich allerdings schon.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dividuum war diese Woche ganz sch&#246;n fleissig, und ich werde hier mal kurz &#252;ber die Software bloggen die er geschrieben hat. Wir wollten demn&#228;chst die Welt mit unser Streaming-Software &#252;bernehmen. Doch dazu braucht man auch Software, die den Zustand der &hellip; <a href=\"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/03\/20\/process-watch\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-134","post","type-post","status-publish","format-standard","hentry","category-programmstaub"],"_links":{"self":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/134","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/comments?post=134"}],"version-history":[{"count":0,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/134\/revisions"}],"wp:attachment":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/media?parent=134"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/categories?post=134"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/tags?post=134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}