diff --git a/404.html b/404.html index 2a74816..8676489 100644 --- a/404.html +++ b/404.html @@ -8,13 +8,13 @@ Page Not Found | Impact Framework - +
Skip to main content
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/FAQ/index.html b/FAQ/index.html index 2680c3b..d639e68 100644 --- a/FAQ/index.html +++ b/FAQ/index.html @@ -8,13 +8,13 @@ FAQs | Impact Framework - +
Skip to main content
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

FAQs

How can I contribute to Impact framework?

We welcome all kinds of contributions to Impact Framework, from bug reports to work on core features or documentation. For detailed instructions on how to start and what to expect when you contribute, please visit our contributions guidelines. There you will find guidance on how to raise bug reports, how to contribute code and what processes we have in place for handling your issues and PRs.

To contribute to these docs, you can raise pull requests against our Github repository.

In general, you can consider any ticket on our issue board open for community contributions if it does not have the core-only tag. We tag suitable introductory issues as good-first-issue - these are great places to get started.

Is there any way to auto-generate manifest files?

Not really. We provide a command line tool called if-env which will generate a template manifest for you if you run it in an empty folder. We also provide a built-in feature called mockObservations that will autogenerate some input data for you (designed to generate dummy data for experimenting or scenario testing). There are also importer plugins that will pull data from files or APIs into IF manifests. However, we do not currently provide any tooling that will generate a full manifest file for you - we think humans in the loop are important for ensuring the nuances of your system are captured in the manifest and in general, we'd prefer not to automate that away. That said, there is potential for tooling that helps abstract out the more repetitive tasks associated with building large manifest files.

Where do I get the data for my manifest file?

This is up to you! One of the strengths of the IF is that you can build manifests your way. We expect input data to come from an external file or an API in most cases, although you can manually add input data into your manifest too. People have built importer plugins for Azure's monitor API, Prometheus databases, CSV files, Datadog, and others. These importer plugins can be found on the Explorer website.

What is your vision for the IF?

The vision is to build a protocol that enables you to calculate the appropriate environmental impact for your application, whatever it is. We want to be an open communication standard - our dream is that an IF manifest is the primary way that people share the environmental impacts of their systems.

Right now we consider our core functionality to be calculating software carbon intensity scores for software applications running in the cloud, but we have people using IF on-premise and even for supply chain modeling and other non-software applications. The idea is that we provide the minimal infrastructure required for you to build up different use cases using plugins.

Is there a way to generate an audit/report along with a csv/yaml output?

No - we see the manifest file and associated manifest as a new type of "executable audit". The IF itself does not generate any other form of report, although there is nothing stopping others from building out this functionality on top of IF.

IF makes things transparent, but how can you fight against users inputting incorrect or misleading data?

We can't really stop people providing fake data into a manifest file. We're very interested in ways we can verify that the computation was done correctly and provide public proofs, but without direct access to a user's systems we can never guarantee they are providing truthful data. This is not unique to IF - fraud is a problem across all industries.

Is it planned to make the IF more user-friendly or also more usable for less technical people?

IF is a low-level infrastructure project. The core team focuses on building out solid foundations for others to build UIs, apps etc on top. There is no official IF UI.

Does it only calculate emissions for CPU usage or does it also work with other meters like storage/bandwidth?

We do have basic models for memory and network usage in addition to CPU. You can create plugins for anything you can observe and model.

Have you compared the results to the carbon reports that cloud providers are producing for their customers?

No we haven't - we've been focussing on capturing impacts of individual applications over relatively short periods so far. It's difficult to find the underlying data to build the comparisons.

Are you planning to support e.g. for PaaS, IaaS, alternative cloud providers...etc?

We are focused on building out the low-level core infrastructure so that users can apply IF to any use case. We do not currently intend to build out support for any specific service. We want users to build plugins and publish them to the Explorer and share manifests to support individual use cases.

What is on your roadmap?

You can check what's coming up for IF core development by checking our Github discussion board. We publish our plans in advance of incorporating them into our development sprints so that the community can comment, ask for more information and report any potential impact for their specific use cases.

