From 68bb94d1d817ace50c2af7212980fc9c642398cd Mon Sep 17 00:00:00 2001
From: David Huss <dh@atoav.com>
Date: Wed, 10 Jun 2020 17:00:50 +0200
Subject: [PATCH] Add --read-load-from option to meetings

---
 bbbmon/__init__.py |  2 +-
 bbbmon/bbbmon.py   | 18 ++++++++++------
 bbbmon/load.py     | 52 ++++++++++++++++++++++++++++++++++++++++++++++
 bbbmon/meetings.py | 16 +++++++++++---
 pyproject.toml     |  2 +-
 5 files changed, 79 insertions(+), 11 deletions(-)
 create mode 100644 bbbmon/load.py

diff --git a/bbbmon/__init__.py b/bbbmon/__init__.py
index a64f0d9..6903c24 100644
--- a/bbbmon/__init__.py
+++ b/bbbmon/__init__.py
@@ -1 +1 @@
-__version__ = '0.1.31'
+__version__ = '0.1.32'
diff --git a/bbbmon/bbbmon.py b/bbbmon/bbbmon.py
index da0451b..f4c6af0 100755
--- a/bbbmon/bbbmon.py
+++ b/bbbmon/bbbmon.py
@@ -13,6 +13,7 @@ from bbbmon.xmldict import XmlListConfig, XmlDictConfig
 from bbbmon.configuration import Config, Endpoint, SERVER_PROPERTIES_FILE, Url, Secret, get_user_config_path, init_config, new_config
 from bbbmon.meetings import *
 from bbbmon.printing import *
+from bbbmon.load import *
 
 
 
@@ -53,7 +54,7 @@ def main(userconfig, watch, version):
 
     Internally bbbmon relies on the offical bbb-API, which means you need to have the server's secret in order to create a valid request. Create a new configuration with: bbbmon config --new
     """
-    __version__ = "0.1.31"
+    __version__ = "0.1.32"
 
     if version:
         print(__version__)
@@ -68,6 +69,7 @@ def main(userconfig, watch, version):
 @click.option('--endpoint', '-e', multiple=True, help="Filter by one or more endpoints as named in the user configuration (e.g. [servername]). Order is respected.")
 @click.option('--watch', '-w', help="Run repeatedly with the given interval in seconds", type=click.IntRange(2, 2147483647, clamp=True))
 @click.option('-n', help="The number of meetings to show in the leaderboards (Default: 5)", type=int)
+@click.option('--read-load-from', help="Read the load from file/https (if starts with http(s) request, else read from path). Looks for floats and calculates the average.", type=str)
 @click.option('--leaderboards/--no-leaderboards', default=True, show_default=True, help="Hide or show the meeting leaderboards")
 @click.option('--participants/--no-participants', default=True, show_default=True, help="Hide or show the participants")
 @click.option('--meetings/--no-meetings', default=True, show_default=True, help="Hide or show the meetings")
@@ -79,7 +81,7 @@ def main(userconfig, watch, version):
 @click.option('--all', '-a', 'all_', is_flag=True, help="Print all")
 @click.option('--fancy/--no-fancy', default=False, show_default=True, help="Use fancy headers")
 @click.option('--sum','sum_', is_flag=True, help="Print all")
-def meetings(ctx, userconfig, watch, short, compact, n, all_, twolines, leaderboards, participants, presenter, presenter_id, meetings, endpoint, fancy, sum_):
+def meetings(ctx, userconfig, watch, short, compact, n, all_, twolines, read_load_from, leaderboards, participants, presenter, presenter_id, meetings, endpoint, fancy, sum_):
     """View currently active meetings"""
     if short:
         leaderboards = False
@@ -99,21 +101,25 @@ def meetings(ctx, userconfig, watch, short, compact, n, all_, twolines, leaderbo
 
     config = init_config(userconfig)
     config.filter_endpoints(endpoint)
+
+    # Load is none if it doesn't work
+    load = read_load(read_load_from)
+
     if watch is not None:
         while watch is not None:
             try:
                 if twolines:
-                    meetings_twolines(config, watch, fancy, sum_)
+                    meetings_twolines(config, watch, fancy, sum_, load)
                 else:
-                    list_meetings(config, leaderboards, n, participants, presenter, presenter_id, meetings, watch, fancy, compact, sum_)
+                    list_meetings(config, leaderboards, n, participants, presenter, presenter_id, meetings, watch, fancy, compact, sum_, load)
                 time.sleep(watch)
             except KeyboardInterrupt:
                 sys.exit()
     else:
         if twolines:
-            meetings_twolines(config, watch, fancy, sum_)
+            meetings_twolines(config, watch, fancy, sum_, load)
         else:
-            list_meetings(config, leaderboards, n, participants, presenter, presenter_id, meetings, watch, fancy, compact, sum_)
+            list_meetings(config, leaderboards, n, participants, presenter, presenter_id, meetings, watch, fancy, compact, sum_, load)
 
 
 
diff --git a/bbbmon/load.py b/bbbmon/load.py
new file mode 100644
index 0000000..ac1d483
--- /dev/null
+++ b/bbbmon/load.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import os
+import sys
+import re
+import requests
+from typing import Optional
+from bbbmon.printing import eprint
+
+
+LOAD_PATTERN = re.compile(r"(?!\d+\.\d+[\./])\d+\.\d+")
+
+def read_load(read_load_from: Optional[str]) -> Optional[float]:
+    """
+    Read the load either from a file or a url depending on the input. Return None if anything fails.
+    This matches all floats in the given file/url and returns the average
+    """
+    if read_load_from is None:
+        return None
+
+    text = ""
+    if read_load_from.lower().startswith(("http")):
+        try:
+            r = requests.get(read_load_from, timeout=3)
+            text = r.text
+        except:
+            eprint("Error: Ignored load - Couldn't read load from {}: Response was: {}".format(read_load_from, r))
+            return None
+    else:
+        if os.path.isfile(read_load_from):
+            with open(read_load_from, "r", encoding="utf-8") as f:
+                text = f.read()
+        else:
+            eprint("Error: Ignored load - Couldn't read load from {}: Not a file or not readable".format(read_load_from))
+            return None
+
+    text = text.strip()
+
+    if text == "":
+        eprint("Error: Ignored load - Couldn't read load from {}: The file was empty".format(read_load_from))
+        return None
+
+    matches = re.findall(LOAD_PATTERN, text)
+
+    if matches is None:
+        eprint("Error: Ignored load - Couldn't read load from {}: No float values found".format(read_load_from))
+        return None
+    elif len(matches) == 0:
+        eprint("Error: Ignored load - Couldn't read load from {}: No float values found".format(read_load_from))
+        return None
+    else:
+        return sum([float(m) for m in matches]) / float(len(matches))
\ No newline at end of file
diff --git a/bbbmon/meetings.py b/bbbmon/meetings.py
index 553e6ed..84b0c8c 100644
--- a/bbbmon/meetings.py
+++ b/bbbmon/meetings.py
@@ -143,7 +143,7 @@ def get_duration(meeting: XmlDictConfig) -> timedelta:
     return duration
 
 
-def list_meetings(config: Config, leaderboards: bool, n: int, participants: bool, presenter: bool, presenter_id: bool, show_meetings: bool, watch: int, fancy: bool, compact: bool, sum_:bool):
+def list_meetings(config: Config, leaderboards: bool, n: int, participants: bool, presenter: bool, presenter_id: bool, show_meetings: bool, watch: int, fancy: bool, compact: bool, sum_:bool, load: Optional[float]):
     """
     For each endpoint in the configuration get the active meetings and print 
     out an overview of the current bbb-usage
