diff --git a/.gitignore b/.gitignore index 3cd7bcc..423987e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /secret/*.txt /data/*.xml -src/__pycache__/ +/src/__pycache__/ +/plot/*.png diff --git a/.idea/bbb-status.iml b/.idea/bbb-status.iml index 371977c..e0afde7 100644 --- a/.idea/bbb-status.iml +++ b/.idea/bbb-status.iml @@ -7,4 +7,7 @@ + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8fc91a3..2b11a7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,39 @@ FROM python:3.8-alpine WORKDIR /usr/src/ -COPY requirements.txt ./ -RUN pip3 install --no-cache-dir -r requirements.txt +RUN apk update && \ + apk add \ + tzdata \ + make automake gcc g++ subversion python3-dev \ + zlib-dev jpeg-dev freetype-dev lcms2-dev libwebp-dev tcl-dev tk-dev harfbuzz-dev \ + fribidi-dev libimagequant-dev libxcb-dev && \ + rm -rf /var/cache/apk/* + +# source: https://serverfault.com/a/683651/537998 +ENV TZ=Europe/Berlin +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +#COPY requirements.txt ./ +#RUN pip3 install --no-cache-dir -r requirements.txt + +# LDFLAGS required for "pip install pillow" +# otherwise this would be necessary: +## RUN ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ +## RUN ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /lib/ +# source: https://github.com/python-pillow/Pillow/issues/3103#issuecomment-382634946 +ENV LDFLAGS=-L/usr/lib/x86_64-linux-gnu/ + +RUN python3 -m pip install --no-cache-dir --upgrade pip +RUN python3 -m pip install --no-cache-dir --upgrade requests +RUN python3 -m pip install --no-cache-dir --upgrade setuptools +RUN python3 -m pip install --no-cache-dir --upgrade numpy +RUN python3 -m pip install --no-cache-dir --upgrade pillow +RUN python3 -m pip install --no-cache-dir --upgrade matplotlib COPY ./src/ ./ -RUN chmod +x ./langfingaz/logMeetingData.py +RUN chmod +x ./langfingaz/main.py # unbuffered output option otherwise script sleeps before any output appears -CMD [ "python", "-u", "./langfingaz/logMeetingData.py" ] +# python -u +ENTRYPOINT [ "python", "-u", "./langfingaz/main.py" ] +CMD [ "log-verbose" ] diff --git a/docker-compose.yml b/docker-compose.yml index a0c279d..1d1d6de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,9 +2,11 @@ version: '3.7' services: dk-gen: build: . + command: ["log"] # see main.py for available commandline arguments environment: - # pythonpath is required to import from self created modules (langfingaz) - - PYTHONPATH=/usr/src/ + - PYTHONPATH=/usr/src/ # pythonpath is required to import from self created modules ("from langfingaz ...") + - PYTHONUNBUFFERED=1 volumes: - ./secret:/usr/secret - ./data:/usr/data + - ./plot:/usr/plot diff --git a/requirements.txt b/requirements.txt index eed6988..9d7394f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests>=2.25.0 \ No newline at end of file +requests>=2.25.0 +matplotlib>=3.3.3 \ No newline at end of file diff --git a/src/langfingaz/logMeetingData.py b/src/langfingaz/logMeetingData.py index 96fdb26..dce29c5 100755 --- a/src/langfingaz/logMeetingData.py +++ b/src/langfingaz/logMeetingData.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 +import sys import time from pathlib import Path @@ -13,6 +14,8 @@ def sleepFiveMin(verbose=False): if verbose: print(">> Sleeping for five minutes <<") + + sys.stdout.flush() time.sleep(fiveMinutes) @@ -27,7 +30,7 @@ def v2(folder: Path = saveData.getDefaultFolder()): meetings = parseMeetings.parseMeetingsData(meetingsStr) bbbStatus = parseMeetings.BbbStatus(meetings) bbbStatusStr = str(bbbStatus) - print(util.indentMultilineStr(bbbStatusStr)) + print(util.indentMultilineStr(bbbStatusStr), flush=True) sleepFiveMin(verbose=True) @@ -35,7 +38,7 @@ def v2(folder: Path = saveData.getDefaultFolder()): def v1(folder: Path = saveData.getDefaultFolder()): while True: saveData.requestAndSaveMeetingData(folder) - print('.', end='') + print('.', end='', flush=True) sleepFiveMin() diff --git a/src/langfingaz/main.py b/src/langfingaz/main.py new file mode 100755 index 0000000..68b6036 --- /dev/null +++ b/src/langfingaz/main.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +from sys import argv + +from langfingaz import plotMeetings +from langfingaz import logMeetingData + + +def main(): + print("=== bbb-status ===") + print(">> given args: " + str(argv)) + print() + + usageStr = 'Usage:\n' + argv[0] + ' [log|log-verbose|plot]\n' + \ + '\tlog: log BBB meeting data every 5 minutes;\n' + \ + '\t prints one dot after each successfully saved file\n' + \ + '\tlog-verbose: log BBB meeting every 5 minute and write current status to stdout\n' + \ + '\tplot: saves a plot of the saved BBB meeting data\n' + + if len(argv) != 2: + raise ValueError("Expected one commandline argument!") + + if argv[1] == "log": + logMeetingData.v1() + elif argv[1] == "log-verbose": + logMeetingData.v2() + elif argv[1] == "plot": + plotMeetings.plotMeetings() + else: + print(usageStr) + exit(1) + + +if __name__ == '__main__': + main() diff --git a/src/langfingaz/plotMeetings.py b/src/langfingaz/plotMeetings.py index 43afb12..1799711 100644 --- a/src/langfingaz/plotMeetings.py +++ b/src/langfingaz/plotMeetings.py @@ -1,5 +1,6 @@ from pathlib import Path from typing import List +import numpy import matplotlib.pyplot as plt # TODO from datetime import datetime @@ -9,23 +10,28 @@ from langfingaz.parseMeetings import BbbStatus, Meeting from langfingaz.util import fileUtil -def plotMeetings(folder: Path): +def getDefaultPlotFolder() -> Path: + return fileUtil.getProjectBaseDir().joinpath("plot") + + +def plotMeetings(dataDir: Path = fileUtil.getProjectBaseDir().joinpath("data")): bbbStati: List[BbbStatus] = [] - for file in folder.iterdir(): + for file in dataDir.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) + image: Path = doPlotMeetings(bbbStati) + print("saved image at " + str(image)) -def doPlotMeetings(bbbStati: List[BbbStatus]): - time = [] # x-axis: time - participants = [] # yAxis (1) - videos = [] # yAxis (2) - voices = [] # yAxis (3) +def doPlotMeetings(bbbStati: List[BbbStatus]) -> Path: + time = [] # x-axis: time + participants = [] # yAxis (1) + videos = [] # yAxis (2) + voices = [] # yAxis (3) for bbbStatus in bbbStati: time.append(bbbStatus.pointOfTime) @@ -33,7 +39,6 @@ def doPlotMeetings(bbbStati: List[BbbStatus]): 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. @@ -44,9 +49,14 @@ def doPlotMeetings(bbbStati: List[BbbStatus]): 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() + image: Path = getDefaultPlotFolder().joinpath( + "{}_until_{}".format(fileUtil.asString(time[0]), fileUtil.asString(time[-1])) + ) + fig.savefig(image) + # plt.show() + + return image if __name__ == '__main__': - plotMeetings(fileUtil.getProjectBaseDir().joinpath("data")) + plotMeetings() diff --git a/src/langfingaz/util/fileUtil.py b/src/langfingaz/util/fileUtil.py index e256e2a..c4493c3 100644 --- a/src/langfingaz/util/fileUtil.py +++ b/src/langfingaz/util/fileUtil.py @@ -24,9 +24,13 @@ def __getDatetimePrefixLength() -> int: return len("20200101_120030") # 1st January 2020, 12:00 and 30 seconds +def asString(t: datetime) -> str: + return t.strftime('%Y%m%d_%H%M%S') + + def setDatetimePrefix(file: Path, t: datetime) -> Path: # filename = file.name - prefix = t.strftime('%Y%m%d_%H%M%S') + prefix = asString(t) filename = prefix + "_" + file.name return file.parent.joinpath(filename)