- + \ No newline at end of file diff --git a/assets/js/449bc0d9.633bc0c4.js b/assets/js/449bc0d9.a58615c7.js similarity index 69% rename from assets/js/449bc0d9.633bc0c4.js rename to assets/js/449bc0d9.a58615c7.js index 17c6478..0e13592 100644 --- a/assets/js/449bc0d9.633bc0c4.js +++ b/assets/js/449bc0d9.a58615c7.js @@ -1 +1 @@ -"use strict";(self.webpackChunkgreen_software_training=self.webpackChunkgreen_software_training||[]).push([[99],{4137:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>m});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var u=n.createContext({}),l=function(e){var t=n.useContext(u),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},p=function(e){var t=l(e.components);return n.createElement(u.Provider,{value:t},e.children)},c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},f=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,u=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),f=l(r),m=a,d=f["".concat(u,".").concat(m)]||f[m]||c[m]||o;return r?n.createElement(d,i(i({ref:t},p),{},{components:r})):n.createElement(d,i({ref:t},p))}));function m(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,i=new Array(o);i[0]=f;var s={};for(var u in t)hasOwnProperty.call(t,u)&&(s[u]=t[u]);s.originalType=e,s.mdxType="string"==typeof e?e:a,i[1]=s;for(var l=2;l{r.r(t),r.d(t,{assets:()=>u,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>l});var n=r(7462),a=(r(7294),r(4137));const o={sidebar_position:3},i="Users",s={unversionedId:"users/index",id:"users/index",title:"Users",description:"This section contains information for Impact Framework users. You are a user if you want to apply the Impact Framework to your own use-case, such as using it to measure the environmental impact of your own apps running on some cloud platform.",source:"@site/docs/users/index.md",sourceDirName:"users",slug:"/users/",permalink:"/users/",draft:!1,editUrl:"https://github.com/Green-Software-Foundation/if-docs/edit/master/docs/users/index.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Time",permalink:"/major-concepts/time"},next:{title:"Quick start",permalink:"/users/quick-start"}},u={},l=[],p={toc:l};function c(e){let{components:t,...r}=e;return(0,a.kt)("wrapper",(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"users"},"Users"),(0,a.kt)("p",null,"This section contains information for Impact Framework users. You are a user if you want to apply the Impact Framework to your own use-case, such as using it to measure the environmental impact of your own apps running on some cloud platform. "),(0,a.kt)("p",null,"The user documentation includes:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/quick-start"},"Quick start guide")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-install-if"},"How to install and run IF")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-import-plugins"},"How to load plugins")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-write-manifests"},"How to write manifest files")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-export-csv-file-with-if-csv"},"How to export CSV file with ",(0,a.kt)("inlineCode",{parentName:"a"},"if-csv"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-compare-files-with-if-diff"},"How to compare files with ",(0,a.kt)("inlineCode",{parentName:"a"},"if-diff"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"./using-the-explain-feature.md"},"How to check parameters and units using ",(0,a.kt)("inlineCode",{parentName:"a"},"explain"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-verify-files-with-if-check"},"How to verify manifest files with ",(0,a.kt)("inlineCode",{parentName:"a"},"if-check")))),(0,a.kt)("p",null,"If you are looking for guidance for how to ",(0,a.kt)("em",{parentName:"p"},"change or update")," the Impact Framework by adding new features, fixing bugs or building new plugins, you should go to our ",(0,a.kt)("a",{parentName:"p",href:"../developers/"},(0,a.kt)("inlineCode",{parentName:"a"},"developers")," documentation")," instead."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkgreen_software_training=self.webpackChunkgreen_software_training||[]).push([[99],{4137:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>m});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var u=n.createContext({}),l=function(e){var t=n.useContext(u),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},p=function(e){var t=l(e.components);return n.createElement(u.Provider,{value:t},e.children)},c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},f=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,u=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),f=l(r),m=a,d=f["".concat(u,".").concat(m)]||f[m]||c[m]||o;return r?n.createElement(d,i(i({ref:t},p),{},{components:r})):n.createElement(d,i({ref:t},p))}));function m(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,i=new Array(o);i[0]=f;var s={};for(var u in t)hasOwnProperty.call(t,u)&&(s[u]=t[u]);s.originalType=e,s.mdxType="string"==typeof e?e:a,i[1]=s;for(var l=2;l{r.r(t),r.d(t,{assets:()=>u,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>l});var n=r(7462),a=(r(7294),r(4137));const o={sidebar_position:3},i="Users",s={unversionedId:"users/index",id:"users/index",title:"Users",description:"This section contains information for Impact Framework users. You are a user if you want to apply the Impact Framework to your own use-case, such as using it to measure the environmental impact of your own apps running on some cloud platform.",source:"@site/docs/users/index.md",sourceDirName:"users",slug:"/users/",permalink:"/users/",draft:!1,editUrl:"https://github.com/Green-Software-Foundation/if-docs/edit/master/docs/users/index.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Time",permalink:"/major-concepts/time"},next:{title:"Quick start",permalink:"/users/quick-start"}},u={},l=[],p={toc:l};function c(e){let{components:t,...r}=e;return(0,a.kt)("wrapper",(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"users"},"Users"),(0,a.kt)("p",null,"This section contains information for Impact Framework users. You are a user if you want to apply the Impact Framework to your own use-case, such as using it to measure the environmental impact of your own apps running on some cloud platform."),(0,a.kt)("p",null,"The user documentation includes:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/quick-start"},"Quick start guide")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-install-if"},"How to install and run IF")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-import-plugins"},"How to load plugins")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-write-manifests"},"How to write manifest files")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-export-csv-file-with-if-csv"},"How to export CSV file with ",(0,a.kt)("inlineCode",{parentName:"a"},"if-csv"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-compare-files-with-if-diff"},"How to compare files with ",(0,a.kt)("inlineCode",{parentName:"a"},"if-diff"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-use-the-explain-feature"},"How to check parameters and units using ",(0,a.kt)("inlineCode",{parentName:"a"},"explain"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"/users/how-to-verify-files-with-if-check"},"How to verify manifest files with ",(0,a.kt)("inlineCode",{parentName:"a"},"if-check")))),(0,a.kt)("p",null,"If you are looking for guidance for how to ",(0,a.kt)("em",{parentName:"p"},"change or update")," the Impact Framework by adding new features, fixing bugs or building new plugins, you should go to our ",(0,a.kt)("a",{parentName:"p",href:"../developers/"},(0,a.kt)("inlineCode",{parentName:"a"},"developers")," documentation")," instead."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5c5a410f.83bbc838.js b/assets/js/5c5a410f.83bbc838.js new file mode 100644 index 0000000..9b0fd1c --- /dev/null +++ b/assets/js/5c5a410f.83bbc838.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkgreen_software_training=self.webpackChunkgreen_software_training||[]).push([[468],{4137:(e,n,t)=>{t.d(n,{Zo:()=>s,kt:()=>m});var i=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,i)}return t}function r(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var u=i.createContext({}),p=function(e){var n=i.useContext(u),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},s=function(e){var n=p(e.components);return i.createElement(u.Provider,{value:n},e.children)},c={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},d=i.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,u=e.parentName,s=l(e,["components","mdxType","originalType","parentName"]),d=p(t),m=a,h=d["".concat(u,".").concat(m)]||d[m]||c[m]||o;return t?i.createElement(h,r(r({ref:n},s),{},{components:t})):i.createElement(h,r({ref:n},s))}));function m(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,r=new Array(o);r[0]=d;var l={};for(var u in n)hasOwnProperty.call(n,u)&&(l[u]=n[u]);l.originalType=e,l.mdxType="string"==typeof e?e:a,r[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var i=t(7462),a=(t(7294),t(4137));const o={"sidebar-position":2},r="Manifest File",l={unversionedId:"major-concepts/manifest-file",id:"major-concepts/manifest-file",title:"Manifest File",description:"Manifest files are fundamental to Impact Framework and they serve multiple important purposes, including:",source:"@site/docs/major-concepts/manifest-file.md",sourceDirName:"major-concepts",slug:"/major-concepts/manifest-file",permalink:"/major-concepts/manifest-file",draft:!1,editUrl:"https://github.com/Green-Software-Foundation/if-docs/edit/master/docs/major-concepts/manifest-file.md",tags:[],version:"current",frontMatter:{"sidebar-position":2},sidebar:"tutorialSidebar",previous:{title:"Impact Engine (CLI)",permalink:"/major-concepts/if"},next:{title:"Plugins",permalink:"/major-concepts/plugins"}},u={},p=[{value:"Structure of a manifest file",id:"structure-of-a-manifest-file",level:2},{value:"Overview",id:"overview",level:3},{value:"Context",id:"context",level:3},{value:"Metadata",id:"metadata",level:4},{value:"Initialize",id:"initialize",level:4},{value:"Execution (auto-generated)",id:"execution-auto-generated",level:4},{value:"Explain",id:"explain",level:3},{value:"Tree",id:"tree",level:3},{value:"Defaults",id:"defaults",level:4},{value:"Inputs",id:"inputs",level:4},{value:"Creating input data",id:"creating-input-data",level:2},{value:"Regrouping a manifest file",id:"regrouping-a-manifest-file",level:2},{value:"Computing a manifest file",id:"computing-a-manifest-file",level:2},{value:"Running combinations of phases",id:"running-combinations-of-phases",level:2},{value:"Outputs",id:"outputs",level:2}],s={toc:p};function c(e){let{components:n,...o}=e;return(0,a.kt)("wrapper",(0,i.Z)({},s,o,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"manifest-file"},"Manifest File"),(0,a.kt)("p",null,"Manifest files are fundamental to Impact Framework and they serve multiple important purposes, including:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"They contain all the necessary configurations for Impact Framework"),(0,a.kt)("li",{parentName:"ul"},"They define your application architecture"),(0,a.kt)("li",{parentName:"ul"},"They hold your input data"),(0,a.kt)("li",{parentName:"ul"},"They are shareable, portable and human-readable"),(0,a.kt)("li",{parentName:"ul"},"They can be used as verifiable audits form your application")),(0,a.kt)("p",null,"The manifest is a ",(0,a.kt)("a",{parentName:"p",href:"https://circleci.com/blog/what-is-yaml-a-beginner-s-guide/"},"yaml")," file with a particular structure.\nIt can be thought of as an ",(0,a.kt)("strong",{parentName:"p"},(0,a.kt)("em",{parentName:"strong"},"executable audit"))," because the file itself can be shared with others and re-executed to verify your environmental impact calculations."),(0,a.kt)("p",null,"It is a formal report detailing not just the end impact but all the assumptions, inputs, and plugins used in calculating the impact."),(0,a.kt)("p",null,"This is possible because ",(0,a.kt)("em",{parentName:"p"},"all the configuration and data required to run Impact Framework is contained in the manifest file"),"."),(0,a.kt)("p",null,"Anyone can download Impact Framework and execute a manifest file to verify the results."),(0,a.kt)("h2",{id:"structure-of-a-manifest-file"},"Structure of a manifest file"),(0,a.kt)("h3",{id:"overview"},"Overview"),(0,a.kt)("p",null,"Manifest files can be simple or very intricate, depending on the plugin pipeline you want to use and the complexity of your application. However, all manifest files conform to a basic structure that looks as follows:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"name:\ndescription:\ntags:\ninitialize:\n plugins:\n :\n method:\n path:\ntree:\n children:\n child:\n pipeline:\n observe:\n regroup:\n compute:\n defaults:\n inputs:\n - timestamp: 2023-08-06T00:00\n duration: 3600\n")),(0,a.kt)("p",null,"Everything above the ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," is collectively referred to as the ",(0,a.kt)("inlineCode",{parentName:"p"},"context"),". The ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," contains the input data and is structured according to the architecture of the application being examined, with individual components being nodes in the tree. Individual components can be grouped under parent nodes."),(0,a.kt)("h3",{id:"context"},"Context"),(0,a.kt)("h4",{id:"metadata"},"Metadata"),(0,a.kt)("p",null,"The global metadata includes the ",(0,a.kt)("inlineCode",{parentName:"p"},"name"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"description"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"tags")," that can be used to describe the nature of the manifest file. For example, you might name the file ",(0,a.kt)("inlineCode",{parentName:"p"},"Carbon Jan 2024")," or similar. A short description might briefly outline the scope of the manifest file, e.g. ",(0,a.kt)("inlineCode",{parentName:"p"},"company x's carbon emissions due to web serves from Jab 24 - July 24"),". Tags can be used to group manifest files (we do not explicitly use this field for anything currently)."),(0,a.kt)("h4",{id:"initialize"},"Initialize"),(0,a.kt)("p",null,"The initialize section is where you define which plugins will be used in your manifest file and provide the configuration for them. Below is sample for initialization:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"initialize:\n plugins:\n :\n method: \n")),(0,a.kt)("p",null,"Where required values are:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"method"),": the name of the function exported by the plugin."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"path"),": the path to the plugin code. For example, for a plugin from our standard library, this value would be ",(0,a.kt)("inlineCode",{parentName:"li"},"builtin"))),(0,a.kt)("p",null,"There is also an optional ",(0,a.kt)("inlineCode",{parentName:"p"},"config")," field that can be used to set ",(0,a.kt)("em",{parentName:"p"},"config")," that is common to a plugin wherever it is invoked across the entire manifest file."),(0,a.kt)("p",null,"Impact Framework uses the ",(0,a.kt)("inlineCode",{parentName:"p"},"initialize")," section to instantiate each plugin. A plugin cannot be invoked elsewhere in the manifest file unless it is included in this section."),(0,a.kt)("p",null,"There is also the option to provide a mapping to the plugin in the initialize block. Its purpose is to rename the arguments expected or returned from the plugin as part of the plugin's execution, avoiding the need to use additional plugins to rename parameters."),(0,a.kt)("p",null,"For example, your plugin might expect cpu/energy and your input data has the parameter cpu-energy returned from another plugin. Instead of using an additional plugin to rename the parameter and add a new one, you can use mapping to:"),(0,a.kt)("p",null,"a) rename the output from the first plugin so that cpu/energy is returned instead of the default cpu-energy"),(0,a.kt)("p",null,"b) instruct the second plugin to accept cpu-energy instead of the default cpu/energy"),(0,a.kt)("p",null,"e.g."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"initialize:\n plugins:\n sci:\n kind: plugin\n method: Sci\n path: 'builtin'\n config:\n functional-unit: requests\n mapping:\n sci: if-sci\n")),(0,a.kt)("p",null,"In the outputs, the ",(0,a.kt)("inlineCode",{parentName:"p"},"sci")," value returned by the Sci plugin will be named if-sci."),(0,a.kt)("p",null,"You can also add information to the plugin's initialize section about parameter metadata if you wish to add or override the metadata hardcoded into the plugin. This is what will be reported by the ",(0,a.kt)("inlineCode",{parentName:"p"},"explainer")," feature if you enable it. E.g."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"plugins:\n sum-carbon:\n path: 'builtin'\n method: Sum\n config:\n input-parameters:\n - carbon-operational\n - embodied-carbon\n output-parameter: carbon\n parameter-metadata:\n inputs:\n carbon-operational:\n description: \"carbon emitted due to an application's execution\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n embodied-carbon:\n description: \"carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n")),(0,a.kt)("h4",{id:"execution-auto-generated"},"Execution (auto-generated)"),(0,a.kt)("p",null,"This section is auto generated by IF at runtime. You don't have to include this section in your manifest. The ",(0,a.kt)("inlineCode",{parentName:"p"},"execution")," node contains all the necessary information to rebuild the environment, which can support debugging or verifying output files."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"execution:\n status: success\n command: if-run --manifest examples/basic.yml\n environment:\n if-version: v0.3.2\n os: ubuntu\n os-version: 22.04.6\n node-version: v21.4.0\n date-time: 2023-12-12T00:00:00.000Z (UTC)\n dependencies:\n - '@babel/core@7.22.10'\n - ...\n error: 'InputValidationError: \"duration\" parameter is required. Error code: invalid_type'. ## appears when execution failed\n")),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"status"),": execution state: ",(0,a.kt)("inlineCode",{parentName:"li"},"success")," (indicating that IF successfully executed this manifest) or ",(0,a.kt)("inlineCode",{parentName:"li"},"fail")," (indicating that IF encountered a problem that halted execution)."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"command"),": exact command which was used to run the framework to execute this manifest (it may include full path to tools in place of aliases such as ",(0,a.kt)("inlineCode",{parentName:"li"},"run"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"environment"),": information about the environment the manifest was executed in, including the local operating system, Node.js version, time, and dependencies."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"error"),": this field only appears if execution failed. The error message returned by IF is captured here.")),(0,a.kt)("h3",{id:"explain"},"Explain"),(0,a.kt)("p",null,"This section is autogenerated at runtime. It is a list of all the parameter metadata that IF can scrape from your plugin instances. It looks as follows:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"explain:\n sci:\n inputs:\n carbon:\n unit: gCO2eq\n description: >-\n total carbon emissions attributed to an application's usage as the sum\n of embodied and operational carbon\n aggregation-method:\n time: sum\n component: sum\n requests:\n unit: requests\n description: number of requests made to application in the given timestep\n aggregation-method:\n time: sum\n component: sum\n outputs:\n sci:\n unit: gCO2eq/request\n description: >-\n software carbon intensity expressed as a rate of carbon emission per\n request\n aggregation-method:\n time: sum\n component: sum\n")),(0,a.kt)("h3",{id:"tree"},"Tree"),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," section of a manifest file defines the topology of all the components being measured. The shape of the ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," defines the grouping of components. It describes the architecture of the application being studied and contains all the usage observations for each component. The tree has individual components such as leaves, intermediate nodes representing groupings, and the top level is the root."),(0,a.kt)("p",null,(0,a.kt)("img",{src:t(403).Z,width:"614",height:"569"})),(0,a.kt)("p",null,"For example, a web application could be organized as follows:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"tree:\n children:\n front-end:\n children:\n build-pipeline:\n children:\n vercel:\n github-pages:\n backend-database:\n children:\n server1:\n server2:\n server3:\n front-end:\n networking:\n")),(0,a.kt)("p",null,"This example has a relatively straightforward structure with a maximum of 3 levels of nesting. You can continue to nest components to any depth."),(0,a.kt)("p",null,"Each component has some configuration, some input data, and a plugin pipeline."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"pipeline"),": a list of plugins that should be executed for a specific component. This is broken down into three subsections representing distinct phases of execution that can be triggered independently using command line flags. These subsections are:",(0,a.kt)("ul",{parentName:"li"},(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"observe"),": the plugins that generate input data"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"regroup"),": configuration for regrouping input data by given keys"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"compute"),": the plugins that operate over input data and generate output data"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"defaults"),": fallback values that IF defaults to if they are not present in an input observation."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"inputs"),": an array of ",(0,a.kt)("inlineCode",{parentName:"li"},"observation")," data, with each ",(0,a.kt)("inlineCode",{parentName:"li"},"observation")," containing usage data for a given timestep.")),(0,a.kt)("p",null,"If a component ",(0,a.kt)("em",{parentName:"p"},"does not")," include its own ",(0,a.kt)("inlineCode",{parentName:"p"},"pipeline")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"default")," values, they are inherited from the closest parent."),(0,a.kt)("p",null,"Here's an example of a moderately complex tree:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"tree:\n children:\n child-0:\n children:\n child-0-1:\n pipeline:\n observe:\n regroup:\n compute:\n - sum\n defaults: null\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n outputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n energy: 0.000811\n child-0-2:\n children:\n child-0-2-1:\n pipeline:\n observe:\n regroup:\n compute:\n - sum\n defaults: null\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n outputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n energy: 0.000811\n")),(0,a.kt)("h4",{id:"defaults"},"Defaults"),(0,a.kt)("p",null,"Defaults are fallback values that are only used if a given value is missing in the inputs array. For example, if you have a value that could feasibly be missing in a given timestep, perhaps because your plugin relies on a third party API that can fail, you can provide a value in ",(0,a.kt)("inlineCode",{parentName:"p"},"defaults")," that can be used as a fallback value."),(0,a.kt)("p",null,"The values in defaults are applied to every timestep where the given value is missing. This means that as well as acting as a fallback ",(0,a.kt)("inlineCode",{parentName:"p"},"defaults")," can be used as a convenience tool for efficiently adding a constant value to every timestep in your inputs array."),(0,a.kt)("h4",{id:"inputs"},"Inputs"),(0,a.kt)("p",null,"Every component includes an ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," field that gets read into plugins as an array. ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," are divided into ",(0,a.kt)("inlineCode",{parentName:"p"},"observations"),", each having a ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," and a ",(0,a.kt)("inlineCode",{parentName:"p"},"duration"),". Every ",(0,a.kt)("inlineCode",{parentName:"p"},"observation")," refers to an element in ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," representing some snapshot in time."),(0,a.kt)("p",null,"Each plugin takes the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array and applies some calculation or transformation to each ",(0,a.kt)("inlineCode",{parentName:"p"},"observation")," in the array."),(0,a.kt)("p",null,"Observations can include any type of data, including human judgment, assumptions, other plugins, APIs, survey data or telemetry."),(0,a.kt)("p",null,"The separation of timestamps in the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array determines the temporal granularity of your impact calculations. The more frequent your observations, the more accurate your impact assessment."),(0,a.kt)("h2",{id:"creating-input-data"},"Creating input data"),(0,a.kt)("p",null,"The plugins in the ",(0,a.kt)("inlineCode",{parentName:"p"},"observe")," part of the pipeline generate ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data. The manifest file should not have ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data when the ",(0,a.kt)("inlineCode",{parentName:"p"},"observe")," phase is executed. Plugins in this phase ",(0,a.kt)("em",{parentName:"p"},"only")," generate input data, they can never generate output data. If you run the observe phase on its own (by running ",(0,a.kt)("inlineCode",{parentName:"p"},"if-run --observe"),") then your manifest will be returned populated with input data according to the plugins you included in your ",(0,a.kt)("inlineCode",{parentName:"p"},"observe")," pipeline."),(0,a.kt)("h2",{id:"regrouping-a-manifest-file"},"Regrouping a manifest file"),(0,a.kt)("p",null,"The second phase of manifest execution is ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup"),". This reorganizes existing ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data into a new structure using keys provided in the ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," config in the manifest. For example, a manifest with the following ",(0,a.kt)("inlineCode",{parentName:"p"},"tree"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"tree:\n children:\n my-app:\n pipeline:\n observe:\n regroup:\n - cloud/instance-type\n - cloud/region\n compute:\n children:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 99\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 12\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 1\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 9\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 12\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 1\n")),(0,a.kt)("p",null,"generates the following output when ",(0,a.kt)("inlineCode",{parentName:"p"},"if-run --regroup")," is executed:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"tree:\n children:\n my-app:\n pipeline:\n observe: null\n regroup:\n - cloud/instance-type\n - cloud/region\n compute: null\n children:\n A1:\n children:\n uk-west:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 99\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 12\n uk-east:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 9\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 12\n B1:\n children:\n uk-west:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 1\n uk-east:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 1\n")),(0,a.kt)("h2",{id:"computing-a-manifest-file"},"Computing a manifest file"),(0,a.kt)("p",null,"Impact Framework computes manifest files. For each component in the tree, the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array is passed to each plugin in the ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," pipeline in sequence."),(0,a.kt)("p",null,"In order for the ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," phase to execute correctly, the manifest needs to have ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data available."),(0,a.kt)("p",null,"Each plugin ",(0,a.kt)("em",{parentName:"p"},"enriches")," the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array in some specific way, typically by adding a new ",(0,a.kt)("inlineCode",{parentName:"p"},"key-value")," pair to each observation in the array. For example, the ",(0,a.kt)("inlineCode",{parentName:"p"},"teads-curve")," plugin takes in CPU utilization expressed as a percentage as an input and appends ",(0,a.kt)("inlineCode",{parentName:"p"},"cpu/energy")," expressed in kWh. ",(0,a.kt)("inlineCode",{parentName:"p"},"cpu/energy")," is then available to be passed as an input to, for example, the ",(0,a.kt)("inlineCode",{parentName:"p"},"sci-e")," plugin."),(0,a.kt)("p",null,"This implies a sequence of plugins where the inputs for some plugins must either be present in the original manifest file or be outputs of the preceding plugins in the pipeline."),(0,a.kt)("p",null,"There are also plugins and built-in features that can synchronize time series of ",(0,a.kt)("inlineCode",{parentName:"p"},"observations")," across an entire tree and aggregate data across time or across components."),(0,a.kt)("h2",{id:"running-combinations-of-phases"},"Running combinations of phases"),(0,a.kt)("p",null,"It is possible to run each phase of the execution individually, or together. You can choose to ",(0,a.kt)("em",{parentName:"p"},"only")," run the ",(0,a.kt)("inlineCode",{parentName:"p"},"observe"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," phases of the manifest execution. This saves you from having to re-execute entire manifests every time you want to tweak something, making it a greener way to use IF."),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"if-run")," executes all the phases together, including ",(0,a.kt)("inlineCode",{parentName:"p"},"observe"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"compute"),". It generates yaml output data. However, you can run individual phases by passing ",(0,a.kt)("inlineCode",{parentName:"p"},"--observe"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"--regroup")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"--compute")," flags on the command line. For example, to run ",(0,a.kt)("em",{parentName:"p"},"only")," the compute phase:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"if-run -m --compute\n")),(0,a.kt)("p",null,"Maybe you only want to generate a static file that contains input data but don't want to run the full compute pipeline right now. You can run with ",(0,a.kt)("inlineCode",{parentName:"p"},"--observe"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"if-run --observe\n")),(0,a.kt)("p",null,"You can also combine the command, e.g. if you have a file with inputs and you want to run ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," but not ",(0,a.kt)("inlineCode",{parentName:"p"},"observe"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"if-run -m --regroup --compute\n")),(0,a.kt)("h2",{id:"outputs"},"Outputs"),(0,a.kt)("p",null,"When Impact Framework computes a manifest file, it appends new data to the manifest file and the final result is an enriched manifest that includes all the configuration and contextual data, the input data, and the results of executing each plugin. This means the output file is completely auditable - the original manifest file can be recovered simply by deleting the ",(0,a.kt)("inlineCode",{parentName:"p"},"outputs")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"execution")," sections of the output file."),(0,a.kt)("p",null,"IF generates yaml output data. Any other output formats have to be generated by separate exhaust scripts that take IF's yaml output as their input."),(0,a.kt)("p",null,"Here's an example output file:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"name: sum\ndescription: successful path\ntags: null\ninitialize:\n plugins:\n sum:\n path: builtin\n method: Sum\n config:\n input-parameters:\n - cpu/energy\n - network/energy\n output-parameter: energy\nexecution:\n command: >-\n /home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node\n /home/user/Code/if/src/index.ts -m\n /home/user/Code/if/manifests/plugins/sum/success.yml\n environment:\n if-version: 0.3.3-beta.0\n os: linux\n os-version: 5.15.0-105-generic\n node-version: 21.4.0\n date-time: 2024-05-31T09:18:48.895Z (UTC)\n dependencies:\n - '@babel/core@7.22.10'\n - '@babel/preset-typescript@7.23.3'\n - '@commitlint/cli@18.6.0'\n - '@commitlint/config-conventional@18.6.0'\n - '@jest/globals@29.7.0'\n - '@types/jest@29.5.8'\n - '@types/js-yaml@4.0.9'\n - '@types/luxon@3.4.2'\n - '@types/node@20.9.0'\n - csv-stringify@6.4.6\n - fixpack@4.0.0\n - gts@5.2.0\n - husky@8.0.3\n - jest@29.7.0\n - js-yaml@4.1.0\n - lint-staged@15.2.2\n - luxon@3.4.4\n - release-it@16.3.0\n - rimraf@5.0.5\n - ts-command-line-args@2.5.1\n - ts-jest@29.1.1\n - typescript-cubic-spline@1.0.1\n - typescript@5.2.2\n - winston@3.11.0\n - zod@3.22.4\n status: success\ntree:\n children:\n child:\n pipeline:\n observe:\n regroup:\n compute:\n - sum\n inputs:\n - timestamp: 2023-08-06T00:00\n duration: 3600\n cpu/energy: 0.001\n network/energy: 0.001\n outputs:\n - timestamp: 2023-08-06T00:00\n duration: 3600\n cpu/energy: 0.001\n network/energy: 0.001\n energy: 0.002\n")))}c.isMDXComponent=!0},403:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/3f18767c1a55cee416e3de70314609e3-c7fa9feaf0993c3ed2b5a34b8b82432c.png"}}]); \ No newline at end of file diff --git a/assets/js/5c5a410f.e81f04e7.js b/assets/js/5c5a410f.e81f04e7.js deleted file mode 100644 index 8dc25ab..0000000 --- a/assets/js/5c5a410f.e81f04e7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkgreen_software_training=self.webpackChunkgreen_software_training||[]).push([[468],{4137:(e,n,t)=>{t.d(n,{Zo:()=>s,kt:()=>m});var i=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,i)}return t}function r(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var u=i.createContext({}),p=function(e){var n=i.useContext(u),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},s=function(e){var n=p(e.components);return i.createElement(u.Provider,{value:n},e.children)},c={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},d=i.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,u=e.parentName,s=l(e,["components","mdxType","originalType","parentName"]),d=p(t),m=a,h=d["".concat(u,".").concat(m)]||d[m]||c[m]||o;return t?i.createElement(h,r(r({ref:n},s),{},{components:t})):i.createElement(h,r({ref:n},s))}));function m(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,r=new Array(o);r[0]=d;var l={};for(var u in n)hasOwnProperty.call(n,u)&&(l[u]=n[u]);l.originalType=e,l.mdxType="string"==typeof e?e:a,r[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var i=t(7462),a=(t(7294),t(4137));const o={"sidebar-position":2},r="Manifest File",l={unversionedId:"major-concepts/manifest-file",id:"major-concepts/manifest-file",title:"Manifest File",description:"Manifest files are fundamental to Impact Framework and they serve multiple important purposes, including:",source:"@site/docs/major-concepts/manifest-file.md",sourceDirName:"major-concepts",slug:"/major-concepts/manifest-file",permalink:"/major-concepts/manifest-file",draft:!1,editUrl:"https://github.com/Green-Software-Foundation/if-docs/edit/master/docs/major-concepts/manifest-file.md",tags:[],version:"current",frontMatter:{"sidebar-position":2},sidebar:"tutorialSidebar",previous:{title:"Impact Engine (CLI)",permalink:"/major-concepts/if"},next:{title:"Plugins",permalink:"/major-concepts/plugins"}},u={},p=[{value:"Structure of a manifest file",id:"structure-of-a-manifest-file",level:2},{value:"Overview",id:"overview",level:3},{value:"Context",id:"context",level:3},{value:"Metadata",id:"metadata",level:4},{value:"Initialize",id:"initialize",level:4},{value:"Execution (auto-generated)",id:"execution-auto-generated",level:4},{value:"Explain",id:"explain",level:3},{value:"Tree",id:"tree",level:3},{value:"Defaults",id:"defaults",level:4},{value:"Inputs",id:"inputs",level:4},{value:"Creating input data",id:"creating-input-data",level:2},{value:"Regrouping a manifest file",id:"regrouping-a-manifest-file",level:2},{value:"Computing a manifest file",id:"computing-a-manifest-file",level:2},{value:"Running combinations of phases",id:"running-combinations-of-phases",level:2},{value:"Outputs",id:"outputs",level:2}],s={toc:p};function c(e){let{components:n,...o}=e;return(0,a.kt)("wrapper",(0,i.Z)({},s,o,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"manifest-file"},"Manifest File"),(0,a.kt)("p",null,"Manifest files are fundamental to Impact Framework and they serve multiple important purposes, including:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"They contain all the necessary configurations for Impact Framework"),(0,a.kt)("li",{parentName:"ul"},"They define your application architecture"),(0,a.kt)("li",{parentName:"ul"},"They hold your input data"),(0,a.kt)("li",{parentName:"ul"},"They are shareable, portable and human-readable"),(0,a.kt)("li",{parentName:"ul"},"They can be used as verifiable audits form your application")),(0,a.kt)("p",null,"The manifest is a ",(0,a.kt)("a",{parentName:"p",href:"https://circleci.com/blog/what-is-yaml-a-beginner-s-guide/"},"yaml")," file with a particular structure.\nIt can be thought of as an ",(0,a.kt)("strong",{parentName:"p"},(0,a.kt)("em",{parentName:"strong"},"executable audit"))," because the file itself can be shared with others and re-executed to verify your environmental impact calculations."),(0,a.kt)("p",null,"It is a formal report detailing not just the end impact but all the assumptions, inputs, and plugins used in calculating the impact."),(0,a.kt)("p",null,"This is possible because ",(0,a.kt)("em",{parentName:"p"},"all the configuration and data required to run Impact Framework is contained in the manifest file"),"."),(0,a.kt)("p",null,"Anyone can download Impact Framework and execute a manifest file to verify the results."),(0,a.kt)("h2",{id:"structure-of-a-manifest-file"},"Structure of a manifest file"),(0,a.kt)("h3",{id:"overview"},"Overview"),(0,a.kt)("p",null,"Manifest files can be simple or very intricate, depending on the plugin pipeline you want to use and the complexity of your application. However, all manifest files conform to a basic structure that looks as follows:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"name:\ndescription:\ntags:\ninitialize:\n plugins:\n :\n method:\n path:\ntree:\n children:\n child:\n pipeline:\n observe:\n regroup:\n compute:\n defaults:\n inputs:\n - timestamp: 2023-08-06T00:00\n duration: 3600\n")),(0,a.kt)("p",null,"Everything above the ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," is collectively referred to as the ",(0,a.kt)("inlineCode",{parentName:"p"},"context"),". The ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," contains the input data and is structured according to the architecture of the application being examined, with individual components being nodes in the tree. Individual components can be grouped under parent nodes."),(0,a.kt)("h3",{id:"context"},"Context"),(0,a.kt)("h4",{id:"metadata"},"Metadata"),(0,a.kt)("p",null,"The global metadata includes the ",(0,a.kt)("inlineCode",{parentName:"p"},"name"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"description"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"tags")," that can be used to describe the nature of the manifest file. For example, you might name the file ",(0,a.kt)("inlineCode",{parentName:"p"},"Carbon Jan 2024")," or similar. A short description might briefly outline the scope of the manifest file, e.g. ",(0,a.kt)("inlineCode",{parentName:"p"},"company x's carbon emissions due to web serves from Jab 24 - July 24"),". Tags can be used to group manifest files (we do not explicitly use this field for anything currently)."),(0,a.kt)("h4",{id:"initialize"},"Initialize"),(0,a.kt)("p",null,"The initialize section is where you define which plugins will be used in your manifest file and provide the configuration for them. Below is sample for initialization:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"initialize:\n plugins:\n :\n method: \n")),(0,a.kt)("p",null,"Where required values are:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"method"),": the name of the function exported by the plugin."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"path"),": the path to the plugin code. For example, for a plugin from our standard library, this value would be ",(0,a.kt)("inlineCode",{parentName:"li"},"builtin"))),(0,a.kt)("p",null,"There is also an optional ",(0,a.kt)("inlineCode",{parentName:"p"},"config")," field that can be used to set ",(0,a.kt)("em",{parentName:"p"},"config")," that is common to a plugin wherever it is invoked across the entire manifest file."),(0,a.kt)("p",null,"Impact Framework uses the ",(0,a.kt)("inlineCode",{parentName:"p"},"initialize")," section to instantiate each plugin. A plugin cannot be invoked elsewhere in the manifest file unless it is included in this section."),(0,a.kt)("p",null,"There is also the option to provide a mapping to the plugin in the initialize block. Its purpose is to rename the arguments expected or returned from the plugin as part of the plugin's execution, avoiding the need to use additional plugins to rename parameters."),(0,a.kt)("p",null,"For example, your plugin might expect cpu/energy and your input data has the parameter cpu-energy returned from another plugin. Instead of using an additional plugin to rename the parameter and add a new one, you can use mapping to:"),(0,a.kt)("p",null,"a) rename the output from the first plugin so that cpu/energy is returned instead of the default cpu-energy"),(0,a.kt)("p",null,"b) instruct the second plugin to accept cpu-energy instead of the default cpu/energy"),(0,a.kt)("p",null,"e.g."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"initialize:\n plugins:\n sci:\n kind: plugin\n method: Sci\n path: 'builtin'\n config:\n functional-unit: requests\n mapping:\n sci: if-sci\n")),(0,a.kt)("p",null,"In the outputs, the ",(0,a.kt)("inlineCode",{parentName:"p"},"sci")," value returned by the Sci plugin will be named if-sci."),(0,a.kt)("p",null,"You can also add information to the plugin's initialize section about parameter metadata if you wish to add or override the metadata hardcoded into the plugin. This is what will be reported by the ",(0,a.kt)("inlineCode",{parentName:"p"},"explainer")," feature if you enable it. E.g."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"plugins:\n sum-carbon:\n path: 'builtin'\n method: Sum\n config:\n input-parameters:\n - carbon-operational\n - embodied-carbon\n output-parameter: carbon\n parameter-metadata:\n inputs:\n carbon-operational:\n description: \"carbon emitted due to an application's execution\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n embodied-carbon:\n description: \"carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n")),(0,a.kt)("h4",{id:"execution-auto-generated"},"Execution (auto-generated)"),(0,a.kt)("p",null,"This section is auto generated by IF at runtime. You don't have to include this section in your manifest. The ",(0,a.kt)("inlineCode",{parentName:"p"},"execution")," node contains all the necessary information to rebuild the environment, which can support debugging or verifying output files."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"execution:\n status: success\n command: if-run --manifest examples/basic.yml\n environment:\n if-version: v0.3.2\n os: ubuntu\n os-version: 22.04.6\n node-version: v21.4.0\n date-time: 2023-12-12T00:00:00.000Z (UTC)\n dependencies:\n - '@babel/core@7.22.10'\n - ...\n error: 'InputValidationError: \"duration\" parameter is required. Error code: invalid_type'. ## appears when execution failed\n")),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"status"),": execution state: ",(0,a.kt)("inlineCode",{parentName:"li"},"success")," (indicating that IF successfully executed this manifest) or ",(0,a.kt)("inlineCode",{parentName:"li"},"fail")," (indicating that IF encountered a problem that halted execution)."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"command"),": exact command which was used to run the framework to execute this manifest (it may include full path to tools in place of aliases such as ",(0,a.kt)("inlineCode",{parentName:"li"},"run"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"environment"),": information about the environment the manifest was executed in, including the local operating system, Node.js version, time, and dependencies."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"error"),": this field only appears if execution failed. The error message returned by IF is captured here.")),(0,a.kt)("h3",{id:"explain"},"Explain"),(0,a.kt)("p",null,"This section is autogenerated at runtime. It is a list of all the parameter metadata that IF can scrape from your plugin instances. It looks as follows:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"explain:\n carbon:\n plugins:\n - sci\n unit: gCO2eq\n description: >-\n total carbon emissions attributed to an application's usage as the sum\n of embodied and operational carbon\n aggregation-method:\n time: sum\n component: sum\n requests:\n plugins:\n - sci\n unit: requests\n description: number of requests made to application in the given timestep\n aggregation-method:\n time: sum\n component: sum\n sci:\n plugins:\n - sci\n unit: gCO2eq/request\n description: >-\n software carbon intensity expressed as a rate of carbon emission per\n request\n aggregation-method:\n time: sum\n component: sum\n")),(0,a.kt)("h3",{id:"tree"},"Tree"),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," section of a manifest file defines the topology of all the components being measured. The shape of the ",(0,a.kt)("inlineCode",{parentName:"p"},"tree")," defines the grouping of components. It describes the architecture of the application being studied and contains all the usage observations for each component. The tree has individual components such as leaves, intermediate nodes representing groupings, and the top level is the root."),(0,a.kt)("p",null,(0,a.kt)("img",{src:t(403).Z,width:"614",height:"569"})),(0,a.kt)("p",null,"For example, a web application could be organized as follows:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"tree:\n children:\n front-end:\n children:\n build-pipeline:\n children:\n vercel:\n github-pages:\n backend-database:\n children:\n server1:\n server2:\n server3:\n front-end:\n networking:\n")),(0,a.kt)("p",null,"This example has a relatively straightforward structure with a maximum of 3 levels of nesting. You can continue to nest components to any depth."),(0,a.kt)("p",null,"Each component has some configuration, some input data, and a plugin pipeline."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"pipeline"),": a list of plugins that should be executed for a specific component. This is broken down into three subsections representing distinct phases of execution that can be triggered independently using command line flags. These subsections are:",(0,a.kt)("ul",{parentName:"li"},(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"observe"),": the plugins that generate input data"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"regroup"),": configuration for regrouping input data by given keys"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"compute"),": the plugins that operate over input data and generate output data"))),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"defaults"),": fallback values that IF defaults to if they are not present in an input observation."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"inputs"),": an array of ",(0,a.kt)("inlineCode",{parentName:"li"},"observation")," data, with each ",(0,a.kt)("inlineCode",{parentName:"li"},"observation")," containing usage data for a given timestep.")),(0,a.kt)("p",null,"If a component ",(0,a.kt)("em",{parentName:"p"},"does not")," include its own ",(0,a.kt)("inlineCode",{parentName:"p"},"pipeline")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"default")," values, they are inherited from the closest parent."),(0,a.kt)("p",null,"Here's an example of a moderately complex tree:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"tree:\n children:\n child-0:\n children:\n child-0-1:\n pipeline:\n observe:\n regroup:\n compute:\n - sum\n defaults: null\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n outputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n energy: 0.000811\n child-0-2:\n children:\n child-0-2-1:\n pipeline:\n observe:\n regroup:\n compute:\n - sum\n defaults: null\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n outputs:\n - timestamp: 2023-07-06T00:00\n duration: 10\n cpu-util: 50\n energy-network: 0.000811\n energy: 0.000811\n")),(0,a.kt)("h4",{id:"defaults"},"Defaults"),(0,a.kt)("p",null,"Defaults are fallback values that are only used if a given value is missing in the inputs array. For example, if you have a value that could feasibly be missing in a given timestep, perhaps because your plugin relies on a third party API that can fail, you can provide a value in ",(0,a.kt)("inlineCode",{parentName:"p"},"defaults")," that can be used as a fallback value."),(0,a.kt)("p",null,"The values in defaults are applied to every timestep where the given value is missing. This means that as well as acting as a fallback ",(0,a.kt)("inlineCode",{parentName:"p"},"defaults")," can be used as a convenience tool for efficiently adding a constant value to every timestep in your inputs array."),(0,a.kt)("h4",{id:"inputs"},"Inputs"),(0,a.kt)("p",null,"Every component includes an ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," field that gets read into plugins as an array. ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," are divided into ",(0,a.kt)("inlineCode",{parentName:"p"},"observations"),", each having a ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," and a ",(0,a.kt)("inlineCode",{parentName:"p"},"duration"),". Every ",(0,a.kt)("inlineCode",{parentName:"p"},"observation")," refers to an element in ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," representing some snapshot in time."),(0,a.kt)("p",null,"Each plugin takes the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array and applies some calculation or transformation to each ",(0,a.kt)("inlineCode",{parentName:"p"},"observation")," in the array."),(0,a.kt)("p",null,"Observations can include any type of data, including human judgment, assumptions, other plugins, APIs, survey data or telemetry."),(0,a.kt)("p",null,"The separation of timestamps in the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array determines the temporal granularity of your impact calculations. The more frequent your observations, the more accurate your impact assessment."),(0,a.kt)("h2",{id:"creating-input-data"},"Creating input data"),(0,a.kt)("p",null,"The plugins in the ",(0,a.kt)("inlineCode",{parentName:"p"},"observe")," part of the pipeline generate ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data. The manifest file should not have ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data when the ",(0,a.kt)("inlineCode",{parentName:"p"},"observe")," phase is executed. Plugins in this phase ",(0,a.kt)("em",{parentName:"p"},"only")," generate input data, they can never generate output data. If you run the observe phase on its own (by running ",(0,a.kt)("inlineCode",{parentName:"p"},"if-run --observe"),") then your manifest will be returned populated with input data according to the plugins you included in your ",(0,a.kt)("inlineCode",{parentName:"p"},"observe")," pipeline."),(0,a.kt)("h2",{id:"regrouping-a-manifest-file"},"Regrouping a manifest file"),(0,a.kt)("p",null,"The second phase of manifest execution is ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup"),". This reorganizes existing ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data into a new structure using keys provided in the ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," config in the manifest. For example, a manifest with the following ",(0,a.kt)("inlineCode",{parentName:"p"},"tree"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"tree:\n children:\n my-app:\n pipeline:\n observe:\n regroup:\n - cloud/instance-type\n - cloud/region\n compute:\n children:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 99\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 12\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 1\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 9\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 12\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 1\n")),(0,a.kt)("p",null,"generates the following output when ",(0,a.kt)("inlineCode",{parentName:"p"},"if-run --regroup")," is executed:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"tree:\n children:\n my-app:\n pipeline:\n observe: null\n regroup:\n - cloud/instance-type\n - cloud/region\n compute: null\n children:\n A1:\n children:\n uk-west:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 99\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-west\n cpu/utilization: 12\n uk-east:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 9\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 23\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: A1\n cloud/region: uk-east\n cpu/utilization: 12\n B1:\n children:\n uk-west:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-west\n cpu/utilization: 1\n uk-east:\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 11\n - timestamp: 2023-07-06T05:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 67\n - timestamp: 2023-07-06T10:00\n duration: 300\n cloud/instance-type: B1\n cloud/region: uk-east\n cpu/utilization: 1\n")),(0,a.kt)("h2",{id:"computing-a-manifest-file"},"Computing a manifest file"),(0,a.kt)("p",null,"Impact Framework computes manifest files. For each component in the tree, the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array is passed to each plugin in the ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," pipeline in sequence."),(0,a.kt)("p",null,"In order for the ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," phase to execute correctly, the manifest needs to have ",(0,a.kt)("inlineCode",{parentName:"p"},"input")," data available."),(0,a.kt)("p",null,"Each plugin ",(0,a.kt)("em",{parentName:"p"},"enriches")," the ",(0,a.kt)("inlineCode",{parentName:"p"},"inputs")," array in some specific way, typically by adding a new ",(0,a.kt)("inlineCode",{parentName:"p"},"key-value")," pair to each observation in the array. For example, the ",(0,a.kt)("inlineCode",{parentName:"p"},"teads-curve")," plugin takes in CPU utilization expressed as a percentage as an input and appends ",(0,a.kt)("inlineCode",{parentName:"p"},"cpu/energy")," expressed in kWh. ",(0,a.kt)("inlineCode",{parentName:"p"},"cpu/energy")," is then available to be passed as an input to, for example, the ",(0,a.kt)("inlineCode",{parentName:"p"},"sci-e")," plugin."),(0,a.kt)("p",null,"This implies a sequence of plugins where the inputs for some plugins must either be present in the original manifest file or be outputs of the preceding plugins in the pipeline."),(0,a.kt)("p",null,"There are also plugins and built-in features that can synchronize time series of ",(0,a.kt)("inlineCode",{parentName:"p"},"observations")," across an entire tree and aggregate data across time or across components."),(0,a.kt)("h2",{id:"running-combinations-of-phases"},"Running combinations of phases"),(0,a.kt)("p",null,"It is possible to run each phase of the execution individually, or together. You can choose to ",(0,a.kt)("em",{parentName:"p"},"only")," run the ",(0,a.kt)("inlineCode",{parentName:"p"},"observe"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," phases of the manifest execution. This saves you from having to re-execute entire manifests every time you want to tweak something, making it a greener way to use IF."),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"if-run")," executes all the phases together, including ",(0,a.kt)("inlineCode",{parentName:"p"},"observe"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"compute"),". It generates yaml output data. However, you can run individual phases by passing ",(0,a.kt)("inlineCode",{parentName:"p"},"--observe"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"--regroup")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"--compute")," flags on the command line. For example, to run ",(0,a.kt)("em",{parentName:"p"},"only")," the compute phase:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"if-run -m --compute\n")),(0,a.kt)("p",null,"Maybe you only want to generate a static file that contains input data but don't want to run the full compute pipeline right now. You can run with ",(0,a.kt)("inlineCode",{parentName:"p"},"--observe"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"if-run --observe\n")),(0,a.kt)("p",null,"You can also combine the command, e.g. if you have a file with inputs and you want to run ",(0,a.kt)("inlineCode",{parentName:"p"},"regroup")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"compute")," but not ",(0,a.kt)("inlineCode",{parentName:"p"},"observe"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"if-run -m --regroup --compute\n")),(0,a.kt)("h2",{id:"outputs"},"Outputs"),(0,a.kt)("p",null,"When Impact Framework computes a manifest file, it appends new data to the manifest file and the final result is an enriched manifest that includes all the configuration and contextual data, the input data, and the results of executing each plugin. This means the output file is completely auditable - the original manifest file can be recovered simply by deleting the ",(0,a.kt)("inlineCode",{parentName:"p"},"outputs")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"execution")," sections of the output file."),(0,a.kt)("p",null,"IF generates yaml output data. Any other output formats have to be generated by separate exhaust scripts that take IF's yaml output as their input."),(0,a.kt)("p",null,"Here's an example output file:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},"name: sum\ndescription: successful path\ntags: null\ninitialize:\n plugins:\n sum:\n path: builtin\n method: Sum\n config:\n input-parameters:\n - cpu/energy\n - network/energy\n output-parameter: energy\nexecution:\n command: >-\n /home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node\n /home/user/Code/if/src/index.ts -m\n /home/user/Code/if/manifests/plugins/sum/success.yml\n environment:\n if-version: 0.3.3-beta.0\n os: linux\n os-version: 5.15.0-105-generic\n node-version: 21.4.0\n date-time: 2024-05-31T09:18:48.895Z (UTC)\n dependencies:\n - '@babel/core@7.22.10'\n - '@babel/preset-typescript@7.23.3'\n - '@commitlint/cli@18.6.0'\n - '@commitlint/config-conventional@18.6.0'\n - '@jest/globals@29.7.0'\n - '@types/jest@29.5.8'\n - '@types/js-yaml@4.0.9'\n - '@types/luxon@3.4.2'\n - '@types/node@20.9.0'\n - csv-stringify@6.4.6\n - fixpack@4.0.0\n - gts@5.2.0\n - husky@8.0.3\n - jest@29.7.0\n - js-yaml@4.1.0\n - lint-staged@15.2.2\n - luxon@3.4.4\n - release-it@16.3.0\n - rimraf@5.0.5\n - ts-command-line-args@2.5.1\n - ts-jest@29.1.1\n - typescript-cubic-spline@1.0.1\n - typescript@5.2.2\n - winston@3.11.0\n - zod@3.22.4\n status: success\ntree:\n children:\n child:\n pipeline:\n observe:\n regroup:\n compute:\n - sum\n inputs:\n - timestamp: 2023-08-06T00:00\n duration: 3600\n cpu/energy: 0.001\n network/energy: 0.001\n outputs:\n - timestamp: 2023-08-06T00:00\n duration: 3600\n cpu/energy: 0.001\n network/energy: 0.001\n energy: 0.002\n")))}c.isMDXComponent=!0},403:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/3f18767c1a55cee416e3de70314609e3-c7fa9feaf0993c3ed2b5a34b8b82432c.png"}}]); \ No newline at end of file diff --git a/assets/js/9e4f9ffe.4d9c6eb1.js b/assets/js/9e4f9ffe.699a4e00.js similarity index 70% rename from assets/js/9e4f9ffe.4d9c6eb1.js rename to assets/js/9e4f9ffe.699a4e00.js index 69c81fa..b22ab29 100644 --- a/assets/js/9e4f9ffe.4d9c6eb1.js +++ b/assets/js/9e4f9ffe.699a4e00.js @@ -1 +1 @@ -"use strict";(self.webpackChunkgreen_software_training=self.webpackChunkgreen_software_training||[]).push([[684],{4137:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>d});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function r(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var p=a.createContext({}),l=function(e){var n=a.useContext(p),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},c=function(e){var n=l(e.components);return a.createElement(p.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},m=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,o=e.originalType,p=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),m=l(t),d=i,g=m["".concat(p,".").concat(d)]||m[d]||u[d]||o;return t?a.createElement(g,r(r({ref:n},c),{},{components:t})):a.createElement(g,r({ref:n},c))}));function d(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var o=t.length,r=new Array(o);r[0]=m;var s={};for(var p in n)hasOwnProperty.call(n,p)&&(s[p]=n[p]);s.originalType=e,s.mdxType="string"==typeof e?e:i,r[1]=s;for(var l=2;l{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>s,toc:()=>l});var a=t(7462),i=(t(7294),t(4137));const o={sidebar_position:7},r="How to check parameters and units using `explainer`",s={unversionedId:"users/how-to-use-the-explain-feature",id:"users/how-to-use-the-explain-feature",title:"How to check parameters and units using `explainer`",description:"Manifest files can get complicated, especially when there are many plugin instances initialized. It can be challenging to keep track of the flow of parameters and their units through a pipeline. To help manifest authors and auditors verify the correct flow of information through a pipeline, we provide the explainer feature.",source:"@site/docs/users/how-to-use-the-explain-feature.md",sourceDirName:"users",slug:"/users/how-to-use-the-explain-feature",permalink:"/users/how-to-use-the-explain-feature",draft:!1,editUrl:"https://github.com/Green-Software-Foundation/if-docs/edit/master/docs/users/how-to-use-the-explain-feature.md",tags:[],version:"current",sidebarPosition:7,frontMatter:{sidebar_position:7},sidebar:"tutorialSidebar",previous:{title:"Verifying IF outputs with `if-check`",permalink:"/users/how-to-verify-files-with-if-check"},next:{title:"Developers",permalink:"/developers/"}},p={},l=[{value:"Toggling explainer on or off",id:"toggling-explainer-on-or-off",level:2},{value:"Defining parameter metadata",id:"defining-parameter-metadata",level:2},{value:"Example manifest",id:"example-manifest",level:2},{value:"When not to use explainer",id:"when-not-to-use-explainer",level:2}],c={toc:l};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"how-to-check-parameters-and-units-using-explainer"},"How to check parameters and units using ",(0,i.kt)("inlineCode",{parentName:"h1"},"explainer")),(0,i.kt)("p",null,"Manifest files can get complicated, especially when there are many plugin instances initialized. It can be challenging to keep track of the flow of parameters and their units through a pipeline. To help manifest authors and auditors verify the correct flow of information through a pipeline, we provide the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature."),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," adds a block to the manifest that simply lists the parameter metadata used be the plugin's instance in the manifest. The metadata contains:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"plugins:")," the list of plugins where the parameter is used"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"unit"),": the unit in which the parameter is expressed"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"description:")," a plain-language summary of the parameter"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"aggregation-method:"),": The appropriate method to use when aggregating the parameter across time or components (e.g. should it be summed, averaged, or held constant)")),(0,i.kt)("p",null,"This information allows you to check that the units output by one plugin are consistent with those expected as inputs to another, in one clear itemized list in your output manifest."),(0,i.kt)("p",null,"Note that when the parameter has different units across instances, an error will occur."),(0,i.kt)("h2",{id:"toggling-explainer-on-or-off"},"Toggling ",(0,i.kt)("inlineCode",{parentName:"h2"},"explainer")," on or off"),(0,i.kt)("p",null,"To enable the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature, add the following line to your manifest, somewhere in the manifest context (e.g. above the ",(0,i.kt)("inlineCode",{parentName:"p"},"plugins")," block):"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"explainer: true\n")),(0,i.kt)("p",null,"If you set ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," or omit the line altogether, the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature will not run."),(0,i.kt)("h2",{id:"defining-parameter-metadata"},"Defining parameter metadata"),(0,i.kt)("p",null,"Plugins are expected to ship with default values for their parameter metadata in their source code. For example, our plugin for calculating embodied carbon, ",(0,i.kt)("inlineCode",{parentName:"p"},"SciEmbodied"),", includes the following metadata definition:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"export const SciEmbodied = PluginFactory({\n metadata: {\n inputs: {\n vCPUs: {\n description: 'number of CPUs allocated to an application',\n unit: 'CPUs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n memory: {\n description: 'RAM available for a resource, in GB',\n unit: 'GB',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n ssd: {\n description: 'number of SSDs available for a resource',\n unit: 'SSDs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n hdd: {\n description: 'number of HDDs available for a resource',\n unit: 'HDDs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n gpu: {\n description: 'number of GPUs available for a resource',\n unit: 'GPUs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n 'usage-ratio': {\n description:\n 'a scaling factor that can be used to describe the ratio of actual resource usage comapred to real device usage, e.g. 0.25 if you are using 2 out of 8 vCPUs, 0.1 if you are responsible for 1 out of 10 GB of storage, etc',\n unit: 'dimensionless',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n time: {\n description:\n 'a time unit to scale the embodied carbon by, in seconds. If not provided,time defaults to the value of the timestep duration.',\n unit: 'seconds',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n },\n outputs: {\n 'embodied-carbon': {\n description: 'embodied carbon for a resource, scaled by usage',\n unit: 'gCO2e',\n 'aggregation-method': {\n time: 'sum',\n component: 'sum',\n },\n },\n },\n },\n});\n")),(0,i.kt)("p",null,"However, there are cases where a plugin might not have parameter metadata in its source code, either because it was omitted, it was not knowable in advance, or the plugin was built before we shipped the ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," feature. Sometimes, you might want to override the hard-coded defaults and use alternative metadata. In these cases, you can define new plugin metadata in the manifest file. It is considered best-practice to ensure all plugin instances have a complete set of plugin metadata."),(0,i.kt)("p",null,"Setting parameter metadata from the manifest file is done in the plugin instance's ",(0,i.kt)("inlineCode",{parentName:"p"},"initialize")," block, as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"initialize:\n plugins:\n interpolate:\n method: Interpolation\n path: 'builtin'\n config:\n method: linear\n x: [0, 10, 50, 100]\n y: [0.12, 0.32, 0.75, 1.02]\n input-parameter: 'cpu/utilization'\n output-parameter: 'cpu-factor'\n parameter-metadata:\n inputs:\n cpu/utilization:\n description: 'portion of the total CPU capacity being used by an application'\n unit: 'percentage'\n aggregation-method:\n time: avg\n component: avg\n outputs:\n cpu-factor:\n description: \"a dimensionless intermediate used to scale a processor's thermal design power by CPU usage\"\n unit: 'dimensionless'\n aggregation-method:\n time: avg\n component: avg\n")),(0,i.kt)("h2",{id:"example-manifest"},"Example manifest"),(0,i.kt)("p",null,"The following manifest uses three plugins: ",(0,i.kt)("inlineCode",{parentName:"p"},"sci"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"sci-embodied")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sum-carbon"),". Of these, only ",(0,i.kt)("inlineCode",{parentName:"p"},"sci-embodied"),' has defaults hardcoded into the plugin code. The other two do not because they are "generic" arithmetic plugins for whom the values cannot be known in advance. Therefore, we set new parameter metadata in the ',(0,i.kt)("inlineCode",{parentName:"p"},"initialize")," block for ",(0,i.kt)("inlineCode",{parentName:"p"},"sci")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sum-carbon")," but use the hardcoded defaults for ",(0,i.kt)("inlineCode",{parentName:"p"},"sci-embodied"),"."),(0,i.kt)("p",null,"We toggle the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature by adding ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer: true")," in the manifest context."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"name: sci\ndescription: successful path\ntags:\nexplainer: true\ninitialize:\n plugins:\n sci-embodied:\n path: 'builtin'\n method: SciEmbodied\n 'sum-carbon':\n path: 'builtin'\n method: Sum\n config:\n input-parameters:\n - carbon-operational\n - embodied-carbon\n output-parameter: carbon\n parameter-metadata:\n inputs:\n carbon-operational:\n description: \"carbon emitted due to an application's execution\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n embodied-carbon:\n description: \"carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n outputs:\n carbon:\n description: \"total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n sci:\n kind: plugin\n method: Sci\n path: 'builtin'\n config:\n functional-unit: requests\n parameter-metadata:\n inputs:\n carbon:\n description: \"total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n requests:\n description: 'number of requests made to application in the given timestep'\n unit: 'requests'\n aggregation-method:\n time: sum\n component: sum\n outputs:\n sci:\n description: 'software carbon intensity expressed as a rate of carbon emission per request'\n unit: 'gCO2eq/request'\n aggregation-method:\n time: sum\n component: sum\ntree:\n children:\n child:\n pipeline:\n compute:\n - sci-embodied\n - sum-carbon\n - sci\n defaults:\n device/emissions-embodied: 1533.120 # gCO2eq\n time-reserved: 3600 # 1hr in seconds\n device/expected-lifespan: 94608000 # 3 years in seconds\n vcpus-allocated: 1\n vcpus-total: 8\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 3600\n energy: 5\n carbon-operational: 5\n requests: 100\n")),(0,i.kt)("p",null,"When we execute this manifest, the following ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," block is added to the output file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"explain:\n vCPUs:\n plugins:\n - sci-embodied\n unit: CPUs\n description: number of CPUs allocated to an application\n aggregation-method:\n time: copy\n component: copy\n memory:\n plugins:\n - sci-embodied\n unit: GB\n description: RAM available for a resource, in GB\n aggregation-method:\n time: copy\n component: copy\n ssd:\n plugins:\n - sci-embodied\n unit: SSDs\n description: number of SSDs available for a resource\n aggregation-method:\n time: copy\n component: copy\n hdd:\n plugins:\n - sci-embodied\n unit: HDDs\n description: number of HDDs available for a resource\n aggregation-method:\n time: copy\n component: copy\n gpu:\n plugins:\n - sci-embodied\n unit: GPUs\n description: number of GPUs available for a resource\n aggregation-method:\n time: copy\n component: copy\n usage-ratio:\n plugins:\n - sci-embodied\n unit: dimensionless\n description: >-\n a scaling factor that can be used to describe the ratio of actual resource\n usage comapred to real device usage, e.g. 0.25 if you are using 2 out of 8\n vCPUs, 0.1 if you are responsible for 1 out of 10 GB of storage, etc\n aggregation-method:\n time: copy\n component: copy\n time:\n plugins:\n - sci-embodied\n unit: seconds\n description: >-\n a time unit to scale the embodied carbon by, in seconds. If not\n provided,time defaults to the value of the timestep duration.\n aggregation-method:\n time: copy\n component: copy\n embodied-carbon:\n plugins:\n - sci-embodied\n - sum-carbon\n unit: gCO2eq\n description: >-\n carbon emitted during the production, distribution and disposal of a\n hardware component, scaled by the fraction of the component's lifespan\n being allocated to the application under investigation\n aggregation-method:\n time: sum\n component: sum\n carbon-operational:\n plugins:\n - sum-carbon\n unit: gCO2eq\n description: carbon emitted due to an application's execution\n aggregation-method:\n time: sum\n component: sum\n carbon:\n plugins:\n - sum-carbon\n - sci\n unit: gCO2eq\n description: >-\n total carbon emissions attributed to an application's usage as the sum of\n embodied and operational carbon\n aggregation-method:\n time: sum\n component: sum\n requests:\n plugins:\n - sci\n unit: requests\n description: number of requests made to application in the given timestep\n aggregation-method:\n time: sum\n component: sum\n sci:\n plugins:\n - sci\n unit: gCO2eq/request\n description: >-\n software carbon intensity expressed as a rate of carbon emission per\n request\n aggregation-method:\n time: sum\n component: sum\n")),(0,i.kt)("h2",{id:"when-not-to-use-explainer"},"When ",(0,i.kt)("em",{parentName:"h2"},"not")," to use ",(0,i.kt)("inlineCode",{parentName:"h2"},"explainer")),(0,i.kt)("p",null,"In manifests where you are only using generic plugins, or override all the metadata loaded in from the plugin source code, ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," will simply echo back information from your ",(0,i.kt)("inlineCode",{parentName:"p"},"initialize")," block since all the parameter metadata is set there. In these cases, the ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," block is probably redundant information as you could just read the same information in your manifest's ",(0,i.kt)("inlineCode",{parentName:"p"},"plugins")," section. The point of ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," is to confirm what units and parameters are being passed through a pipeline when you have a mixture of plugins from many sources whose parameter metadata is defined in-code and in-manifest."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkgreen_software_training=self.webpackChunkgreen_software_training||[]).push([[684],{4137:(e,n,t)=>{t.d(n,{Zo:()=>l,kt:()=>d});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function r(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var p=a.createContext({}),u=function(e){var n=a.useContext(p),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},l=function(e){var n=u(e.components);return a.createElement(p.Provider,{value:n},e.children)},c={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},m=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,o=e.originalType,p=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),m=u(t),d=i,g=m["".concat(p,".").concat(d)]||m[d]||c[d]||o;return t?a.createElement(g,r(r({ref:n},l),{},{components:t})):a.createElement(g,r({ref:n},l))}));function d(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var o=t.length,r=new Array(o);r[0]=m;var s={};for(var p in n)hasOwnProperty.call(n,p)&&(s[p]=n[p]);s.originalType=e,s.mdxType="string"==typeof e?e:i,r[1]=s;for(var u=2;u{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>u});var a=t(7462),i=(t(7294),t(4137));const o={sidebar_position:7},r="How to check parameters and units using `explainer`",s={unversionedId:"users/how-to-use-the-explain-feature",id:"users/how-to-use-the-explain-feature",title:"How to check parameters and units using `explainer`",description:"Manifest files can get complicated, especially when there are many plugin instances initialized. It can be challenging to keep track of the flow of parameters and their units through a pipeline. To help manifest authors and auditors verify the correct flow of information through a pipeline, we provide the explainer feature.",source:"@site/docs/users/how-to-use-the-explain-feature.md",sourceDirName:"users",slug:"/users/how-to-use-the-explain-feature",permalink:"/users/how-to-use-the-explain-feature",draft:!1,editUrl:"https://github.com/Green-Software-Foundation/if-docs/edit/master/docs/users/how-to-use-the-explain-feature.md",tags:[],version:"current",sidebarPosition:7,frontMatter:{sidebar_position:7},sidebar:"tutorialSidebar",previous:{title:"Verifying IF outputs with `if-check`",permalink:"/users/how-to-verify-files-with-if-check"},next:{title:"Developers",permalink:"/developers/"}},p={},u=[{value:"Toggling explainer on or off",id:"toggling-explainer-on-or-off",level:2},{value:"Defining parameter metadata",id:"defining-parameter-metadata",level:2},{value:"Example manifest",id:"example-manifest",level:2},{value:"When not to use explainer",id:"when-not-to-use-explainer",level:2}],l={toc:u};function c(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},l,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"how-to-check-parameters-and-units-using-explainer"},"How to check parameters and units using ",(0,i.kt)("inlineCode",{parentName:"h1"},"explainer")),(0,i.kt)("p",null,"Manifest files can get complicated, especially when there are many plugin instances initialized. It can be challenging to keep track of the flow of parameters and their units through a pipeline. To help manifest authors and auditors verify the correct flow of information through a pipeline, we provide the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature."),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," adds a block to the manifest that simply lists the parameter metadata used be the plugin's instance in the manifest. The metadata contains:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"plugins:")," the list of plugins where the parameter is used"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"unit"),": the unit in which the parameter is expressed"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"description:")," a plain-language summary of the parameter"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"aggregation-method:"),": The appropriate method to use when aggregating the parameter across time or components (e.g. should it be summed, averaged, or held constant)")),(0,i.kt)("p",null,"This information allows you to check that the units output by one plugin are consistent with those expected as inputs to another, in one clear itemized list in your output manifest."),(0,i.kt)("p",null,"Note that when the parameter has different units across instances, an error will occur."),(0,i.kt)("h2",{id:"toggling-explainer-on-or-off"},"Toggling ",(0,i.kt)("inlineCode",{parentName:"h2"},"explainer")," on or off"),(0,i.kt)("p",null,"To enable the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature, add the following line to your manifest, somewhere in the manifest context (e.g. above the ",(0,i.kt)("inlineCode",{parentName:"p"},"plugins")," block):"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"explainer: true\n")),(0,i.kt)("p",null,"If you set ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," or omit the line altogether, the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature will not run."),(0,i.kt)("h2",{id:"defining-parameter-metadata"},"Defining parameter metadata"),(0,i.kt)("p",null,"Plugins are expected to ship with default values for their parameter metadata in their source code. For example, our plugin for calculating embodied carbon, ",(0,i.kt)("inlineCode",{parentName:"p"},"SciEmbodied"),", includes the following metadata definition:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"export const SciEmbodied = PluginFactory({\n metadata: {\n inputs: {\n vCPUs: {\n description: 'number of CPUs allocated to an application',\n unit: 'CPUs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n memory: {\n description: 'RAM available for a resource, in GB',\n unit: 'GB',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n ssd: {\n description: 'number of SSDs available for a resource',\n unit: 'SSDs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n hdd: {\n description: 'number of HDDs available for a resource',\n unit: 'HDDs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n gpu: {\n description: 'number of GPUs available for a resource',\n unit: 'GPUs',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n 'usage-ratio': {\n description:\n 'a scaling factor that can be used to describe the ratio of actual resource usage comapred to real device usage, e.g. 0.25 if you are using 2 out of 8 vCPUs, 0.1 if you are responsible for 1 out of 10 GB of storage, etc',\n unit: 'dimensionless',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n time: {\n description:\n 'a time unit to scale the embodied carbon by, in seconds. If not provided,time defaults to the value of the timestep duration.',\n unit: 'seconds',\n 'aggregation-method': {\n time: 'copy',\n component: 'copy',\n },\n },\n },\n outputs: {\n 'embodied-carbon': {\n description: 'embodied carbon for a resource, scaled by usage',\n unit: 'gCO2e',\n 'aggregation-method': {\n time: 'sum',\n component: 'sum',\n },\n },\n },\n },\n});\n")),(0,i.kt)("p",null,"However, there are cases where a plugin might not have parameter metadata in its source code, either because it was omitted, it was not knowable in advance, or the plugin was built before we shipped the ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," feature. Sometimes, you might want to override the hard-coded defaults and use alternative metadata. In these cases, you can define new plugin metadata in the manifest file. It is considered best-practice to ensure all plugin instances have a complete set of plugin metadata."),(0,i.kt)("p",null,"Setting parameter metadata from the manifest file is done in the plugin instance's ",(0,i.kt)("inlineCode",{parentName:"p"},"initialize")," block, as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"initialize:\n plugins:\n interpolate:\n method: Interpolation\n path: 'builtin'\n config:\n method: linear\n x: [0, 10, 50, 100]\n y: [0.12, 0.32, 0.75, 1.02]\n input-parameter: 'cpu/utilization'\n output-parameter: 'cpu-factor'\n parameter-metadata:\n inputs:\n cpu/utilization:\n description: 'portion of the total CPU capacity being used by an application'\n unit: 'percentage'\n aggregation-method:\n time: avg\n component: avg\n outputs:\n cpu-factor:\n description: \"a dimensionless intermediate used to scale a processor's thermal design power by CPU usage\"\n unit: 'dimensionless'\n aggregation-method:\n time: avg\n component: avg\n")),(0,i.kt)("h2",{id:"example-manifest"},"Example manifest"),(0,i.kt)("p",null,"The following manifest uses three plugins: ",(0,i.kt)("inlineCode",{parentName:"p"},"sci"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"sci-embodied")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sum-carbon"),". Of these, only ",(0,i.kt)("inlineCode",{parentName:"p"},"sci-embodied"),' has defaults hardcoded into the plugin code. The other two do not because they are "generic" arithmetic plugins for whom the values cannot be known in advance. Therefore, we set new parameter metadata in the ',(0,i.kt)("inlineCode",{parentName:"p"},"initialize")," block for ",(0,i.kt)("inlineCode",{parentName:"p"},"sci")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sum-carbon")," but use the hardcoded defaults for ",(0,i.kt)("inlineCode",{parentName:"p"},"sci-embodied"),"."),(0,i.kt)("p",null,"We toggle the ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," feature by adding ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer: true")," in the manifest context."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"name: sci\ndescription: successful path\ntags:\nexplainer: true\ninitialize:\n plugins:\n sci-embodied:\n path: 'builtin'\n method: SciEmbodied\n 'sum-carbon':\n path: 'builtin'\n method: Sum\n config:\n input-parameters:\n - carbon-operational\n - embodied-carbon\n output-parameter: carbon\n parameter-metadata:\n inputs:\n carbon-operational:\n description: \"carbon emitted due to an application's execution\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n embodied-carbon:\n description: \"carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n outputs:\n carbon:\n description: \"total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n sci:\n kind: plugin\n method: Sci\n path: 'builtin'\n config:\n functional-unit: requests\n parameter-metadata:\n inputs:\n carbon:\n description: \"total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon\"\n unit: 'gCO2eq'\n aggregation-method:\n time: sum\n component: sum\n requests:\n description: 'number of requests made to application in the given timestep'\n unit: 'requests'\n aggregation-method:\n time: sum\n component: sum\n outputs:\n sci:\n description: 'software carbon intensity expressed as a rate of carbon emission per request'\n unit: 'gCO2eq/request'\n aggregation-method:\n time: sum\n component: sum\ntree:\n children:\n child:\n pipeline:\n compute:\n - sci-embodied\n - sum-carbon\n - sci\n defaults:\n device/emissions-embodied: 1533.120 # gCO2eq\n time-reserved: 3600 # 1hr in seconds\n device/expected-lifespan: 94608000 # 3 years in seconds\n vcpus-allocated: 1\n vcpus-total: 8\n inputs:\n - timestamp: 2023-07-06T00:00\n duration: 3600\n energy: 5\n carbon-operational: 5\n requests: 100\n")),(0,i.kt)("p",null,"When we execute this manifest, the following ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," block is added to the output file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},"explain:\n sci-embodied:\n inputs:\n vCPUs:\n description: number of CPUs allocated to an application\n unit: CPUs\n aggregation-method:\n time: copy\n component: copy\n memory:\n description: RAM available for a resource, in GB\n unit: GB\n aggregation-method:\n time: copy\n component: copy\n ssd:\n description: number of SSDs available for a resource\n unit: SSDs\n aggregation-method:\n time: copy\n component: copy\n hdd:\n description: number of HDDs available for a resource\n unit: HDDs\n aggregation-method:\n time: copy\n component: copy\n gpu:\n description: number of GPUs available for a resource\n unit: GPUs\n aggregation-method:\n time: copy\n component: copy\n usage-ratio:\n description: >-\n a scaling factor that can be used to describe the ratio of actual\n resource usage comapred to real device usage, e.g. 0.25 if you are\n using 2 out of 8 vCPUs, 0.1 if you are responsible for 1 out of 10 GB\n of storage, etc\n unit: dimensionless\n aggregation-method:\n time: copy\n component: copy\n time:\n description: >-\n a time unit to scale the embodied carbon by, in seconds. If not\n provided,time defaults to the value of the timestep duration.\n unit: seconds\n aggregation-method:\n time: copy\n component: copy\n outputs:\n embodied-carbon:\n description: embodied carbon for a resource, scaled by usage\n unit: gCO2eq\n aggregation-method:\n time: sum\n component: sum\n sum-carbon:\n inputs:\n carbon-operational:\n unit: gCO2eq\n description: carbon emitted due to an application's execution\n aggregation-method:\n time: sum\n component: sum\n embodied-carbon:\n unit: gCO2eq\n description: >-\n carbon emitted during the production, distribution and disposal of a\n hardware component, scaled by the fraction of the component's lifespan\n being allocated to the application under investigation\n aggregation-method:\n time: sum\n component: sum\n outputs:\n carbon:\n unit: gCO2eq\n description: >-\n total carbon emissions attributed to an application's usage as the sum\n of embodied and operational carbon\n aggregation-method:\n time: sum\n component: sum\n sci:\n inputs:\n carbon:\n unit: gCO2eq\n description: >-\n total carbon emissions attributed to an application's usage as the sum\n of embodied and operational carbon\n aggregation-method:\n time: sum\n component: sum\n functional-unit:\n description: >-\n the name of the functional unit in which the final SCI value should be\n expressed, e.g. requests, users\n unit: none\n aggregation-method:\n time: sum\n component: sum\n requests:\n unit: requests\n description: number of requests made to application in the given timestep\n aggregation-method:\n time: sum\n component: sum\n outputs:\n sci:\n unit: gCO2eq/request\n description: >-\n software carbon intensity expressed as a rate of carbon emission per\n request\n aggregation-method:\n time: sum\n component: sum\n")),(0,i.kt)("h2",{id:"when-not-to-use-explainer"},"When ",(0,i.kt)("em",{parentName:"h2"},"not")," to use ",(0,i.kt)("inlineCode",{parentName:"h2"},"explainer")),(0,i.kt)("p",null,"In manifests where you are only using generic plugins, or override all the metadata loaded in from the plugin source code, ",(0,i.kt)("inlineCode",{parentName:"p"},"explainer")," will simply echo back information from your ",(0,i.kt)("inlineCode",{parentName:"p"},"initialize")," block since all the parameter metadata is set there. In these cases, the ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," block is probably redundant information as you could just read the same information in your manifest's ",(0,i.kt)("inlineCode",{parentName:"p"},"plugins")," section. The point of ",(0,i.kt)("inlineCode",{parentName:"p"},"explain")," is to confirm what units and parameters are being passed through a pipeline when you have a mixture of plugins from many sources whose parameter metadata is defined in-code and in-manifest."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.8a3c0337.js b/assets/js/runtime~main.22b0b624.js similarity index 96% rename from assets/js/runtime~main.8a3c0337.js rename to assets/js/runtime~main.22b0b624.js index ab7ca40..c5ec29b 100644 --- a/assets/js/runtime~main.8a3c0337.js +++ b/assets/js/runtime~main.22b0b624.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,t,r,f,c={},o={};function d(e){var a=o[e];if(void 0!==a)return a.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return c[e].call(t.exports,t,t.exports,d),t.loaded=!0,t.exports}d.m=c,d.c=o,e=[],d.O=(a,t,r,f)=>{if(!t){var c=1/0;for(i=0;i=f)&&Object.keys(d.O).every((e=>d.O[e](t[n])))?t.splice(n--,1):(o=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[t,r,f]},d.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return d.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var f=Object.create(null);d.r(f);var c={};a=a||[null,t({}),t([]),t(t)];for(var o=2&r&&e;"object"==typeof o&&!~a.indexOf(o);o=t(o))Object.getOwnPropertyNames(o).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,d.d(f,c),f},d.d=(e,a)=>{for(var t in a)d.o(a,t)&&!d.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((a,t)=>(d.f[t](e,a),a)),[])),d.u=e=>"assets/js/"+({53:"935f2afb",54:"667a5e8e",85:"1f391b9e",95:"95ad2285",99:"449bc0d9",109:"0a19367a",112:"cca8bc57",117:"29966d47",162:"486bc80e",168:"fd565be6",169:"89d906ef",186:"4c7f2983",229:"75e434b4",237:"1df93b7f",278:"3dce4fd2",288:"ad895e75",316:"a34125e0",352:"f744e480",383:"ce9192f0",426:"244c9605",448:"8e29a039",468:"5c5a410f",485:"37c09b06",491:"b5ada7f5",514:"1be78505",527:"859a76f9",533:"bdfbfcca",642:"fbcc0412",644:"68ffc9f1",671:"0e384e19",672:"a4954f21",684:"9e4f9ffe",712:"ab3e713c",713:"93f138be",751:"1a3c9b31",760:"069226b7",796:"0569acb0",806:"28f080da",822:"63925da8",918:"17896441",939:"0734eb39",940:"d57fc049",954:"8c7895a0",960:"91c76d4c",996:"e7b22fe0"}[e]||e)+"."+{53:"cf6c6ad2",54:"8c1193b8",85:"c80ed877",95:"13354aaf",99:"633bc0c4",109:"0015d164",112:"906ff5a6",117:"01ab4fb5",162:"e37292f9",168:"55aba2ff",169:"e683a8ca",186:"42112da7",229:"facb9f0e",237:"c4c02241",248:"d6c76d13",278:"4524ae0a",288:"c7013a94",316:"3ff6d5e2",352:"0771c3e7",383:"f8871535",426:"8859fe18",448:"0882b3db",468:"e81f04e7",485:"40fe1089",491:"1886046d",514:"a282f924",527:"aa11797a",533:"601c6a75",642:"90d01f02",644:"d410a3f8",671:"b41b0840",672:"97c940d4",684:"4d9c6eb1",712:"cada5228",713:"a9252a89",751:"b532e852",760:"19270c29",796:"242560b0",806:"36b2c0f7",822:"85a114ee",918:"d3687259",939:"17f20c53",940:"c32b0ca1",954:"a4e59a8f",960:"9a236c64",982:"f6f252ba",996:"7a691793"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},f="green-software-training:",d.l=(e,a,t,c)=>{if(r[e])r[e].push(a);else{var o,n;if(void 0!==t)for(var b=document.getElementsByTagName("script"),i=0;i{o.onerror=o.onload=null,clearTimeout(s);var f=r[e];if(delete r[e],o.parentNode&&o.parentNode.removeChild(o),f&&f.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),n&&document.head.appendChild(o)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/",d.gca=function(e){return e={17896441:"918","935f2afb":"53","667a5e8e":"54","1f391b9e":"85","95ad2285":"95","449bc0d9":"99","0a19367a":"109",cca8bc57:"112","29966d47":"117","486bc80e":"162",fd565be6:"168","89d906ef":"169","4c7f2983":"186","75e434b4":"229","1df93b7f":"237","3dce4fd2":"278",ad895e75:"288",a34125e0:"316",f744e480:"352",ce9192f0:"383","244c9605":"426","8e29a039":"448","5c5a410f":"468","37c09b06":"485",b5ada7f5:"491","1be78505":"514","859a76f9":"527",bdfbfcca:"533",fbcc0412:"642","68ffc9f1":"644","0e384e19":"671",a4954f21:"672","9e4f9ffe":"684",ab3e713c:"712","93f138be":"713","1a3c9b31":"751","069226b7":"760","0569acb0":"796","28f080da":"806","63925da8":"822","0734eb39":"939",d57fc049:"940","8c7895a0":"954","91c76d4c":"960",e7b22fe0:"996"}[e]||e,d.p+d.u(e)},(()=>{var e={303:0,532:0};d.f.j=(a,t)=>{var r=d.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(303|532)$/.test(a))e[a]=0;else{var f=new Promise(((t,f)=>r=e[a]=[t,f]));t.push(r[2]=f);var c=d.p+d.u(a),o=new Error;d.l(c,(t=>{if(d.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var f=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src;o.message="Loading chunk "+a+" failed.\n("+f+": "+c+")",o.name="ChunkLoadError",o.type=f,o.request=c,r[1](o)}}),"chunk-"+a,a)}},d.O.j=a=>0===e[a];var a=(a,t)=>{var r,f,c=t[0],o=t[1],n=t[2],b=0;if(c.some((a=>0!==e[a]))){for(r in o)d.o(o,r)&&(d.m[r]=o[r]);if(n)var i=n(d)}for(a&&a(t);b{"use strict";var e,a,t,r,f,c={},o={};function d(e){var a=o[e];if(void 0!==a)return a.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return c[e].call(t.exports,t,t.exports,d),t.loaded=!0,t.exports}d.m=c,d.c=o,e=[],d.O=(a,t,r,f)=>{if(!t){var c=1/0;for(i=0;i=f)&&Object.keys(d.O).every((e=>d.O[e](t[n])))?t.splice(n--,1):(o=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[t,r,f]},d.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return d.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var f=Object.create(null);d.r(f);var c={};a=a||[null,t({}),t([]),t(t)];for(var o=2&r&&e;"object"==typeof o&&!~a.indexOf(o);o=t(o))Object.getOwnPropertyNames(o).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,d.d(f,c),f},d.d=(e,a)=>{for(var t in a)d.o(a,t)&&!d.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((a,t)=>(d.f[t](e,a),a)),[])),d.u=e=>"assets/js/"+({53:"935f2afb",54:"667a5e8e",85:"1f391b9e",95:"95ad2285",99:"449bc0d9",109:"0a19367a",112:"cca8bc57",117:"29966d47",162:"486bc80e",168:"fd565be6",169:"89d906ef",186:"4c7f2983",229:"75e434b4",237:"1df93b7f",278:"3dce4fd2",288:"ad895e75",316:"a34125e0",352:"f744e480",383:"ce9192f0",426:"244c9605",448:"8e29a039",468:"5c5a410f",485:"37c09b06",491:"b5ada7f5",514:"1be78505",527:"859a76f9",533:"bdfbfcca",642:"fbcc0412",644:"68ffc9f1",671:"0e384e19",672:"a4954f21",684:"9e4f9ffe",712:"ab3e713c",713:"93f138be",751:"1a3c9b31",760:"069226b7",796:"0569acb0",806:"28f080da",822:"63925da8",918:"17896441",939:"0734eb39",940:"d57fc049",954:"8c7895a0",960:"91c76d4c",996:"e7b22fe0"}[e]||e)+"."+{53:"cf6c6ad2",54:"8c1193b8",85:"c80ed877",95:"13354aaf",99:"a58615c7",109:"0015d164",112:"906ff5a6",117:"01ab4fb5",162:"e37292f9",168:"55aba2ff",169:"e683a8ca",186:"42112da7",229:"facb9f0e",237:"c4c02241",248:"d6c76d13",278:"4524ae0a",288:"c7013a94",316:"3ff6d5e2",352:"0771c3e7",383:"f8871535",426:"8859fe18",448:"0882b3db",468:"83bbc838",485:"40fe1089",491:"1886046d",514:"a282f924",527:"aa11797a",533:"601c6a75",642:"90d01f02",644:"d410a3f8",671:"b41b0840",672:"97c940d4",684:"699a4e00",712:"cada5228",713:"a9252a89",751:"b532e852",760:"19270c29",796:"242560b0",806:"36b2c0f7",822:"85a114ee",918:"d3687259",939:"17f20c53",940:"c32b0ca1",954:"a4e59a8f",960:"9a236c64",982:"f6f252ba",996:"7a691793"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},f="green-software-training:",d.l=(e,a,t,c)=>{if(r[e])r[e].push(a);else{var o,n;if(void 0!==t)for(var b=document.getElementsByTagName("script"),i=0;i{o.onerror=o.onload=null,clearTimeout(s);var f=r[e];if(delete r[e],o.parentNode&&o.parentNode.removeChild(o),f&&f.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),n&&document.head.appendChild(o)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/",d.gca=function(e){return e={17896441:"918","935f2afb":"53","667a5e8e":"54","1f391b9e":"85","95ad2285":"95","449bc0d9":"99","0a19367a":"109",cca8bc57:"112","29966d47":"117","486bc80e":"162",fd565be6:"168","89d906ef":"169","4c7f2983":"186","75e434b4":"229","1df93b7f":"237","3dce4fd2":"278",ad895e75:"288",a34125e0:"316",f744e480:"352",ce9192f0:"383","244c9605":"426","8e29a039":"448","5c5a410f":"468","37c09b06":"485",b5ada7f5:"491","1be78505":"514","859a76f9":"527",bdfbfcca:"533",fbcc0412:"642","68ffc9f1":"644","0e384e19":"671",a4954f21:"672","9e4f9ffe":"684",ab3e713c:"712","93f138be":"713","1a3c9b31":"751","069226b7":"760","0569acb0":"796","28f080da":"806","63925da8":"822","0734eb39":"939",d57fc049:"940","8c7895a0":"954","91c76d4c":"960",e7b22fe0:"996"}[e]||e,d.p+d.u(e)},(()=>{var e={303:0,532:0};d.f.j=(a,t)=>{var r=d.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(303|532)$/.test(a))e[a]=0;else{var f=new Promise(((t,f)=>r=e[a]=[t,f]));t.push(r[2]=f);var c=d.p+d.u(a),o=new Error;d.l(c,(t=>{if(d.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var f=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src;o.message="Loading chunk "+a+" failed.\n("+f+": "+c+")",o.name="ChunkLoadError",o.type=f,o.request=c,r[1](o)}}),"chunk-"+a,a)}},d.O.j=a=>0===e[a];var a=(a,t)=>{var r,f,c=t[0],o=t[1],n=t[2],b=0;if(c.some((a=>0!==e[a]))){for(r in o)d.o(o,r)&&(d.m[r]=o[r]);if(n)var i=n(d)}for(a&&a(t);b How can I contribute to Impact framework? | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How can I contribute to Impact framework?

We welcome all kinds of contributions to Impact Framework, from bug reports to work on core features or documentation. For detailed instructions on how to start and what to expect when you contribute, please visit our contributions guidelines. There you will find guidance on how to raise bug reports, how to contribute code and what processes we have in place for handling your issues and PRs.

To contribute to these docs, you can raise pull requests against our Github repository.

In general, you can consider any ticket on our issue board open for community contributions if it does not have the core-only tag. We tag suitable introductory issues as good-first-issue - these are great places to get started.

You can also contribute by participating in discussions on our mailing list at if-community@greensoftware.foundation. We send out weekly updates that includes what we've shipped, what we're working on and how you can get involved each week.

- + \ No newline at end of file diff --git a/developers/how-to-build-plugins/index.html b/developers/how-to-build-plugins/index.html index f626f27..c79096b 100644 --- a/developers/how-to-build-plugins/index.html +++ b/developers/how-to-build-plugins/index.html @@ -8,7 +8,7 @@ How to build plugins | Impact Framework - + @@ -17,7 +17,7 @@ To help developers write Typescript plugins to integrate easily into IF, we provide the PluginFactory interface. Here's an overview of the stages you need to follow to integrate your plugin:

  • create a Typescript file that implements the PluginFactory from if-core
  • install the plugin
  • initialize and invoke the plugin in your manifest file

Step 1: Use our template repository

Instead of building up your plugin repository and all the configuration from scratch, you can use our plugin template repository. To use the template, visit the Github repository and click the Use this template button. You will have the option to create a new repository under your own account. Then, you can clone that repository to your local machine.

use our template repository

Inside that repository, all you have to do is run npm install typescript in the template folder, rename the project in package.json and write your plugin code inside index.ts. All the configuration and setup is taken care of for you.

Step 2: Writing your plugin code

Now your project is setup, you can focus on your plugin logic. The entry point for your plugin is index.ts. In this guide it is assumed that all your plugin logic is in index.ts but depending on the copmplexity of your plugin you might want to split the code across multiple files. index.ts should always be your entry point, though.

The following sections describe the rules your plugin code should conform to. We also have an appendix that deep dives a real plugin.

Plugin interface

Your plugin must implement the PluginFactory interface, which is a higher-order function that takes a params object of type PluginFactoryParams. This factory function returns another function (referred to as the "inner function") that manages the plugin’s config, parametersMetadata, and mapping.

The PluginFactory is structured as follows:

export const PluginFactory =
<C = ConfigParams>(params: PluginFactoryParams<C>) =>
(
config: C = {} as C,
parametersMetadata: PluginParametersMetadata,
mapping: MappingParams
) => ({
metadata: {
inputs: {...params.metadata.inputs, ...parametersMetadata?.inputs},
outputs: parametersMetadata?.outputs || params.metadata.outputs,
},
execute: async (inputs: PluginParams[]) => {
// Generic plugin functionality goes here
// E.g., mapping, arithmetic operations, validation
// Process inputs and mapping logic
});
});

The inner function returned by the PluginFactory handles the following parameters:

  • config: An object of type ConfigParams. This parameter holds the configuration settings for the plugin and defaults to an empty object ({}).
  • parametersMetadata: An object of type PluginParametersMetadata that contains metadata describing the plugin’s parameters.
  • mapping: A MappingParams object that outlines how plugin parameters are mapped.

Config

The config object is passed as an argument to your plugin and can be handled as shown in the example above. The structure of the config depends on what is defined in the manifest file. For example, the Sci plugin has access to input-parameters and output-parameter fields in its global configuration, as defined in the Initialize block of the manifest file:

initialize:
plugins:
sum:
method: Sci
path: 'builtin'
config:
input-parameters: ['cpu/energy', 'network/energy']
output-parameter: 'energy'

Parameter metadata

The parameter-metadata is passed as an argument to the plugin as the config. It contains information about the description, unit and aggregation-method of the parameters of the inputs and outputs that defined in the manifest.

initialize:
plugins:
sum:
method: Sci
path: 'builtin'
config:
input-parameters: ['cpu/energy', 'network/energy']
output-parameter: 'energy-sum'
parameter-metadata:
inputs:
cpu/energy:
description: energy consumed by the cpu
unit: kWh
aggregation-method:
time: sum
component: sum
network/energy:
description: energy consumed by data ingress and egress
unit: kWh
aggregation-method:
time: sum
component: sum
outputs:
energy-sum:
description: sum of energy components
unit: kWh
aggregation-method:
time: sum
component: sum

Mapping

The mapping is an optional argument passed to the plugin. Its purpose is to rename the arguments expected or returned from the plugin as part of the plugin's execution, avoiding the need to use additional plugins to rename parameters.

For example, your plugin might expect cpu/energy and your input data has the parameter cpu-energy returned from another plugin. Instead of using an additional plugin to rename the parameter and add a new one, you can use mapping to:

a) rename the output from the first plugin so that cpu/energy is returned instead of the default cpu-energy

b) instruct the second plugin to accept cpu-energy instead of the default cpu/energy

The mapping config is an object with key-value pairs, where the key is the 'original' parameter name that the plugin uses, and the value is the 'new' name that you want to use instead. The mapping block is an optional and allows mapping the input and output parameters of the plugin. The structure of the mapping block is:

name: sci
description: successful path
tags:
initialize:
plugins:
sci:
kind: plugin
method: Sci
path: 'builtin'
config:
functional-unit: requests
mapping:
sci: if-sci
tree:
children:
child:
pipeline:
compute:
- sci
inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 5
carbon-operational: 5
carbon-embodied: 0.02
carbon: 5.02
requests: 100

In the outputs, the sci value returned by the Sci plugin will be named if-sci.

Plugin example

Here’s a minimal example of a plugin that sums inputs based on the configuration:

export const Plugin = PluginFactory({
metadata: {
inputs: {
// Define your input parameters here
},
outputs: {
// Define your output parameters here
},
},
configValidation: (config: ConfigParams) => {
// Implement validation logic for config here
},
inputValidation: (input: PluginParams, config: ConfigParams) => {
// Implement validation logic for inputs here
},
implementation: async (inputs: PluginParams[], config: ConfigParams) => {
// Implement plugin logic here
// e.g., summing input parameters
},
allowArithmeticExpressions: [],
});

const plugin = Plugin(config, parametersMetadata, mapping);
const result = await plugin.execute(inputs);

PluginFactoryParams

The PluginFactory interface requires the mandatory parameters defined in the PluginFactoryParams interface:

export interface PluginFactoryParams<C = ConfigParams> {
implementation: (
inputs: PluginParams[],
config: C,
mapping?: MappingParams
) => Promise<PluginParams[]>;
metadata?: PluginParametersMetadata;
configValidation?: z.ZodSchema | ConfigValidatorFunction;
inputValidation?: z.ZodSchema | InputValidatorFunction;
allowArithmeticExpressions?: string[];
}

Additional Notes

  • Implement: You should implement implementation function. It should contains the primary logic to generate outputs.
  • Validation: You should define appropriate zod schemas or validation functions for both config and inputs. This ensures that invalid data is caught early and handled appropriately.
  • Arithmetic Expressions: By including configuration, input, and output parameters of the plugin in the allowArithmeticExpressions array, you enable dynamic evaluation of mathematical expressions within parameter values. This eliminates the need for manual pre-calculation and allows basic mathematical operations to be embedded directly within parameter values in manifest files. More details here.
  • Mapping: Ensure your plugin correctly handles the mapping of parameters. This is essential when working with dynamic input and output configurations.

Step 3: Install your plugin

Now your plugin code is written, you can install it to make it available to IF.

npm run build

Then use npm link to create a package that can be installed into IF:

npm link

Step 4: Load your plugin into IF

Now your plugin is ready to run in IF. First install your plugin by navigating to the if project folder and running:

npm link new-plugin

replacing new-plugin with your plugin name as defined in the plugin's package.json. If you are not sure, the name can be checked by running npm ls -g --depth=0 --link=true.

Your plugin is now ready to be run in IF. All that remains is to add your plugin to your manifest file. This means adding it to the initialize block and adding it to the component pipelines where you want your plugin to be executed. For example, an initilize block might look as follows:

initialize:
plugins:
new-plugin:
method: YourFunctionName
path: 'new-plugin'
config:
something: true

Run your manifest uisng

npm run if-run -- --manifest <path-to-manifest>

If you have to link more than one local plugin, for example to test your plugin in a pipeline, you can do so with

npm link new-plugin --save

This will create an entry like "new-plugin": "file:path/to/your/plugin" in the package.json which links to your local plugin. This way, multiple plugins can be linked at once. Of course, these changes should not be committed, but they can be helpful for local testing.

Step 5: Publishing your plugin

Now you have run your plugin locally and you are happy with how it works, you can make it public by publishing it to a public Github repository. Now all you have to do to use it in a manifest file is npm install it and pass the path to the Github repository in the plugin initialize block.

For example, for a plugin saved in github.com/my-repo/new-plugin you can do the following:

npm install https://github.com/my-repo/new-plugin

Then, in your manifest file, provide the path in the plugin instantiation. You also need to specify which function the plugin instantiates. Let's say you are using the Sci plugin from the example above:

name: plugin-demo
description: loads plugin
tags: null
initialize:
plugins:
new-plugin:
method: FunctionName
path: https://github.com/my-repo/new-plugin
tree:
children:
child:
inputs:

Now, when you run the manifest file, it will load the plugin automatically.

You can run this using the globally installed IF as follows:

if-run --manifest <path-to-my-manifest>

Summary of steps

  • Copy our template repository and update package.json
  • Add your plugin code to index.ts
  • Build and link the plugin using npm run build && npm link
  • Load your plugin into if using npm link
  • Initialize your plugin and add it to a pipeline in your manifest file.
  • Publish your plugin to Github

You should also create unit tests for your plugin to demonstrate correct execution and handling of corner cases.

Next steps

You can read our more advanced guide on how to refine your plugins.

Appendix: Walk-through of the Sci plugin

To demonstrate how to build a plugin that conforms to the PluginFactory, let's examine the Sum plugin.

The sum plugin implements the following logic:

  • sum whatever is provided in the input-parameters field from config.
  • append the result to each element in the output array with the name provided as output-parameter in config.

Let's look at how you would implement this from scratch:

The plugin must be a function conforming to PluginFactory.

export const Sum = PluginFactory({
configValidation: z.object({
'input-parameters': z.array(z.string()),
'output-parameter': z.string().min(1),
}),
inputValidation: (input: PluginParams, config: ConfigParams) => {
return validate(validationSchema, inputData);
},
implementation: async (inputs: PluginParams[], config: ConfigParams) => {},
allowArithmeticExpressions: [],
});

Your plugin now has the basic structure required for IF integration. Your next task is to add code to the body of implementation to enable the actual plugin logic to be implemented.

The implementation function should grab the input-parameters (the values to sum) from config. It should then iterate over the inputs array, get the values for each of the input-parameters and append them to the inputs array, using the name from the output-parameter value in config. Here's what this can look like, with the actual calculation pushed to a separate function, calculateSum.

{
implementation: async (inputs: PluginParams[], config: ConfigParams) => {
const {
'input-parameters': inputParameters,
'output-parameter': outputParameter,
} = config;

return inputs.map((input) => {
const calculatedResult = calculateSum(input, inputParameters);

return {
...input,
[outputParameter]: calculatedResult,
};
});
};
}

Now we just need to define what happens in calculateSum - this can be a simple reduce:

/**
* Calculates the sum of the energy components.
*/
const calculateSum = (input: PluginParams, inputParameters: string[]) =>
inputParameters.reduce(
(accumulator, metricToSum) => accumulator + input[metricToSum],
0
);

Note that this example did not include any validation or error handling - you will likely want to add some for a real plugin.

Managing errors

The IF framework provides its own set of error classes, making your task as a plugin builder much simpler! These are available to you in the if-core package that comes bundled with IF. You can import the appropriate error classes and add custom messages. The If Core repository contains the PluginFactory interface, utility functions, and a set of error classes that can be fully integrated with the IF framework. Detailed information on each error class can be found in the Errors Reference.

Now you are ready to run your plugin using the if-run CLI tool!

- + \ No newline at end of file diff --git a/developers/how-to-create-exhaust-script/index.html b/developers/how-to-create-exhaust-script/index.html index c13281d..e12e322 100644 --- a/developers/how-to-create-exhaust-script/index.html +++ b/developers/how-to-create-exhaust-script/index.html @@ -8,14 +8,14 @@ How to create an exhaust script | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to create an exhaust script

The If framework outputs data in yaml format. Any other output formats require a separate script that takes the yaml output data and processes it. We provide if-csv for outputting data in csv format bundled with IF. For any other format, you need to write an exhaust script. This guide will help you create your own exhaust script.

In this example, we'll create a script that executes the manifest and outputs the data in json format.

const IfJson = async () => {
const { manifest, output } = await parseIfCsvArgs();

if (manifest) {
const { rawManifest } = await load(manifest);
const { children } = rawManifest.tree;

if (!(children?.child || children?.['child-0']).outputs) {
throw new ManifestValidationError(FAILURE_MESSAGE_OUTPUTS);
}

// Add logic to export the executed manifest to `json` format.
}

process.exit(0);
};

IfJson().catch(handleError);

To add this script to your package.json, include the following entry in the scripts section:

"scripts": {
"if-json": "npx ts-node if-json.ts"
}

This setup ensures that your script will execute the manifest and output the data in JSON format.

- + \ No newline at end of file diff --git a/developers/how-to-refine-plugins/index.html b/developers/how-to-refine-plugins/index.html index 7d82849..8948d78 100644 --- a/developers/how-to-refine-plugins/index.html +++ b/developers/how-to-refine-plugins/index.html @@ -8,7 +8,7 @@ How to make plugins production ready | Impact Framework - + @@ -16,7 +16,7 @@
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to make plugins production ready

Our How to build plugins guide covered the basics for how to construct an Impact Framework plugin. This guide will help you to refine your plugin to make it production-ready. These are best practice guidelines - if you intend to contribute your plugin to one of our repositories, following these guidelines will help your PR to get merged. Even if you are not aiming to have a plugin merged into one of our repositories, consistency with our norms is useful for debugging and maintaining and for making your plugin as useful as possible for other Impact Framework developers.

1. Naming conventions

We prefer not to use abbreviations of contractions in parameter names. Using fully descriptive names makes the code more readable, which in turn helps reviewers and anyone else aiming to understand how the plugin works. It also helps to avoid ambiguity and naming collisions within and across plugins. Your name should describe what an element does as precisely as practically possible.

For example, we prefer cpu/energy to e-cpu and we prefer functionalUnit to funcUnit, fUnit, or any other abbreviation.

In Typescript code we use lower Camel case (likeThis) for variable and function names and Pascal/Upper Camel case for class, type, enum, and interface names (LikeThis).

For example:

  • sci is the name for the SCI value normalized per second.
  • energy is the name for the array of energy metrics available to be summed in the sci-e plugin

In yaml files, we prefer to use kebab-case (like-this) for field names. For example:

  • network/energy is the field name for the energy consumed by networking for an application
  • functional-unit is the unit in which to express an SCI value.

Global constants can be given capitalized names, such as TIME_UNITS_IN_SECONDS.

2. Plugin code

Imports

We prefer the following ordering of imports in your plugin code:

  1. Node built-in modules (e.g. import fs from 'fs';)
  2. External modules (e.g. import {z} from 'zod';)
  3. Internal modules (e.g. import config from 'src/config';)
  4. Interfaces (e.g. import {PluginInterface} from '@grnsft/if-core/types';)
  5. Types (e.g. import {PluginParams} from '@grnsft/if-core/types';)

Comments

Each logical unit in the code should be preceded by an appropriate explanatory comment. Sometimes it is useful to include short comments inside a function that clarifies the purpose of a particular statement. Here's an example from our codebase:

/**
* Calculates the energy consumption for a single input.
*/
const calculateEnergy = (input: PluginParams) => {
const {
'memory/capacity': totalMemory,
'memory/utilization': memoryUtil,
'energy-per-gb': energyPerGB,
} = input;

// GB * kWh/GB == kWh
return totalMemory * (memoryUtil / 100) * energyPerGB;
};

Error handling

We use custom errors across our codebase to make it as easy as possible to understand the root cause of a problem. You can use our error handlers by importing if-core as a dependency of your plugin. This provides you with our error handling code and predefined list of error classes that you can invoke. This gives you tight integration with IF, because the framework can recognize those error classes and automatically incorporate them into the framework's error handling routines.

Just import ERRORS from if-core and use the error classes that are appropriate for your use-case.

e.g.

import {ERRORS} from '@grnsft/if-core/util';

const {MissingInputDataError} = ERRORS;

...

throw new MissingInputDataError("my-plugin is missing my-parameter from inputs[0]");

Validation

We recommend using inputValidation property from PluginFactory for validation to ensure the integrity of input data. Validate input parameters against expected types, ranges, or constraints to prevent runtime errors and ensure data consistency.

You need to use zod schema or InputValidatorFunction. Here's an example from our codebase:

  • When using function with InputValidatorFunction type.
// `inputValidation` from plugin definition
inputValidation: (input: PluginParams, config: ConfigParams) => {
const inputData = {
'input-parameter': input[config['input-parameter']],
};
const validationSchema = z.record(z.string(), z.number());
validate(validationSchema, inputData);

return input;
};
  • When using zod schema
// `inputValidation` from plugin definition
inputValidation: z.object({
duration: z.number().gt(0),
vCPUs: z.number().gt(0).default(1),
memory: z.number().gt(0).default(16),
ssd: z.number().gte(0).default(0),
hdd: z.number().gte(0).default(0),
gpu: z.number().gte(0).default(0),
'usage-ratio': z.number().gt(0).default(1),
time: z.number().gt(0).optional(),
});

Code Modularity

Break down complex functionality into smaller, manageable methods with well-defined responsibilities. Encapsulate related functionality into private methods to promote code reusability and maintainability.

3. Unit tests

Your plugin should have unit tests with 100% coverage. We use jest to handle unit testing. We strive to have one describe per function. Each possible outcome from each function is separated using it with a precise and descriptive message.

Here's an example that covers plugin initialization and the happy path for the execute() function.

import { ERRORS } from '@grnsft/if-core/utils';

import { Sum } from '../../../if-run/builtins/sum';

const { InputValidationError, WrongArithmeticExpressionError } = ERRORS;

describe('builtins/sum: ', () => {
describe('Sum: ', () => {
const config = {
'input-parameters': ['cpu/energy', 'network/energy', 'memory/energy'],
'output-parameter': 'energy',
};
const parametersMetadata = {};
const sum = Sum(config, parametersMetadata, {});

describe('init: ', () => {
it('successfully initalized.', () => {
expect(sum).toHaveProperty('metadata');
expect(sum).toHaveProperty('execute');
});
});

describe('execute(): ', () => {
it('successfully applies Sum strategy to given input.', async () => {
expect.assertions(1);

const expectedResult = [
{
duration: 3600,
'cpu/energy': 1,
'network/energy': 1,
'memory/energy': 1,
energy: 3,
timestamp: '2021-01-01T00:00:00Z',
},
];

const result = await sum.execute([
{
duration: 3600,
'cpu/energy': 1,
'network/energy': 1,
'memory/energy': 1,
timestamp: '2021-01-01T00:00:00Z',
},
]);

expect(result).toStrictEqual(expectedResult);
});
});
});
});

We have a dedicated page explaining in more detail how to write great unit tests for Impact Framework plugins.

4. Linting

We use ESLint to format our code. We use a very simple configuration file (eslintrc.json), as follows:

{
"extends": "./node_modules/gts/",
"rules": {
"@typescript-eslint/no-explicit-any": ["off"]
}
}

For our repositories we use Github CI to enforce the linting rules for any pull requests.

Summary

On this page, we have outlined best practices for refining your plugins so that they conform to our expected norms. This will help you write clean, efficient, and understandable code!

- + \ No newline at end of file diff --git a/developers/how-to-submit-plugins/index.html b/developers/how-to-submit-plugins/index.html index c7ab216..ebdaf26 100644 --- a/developers/how-to-submit-plugins/index.html +++ b/developers/how-to-submit-plugins/index.html @@ -8,13 +8,13 @@ How to submit plugins | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to submit plugins

Once you have built a plugin and made it production ready you probably want to share it with the world!

We provide an IF Explorer website where you can list your plugins for the world to see.

By submitting your plugins to the Explorer you make them discoverable to other IF users. Users can browse or search the listed plugins and install them in their own pipelines. This is a great way to get exposure for your work and help grow the IF ecosystem.

Submitting a plugin

When you are happy with your plugin code, you can simply head to our submission portal and fill in the form. As long as you provide the requested information and your README is sufficiently detailed, your plugin will get listed on the site.

It's as easy as filling in the form - we handle the rest!

Acceptance criteria

You should fill in alll the fields on the submission form in order to list your plugin (it is ok to leave the npm field blank if you did not yet publish your plugin to npm).

However, we do want to see some specific information provided in your README.

Your plugin documentation should include the following information:

  • A list of the required and optional parameters and return values, with types and any valid ranges/properties clearly defined (REQUIRED).
  • A list of required and optional configuration (REQUIRED)
  • A description of any environment setup such as credentials in environment variables, etc. (REQUIRED)
  • Installation instructions (REQUIRED)
  • A written description of the plugin behaviour (REQUIRED)
  • A demo manifest that executes the plugin correctly without errors (REQUIRED)
  • A link to your unit tests
  • A list of errors that your plugin can raise, the behaviours that cause them and potential remedies.
  • A reference list of any publications or other material supporting the approach you have taken in your plugin.

The more comprehensive the documentation, the more likely users are to download and use your plugin.

Disclaimer

PLEASE BE ADVISED THAT IF YOU RUN CODE INCLUDED IN THESE PLUGINS OR USE INFORMATION GENERATED BY THESE PLUGINS, INCLUDING MAKING ANY DECISIONS BASED UPON THE INFORMATION, YOU DO SO AT YOUR OWN RISK. GREEN SOFTWARE FOUNDATION MAKES NO REPRESENTATION, GUARANTEE OR WARRANTY, EXPRESS OR IMPLIED, STATUTORY OR OTHERWISE, INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE NOR ANY WARRANTIES ARISING FROM COURSE OF PERFORMANCE, COURSE OF DEALING OR USAGE IN TRADE.

We are only doing minimal QA on plugins. This amounts to checking that the necessary information is provided in a submission form and README. This means we are not checking that plugins execute correctly, provide accurate results or that they are free from security vulnerabilities. You must do your own research and do your own due diligence for any use of plugins listed on the explorer.

- + \ No newline at end of file diff --git a/developers/how-to-write-unit-tests/index.html b/developers/how-to-write-unit-tests/index.html index a13b057..5d65115 100644 --- a/developers/how-to-write-unit-tests/index.html +++ b/developers/how-to-write-unit-tests/index.html @@ -8,14 +8,14 @@ How to write unit tests | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to write unit tests

Impact Framework unit tests follow a standard format. We use the jest testing library. You can run all our existing tests by opening the project directory and running npm test. This page explains how you can add new unit tests for your plugins (or add some for our plugins if you notice a gap).

Test files

The IF includes a __test__ directory. Inside, you will find subdirectory if-run/builtins containing test files for each plugin. Your plugin repository should also follow this structure. Inside the builtins you can add plugin.test.ts. This is where you write your unit tests. For example, here's the directory tree for our sum test file:


if
|
|- src
|
|-__tests__
|
|-if-run
|
|-builtins
|
sum.test.ts

Setting up your test file

You will need to import your plugin so that it can be instantiated and tested. You will also need some elements from jest/globals: For example, these are the imports for our Sum plugin.

import { ERRORS } from '@grnsft/if-core/utils';

import { Sum } from '../../../if-run/builtins/sum';

const { InputValidationError } = ERRORS;

You may require other imports for your specific set of tests.

Describe

Each method should have its own dedicated describe block.

Your unit tests should have at least two describe blocks, one to test the plugin initialization and one for execute.

describe('init', () => {});
describe('execute', () => {});

For example, here is a describe block checking that the Sum plugin initializes correctly:

describe('builtins/sum: ', () => {
describe('Sum: ', () => {
const config = {
'input-parameters': ['cpu/energy', 'network/energy', 'memory/energy'],
'output-parameter': 'energy',
};
const sum = Sum(config);

describe('init: ', () => {
it('successfully initalized.', () => {
expect(sum).toHaveProperty('metadata');
expect(sum).toHaveProperty('execute');
});
});
});
});

It

Within each describe block, each effect to be tested should have a dedicated it block.

Here's an example of a new describe block for the execute() method on the Sum plugin. The describe block indicates that we are testing effects of the execute() method. it is specific to a single outcome - in this case there are two it blocks that test that the plugin returns a specific result in the happy path and throws an exception if the user has provided invalid config data, specifically that the user-provided cpu/energy parameter is missing:

describe('execute(): ', () => {
it('successfully applies Sum strategy to given input.', async () => {
expect.assertions(1);

const expectedResult = [
{
duration: 3600,
'cpu/energy': 1,
'network/energy': 1,
'memory/energy': 1,
energy: 3,
timestamp: '2021-01-01T00:00:00Z',
},
];

const result = await sum.execute([
{
duration: 3600,
'cpu/energy': 1,
'network/energy': 1,
'memory/energy': 1,
timestamp: '2021-01-01T00:00:00Z',
},
]);

expect(result).toStrictEqual(expectedResult);
});

it('throws an error on missing params in input.', async () => {
const expectedMessage = 'Sum: cpu/energy is missing from the input array.';

expect.assertions(1);

try {
await sum.execute([
{
duration: 3600,
timestamp: '2021-01-01T00:00:00Z',
},
]);
} catch (error) {
expect(error).toStrictEqual(new InputValidationError(expectedMessage));
}
});
});

Errors

We prefer to use expect to check the errors returned from a test. We do this by writing expect in a catch block. Here's an example from our sci plugin tests:

it('throws an exception on missing functional unit data.', async () => {
const inputs = [
{
timestamp: '2021-01-01T00:00:00Z',
'operational-carbon': 0.002,
'embodied-carbon': 0.0005,
'functional-unit': 'requests',
duration: 1,
},
];
expect.assertions(1);

try {
await sciModel.execute(inputs);
} catch (error) {
expect(error).toBeInstanceOf(InputValidationError);
}
});

It is also necessary to include expect.assertions(n) for testing asynchronous code, where n is the number of assertiosn that should be tested before the test completes.

Mocks

Please try to avoid mocking data if possible. However, if it is necessary to mock (e.g. if your plugin relies on a third party credentialed API) then please make your mock data as realistic as possible (no foo, bar, baz style mock data, please).

We do have mock backends in several of our tests, and we also have a mock data generator plugin that can create realistic dummy data to your specific requirements.

Coverage

Please use jest --coverage to see a coverage report for your plugin - your unit tests should yield 100% coverage. The snippet below shows what to expect from the coverage report:

------------------------------------|---------|----------|---------|---------|-------------------
| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
| --------------------------------- | ------- | -------- | ------- | ------- | ----------------- |
| All files | 100| 100 | 100 | 100 |
| if-run/builtins/coefficient | 100 | 100 | 100 | 100 |
| index.ts | 100 | 100 | 100 | 100 |
| if-run/builtins/copy-param | 100 | 100 | 100 | 100 |
| index.ts | 100 | 100 | 100 | 100 |
| if-run/builtins/csv-lookup | 100 | 100 | 100 | 100 |
| index.ts | 100 | 100 | 100 | 100 |
| if-run/builtins/divide | 100 | 94.11 | 100 | 100 |
- + \ No newline at end of file diff --git a/developers/index.html b/developers/index.html index 8b322ff..a0fc2ac 100644 --- a/developers/index.html +++ b/developers/index.html @@ -8,13 +8,13 @@ Developers | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Developers

This section contains information for Impact Framework developers. You are a developer if you want to change or update the Impact Framework by adding new features, fixing bugs or building new plugins.

The developer documentation includes:

If you are looking for guidance for how to use IF to measure the environmental impact of your apps, you should go to our user documentation instead.

- + \ No newline at end of file diff --git a/index.html b/index.html index f492cdd..11cd968 100644 --- a/index.html +++ b/index.html @@ -8,13 +8,13 @@ Welcome to Impact Framework | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Impact Framework

The solution to opaque carbon reporting

Transform Observations into Impacts

Take easily observable metrics like CPU utilization, page views and installs and convert them into environmental impacts such as carbon emissions, water usage, energy consumption, and air quality.

Buildable Plugin Ecosystem

Engage an existing library of plugins or create new plugins within Impact Framework based on your assumptions and observations.

Explore What-If Scenarios

Uncover the environmental impact of your software changes in real-time by examining how your software’s environmental performance changes if your application moves to the cloud or experiences shifts in its runtime.

Decentralize Data

Record your observations, chosen plugins, configurations, and computed environmental impacts in a manifest file. Open the door for others to understand, verify, and challenge the entire process.

Democratize Measurement

Empower others to rerun your manifest file, validate your findings, or question your assumptions. They can tweak configurations, select different plugins, and run the analysis themselves.

- + \ No newline at end of file diff --git a/intro/index.html b/intro/index.html index 4bd3ab0..37cd0f4 100644 --- a/intro/index.html +++ b/intro/index.html @@ -8,13 +8,13 @@ Introduction | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Introduction


Impact Framework

Impact Framework (IF) aims to make the environmental impacts of software easier to calculate and share.

IF allows you to calculate the environmental impacts, such as carbon, of your software applications without writing any code. All you have to do is write a simple manifest file and IF handles the rest.

The project is entirely open source and composability is a core design principle - we want you to be able to create your own plugins and plug them in to our framework, or pick from a broad universe of open source plugins created by others.

Motivation

If you can't measure, you can't improve. Software has many negative environmental impacts which we need to optimize, carbon, water, and energy, to name just a few.

Unfortunately, measuring software impact metrics like carbon, water, and energy is complex and nuanced.

Modern applications are composed of many smaller pieces of software (components) running on different environments, for example, private cloud, public cloud, bare-metal, virtualized, containerized, mobile, laptops, desktops, embedded, and IoT. Many components that make up a typical software application are run on something other than resources you own or control, which makes including the impact of managed services in your measurement especially hard.

The impacts of software components also vary over time, so as well as understanding which components contribute most to the overall impacts, there is also a question of when they contribute the most.

Only through a granular analysis of the impacts of your software system can investments in reducing its impact be prioritized and verified. Measurement is the first and most crucial step in greening a software system, and the first step in that process with the Impact Framework is to create a tree.

Background

This project has evolved over the two years of the GSF's existence.

During the development of the SCI, we acknowledged that the biggest blocker to adoption was data regarding the emissions of software components on different platforms and runtimes.

We then launched the sci-data project to help create the data sets required to calculate an SCI score.

After some investigation, the original sci-data team quickly realized that there were several existing data sources, and many more were in development, free open source or private commercial. The future challenge wouldn't be to source them, it would be knowing which data set to use for which use case, how data sets differed in their methodology and interface and when to use one over the other, the pros/cons, and trade-offs.

The project evolved into the sci-guide to document existing data sets, providing guidance for when to use one over another and how to use it to create your own software measurement reports.

Finally, we had enough information, and SCI case studies started to be written. This was a milestone moment.

But now we are in the next evolution, to have software measurement be a mainstream activity. For this to be an industry with thousands of professionals working to decarbonize software, for businesses to grow and thrive in a commercial software measurement ecosystem, we need to formalize software measurement into a discipline with standards and tooling. The SCI Specification is the standard, and the Impact Framework is the tooling.

Project Structure

The IF source code can be found in the IF Github repository. The code there covers the framework, which includes all the infrastructure for reading and writing input and output yamls, invoking plugins, running the command line tool and associated helper functions. However, it does not include the actual plugins themselves. Part of the IF design philosophy is that plugins should aim to do one thing as simply as possible, so that the IF is as composable and configurable as possible. Therefore, to use IF, you have to either create your own plugins or find some prebuilt ones and install them yourself. This also implies that you take responsibility for the plugins you choose to install.

We do provide a standard library of plugins built and maintained by the IF core team. These come bundled with IF. Their source code and README documentation and can be found in if/src/builtins.

There are also a wide range of community-owned plugins that we make discoverable on our Explorer website

Finally, the source code for this documentation website is available at the if-docs Github repository.

The lefthand sidebar contains links to all the information you need to understand Impact Framework.

You can explore the key ideas underpinning Impact Framework in the Major Concepts section.

Users can read our guides explaining how to use IF, including installation, using the CLI and loading plugins.

We also have developer documentation to help you get started building with IF.

You will find documentation for the individual built-in plugin implementations in plugins.

- + \ No newline at end of file diff --git a/major-concepts/aggregation/index.html b/major-concepts/aggregation/index.html index 4275973..6cc26a3 100644 --- a/major-concepts/aggregation/index.html +++ b/major-concepts/aggregation/index.html @@ -8,13 +8,13 @@ Aggregation | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Aggregation

Aggregation is the process of summarizing a set of metrics.

Two types of aggregation can be executed in IF. The first takes a time series and condenses it down into a single number representing an entire observation period, for example, if your time series contains three timesteps, the value of the metric being aggregated is 1 in each timestep and the aggregate is being computed as a sum, the aggregated value for the whole observation period is 3. We refer to this as "time series aggregation" or "horizontal aggregation".

The second type of aggregation happens across components in a tree. Where time series exist for multiple child nodes under a parent, their time series are aggregated together into one summary time series that is pushed to the parent node. For example, in the following tree, each child has a time series with three timesteps. At each timestep, the metric value is 1 for both children:

         parent
/ \
/ \
child-1 child-2
data: [1,1,1] data: [1,1,1]

Assuming the aggregation method is sum, the parent would receive an aggregated time series [2,2,2], representing the aggregated values from both children. We refer to this as "tree aggregation" or "vertical aggregation".

Configuration

Aggregate is a built-in feature of the IF. This means you do not need to initialize it along with the plugins you are using in your pipeline. All you need to do is to add a small piece of config to your manifest file.

The aggregate config looks as follows:

aggregation:
metrics:
- 'carbon'
- 'energy'
type: 'both'

There are two fields: metrics and type.

metrics is an array of metrics that you want to aggregate. You can provide any value here, but they must match a key that exists in your output data (i.e. if you tell IF to aggregate carbon but carbon is not in your outputs you will receive an error message and aggregation will fail). You can provide any number of metrics. In the example above, the aggregation feature will operate on the carbon and energy values.

type determines which kind of aggregation you want to perform. The choices are time (previously horizontal: time-series aggregation only), component (previously vertical: tree aggregation only) or both (both kinds of aggregation will be performed). In the example above, both types of aggregation will be performed over the two selected metrics.

Aggregation methods

Aggregation can happen in several ways. In the example above, both of the selected metrics are absolute values measured in gCO2eq (carbon) and kWh (energy). To aggregate, it makes sense to add the values in each timestep for time-series aggregation and element-wise across components for tree aggregation. However, summing the values is not always the right way to aggregate.

For example, some values are constants that apply to every timestep - these should simply be copied into the aggregated metric and not summed. For example, if you are using a hard-coded value for grid carbon intensity that applies globally, then you simply want to persist that value into the aggregate - adding them together would provide a misleading result.

Similarly, some values are proportions or percentages. In these cases, the right way to aggregate is usually to take an average rather than summing over a set of values.

Aggregation outputs

The aggregation process adds new output data to your manifest file. The two types of aggregation add different outputs. The horizontal (time-series) aggregation adds a new field called aggregated to each node whose time series has been aggregated. In the aggregated block, you will find the aggregated value for each of the aggregation metrics defined in the aggregation config.

The vertical aggregation adds a new array of output observations. These are simply named outputs and they always contain a timestamp and duration along with the aggregated metrics for each timestep.

The example below shows the result of running both kinds of aggregation for a single component:

   "outputs": [
{
"carbon": 0.04846481793320214,
"energy": 0.00030285447154471535,
"timestamp": "2023-12-12T00:00:00.000Z"
},
{
"carbon": 0.037777724630840566,
"energy": 0.00023606013840495548,
"timestamp": "2023-12-12T00:00:05.000Z"
},
{
"carbon": 0.03630388278027921,
"energy": 0.000226848626838947,
"timestamp": "2023-12-12T00:00:10.000Z"
},
{
"carbon": 0.0360935970659935,
"energy": 0.0002255343411246613,
"timestamp": "2023-12-12T00:00:15.000Z"
},
{
"carbon": 0.0360935970659935,
"energy": 0.0002255343411246613,
"timestamp": "2023-12-12T00:00:20.000Z"
},
{
"carbon": 0.0360935970659935,
"energy": 0.0002255343411246613,
"timestamp": "2023-12-12T00:00:25.000Z"
},
{
"carbon": 0.0360935970659935,
"energy": 0.0002255343411246613,
"timestamp": "2023-12-12T00:00:30.000Z"
},
{
"carbon": 0.0360935970659935,
"energy": 0.0002255343411246613,
"timestamp": "2023-12-12T00:00:35.000Z"
},
],
"aggregated": {
"carbon": 0.3246705689138855,
"energy": 0.0020287555470867216
}
- + \ No newline at end of file diff --git a/major-concepts/design-philosophy/index.html b/major-concepts/design-philosophy/index.html index 332bfa6..c288eef 100644 --- a/major-concepts/design-philosophy/index.html +++ b/major-concepts/design-philosophy/index.html @@ -8,13 +8,13 @@ Design philosophy | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Design philosophy

Transparency

The manifest file is the lifeblood of Impact Framework. It defines all the context for an environmental impact calculation, defining the architecture of an application, the observation period, the pipeline of calculations and transformations to execute, and the environmental impacts to track. This document can then be executed to generate impact values. This gives unparalleled transparency to an environmental audit, all in a standard, easy to read format that anyone can read or re-execute.

Verifiability

An Impact Framework manifest file is powerful because anyone can re-execute it and verify an organization's impact calculation. You can even experiment by swapping out different plugins. The critical concept is that everything you need to calculate an impact is provided in the manifest file and anyone can re-run a calculation with the manifest file and the lightweight Impact Framework command line tool.

Flexibility

We aim to bake the minimum of constraints into the Imapct Framework, balancing the helpers and standards that make plugins interoperable and consistent against freedom of expression and creativity. The real power of Impact Framework comes from the community. This includes the community of experts contributing to the design decisions and standards baked into the protocol and the community of plugin developers experimenting at the margins, and the organizations using Impact Framework to measure, report and mitigate their environmental imapct.

Impact Framework can be a tool for transparent, verifiable environmental impact audits, but it can also be a platform for experimentation. Your manifest file is a foundation for forecasting into the future or exploring where you can tweak your stack to most effectively minimize your impact. Impact Framework can be a tool for research, hypothesis testing, R&D and business decision making as well as environemntal reporting. To realize this vision, we know we have to make Impact Framework as flexible as possible, imposing the absolute minimum of constraints in the underlying protocol, while also providing the necessary functionality and safeguards our users require.

Modularity

Impact Framework is the minimal set of features that enable a manifest file to be processed according to some agreed principles. We provide a tool for processing manifest files and a set of standards and norms. This allows builders to create plugins that do some specific task, such as grabbing data from a particular cloud provider, or applying some calculation over some particular data.

Anyone can build a plugin and share them with the world, meaning Impact Framework development can be bottom-up and community driven. It also means that if you are not satisfied with how some calculation was done, you can easily fork it and replace it with your own.

What we provide is a minimal set of rules and guardrails for plugin builders to conform to to ensure compatibility with Impact Framework.

Neutrality

Impact Framework aims to support maximally decentralized plugin development. We want anyone to be able to build plugins and use them to calculate their environmental impacts. We do not want to gatekeep what people can measure and monitor - we want to encourage people to build freely and experiment on Impact Framework rails!

At the same time, we want to provide the helpers and guardrails that make impact calculations as friction free as possible. This means we focus on providing the minimal protocol required to support community plugin development and make it as safe as possible from unit errors and other footguns.

We want to see the universe of Impact Framework plugins grow organically and permissionlessly in ways we can't even imagine today!

To this end, what we are really building is a protocol. Impact Framework is just a Typescript implementation of the protocol. The protocol itself is a set of fundamental principles that define how a manifest file should be processed, such that any implementation in any language will yield the same result from a given manifest file.

The Impact Protocol is the result of countless discussions, experiments, conversations with industry partners, academics, researchers and developers, and represents a community consensus for how certain actions should be executed, for example, how should a series of observations be aggregated, what standard units should be used, how should a manifest file be structured, etc.

This means we can be neutral about what can be built with IF while also providing a set of canonical processes and standards.

Where to go next

This page has outlined the design philosophies that guide Impact Framework development.

Explore the other pages in this section to see how these principles have been applied to specific Impact Framework features, or head to our user documentation to get started running Impact Framework for yourself.

- + \ No newline at end of file diff --git a/major-concepts/exhaust-script/index.html b/major-concepts/exhaust-script/index.html index af826e6..f74684d 100644 --- a/major-concepts/exhaust-script/index.html +++ b/major-concepts/exhaust-script/index.html @@ -8,13 +8,13 @@ Exhaust scripts | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Exhaust scripts

Exhaust scripts are scripts that can run independently of IF itself that take an executed manifest file (one with outputs) as an input, parse the yaml data and reformat it into some other representation. We provide if-csv bundled with IF, but if you want other data formats, you'll have to create an exhaust script yourself.

if-run

if-run isn't really an exhaust script, because it also grabs input data, regroups data, computes the pipeline and aggregates. However, we're mentioning it here because it does have some built-in exhaust functionality. Specifically, if-run outputs yaml data. if-run can only output yaml data. This yaml data can be dumped to the console or saved to a yaml file.

How to Use if-run

To use if-run, you need to provide a manifest file in yaml or yml foramt. The output will be in yaml format if you specify the output file path.

Here's a simple manifest file example. This manifest sums two components, cpu/energy and network/energy and assigns the result to energy in the outputs array.

name: sum
description: successful path
tags:
initialize:
plugins:
sum:
method: Sum
path: 'builtin'
config:
input-parameters: ['cpu/energy', 'network/energy']
output-parameter: 'energy'
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- sum
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001

To execute this manifest with if-run, use the following command:

if-run -m sum.yaml -o output-sum

You will get the executed manifest in the output-sum.yaml file.

if-csv

The if-csv script allows users to pass in yaml and yml files created using if-run and save the output in csv format. Yopu have to define the parameters you want to export from the yaml file, e.g. energy or carbon.

For the above example, you can get the following result:

Path,2023-08-06T00:00
tree.children.child.energy,0.002

by running:

if-csv -m sum.yaml -p energy -o output-sum

This command specifies the manifest file (sum.yaml), the parameter to export (energy), and the output file path (output-sum).

- + \ No newline at end of file diff --git a/major-concepts/if/index.html b/major-concepts/if/index.html index 74d0af5..1d6fcb0 100644 --- a/major-concepts/if/index.html +++ b/major-concepts/if/index.html @@ -8,14 +8,14 @@ Impact Engine (CLI) | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Impact Engine (CLI)

Introduction

if-run is a command line tool that computes Manifest files. It is the portal allowing users to interact with the Impact Framework.

The available options and their shortcuts are:

  • --manifest or -m: path to an input manifest file
  • --output or -o (optional): path to the output file where the results as saved
  • --no-output or -n (optional): suppress the output to console
  • --help or -h: prints out help instruction
  • --debug: enables IF execution logs
  • --append: allows you to rerun an already-computed manifest and append new values to the existing data.

The only required command is --manifest. Without a valid path to a manifest file, if-run has nothing to execute.

To use if-run, you must first write a manifest file. Then, you can simply pass the path to the manifest file to if-run on the command line.

if-run --manifest /my-manifest.yml
## or using aliases
if-run -m /my-manifest.yml

You can also pass a path where you would like to save the output file to. For example:

if-run --manifest ./my-manifest.yml --output ./my-results.yml
## or using aliases
if-run -m ./my-manifest.yml -o ./my-results.yml

If you omit the --output command, your results will only be displayed in the console.

For more information on the if-run commands see the CLI reference documentation.

Phased execution

To enable greener and more flexible use of IF, we separate the manifest execution into distinct phases: observe, regroup and compute. This is invisible to you when you run if-run but behind the scenes all three of these phases are being run. However, you can instruct IF to run these phases individually, to avoid recomputing parts of the manifest unnecessarily. To do this, you simply pass --observe, --regroup, and --compute flags to IF in the combination you need. For example, to run only the observe phase (to generate input data):

if-run -m <manifest> --observe

to run the compute phase on its own:

if-run -m <manifest> --compute

To run the observe and compute phases without regrouping:

if-run -m <mnaifest> --observe --compute
- + \ No newline at end of file diff --git a/major-concepts/index.html b/major-concepts/index.html index cdbfc18..354b41c 100644 --- a/major-concepts/index.html +++ b/major-concepts/index.html @@ -8,13 +8,13 @@ Major Concepts | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.
- + \ No newline at end of file diff --git a/major-concepts/manifest-file/index.html b/major-concepts/manifest-file/index.html index b73071a..eb42d52 100644 --- a/major-concepts/manifest-file/index.html +++ b/major-concepts/manifest-file/index.html @@ -8,14 +8,14 @@ Manifest File | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Manifest File

Manifest files are fundamental to Impact Framework and they serve multiple important purposes, including:

  • They contain all the necessary configurations for Impact Framework
  • They define your application architecture
  • They hold your input data
  • They are shareable, portable and human-readable
  • They can be used as verifiable audits form your application

The manifest is a yaml file with a particular structure. -It can be thought of as an executable audit because the file itself can be shared with others and re-executed to verify your environmental impact calculations.

It is a formal report detailing not just the end impact but all the assumptions, inputs, and plugins used in calculating the impact.

This is possible because all the configuration and data required to run Impact Framework is contained in the manifest file.

Anyone can download Impact Framework and execute a manifest file to verify the results.

Structure of a manifest file

Overview

Manifest files can be simple or very intricate, depending on the plugin pipeline you want to use and the complexity of your application. However, all manifest files conform to a basic structure that looks as follows:

name:
description:
tags:
initialize:
plugins:
<PLUGIN-NAME-HERE>:
method:
path:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
defaults:
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600

Everything above the tree is collectively referred to as the context. The tree contains the input data and is structured according to the architecture of the application being examined, with individual components being nodes in the tree. Individual components can be grouped under parent nodes.

Context

Metadata

The global metadata includes the name, description, and tags that can be used to describe the nature of the manifest file. For example, you might name the file Carbon Jan 2024 or similar. A short description might briefly outline the scope of the manifest file, e.g. company x's carbon emissions due to web serves from Jab 24 - July 24. Tags can be used to group manifest files (we do not explicitly use this field for anything currently).

Initialize

The initialize section is where you define which plugins will be used in your manifest file and provide the configuration for them. Below is sample for initialization:

initialize:
plugins:
<PLUGIN-NAME-HERE>:
method: <METHOD-NAME-HERE>

Where required values are:

  • method: the name of the function exported by the plugin.
  • path: the path to the plugin code. For example, for a plugin from our standard library, this value would be builtin

There is also an optional config field that can be used to set config that is common to a plugin wherever it is invoked across the entire manifest file.

Impact Framework uses the initialize section to instantiate each plugin. A plugin cannot be invoked elsewhere in the manifest file unless it is included in this section.

There is also the option to provide a mapping to the plugin in the initialize block. Its purpose is to rename the arguments expected or returned from the plugin as part of the plugin's execution, avoiding the need to use additional plugins to rename parameters.

For example, your plugin might expect cpu/energy and your input data has the parameter cpu-energy returned from another plugin. Instead of using an additional plugin to rename the parameter and add a new one, you can use mapping to:

a) rename the output from the first plugin so that cpu/energy is returned instead of the default cpu-energy

