From 1b43c004b908c3683505db286e19f8a96d0cbd1c Mon Sep 17 00:00:00 2001
From: Steffen Hauf <steffen.hauf@xfel.eu>
Date: Mon, 26 Aug 2019 16:54:27 +0200
Subject: [PATCH] Initial working version

---
 webservice/serve_overview.css |  33 +++++
 webservice/serve_overview.py  | 248 ++++++++++++++++++++++++++++++++++
 2 files changed, 281 insertions(+)
 create mode 100644 webservice/serve_overview.css
 create mode 100644 webservice/serve_overview.py

diff --git a/webservice/serve_overview.css b/webservice/serve_overview.css
new file mode 100644
index 000000000..d3d2e2522
--- /dev/null
+++ b/webservice/serve_overview.css
@@ -0,0 +1,33 @@
+h2 {
+   font-family: monospace;
+}
+
+div {
+   background-color: #e5f0e4;
+   padding: 20px;
+   text-align: left;
+   border: 1px solid black;
+   font-family: monospace;
+   width: auto;
+   margin: auto;
+   
+}
+
+dt {
+   font-weight: bold;
+   float: left;
+   min-width: 300px;
+   padding-right: 1em;
+   text-align: left;
+}
+
+dd {
+   text-align: left;
+}
+
+.log-out {
+   height: 200px;
+   overflow: auto;
+   background-color: white;
+
+}
diff --git a/webservice/serve_overview.py b/webservice/serve_overview.py
new file mode 100644
index 000000000..9a92ee3a2
--- /dev/null
+++ b/webservice/serve_overview.py
@@ -0,0 +1,248 @@
+import argparse
+from datetime import datetime
+import glob
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from iCalibrationDB import Detectors
+from jinja2 import Template
+import json
+import os
+import requests
+from oauthlib.oauth2 import BackendApplicationClient
+from requests_oauthlib import OAuth2Session, OAuth2
+from subprocess import check_output
+import yaml
+
+
+from xfel_calibrate.settings import free_nodes_cmd, preempt_nodes_cmd, reservation, reservation_char
+
+main_doc = """
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <link rel="stylesheet" href="serve_overview.css">
+    <title>Overview of offline calibration tasks</title>
+</head>
+<body>
+{{ maxwell_status }}
+{{ log_output }}
+{{ last_characterizations }}
+</body>
+</html>
+
+"""
+
+nodes_avail_res_cmd = "sinfo --nodes=`sinfo -p exfel -t idle -N --noheader -T | grep {} | awk '{{print $6}}'` --noheader -p exfel -o %A"
+
+maxwell_status = """
+<div>
+    <h2>Maxwell status</h2>
+    <dl>
+        <dt>Total jobs running</dt>
+        <dd>{{ total_jobs_running }}</dd>
+        <dt>Nodes available on general partition</dt>
+        <dd>{{ nodes_avail_general }}</dd>
+        <dt>Nodes available on general reservation</dt>
+        <dd>{{ nodes_avail_general_res }}</dd>
+        {% for res, avail in upex_reservations.items() %}
+            <dt>Nodes available on {{ res }}</dt>
+            <dd>{{ avail }}</dd>
+        {% endfor %}
+        <dt>Recommendation</dt>
+        <dd>{{ recommendation }}</dd>
+    </dl>
+</div>
+"""
+
+log_output = """
+<div>
+   <h2> Webservice log </h2>
+   <div class="log-out">
+   {{ logout }}
+   </div>
+</div>
+
+"""
+
+last_characterizations = """
+<div>
+   <h2>Last characterization runs</h2>
+   {% for instrument, data in char_runs.items() %}
+       <h3>{{ instrument }}</h3>
+       <dl>
+           <dt>Requested</dt><dd>{{ data['requested'] }}</dd>
+           <dt>Check in DB:</dt><dd><a href="https://in.xfel.eu/calibration/admin/calibration_constant_version?model_name=calibration_constant_version&utf8=%E2%9C%93&f%5Bphysical_device%5D%5B02524%5D%5Bo%5D=like&f%5Bphysical_device%5D%5B02524%5D%5Bv%5D={{ data['device_type'] }}&f%5Bcalibration_constant%5D%5B02697%5D%5Bo%5D=like&f%5Bcalibration_constant%5D%5B02697%5D%5Bv%5D=Offset&query=" target="_blank"> Open in calDB </a></dd>
+           <dt>Output path</dt><dd>{{ data['out_path'] }}</dd>
+           <dt>Input path</dt><dd>{{ data['in_path'] }}</dd>
+           <dt>Input runs</dt><dd>{{ data['runs'] }}</dd>
+           <dt>Input data size</dt><dd>{{ data['size'] }}</dd>
+           <dt>Output PDFs</dt><dd>{{ data['pdfs'] }}</dd>
+       </dl>
+   {% endfor %}
+
+</div>
+"""
+ 
+# HTTPRequestHandler class
+class testHTTPServer_RequestHandler(BaseHTTPRequestHandler):
+ 
+  # GET
+  def do_GET(self):
+        # Send response status code
+        self.send_response(200)
+        if self.path == "/serve_overview.css":
+            with open("serve_overview.css", "r") as f:
+                self.send_header('Content-type','text/css')
+                self.end_headers()
+                for s in f:
+                    self.wfile.write(s.encode())
+                return
+ 
+        # Send headers
+        self.send_header('Content-type','text/html')
+        self.end_headers()
+ 
+        # Send message back to client
+        # general maxwell stats
+        free = int(check_output(free_nodes_cmd, shell=True).decode('utf8'))
+        preempt = int(check_output(
+            preempt_nodes_cmd, shell=True).decode('utf8'))
+        nodes_avail_general = free + preempt
+        nodes_avail_general_res = check_output(nodes_avail_res_cmd.format(reservation), shell=True).decode('utf8')
+        ures = nodes_avail_general_res.split("/")
+        if len(ures) == 2:
+            used, reserved = ures
+        else:
+            used = 0
+            reserved = 0
+        nodes_avail_general_res = "{}/{}".format(int(reserved)-int(used), reserved)
+        total_jobs_running = check_output("sinfo -p exfel -o %A --noheader", shell=True).decode('utf8').split("/")[0]
+
+        upex_res = [r for r in check_output("sinfo -T --noheader | awk '{print $1}'", shell=True).decode('utf8').split()
+                    if "upex_" in r]
+
+        upex_reservations = {}
+        for res in upex_res:
+            nodes_avail_res = check_output(nodes_avail_res_cmd.format(res), shell=True).decode('utf8')
+            used, reserved = nodes_avail_res.split("/")
+            nodes_avail_res = "{}/{}".format(int(reserved)-int(used), reserved)
+            upex_reservations[res] = nodes_avail_res
+
+        recommendation = "DON'T SUBMIT TO RESERVATION"
+        if nodes_avail_general < int(reserved)-int(used):
+            "CONSIDER SUBMITTING TO RESERVATION"
+
+        maxwell_status_r = Template(maxwell_status).render(nodes_avail_general=nodes_avail_general,
+                                                           nodes_avail_general_res=nodes_avail_general_res,
+                                                           total_jobs_running=total_jobs_running,
+                                                           upex_reservations=upex_reservations,
+                                                           recommendation=recommendation)
+        
+        last_n_lines = check_output("tail -5000 web.log", shell=True).decode('utf8').split("\n")
+        last_n_lines = [l for l in last_n_lines if "Response error from MDC" not in l]
+        log_output_r = Template(log_output).render(logout="<br>".join(last_n_lines[::-1]))
+
+
+        last_n_lines = check_output("cat web.log", shell=True).decode('utf8').split("\n")[::-1]
+        last_chars = {}
+        mappings = {"LPD": ["LPD"],
+                    "AGIPD": ["AGIPD"],
+                    "DSSC": ["DSSC"],
+                    "PNCCD": ["PNCCD"],
+                    "JUNGFRAU": ["JNGFR", "DA05", "DA04"],
+                    "EPIX": ["EPIX"], "EPIX10K": ["EPIX"],
+                    "FASTCCD": ["DA05"] }
+        for l in last_n_lines:
+           if "DARK" in l:
+               ls = l.split()
+               if "DARK" not in ls:
+                   continue
+               detector = ls[ls.index("DARK")-1]
+               dinstance = ""
+               if "--instrument" in ls:
+                   instrument = ls[ls.index("--instrument") + 1]
+                   if detector == "DSSC":
+                        dinstance = "DSSC1M1"
+                   if detector == "AGIPD" and instrument == "SPB":
+                        dinstance = "AGIPD1M1"
+                   if detector == "AGIPD" and instrument == "MID":
+                        dinstance = "AGIPD1M2"
+                   if detector == "LPD":
+                        dinstance = "LPD1M1"
+               else:
+                   if "--db-module" in ls:
+                       instrument = ls[ls.index("--db-module") + 1]
+                       dinstance = instrument
+                   elif detector == "PNCCD":
+                       instrument = "SQS"
+                       dinstance = "PnCCD1"
+                   elif detector == "FASTCCD":
+                       instrument = "SCS"
+                       dinstance = "fastCCD1"
+                   else:
+                       instrument = ""
+ 
+               in_folder = ls[ls.index("--in-folder") + 1]
+               out_folder = ls[ls.index("--out-folder") + 1]
+               run_candidates = ["--run-high", "--run-med", "--run-low", "--run"]
+               runs = []
+               for rc in run_candidates:
+                   if rc in ls:
+                      runs.append(ls[ls.index(rc)+1])
+               if f"{instrument}-{detector}" in last_chars:
+                  continue
+
+               requested = "{} {}".format(ls[0], ls[1])
+
+               pdfs = glob.glob(f"{out_folder}/*.pdf")
+               pdfs = [p.split("/")[-1] for p in pdfs]
+               if "xfel.pdf" in pdfs:
+                   del pdfs[pdfs.index("xfel.pdf")]
+               if len(pdfs):
+                   pdfs = ", ".join(pdfs)
+               else:
+                   pdfs = "---"
+               
+               tsize = 0
+ 
+               for run in runs:
+                   run = int(run)
+                   if detector not in mappings:
+                       continue
+                   for mp in mappings[detector]:
+                       for f in glob.glob(f"{in_folder}/r{run:04d}/*{mp}*.h5"):
+                           tsize += os.stat(f).st_size
+               last_injected = ""
+               constant_valid_from = ""
+
+               last_chars[f"{instrument}-{detector}"] = {"in_path": in_folder, 
+                                                        "out_path": out_folder,
+                                                        "runs": runs,
+                                                        "pdfs": pdfs,
+                                                        "size": "{:0.1f} GB".format(tsize/1e9),
+                                                        "requested": requested,
+                                                        "device_type": detector, 
+                                                        "last_valid_from": constant_valid_from}
+        last_characterizations_r = Template(last_characterizations).render(char_runs=last_chars)        
+
+        message = Template(main_doc).render(maxwell_status=maxwell_status_r,
+                                            log_output=log_output_r,
+                                            last_characterizations=last_characterizations_r)
+        # Write content as utf-8 data
+        self.wfile.write(bytes(message, "utf8"))
+        return
+ 
+def run(port=8008):
+  print('starting server...')
+ 
+  server_address = ('localhost', port)
+  httpd = HTTPServer(server_address, testHTTPServer_RequestHandler)
+  print('running server...')
+  httpd.serve_forever()
+ 
+parser = argparse.ArgumentParser(
+    description='Start the overview server')
+parser.add_argument('--port', type=int, default=8008)
+if __name__ == "__main__":
+    args = vars(parser.parse_args())
+    run(args["port"])
-- 
GitLab