@@ -163,6 +163,10 @@ def list_meetings(config: Config, leaderboards: bool, n: int, participants: bool
     if watch is not None:
         click.clear()
 
+    # Average load
+    if load is not None:
+        print("Average load is: {:04.2f}".format(load))
+
 
     for i, endpoint in enumerate(config_override.endpoints):
         # Print divider if there is more than one endpoint
@@ -270,7 +274,7 @@ def sum_meetings(config: Config, meetings:  Optional[XmlDictConfig]) ->  Optiona
 
 
 
-def meetings_twolines(config: Config, watch: int, fancy: bool, sum_:bool):
+def meetings_twolines(config: Config, watch: int, fancy: bool, sum_:bool, load: Optional[float]):
     """
     For each endpoint in the configuration get the active meetings and print 
     out an overview of the current bbb-usage. This is guaranteed to fit within
@@ -349,7 +353,7 @@ def meetings_twolines(config: Config, watch: int, fancy: bool, sum_:bool):
         avg_s = avg_s/60./60.
 
         if not fancy:
-            if config_override.on_server:
+            if config_override.on_server and load is None:
                 # If on server get load
                 w = subprocess.run(["w | head -1"], shell=True, stdout=subprocess.PIPE)
                 w = w.stdout.decode('utf-8').strip().split("load average:")
@@ -366,6 +370,12 @@ def meetings_twolines(config: Config, watch: int, fancy: bool, sum_:bool):
                     "stats   {:>2} /  {:<2}    {:>3}    {:>3}   {:>3}   {:>3}  {:>3}  {:.1f}"\
                     .format(n_recording, n_running, n_participants, n_moderator, n_video, n_voice, n_listeners, w)
                 ]
+            elif load is not None:
+                lines = [
+                    "{}    rec / ses    ppl    mod   vid   mic  ear  lod".format(endpoint.name[:3]),
+                    "stats   {:>2} /  {:<2}    {:>3}    {:>3}   {:>3}   {:>3}  {:>3}  {:.1f}"\
+                    .format(n_recording, n_running, n_participants, n_moderator, n_video, n_voice, n_listeners, load)
+                ]
             else:
                 lines = [
                     "{}    rec / ses    ppl    mod   vid   mic  ear".format(endpoint.name[:3]),
diff --git a/pyproject.toml b/pyproject.toml
index fa89388..d6295e2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "bbbmon"
-version = "0.1.31"
+version = "0.1.32"
 description = "A small CLI utility to monitor bbb usage"
 authors = ["David Huss <david.huss@hfbk-hamburg.de>"]
 maintainers = ["David Huss <david.huss@hfbk-hamburg.de>"]
-- 
GitLab