b) instruct the second plugin to accept cpu-energy instead of the default cpu/energy

e.g.

initialize:
plugins:
sci:
kind: plugin
method: Sci
path: 'builtin'
config:
functional-unit: requests
mapping:
sci: if-sci

In the outputs, the sci value returned by the Sci plugin will be named if-sci.

You can also add information to the plugin's initialize section about parameter metadata if you wish to add or override the metadata hardcoded into the plugin. This is what will be reported by the explainer feature if you enable it. E.g.

plugins:
sum-carbon:
path: 'builtin'
method: Sum
config:
input-parameters:
- carbon-operational
- embodied-carbon
output-parameter: carbon
parameter-metadata:
inputs:
carbon-operational:
description: "carbon emitted due to an application's execution"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
embodied-carbon:
description: "carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum

Execution (auto-generated)

This section is auto generated by IF at runtime. You don't have to include this section in your manifest. The execution node contains all the necessary information to rebuild the environment, which can support debugging or verifying output files.

execution:
status: success
command: if-run --manifest examples/basic.yml
environment:
if-version: v0.3.2
os: ubuntu
os-version: 22.04.6
node-version: v21.4.0
date-time: 2023-12-12T00:00:00.000Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- ...
error: 'InputValidationError: "duration" parameter is required. Error code: invalid_type'. ## appears when execution failed
  • status: execution state: success (indicating that IF successfully executed this manifest) or fail (indicating that IF encountered a problem that halted execution).
  • command: exact command which was used to run the framework to execute this manifest (it may include full path to tools in place of aliases such as run)
  • environment: information about the environment the manifest was executed in, including the local operating system, Node.js version, time, and dependencies.
  • error: this field only appears if execution failed. The error message returned by IF is captured here.

