BBB: Server-Stats anzeigen lassen

Hallo zusammen,

um die Serverlast besser einordnen zu können, habe ich ein Python-Skript geschrieben, das in einer Übersicht die aktuellen Räume, Anzahl der Videos, Anzahl der Audios pro Raum und gesamt anzeigt.
Vielleicht hilft es euch ja auch :slight_smile:

Viele Grüße
Dominik

Python 2 (vorher muss evtl. noch requests mit pip install requests installiert werden)

#!/usr/bin/env python
#coding:utf-8
# Mit bbb-conf --secret den Endpoint und das Secret anzeigen lassen
URL = "<URL aus bbb-conf --secret>"
SECRET = "<Secret aus bbb-conf --secret>"

import imp, hashlib
from xml.etree import ElementTree

try:
    imp.find_module('requests')
    import requests
except ImportError:
    print "Package requests fehlt. Bitte mit pip install requests installieren."
    exit()

meeting_api_url = "getMeetings"

def main():

	request_url = URL + "api/" + meeting_api_url  + "?checksum=" + get_checksum(meeting_api_url)
	response = get_server_response(request_url)
	tree = get_tree_from_response(response)
	meetings = tree.find("meetings")

	sum_audio = 0
	sum_video = 0
	sum_listening = 0
	sum_participants = 0

	output_table = [["ID", "Name", "Moderator", "Teilnehmer", "Nur zuhoeren", "Im VoiceChat", "Mit Video"]]

	for index, meeting in enumerate(meetings):
		
		table_row = generate_table_row(meeting)
		table_row.insert(0, str(index+1))

		sum_participants += int(table_row[3])
		sum_listening += int(table_row[4])
		sum_audio += int(table_row[5])
		sum_video += int(table_row[6])

		output_table.append(table_row)
		
	output_table.append(["", "", "", "", "", "", ""])
	output_table.append(["", "Gesamt", "", str(sum_participants), str(sum_listening), str(sum_audio), str(sum_video)])

	max_width = calculate_max_width(output_table)
	
	for row in output_table:
		for index, data in enumerate(row):
			print (data.ljust(max_width[index])),
		print ("\n"),

def get_server_response(url):
	try:
		response = requests.get(url=url)
	except requests.exceptions.ConnectionError:
		print ("Rufe URL " + url + " auf.")
		print ("Es trat ein Fehler bei der Verbindung auf. Ist die URL korrekt?")
	except requests.exceptions.MissingSchema:
		print ("Die URL ist nicht korrekt. Bitte auf Fehler prüfen.")


	if response.status_code != 200:
		print "Die URL gibt den Statuscode " + str(response.status_code)  + " zurück."
		exit()

	return response.content

def get_checksum(api_url):
	m = hashlib.sha1()
	m.update(api_url + SECRET)
	return m.hexdigest()

def get_tree_from_response(response):
	tree = ElementTree.fromstring(response)

	if tree.find("returncode").text != "SUCCESS":
		print "API-Fehler:",
		print tree.find("message").text
		exit()
	return tree

def generate_table_row(meeting):
	meetingName = meeting.find("meetingName").text.encode('utf-8')
	meetingAudio = int(meeting.find("voiceParticipantCount").text)
	meetingVideo = int(meeting.find("videoCount").text)
	meetingParticipants = int(meeting.find("participantCount").text)
	meetingListening = int(meeting.find("listenerCount").text)
	creator = "-"
	attendees = meeting.find("attendees")
	for attendee in attendees:
		if attendee.find("role").text == "MODERATOR":
			creator = attendee.find("fullName").text.encode('utf-8')
			break

	return [meetingName, creator, str(meetingParticipants), str(meetingListening), str(meetingAudio), str(meetingVideo)]

def calculate_max_width(table):
	max_width = [0, 0, 0, 0, 0, 0, 0]
	for row in table:
		for index, data in enumerate(row):
			max_width[index] = max(len(data), max_width[index])

	return max_width

if __name__ == '__main__':
    main()
3 „Gefällt mir“

Skript ist super, tut einwandfrei (wenn man’s dann mal richtig macht).
Bisschen Geschiss mit Python und pip gehoert ja immer dazu.
Vielen Dank.

Genial, vielen Dank!

Hallo,

Hinweis zur Zusammenarbeit;

