diff --git a/bookmarklet/README.md b/bookmarklet/README.md new file mode 100644 index 0000000..91bcf3c --- /dev/null +++ b/bookmarklet/README.md @@ -0,0 +1,31 @@ +# CAWS Bookmarklet + +This folder contains the javascript code used for the bookmarklet. The +bookmarklet query the energy usage of your available endpoints within our database and +will inject HTML code into the +[Globus web app's Compute page](https://app.globus.org/compute) to display +energy expenditure information. + +![bookmarklet](figures/bookmarklet.png) + +## How to use + +Create a bookmark of any name with the content of +[bookmarklet](https://github.com/AK2000/caws/blob/bookmarklet/bookmarklet/bookmarklet) +as the URL. Then, proceed to [Globus Compute](https://app.globus.org/compute) and +click on your newly created bookmark to execute the bookmarklet. The Globus Compute page should then +be updated with the total energy expenditure of the endpoints. + +## Server + +By default, the code queries our running server for energy expenditure information. However, if you +have chosen to host this information elsewhere, we have provided the server code used by our bookmarklet. +To execute the server, simply define the following environment variables `CAWS_HOST`, `CAWS_USER`, +`CAWS_PASSWORD` and execute the provided [server.py](https://github.com/AK2000/caws/blob/bookmarklet/bookmarklet/server.py) script. + +For example, `CAWS_HOST='127.0.0.1' CAWS_USER='fake_user' CAWS_PASSWORD='fake_password' python server.py`. + +## Troubleshooting +Currently the bookmarklet is not configured to use SSL, and therefore, your browser will likely tell you that the connection is not +secure. You will need to bypass your browser's default security to have the bookmarklet display. + diff --git a/bookmarklet/bookmark.js b/bookmarklet/bookmark.js new file mode 100644 index 0000000..d610fe0 --- /dev/null +++ b/bookmarklet/bookmark.js @@ -0,0 +1,92 @@ +(async () => { + var endpoint_links = document.querySelectorAll('div[class="h3 my-0"] a[href*="endpoints"]'); + + Array.prototype.forEach.call(endpoint_links, async function (element, index) { + + // step 1: get endpoint id + var segments = element.href.split('/'); + var endpoint = segments.pop() || segments.pop(); + + // step 2: query server for endpoint stats + let url = `https://129.114.27.161:5001/caws?endpoint=${endpoint}` + const response = await fetch(url); + const endpoint_json = await response.json(); + + // step 3: display results + var parentEnergyElement = element.parentNode.parentNode.nextElementSibling.firstElementChild; + + energyContentDiv = parentEnergyElement.querySelector('.caws-energy'); + consumedContentDiv = parentEnergyElement.querySelector('.caws-consumed'); + + if (energyContentDiv != null || consumedContentDiv != null) { + // Edit value in existing div + + if (energyContentDiv != null) + energyContentDiv.innerHTML = endpoint_json.total_energy; + + if (consumedContentDiv != null) + consumedContentDiv.innerHTML = endpoint_json.energy_consumed; + } + else { + // Create parent div + var energyDiv = document.createElement('div'); + energyDiv.classList.add('caws'); + energyDiv.classList.add('col-lg'); + energyDiv.classList.add('py-1'); + + var energyRowDiv = document.createElement('div'); + energyRowDiv.classList.add('row'); + + // title div + var energyTitleDiv = document.createElement('div'); + energyTitleDiv.classList.add('col-4'); + energyTitleDiv.classList.add('col-lg-12'); + energyTitleDiv.classList.add('small'); + energyTitleDiv.classList.add('text-uppercase'); + energyTitleDiv.classList.add('text-success'); + energyTitleDiv.innerHTML = 'Total node energy expenditure (kWh) leaf'; + energyRowDiv.appendChild(energyTitleDiv); + + // content div + var energyContentDiv = document.createElement('div'); + energyContentDiv.classList.add('caws-energy') + energyContentDiv.classList.add('col-8'); + energyContentDiv.classList.add('col-lg-12'); + energyContentDiv.classList.add('small'); + energyContentDiv.innerHTML = endpoint_json.total_energy; + energyRowDiv.appendChild(energyContentDiv); + energyDiv.appendChild(energyRowDiv); + parentEnergyElement.appendChild(energyDiv); + + var consumedDiv = document.createElement('div'); + consumedDiv.classList.add('caws'); + consumedDiv.classList.add('col-lg'); + consumedDiv.classList.add('py-1'); + + var consumedRowDiv = document.createElement('div'); + consumedRowDiv.classList.add('row'); + + var consumedTitleDiv = document.createElement('div'); + consumedTitleDiv.classList.add('col-4'); + consumedTitleDiv.classList.add('col-lg-12'); + consumedTitleDiv.classList.add('small'); + consumedTitleDiv.classList.add('text-uppercase'); + consumedTitleDiv.classList.add('text-success'); + consumedTitleDiv.innerHTML = 'Energy consumed by tasks (kWH) leaf'; + consumedRowDiv.appendChild(consumedTitleDiv); + + // content div + var consumedContentDiv = document.createElement('div'); + consumedContentDiv.classList.add('caws-consumed') + consumedContentDiv.classList.add('col-8'); + consumedContentDiv.classList.add('col-lg-12'); + consumedContentDiv.classList.add('small'); + consumedContentDiv.innerHTML = endpoint_json.energy_consumed; + consumedRowDiv.appendChild(consumedContentDiv); + consumedDiv.appendChild(consumedRowDiv); + parentEnergyElement.appendChild(consumedDiv); + } + + }, endpoint_links); + +})(); diff --git a/bookmarklet/bookmark.min.js b/bookmarklet/bookmark.min.js new file mode 100644 index 0000000..87db46b --- /dev/null +++ b/bookmarklet/bookmark.min.js @@ -0,0 +1 @@ +(async()=>{var s=document.querySelectorAll('div[class="h3 my-0"] a[href*="endpoints"]');Array.prototype.forEach.call(s,(async function(s,e){var a=s.href.split("/");let d=`https://129.114.27.161:5001/caws?endpoint=${a.pop()||a.pop()}`;const l=await fetch(d),t=await l.json();var c=s.parentNode.parentNode.nextElementSibling.firstElementChild;if(o=c.querySelector(".caws-energy"),u=c.querySelector(".caws-consumed"),null!=o||null!=u)null!=o&&(o.innerHTML=t.total_energy),null!=u&&(u.innerHTML=t.energy_consumed);else{var n=document.createElement("div");n.classList.add("caws"),n.classList.add("col-lg"),n.classList.add("py-1");var i=document.createElement("div");i.classList.add("row");var r=document.createElement("div");r.classList.add("col-4"),r.classList.add("col-lg-12"),r.classList.add("small"),r.classList.add("text-uppercase"),r.classList.add("text-success"),r.innerHTML='Total node energy expenditure (kWh) leaf',i.appendChild(r);var o=document.createElement("div");o.classList.add("caws-energy"),o.classList.add("col-8"),o.classList.add("col-lg-12"),o.classList.add("small"),o.innerHTML=t.total_energy,i.appendChild(o),n.appendChild(i),c.appendChild(n);var p=document.createElement("div");p.classList.add("caws"),p.classList.add("col-lg"),p.classList.add("py-1");var m=document.createElement("div");m.classList.add("row");var L=document.createElement("div");L.classList.add("col-4"),L.classList.add("col-lg-12"),L.classList.add("small"),L.classList.add("text-uppercase"),L.classList.add("text-success"),L.innerHTML='Energy consumed by tasks (kWH) leaf',m.appendChild(L);var u=document.createElement("div");u.classList.add("caws-consumed"),u.classList.add("col-8"),u.classList.add("col-lg-12"),u.classList.add("small"),u.innerHTML=t.energy_consumed,m.appendChild(u),p.appendChild(m),c.appendChild(p)}}),s)})(); \ No newline at end of file diff --git a/bookmarklet/bookmarklet b/bookmarklet/bookmarklet new file mode 100644 index 0000000..8c0f891 --- /dev/null +++ b/bookmarklet/bookmarklet @@ -0,0 +1 @@ +javascript:(async()=>{var s=document.querySelectorAll('div[class="h3 my-0"] a[href*="endpoints"]');Array.prototype.forEach.call(s,(async function(s,e){var a=s.href.split("/");let d=`https://129.114.27.161:5001/caws?endpoint=${a.pop()||a.pop()}`;const l=await fetch(d),t=await l.json();var c=s.parentNode.parentNode.nextElementSibling.firstElementChild;if(o=c.querySelector(".caws-energy"),u=c.querySelector(".caws-consumed"),null!=o||null!=u)null!=o&&(o.innerHTML=t.total_energy),null!=u&&(u.innerHTML=t.energy_consumed);else{var n=document.createElement("div");n.classList.add("caws"),n.classList.add("col-lg"),n.classList.add("py-1");var i=document.createElement("div");i.classList.add("row");var r=document.createElement("div");r.classList.add("col-4"),r.classList.add("col-lg-12"),r.classList.add("small"),r.classList.add("text-uppercase"),r.classList.add("text-success"),r.innerHTML='Total node energy expenditure (kWh) leaf',i.appendChild(r);var o=document.createElement("div");o.classList.add("caws-energy"),o.classList.add("col-8"),o.classList.add("col-lg-12"),o.classList.add("small"),o.innerHTML=t.total_energy,i.appendChild(o),n.appendChild(i),c.appendChild(n);var p=document.createElement("div");p.classList.add("caws"),p.classList.add("col-lg"),p.classList.add("py-1");var m=document.createElement("div");m.classList.add("row");var L=document.createElement("div");L.classList.add("col-4"),L.classList.add("col-lg-12"),L.classList.add("small"),L.classList.add("text-uppercase"),L.classList.add("text-success"),L.innerHTML='Energy consumed by tasks (kWH) leaf',m.appendChild(L);var u=document.createElement("div");u.classList.add("caws-consumed"),u.classList.add("col-8"),u.classList.add("col-lg-12"),u.classList.add("small"),u.innerHTML=t.energy_consumed,m.appendChild(u),p.appendChild(m),c.appendChild(p)}}),s)})(); \ No newline at end of file diff --git a/bookmarklet/figures/bookmarklet.png b/bookmarklet/figures/bookmarklet.png new file mode 100644 index 0000000..b152de0 Binary files /dev/null and b/bookmarklet/figures/bookmarklet.png differ diff --git a/bookmarklet/server.py b/bookmarklet/server.py new file mode 100644 index 0000000..8011e63 --- /dev/null +++ b/bookmarklet/server.py @@ -0,0 +1,55 @@ +"""Flask server implementation.""" +import pandas as pd +import psycopg +import os + +from flask import after_this_request +from flask import Flask +from flask import jsonify +from flask import request + +app = Flask(__name__) +user = os.environ['CAWS_USER'] +password = os.environ['CAWS_PASSWORD'] +host = os.environ['CAWS_HOST'] +dbname = 'monitoring' + +def get_kwh(microjoules): + return round(microjoules * 10**-6 / 3600, 7) + +@app.route("/caws", methods=["GET"]) +def get_energy() -> bytes: + """REST request to return proxy/ies to the user. + + Returns (bytes): + The pickled dictionary of all proxies or a single pickled proxy if \ + database name to return is provided. + """ + @after_this_request + def add_header(response): + response.headers.add('Access-Control-Allow-Origin', '*') + return response + + endpoint_id = request.args.get('endpoint') + + with psycopg.connect(f'dbname={dbname} user={user} password={password} host={host}', row_factory=psycopg.rows.dict_row) as conn: + with conn.cursor() as cur: + cur.execute(f'''SELECT * + FROM "energy" + INNER JOIN "caws_task" + ON "energy".run_id = "caws_task".endpoint_id + WHERE "energy".run_id = \'{endpoint_id}\' ''' + ) # FULL JOIN so we can get energy data of endpoint even if one of the table parameters is missing + data = cur.fetchall() + if len(data) == 0: + return jsonify({'run_id': endpoint_id, 'energy_consumed': 'N/A', 'total_energy': 'N/A'}) + + df = pd.DataFrame(data) + total_energy = df['total_energy'].sum() + energy_consumed = df['energy_consumed'].sum() + + return jsonify({'run_id': endpoint_id, 'energy_consumed': get_kwh(energy_consumed), 'total_energy': get_kwh(total_energy)}) + + +if __name__ == "__main__": + app.run(debug=True, host="0.0.0.0", port=5001, ssl_context='adhoc')