Explain

This section is autogenerated at runtime. It is a list of all the parameter metadata that IF can scrape from your plugin instances. It looks as follows:

explain:
carbon:
plugins:
- sci
unit: gCO2eq
description: >-
total carbon emissions attributed to an application's usage as the sum
of embodied and operational carbon
aggregation-method:
time: sum
component: sum
requests:
plugins:
- sci
unit: requests
description: number of requests made to application in the given timestep
aggregation-method:
time: sum
component: sum
sci:
plugins:
- sci
unit: gCO2eq/request
description: >-
software carbon intensity expressed as a rate of carbon emission per
request
aggregation-method:
time: sum
component: sum

Tree

The tree section of a manifest file defines the topology of all the components being measured. The shape of the tree defines the grouping of components. It describes the architecture of the application being studied and contains all the usage observations for each component. The tree has individual components such as leaves, intermediate nodes representing groupings, and the top level is the root.

For example, a web application could be organized as follows:

tree:
children:
front-end:
children:
build-pipeline:
children:
vercel:
github-pages:
backend-database:
children:
server1:
server2:
server3:
front-end:
networking:

This example has a relatively straightforward structure with a maximum of 3 levels of nesting. You can continue to nest components to any depth.

Each component has some configuration, some input data, and a plugin pipeline.

  • pipeline: a list of plugins that should be executed for a specific component. This is broken down into three subsections representing distinct phases of execution that can be triggered independently using command line flags. These subsections are:
    • observe: the plugins that generate input data
    • regroup: configuration for regrouping input data by given keys
    • compute: the plugins that operate over input data and generate output data
  • defaults: fallback values that IF defaults to if they are not present in an input observation.
  • inputs: an array of observation data, with each observation containing usage data for a given timestep.