Gabs auch in unserem Ansible schon, man muss die Role monitoring ansehen:

https://codeberg.org/DigitalSouveraeneSchule/bbb/src/branch/master/roles/monitoring/files/checkbbb.py

VG

Frank

Edit: Das ist zwar ein check_mk local-Check, lässt sich aber leicht modifizieren.

Hallo,

Skript ist super, tut einwandfrei (wenn man’s dann mal richtig macht).
Bisschen Geschiss mit Pyhton und pip gehoert ja immer dazu.

wie ruf ich das Ding auf, wenn es mit endpoint und secret versorgt ist?

LG

Holger

  1. Erst Zeile ergänzen #!/usr/bin/env python .
  2. Ausführbar machen - chmod +x <filename>.py .
  3. und ausführen ./<filename>.py

Wenn du das Skript nach /bin kopiert, kannst du es jederzeit aufrufen

Hallo Bellm,

  1. Erst Zeile ergänzen |#!/usr/bin/env python| .
  2. Ausführbar machen - |chmod +x .py| .
  3. und ausführen |./.py|

reicht leider noch nciht:

Traceback (most recent call last):
File „./server-stats-bbb.py“, line 4, in
import hashlib, requests
ImportError: No module named requests

LG

Holger

Hallo Holger,

dir fehlt das Packae requests. Das kannst du bspw. mit pip installieren (https://pip.pypa.io/en/stable/installing/). Wenn du pip hast, sollte ein pip install requests reichen, damit das Skript läuft.

Viele Grüße
Dominik

Hallo Dominik,

dir fehlt das Packae |requests|. Das kannst du bspw. mit |pip|
installieren (This page has moved - pip documentation v24.0). Wenn du pip
hast, sollte ein|pip install requests| reichen, damit das Skript läuft.

ne, reicht auch nicht.
Immerhin ist die Meldung neu:

Traceback (most recent call last):
File „./server-stats-bbb.py“, line 16, in
response = requests.get(url=request_url)
File „/usr/local/lib/python2.7/dist-packages/requests/api.py“, line
76, in get
return request(‚get‘, url, params=params, **kwargs)
File „/usr/local/lib/python2.7/dist-packages/requests/api.py“, line
61, in request
return session.request(method=method, url=url, **kwargs)
File „/usr/local/lib/python2.7/dist-packages/requests/sessions.py“,
line 530, in request
resp = self.send(prep, **send_kwargs)
File „/usr/local/lib/python2.7/dist-packages/requests/sessions.py“,
line 637, in send
adapter = self.get_adapter(url=request.url)
File „/usr/local/lib/python2.7/dist-packages/requests/sessions.py“,
line 728, in get_adapter
raise InvalidSchema(„No connection adapters were found for
{!r}“.format(url))
requests.exceptions.InvalidSchema: No connection adapters were found for u’

LG

Holger

Glaube hashlib ist per default auch nich installiert, pip install hashlib ?
Python ist ja eine tolle Sprache, aber das Geschiss mit Version 2, 3 und die dazugehoerigen pip-Versionen in Konflikt mit den linuxrepositoryeigenen Uraltversionen braucht kein Mensch.

Mit der URL scheint auch was nicht zu stimmen, hier mein Auszug aus dem Skript zum Vergleich.
Zeile 7-9

url = "https://bbb.irgendwo.de/bigbluebutton/" + "api/"
secret = "diesesbaseirgendwaskodiertesecret"
meeting_info = "getMeetings"

Ich habe das Skript oben angepasst, damit es die häufigsten Fehler abfängt und hilfreichere Fehlermeldungen gibt.

1 „Gefällt mir“

Hallo,
ich habe leider noch nie etwas in Python programmiert, aber wir haben das Script zum laufen gebracht. Es ist top! Vielen vielen Dank, genauw as ihr noch brauchten um Auslastung zu überwachen.
Allerdings bekommen wir hin und wieder folgende Fehlermeldung (wir haben uns damit ein cron-Log gemacht):

 File "/root/pytest/BBB-Statistik.py", line 113, in <module>
main()
  File "/root/pytest/BBB-Statistik.py", line 52, in main
print (data.ljust(max_width[index])),
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 10: ordinal not in range(128)

Im Internet konnte ich jetzt schon herausfinden, dass dies mit der Unicode Umwandlung zu tun hat.
Komisch ist (für mich, für euch sicherlich nicht). Führe ich es direkt manuell aus, bekomme ich keine Fehlermeldung, führe ich es aber über folgendes Cronscript aus, erhalte ich die besagte Fehlermeldung. Aber das Log ist nicht leer, sondern es werden die BBB-Räume bis zu einem bestimmten Punkt angezeigt! (Evtl. weil da dann jemand Moderator ist, der nen Umlaut im Namen hat?)

*/10 * * * * /usr/bin/python /root/pytest/BBB-Statistik.py >> /root/pytest/logdatei.log

P.S. über ein Extra Crontab machen wir das Datum noch davor :wink:

Und ich hab es dahingehend erweitert, dass alle Moderatoren angezeigt werden und nicht nur einer:
Sicherlich auch umständlich (aber funktioniert immerhin):

count = 0    
for attendee in attendees:
    		count+=1
    		if attendee.find("role").text == "MODERATOR":
    			if count==1:
    				creator = attendee.find("fullName").text
    			else:
    				creator += ", " + attendee.find("fullName").text

Evtl hat ja jemand Lust mir da etwas zu helfen.

Aber trotz allen: Vielen Dank an dominik! Genau danach haben wir schon Ewigkeiten gesucht!

Hier finde ich gaaanz viele Lösungen, aber weiß nicht so recht, wie ich sie umsetze und welche ich umsetze. Kenne mich damit eben leider nicht aus:
https://stackoverflow.com/questions/9942594/unicodeencodeerror-ascii-codec-cant-encode-character-u-xa0-in-position-20

Hallo Thomas,
ich habe das Skript oben angepasst. Du hast recht, dass der Name eines Moderators oder des Raumes einen Umlaut beinhalten könnte. Deshalb ich dort bei meetingName und creator hinten ein .encode('utf-8') angefügt. Damit werden die Namen leider nicht korrekt angezeigt, aber Umlaute werden einfach übersprungen.

Viele Grüße
Dominik

1 „Gefällt mir“

Hallo,
ich habe es mal für Python 3 angepasst… Ein Logging in eine Influxdb ist auch noch geplant. Liefere ich dann nach…

#!/usr/bin/env python
#coding:utf-8
# Mit bbb-conf --secret den Endpoint und das Secret anzeigen lassen
URL = "TODO"
SECRET = "TODO"

import imp, hashlib
from xml.etree import ElementTree

try:
    imp.find_module('requests')
    import requests
except ImportError:
    print "Package requests fehlt. Bitte mit pip install requests installieren."
    exit()

meeting_api_url = "getMeetings"

def main():

        request_url = URL + "api/" + meeting_api_url  + "?checksum=" + get_checksum(meeting_api_url)
        response = get_server_response(request_url)
        tree = get_tree_from_response(response)
        meetings = tree.find("meetings")

        sum_audio = 0
        sum_video = 0
        sum_listening = 0
        sum_participants = 0

        output_table = [["ID", "Name", "Moderator", "Teilnehmer", "Nur zuhoeren", "Im VoiceChat", "Mit Video"]]

        for index, meeting in enumerate(meetings):

                table_row = generate_table_row(meeting)
                table_row.insert(0, str(index+1))

                sum_participants += int(table_row[3])
                sum_listening += int(table_row[4])
                sum_audio += int(table_row[5])
                sum_video += int(table_row[6])

                output_table.append(table_row)

        output_table.append(["", "", "", "", "", "", ""])
        output_table.append(["", "Gesamt", "", str(sum_participants), str(sum_listening), str(sum_audio), str(sum_video)])

        max_width = calculate_max_width(output_table)

        for row in output_table:

                for index, data in enumerate(row):
                        print (data.ljust(max_width[index])),
                print ("\n"),

def get_server_response(url):
        try:
                response = requests.get(url=url)
        except requests.exceptions.ConnectionError:
                print ("Rufe URL " + url + " auf.")
                print ("Es trat ein Fehler bei der Verbindung auf. Ist die URL korrekt?")
        except requests.exceptions.MissingSchema:
                print ("Die URL ist nicht korrekt. Bitte auf Fehler prüfen.")


        if response.status_code != 200:
                print "Die URL gibt den Statuscode " + str(response.status_code)  + " zurück."
                exit()

        return response.content

def get_checksum(api_url):
        m = hashlib.sha1()
        m.update(api_url + SECRET)
        return m.hexdigest()

def get_tree_from_response(response):

        tree = ElementTree.fromstring(response)

        if tree.find("returncode").text != "SUCCESS":
                print "API-Fehler:",
                print tree.find("message").text
                exit()
        return tree

def generate_table_row(meeting):
        meetingName = meeting.find("meetingName").text.encode('utf-8')
        meetingAudio = int(meeting.find("voiceParticipantCount").text)
        meetingVideo = int(meeting.find("videoCount").text)
        meetingParticipants = int(meeting.find("participantCount").text)
        meetingListening = int(meeting.find("listenerCount").text)
        creator = "-"
        attendees = meeting.find("attendees")
        for attendee in attendees:
                if attendee.find("role").text == "MODERATOR":
                        creator = attendee.find("fullName").text.encode('utf-8')
                        break

        return [meetingName, creator, str(meetingParticipants), str(meetingListening), str(meetingAudio), str(meetingVideo)]

def calculate_max_width(table):
        max_width = [0, 0, 0, 0, 0, 0, 0]
        for row in table:
                for index, data in enumerate(row):
                        max_width[index] = max(len(data), max_width[index])

        return max_width

if __name__ == '__main__':
    main()

Guten Morgen, erkennt jemand auf den ersten Blick, welche Änderung vorzunehmen sind, damit alle Nutzer einer Konferenz namentlich mit der Art ihrer Verbindung erfasst werden?

Hier kannst du für jeden Attendee nicht nur „role“ abfragen, sondern auch den „fullName“…

  for attendee in attendees:
    if attendee.find("role").text == "MODERATOR":
            creator = attendee.find("fullName").text
            break

Hallo Leo,

heute hat es mit deinen Anpassungen auch bei mir geklappt.

Ich habe immer das Problem, dass die BBB-Verbindungen über Moodle laufen und ich mich dann tot suche in Moodle den entsprechenden Raum zu finden.
Ausgabe von Name der BBB-Session und Moderator hilft mir nur sehr eingeschrenkt.

Aber die Richtung stimmt, danke :slight_smile:
VG Andre

An welcher Stelle muss ich den Code im Script einfügen?

for attendee in attendees:
    if attendee.find("role").text == "MODERATOR":
            creator = attendee.find("fullName").text
            break

VG Andre

Hallo Andre,

ich nutze es selbst nicht mit Moodle, aber habe gerade mal in den Code des Moodle-Plugins für BBB geschaut. Beim Anlegen eines Raumes werden ein paar Meta-Daten übergeben, die man per getMeetingInfo abfragen kann.

$metadata = [
    'bbb-origin' => $bbbsession['origin'],
    'bbb-origin-version' => $bbbsession['originVersion'],
    'bbb-origin-server-name' => $bbbsession['originServerName'],
    'bbb-origin-server-common-name' => $bbbsession['originServerCommonName'],
    'bbb-origin-tag' => $bbbsession['originTag'],
    'bbb-context' => $bbbsession['course']->fullname,
    'bbb-context-id' => $bbbsession['course']->id,
    'bbb-context-name' => trim(html_to_text($bbbsession['course']->fullname, 0)),
    'bbb-context-label' => trim(html_to_text($bbbsession['course']->shortname, 0)),
    'bbb-recording-name' => bigbluebuttonbn_html2text($bbbsession['meetingname'], 64),
    'bbb-recording-description' => bigbluebuttonbn_html2text($bbbsession['meetingdescription'], 64),
    'bbb-recording-tags' => bigbluebuttonbn_get_tags($bbbsession['cm']->id), // Same as $id.
];

Wenn du den Code anpasst und mal
meeting.find("bbb-context-name").text.encode('utf-8')
müsstest du den Kursnamen herausfinden können. Da ich kein Moodle mitnutze, kann ich es nicht prüfen.

Viele Grüße
Dominik

Hallo Dominik,

danke, jetzt müsste ich Dich nur noch verstehen :wink:

Wie kann ich das abfragen, redest Du von dem Script oder von was??
Bitte Kurzanleitung für DAU’s :wink:
Auf jeden Fall eine Anregung, vielen Dank.
VG Andre