mirror of
https://codeberg.org/langfingaz/bbb-status
synced 2024-11-22 20:29:32 +01:00
plot data
This commit is contained in:
parent
35b305393a
commit
32d8158921
@ -6,7 +6,7 @@ COPY requirements.txt ./
|
||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY ./src/ ./
|
||||
RUN chmod +x ./logMeetingData.py
|
||||
RUN chmod +x ./langfingaz/logMeetingData.py
|
||||
|
||||
# unbuffered output option otherwise script sleeps before any output appears
|
||||
CMD [ "python", "-u", "./logMeetingData.py" ]
|
||||
CMD [ "python", "-u", "./langfingaz/logMeetingData.py" ]
|
||||
|
@ -2,8 +2,9 @@ version: '3.7'
|
||||
services:
|
||||
dk-gen:
|
||||
build: .
|
||||
# environment:
|
||||
# - PYTHONPATH=/usr/src/
|
||||
environment:
|
||||
# pythonpath is required to import from self created modules (langfingaz)
|
||||
- PYTHONPATH=/usr/src/
|
||||
volumes:
|
||||
- ./secret:/usr/secret
|
||||
- ./data:/usr/data
|
||||
|
0
plot/dummy
Normal file
0
plot/dummy
Normal file
0
src/langfingaz/__init__.py
Normal file
0
src/langfingaz/__init__.py
Normal file
@ -2,7 +2,7 @@ import hashlib
|
||||
import requests
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import util
|
||||
import langfingaz.util.fileUtil as fileUtil
|
||||
|
||||
|
||||
def requestMeetingData() -> str:
|
||||
@ -31,16 +31,16 @@ def getRequestUrl(api_method: str = 'getMeetings', query_string: str = '') -> st
|
||||
|
||||
|
||||
def getUrl() -> str:
|
||||
filepath = util.getWorkingDir().joinpath("../secret").joinpath("url.txt")
|
||||
url = util.readFirstLine(filepath).strip()
|
||||
filepath = fileUtil.getProjectBaseDir().joinpath("secret").joinpath("url.txt")
|
||||
url = fileUtil.readFirstLine(filepath).strip()
|
||||
if not url.endswith("/"):
|
||||
raise ValueError("url should end with '/'")
|
||||
return url
|
||||
|
||||
|
||||
def getSecret() -> str:
|
||||
filepath = util.getWorkingDir().joinpath("../secret").joinpath("secret.txt")
|
||||
secret = util.readFirstLine(filepath).strip()
|
||||
filepath = fileUtil.getProjectBaseDir().joinpath("secret").joinpath("secret.txt")
|
||||
secret = fileUtil.readFirstLine(filepath).strip()
|
||||
min_length = 12
|
||||
if len(secret) <= min_length:
|
||||
raise ValueError("secret should be longer than {} characters!".format(min_length))
|
12
src/langfingaz/loadData.py
Normal file
12
src/langfingaz/loadData.py
Normal file
@ -0,0 +1,12 @@
|
||||
from typing import Tuple
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from langfingaz.util import fileUtil
|
||||
|
||||
|
||||
def loadData(file: Path) -> Tuple[str, datetime]:
|
||||
dataStr: str = file.read_text()
|
||||
t: datetime = fileUtil.getDatetimePrefix(file)
|
||||
|
||||
return dataStr, t
|
@ -1,11 +1,10 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import saveMeetings
|
||||
import bbbRequest
|
||||
import parseMeetings
|
||||
import util
|
||||
from langfingaz import parseMeetings, bbbRequest, saveData
|
||||
from langfingaz.util import util as util
|
||||
|
||||
|
||||
def sleepFiveMin(verbose=False):
|
||||
@ -17,13 +16,12 @@ def sleepFiveMin(verbose=False):
|
||||
time.sleep(fiveMinutes)
|
||||
|
||||
|
||||
def v2():
|
||||
def v2(folder: Path = saveData.getDefaultFolder()):
|
||||
print("BBB meetingData logger started!")
|
||||
|
||||
while True:
|
||||
meetingsStr = bbbRequest.requestMeetingData()
|
||||
saveDir = util.getWorkingDir().joinpath("../data")
|
||||
savedFile = saveMeetings.saveMeetingsData(meetingsStr, saveDir)
|
||||
savedFile = saveData.saveMeetingsData(meetingsStr, folder)
|
||||
print("Saved meetings at {}".format(savedFile))
|
||||
|
||||
meetings = parseMeetings.parseMeetingsData(meetingsStr)
|
||||
@ -34,10 +32,9 @@ def v2():
|
||||
sleepFiveMin(verbose=True)
|
||||
|
||||
|
||||
def v1():
|
||||
def v1(folder: Path = saveData.getDefaultFolder()):
|
||||
while True:
|
||||
saveDir = util.getWorkingDir().joinpath("../data")
|
||||
saveMeetings.requestAndSaveMeetingData(saveDir)
|
||||
saveData.requestAndSaveMeetingData(folder)
|
||||
print('.', end='')
|
||||
sleepFiveMin()
|
||||
|
@ -1,7 +1,8 @@
|
||||
from typing import List
|
||||
from xml.etree import ElementTree
|
||||
from datetime import datetime
|
||||
|
||||
import util
|
||||
from langfingaz.util import util
|
||||
|
||||
|
||||
class Meeting(object):
|
||||
@ -11,7 +12,8 @@ class Meeting(object):
|
||||
self.internalMeetingID: str = xml_meeting.find('internalMeetingID').text
|
||||
|
||||
self.isRunning: bool = util.asBoolean(xml_meeting.find('running').text)
|
||||
self.startTime = xml_meeting.find('startTime').text
|
||||
self.startTime: int = int(xml_meeting.find('startTime').text)
|
||||
self.createTime: int = int(xml_meeting.find('createTime').text)
|
||||
if self.isRunning:
|
||||
self.endTime = None
|
||||
else:
|
||||
@ -56,11 +58,14 @@ class BbbStatus(object):
|
||||
and (TODO) it's CPU utilization and network usage.
|
||||
"""
|
||||
|
||||
def __init__(self, meetings: List[Meeting]):
|
||||
def __init__(self, meetings: List[Meeting], pointOfTime: datetime = None):
|
||||
"""
|
||||
:param meetings: All current meetings
|
||||
:param pointOfTime: The date and time at which the information about
|
||||
the running meetings has been captured / requested from the BBB instance
|
||||
"""
|
||||
self.meetings = meetings
|
||||
self.pointOfTime = pointOfTime
|
||||
|
||||
self.recordingCount = 0
|
||||
self.participantCount = 0
|
52
src/langfingaz/plotMeetings.py
Normal file
52
src/langfingaz/plotMeetings.py
Normal file
@ -0,0 +1,52 @@
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
import matplotlib.pyplot as plt # TODO
|
||||
from datetime import datetime
|
||||
|
||||
from langfingaz import loadData
|
||||
from langfingaz import parseMeetings
|
||||
from langfingaz.parseMeetings import BbbStatus, Meeting
|
||||
from langfingaz.util import fileUtil
|
||||
|
||||
|
||||
def plotMeetings(folder: Path):
|
||||
bbbStati: List[BbbStatus] = []
|
||||
|
||||
for file in folder.iterdir():
|
||||
if file.name.endswith(".xml"):
|
||||
dataStr, t = loadData.loadData(file)
|
||||
meetings: List[Meeting] = parseMeetings.parseMeetingsData(dataStr)
|
||||
bbbStati.append(parseMeetings.BbbStatus(meetings, t))
|
||||
|
||||
doPlotMeetings(bbbStati)
|
||||
|
||||
|
||||
def doPlotMeetings(bbbStati: List[BbbStatus]):
|
||||
time = [] # x-axis: time
|
||||
participants = [] # yAxis (1)
|
||||
videos = [] # yAxis (2)
|
||||
voices = [] # yAxis (3)
|
||||
|
||||
for bbbStatus in bbbStati:
|
||||
time.append(bbbStatus.pointOfTime)
|
||||
participants.append(bbbStatus.participantCount)
|
||||
videos.append(bbbStatus.videoCount)
|
||||
voices.append(bbbStatus.voiceParticipantCount)
|
||||
|
||||
|
||||
# Note that even in the OO-style, we use `.pyplot.figure` to create the figure.
|
||||
fig, ax = plt.subplots() # Create a figure and an axes.
|
||||
ax.plot(time, participants, label='participants') # Plot some data on the axes.
|
||||
ax.plot(time, videos, label='video') # Plot more data on the axes...
|
||||
ax.plot(time, voices, label='voice') # ... and some more.
|
||||
ax.set_xlabel('time') # Add an x-label to the axes.
|
||||
ax.set_ylabel('numbers') # Add a y-label to the axes.
|
||||
ax.set_title("BigBlueButton Statistics") # Add a title to the axes.
|
||||
ax.legend() # Add a legend.
|
||||
|
||||
fig.savefig(fileUtil.setDatetimePrefix(fileUtil.getProjectBaseDir().joinpath("plot"), datetime.now()))
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
plotMeetings(fileUtil.getProjectBaseDir().joinpath("data"))
|
@ -1,10 +1,16 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import bbbRequest
|
||||
import langfingaz.util.fileUtil as fileUtil
|
||||
# import util.util as util
|
||||
from langfingaz import bbbRequest
|
||||
|
||||
|
||||
def requestAndSaveMeetingData(folder: Path) -> Path:
|
||||
def getDefaultFolder() -> Path:
|
||||
return fileUtil.getProjectBaseDir().joinpath("data")
|
||||
|
||||
|
||||
def requestAndSaveMeetingData(folder: Path = getDefaultFolder()) -> Path:
|
||||
"""
|
||||
save a new xml file in the given folder
|
||||
|
||||
@ -16,7 +22,7 @@ def requestAndSaveMeetingData(folder: Path) -> Path:
|
||||
return saveMeetingsData(bbbRequest.requestMeetingData(), folder)
|
||||
|
||||
|
||||
def saveMeetingsData(dataStr: str, folder: Path) -> Path:
|
||||
def saveMeetingsData(dataStr: str, folder: Path = getDefaultFolder()) -> Path:
|
||||
"""
|
||||
save a new xml file in the given folder
|
||||
|
||||
@ -28,7 +34,7 @@ def saveMeetingsData(dataStr: str, folder: Path) -> Path:
|
||||
return doSaveData(dataStr, folder, 'meetings')
|
||||
|
||||
|
||||
def doSaveData(dataStr: str, folder: Path, dataType: str = 'meetings') -> Path:
|
||||
def doSaveData(dataStr: str, folder: Path, dataType: str) -> Path:
|
||||
"""
|
||||
save a new xml file in the given folder
|
||||
|
||||
@ -38,11 +44,9 @@ def doSaveData(dataStr: str, folder: Path, dataType: str = 'meetings') -> Path:
|
||||
:return: Path to created file
|
||||
"""
|
||||
|
||||
# time_str = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||||
time_str = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
filename = '{}_{}.xml'.format(time_str, dataType)
|
||||
filepath = folder.joinpath(filename)
|
||||
fileWithoutDate = folder.joinpath(dataType + '.xml')
|
||||
prefixedFile = fileUtil.setDatetimePrefix(fileWithoutDate, datetime.now())
|
||||
|
||||
with open(filepath, "w") as xml_file:
|
||||
with open(prefixedFile, "w") as xml_file:
|
||||
xml_file.write(dataStr)
|
||||
return filepath
|
||||
return prefixedFile
|
0
src/langfingaz/util/__init__.py
Normal file
0
src/langfingaz/util/__init__.py
Normal file
51
src/langfingaz/util/fileUtil.py
Normal file
51
src/langfingaz/util/fileUtil.py
Normal file
@ -0,0 +1,51 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
|
||||
def getWorkingDir() -> Path:
|
||||
return Path(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
|
||||
def getProjectBaseDir() -> Path:
|
||||
return getWorkingDir().parent.parent.parent
|
||||
|
||||
|
||||
def readFirstLine(file: Path) -> str:
|
||||
"""
|
||||
:param file: Path to file
|
||||
:return: first line of file
|
||||
"""
|
||||
with open(file, "r") as f:
|
||||
return f.readline()
|
||||
|
||||
|
||||
def __getDatetimePrefixLength() -> int:
|
||||
return len("20200101_120030") # 1st January 2020, 12:00 and 30 seconds
|
||||
|
||||
|
||||
def setDatetimePrefix(file: Path, t: datetime) -> Path:
|
||||
# filename = file.name
|
||||
prefix = t.strftime('%Y%m%d_%H%M%S')
|
||||
filename = prefix + "_" + file.name
|
||||
return file.parent.joinpath(filename)
|
||||
|
||||
|
||||
def removeDatetimePrefix(file: Path) -> Path:
|
||||
prefixLen = __getDatetimePrefixLength()
|
||||
|
||||
# prefixlen + 1 to remove the underline!
|
||||
return file.parent.joinpath(file.name[prefixLen + 1:])
|
||||
|
||||
|
||||
def getDatetimePrefix(file: Path) -> datetime:
|
||||
"""
|
||||
:param file: some file which filename is prefixed with a date in the form %Y%m%d_%H%M%S
|
||||
:return: date from filename
|
||||
"""
|
||||
prefixLen = __getDatetimePrefixLength()
|
||||
|
||||
if len(file.name) < prefixLen:
|
||||
raise ValueError("Given file does not seem to contain a datetime prefix!")
|
||||
prefix = file.name[:prefixLen]
|
||||
return datetime.strptime(prefix, '%Y%m%d_%H%M%S')
|
@ -1,18 +1,3 @@
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
def getWorkingDir() -> Path:
|
||||
return Path(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
def readFirstLine(file: Path) -> str:
|
||||
"""
|
||||
:param file: Path to file
|
||||
:return: first line of file
|
||||
"""
|
||||
with open(file, "r") as f:
|
||||
return f.readline()
|
||||
|
||||
|
||||
def indentMultilineStr(s: str, indentWith='\t'):
|
||||
indented = indentWith + s.replace('\n', '\n' + indentWith)
|
||||
if s.endswith('\n'):
|
60
src/langfingaz/util/utilTest.py
Normal file
60
src/langfingaz/util/utilTest.py
Normal file
@ -0,0 +1,60 @@
|
||||
import unittest
|
||||
from langfingaz.util import util as util
|
||||
import langfingaz.util.fileUtil as fileUtil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class UtilTestCase(unittest.TestCase):
|
||||
def test_indentString(self):
|
||||
unindented = "Hello\nWorld!"
|
||||
expected = "\tHello\n\tWorld!"
|
||||
|
||||
actual = util.indentMultilineStr(unindented)
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_indentString2(self):
|
||||
unindented = "Hello\nWorld!\n"
|
||||
expected = "\tHello\n\tWorld!\n"
|
||||
|
||||
actual = util.indentMultilineStr(unindented)
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_addPrefixDate(self):
|
||||
file: Path = Path("/foo/bar")
|
||||
t: datetime = datetime.strptime('20200101_120030', '%Y%m%d_%H%M%S')
|
||||
expectedFile: Path = Path("/foo/20200101_120030_bar")
|
||||
|
||||
# ACT
|
||||
prefixedFile: Path = fileUtil.setDatetimePrefix(file, t)
|
||||
|
||||
# ASSERT
|
||||
self.assertEqual(type(expectedFile), type(prefixedFile))
|
||||
self.assertEqual(expectedFile, prefixedFile)
|
||||
|
||||
def test_readPrefixDate(self):
|
||||
file: Path = Path("/foo/20200101_120030_bar")
|
||||
expectedDate = datetime.replace(year=2020, month=1, day=1, hour=12, minute=0, second=1)
|
||||
|
||||
# ACT
|
||||
dateFromFile: datetime = fileUtil.getDatetimePrefix(file)
|
||||
|
||||
# ASSERT
|
||||
self.assertEqual(type(expectedDate), type(dateFromFile))
|
||||
self.assertEqual(expectedDate, dateFromFile)
|
||||
|
||||
def test_setAndRemoveDatetimePrefix(self):
|
||||
t: datetime = datetime.strptime('20200101_120030', '%Y%m%d_%H%M%S')
|
||||
|
||||
file: Path = Path("/foo/bar")
|
||||
prefixed: Path = fileUtil.setDatetimePrefix(file, t)
|
||||
actual: Path = fileUtil.removeDatetimePrefix(prefixed)
|
||||
|
||||
self.assertEqual(type(file), type(actual))
|
||||
self.assertEqual(file, actual)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,24 +0,0 @@
|
||||
import unittest
|
||||
import util
|
||||
|
||||
|
||||
class UtilTestCase(unittest.TestCase):
|
||||
def test_indentString(self):
|
||||
unindented = "Hello\nWorld!"
|
||||
expected = "\tHello\n\tWorld!"
|
||||
|
||||
actual = util.indentMultilineStr(unindented)
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_indentString2(self):
|
||||
unindented = "Hello\nWorld!\n"
|
||||
expected = "\tHello\n\tWorld!\n"
|
||||
|
||||
actual = util.indentMultilineStr(unindented)
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user