If a component does not include its own pipeline or default values, they are inherited from the closest parent.

Here's an example of a moderately complex tree:

tree:
children:
child-0:
children:
child-0-1:
pipeline:
observe:
regroup:
compute:
- sum
defaults: null
inputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
outputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
energy: 0.000811
child-0-2:
children:
child-0-2-1:
pipeline:
observe:
regroup:
compute:
- sum
defaults: null
inputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
outputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
energy: 0.000811

Defaults

Defaults are fallback values that are only used if a given value is missing in the inputs array. For example, if you have a value that could feasibly be missing in a given timestep, perhaps because your plugin relies on a third party API that can fail, you can provide a value in defaults that can be used as a fallback value.

The values in defaults are applied to every timestep where the given value is missing. This means that as well as acting as a fallback defaults can be used as a convenience tool for efficiently adding a constant value to every timestep in your inputs array.

Inputs

Every component includes an inputs field that gets read into plugins as an array. inputs are divided into observations, each having a timestamp and a duration. Every observation refers to an element in inputs representing some snapshot in time.

Each plugin takes the inputs array and applies some calculation or transformation to each observation in the array.

Observations can include any type of data, including human judgment, assumptions, other plugins, APIs, survey data or telemetry.

The separation of timestamps in the inputs array determines the temporal granularity of your impact calculations. The more frequent your observations, the more accurate your impact assessment.

Creating input data

The plugins in the observe part of the pipeline generate input data. The manifest file should not have input data when the observe phase is executed. Plugins in this phase only generate input data, they can never generate output data. If you run the observe phase on its own (by running if-run --observe) then your manifest will be returned populated with input data according to the plugins you included in your observe pipeline.

Regrouping a manifest file

The second phase of manifest execution is regroup. This reorganizes existing input data into a new structure using keys provided in the regroup config in the manifest. For example, a manifest with the following tree:

tree:
children:
my-app:
pipeline:
observe:
regroup:
- cloud/instance-type
- cloud/region
compute:
children:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 99
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 12
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 1
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 9
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 12
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 1

generates the following output when if-run --regroup is executed:

tree:
children:
my-app:
pipeline:
observe: null
regroup:
- cloud/instance-type
- cloud/region
compute: null
children:
A1:
children:
uk-west:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 99
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 12
uk-east:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 9
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 12
B1:
children:
uk-west:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 1
uk-east:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 1

Computing a manifest file

Impact Framework computes manifest files. For each component in the tree, the inputs array is passed to each plugin in the compute pipeline in sequence.

In order for the compute phase to execute correctly, the manifest needs to have input data available.

Each plugin enriches the inputs array in some specific way, typically by adding a new key-value pair to each observation in the array. For example, the teads-curve plugin takes in CPU utilization expressed as a percentage as an input and appends cpu/energy expressed in kWh. cpu/energy is then available to be passed as an input to, for example, the sci-e plugin.

This implies a sequence of plugins where the inputs for some plugins must either be present in the original manifest file or be outputs of the preceding plugins in the pipeline.

There are also plugins and built-in features that can synchronize time series of observations across an entire tree and aggregate data across time or across components.

Running combinations of phases

It is possible to run each phase of the execution individually, or together. You can choose to only run the observe, regroup or compute phases of the manifest execution. This saves you from having to re-execute entire manifests every time you want to tweak something, making it a greener way to use IF.

if-run executes all the phases together, including observe, regroup and compute. It generates yaml output data. However, you can run individual phases by passing --observe, --regroup or --compute flags on the command line. For example, to run only the compute phase:

if-run -m <manifest> --compute

Maybe you only want to generate a static file that contains input data but don't want to run the full compute pipeline right now. You can run with --observe:

if-run <manifest> --observe

You can also combine the command, e.g. if you have a file with inputs and you want to run regroup and compute but not observe:

if-run -m <manifest> --regroup --compute

Outputs

When Impact Framework computes a manifest file, it appends new data to the manifest file and the final result is an enriched manifest that includes all the configuration and contextual data, the input data, and the results of executing each plugin. This means the output file is completely auditable - the original manifest file can be recovered simply by deleting the outputs and execution sections of the output file.

IF generates yaml output data. Any other output formats have to be generated by separate exhaust scripts that take IF's yaml output as their input.

Here's an example output file:

name: sum
description: successful path
tags: null
initialize:
plugins:
sum:
path: builtin
method: Sum
config:
input-parameters:
- cpu/energy
- network/energy
output-parameter: energy
execution:
command: >-
/home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/user/Code/if/src/index.ts -m
/home/user/Code/if/manifests/plugins/sum/success.yml
environment:
if-version: 0.3.3-beta.0
os: linux
os-version: 5.15.0-105-generic
node-version: 21.4.0
date-time: 2024-05-31T09:18:48.895Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.22.4
status: success
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- sum
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
outputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
energy: 0.002
- +It can be thought of as an executable audit because the file itself can be shared with others and re-executed to verify your environmental impact calculations.

It is a formal report detailing not just the end impact but all the assumptions, inputs, and plugins used in calculating the impact.

This is possible because all the configuration and data required to run Impact Framework is contained in the manifest file.

Anyone can download Impact Framework and execute a manifest file to verify the results.

Structure of a manifest file

Overview

Manifest files can be simple or very intricate, depending on the plugin pipeline you want to use and the complexity of your application. However, all manifest files conform to a basic structure that looks as follows:

name:
description:
tags:
initialize:
plugins:
<PLUGIN-NAME-HERE>:
method:
path:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
defaults:
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600

Everything above the tree is collectively referred to as the context. The tree contains the input data and is structured according to the architecture of the application being examined, with individual components being nodes in the tree. Individual components can be grouped under parent nodes.

Context

Metadata

The global metadata includes the name, description, and tags that can be used to describe the nature of the manifest file. For example, you might name the file Carbon Jan 2024 or similar. A short description might briefly outline the scope of the manifest file, e.g. company x's carbon emissions due to web serves from Jab 24 - July 24. Tags can be used to group manifest files (we do not explicitly use this field for anything currently).

Initialize

The initialize section is where you define which plugins will be used in your manifest file and provide the configuration for them. Below is sample for initialization:

initialize:
plugins:
<PLUGIN-NAME-HERE>:
method: <METHOD-NAME-HERE>

Where required values are:

  • method: the name of the function exported by the plugin.
  • path: the path to the plugin code. For example, for a plugin from our standard library, this value would be builtin

There is also an optional config field that can be used to set config that is common to a plugin wherever it is invoked across the entire manifest file.

Impact Framework uses the initialize section to instantiate each plugin. A plugin cannot be invoked elsewhere in the manifest file unless it is included in this section.

There is also the option to provide a mapping to the plugin in the initialize block. Its purpose is to rename the arguments expected or returned from the plugin as part of the plugin's execution, avoiding the need to use additional plugins to rename parameters.

For example, your plugin might expect cpu/energy and your input data has the parameter cpu-energy returned from another plugin. Instead of using an additional plugin to rename the parameter and add a new one, you can use mapping to:

a) rename the output from the first plugin so that cpu/energy is returned instead of the default cpu-energy

b) instruct the second plugin to accept cpu-energy instead of the default cpu/energy

e.g.

initialize:
plugins:
sci:
kind: plugin
method: Sci
path: 'builtin'
config:
functional-unit: requests
mapping:
sci: if-sci

In the outputs, the sci value returned by the Sci plugin will be named if-sci.

You can also add information to the plugin's initialize section about parameter metadata if you wish to add or override the metadata hardcoded into the plugin. This is what will be reported by the explainer feature if you enable it. E.g.

plugins:
sum-carbon:
path: 'builtin'
method: Sum
config:
input-parameters:
- carbon-operational
- embodied-carbon
output-parameter: carbon
parameter-metadata:
inputs:
carbon-operational:
description: "carbon emitted due to an application's execution"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
embodied-carbon:
description: "carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum

Execution (auto-generated)

This section is auto generated by IF at runtime. You don't have to include this section in your manifest. The execution node contains all the necessary information to rebuild the environment, which can support debugging or verifying output files.

execution:
status: success
command: if-run --manifest examples/basic.yml
environment:
if-version: v0.3.2
os: ubuntu
os-version: 22.04.6
node-version: v21.4.0
date-time: 2023-12-12T00:00:00.000Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- ...
error: 'InputValidationError: "duration" parameter is required. Error code: invalid_type'. ## appears when execution failed
  • status: execution state: success (indicating that IF successfully executed this manifest) or fail (indicating that IF encountered a problem that halted execution).
  • command: exact command which was used to run the framework to execute this manifest (it may include full path to tools in place of aliases such as run)
  • environment: information about the environment the manifest was executed in, including the local operating system, Node.js version, time, and dependencies.
  • error: this field only appears if execution failed. The error message returned by IF is captured here.

Explain

This section is autogenerated at runtime. It is a list of all the parameter metadata that IF can scrape from your plugin instances. It looks as follows:

explain:
sci:
inputs:
carbon:
unit: gCO2eq
description: >-
total carbon emissions attributed to an application's usage as the sum
of embodied and operational carbon
aggregation-method:
time: sum
component: sum
requests:
unit: requests
description: number of requests made to application in the given timestep
aggregation-method:
time: sum
component: sum
outputs:
sci:
unit: gCO2eq/request
description: >-
software carbon intensity expressed as a rate of carbon emission per
request
aggregation-method:
time: sum
component: sum

Tree

The tree section of a manifest file defines the topology of all the components being measured. The shape of the tree defines the grouping of components. It describes the architecture of the application being studied and contains all the usage observations for each component. The tree has individual components such as leaves, intermediate nodes representing groupings, and the top level is the root.

For example, a web application could be organized as follows:

tree:
children:
front-end:
children:
build-pipeline:
children:
vercel:
github-pages:
backend-database:
children:
server1:
server2:
server3:
front-end:
networking:

This example has a relatively straightforward structure with a maximum of 3 levels of nesting. You can continue to nest components to any depth.

Each component has some configuration, some input data, and a plugin pipeline.

  • pipeline: a list of plugins that should be executed for a specific component. This is broken down into three subsections representing distinct phases of execution that can be triggered independently using command line flags. These subsections are:
    • observe: the plugins that generate input data
    • regroup: configuration for regrouping input data by given keys
    • compute: the plugins that operate over input data and generate output data
  • defaults: fallback values that IF defaults to if they are not present in an input observation.
  • inputs: an array of observation data, with each observation containing usage data for a given timestep.

If a component does not include its own pipeline or default values, they are inherited from the closest parent.

Here's an example of a moderately complex tree:

tree:
children:
child-0:
children:
child-0-1:
pipeline:
observe:
regroup:
compute:
- sum
defaults: null
inputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
outputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
energy: 0.000811
child-0-2:
children:
child-0-2-1:
pipeline:
observe:
regroup:
compute:
- sum
defaults: null
inputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
outputs:
- timestamp: 2023-07-06T00:00
duration: 10
cpu-util: 50
energy-network: 0.000811
energy: 0.000811

Defaults

Defaults are fallback values that are only used if a given value is missing in the inputs array. For example, if you have a value that could feasibly be missing in a given timestep, perhaps because your plugin relies on a third party API that can fail, you can provide a value in defaults that can be used as a fallback value.

The values in defaults are applied to every timestep where the given value is missing. This means that as well as acting as a fallback defaults can be used as a convenience tool for efficiently adding a constant value to every timestep in your inputs array.

Inputs

Every component includes an inputs field that gets read into plugins as an array. inputs are divided into observations, each having a timestamp and a duration. Every observation refers to an element in inputs representing some snapshot in time.

Each plugin takes the inputs array and applies some calculation or transformation to each observation in the array.

Observations can include any type of data, including human judgment, assumptions, other plugins, APIs, survey data or telemetry.

The separation of timestamps in the inputs array determines the temporal granularity of your impact calculations. The more frequent your observations, the more accurate your impact assessment.

Creating input data

The plugins in the observe part of the pipeline generate input data. The manifest file should not have input data when the observe phase is executed. Plugins in this phase only generate input data, they can never generate output data. If you run the observe phase on its own (by running if-run --observe) then your manifest will be returned populated with input data according to the plugins you included in your observe pipeline.

Regrouping a manifest file

The second phase of manifest execution is regroup. This reorganizes existing input data into a new structure using keys provided in the regroup config in the manifest. For example, a manifest with the following tree:

tree:
children:
my-app:
pipeline:
observe:
regroup:
- cloud/instance-type
- cloud/region
compute:
children:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 99
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 12
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 1
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 9
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 12
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 1

generates the following output when if-run --regroup is executed:

tree:
children:
my-app:
pipeline:
observe: null
regroup:
- cloud/instance-type
- cloud/region
compute: null
children:
A1:
children:
uk-west:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 99
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 12
uk-east:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 9
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 23
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: A1
cloud/region: uk-east
cpu/utilization: 12
B1:
children:
uk-west:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-west
cpu/utilization: 1
uk-east:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 11
- timestamp: 2023-07-06T05:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 67
- timestamp: 2023-07-06T10:00
duration: 300
cloud/instance-type: B1
cloud/region: uk-east
cpu/utilization: 1

Computing a manifest file

Impact Framework computes manifest files. For each component in the tree, the inputs array is passed to each plugin in the compute pipeline in sequence.

In order for the compute phase to execute correctly, the manifest needs to have input data available.

Each plugin enriches the inputs array in some specific way, typically by adding a new key-value pair to each observation in the array. For example, the teads-curve plugin takes in CPU utilization expressed as a percentage as an input and appends cpu/energy expressed in kWh. cpu/energy is then available to be passed as an input to, for example, the sci-e plugin.

This implies a sequence of plugins where the inputs for some plugins must either be present in the original manifest file or be outputs of the preceding plugins in the pipeline.

There are also plugins and built-in features that can synchronize time series of observations across an entire tree and aggregate data across time or across components.

Running combinations of phases

It is possible to run each phase of the execution individually, or together. You can choose to only run the observe, regroup or compute phases of the manifest execution. This saves you from having to re-execute entire manifests every time you want to tweak something, making it a greener way to use IF.

if-run executes all the phases together, including observe, regroup and compute. It generates yaml output data. However, you can run individual phases by passing --observe, --regroup or --compute flags on the command line. For example, to run only the compute phase:

if-run -m <manifest> --compute

Maybe you only want to generate a static file that contains input data but don't want to run the full compute pipeline right now. You can run with --observe:

if-run <manifest> --observe

You can also combine the command, e.g. if you have a file with inputs and you want to run regroup and compute but not observe:

if-run -m <manifest> --regroup --compute

Outputs

When Impact Framework computes a manifest file, it appends new data to the manifest file and the final result is an enriched manifest that includes all the configuration and contextual data, the input data, and the results of executing each plugin. This means the output file is completely auditable - the original manifest file can be recovered simply by deleting the outputs and execution sections of the output file.

IF generates yaml output data. Any other output formats have to be generated by separate exhaust scripts that take IF's yaml output as their input.

Here's an example output file:

name: sum
description: successful path
tags: null
initialize:
plugins:
sum:
path: builtin
method: Sum
config:
input-parameters:
- cpu/energy
- network/energy
output-parameter: energy
execution:
command: >-
/home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/user/Code/if/src/index.ts -m
/home/user/Code/if/manifests/plugins/sum/success.yml
environment:
if-version: 0.3.3-beta.0
os: linux
os-version: 5.15.0-105-generic
node-version: 21.4.0
date-time: 2024-05-31T09:18:48.895Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.22.4
status: success
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- sum
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
outputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
energy: 0.002
+ \ No newline at end of file diff --git a/major-concepts/pipelines/index.html b/major-concepts/pipelines/index.html index eabed34..46cc85a 100644 --- a/major-concepts/pipelines/index.html +++ b/major-concepts/pipelines/index.html @@ -8,13 +8,13 @@ Pipelines | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Pipelines

Pipelines are chains of plugins that operate in sequence over the input data in your manifest file.

Your input data is fed through your pipeline of plugins, with each plugin adding a key-value pair to the inputs array or updating the value of an existing entry.

Each plugin does some specific operation. The idea is that individual plugins are simple - they do one specific thing only - but they act like Impact Legos, building up into complex logic operating on your manifest file.

Prebuilt pipelines

We have designed our "standard library" of builtins to cover many generic operations such as file i/o, arithetic, queries etc so that in any cases you can build up complex pipelines without having to install any third party dependencies. One of the downsides of this is that logic that could be abstracted away into plugin code has to be implemented inside your manifest. We think this is great for transparency, auditability and reproducability, but it does come with a moderate learning curve. For this reason, we have provided prebuilt pipelines for several of our common operations, such as implementing the Teads curve for estimating cpu energy consumption from CPU utilization, looking up metadata for given cloud instance types anc calculating software carbon intensity (SCI) scores.

We recommend looking at the manifests in the manifests/examples folder that comes bundled with IF.

We also have a set of pipeline walkthroughs on this website, including:

  • Teads curve: calculate CPU energy from CPU utilization and the thermal design power of your processor
  • Cloud-instance metadata: lookup information about your cloud instance froma CSV file
  • SCI: calculate a software carbon intensiy score

These pipelines can be modified or chained together with other pipelines to make larger pipelines. Just as each plugin is a building block, pipelines themselves can be building blocks too.

- + \ No newline at end of file diff --git a/major-concepts/plugins/index.html b/major-concepts/plugins/index.html index 1e2f749..19c6fda 100644 --- a/major-concepts/plugins/index.html +++ b/major-concepts/plugins/index.html @@ -8,13 +8,13 @@ Plugins | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Plugins

Plugins are self-contained units of code that do one thing. They can be loaded into IF and chained together in a pipeline so that simple individual plugins can form a complicated procedure for computing a manifest file.

What are plugins?

A plugin is a small, reuseable unit of code that can either observe some usage metric, grab some data from a file or API, or execute some transformation or calculation over some existing input data, for example, some plugins convert an input of CPU utilization into an output of energy.

The set of available plugins is growing; however, no single plugin can cover all impacts, scenarios, environments, contexts, and use cases. To calculate the end-to-end impact of a software application, you need to stitch together many different plugins. Plugins differ in fundamental ways in the inputs inputs they accept, their interface, their calculation methodology, their outputs, their granularity, and their coverage.

We expect the choice of which plugin to use for which software component to come down to an expert decision by a green software professional.

Why do we need to standardize the interface to plugins?

Currently, suppose you want to consume a plugin in your measurement application. In that case, you must craft custom code to interact with a custom interface since every plugin has its unique interface. Swapping one plugin for another requires code changes, and comparing plugins or validating their accuracy/precision is challenging.

If every plugin exposes the same interface, then those plugins can easily be plugged into different applications, swapped in and out, upgraded, and compared.

Our thesis is simple: if we want a large, vibrant ecosystem of people and tooling around measurement, we need a standard, common interface for all plugins.

Ecosystems grow and thrive through standards.

You can explore more in the plugins reference docs or our plugin building tutorial.

Finding community plugins

Anyone can build IF plugins. To make them discoverable, we host the IF Explorer website. There you can search for plugins that you need for your specific use-case.

You can also add your own plugins to the explorer. Simply fill in the submission form.

- + \ No newline at end of file diff --git a/major-concepts/regroup/index.html b/major-concepts/regroup/index.html index c486c9c..6a9b177 100644 --- a/major-concepts/regroup/index.html +++ b/major-concepts/regroup/index.html @@ -8,13 +8,13 @@ Regroup | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Regroup

Regroup is an IF feature that reorganizes a tree according to keys provided by the user. This allows users to regroup their observations according to various properties of their application. For example, the following manifest file contains a flat array of observations. This is how you might expect data to arrive from an importer plugin, maybe one that hits a metrics API for a cloud service.

name: if-demo
description: demo pipeline
tree:
children:
my-app:
pipeline:
observe:
regroup:
- cloud-region
- instance-type
compute:
- teads-curve
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
instance-type: A1
region: uk-west
cpu-util: 99
- timestamp: 2023-07-06T05:00
duration: 300
instance-type: A1
region: uk-west
cpu-util: 23
- timestamp: 2023-07-06T10:00
duration: 300
instance-type: A1
region: uk-west
cpu-util: 12
- timestamp: 2023-07-06T00:00 # note this time restarts at the start timstamp
duration: 300
instance-type: B1
region: uk-west
cpu-util: 11
- timestamp: 2023-07-06T05:00
duration: 300
instance-type: B1
region: uk-west
cpu-util: 67
- timestamp: 2023-07-06T10:00
duration: 300
instance-type: B1
region: uk-west
cpu-util: 1

However, each observation contains an instance-type field that varies between observations. There are two instance types being represented in this array of observations. This means there are duplicate entries for the same timestamp in this array. This is the problem that regroup solves. You provide instance-type as a key in the regroup config and it extracts the data belonging to the different instances and separates them into independent arrays. The above example would be restructured so that instance types A1 and B1 have their own data, as follows:

children:
A1:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
instance-type: A1
region: uk-west
cpu-util: 99
- timestamp: 2023-07-06T05:00
duration: 300
instance-type: A1
region: uk-west
cpu-util: 23
- timestamp: 2023-07-06T10:00
duration: 300
instance-type: A1
region: uk-west
cpu-util: 12
B1:
inputs:
- timestamp: 2023-07-06T00:00
duration: 300
instance-type: B1
region: uk-east
cpu-util: 11
- timestamp: 2023-07-06T05:00
duration: 300
instance-type: B1
region: uk-east
cpu-util: 67
- timestamp: 2023-07-06T10:00
duration: 300
instance-type: B1
region: uk-east
cpu-util: 1

Using regroup

To use regroup, you simply provide the keys you want to regroup by in the regroup pipeline. regroup is NOT a plugin, it is a core feature of IF that is executed when you run if-run or if-run --regroup if the config is available in the manifest file.

The config is provided at the node level, and it looks as follows:

tree:
children:
my-app:
pipeline:
observe:
regroup:
- cloud-region
- instance-type

In the example above, the plugin would regroup the input data for the specific component by cloud-region and by instance-type.

- + \ No newline at end of file diff --git a/major-concepts/time/index.html b/major-concepts/time/index.html index b60885b..eb4a6c6 100644 --- a/major-concepts/time/index.html +++ b/major-concepts/time/index.html @@ -8,13 +8,13 @@ Time | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Time

Every observation in an array of inputs represents a snapshot with a known start time and a known duration. For example, the following observation shows that the CPU utilization for a resource was 20% for the 10 second period starting at 1500 on the 22nd January 2024:

inputs:
- timestamp: 2024-01-15T00:00:00.000Z
duration: 10
cpu-util: 20

The total time covered by an inputs array is determined by the timestamp of the first observation in the array and the timestamp and duration in the last observation in the array. Since every observation needs both a timestamp and a duration, an inputs array is always a time series.

Synchronizing time series'

The time series for each component is defined by its inputs array. However, a manifest file can contain many separate components, each with their own time series. There is no guarantee that an individual time series is continuous, or that all the components in a manifest file have the same start time, end time and resolution. This makes it difficult to aggregate, visualize or do like-for-like comparisons between components.

To solve this problem, we provide a built-in time-sync feature that synchronizes the time series' across all the components in a tree. The time-sync feature takes a global start time, end time and interval, then forces every individual time series to conform to this configuration.

  • This works by first upsampling each time series to a common base resolution (typically 1s).
  • Any gaps in the time series are filled in with "zero objects", which have an identical structure to the real observations but with usage metrics set to zero (we assume that when there is no data, there is no usage).
  • Next, we check to see whether the first timestamp in each time series is before, after or identical to the global start time.
  • If a component's time series starts after the global start time, we pad the start of the time series with "zero objects" so that the start times are identical.
  • If the component's time series starts *before* the global start time, we trim the time series down, discarding observations from before the global start time. The same trimming logic is applied to the end times.
  • After synchronizing the start and end times and padding any discontinuities, we have a set of continuous time series' of identical length.
  • Next, we batch observations together into time bins whose size is define by the global interval value. This means that the resolution of the final time series' are identical and equal to interval.

This process yields synchronized time series for all components across a tree, enabling easy visualization and intercomparison. This synchronization is also a prerequisite for our aggregation function.

Toggling off time sync

Some applications will not want to pad with zero values, and may be strict about continuous time series' being provided in the raw manifest file. In these cases, simply toggle the padding off in the manifest file.

- + \ No newline at end of file diff --git a/pipelines/cpu-to-carbon/index.html b/pipelines/cpu-to-carbon/index.html index 3c8d3d9..3fd242e 100644 --- a/pipelines/cpu-to-carbon/index.html +++ b/pipelines/cpu-to-carbon/index.html @@ -8,14 +8,14 @@ From CPU utilization to carbon emissions | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

From CPU utilization to carbon emissions

Tags

carbon, teads, power-curve

Observations

This manifest requires the following observations:

  • CPU utilization
  • thermal design power of the processors
  • number of vCPUs allocated to the application under observation
  • total number of vCPUs available on the cloud instance being used
  • the name of the cloud instance type being used
  • the grid carbon intensity for the grid powering the data center

Impacts

This pipeline takes the observations described above, and generates carbon emissions in each timestep, expressed in gCO2e.

Scope

This pipeline takes into account the operational carbon of the server running our application. This includes the energy used to run the application, calculated from CPU and memory utilization. It does not account for any embodied carbon, nor networking energy, nor anything related to the end user. In real applications, the pipeline described here will be part of a much larger manifest that considers other parts of the system.

Description

The Teads CPU power curve CPU utilization (as a percentage) against a scaling factor that can be applied to the CPUs thermal design power to estimate the power drawn by the CPU in Watts.

The research underpinning the curve was summarized in a pair of blog posts:

TEADS Engineering: Buildiong an AWS EC2 Carbon Emissions Dataset Teads Engineering: Estimating AWS EC2 Instances Power Consumption

The curve has become very widely used as a general purpose utilization-to-wattage converter for CPUs, despite the fact that it does not generalize well.

The wattage can be transformed into energy by doing the following:

  1. Measure your CPU utilization
  2. Determine the thermal design power of your processor
  3. Determine the scaling factor for your CPU utilization by interpolating the Teads curve
  4. Determine the power drawn by your CPU by multiplying your scaling factor by the CPU's thermal design power
  5. Perform a unit conversion to convert power in Watts to energy in kwH
  6. Scale the energy estimated for the entire chip to the portion of the chip that is actually in use.

These steps can be executed in IF using just three plugins:

  • Interpolate
  • Multiply
  • Divide

Common patterns

The logical flow from CPU utilization to carbon via a power-curve and thermal design power is a common pattern that is likely to be re-used elsewhere.

Constants and coefficients:

parameterdescriptionvalueunitsource
x, yPoints on power curve relating CPU utilization to a coefficient used to scale the processor's thermal design powerx: [0, 10, 50, 100], y: [0.12, 0.32, 0.75, 1.02]dimensionlessDavy, 2021
grid-carbon-intensitythe carbon emitted per unit energy from the electrical grid750gCO2e/kWhapproximates global average

Assumptions and limitations

The following are assumed to be true in this manifest:

  • the power curve relating CPU utilization to power is appropriate for the processor being used to run our application
  • the temporal granularity of the observations are sufficient to accurately capture the behaviour of our application
  • the grid carbon intensity is sufficiently accurate for the location where the computational work is done

Components

There is only one component in this example. It represents the entire application. The component pipeline looks as follows:

pipeline:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
- energy-to-carbon

Plugins

Interpolate

The interpolate plugin is used once. The instance is named interpolate. It is used to interpolate the curve relating CPU utilization and thermal-design-power factor so that the right value can be retrieved for the observed CPU utilization at each timestep.

config

method: linear
x: [0, 10, 50, 100]
y:[0.12, 0.32, 0.75, 1.02]
input-parameter: cpu/utilization
output-parameter: cpu-factor

Multiply

