diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..0322f89 --- /dev/null +++ b/TODO.md @@ -0,0 +1,9 @@ +Website-Tester: Value muss 10k users sein! tomorrow + +Website Tester - Muss noch Weekly Option beinhalten "free for limited time"! Um erste Leute zu kriegen + - Das bilde ich dann inder API ab indem ich weekly freischalte für watchlist - Muss dan Remnder machen die E-mails da rauszufischen und ordentliche Mail zu schicken + +WEbseite-Tester: Blog Article+ Linked In + Newsletter + Eintrag in Products auf der Webseite ... auch mal überlegen software carbon optiizations für das GMT bei Adwords einzubuchen? Genau wie für Webseite? Gucken was da kommt? + - Hier besonders rausstellen: Wie korreleirt Netzwerk mit Device Energie Verbrauch? Gar nicht wahrscheinlich! Somit ist das SWD Model Quark! - Leute pingen! + +- Nur Google.de geht nicht. Muss noch schema etc. davor machen! \ No newline at end of file diff --git a/code.js b/code.js deleted file mode 100644 index 088008f..0000000 --- a/code.js +++ /dev/null @@ -1,214 +0,0 @@ -"use strict"; - -function truncate(str, maxLength=48) { - return str.length > maxLength - ? str.slice(0, maxLength) + '…' - : str; -} - -function normalizeUrl(url) { - const hasProtocol = /^https?:\/\//i.test(url); - const fullUrl = hasProtocol ? url : 'https://' + url; - new URL(fullUrl); // will throw if invalid - return fullUrl; -} - - -// Function to fetch data from the API and output JSON -async function fetchData() { - const apiUrl = 'https://api.green-coding.io/v2/runs?uri=https%3A%2F%2Fgithub.com%2Fgreen-coding-solutions%2Fgreen-metrics-tool&filename=templates%2Fwebsite%2Fusage_scenario.yml&failed=false&limit=10'; - - try { - const response = await fetch(apiUrl); - - if (!response.ok) { - alert('Could not fetch list with last tested websites from API.') - console.error('Error fetching data:', response); - return - } - - const response_body = await response.json(); - return response_body.data - - } catch (error) { - alert('Could not fetch data with last runs from API.') - console.error('Error fetching data:', error); - } -} - -/* Legacy -function addField() { - const container = document.getElementById('inputs-container'); - const cloned_node = document.querySelector('#clonable-input-container .field').cloneNode(true); - container.appendChild(cloned_node); -} - -function removeField(button) { - const groupToRemove = button.parentNode; - groupToRemove.parentNode.removeChild(groupToRemove); -} -*/ - - -(async () => { - - document.querySelectorAll(".close.icon").forEach(link => { - link.addEventListener("click", function(event) { - event.preventDefault(); // Prevent default navigation if needed - this.parentElement.remove(); // Remove the parent container - }); - }); - - let phase_stats_data; - const runs_data = await fetchData(); - if (runs_data == undefined) return; - - const urls = runs_data.map(el => fetch(`https://api.green-coding.io/v1/phase_stats/single/${el[0]}`)) - try { - const responses = await Promise.all(urls); - phase_stats_data = await Promise.all(responses.map(res => res.status === 200 ? res.json() : null)); - } catch (error) { - alert('Could not fetch energy and carbon data for last tested websites from API') - console.error('Error fetching data:', error); - return - } - let idx = 0; - phase_stats_data.forEach(json => { - if (json == undefined) return; // happens if request ist 204 - const data = json.data - const uuid = data['comparison_identifiers'][0] - - let cpu_energy = data['data']['[RUNTIME]']['cpu_energy_rapl_msr_component']['data']['Package_0']['data'][uuid]['mean'] - const total_duration = data['data']['[RUNTIME]']['phase_time_syscall_system']['data']['[SYSTEM]']['data'][uuid]['mean'] - const network_transfer = data['data']['[RUNTIME]']['network_io_cgroup_container']['data']['gcb-playwright']['data'][uuid]['mean'] - - cpu_energy = ((((cpu_energy * 0.000001) / (total_duration / 1000000)) * 10_000) / 1_000).toFixed(2) - const network_carbon = (((network_transfer / 1e9) * 0.06)*300*10000).toFixed(2) - - let energy_class - let energy_color - let network_carbon_class - let network_carbon_color - - if (cpu_energy > 100) { - energy_class = 'F'; - energy_color = 'red' - } else if (cpu_energy >= 80) { - energy_class = 'E'; - energy_color = 'orange' - } else if (cpu_energy >= 60) { - energy_class = 'D'; - energy_color = 'yellow' - } else if (cpu_energy >= 40) { - energy_class = 'C'; - energy_color = 'teal' - } else if (cpu_energy >= 20) { - energy_class = 'B'; - energy_color = 'olive' - } else if (cpu_energy > 0 && cpu_energy < 20) { - energy_class = 'A'; - energy_color = 'green' - } else { - energy_class = 'N/A'; - energy_color = 'purple' - } - - if (network_carbon > 1) { - network_carbon_class = 'F'; - network_carbon_color = 'red' - } else if (network_carbon >= 0.8) { - network_carbon_class = 'E'; - network_carbon_color = 'orange' - } else if (network_carbon >= 0.6) { - network_carbon_class = 'D'; - network_carbon_color = 'yellow' - } else if (network_carbon >= 0.4) { - network_carbon_class = 'C'; - network_carbon_color = 'teal' - } else if (network_carbon >= 0.2) { - network_carbon_class = 'B'; - network_carbon_color = 'olive' - } else if (network_carbon > 0 && network_carbon < 0.2) { - network_carbon_class = 'A'; - network_carbon_color = 'green' - } else { - network_carbon_class = 'N/A'; - network_carbon_color = 'purple' - } - - document.querySelector('#websites').insertAdjacentHTML( - 'beforeend', - `
${truncate(runs_data[idx][7]['__GMT_VAR_PAGE__'])} - (${(new Date(runs_data[idx][4])).toLocaleDateString(navigator.language, { year: 'numeric', month: 'short', day: 'numeric' })}) -
-
- -
-
-
${energy_class}
-
Rendering
-
-
-
${network_carbon_class} -
-
Network Data
-
-
-
-
-
`, - ); - idx++; - }); - - // Prevent form submission (for demonstration purposes) - document.querySelector('#page-form').onsubmit = async function(e) { - e.preventDefault(); - const formData = new FormData(this); - - try { - var dataToSend = { - email: formData.get('email'), - page: normalizeUrl(formData.get('page')), - mode: 'website', - schedule_mode: formData.get('schedule_mode'), - }; - } catch (error) { - alert('URL is invalid. Please enter a valid URL.') - return; - } - - try { - const response = await fetch('https://gateway.green-coding.io/save', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(dataToSend) - }); - - if (!response.ok) { - alert(`Could not send data. HTTP error! status: ${response.status} and text: ${await response.text()} `); - console.error('Error:', response); - return - } - - } catch (error) { - console.error('Error:', error); - alert('An error occurred. Check console for details.'); - return - } - - alert('Thanks, we have received your measurement request and will e-mail you shortly!', 'Success :)'); - - // reset form - document.querySelector('#page-form').reset() - }; -})() diff --git a/details.html b/details.html new file mode 100644 index 0000000..cec3469 --- /dev/null +++ b/details.html @@ -0,0 +1,131 @@ + + + + + + + + + + webNRG - Measure energy and CO2 cost of your website + + + + + + + + + + + + +
+
+

+ +
WebNRG⚡️
+

+
+ +
+
+ What? websites produce carbon emissions? +
+
+

Websites emit carbon through three factors: Hosting, Network Traffic and User Viewing

+

While others look at green hosting we created webNRG to measure Network Traffic and the power consumption while Viewing the page.

+

Check your site now and find out how emission your website creates for a typical 10k users per month!

+
+
+
+

Tested on

+
+
+
+

Rendering Energy

+

The CPU power consumption for rendering was

+

With a visit time of this equates to

+

If you have 10.000 people visiting your page per month this would consume of energy

+ +
+
+
+
+
+

Network Data

The network data transfer the website was for loading and staying on the page for

+

Assuming you have 10,000 visitors per month in your page this and the site runs on fossil energy this would equate to

+
+ + +
+ +
+ +
+
+ + + + \ No newline at end of file diff --git a/details.js b/details.js new file mode 100644 index 0000000..ddfd9ca --- /dev/null +++ b/details.js @@ -0,0 +1,57 @@ +"use strict"; + +async function fetchRunData(id) { + + let phase_stats_data; + + try { + const response = await fetch(`https://api.green-coding.io/v1/compare?ids=${id}`); + if (response.status != 200) { + alert('Could not fetch energy and carbon data for tested websites from API') + console.error('Error fetching data:', status); + return + } + return response.json() + } catch (error) { + alert('Could not fetch energy and carbon data for tested websites from API') + console.error('Error fetching data:', error); + return + } +} + +function displayRunData(run_data) { + const data = run_data.data + const uuid = data['comparison_identifiers'][0] + console.log(data); + const [total_duration_s, cpu_energy_mWh, cpu_power_W, network_data_MB, network_carbon_g, network_carbon_g_10k] = parsePhaseStats(data) + + const [energy_class, energy_color, network_carbon_class, network_carbon_color] = classifyWebsite(cpu_power_W, network_carbon_g) + + document.querySelectorAll('.website-duration').forEach(el => el.innerText = `${total_duration_s} s`) + document.querySelector('.website-network-data').innerText = `${network_data_MB} MB` + document.querySelector('.website-network-carbon-10k').innerText = `${network_carbon_g_10k} g` + document.querySelector('.website-network-data-class').innerText = network_carbon_class + document.querySelector('.website-network-data-class').classList.add(network_carbon_color) + + document.querySelector('.website-energy').innerText = `${cpu_energy_mWh} mWh` + document.querySelector('.website-power').innerText = `${cpu_power_W} W` + document.querySelector('.website-energy-class').innerText = energy_class + document.querySelector('.website-energy-class').classList.add(energy_color) + + document.querySelector('.website-energy-10k').innerText = `${cpu_energy_mWh/100} kWh` + + + document.querySelector('.website-name').innerText = data['common_info']['Usage Scenario'] + +} + +(async () => { + + bindClose() + + const id = new URL(window.location.href).searchParams.get('id'); + const run_data = await fetchRunData(id); + if (run_data == undefined) return; // happens if request ist 204 + displayRunData(run_data); + +})() diff --git a/index.html b/index.html index c00141d..0c796f5 100644 --- a/index.html +++ b/index.html @@ -63,18 +63,13 @@ max-width: 350px; } - + + - - -
-
+ + +
+

WebNRG⚡️
diff --git a/index.js b/index.js new file mode 100644 index 0000000..4d27ede --- /dev/null +++ b/index.js @@ -0,0 +1,162 @@ +"use strict"; + +function truncate(str, maxLength=48) { + return str.length > maxLength + ? str.slice(0, maxLength) + '…' + : str; +} + +function normalizeUrl(url) { + const hasProtocol = /^https?:\/\//i.test(url); + const fullUrl = hasProtocol ? url : 'https://' + url; + new URL(fullUrl); // will throw if invalid + return fullUrl; +} + + +// Function to fetch data from the API and output JSON +async function fetchRunsList() { + const apiUrl = 'https://api.green-coding.io/v2/runs?uri=https%3A%2F%2Fgithub.com%2Fgreen-coding-solutions%2Fgreen-metrics-tool&filename=templates%2Fwebsite%2Fusage_scenario.yml&failed=false&limit=10'; + + try { + const response = await fetch(apiUrl); + + if (!response.ok || response.status == 204) { + alert('Could not fetch list with last tested websites from API.') + console.error('Error fetching data:', response); + return + } + + const response_body = await response.json(); + return response_body.data + + } catch (error) { + alert('Could not fetch data with last runs from API.') + console.error('Error fetching data:', error); + } +} + +/* Legacy +function addField() { + const container = document.getElementById('inputs-container'); + const cloned_node = document.querySelector('#clonable-input-container .field').cloneNode(true); + container.appendChild(cloned_node); +} + +function removeField(button) { + const groupToRemove = button.parentNode; + groupToRemove.parentNode.removeChild(groupToRemove); +} +*/ + + +async function fetchData(runs_data) { + + let phase_stats_data; + const urls = runs_data.map(el => fetch(`https://api.green-coding.io/v1/phase_stats/single/${el[0]}`)) + try { + const responses = await Promise.all(urls); + phase_stats_data = await Promise.all(responses.map(res => res.status === 200 ? res.json() : null)); + } catch (error) { + alert('Could not fetch energy and carbon data for last tested websites from API') + console.error('Error fetching data:', error); + return + } + + let idx = 0; + phase_stats_data.forEach(json => { + if (json == undefined) return; // happens if request ist 204 + const data = json.data + + const uuid = data['comparison_identifiers'][0] + + const [total_duration_s, cpu_energy_mWh, cpu_power_W, network_data_MB, network_carbon_g] = parsePhaseStats(data) + + const [energy_class, energy_color, network_carbon_class, network_carbon_color] = classifyWebsite(cpu_power_W, network_carbon_g) + + document.querySelector('#websites').insertAdjacentHTML( + 'beforeend', + ``, + ); + idx++; + }); +} + +(async () => { + + bindClose() + + const runs_data = await fetchRunsList(); + if (runs_data == undefined) return; + + fetchData(runs_data); + + // Prevent form submission (for demonstration purposes) + document.querySelector('#page-form').onsubmit = async function(e) { + e.preventDefault(); + const formData = new FormData(this); + + try { + var dataToSend = { + email: formData.get('email'), + page: normalizeUrl(formData.get('page')), + mode: 'website', + schedule_mode: formData.get('schedule_mode'), + }; + } catch (error) { + alert('URL is invalid. Please enter a valid URL.') + return; + } + + try { + const response = await fetch('https://gateway.green-coding.io/save', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(dataToSend) + }); + + if (!response.ok) { + alert(`Could not send data. HTTP error! status: ${response.status} and text: ${await response.text()} `); + console.error('Error:', response); + return + } + + } catch (error) { + console.error('Error:', error); + alert('An error occurred. Check console for details.'); + return + } + + alert('Thanks, we have received your measurement request and will e-mail you shortly!', 'Success :)'); + + // reset form + document.querySelector('#page-form').reset() + }; +})() diff --git a/shared.js b/shared.js new file mode 100644 index 0000000..a43f859 --- /dev/null +++ b/shared.js @@ -0,0 +1,103 @@ +"use strict"; + +function bindClose() { + document.querySelectorAll(".close.icon").forEach(link => { + link.addEventListener("click", function(event) { + event.preventDefault(); // Prevent default navigation if needed + this.parentElement.remove(); // Remove the parent container + }); + }); +} + +function classifyWebsite(cpu_power, network_carbon) { + let energy_class + let energy_color + let network_carbon_class + let network_carbon_color + + if (cpu_power >= 4) { + energy_class = 'F'; + energy_color = 'red' + } else if (cpu_power >= 3.6) { + energy_class = 'E'; + energy_color = 'orange' + } else if (cpu_power >= 3.4) { + energy_class = 'D'; + energy_color = 'yellow' + } else if (cpu_power >= 3.2) { + energy_class = 'C'; + energy_color = 'teal' + } else if (cpu_power >= 3.0) { + energy_class = 'B'; + energy_color = 'olive' + } else if (cpu_power > 0.0 && cpu_power < 3.0) { + energy_class = 'A'; + energy_color = 'green' + } else { + energy_class = 'N/A'; + energy_color = 'purple' + } + + if (network_carbon > 0.01) { + network_carbon_class = 'F'; + network_carbon_color = 'red' + } else if (network_carbon >= 0.008) { + network_carbon_class = 'E'; + network_carbon_color = 'orange' + } else if (network_carbon >= 0.006) { + network_carbon_class = 'D'; + network_carbon_color = 'yellow' + } else if (network_carbon >= 0.004) { + network_carbon_class = 'C'; + network_carbon_color = 'teal' + } else if (network_carbon >= 0.002) { + network_carbon_class = 'B'; + network_carbon_color = 'olive' + } else if (network_carbon > 0 && network_carbon < 0.002) { + network_carbon_class = 'A'; + network_carbon_color = 'green' + } else { + network_carbon_class = 'N/A'; + network_carbon_color = 'purple' + } + + return [energy_class, energy_color, network_carbon_class, network_carbon_color] + +} + +function parsePhaseStats(data) { + const uuid = data['comparison_identifiers'][0] + + const cpu_energy = data['data']['Load and idle']['cpu_energy_rapl_msr_component']['data']['Package_0']['data'][uuid]['mean'] + const cpu_energy_unit = data['data']['Load and idle']['cpu_energy_rapl_msr_component']['unit'] + + const total_duration = data['data']['Load and idle']['phase_time_syscall_system']['data']['[SYSTEM]']['data'][uuid]['mean'] + const total_duration_unit = data['data']['Load and idle']['phase_time_syscall_system']['unit'] + + const network_data = data['data']['Load and idle']['network_total_cgroup_container']['data']['gcb-playwright']['data'][uuid]['mean'] + const network_data_unit = data['data']['Load and idle']['network_total_cgroup_container']['unit'] + + if (network_data_unit != 'Bytes') { + alert(`Unexpected unit returned from API for network_data_unit: ${network_data_unit}`) + throw data + } + + if (cpu_energy_unit != 'uJ') { + alert(`Unexpected unit returned from API for cpu_energy_unit: ${cpu_energy_unit}`) + throw data + } + + if (total_duration_unit != 'us') { + alert(`Unexpected unit returned from API for total_duration_unit: ${total_duration_unit}`) + throw data + } + + + const cpu_energy_mWh = (cpu_energy / 3600000) + const cpu_power_W = (cpu_energy / total_duration) + const network_data_MB = (network_data / 1e6) + const network_carbon_g = (((network_data / 1e9) * 0.06)*300) + const total_duration_s = (total_duration / 1e6) + + return [total_duration_s, cpu_energy_mWh, cpu_power_W, network_data_MB, network_carbon_g] +} \ No newline at end of file diff --git a/svg.js b/svg.js new file mode 100644 index 0000000..d5b850b --- /dev/null +++ b/svg.js @@ -0,0 +1,28 @@ + + + + + + + + ${energy_class} + Energy (Browser): ${cpu_energy} Wh + + + + ${network_carbon_class} + Carbon (Network): ${network_carbon} gCO2 + + + + ${(new Date(runs_data[idx][4])).toLocaleDateString(navigator.language, { year: 'numeric', month: 'short', day: 'numeric' })} + + +--> \ No newline at end of file