Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions bookmarklet/README.md
Original file line number Diff line number Diff line change
@@ -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.

92 changes: 92 additions & 0 deletions bookmarklet/bookmark.js
Original file line number Diff line number Diff line change
@@ -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) <img src="https://www.svgrepo.com/show/923/leaf.svg" alt="leaf" width="13px" heigh="13px" />';
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) <img src="https://www.svgrepo.com/show/923/leaf.svg" alt="leaf" width="13px" heigh="13px" />';
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);

})();
1 change: 1 addition & 0 deletions bookmarklet/bookmark.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bookmarklet/bookmarklet
Original file line number Diff line number Diff line change
@@ -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) <img src="https://www.svgrepo.com/show/923/leaf.svg" alt="leaf" width="13px" heigh="13px" />',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) <img src="https://www.svgrepo.com/show/923/leaf.svg" alt="leaf" width="13px" heigh="13px" />',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)})();
Binary file added bookmarklet/figures/bookmarklet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions bookmarklet/server.py
Original file line number Diff line number Diff line change
@@ -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')