The Multiply plugin is used several times. The instances are:

  • cpu-factor-to-wattage: used to multiply the thermal design power of the processor by the factor returned from the power curve interpolation, yielding power in Watts.
  • wattage-times-duration: used to multiply the power in Watts by the duration of each timestep, yielding energy in W/duration.
  • energy-to-carbon: used to convert energy expended to carbon emitted.

config

cpu-factor-to-wattage:
input-parameters:
- cpu-factor
- cpu/thermal-design-power
output-parameter:
- cpu-wattage

wattage-times-duration:
input-parameters:
- cpu-wattage
- duration
output-parameter:
- cpu-wattage-times-duration

energy-to-carbon:
input-parameters:
- grid-carbon-intensity
- energy-cpu-kwh
output-parameter:
- carbon

Divide

The Divide plugin is used several times in this manifest. The instances are:

  • wattage-to-energy-kwh. used to convert energy in W/duration to kWh.
  • calculate-vcpu-ratio: used to calculate the ratio of allocated vCPUs to total vCPUS
  • correct-cpu-energy-for-vcpu-ratio: used to scale the CPU energy by the vCPU ratio

config

wattage-to-energy-kwh:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw

calculate-vcpu-ratio:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio

correct-cpu-energy-for-vcpu-ratio:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu/energy

Manifest

name: teads curve demo
description: null
tags: null
initialize:
plugins:
interpolate:
path: builtin
method: Interpolation
config:
method: linear
x:
- 0
- 10
- 50
- 100
'y':
- 0.12
- 0.32
- 0.75
- 1.02
input-parameter: cpu/utilization
output-parameter: cpu-factor
cpu-factor-to-wattage:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-factor
- thermal-design-power
output-parameter: cpu-wattage
wattage-times-duration:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-wattage
- duration
output-parameter: cpu-wattage-times-duration
wattage-to-energy-kwh:
path: builtin
method: Divide
config:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw
calculate-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio
correct-cpu-energy-for-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu-energy-kwh
energy-to-carbon:
path: builtin
method: Multiply
config:
input-parameters:
- grid-carbon-intensity
- cpu-energy-kwh
output-parameter: carbon
execution:
command: >-
/home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/user/if/src/index.ts -m manifests/examples/teads-curve.yml
environment:
if-version: 0.6.0
os: macOS
os-version: 14.6.1
node-version: 18.20.4
date-time: 2024-10-03T15:11:48.498Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.25'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.23.8
status: success
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
- energy-to-carbon
defaults:
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
grid-carbon-intensity: 750
inputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100
outputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
grid-carbon-intensity: 750
cpu-factor: 0.13999999999999999
cpu-wattage: 13.999999999999998
cpu-wattage-times-duration: 5039.999999999999
cpu-energy-raw: 0.0013999999999999998
vcpu-ratio: 4
cpu-energy-kwh: 0.00034999999999999994
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
grid-carbon-intensity: 750
cpu-factor: 0.32
cpu-wattage: 32
cpu-wattage-times-duration: 11520
cpu-energy-raw: 0.0032
vcpu-ratio: 4
cpu-energy-kwh: 0.0008
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
grid-carbon-intensity: 750
cpu-factor: 0.75
cpu-wattage: 75
cpu-wattage-times-duration: 27000
cpu-energy-raw: 0.0075
vcpu-ratio: 4
cpu-energy-kwh: 0.001875
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
grid-carbon-intensity: 750
cpu-factor: 1.02
cpu-wattage: 102
cpu-wattage-times-duration: 36720
cpu-energy-raw: 0.0102
vcpu-ratio: 4
cpu-energy-kwh: 0.00255
- + \ No newline at end of file diff --git a/pipelines/index.html b/pipelines/index.html index 09e0723..1182c13 100644 --- a/pipelines/index.html +++ b/pipelines/index.html @@ -8,13 +8,13 @@ Pipelines | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Pipelines

This section contains walkthrough guides for some common pipelines you may want to use in your manifest files.

These pipelines only use plugins from our standard library that comes bundled with IF and do not require any third party plugins to be installed. They all work out of the box with IF.

These walkthroughs will help you to understand how complex operations can be implemented using simple generic features and provide templates that you can use, modify or chain together in your own workflows.

The pipeline guides include:

You can find the manifests themselves in your copy of IF in manifests/examples or find them on our Github.

- + \ No newline at end of file diff --git a/pipelines/instance-metadata/index.html b/pipelines/instance-metadata/index.html index dcc8a69..2a87319 100644 --- a/pipelines/instance-metadata/index.html +++ b/pipelines/instance-metadata/index.html @@ -8,13 +8,13 @@ Grabbing instance metadata from a CSV file | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Grabbing instance metadata from a CSV file

Observations

This manifest requires the following observations:

  • name of the specific cloud instance being used

Impacts

This pipeline looks up metadata associated with the given cloud instance. It does not generate impacts per se, it just retrieves additional data from an external file using the given instance name as a search key.

Scope

This pipeline is likely to be used as part of a larger pipeline. All we are doing here is retrieving metadata from an external file. Typicaly, this metadata will be used to feed further plugind to support impactestimates.

Description

The instance metadata pipeline simply looks up a metadata for a given virtual machine instance name using the csv-lookup plugin from the IF standard library. However, the target dataset can return multiple processor names for a given VM instance where there are multiple possibilitiers. This means we need to create a pipeline that includes the regex plugin so parse out just one of the possible values.

For this demo we'll just extract the first value if there are multiple available for the processor-name.

Tags

csv, instance-metadata, regex

Common Patterns

The lookup process described on this page will likely be a common pattern used in other pipelines.

Assumptions and limitations

The following are assumed to be true in this manifest:

  • the target dataset is up to date
  • where there are multiple possible processors associated with an instance name, it is appropriate to select the first in the list.

Components

There is only one component in this example. It represents the entire application. The component pipeline looks as follows:

pipeline:
compute:
- cloud-instance-metadata
- extract-processor-name

Plugins

csv-lookup

The csv-lookup plugin is used once. The instance is named cloud-instance-metadata. It targets a csv file in our if-data repository.

config

cloud-instance-metadata:
filepath: https://raw.githubusercontent.com/Green-Software-Foundation/if-data/main/cloud-metdata-azure-instances.csv
query: instance-class: "cloud/instance-type"
output: "*"

regex

The regex plugin is used once. The instance is named extract-processor-name. It parses the response from the csv lookup plugin and extracts the first entry from the returned list.

config

extract-processor-name:
method: Regex
path: 'builtin'
config:
parameter: cpu-model-name
match: /^([^,])+/g
output: cpu/name

Manifest

name: instance-metadata
description:
tags:
initialize:
plugins:
cloud-instance-metadata:
method: CSVLookup
path: 'builtin'
config:
filepath: https://raw.githubusercontent.com/Green-Software-Foundation/if-data/main/cloud-metdata-azure-instances.csv
query:
instance-class: 'cloud/instance-type'
output: '*'
extract-processor-name:
method: Regex
path: 'builtin'
config:
parameter: cpu-model-name
match: /^([^,])+/g
output: cpu/name
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- cloud-instance-metadata
- extract-processor-name
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
cloud/provider: gcp
cloud/region: asia-east
cloud/instance-type: Standard_A1_v2

Now you can run this manifest using:

if-run -m instance-metadata.yml -o output.yml

Your new output.yml file will contain the following:

name: csv-demo
description: null
tags: null
initialize:
plugins:
cloud-instance-metadata:
path: builtin
method: CSVLookup
config:
filepath: >-
https://raw.githubusercontent.com/Green-Software-Foundation/if-data/main/cloud-metdata-azure-instances.csv
query:
instance-class: cloud/instance-type
output: '*'
extract-processor-name:
path: builtin
method: Regex
config:
parameter: cpu-model-name
match: /^([^,])+/g
output: cpu/name
execution:
command: >-
/home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/user/Code/if/src/index.ts -m manifests/examples/instance-metadata.yml
environment:
if-version: 0.6.0
os: macOS
os-version: 14.6.1
node-version: 18.20.4
date-time: 2024-10-03T15:15:36.328Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.25'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.23.8
status: success
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- cloud-instance-metadata
- extract-processor-name
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
cloud/provider: gcp
cloud/region: asia-east
cloud/instance-type: Standard_A1_v2
outputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
cloud/provider: gcp
cloud/region: asia-east
cloud/instance-type: Standard_A1_v2
cpu-cores-available: 52
cpu-cores-utilized: 1
cpu-manufacturer: Intel
cpu-model-name: >-
Intel® Xeon® Platinum 8272CL,Intel® Xeon® 8171M 2.1 GHz,Intel® Xeon®
E5-2673 v4 2.3 GHz,Intel® Xeon® E5-2673 v3 2.4 GHz
cpu-tdp: 205
gpu-count: nan
gpu-model-name: nan
gpu-tdp: nan
memory-available: 2
cpu/name: Intel® Xeon® Platinum 8272CL
- + \ No newline at end of file diff --git a/pipelines/sci/index.html b/pipelines/sci/index.html index 8fdbce4..4d12c81 100644 --- a/pipelines/sci/index.html +++ b/pipelines/sci/index.html @@ -8,13 +8,13 @@ Software Carbon Intensity (SCI) | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Software Carbon Intensity (SCI)

Description

The software carbon intensity (SCI) score is perhaps the most important value that can be generated using Impact Framework.

SCI is an ISO-recognized standard for reporting the carbon costs of running software. This tutorial demonstrates how to organize a pipeline of Impact framework plugins to calculate SCI scores from some simple observations that are commonly available for software applications running in the cloud.

Tags

SCI, cloud, cpu, memory, power-curve

Prerequisites

This tutorial builds on top of the Teads curve pipeline tutorial. That tutorial demonstrates how to organize a pipeline that converts CPU utilization observations into CPU energy. This tutorial uses the same pipeline but goes several steps further, including converting the CPU energy estimates into carbon, adding the embodied carbon associated with the hardware being used and calculating the SCI score.

Scope

This SCI calculation takes into account the operational and embodied carbon of the server running our application. This includes the energy used to run the application, calculated from CPU and memory utilization, and the energy required to transfer data over the internet between server and client. It does not account for data center embodied carbon, embodied carbon of end user devices nor operational carbon in end user devices.

Common patterns

We employ the well known power curve from Davy, 2021 to estimate CPU power from CPU utilization. You can find a detailed explanation of our implementation of this power curve methodology here.

We also use the networking energy and embodied carbon estimation methods from Cloud Carbon Footprint. This includes using the networking energy coefficient they suggest and implementing their method for calculating embodied emissions in an Impact Framework plugin.

Observations

This manifest requires the following observations:

  • CPU utilization
  • thermal design power of the processors
  • number of vCPUs allocated to the application under observation
  • total number of vCPUs available on the cloud instance being used
  • the name of the cloud instance type being used
  • the grid carbon intensity for the grid powering the data center
  • data transferred in/out of the application
  • users per timestep

Constants and coefficients:

parameterdescriptionvalueunitsource
network-energy-coefficientCoefficient relating data sent over network to energy0.001kWh/GBCCF
x, yPoints on power curve relating CPU utilization to a coefficient used to scale the processor's thermal design powerx: [0, 10, 50, 100], y: [0.12, 0.32, 0.75, 1.02]dimensionlessDavy, 2021
baseline-emissionsembodied emissions for a "baseline" server with 1 CPU, 16GB RAM1000000gCO2eCCF
lifespanlifespan for the server running our application126144000secondsnone, assumed 4 years is typical
usage-ratioscaling factor for adjusting total embodied carbon down tot he portion our application is responsible for1dimensionlessno usage scaling is done here as we assume dedicated hardware, we only scale by time

Assumptions and limitations

The following are assumed to be true in this manifest:

  • the embodied carbon of the baseline server approximates the real embodied carbon of the server running our application
  • the lifespan of the server running our application is really 4 years
  • the coefficient relating data transferred over the network to energy is accurate
  • the power curve relating CPU utilization to power is appropriate for the processor being used to run our application
  • the coefficient relating memory utilization to energy is accurate
  • it is appropriate to consider end user embodied carbon, end user operational carbon and the operationl and embodied emissions of the data center to be out of scope.
  • the temporal granularity of the observations are sufficient to accurately capture the behaviour of our application

Components

There is only one component in this example. It represents the entire application. The component pipeline looks as follows:

pipeline:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
- sum-energy-components
- embodied-carbon
- operational-carbon
- sum-carbon
- sci

Plugins

Interpolate

The interpolate plugin is used once. The instance is named interpolate. It is used to interpolate the curve relating CPU utilization and thermal-design-power factor so that the right value can be retrieved for the observed CPU utilization at each timestep.

config

method: linear
x: [0, 10, 50, 100]
y:[0.12, 0.32, 0.75, 1.02]
input-parameter: cpu/utilization
output-parameter: cpu-factor

Multiply

The Multiply plugin is used several times. The instances are:

  • cpu-factor-to-wattage: used to multiply the thermal design power of the processor by the factor returned from the power curve interpolation, yielding power in Watts.
  • wattage-times-duration: used to multiply the power in Watts by the duration of each timestep, yielding energy in W/duration.
  • operational-carbon: used to convert energy into operational carbon by multiplying energy in kWh by the grid carbon intensity in gCO2/kWh.

config

cpu-factor-to-wattage:
input-parameters:
- cpu-factor
- cpu/thermal-design-power
output-parameter:
- cpu-wattage

wattage-times-duration:
input-parameters:
- cpu-wattage
- duration
output-parameter:
- cpu-wattage-times-duration

operational-carbon:
input-parameters:
- energy
- grid/carbon-intensity
output-parameter:
- carbon-operational

Divide

The Divide plugin is used once in this manifest. The instance is named wattage-to-energy-kwh. It is used to convert energy in W/duration to kWh.

config

wattage-to-energy-kwh:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw

Sum

The Sum plugin is used several times in this manifest. The instances are:

  • sum-energy-components: used to sum all the various components of energy into a single value, called energy.
  • sum-carbon: used to sum the various components of carbon into a single value, named carbon.

config

sum-energy-components:
input-parameters:
- cpu/energy
- network/energy
output-parameter:
- energy

sum-carbon:
input-parameters:
- carbon-operational
- carbon-embodied
output-parameter:
- carbon

SciEmbodied

The SciEmbodied plugin is used once. Its purpose is to calculate the embodied emissions of the server running our application and apportion the total embodied carbon to the fraction that we are responsible (i.e. scale it based on the application only using a fraction of the available resources and a fraction of the server lifespan). We do not scale by resource allocation in this example, only time.

config

We use the plugin defaults for all the SciEmbodied config. This means we assume the total embodied emissions to be 1000000 gCO2e and the server to be a simple rack server with 1 CPU and 16GB RAM and no other components.

SCI

The SCI plugin is used once. It is used to calculate the software carbon intensity by dividing carbon by a functional unit, that has to be available in the manifest inputs array at the time the plugin is executed. The functional unit in this example is users in each timestep.

config:

sci:
functional-unit: users

Manifest

name: sci example
description:
tags:
aggregation:
metrics:
- carbon
- sci
type: both

initialize:
plugins:
interpolate:
path: builtin
method: Interpolation
config:
method: linear
x:
- 0
- 10
- 50
- 100
'y':
- 0.12
- 0.32
- 0.75
- 1.02
input-parameter: cpu/utilization
output-parameter: cpu-factor
cpu-factor-to-wattage:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-factor
- cpu/thermal-design-power
output-parameter: cpu-wattage
wattage-times-duration:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-wattage
- duration
output-parameter: cpu-wattage-times-duration
wattage-to-energy-kwh:
path: builtin
method: Divide
config:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw
calculate-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio
correct-cpu-energy-for-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu/energy
sum-energy-components:
path: builtin
method: Sum
config:
input-parameters:
- cpu/energy
- network/energy
output-parameter: energy
embodied-carbon:
path: builtin
method: SciEmbodied
config:
output-parameter: embodied-carbon
operational-carbon:
path: builtin
method: Multiply
config:
input-parameters:
- energy
- grid/carbon-intensity
output-parameter: carbon-operational
sum-carbon:
path: builtin
method: Sum
config:
input-parameters:
- carbon-operational
- embodied-carbon
output-parameter: carbon
sci:
path: builtin
method: Sci
config:
functional-unit: users

tree:
children:
github-storage-for-if-docs:
pipeline:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
- sum-energy-components
- embodied-carbon
- operational-carbon
- sum-carbon
- sci
defaults:
cpu/thermal-design-power: 100
vcpus-total: 1
vcpus-allocated: 1
network/energy: 0.001
grid/carbon-intensity: 130
inputs:
- timestamp: '2024-07-22T00:00:00'
duration: 3600
site-visits: 228
cpu/utilization: 45
component: 1
users: 1100
- timestamp: '2024-07-23T00:00:00'
duration: 3600
site-visits: 216
cpu/utilization: 30
component: 1
users: 1050
- timestamp: '2024-07-24T00:00:00'
duration: 3600
site-visits: 203
cpu/utilization: 50
component: 1
users: 1055
- timestamp: '2024-07-25T00:00:00'
duration: 3600
site-visits: 203
cpu/utilization: 33
component: 1
users: 996
- timestamp: '2024-07-26T00:00:00'
duration: 3600
site-visits: 172
cpu/utilization: 29
component: 1
users: 899
- timestamp: '2024-07-27T00:00:00'
duration: 3600
site-visits: 38
cpu/utilization: 68
component: 1
users: 1080
- timestamp: '2024-07-28T00:00:00'
duration: 3600
site-visits: 63
cpu/utilization: 49
component: 1
users: 1099
- timestamp: '2024-07-29T00:00:00'
duration: 3600
site-visits: 621
cpu/utilization: 77
component: 1
users: 1120
- timestamp: '2024-07-30T00:00:00'
duration: 3600
site-visits: 181
cpu/utilization: 31
component: 1
users: 1125
- timestamp: '2024-07-31T00:00:00'
duration: 3600
site-visits: 213
cpu/utilization: 29
component: 1
users: 1113
- timestamp: '2024-08-01T00:00:00'
duration: 3600
site-visits: 167
cpu/utilization: 29
component: 1
users: 1111
- timestamp: '2024-08-02T00:00:00'
duration: 3600
site-visits: 428
cpu/utilization: 29
component: 1
users: 1230
- timestamp: '2024-08-03T00:00:00'
duration: 3600
site-visits: 58
cpu/utilization: 64
component: 1
users: 1223
- timestamp: '2024-08-04T00:00:00'
duration: 3600
site-visits: 66
cpu/utilization: 59
component: 1
users: 1210
- timestamp: '2024-08-05T00:00:00'
duration: 3600
site-visits: 301
cpu/utilization: 60
component: 1
users: 1011
- timestamp: '2024-08-06T00:00:00'
duration: 3600
site-visits: 193
cpu/utilization: 35
component: 1
users: 999
- timestamp: '2024-08-07T00:00:00'
duration: 3600
site-visits: 220
cpu/utilization: 37
component: 1
users: 1010
- timestamp: '2024-08-08T00:00:00'
duration: 3600
site-visits: 215
cpu/utilization: 43
component: 1
users: 1008
- timestamp: '2024-08-09T00:00:00'
duration: 3600
site-visits: 516
cpu/utilization: 28
component: 1
users: 992
- timestamp: '2024-08-10T00:00:00'
duration: 3600
site-visits: 42
cpu/utilization: 39
component: 1
users: 1101
- timestamp: '2024-08-11T00:00:00'
duration: 3600
cpu/utilization: 40
site-visits: 76
component: 1
users: 1000
- timestamp: '2024-08-12T00:00:00'
duration: 3600
site-visits: 226
cpu/utilization: 55
component: 1
users: 845
- timestamp: '2024-08-13T00:00:00'
duration: 3600
site-visits: 180
cpu/utilization: 62
component: 1
users: 1006
- timestamp: '2024-08-14T00:00:00'
duration: 3600
site-visits: 232
cpu/utilization: 71
component: 1
users: 1076
- timestamp: '2024-08-15T00:00:00'
duration: 3600
site-visits: 175
cpu/utilization: 75
component: 1
users: 1050
- timestamp: '2024-08-16T00:00:00'
duration: 3600
site-visits: 235
cpu/utilization: 77
component: 1
users: 1047
- timestamp: '2024-08-17T00:00:00'
duration: 3600
site-visits: 44
cpu/utilization: 80
component: 1
users: 1020
- timestamp: '2024-08-18T00:00:00'
duration: 3600
site-visits: 31
cpu/utilization: 84
component: 1
users: 1038
- + \ No newline at end of file diff --git a/pipelines/teads/index.html b/pipelines/teads/index.html index b707acc..16747b9 100644 --- a/pipelines/teads/index.html +++ b/pipelines/teads/index.html @@ -8,14 +8,14 @@ Teads CPU pipeline | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Teads CPU pipeline

The Teads CPU power curve CPU utilization (as a percentage) against a scaling factor that can be applied to the CPUs thermal design power to estimate the power drawn by the CPU in Watts.

The research underpinning the curve was summarized in a pair of blog posts:

TEADS Engineering: Buildiong an AWS EC2 Carbon Emissions Dataset Teads Engineering: Estimating AWS EC2 Instances Power Consumption

The curve has become very widely used as a general purpose utilization-to-wattage converter for CPUs, despite the fact that it does not geenralize well.

The wattage can be transformed into energy by doing the following:

  1. Measure your CPU utilization
  2. Determine the thermal design power of your processor
  3. Determine the scaling factor for your CPU utilization by interpolating the Teads curve
  4. Determine the power drawn by your CPU by multiplying your scaling factor by the CPU's thermal design power
  5. Perform a unit conversion to convert power in Watts to energy in kwH
  6. Scale the energy estimated for the entire chip to the portion of the chip that is actually in use.

These steps can be executed in IF using just three plugins:

  • Interpolate
  • Multiply
  • Divide

We'll go through each step in the energy estimate and examine how to implement it in a manifest file using IF's standard library of builtins.

Impact Framework implementation

First, create a manifest file and add this following boilerplate code:

name: carbon-intensity plugin demo
description:
tags:
initialize:
plugins:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
defaults:
inputs:

If this structure looks unfamiliar to you, you can go back to our manifests page.

Step 1: measure CPU utilization

The first step was to measure your CPU utilization. In real use cases you would typoically do this using an importer plugin that grabs data from a monitor API or similar. However, for this example we will just manually create some dummy data. Add some timestamps, durations and cpu/utilization data to your inputs array, as follows:

name: teads demo
description:
tags:
initialize:
plugins:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
defaults:
inputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100

Step 2: Determine the thermal design power of your processor

Typically determinign the TDP of your processor would be done using a CSV lookup. We have a pipeline example for tdp-finder in these docs - combining this pipeline with the tdp-finder pipeline would eb a great follow on exercise after you have finished this tutorial. Foir now, we will just hartd code some TDP data into your manifest so we can focus on the CPU utilization to energy calculations. Add thermal-design-power to defaults - this is a shortcut to providing it in every timestep in your inputs array.

default:
thermal-design-power: 100

Step 3: Interpolate the Teads curve

The Teads curve has CPU utilization ont he x axis and a scaling factor on the y axis. There are only four points on the published curve. Your task is to get the scaling factor for your specific CPU utilization values by interpolating between the known points. Luckily, we have a builtin for that purpose!

Add the Interpolation plugin to your list of plugins in the initialize block.

initialize:
plugins:
interpolate:
method Interpolation
path: builtin

The details about the interpolation you want to do and the values to return are configured in the config whoch is also added int he initialize block. Specifically, you have to provide the known points of the curve you want to interpolate, the input-parameter (which is the x value whose correspondiong y value you want to find out, i.e. your CPU utilization value) and the output-parameter (the name you want to give to your retrieved y value).

You want to interpolate the Teads curve, so you can provide the x and y values obtained from the articles linked in the introduction section above:

x: [0, 10, 50, 100]
y: [0.12, 0.32, 0.75, 1.02]

Your input-parameter is your cpu/utilization and we'll name give the output-parameter the name cpu-factor.

Your compelted initialize block for interpolate should look as follows:

interpolate:
method: Interpolation
path: 'builtin'
config:
method: linear
x: [0, 10, 50, 100]
y: [0.12, 0.32, 0.75, 1.02]
input-parameter: 'cpu/utilization'
output-parameter: 'cpu-factor'

Step 4: Convert CPU factor to power

The interpoaltion only gave use the scaling factor; we need to apply that scaling factor to the processor's TDP to get the power drawn by the CPU at your specific CPU utilization.

To do this, we can use the Multiply plugin in the IF standard library. We'll give the instance of Multiply the name cpu-factor-to-wattage and int he config we'll define cpu-factor and thermal-design-power as the two elements in our inputs array that we want to multiply together. Then we'll name the result cpu-wattage:

cpu-factor-to-wattage:
method: Multiply
path: builtin
config:
input-parameters: ['cpu-factor', 'thermal-design-power']
output-parameter: 'cpu-wattage'

Add this to your initialize block.

Step 5: Convert wattage to energy

Next we have to perform some unit conversions. Wattage is a measure of power (energy over time). To convert to energy, we can first multiply by the number of seconds our observation covers (duration) to yield energy in joules. Then, convert to kWh by applying a scaling factor that takes seconds to hours and watts to kilowatts.

You can do this in two steps: the first uses another instance of Multiply an the second uses Divide:

To do the initial multiplication of the CPU wattage and the observation duration, add the following config to your initialize block:

wattage-times-duration:
method: Multiply
path: builtin
config:
input-parameters: ['cpu-wattage', 'duration']
output-parameter: 'cpu-wattage-times-duration'

next, use the Divide plugin to do the unit conversion:

wattage-to-energy-kwh:
method: Divide
path: 'builtin'
config:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw

Step 6: Scale the energy by the allocated CPUs

The cpu-energy-raw value you just configured is for the entire chip. But your application probably doesn't use the entire chip. Chances are you have some number of VCPUs allocated to you that is less than the total available. So you can scale your energy estimate by the ratio of VCPUs allocated to VCPUS available.

Let's assume you know the number of VCPUs allocated and available in advance and that they are the same in every timestep. In this case, you can just add the values to defaults so they become available in every timestep, just as you did with thermal-design-power.

defaults:
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2

You need one instance of Divide to calculate the vcpu-ratio and another to apply that vcpu-ratio to your cpu-energy-raw value and yield your final result: cpu-energy-kwh. Add the following to your initialize block to achieve those steps:

calculate-vcpu-ratio:
method: Divide
path: 'builtin'
config:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio
correct-cpu-energy-for-vcpu-ratio:
method: Divide
path: 'builtin'
config:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu-energy-kwh

Step 7: Define your pipeline

Now you have configured all your plugins, covering all the stages of the calculation, you can simple define them in order in the pipeline section of your manifest, as follows:

tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio

You also need to add some input data that your pipeline can operate over.

You can see the full manifest in the IF repository.

That's it! Your manifest is ready to run!

Running the manifest

Having saved your manifest as teads-curve.yaml you can run it using IF:

if-run -m teads-curve.yml -o teads-output.yml

This will yield the following output file:

name: teads curve demo
description: null
tags: null
initialize:
plugins:
interpolate:
path: builtin
method: Interpolation
config:
method: linear
x:
- 0
- 10
- 50
- 100
'y':
- 0.12
- 0.32
- 0.75
- 1.02
input-parameter: cpu/utilization
output-parameter: cpu-factor
cpu-factor-to-wattage:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-factor
- thermal-design-power
output-parameter: cpu-wattage
wattage-times-duration:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-wattage
- duration
output-parameter: cpu-wattage-times-duration
wattage-to-energy-kwh:
path: builtin
method: Divide
config:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw
calculate-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio
correct-cpu-energy-for-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu-energy-kwh
execution:
command: >-
/home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/user/if/src/index.ts -m manifests/examples/teads-curve.yml
environment:
if-version: 0.6.0
os: macOS
os-version: 14.6.1
node-version: 18.20.4
date-time: 2024-10-03T15:05:11.948Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.25'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.23.8
status: success
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
defaults:
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
inputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100
outputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 0.13999999999999999
cpu-wattage: 13.999999999999998
cpu-wattage-times-duration: 5039.999999999999
cpu-energy-raw: 0.0013999999999999998
vcpu-ratio: 4
cpu-energy-kwh: 0.00034999999999999994
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 0.32
cpu-wattage: 32
cpu-wattage-times-duration: 11520
cpu-energy-raw: 0.0032
vcpu-ratio: 4
cpu-energy-kwh: 0.0008
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 0.75
cpu-wattage: 75
cpu-wattage-times-duration: 27000
cpu-energy-raw: 0.0075
vcpu-ratio: 4
cpu-energy-kwh: 0.001875
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 1.02
cpu-wattage: 102
cpu-wattage-times-duration: 36720
cpu-energy-raw: 0.0102
vcpu-ratio: 4
cpu-energy-kwh: 0.00255
- + \ No newline at end of file diff --git a/reference/cli/index.html b/reference/cli/index.html index c750e21..5c5549a 100644 --- a/reference/cli/index.html +++ b/reference/cli/index.html @@ -8,13 +8,13 @@ Command line tool | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Command line tool

A core feature of the Impact Framework is the if-run command line tool (CLI). This is how you trigger Impact Framework to execute a certain manifest file.

We also provide several other command line tools that work in concert with if-run to enable flows such as comparing, re-executing and verifying IF output files.

This page includes reference documentation for the CLI tools, including the various commands and flags each tool exposes.

We also provide tutorial-style user documentation for these tools in the Users section.

if-run

If you have globally installed our if npm package, you can invoke the CLI using the if-run command directly in your terminal. The if-run command is an alias to npx ts-node src/index.ts, which executes the Impact Framework's src/index.ts script and acts as the entry point for Impact Framework.

if-run <args>

if-run runs the full execution cycle of a manifest file, including observe, regroup and compute phases along with aggregation and explain if they are configured in the manifest.

--manifest , -m

The --manifest flag is the only required flag and tells if-run where to find the manifest file that you want to execute. This command expects to receive the path where your manifest file is saved, as shown in the following example:

if-run --manifest examples/manifests/my-manifest.yml

--output , -o

The --output flag is optional and is used for defining a path to save your output data.

Here is an example of --output being used to define a path:

if-run --manifest examples/manifests/my-manifest.yml --output examples/outputs/my-outdata
## or using aliases
if-run -m examples/manifests/my-manifest.yml -o examples/outputs/my-outdata

--help , -h

The --help command provides information about all available commands in order to help you easily find the command you need.

Example:

if-run --help
## or using alias
if-run -h

--observe

if-run --observe runs only the observe phase of the manifest execution. This means only those plugins that generate input data are run. These are defined in the observe section of the pipeline for each component in the manifest.

An example of an observe pipeline that invokes a plugin called "azure-importer" could look as follows:

tree:
children:
child:
pipeline:
observe:
- azure-importer

--regroup

if-run --regroup runs only the regrouping phase of the manifest's execution. There has to be input data available in the manifest to regroup (or --observe has to be invoked too) and the regrouping configuration has to be included in the manifest. This config defines which parameters if-run --regroup should regroup the data by.

For example, to regroup on cloud/region and cloud/instance-type:

tree:
children:
child:
pipeline:
observe:
regroup:
- cloud/region
- cloud/instance-type

--compute

if-run --compute runs only the compute phase of the manifest's execution. The manifest passed to if-run --compute should already have input data, appropriately grouped (or you have to pass --observe --regroup too). This includes the plugins that do operations over the input data to generate output data.

For example, in a manifest that executes sum, coefficient and multiply in its compute phase:

tree:
children:
child-1:
pipeline:
observe:
compute:
- sum
- coefficient
- multiply

--debug

You can provide the --debug flag to if-run in order to display execution logs to the console. These logs show messages for each operation IF and its plugins are executing. For example, your debug logs will look similar to the following:

INFO: 2024-06-12T08:48:02.918Z: Starting IF
DEBUG: 2024-06-12T08:48:02.919Z: Loading manifest
DEBUG: 2024-06-12T08:48:02.924Z: Capturing runtime environment data
DEBUG: 2024-06-12T08:48:03.978Z: Validating manifest
DEBUG: 2024-06-12T08:48:03.980Z: Syncing parameters
DEBUG: 2024-06-12T08:48:03.980Z: Initializing plugins
DEBUG: 2024-06-12T08:48:03.981Z: Initializing Sum
DEBUG: 2024-06-12T08:48:03.981Z: Loading Sum from builtin
DEBUG: 2024-06-12T08:48:04.859Z: Initializing Coefficient
DEBUG: 2024-06-12T08:48:04.859Z: Loading Coefficient from builtin
DEBUG: 2024-06-12T08:48:04.860Z: Initializing Multiply
DEBUG: 2024-06-12T08:48:04.860Z: Loading Multiply from builtin
DEBUG: 2024-06-12T08:48:04.860Z: Computing pipeline for `sum`
DEBUG: 2024-06-12T08:48:04.861Z: Computing pipeline for `coefficient`
DEBUG: 2024-06-12T08:48:04.861Z: Computing pipeline for `multiply`
DEBUG: 2024-06-12T08:48:04.862Z: Aggregating outputs
DEBUG: 2024-06-12T08:48:04.862Z: Preparing output data

You can use the --debug flag to help debug failing IF runs. You will see exactly where in the execution pipeline an error arose. If the error arose from a plugin, this will be clear from the execution logs, for example:

INFO: 2024-06-12T08:53:21.376Z: Starting IF
DEBUG: 2024-06-12T08:53:21.376Z: Loading manifest
DEBUG: 2024-06-12T08:53:21.381Z: Capturing runtime environment data
DEBUG: 2024-06-12T08:53:22.367Z: Validating manifest
DEBUG: 2024-06-12T08:53:22.369Z: Syncing parameters
DEBUG: 2024-06-12T08:53:22.369Z: Initializing plugins
DEBUG: 2024-06-12T08:53:22.369Z: Initializing Sum
DEBUG: 2024-06-12T08:53:22.370Z: Loading Sum from builtin
DEBUG: 2024-06-12T08:53:23.165Z: Initializing Coefficient
DEBUG: 2024-06-12T08:53:23.165Z: Loading Coefficient from builtin
DEBUG: 2024-06-12T08:53:23.165Z: Initializing Multiply
DEBUG: 2024-06-12T08:53:23.165Z: Loading Multiply from builtin
DEBUG: 2024-06-12T08:53:23.165Z: Computing pipeline for `sum`
[2024-06-12 09:53:23.166 AM] error: cpu/energy is missing from the input array.

if-diff

The if-diff command line tool allows you to determine whether two manifest or output files are the same, and if not, how they differ.

if-diff needs two files to compare - a source and a target. The source file is considered to be the "true" file that another file, the target, is compared against. Note that for most purposes, it doesn't matter which file is assigned as source or target - the important thing is that if-diff receives two files. Both files should be yaml files. They are expected to be IF output files, meaning they contain all the required fields of a manifest plus the IF-generated output and execution blocks.

if-diff is run as follows:

if-diff --source file-1.yml --target file2.yml

You can also pipe the outputs from if-run directly into if-diff. This means you only provide one file to if-diff and the other comes from a new if-run run configured to send its output data to the console. This is an important feature because it allows you to receive an output file and verify that it was computed correctly and not tampered with post-execution. For example, if someone provides you with an output file, you can strip out the outputs section and re-run it with if-run, piping the outputs straight to if-diff to compare against the original you received.

If the original was correctly and honestly reported, if-diff will return a success response.

e.g.

if-run -m my-manifest | if-diff --target my-output-file.yml

if-diff matching rules

if-diff looks for differences between the source and target. However, if-diff applies its own IF-specific matching rules, ensuring that the outputs are functionally identical even if they are not precisely identical. For example, if-diff allows the order of nodes in a tree to vary between files as long as identically named components contain identical data.

Difference identifiedReport or ignore?Note
trees contain different number of nodesreportworks the same regardless whether source or target has more nodes
nodes in tree have different namesreportThere should be no named nodes existing in one file that aren't also in the other
nodes in tree contain non-identical fields and/or valuesreportthe data inside each tree component should contain identical keys/values
keys and values in context field are non- identicalreportthe same fields should exist in the context section and their values should be identical
status and error fields in execution blockreportOnly these two fields in execution are considered
order of nodes in tree are differentignoreif data is identical, position of node in tree is ignored
order of fields in contextignoreif data is identical, position of field in context is ignored
content of execution block EXCEPT status and errorignoreenvironment information is ignored

if-diff outputs

If if-diff finds no in-scope differences between the source and target then it returns a success message and exit code 0:

FILES MATCH and exit code 0.

If if-diff detects an in-scope difference between the files, it halts execution, returns exit code 1 and reports the difference to the command line.

The report includes the yaml path to the differing element in the tree, the value in the source and the value in the target, using the following schema:

Files do not match!
<yaml path to non-matching element>
source: <value in source file>
target: <value in target file>

If the difference relates to a missing node in the tree for source or target then <value in x file> should be either exists or missing and the yaml path should point to the highest level element that is missing (e.g. if an entire child component is missing, provide the path to the child component).

e.g. different values detected for a given key in an input array:

Files do not match!
tree.children.vm1[4].cpu/utilization
source: 45
target: 43

e.g. different components in tree in source and target:

Files do not match!
tree.children.child1
source: missing
target: exists

if-env

if-env is a command line tool that helps you to create local development environments where you can run manifests.

There are two use cases for this:

  1. setting up a new development environment for plugin building
  2. replicating a runtime environment for a given manifest, so you can re-execute it

commands

  • --manifest or -m: the path to a manifest whose dependencies you want to install
  • --install or -i: instructs if-env to automatically install the dependencies in the local package.json
  • --cwd or -c: forces if-env to create or update the package.json in the current working directory. This is already default behaviour when no arguments are passed to if-env, but when a manifest is passed to -m, if-env defaults to saving a package.json in the same folder as the manifest. using -cwd overrides that behaviour and uses the current working directory as the package.json target path.

Setting up new development environments using if-env

If you are creating a new manifest from scratch and want to bootstrap your way in, you can use if-env with no arguments to generate a template manifest and package.json in your current working directory. Then, all you need to do is tweak the templates for your specific use case.

For example:

mkdir my-manifest && cd my-manifest
if-env

After running these commands, you will see the following files in my-manifest:

ls my-manifest

> package.json manifest.yaml

Now, you can use these files as templates for your manifest development.

Replicating runtime environments using if-env

If you are given an IF output file and you want to re-run it, you can use if-env to install that output file's dependencies so that all the plugins in its execution pipeline can be executed.

For example, if you are given a file, output-file.yml, you can save the file to if and run

cd if
if-env -m output-file.yml

if-env will compare the installed dependencies in the package.json it sees in if with the dependencies listed in output-file.yaml. Any dependencies that are in output-file.yaml and not in if/package.json will be added to if-package.json. Then, you can run:

npm i

and you are ready to re-execute output-file.yaml in your local environment. We also provide the --install flag to instruct if-env to automatically run npm i after merging the dependencies, so you could craft a single command to install all the relevant dependencies and then run the manifest, as follows:

if-env -m output-file.yml -i && if-run -m output-file.yml

if-check

if-check is a manifest verification tool that is equivalent to running if-env and if-diff on a given manifest file. The manifest file must have outputs and an execution section for if-check to run.

The intended use case is to verify that a manifest's outputs are correct and honest. Say someone handed you a manifest as evidence of their environmental impact. You could choose to trust them, or you could run if-check to verify that their calculations are correct. Under the hood, IF is creating a development environment using the dependencies listed in the given file's execution section and then executing the file locally, then comparing the newly generated results to those in the given file.

To check a file:

if-check -m <path-to-file>

If the if-check is successful you will receive the following response:

if-check: successfully verified <filename>

If if-check was not able to verify the file because there were differences in the given and re-executed files, then you will receive the following response which includes the details of how the files differ, as per if-diff.

if-check: could not verify <filename>. The re-executed file does not match the original.

Running IF over multiple manifests with --d

Alice could also run if-check over any number of manifests in a single command, using the --directory or -d subcommand. For a folder containing multiple manifests, pass the folder path:

if-check -d /my-folder-of-manifests

Each manifest will be run through if-check in sequence.

if-csv

if-csv is a command line tool that helps to save data to CSV file.

commands

  • --manifest or -m: (optional) the path to an executed manifest
  • --output or -o: (optional) the path to save your output data in csv format
  • --params or -p: (required) the metric to export the data

There are three use cases for this:

  1. Exporting CSV with the --output flag: When the --output flag is provided, if-csv exports the data to a CSV file at the specified path. This is useful for saving data for later use or sharing with others.
if-csv -m ./my-manifest.yml -p carbon -o ./my-outdata
  1. Printing CSV to the console without the --output flag: If the --output flag is omitted, if-csv will print the CSV data directly to the console. This is useful for quick checks.
if-csv -m ./my-manifest.yml -p carbon
  1. Piping output from if-run to if-csv. By piping the output from if-run, you can chain commands to execute a manifest and then immediately export the data to a CSV file.
if-run -m ./my-manifest.yml | if-csv -p carbon -o ./my-outdata

--append

You can re-use a manifest file to make multiple batches of observations, appending the results to the existing outputs. The command that makes this possible is --append. To use --append you have to pass a manifest files that has already been computed - i.e.it already has outputs. If you do, then the newly generated outputs will be appended to the existing output data.

The use case for this is when you want to repeatedly monitor the same resource or set of resources without changign the manifest config - you just want to grab new observations. The --append command allows you to do this without havign to generate lots of individual manifest files.

example

With a computed manifest:

name: append
description: >-
a complete pipeline that starts with mocked CPU utilization data and outputs
operational carbon in gCO2eq
initialize:
plugins:
mock-observations:
path: builtin
method: MockObservations
config:
timestamp-from: '2024-03-05T00:00:04.000Z'
timestamp-to: '2024-03-05T00:00:07.000Z'
duration: 1
components:
- name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
generators:
common:
cloud/vendor: azure
randint:
cpu/energy:
min: 1
max: 99
mem/energy:
min: 1
max: 99
sum:
path: builtin
method: Sum
config:
input-parameters:
- cpu/energy
- mem/energy
output-parameter: energy
execution:
command: >-
/home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/user/Code/if/src/index.ts -m
manifests/examples/mock-cpu-util-to-carbon.yml -s
environment:
if-version: 0.4.0
os: linux
os-version: 5.15.0-107-generic
node-version: 21.4.0
date-time: 2024-06-18T14:18:44.864Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.3'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.22.4
status: success
tree:
pipeline:
compute:
- mock-observations
- sum
regroup:
- cloud/region
- name
defaults: null
inputs:
- timestamp: '2024-03-05T00:00:00.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 5
mem/energy: 10
- timestamp: '2024-03-05T00:00:01.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 71
mem/energy: 5
- timestamp: '2024-03-05T00:00:02.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 36
mem/energy: 74
outputs:
- timestamp: '2024-03-05T00:00:00.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 5
mem/energy: 10
energy: 15
- timestamp: '2024-03-05T00:00:01.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 71
mem/energy: 5
energy: 76
- timestamp: '2024-03-05T00:00:02.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 36
mem/energy: 74
energy: 110

run

npm run if-run  -- -m manifests/outputs/features/append.yaml -o manifests/outputs/features/re-append --append 

And see the following output (with new observations appended to old observations):

name: append
description: >-
a complete pipeline that starts with mocked CPU utilization data and outputs
operational carbon in gCO2eq
initialize:
plugins:
mock-observations:
path: builtin
method: MockObservations
config:
timestamp-from: '2024-03-05T00:00:04.000Z'
timestamp-to: '2024-03-05T00:00:07.000Z'
duration: 1
components:
- name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
generators:
common:
cloud/vendor: azure
randint:
cpu/energy:
min: 1
max: 99
mem/energy:
min: 1
max: 99
sum:
path: builtin
method: Sum
config:
input-parameters:
- cpu/energy
- mem/energy
output-parameter: energy
execution:
command: >-
/Users/jcrowley/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/Users/jcrowley/Development/gsf/if/src/if-run/index.ts -m
manifests/outputs/features/append.yaml -o
manifests/outputs/features/re-append --append
environment:
if-version: 0.6.0
os: macOS
os-version: 14.6.1
node-version: 20.16.0
date-time: 2024-09-04T01:05:58.758Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.16'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.23.8
status: success
tree:
pipeline:
compute:
- mock-observations
- sum
regroup:
- cloud/region
- name
defaults: null
children:
westus3:
children:
server-1:
inputs:
- timestamp: '2024-03-05T00:00:00.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 5
mem/energy: 10
- timestamp: '2024-03-05T00:00:01.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 71
mem/energy: 5
- timestamp: '2024-03-05T00:00:02.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 36
mem/energy: 74
outputs:
- timestamp: '2024-03-05T00:00:00.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 5
mem/energy: 10
energy: 15
- timestamp: '2024-03-05T00:00:01.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 71
mem/energy: 5
energy: 76
- timestamp: '2024-03-05T00:00:02.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 36
mem/energy: 74
energy: 110
- timestamp: '2024-03-05T00:00:04.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 2
mem/energy: 26
energy: 28
- timestamp: '2024-03-05T00:00:05.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 67
mem/energy: 27
energy: 94
- timestamp: '2024-03-05T00:00:06.000Z'
duration: 1
name: server-1
cloud/instance-type: Standard_E64_v3
cloud/region: westus3
cloud/vendor: azure
cpu/energy: 88
mem/energy: 6
energy: 94
- + \ No newline at end of file diff --git a/reference/errors/index.html b/reference/errors/index.html index cb077e5..648ef7e 100644 --- a/reference/errors/index.html +++ b/reference/errors/index.html @@ -8,7 +8,7 @@ Errors | Impact Framework - + @@ -17,7 +17,7 @@ The message itself indicates that the problematic element is initialize and the problem is that it is missing.

The remedy for this issue is to add an initialize block into the manifest.

InvalidGroupingError

Errors of the InvalidGroupingError are only emitted by the regroup feature. There is only one associated message; it is emitted when the requested groups do not exist in the tree.

messagecauseremedy
Invalid group ${type}.you are requested the feature to regroup the tree based on fields that do not existCheck the spelling of the values passed to regroup and ensure the values exist in the tree

WriteFileError

Errors of the WriteFileError class are caused by problems writing output data to files. Typically, this can occur when the user does not have sufficient permissions to write to a given file.

Messages

messagecauseremedy
Failed to write CSV to ${outputPath}: ${error}There was a problem writing data to filecheck that you have provided a valid output path and that you have valid permissions to write to that location

CliSourceFileError

Errors of the CliSourceFileError class are caused by problems with source manifest.

Messages

messagecauseremedy
Manifest is missing.Source manifest is not providedcheck that you have provided a path to source manifest
Given source file is not in yaml format.Source file is provided, but format is not a yaml formatcheck that you have provided valid yaml manifest

CliTargetFileError

Errors of the CliTargetFileError class are caused by problems with target manifest.

messagecauseremedy
Given target file is not in yaml format.Target file is provided, but format is not a yaml formatcheck that you have provided valid yaml manifest

PluginInitializationError

Errors of the PluginInitializationError arise when a plugin is invoked in a pipeline without having been initialized in the initialize block of the manifest being executed.

messagecauseremedy
Not initalized plugin: ${name}. Check if ${name} is in 'manifest.initalize.plugins'.a plugin invoked in a pipeline is not initializedensure all plugins that exist in pipelines across your manifest have been included in the manifest's initialize block
Provided module ${path} is invalid or not found. ${error ?? ''}a plugin invoked in a pipeline is not initializedensure all plugins that exist in pipelines across your manifest have been included in the manifest's initialize block

InvalidAggregationMethodError

Errors of the InvalidAggregationMethodError class are caused by problems in the configuration of the aggregation feature.

messagecauseremedy
Aggregation is not possible for given ${metric} since method is 'none'.You are trying to aggregate a metric whose method is set to noneUpdate the aggregation method, or choose a different metric to aggregate.

MissingAggregationParamError

Errors of the MissingAggregationParamError class are caused by problems in the configuration of the aggregation feature. Typically, the aggregation method may be undefined or you have tried to aggregate a metric that IF cannot find in the input data.

Messages

messagecauseremedy
Aggregation metric ${metric} is not found in inputs[${index}].You are trying to aggegate a metric that doesn't exist in the input dataCheck that your chosen metric is spelled correctly and that it exists in the input data by the time the aggregate feature executes.

MissingPluginMethodError

Errors of the MissingPluginMethodError class are caused by missing information in manifest's initalize.plugins section.

messagecauseremedy
Initalization param 'method' is missing.The required method field is missing from the initialize block for a given plugin.Ensure the method field is added to the initialize block for each plugin. The value should be the name of the function exported by the plugin.

MissingPluginPathError

Errors of the MissingPluginPathError class are caused by missing information in manifest's initalize.plugins section.

messagecauseremedy
Initalization param 'path' is missing.The required path field is missing from the initialize block for a given pluginEnsure the path field is added to the initialize block for each plugin. The value should be the path to the directory in if/node_modules for your plugin.

InvalidExhaustPluginError

Errors of the InvalidExhaustPluginError class are caused by using unsupported exhaust plugin.

messagecauseremedy
Invalid exhaust plugin: ${pluginName}.Unsupported or misspelled plugin was used as output methodEnsure the pluginName corresponds to supported plugins.

Plugin Errors

Plugins can emit their own custom error messages, but we still prefer those messages to be attached to one of a finite set of predefined error classes. Those classes are listed in this section.

ConfigError

Errors of the ConfigError are used when part of the config data provided to a plugin is invalid or missing.

For example the Divide plugin throws a ConfigError when it receives a denominator equal to zero.

The message should name the config element that was invalid and describe the reason why. For example:

ConfigError: "denominator" parameter is number must be greater than 0. Error code: too_small.

MissingInputDataError

Errors of the MissingInputDataError class arise because your plugin is not receiving the data it expects in input data or config. The specific messages depend on the plugin. It is expected that the messages emitted by each plugin are listed in their own documentation.

The example below is a message emitted by the interpolation plugin when the method given in config is not one of the expected enum variants:

MissingInputDataError: "interpolation" parameter is invalid enum value. expected 'spline' | 'linear', received 'dummy'. Error code: invalid_enum_value.

ProcessExecutionError

Errors of the ProcessExecutionError class arise because shell plugin have faced problems while executing the script you have provided.

RegexMismatchError

Errors of the RegexMismatchError class arise because regex plugin have faced problems while parsing given string with specified regex.

messagecauseremedy
${input} does not match the ${match} regex expressionGiven string doesn't contain anything matching given regexEnsure that input contains string which can be matched by your regex.

FetchingFileError

Errors of the FetchingFileError class arise because csv-lookup plugin have faced problems fetching given url.

messagecauseremedy
Failed fetching the file: ${filepath}.Fetching the file with given URL failedEnsure that file's url is accessible

ReadFileError

Errors of the ReadFileError class arise because csv-lookup plugin have faced problems reading given file path. The error should include the file path and the system error that was encountered when IF attempted to read data from the file:

messagecauseremedy
Failed reading the file: ${filepath}.Reading the file with given path failedEnsure that file's path is correct

MissingCSVColumnError

Errors of the MissingCSVColumnError class arise because csv-lookup plugin can't access given csv file column.

messagecauseremedy
There is no column with the name: ${columnName}.CSV file doens't contain such columnEnsure that specified query is correct and contains existing column name.

QueryDataNotFoundError

Errors of the QueryDataNotFoundError class arise because csv-lookup plugin can't find query related data in given CSV file.

messagecauseremedy
One or more of the given query parameters are not found in the target CSV file column headers.CSV file doens't contain data with given criteria.Ensure that specified query and input values have intersection with CSV file's data.

InvalidDateInInputError

Errors of the InvalidDateInInputError class arise because time-sync plugin can't parse date from inputs.

messagecauseremedy
Unexpected date datatype: ${typeof date}: ${date}Unsupported type for date.Ensure that dates in inputs are correct timestamps.

InvalidPaddingError

Errors of the InvalidPaddingError class arise when there is misconfiguration of time-sync plugin.

messagecauseremedy
Avoiding padding at ${start or end}Error on padding is enabled and config is missing padding configuration.Make sure padding is correctly configured.

InvalidInputError

Errors of the InvalidInputError class arise when there is input timestamps incompatibility while using time-sync plugin.

messagecauseremedy
Observation timestamps overlap, please check inputs.Input timestamps have overlap.Make sure that input timestamps are continuous.

ExhaustOutputArgError

Errors of the ExhaustOutputArgError class arise when there is output path issues while exporting file or exporting criteria misconfiguration.

messagecauseremedy
Output path is required, please make sure output is configured properly.Missed output path.Make sure that output path is present in your cli command.

CSVParseError

Errors of the CSVParseError occur due to a problem reading CSV file. Typically, this can occur when provided file is not a CSV.

Capturing errors in manifests

When you run a manifest, IF generates output data and either displays it in the console or saves it to a file. If IF or one of the plugins being executed throws an exception, IF can still return an output file, except instead of adding outputs, it captures the error message that caused IF to fail in the manifest's execution section. Inside the execution section, you will find two fields: status and error. The status field is either success or fail, and the error field contains the error message.

For example, the following is an output file generated by running a manifest whose input data omitted the required duration field:

name: input-error-missing-duration
description: >-
a negative test case that fails due to the required `duration` field being
omitted from input data
tags:
initialize:
plugins:
interpolate:
method: Interpolation
path: builtin
config:
method: linear
x:
- 0
- 10
- 50
- 100
'y':
- 0.12
- 0.32
- 0.75
- 1.02
input-parameter: cpu/utilization
output-parameter: cpu-factor
execution:
status: fail
error: >-
InputValidationError: "duration" parameter is required at index 0. Error
code: invalid_type.
tree:
children:
child-0:
defaults:
cpu/thermal-design-power: 100
pipeline:
compute:
- interpolate
inputs:
- timestamp: 2023-07-06T00:00
cpu/utilization: 20

No configuration is necessary - this is the default behaviour for IF if the output is configured to save to yaml and the manifest has an error causing IF to fail.

- + \ No newline at end of file diff --git a/reference/features/index.html b/reference/features/index.html index 90b793c..cd366ea 100644 --- a/reference/features/index.html +++ b/reference/features/index.html @@ -8,13 +8,13 @@ IF features | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

IF features

This page simply lists the features of Impact Framework that are not plugins or CLI tools, along with a brief description, usage instruction and link to more detailed docs.

Typically these features are enabled using a piece of manifest config.

Aggregate

Aggregate collects and summarizes data across time or across components in your tree.

How to configure

Add the following config to your manifest (this example is for aggregating "cpu/utilization" values across both time and components):

aggregation:
metrics:
- 'cpu/utilization'
type: both

Read more on aggregate

Explainer

The explainer lists the unit, description, aggregation method, and plugins of the parameter that is used in the manifest.

How to configure

You can toggle the explainer by adding the following config to your manifest:

explainer: true

You can override the parameter metadata provided in a plugin's source code by adding it to the plugin's initialize block, as follows:

explainer: true
plugins:
'sum-carbon':
path: 'builtin'
method: Sum
config:
input-parameters:
- carbon-operational
- carbon-embodied
output-parameter: carbon
parameter-metadata:
inputs:
carbon-operational:
description: "carbon emitted due to an application's execution"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum,
carbon-embodied:
description: "carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum

Read more on explainer

Inline Arithmetic Expressions

Inline arithmetic expressions allow basic mathematical operations to be embedded directly within config parameters and inputs values in manifest files. This enables dynamic calculations using constants or input variables, eliminating the need for manual pre-calculation of parameters.

Supported Symbols and Operations:

  • =: Indicates the start of an arithmetic expression.
  • Supported operators: * (multiplication), + (addition), - (subtraction), / (division).

Syntax:

  • To define an inline arithmetic expression, the string must start with an equal sign (=). For example:
    'input-parameter': '= 2 * carbon'
    This expression evaluates the multiplication of 2 by the value of the carbon parameter from the input.
  • Arithmetic operations between two constants can also be defined without using the equal sign (=):
    coefficient: 2 * 2
    This expression evaluates the multiplication of 2 by 2 directly.
  • If the parameter name contains symbols, it should be placed in the quotes. The expresion should look like:
    output-parameter: '= 2 * "carbon-product"'

Example:

config:
'input-parameter': '= 2 * carbon'
coefficient: 2 * 2
'output-parameter': '= 2 * "carbon-product"'
---
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600 * 60
carbon: = 10 * "other-param
other-param: 3

Plugin support

To enable inline arithmetic expressions in your plugin, specify it in your plugin’s definition function like this:

allowArithmeticExpressions: ['input-parameter'];

In the allowArithmeticExpressions array, list all parameters (whether in config, inputs, or outputs) that can contain arithmetic expressions. The calculations are handled internally (in the PluginFactory interface).

If your plugin doesn’t have specified parameters but has dynamic output parameters that should support evaluation, you can enable arithmeticExpressions with an empty array:

allowArithmeticExpressions: [];

To design your plugin with support for arithmetic expressions, you can use various utility functions.

  • If your plugin's config parameters must be of type number, you can use the validateArithmeticExpression function from @grnsft/if-core/utils:
import {validateArithmeticExpression} from '@grnsft/if-core/utils';

// Plugin definition

configValidation: (config: ConfigParams) => {
const configSchema = z.object({
coefficient: z.preprocess(
value => validateArithmeticExpression('coefficient', value, 'number'),
z.number()
),
'input-parameter': z.string().min(1),
'output-parameter': z.string().min(1),
});

return validate<z.infer<typeof configSchema>>(
configSchema as ZodType<any>,
config
);
},
  • If your config parameters contain arithmetic expressions like the following:
config:
keep-existing: false
from: = 4 * "if-size"
to: 'if-repo-size'

But during implementation, you need to extract the pure parameter name (e.g., if-size), you can use the getParameterFromArithmeticExpression function:

import { getParameterFromArithmeticExpression } from '@grnsft/if-core/utils';

// Plugin definition

configValidation: (config: ConfigParams) => {
const configSchema = z.object({
'keep-existing': z.boolean(),
from: z.string().min(1),
to: z.string().min(1),
});

const extractedFrom = getParameterFromArithmeticExpression(config.from);
const updatedConfig = config['keep-existing']
? config
: { ...config, 'pure-from': extractedFrom };

validate<z.infer<typeof configSchema>>(configSchema, updatedConfig);

return updatedConfig;
};
- + \ No newline at end of file diff --git a/reference/index.html b/reference/index.html index 8079051..3acf027 100644 --- a/reference/index.html +++ b/reference/index.html @@ -8,13 +8,13 @@ Reference | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Reference

In this section you will find reference documentation for the core data structures and features used in the Impact Framework.

This includes:

These are developer focused reference docs. If you are not a developer and looking for usage guides, please head over to the Using IF section.

- + \ No newline at end of file diff --git a/reference/plugins/index.html b/reference/plugins/index.html index daf3eca..80e36e2 100644 --- a/reference/plugins/index.html +++ b/reference/plugins/index.html @@ -8,13 +8,13 @@ Plugins | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Plugins

Impact Framework works by executing pipelines of plugins over input data. Those plugins are re-useable units of code that can be thought of as Lego bricks - simple blocks of code that can be assembled into complex workflows.

IF comes bundled with a standard library of builtins that allow you to do basic and/or generic operations over your data. These include simple arithmetic, regrouping data, calculating SCI scores, and running processes in a spawned shell.

Most IF plugins are created and maintained by the community. Anyone can create a plugin and share it so that other iF users can install it and use it in their pipelines.

We provide a website where you can search for plugins:

IF Explorer

You can also add your own plugins to the Explorer using this form.

built-in

IF builtins all come bundled with IF. Below you will find a list of each builtin along with a brief description of its purpose and a link to its README documentation.

  • Time Sync: Takes a heterogeneous set of time series data that might be offset, discontinuous or irregularly spaces and returns time series conforming to a user defined time grid. E.g. a user can define that all sets of observations should start at some global start time, end at some global end time and have a specific temporal resolution.

  • SCI-embodied - Calculates the embodied carbon for a component.

  • SCI: Calculates the software carbon intensity.

  • Shell - A plugin that enables external code in any language to be run in a child process

  • Sum: a generic arithmetic plugin that allows you to sum any set of input parameters.

  • Multiply: a generic arithmetic plugin that allows you to multiply any set of input parameters.

  • Coefficient: a generic arithmetic plugin that allows you to multiply any input value by a coefficient.

  • Mock Observations: A plugin for mocking observations (inputs) for testing and demo purposes.

  • Subtract: a generic plugin for subtracting one value from another

  • Divide: A generic plugin for doing arithmetic division of two values.

  • Regex: A generic plugin to match part of one string and extract it into another.

  • Exponent: A generic plugin for raising a value to a power

  • Interpolation: A generic plugin for interpolating between known points.

  • Copy Param: A generic plugin for copying a parameter to a new element in the input array, optionally deleting the original. Useful as a way to rename parameters.

  • CSV lookup: A generic plugin for querying data from CSV files.

  • Time Converter: A gerenric plugin for converting time unit of energy value to another time unit.

- + \ No newline at end of file diff --git a/users/how-to-compare-files-with-if-diff/index.html b/users/how-to-compare-files-with-if-diff/index.html index f73116b..76e5647 100644 --- a/users/how-to-compare-files-with-if-diff/index.html +++ b/users/how-to-compare-files-with-if-diff/index.html @@ -8,13 +8,13 @@ How to compare files with `if-diff` | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to compare files with if-diff

if-diff is a command line tool that allows you to compare two if-run output files. They either match according to if-diff's matching rules, or they don't. If they match, then if-diff returns a simple success response. If the differ, then if-diff returns a report of the differences it finds.

Why is this useful?

if-diff can be used to verify that a given output file was correctly executed and that it was not tampered with after it was computed. Imagine you received an output file from someone, reporting their carbon expenditure. It is better to verify than trust this person's report, so you simply delete the outputs block from the file (creating a manifest), run it through if-run and compare the output to the original file you received. All being well, the two files are identical. if not, you can see exactly where the differences are.

if-diff can also be used for debugging your own files. Maybe you have some large manifest files in development and have accidentally introduced some changes that you are now struggling to identify, but are leading to different if-run outcomes. if-diff can quickly scan your two files and tell you where the differences are so you can get back in sync.

Example: output verification

Let's say someone provides you with this output file, given-output-file.yml:

name: sum
description: successful path
tags:
initialize:
plugins:
sum:
method: Sum
path: 'builtin'
config:
input-parameters: ['cpu/energy', 'network/energy']
output-parameter: 'energy'
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- sum
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
outputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
energy: 0.0005

This manifest simply sums two components, cpu/energy and network/energy and assigns the result to energy in the outputs array. You receive this file and feel like something's not quite right. So you delete the outputs block to create test-manifest.yml:

name: sum
description: successful path
tags:
initialize:
plugins:
sum:
method: Sum
path: 'builtin'
config:
input-parameters: ['cpu/energy', 'network/energy']
output-parameter: 'energy'
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- sum
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001

Now you want to run the manifest through if-run and compare the result to the given output file. You can do this by piping the result of if-run directly into if-diff as follows:

if-run -m test-manifest.yml | if-diff --target given-output-file.yml

The result is:

Files do not match!
tree.children.child[0].energy
source: 0.002
target: 0.0005

Uh oh. It seems there has been some mistake or tampering with the outputs in given-output-file.yml. The right result of summing cpu/energy and network/energy is 0.002, but they reported 0.0005. You can now query that result with the sender and ask them to fix it.

Obviously, this is an arbitrary, simplified example, but if-diff enables you to do this kind of output verification on very complex manifests where errors are harder to spot by eye and to do it programmatically over large numbers of files.

Example: debugging

Imagine you developed a manifest that was giving you a consistent result, but now when you run it your result is different and you are not sure why. Maybe one of your colleagues changed something and forgot to tell you, maybe you accidentally inserted or removed something while you were working.

You could revert to an archived version, but you moved all the components around into a structure you prefer! if-diff has you covered. It will step through the files identifying all the functional differences between the files to help you identify the problematic one(s).

You have original-manifest.yml and new-manifest.yml. Pass them to if-diff as follows:

if-diff --source original-manifest.yml --target new-manifest.yml

if-diff will report each difference it finds. You can fix the difference and run if-diff again until you get a success response - since if-diff ignores positional differences and only considers differences in context and tree keys and values, your two manifests will run identically even though you persist your tree reorganization.

- + \ No newline at end of file diff --git a/users/how-to-export-csv-file-with-if-csv/index.html b/users/how-to-export-csv-file-with-if-csv/index.html index 6b29e50..a82c9be 100644 --- a/users/how-to-export-csv-file-with-if-csv/index.html +++ b/users/how-to-export-csv-file-with-if-csv/index.html @@ -8,13 +8,13 @@ Exporting CSV file with `if-csv` | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Exporting CSV file with if-csv

IF includes a command line tool called if-csv which is designed to export CSV files based on a specified manifest file and metric.

Example:

Let's execute this manifest file. This manifest simply sums two components, cpu/energy and network/energy and assigns the result to energy in the outputs array.

name: sum
description: successful path
tags: null
initialize:
plugins:
sum:
path: builtin
method: Sum
config:
input-parameters:
- cpu/energy
- network/energy
output-parameter: energy
execution:
command: >-
/Users/manushak/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/Users/manushak/Documents/Projects/Green-Software/if/src/if-run/index.ts -m
manifests/examples/test.yaml
environment:
if-version: 0.6.0
os: macOS
os-version: 14.6.1
node-version: 18.20.4
date-time: 2024-10-03T15:23:26.460Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.25'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.23.8
status: success
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- sum
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
outputs:
- timestamp: 2023-08-06T00:00
duration: 3600
cpu/energy: 0.001
network/energy: 0.001
energy: 0.002

To generate a CSV file in the provided path, run the following command:

if-csv -m ./sum.yaml -p energy -o ./output-sum

To print data in the console, you need to run:

if-csv -m ./sum.yaml -p energy

The output will be:

Path,2023-08-06T00:00
tree.children.child.energy,0.002

Alternatively, you can pipe the result from if-run.

if-run -m ./sum.yaml | if-csv -p energy
- + \ No newline at end of file diff --git a/users/how-to-import-plugins/index.html b/users/how-to-import-plugins/index.html index e7de2a2..354efea 100644 --- a/users/how-to-import-plugins/index.html +++ b/users/how-to-import-plugins/index.html @@ -8,13 +8,13 @@ How to load plugins | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to load plugins

Plugins are developed separately to the Impact Framework core. However, the IF core developers maintain a standard library of plugins come bundled with IF. These are known as builtins.

Builtins have to be initialized in a manifest file using the path builtin. Then they can be invoked in pipelines.

name: if-demo
description: demo pipeline
tags:
initialize:
plugins:
'sum':
path: 'builtin'
method: Sum
config:
input-parameters:
- cpu/energy
- network/energy
output-parameter: energy-sum

Other plugins are hosted externally to the IF. Anyone can build a plugin and provide it as an npm package or a public code repository (such as Github) and share it using our Explorer.

These external plugins are loaded into IF by installing locally and initializing in a manifest.

First, install the plugin by providing the path to the repository to npm install as follows:

npm install https://github.com/some-account/some-repo

Then, in the manifest's initialize section, you'll need to provide the following fields:

  • YOUR-PLUGIN-HERE: a name to reference this specific instance of the plugin. The same name has to be used to refer to this plugin instance everywhere across the manifest
  • method: the function name exported by your plugin, e.g. AzureImporter
  • path: the path to the plugin

And, if your plugin requires it, add its config too.

name: plugin-demo
description: loads plugin
tags: null
initialize:
plugins:
<YOUR-PLUGIN-HERE:
method: OutputPlugin
path: https://github.com/my-repo/my-plugin

Anyone can develop plugins. As long as you conform to our plugin specification, you can load your plugin into the Impact Framework and run it as part of a pipeline. We provide a guide to building plugins and a template to help you structure them correctly.

- + \ No newline at end of file diff --git a/users/how-to-install-if/index.html b/users/how-to-install-if/index.html index 32e7c18..549ab25 100644 --- a/users/how-to-install-if/index.html +++ b/users/how-to-install-if/index.html @@ -8,13 +8,13 @@ How to install Impact Framework | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to install Impact Framework

You can install Impact Framework either globally or locally. For most users, we recommend installing our official releases globally using npm. You can do this using the following command:

npm install -g @grnsft/if

Then, run the package using the if-run command:

if-run --manifest <path to manifest file> 

Installing locally

You can also clone the Impact Framework repositories and install them locally, useful for developers who want to make changes or build new plugins. Use the following command for local installation:

git clone https://github.com/Green-Software-Foundation/if && cd if
npm install

Then, use the following command to run Impact Framework:

npm run if-run -- --manifest <path to your manifest file>

Next, install local plugin repositories using npm link. You can do this by entering the plugin folder and running the following command:

npm link

This creates a global package with the same name as your project root directory which you can then load by passing the path in your manifest file.

Read our detailed guide to installing plugins.

- + \ No newline at end of file diff --git a/users/how-to-use-the-explain-feature/index.html b/users/how-to-use-the-explain-feature/index.html index 7cb2c03..98d0f3d 100644 --- a/users/how-to-use-the-explain-feature/index.html +++ b/users/how-to-use-the-explain-feature/index.html @@ -8,13 +8,13 @@ How to check parameters and units using `explainer` | Impact Framework - +
-
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to check parameters and units using explainer

Manifest files can get complicated, especially when there are many plugin instances initialized. It can be challenging to keep track of the flow of parameters and their units through a pipeline. To help manifest authors and auditors verify the correct flow of information through a pipeline, we provide the explainer feature.

explainer adds a block to the manifest that simply lists the parameter metadata used be the plugin's instance in the manifest. The metadata contains:

  • plugins: the list of plugins where the parameter is used
  • unit: the unit in which the parameter is expressed
  • description: a plain-language summary of the parameter
  • aggregation-method:: The appropriate method to use when aggregating the parameter across time or components (e.g. should it be summed, averaged, or held constant)

This information allows you to check that the units output by one plugin are consistent with those expected as inputs to another, in one clear itemized list in your output manifest.

Note that when the parameter has different units across instances, an error will occur.

Toggling explainer on or off

To enable the explainer feature, add the following line to your manifest, somewhere in the manifest context (e.g. above the plugins block):

explainer: true

If you set explainer to false or omit the line altogether, the explainer feature will not run.

Defining parameter metadata

Plugins are expected to ship with default values for their parameter metadata in their source code. For example, our plugin for calculating embodied carbon, SciEmbodied, includes the following metadata definition:

export const SciEmbodied = PluginFactory({
metadata: {
inputs: {
vCPUs: {
description: 'number of CPUs allocated to an application',
unit: 'CPUs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
memory: {
description: 'RAM available for a resource, in GB',
unit: 'GB',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
ssd: {
description: 'number of SSDs available for a resource',
unit: 'SSDs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
hdd: {
description: 'number of HDDs available for a resource',
unit: 'HDDs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
gpu: {
description: 'number of GPUs available for a resource',
unit: 'GPUs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
'usage-ratio': {
description:
'a scaling factor that can be used to describe the ratio of actual resource usage comapred to real device usage, e.g. 0.25 if you are using 2 out of 8 vCPUs, 0.1 if you are responsible for 1 out of 10 GB of storage, etc',
unit: 'dimensionless',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
time: {
description:
'a time unit to scale the embodied carbon by, in seconds. If not provided,time defaults to the value of the timestep duration.',
unit: 'seconds',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
},
outputs: {
'embodied-carbon': {
description: 'embodied carbon for a resource, scaled by usage',
unit: 'gCO2e',
'aggregation-method': {
time: 'sum',
component: 'sum',
},
},
},
},
});

However, there are cases where a plugin might not have parameter metadata in its source code, either because it was omitted, it was not knowable in advance, or the plugin was built before we shipped the explain feature. Sometimes, you might want to override the hard-coded defaults and use alternative metadata. In these cases, you can define new plugin metadata in the manifest file. It is considered best-practice to ensure all plugin instances have a complete set of plugin metadata.

Setting parameter metadata from the manifest file is done in the plugin instance's initialize block, as follows:

initialize:
plugins:
interpolate:
method: Interpolation
path: 'builtin'
config:
method: linear
x: [0, 10, 50, 100]
y: [0.12, 0.32, 0.75, 1.02]
input-parameter: 'cpu/utilization'
output-parameter: 'cpu-factor'
parameter-metadata:
inputs:
cpu/utilization:
description: 'portion of the total CPU capacity being used by an application'
unit: 'percentage'
aggregation-method:
time: avg
component: avg
outputs:
cpu-factor:
description: "a dimensionless intermediate used to scale a processor's thermal design power by CPU usage"
unit: 'dimensionless'
aggregation-method:
time: avg
component: avg

Example manifest

The following manifest uses three plugins: sci, sci-embodied and sum-carbon. Of these, only sci-embodied has defaults hardcoded into the plugin code. The other two do not because they are "generic" arithmetic plugins for whom the values cannot be known in advance. Therefore, we set new parameter metadata in the initialize block for sci and sum-carbon but use the hardcoded defaults for sci-embodied.

We toggle the explainer feature by adding explainer: true in the manifest context.

name: sci
description: successful path
tags:
explainer: true
initialize:
plugins:
sci-embodied:
path: 'builtin'
method: SciEmbodied
'sum-carbon':
path: 'builtin'
method: Sum
config:
input-parameters:
- carbon-operational
- embodied-carbon
output-parameter: carbon
parameter-metadata:
inputs:
carbon-operational:
description: "carbon emitted due to an application's execution"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
embodied-carbon:
description: "carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
outputs:
carbon:
description: "total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
sci:
kind: plugin
method: Sci
path: 'builtin'
config:
functional-unit: requests
parameter-metadata:
inputs:
carbon:
description: "total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
requests:
description: 'number of requests made to application in the given timestep'
unit: 'requests'
aggregation-method:
time: sum
component: sum
outputs:
sci:
description: 'software carbon intensity expressed as a rate of carbon emission per request'
unit: 'gCO2eq/request'
aggregation-method:
time: sum
component: sum
tree:
children:
child:
pipeline:
compute:
- sci-embodied
- sum-carbon
- sci
defaults:
device/emissions-embodied: 1533.120 # gCO2eq
time-reserved: 3600 # 1hr in seconds
device/expected-lifespan: 94608000 # 3 years in seconds
vcpus-allocated: 1
vcpus-total: 8
inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 5
carbon-operational: 5
requests: 100

When we execute this manifest, the following explain block is added to the output file:

explain:
vCPUs:
plugins:
- sci-embodied
unit: CPUs
description: number of CPUs allocated to an application
aggregation-method:
time: copy
component: copy
memory:
plugins:
- sci-embodied
unit: GB
description: RAM available for a resource, in GB
aggregation-method:
time: copy
component: copy
ssd:
plugins:
- sci-embodied
unit: SSDs
description: number of SSDs available for a resource
aggregation-method:
time: copy
component: copy
hdd:
plugins:
- sci-embodied
unit: HDDs
description: number of HDDs available for a resource
aggregation-method:
time: copy
component: copy
gpu:
plugins:
- sci-embodied
unit: GPUs
description: number of GPUs available for a resource
aggregation-method:
time: copy
component: copy
usage-ratio:
plugins:
- sci-embodied
unit: dimensionless
description: >-
a scaling factor that can be used to describe the ratio of actual resource
usage comapred to real device usage, e.g. 0.25 if you are using 2 out of 8
vCPUs, 0.1 if you are responsible for 1 out of 10 GB of storage, etc
aggregation-method:
time: copy
component: copy
time:
plugins:
- sci-embodied
unit: seconds
description: >-
a time unit to scale the embodied carbon by, in seconds. If not
provided,time defaults to the value of the timestep duration.
aggregation-method:
time: copy
component: copy
embodied-carbon:
plugins:
- sci-embodied
- sum-carbon
unit: gCO2eq
description: >-
carbon emitted during the production, distribution and disposal of a
hardware component, scaled by the fraction of the component's lifespan
being allocated to the application under investigation
aggregation-method:
time: sum
component: sum
carbon-operational:
plugins:
- sum-carbon
unit: gCO2eq
description: carbon emitted due to an application's execution
aggregation-method:
time: sum
component: sum
carbon:
plugins:
- sum-carbon
- sci
unit: gCO2eq
description: >-
total carbon emissions attributed to an application's usage as the sum of
embodied and operational carbon
aggregation-method:
time: sum
component: sum
requests:
plugins:
- sci
unit: requests
description: number of requests made to application in the given timestep
aggregation-method:
time: sum
component: sum
sci:
plugins:
- sci
unit: gCO2eq/request
description: >-
software carbon intensity expressed as a rate of carbon emission per
request
aggregation-method:
time: sum
component: sum

When not to use explainer

In manifests where you are only using generic plugins, or override all the metadata loaded in from the plugin source code, explainer will simply echo back information from your initialize block since all the parameter metadata is set there. In these cases, the explain block is probably redundant information as you could just read the same information in your manifest's plugins section. The point of explain is to confirm what units and parameters are being passed through a pipeline when you have a mixture of plugins from many sources whose parameter metadata is defined in-code and in-manifest.

- +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to check parameters and units using explainer

Manifest files can get complicated, especially when there are many plugin instances initialized. It can be challenging to keep track of the flow of parameters and their units through a pipeline. To help manifest authors and auditors verify the correct flow of information through a pipeline, we provide the explainer feature.

explainer adds a block to the manifest that simply lists the parameter metadata used be the plugin's instance in the manifest. The metadata contains:

  • plugins: the list of plugins where the parameter is used
  • unit: the unit in which the parameter is expressed
  • description: a plain-language summary of the parameter
  • aggregation-method:: The appropriate method to use when aggregating the parameter across time or components (e.g. should it be summed, averaged, or held constant)

This information allows you to check that the units output by one plugin are consistent with those expected as inputs to another, in one clear itemized list in your output manifest.

Note that when the parameter has different units across instances, an error will occur.

Toggling explainer on or off

To enable the explainer feature, add the following line to your manifest, somewhere in the manifest context (e.g. above the plugins block):

explainer: true

If you set explainer to false or omit the line altogether, the explainer feature will not run.

Defining parameter metadata

Plugins are expected to ship with default values for their parameter metadata in their source code. For example, our plugin for calculating embodied carbon, SciEmbodied, includes the following metadata definition:

export const SciEmbodied = PluginFactory({
metadata: {
inputs: {
vCPUs: {
description: 'number of CPUs allocated to an application',
unit: 'CPUs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
memory: {
description: 'RAM available for a resource, in GB',
unit: 'GB',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
ssd: {
description: 'number of SSDs available for a resource',
unit: 'SSDs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
hdd: {
description: 'number of HDDs available for a resource',
unit: 'HDDs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
gpu: {
description: 'number of GPUs available for a resource',
unit: 'GPUs',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
'usage-ratio': {
description:
'a scaling factor that can be used to describe the ratio of actual resource usage comapred to real device usage, e.g. 0.25 if you are using 2 out of 8 vCPUs, 0.1 if you are responsible for 1 out of 10 GB of storage, etc',
unit: 'dimensionless',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
time: {
description:
'a time unit to scale the embodied carbon by, in seconds. If not provided,time defaults to the value of the timestep duration.',
unit: 'seconds',
'aggregation-method': {
time: 'copy',
component: 'copy',
},
},
},
outputs: {
'embodied-carbon': {
description: 'embodied carbon for a resource, scaled by usage',
unit: 'gCO2e',
'aggregation-method': {
time: 'sum',
component: 'sum',
},
},
},
},
});

However, there are cases where a plugin might not have parameter metadata in its source code, either because it was omitted, it was not knowable in advance, or the plugin was built before we shipped the explain feature. Sometimes, you might want to override the hard-coded defaults and use alternative metadata. In these cases, you can define new plugin metadata in the manifest file. It is considered best-practice to ensure all plugin instances have a complete set of plugin metadata.

Setting parameter metadata from the manifest file is done in the plugin instance's initialize block, as follows:

initialize:
plugins:
interpolate:
method: Interpolation
path: 'builtin'
config:
method: linear
x: [0, 10, 50, 100]
y: [0.12, 0.32, 0.75, 1.02]
input-parameter: 'cpu/utilization'
output-parameter: 'cpu-factor'
parameter-metadata:
inputs:
cpu/utilization:
description: 'portion of the total CPU capacity being used by an application'
unit: 'percentage'
aggregation-method:
time: avg
component: avg
outputs:
cpu-factor:
description: "a dimensionless intermediate used to scale a processor's thermal design power by CPU usage"
unit: 'dimensionless'
aggregation-method:
time: avg
component: avg

Example manifest

The following manifest uses three plugins: sci, sci-embodied and sum-carbon. Of these, only sci-embodied has defaults hardcoded into the plugin code. The other two do not because they are "generic" arithmetic plugins for whom the values cannot be known in advance. Therefore, we set new parameter metadata in the initialize block for sci and sum-carbon but use the hardcoded defaults for sci-embodied.

We toggle the explainer feature by adding explainer: true in the manifest context.

name: sci
description: successful path
tags:
explainer: true
initialize:
plugins:
sci-embodied:
path: 'builtin'
method: SciEmbodied
'sum-carbon':
path: 'builtin'
method: Sum
config:
input-parameters:
- carbon-operational
- embodied-carbon
output-parameter: carbon
parameter-metadata:
inputs:
carbon-operational:
description: "carbon emitted due to an application's execution"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
embodied-carbon:
description: "carbon emitted during the production, distribution and disposal of a hardware component, scaled by the fraction of the component's lifespan being allocated to the application under investigation"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
outputs:
carbon:
description: "total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
sci:
kind: plugin
method: Sci
path: 'builtin'
config:
functional-unit: requests
parameter-metadata:
inputs:
carbon:
description: "total carbon emissions attributed to an application's usage as the sum of embodied and operational carbon"
unit: 'gCO2eq'
aggregation-method:
time: sum
component: sum
requests:
description: 'number of requests made to application in the given timestep'
unit: 'requests'
aggregation-method:
time: sum
component: sum
outputs:
sci:
description: 'software carbon intensity expressed as a rate of carbon emission per request'
unit: 'gCO2eq/request'
aggregation-method:
time: sum
component: sum
tree:
children:
child:
pipeline:
compute:
- sci-embodied
- sum-carbon
- sci
defaults:
device/emissions-embodied: 1533.120 # gCO2eq
time-reserved: 3600 # 1hr in seconds
device/expected-lifespan: 94608000 # 3 years in seconds
vcpus-allocated: 1
vcpus-total: 8
inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 5
carbon-operational: 5
requests: 100

When we execute this manifest, the following explain block is added to the output file:

explain:
sci-embodied:
inputs:
vCPUs:
description: number of CPUs allocated to an application
unit: CPUs
aggregation-method:
time: copy
component: copy
memory:
description: RAM available for a resource, in GB
unit: GB
aggregation-method:
time: copy
component: copy
ssd:
description: number of SSDs available for a resource
unit: SSDs
aggregation-method:
time: copy
component: copy
hdd:
description: number of HDDs available for a resource
unit: HDDs
aggregation-method:
time: copy
component: copy
gpu:
description: number of GPUs available for a resource
unit: GPUs
aggregation-method:
time: copy
component: copy
usage-ratio:
description: >-
a scaling factor that can be used to describe the ratio of actual
resource usage comapred to real device usage, e.g. 0.25 if you are
using 2 out of 8 vCPUs, 0.1 if you are responsible for 1 out of 10 GB
of storage, etc
unit: dimensionless
aggregation-method:
time: copy
component: copy
time:
description: >-
a time unit to scale the embodied carbon by, in seconds. If not
provided,time defaults to the value of the timestep duration.
unit: seconds
aggregation-method:
time: copy
component: copy
outputs:
embodied-carbon:
description: embodied carbon for a resource, scaled by usage
unit: gCO2eq
aggregation-method:
time: sum
component: sum
sum-carbon:
inputs:
carbon-operational:
unit: gCO2eq
description: carbon emitted due to an application's execution
aggregation-method:
time: sum
component: sum
embodied-carbon:
unit: gCO2eq
description: >-
carbon emitted during the production, distribution and disposal of a
hardware component, scaled by the fraction of the component's lifespan
being allocated to the application under investigation
aggregation-method:
time: sum
component: sum
outputs:
carbon:
unit: gCO2eq
description: >-
total carbon emissions attributed to an application's usage as the sum
of embodied and operational carbon
aggregation-method:
time: sum
component: sum
sci:
inputs:
carbon:
unit: gCO2eq
description: >-
total carbon emissions attributed to an application's usage as the sum
of embodied and operational carbon
aggregation-method:
time: sum
component: sum
functional-unit:
description: >-
the name of the functional unit in which the final SCI value should be
expressed, e.g. requests, users
unit: none
aggregation-method:
time: sum
component: sum
requests:
unit: requests
description: number of requests made to application in the given timestep
aggregation-method:
time: sum
component: sum
outputs:
sci:
unit: gCO2eq/request
description: >-
software carbon intensity expressed as a rate of carbon emission per
request
aggregation-method:
time: sum
component: sum

When not to use explainer

In manifests where you are only using generic plugins, or override all the metadata loaded in from the plugin source code, explainer will simply echo back information from your initialize block since all the parameter metadata is set there. In these cases, the explain block is probably redundant information as you could just read the same information in your manifest's plugins section. The point of explain is to confirm what units and parameters are being passed through a pipeline when you have a mixture of plugins from many sources whose parameter metadata is defined in-code and in-manifest.

+ \ No newline at end of file diff --git a/users/how-to-verify-files-with-if-check/index.html b/users/how-to-verify-files-with-if-check/index.html index 5a02274..3df9e4c 100644 --- a/users/how-to-verify-files-with-if-check/index.html +++ b/users/how-to-verify-files-with-if-check/index.html @@ -8,13 +8,13 @@ Verifying IF outputs with `if-check` | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Verifying IF outputs with if-check

IF includes a command line tool called if-check that can be used to verify the results in a manifest file.

Imagine that someone provides you with a computed manifest file that they claim demonstrates the environmental impact of their software application.

You could trust them and take their results at face value, but there are probably cases where you think someone might have made a mistake or might be massaging their data in a dishonest way. Maybe you just need to demonstrate due diligence in the quality of the data you receive and use. In these cases, you can make use of if-check.

if-check is a single command that takes a given manifest file, sets up an environment where it can be executed, executes it, and compares the newly generated results to those in the original file. This means you can independently verify the results in the file using your own local copy of IF.

Under the hood, if-check is wrapping calls to if-env and if-diff, so if you need granular control over the information flow for some reason, you could achieve the same result using separate calls to those commands.

Example

Alice is a manifest verifier. She receives a manifest from Bob. Bob is a good actor and has provided a valid file. Alice is a good cypherpunk who verifies everything.

Here is Bob's manifest:

name: bobs-manifest
description:
tags: null
initialize:
plugins:
sci:
path: builtin
method: Sci
config:
functional-unit: requests
execution:
command: >-
/home/bob/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/bob/Code/if/src/index.ts -m manifests/plugins/sci/success.yml -s
environment:
if-version: 0.4.0
os: linux
os-version: 5.15.0-113-generic
node-version: 21.4.0
date-time: 2024-06-27T14:10:29.830Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.9'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.22.4
status: success
tree:
children:
child-1:
pipeline:
observe:
regroup:
compute:
- sci
inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 5
carbon-operational: 5
carbon-embodied: 0.02
carbon: 5.02
requests: 100
outputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 5
carbon-operational: 5
carbon-embodied: 0.02
carbon: 5.02
requests: 100
sci: 0.050199999999999995
child-2:
pipeline:
observe:
regroup:
compute:
- sci
inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 8
carbon-operational: 8
carbon-embodied: 0.02
carbon: 8.02
requests: 100
outputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 8
carbon-operational: 8
carbon-embodied: 0.02
carbon: 8.02
requests: 100
sci: 0.0802

Alice runs :

if-check -m bobs-manifest.yml

And receives the response:

if-check: successfully verified bobs-manifest

Charlie also has a copy of Bob's manifest. He wants to trick Alice into thinking his SCI score is lower, so he overwrites the values in the manifest file, making them lower. Charlie's manifest looks like this:

# start
name: charlies-manifest
description:
tags: null
initialize:
plugins:
sci:
path: builtin
method: Sci
config:
functional-unit: requests
execution:
command: >-
/home/charlie/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/charlie/Code/if/src/index.ts -m manifests/plugins/sci/success.yml -s
environment:
if-version: 0.4.0
os: linux
os-version: 5.15.0-113-generic
node-version: 21.4.0
date-time: 2024-06-27T14:10:29.830Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.9'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.22.4
status: success
tree:
children:
child-1:
pipeline:
observe:
regroup:
compute:
- sci
inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 5
carbon-operational: 5
carbon-embodied: 0.02
carbon: 5.02
requests: 100
outputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 5
carbon-operational: 5
carbon-embodied: 0.02
carbon: 5.02
requests: 100
sci: 0.020199999999999995
child-2:
pipeline:
observe:
regroup:
compute:
- sci
inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 8
carbon-operational: 8
carbon-embodied: 0.02
carbon: 8.02
requests: 100
outputs:
- timestamp: 2023-07-06T00:00
duration: 3600
energy: 8
carbon-operational: 8
carbon-embodied: 0.02
carbon: 8.02
requests: 100
sci: 0.0102

Now, when Alice runs if-check -m charlies-manifest, she receives:

if-check could not verify charlies-manifest. The re-executed file does not match the original.

Files do not match!
tree.children.child-1.outputs.0.sci
source: 0.050199999999999995
target: 0.020199999999999995

Not only can Alice see that the files do not match, she can see which values Charlie manipulated.

Running IF over multiple manifests

Alice could also run if-check over any number of manifests in a single command, using the -d subcommand. For a folder containing n manifests, pass the folder path:

if-check -d /my-folder-of-manifests

Each manifest will be run through if-check in sequence.

if-check limitations

if-check can verify that a manifest is correctly calculated. However, if someone really wanted to use a fraudulent manifest, they could provide fraudulent input data not output data. There's little we can really do about this - if someone provides fake input data it is out of IF's remit. This means that although the examples above are good for demonstrating how if-check works, it's more likely to be used to check for bugs and configuration errors than it is to be used to detect fraud.

- + \ No newline at end of file diff --git a/users/how-to-write-manifests/index.html b/users/how-to-write-manifests/index.html index 946f02c..54ad504 100644 --- a/users/how-to-write-manifests/index.html +++ b/users/how-to-write-manifests/index.html @@ -8,13 +8,13 @@ How to write a manifest file | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

How to write a manifest file

The Impact Framework receives all its configuration and input data in the form of a manifest file known as an manifest. To use the framework, you will need to write a manifest file and pass its path to the command line tool. This guide will help you to understand how to construct one of these files and use it to measure the energy and carbon usage of your app.

Structure of a manifest

The basic structure of a manifest is as follows:

name:
description:
tags:
initialize:
plugins:
<PLUGIN-NAME-HERE>:
method:
path:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
defaults:
inputs:

Project metadata

The file starts with some metadata about the project. There are no strict specifications for what to put in these fields, they are for you to keep track of your manifest files and to help other users to understand your use case.

name:
description:
tags:

Initialize

The initialize fields are where you specify each individual plugin that will be initialized in your pipeline. The plugins can be initialized in any order, but can only be invoked elsewhere in the manifest if they have been initialized first here. In each case, you will need to provide the name, path and method (and config if your plugin requires it):

initialize:
plugins:
sci-m:
path: ''
method:
  • The name is the name you want this plugin instance to be recognized as by Impact Framework.
  • The path defines where IF should look for the installed plugin. For example, for our standard library of plugins you would specify builtin, for other installed plugins you use the name of the directory they are installed into in node_modules.
  • For the method field, you should provide the name of the function exported by your plugin. For example, for the sum plugin, the correct value is Sum.

Tree

The tree fields are where you define the various components of your application. Each component is defined as children, where each child's output is summed to give the overall impact. Each child can have its own plugin pipeline and its own configuration, but when none is provided, it is inherited from the tree-level configuration.

In the following example, there is only one component but the plugin pipeline contains two plugins; teads-curve and sci-m. Neither requires any config data, but certain information is required in inputs.

tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- teads-curve
- sci-m
defaults:
inputs:
- timestamp: '2023-11-02T10:35:31.820Z'
duration: 3600
total-embodied-emissions: 1533.12
time-reserved: 1
expected-lifespan: 3
resources-reserved: 1
total-resources: 8

Inputs

The most granular level of the manifest file are the inputs. This is where you can add specific data for each child. Inputs must always include a timestamp and a duration.

inputs:
- timestamp: 2023-07-06T00:00
duration: 3600
cpu-util: 45

You now have a simple manifest file that will use the plugin config and input data to run the teads-curve and sci-m plugins. The output data will be appended to the manifest under a new outputs field and saved as an output file.

More complex manifests

Complex pipelines

Whilst the manifest file we looked at above works perfectly well, it will only return the most basic output data. Most users will want to calculate an SCI score, which implies a number of additional steps:

  • operational-carbon and embodied-carbon must appear as inputs.
  • This means that sci will need to be preceded by sci-m and sci-o in the plugin pipeline.
  • In most cases, sci-o will have to be preceded by sci-e to ensure energy is available to be piped to sci-o.
  • The inputs to sci-e will most likely be coming from a plugin such as teads-curve or boavizta.
  • The sci plugin also requires functional-unit information so it can convert the estimated carbon into a useful unit.
  • You may also wish to grab your input data by querying a metrics API on a virtual machine.

The example below gives you the full pipeline implemented in a manifest. There are also several other executable example manifests in if/manifests/examples that you can run for yourself.

name: pipeline-with-aggregate
description: a full pipeline with the aggregate feature enabled
tags:
aggregation:
metrics:
- 'carbon'
type: 'both'
initialize:
plugins:
'interpolate':
method: Interpolation
path: 'builtin'
config:
method: linear
x: [0, 10, 50, 100]
y: [0.12, 0.32, 0.75, 1.02]
input-parameter: 'cpu/utilization'
output-parameter: 'cpu-factor'
parameter-metadata:
inputs:
cpu/utilization:
description: refers to CPU utilization
unit: percentage
aggregation-method:
time: avg
component: avg
outputs:
cpu-factor:
description: the factor of cpu
unit: kWh
aggregation-method:
time: avg
component: avg
'cpu-factor-to-wattage':
method: Multiply
path: builtin
config:
input-parameters: ['cpu-factor', 'cpu/thermal-design-power']
output-parameter: 'cpu-wattage'
parameter-metadata:
inputs:
cpu-factor:
description: the factor of cpu
unit: kWh
aggregation-method:
time: avg
component: avg
cpu/thermal-design-power:
description: thermal design power for a processor
unit: kwh
aggregation-method:
time: avg
component: avg
outputs:
cpu-wattage:
description: cpu in Wattage
unit: wattage
aggregation-method:
time: sum
component: sum
'wattage-times-duration':
method: Multiply
path: builtin
config:
input-parameters: ['cpu-wattage', 'duration']
output-parameter: 'cpu-wattage-times-duration'
'wattage-to-energy-kwh':
method: Divide
path: 'builtin'
config:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw
'calculate-vcpu-ratio':
method: Divide
path: 'builtin'
config:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio
'correct-cpu-energy-for-vcpu-ratio':
method: Divide
path: 'builtin'
config:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu-energy-kwh
'sci-embodied':
path: 'builtin'
method: SciEmbodied
'operational-carbon':
method: Multiply
path: builtin
config:
input-parameters: ['cpu-energy-kwh', 'grid/carbon-intensity']
output-parameter: 'carbon-operational'
'sci':
path: 'builtin'
method: Sci
config:
functional-unit-time: 1 sec
functional-unit: requests # factor to convert per time to per f.unit
parameter-metadata:
inputs:
carbon:
description: an amount of carbon emitted into the atmosphere
unit: gCO2e
aggregation-method:
time: sum
component: sum
requests:
description: factor to convert per time to per f.unit
unit: number
aggregation-method:
time: sum
component: sum
outputs:
sci:
description: carbon expressed in terms of the given functional unit
unit: gCO2e
aggregation-method:
time: avg
component: sum
'sum-carbon':
path: 'builtin'
method: Sum
config:
input-parameters:
- carbon-operational
- embodied-carbon
output-parameter: carbon
'time-sync':
method: TimeSync
path: 'builtin'
config:
start-time: '2023-12-12T00:00:00.000Z'
end-time: '2023-12-12T00:01:00.000Z'
interval: 5
allow-padding: true
tree:
children:
child-1:
pipeline:
observe:
regroup:
- cloud/region
- cloud/instance-type
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
- sci-embodied
- operational-carbon
- sum-carbon
- time-sync
# - sci
defaults:
cpu/thermal-design-power: 100
grid/carbon-intensity: 800
device/emissions-embodied: 1533.120 # gCO2eq
time-reserved: 3600 # 1hr in seconds
device/expected-lifespan: 94608000 # 3 years in seconds
vcpus-total: 8
vcpus-allocated: 1
inputs:
- timestamp: '2023-12-12T00:00:00.000Z'
cloud/instance-type: A1
cloud/region: uk-west
duration: 1
cpu/utilization: 10
requests: 10
- timestamp: '2023-12-12T00:00:01.000Z'
duration: 5
cpu/utilization: 20
cloud/instance-type: A1
cloud/region: uk-west
requests: 5
- timestamp: '2023-12-12T00:00:06.000Z'
duration: 7
cpu/utilization: 15
cloud/instance-type: A1
cloud/region: uk-west
requests: 15
- timestamp: '2023-12-12T00:00:13.000Z'
duration: 30
cloud/instance-type: A1
cloud/region: uk-west
cpu/utilization: 15
requests: 30

Complex applications

The manifest examples provided so far have only had a single component. However, Impact Framework can handle any number of nested children.

In this way, you can combine complex plugin pipelines and application architectures to calculate the energy and carbon outputs of complicated systems.

Choosing which plugins to run

The plugins are designed to be composable, but they each have specific input requirements that must be met in order for the plugins to run correctly. For example, the teads-curve plugin requires cpu/thermal-design-power to be available in the manifest. If it is not there, the plugin cannot use it to calculate cpu/energy.

It is also possible to leapfrog some plugins if you have access to high-level data. For example, perhaps you already know the energy being used by your CPU. In this case, there is no need to run teads-curve, you can simply provide cpu/energy as an input and omit teads-curve from the plugin pipeline.

We have deliberately made the plugins modular and composable so that you can be creative in developing new plugins to replace those provided as part of IF.

Adding real-life inputs

The examples above already include inputs for the components. However, you may want to input real-life data into the manifest file.

There is no one-size-fits-all solution for getting data into the manifest file. This is because there are so many possible sources for your input data, all of which have their own particular requirements related to authorization, API request syntax and return types. Therefore, the approach taken by IF is to have specific plugins for specific services.

The recommended method for integrating data is to use the plugin system of the Impact Framework. You can either use an existing specific importer plugin or write your own.

There are already some community plugins available, including plugins for fetching data from Kubernetes, GCP, and third-party data aggregators like Datadog.

If there is no fitting plugin available yet, we encourage you to write and add one for your specific use case. See developer documentation for more information on how to build a plugin.

If you already have external scripts you might have a look at the shell plugin to integrate them with the Impact Framework.

If you just need data for testing purposes, you can use the mock-observation plugin.

Running a manifest

You run a manifest by providing its path to our command line tool and a path to save the results file to. You can run a manifest named my-manifest.yml using the following command:

if-run --manifest my-manifest.yml
- + \ No newline at end of file diff --git a/users/index.html b/users/index.html index 1d50bfd..cd99aa0 100644 --- a/users/index.html +++ b/users/index.html @@ -8,13 +8,13 @@ Users | Impact Framework - +
-
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Users

This section contains information for Impact Framework users. You are a user if you want to apply the Impact Framework to your own use-case, such as using it to measure the environmental impact of your own apps running on some cloud platform.

The user documentation includes:

If you are looking for guidance for how to change or update the Impact Framework by adding new features, fixing bugs or building new plugins, you should go to our developers documentation instead.

- +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Users

This section contains information for Impact Framework users. You are a user if you want to apply the Impact Framework to your own use-case, such as using it to measure the environmental impact of your own apps running on some cloud platform.

The user documentation includes:

If you are looking for guidance for how to change or update the Impact Framework by adding new features, fixing bugs or building new plugins, you should go to our developers documentation instead.

+ \ No newline at end of file diff --git a/users/quick-start/index.html b/users/quick-start/index.html index c92c77c..e2443aa 100644 --- a/users/quick-start/index.html +++ b/users/quick-start/index.html @@ -8,13 +8,13 @@ Quick start | Impact Framework - +
This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day Graduate, in which case this disclaimer will be removed.

Quick start

This page will provide the basic instructions for getting up and running with Impact Framework.

1: Install Impact Framework

Install the Impact Framework globally using npm.

npm install -g @grnsft/if

Read our detailed guide to installing IF.

2: Create a manifest file

A manifest file contains all the configuration and input data required to measure your application's energy and carbon impacts and should have a .yml extension.

Open the file, add your data and save the file. The minimal example below runs two snapshot observations through a single plugin - all it does is multiply a value in each element of the input data by 2.

name: basic-demo
description:
tags:
initialize:
plugins:
double-a-value:
path: 'builtin'
method: Coefficient
config:
input-parameter: "cpu-utilization"
coefficient: 2
output-parameter: "cpu-utilization-doubled"

tree:
children:
child-0:
defaults:
cpu/thermal-design-power: 100
pipeline:
observe:
regroup:
compute:
- double-a-value
inputs:
- timestamp: 2023-07-06T00:00
duration: 1
cpu/utilization: 20
- timestamp: 2023-07-06T00:01
duration: 1
cpu/utilization: 80

Read our detailed guides to writing manifest files.

3: Compute your manifest file

Run the pipeline by passing the path to your manifest file to the if-run command line tool:

if-run --manifest <path-to-your-manifest>

The output will be printed to the console.

🎉Congratulations 🎉! You have just used the Impact Framework to compute a manifest file! Your challenge now is to use these principles to construct manifest files for real applications. Our docs will help!

Next steps

Now you know how to use the if-run you can start building more complex pipelines of plugins and more complicated manifest files. Your overall aim is to create a manifest file that accurately represents a real software application, and a plugin pipeline that yields an environmental metric that's important to you (e.g. carbon).

Experiment by adding more plugins to the pipeline and observe how each plugin enriches each element in the inputs array with new values.

You can also configure if to save your output data to another yaml file. To do this, add the --output flag and the path to the output file where the results are saved.

The command is then as follows:

if-run --manifest <path-to-your-impl> --output <save-path>

Explore our user documentation for walkthrough guides to common Impact Framework tasks:

- + \ No newline at end of file