diff --git a/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle b/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle index a034ab8..9854a8d 100644 --- a/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle +++ b/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle @@ -14,7 +14,7 @@ repositories { java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(19) } } diff --git a/plugins/nf-co2footprint/build.gradle b/plugins/nf-co2footprint/build.gradle index 76355e4..9fba27a 100644 --- a/plugins/nf-co2footprint/build.gradle +++ b/plugins/nf-co2footprint/build.gradle @@ -50,7 +50,7 @@ sourceSets { } ext{ - nextflowVersion = '22.10.0' + nextflowVersion = '23.07.0-edge' } dependencies { @@ -61,10 +61,10 @@ dependencies { // add here plugins depepencies // test configuration - testImplementation "org.codehaus.groovy:groovy:3.0.10" - testImplementation "org.codehaus.groovy:groovy-nio:3.0.10" + testImplementation "org.codehaus.groovy:groovy:3.0.17" + testImplementation "org.codehaus.groovy:groovy-nio:3.0.17" testImplementation "io.nextflow:nextflow:$nextflowVersion" - testImplementation ("org.codehaus.groovy:groovy-test:3.0.10") { exclude group: 'org.codehaus.groovy' } + testImplementation ("org.codehaus.groovy:groovy-test:3.0.17") { exclude group: 'org.codehaus.groovy' } testImplementation ("cglib:cglib-nodep:3.3.0") testImplementation ("org.objenesis:objenesis:3.1") testImplementation ("org.spockframework:spock-core:2.2-groovy-3.0") { exclude group: 'org.codehaus.groovy'; exclude group: 'net.bytebuddy' } diff --git a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintConfig.groovy b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintConfig.groovy index 257df4c..4078387 100644 --- a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintConfig.groovy +++ b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintConfig.groovy @@ -11,13 +11,16 @@ import groovy.transform.PackageScope * co2footprint { * file = "co2footprint.txt" * summaryFile = "co2footprint.summary.txt" + * ci = 300 + * pue = 1.4 + * powerdrawMem = 0.67 * } * * * We anotate this class as @PackageScope to restrict the access of their methods only to class in the * same package * - * @author : Sabrina Krakau + * @author Júlia Mir Pedrol , Sabrina Krakau * */ @PackageScope @@ -25,14 +28,53 @@ class CO2FootprintConfig { final private String file final private String summaryFile + final private String reportFile + final private Double ci // CI: carbon intensity + final private Double pue // PUE: power usage effectiveness efficiency, coefficient of the data centre + final private Double powerdrawMem // Power draw of memory [W per GB] + + // Retrieve CI value from file containing CI values for different locations + protected Double retrieveCi(String country) { + def dataReader = new InputStreamReader(this.class.getResourceAsStream('/ci_values.csv')) + + Double localCi = 0.0 + String line + while ( line = dataReader.readLine() ) { + def row = line.split(",") + if (row[0] == country) { + localCi = row[1].toFloat() + break + } + } + dataReader.close() + if (localCi == 0.0) + throw new IllegalArgumentException("Invalid 'country' parameter: $country. Could not be found in 'ci_values.csv'.") + + return localCi + } CO2FootprintConfig(Map map){ def config = map ?: Collections.emptyMap() file = config.file ?: CO2FootprintFactory.CO2FootprintTextFileObserver.DEF_FILE_NAME summaryFile = config.summaryFile ?: CO2FootprintFactory.CO2FootprintTextFileObserver.DEF_SUMMARY_FILE_NAME + reportFile = config.reportFile ?: CO2FootprintFactory.CO2FootprintReportObserver.DEF_REPORT_FILE_NAME + + ci = 475 + if (config.ci && config.country) + throw new IllegalArgumentException("Invalid combination of 'ci' and 'country' parameters specified for the CO2Footprint plugin. Please specify either 'ci' and 'country'!") + if (config.ci) + ci = config.ci + if (config.country) + ci = retrieveCi(config.country) + + pue = config.pue ?: 1.67 + powerdrawMem = config.powerdrawMem ?: 0.3725 } String getFile() { file } - String getSummaryFile() { summaryFile } + String getReportFile() { reportFile } + Double getCI() { ci } + Double getPUE() { pue } + Double getPowerdrawMem() { powerdrawMem } } diff --git a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintExtension.groovy b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintExtension.groovy deleted file mode 100644 index b6e3100..0000000 --- a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintExtension.groovy +++ /dev/null @@ -1,114 +0,0 @@ -package nextflow.co2footprint - - -import groovy.transform.CompileStatic -import groovy.util.logging.Slf4j -import groovyx.gpars.dataflow.DataflowReadChannel -import groovyx.gpars.dataflow.DataflowWriteChannel -import nextflow.Channel -import nextflow.Session -import nextflow.extension.CH -import nextflow.extension.DataflowHelper -import nextflow.plugin.extension.Factory -import nextflow.plugin.extension.Function -import nextflow.plugin.extension.Operator -import nextflow.plugin.extension.PluginExtensionPoint - -/** - * Example plugin extension showing how to implement a basic - * channel factory method, a channel operator and a custom function. - * - * @author : jorge - * - */ -@Slf4j -@CompileStatic -class CO2FootprintExtension extends PluginExtensionPoint { - - /* - * A session hold information about current execution of the script - */ - private Session session - - /* - * A Custom config extracted from nextflow.config under CO2footprint tag - * nextflow.config - * --------------- - * docker{ - * enabled = true - * } - * ... - * co2footprint{ - * prefix = 'Mrs' - * } - */ - private CO2FootprintConfig config - - /* - * nf-core initializes the plugin once loaded and session is ready - * @param session - */ - @Override - protected void init(Session session) { - this.session = session - this.config = new CO2FootprintConfig(session.config.navigate('co2footprint') as Map) - } - - /* - * {@code reverse} is a `producer` method and will be available to the script because: - * - * - it's public - * - it returns a DataflowWriteChannel - * - it's marked with the @Factory annotation - * - * The method can require arguments but it's not mandatory, it depends of the business logic of the method. - * - */ - @Factory - DataflowWriteChannel reverse(String message) { - final channel = CH.create() - session.addIgniter((action) -> reverseImpl(channel, message)) - return channel - } - - private void reverseImpl(DataflowWriteChannel channel, String message) { - channel.bind(message.reverse()); - channel.bind(Channel.STOP) - } - - /* - * {@code goodbye} is a *consumer* method as it receives values from a channel to perform some logic. - * - * Consumer methods are introspected by nextflow-core and include into the DSL if the method: - * - * - it's public - * - it returns a DataflowWriteChannel - * - it has only one arguments of DataflowReadChannel class - * - it's marked with the @Operator annotation - * - * a consumer method needs to proportionate 2 closures: - * - a closure to consume items (one by one) - * - a finalizer closure - * - * in this case `goodbye` will consume a message and will store it as an upper case - */ - @Operator - DataflowWriteChannel goodbye(DataflowReadChannel source) { - final target = CH.createBy(source) - final next = { target.bind("Goodbye $it".toString()) } - final done = { target.bind(Channel.STOP) } - DataflowHelper.subscribeImpl(source, [onNext: next, onComplete: done]) - return target - } - - /* - * Generate a random string - * - * Using @Function annotation we allow this function can be imported from the pipeline script - */ - @Function - String randomString(int length=9){ - new Random().with {(1..length).collect {(('a'..'z')).join(null)[ nextInt((('a'..'z')).join(null).length())]}.join(null)} - } - -} diff --git a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintFactory.groovy b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintFactory.groovy index 15fb9e2..b08f58b 100644 --- a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintFactory.groovy +++ b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintFactory.groovy @@ -42,7 +42,7 @@ import java.util.concurrent.ConcurrentHashMap /** * Implements the CO2Footprint observer factory * - * @author Sabrina Krakau + * @author Júlia Mir Pedrol , Sabrina Krakau */ @Slf4j @CompileStatic @@ -61,12 +61,14 @@ class CO2FootprintFactory implements TraceObserverFactory { // Load file containing TDP values for different CPU models protected void loadCpuTdpData(Map data) { - def inData = new InputStreamReader(this.class.getResourceAsStream('/cpu_tdp_values.csv')).text + def dataReader = new InputStreamReader(this.class.getResourceAsStream('/cpu_tdp_values.csv')) - for (String line : inData.readLines()) { + String line + while ( line = dataReader.readLine() ) { def h = line.split(",") if (h[0] != 'model_name') data[h[0]] = h[3].toFloat() } + dataReader.close() log.info "$data" } @@ -84,7 +86,7 @@ class CO2FootprintFactory implements TraceObserverFactory { result.add( new CO2FootprintTextFileObserver(co2eFile, co2eSummaryFile) ) // Generate CO2 footprint report with box-plot - def co2eReport = (CO2FootprintReportObserver.DEF_FILE_NAME as Path).complete() + def co2eReport = (this.config.getReportFile() as Path).complete() result.add( new CO2FootprintReportObserver(co2eReport) ) return result @@ -168,15 +170,15 @@ class CO2FootprintFactory implements TraceObserverFactory { // TODO handle if more memory/cpus used than requested? // Pm: power draw of memory [W per GB] - def pm = 0.3725 + def pm = config.getPowerdrawMem() /** * Remaining factors */ // PUE: efficiency coefficient of the data centre - def pue = 1.67 + def pue = config.getPUE() // CI: carbon intensity [gCO2e kWh−1] - def ci = 475 + def ci = config.getCI() /** * Calculate energy consumption [kWh] @@ -190,6 +192,10 @@ class CO2FootprintFactory implements TraceObserverFactory { def Double c = (e * ci) as Double log.info "CO2: $c" + // Return values in mWh and mg + e = e * 1000000 + c = c * 1000 + return [e, c, t as Double, nc as Double, pc as Double, uc as Double, nm as Double, pm as Double, pue as Double, ci as Double] } @@ -286,8 +292,8 @@ class CO2FootprintFactory implements TraceObserverFactory { //writer.send { co2eFile.println("Test CO2 emission is:"); co2eFile.flush() } //writer.send { PrintWriter it -> it.println("Test CO2 emission is:"); it.flush() } - co2eSummaryFile.println("The total CO2 emission is: ${HelperFunctions.convertToReadableUnits(total_co2)}g") - co2eSummaryFile.println("The total energy consumption is: ${HelperFunctions.convertToReadableUnits(total_energy,5)}Wh") + co2eSummaryFile.println("The total CO2 emission is: ${HelperFunctions.convertToReadableUnits(total_co2,3)}g") + co2eSummaryFile.println("The total energy consumption is: ${HelperFunctions.convertToReadableUnits(total_energy,3)}Wh") co2eSummaryFile.flush() co2eSummaryFile.close() @@ -356,7 +362,7 @@ class CO2FootprintFactory implements TraceObserverFactory { total_co2 += co2 // save to the file - writer.send { PrintWriter it -> it.println("${taskId}\t${HelperFunctions.convertToReadableUnits(eConsumption,5)}Wh\t${HelperFunctions.convertToReadableUnits(co2)}g\t\ + writer.send { PrintWriter it -> it.println("${taskId}\t${HelperFunctions.convertToReadableUnits(eConsumption,3)}Wh\t${HelperFunctions.convertToReadableUnits(co2,3)}g\t\ ${t} hours\t${nc}\t${pc}\t${uc}\t${HelperFunctions.convertToReadableUnits(nm,8)}B\t${HelperFunctions.convertToReadableUnits(pm)}W\t${pue}\t${HelperFunctions.convertToReadableUnits(ci)}g/kWh"); it.flush() } } @@ -389,7 +395,7 @@ class CO2FootprintFactory implements TraceObserverFactory { total_co2 += co2 // save to the file - writer.send { PrintWriter it -> it.println("${taskId}\t${HelperFunctions.convertToReadableUnits(eConsumption,5)}Wh\t${HelperFunctions.convertToReadableUnits(co2)}g\t\ + writer.send { PrintWriter it -> it.println("${taskId}\t${HelperFunctions.convertToReadableUnits(eConsumption,3)}Wh\t${HelperFunctions.convertToReadableUnits(co2,3)}g\t\ ${t} hours\t${nc}\t${pc}\t${uc}\t${HelperFunctions.convertToReadableUnits(nm,8)}B\t${HelperFunctions.convertToReadableUnits(pm)}W\t${pue}\t${HelperFunctions.convertToReadableUnits(ci)}g/kWh"); it.flush() } } } @@ -400,7 +406,7 @@ class CO2FootprintFactory implements TraceObserverFactory { */ class CO2FootprintReportObserver implements TraceObserver { - static final public String DEF_FILE_NAME = "CO2Footprint-report-${TraceHelper.launchTimestampFmt()}.html" + static final public String DEF_REPORT_FILE_NAME = "co2footprint-report-${TraceHelper.launchTimestampFmt()}.html" static final public int DEF_MAX_TASKS = 10_000 @@ -642,6 +648,7 @@ class CO2FootprintFactory implements TraceObserverFactory { readTemplate('assets/CO2FootprintReportTemplate.js') ] ] + //log.info "${tpl_fields['payload']}" final tpl = readTemplate('CO2FootprintReportTemplate.html') def engine = new GStringTemplateEngine() def html_template = engine.createTemplate(tpl) diff --git a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintReportSummary.groovy b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintReportSummary.groovy index 5673db9..9ae9983 100644 --- a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintReportSummary.groovy +++ b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintReportSummary.groovy @@ -24,7 +24,7 @@ import nextflow.trace.TraceRecord /** * Model a process summary data used to render box-plots in the execution HTML report * - * @author Paolo Di Tommaso + * @author Júlia Mir Pedrol , Sabrina Krakau */ @Slf4j @@ -202,9 +202,12 @@ class CO2FootprintReportSummary { result.q3 = quantile(sorted, 75) result.max = quantile(sorted, 100) - // discard entry with all zero - //if( result.min == 0 && result.min == result.max ) - // return null + /* + Unlike the Nextflow Report, we are not rounding the results nor discarding entries with all zeros. + This decision is taken to avoid the loss of information in the report. + Plots will show values of 0, making the representation of all processes consistent. + This class reports all values in mili-unit to increase precision. Values are converted to the required units by CO2FootprintReportTemplate.js + */ result.minLabel = minLabel result.maxLabel = maxLabel diff --git a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintResourcesAggregator.groovy b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintResourcesAggregator.groovy index 4da3d33..31c7433 100644 --- a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintResourcesAggregator.groovy +++ b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2FootprintResourcesAggregator.groovy @@ -14,7 +14,7 @@ import java.util.concurrent.Future /** * Collect and aggregate execution metrics used by execution report * - * @author Paolo Di Tommaso + * @author Júlia Mir Pedrol , Sabrina Krakau */ @Slf4j @CompileStatic diff --git a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2Record.groovy b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2Record.groovy index 0abdc72..232aded 100644 --- a/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2Record.groovy +++ b/plugins/nf-co2footprint/src/main/nextflow/co2footprint/CO2Record.groovy @@ -5,9 +5,12 @@ import groovy.util.logging.Slf4j import nextflow.trace.TraceRecord import groovy.json.StringEscapeUtils - import nextflow.co2footprint.HelperFunctions +/** + * + * @author Júlia Mir Pedrol , Sabrina Krakau + */ @Slf4j @CompileStatic class CO2Record extends TraceRecord { @@ -21,7 +24,7 @@ class CO2Record extends TraceRecord { this.energy = energy this.co2e = co2e this.name = name - this.store = store + this.store = new LinkedHashMap<>(['energy': energy, 'co2e': co2e, 'name': name]) } final public static Map FIELDS = [ @@ -40,8 +43,8 @@ class CO2Record extends TraceRecord { // TODO implement accordingly to TraceRecord Double getEnergyConsumption() { energy } - String getEnergyConsumptionReadable() { HelperFunctions.convertToReadableUnits(energy,5) } - String getCO2eReadable() { HelperFunctions.convertToReadableUnits(co2e) } + String getEnergyConsumptionReadable() { HelperFunctions.convertToReadableUnits(energy,3) } + String getCO2eReadable() { HelperFunctions.convertToReadableUnits(co2e,3) } Double getCO2e() { co2e } String getName() { name } diff --git a/plugins/nf-co2footprint/src/resources/CO2FootprintReportTemplate.html b/plugins/nf-co2footprint/src/resources/CO2FootprintReportTemplate.html index d21ec1a..35dabee 100644 --- a/plugins/nf-co2footprint/src/resources/CO2FootprintReportTemplate.html +++ b/plugins/nf-co2footprint/src/resources/CO2FootprintReportTemplate.html @@ -203,19 +203,14 @@

Workflow execution completed unsuccessfully!

-

CO2 footprint Mesures

+

CO2 Footprint Measures

These plots give an overview of the distribution of resource usage for each process.

CO2e

@@ -235,11 +230,6 @@

Energy consumption

Raw Consumption -
diff --git a/plugins/nf-co2footprint/src/resources/META-INF/MANIFEST.MF b/plugins/nf-co2footprint/src/resources/META-INF/MANIFEST.MF index 48eca33..c06c0eb 100644 --- a/plugins/nf-co2footprint/src/resources/META-INF/MANIFEST.MF +++ b/plugins/nf-co2footprint/src/resources/META-INF/MANIFEST.MF @@ -3,4 +3,4 @@ Plugin-Id: nf-co2footprint Plugin-Version: 0.4.0 Plugin-Class: nextflow.co2footprint.CO2FootprintPlugin Plugin-Provider: nextflow -Plugin-Requires: >=22.10.0 +Plugin-Requires: >=23.07.0 diff --git a/plugins/nf-co2footprint/src/resources/META-INF/extensions.idx b/plugins/nf-co2footprint/src/resources/META-INF/extensions.idx index d52daa6..fcb83ac 100644 --- a/plugins/nf-co2footprint/src/resources/META-INF/extensions.idx +++ b/plugins/nf-co2footprint/src/resources/META-INF/extensions.idx @@ -1,2 +1 @@ -nextflow.co2footprint.CO2FootprintFactory -nextflow.co2footprint.CO2FootprintExtension \ No newline at end of file +nextflow.co2footprint.CO2FootprintFactory \ No newline at end of file diff --git a/plugins/nf-co2footprint/src/resources/assets/CO2FootprintReportTemplate.js b/plugins/nf-co2footprint/src/resources/assets/CO2FootprintReportTemplate.js index 4fecb4f..81cf930 100644 --- a/plugins/nf-co2footprint/src/resources/assets/CO2FootprintReportTemplate.js +++ b/plugins/nf-co2footprint/src/resources/assets/CO2FootprintReportTemplate.js @@ -1,21 +1,14 @@ // JavaScript used to power the Nextflow Report Template output. window.data_byprocess = {}; -/* helper functions that takes an array of numbers each of each - is a integer representing a number of bytes and normalise to base 2 scale */ -function norm_mem( list ) { +/* helper functions that takes an array of numbers + units are in milliwatt-hours (mWh) or milligramm (mg) and are converted to its base unit */ +function norm_units( list ) { if( list == null ) return null; var result = new Array(list.length); for( i=0; i 24) { - days = Math.floor(duration / 24); - hours = Math.floor(duration % 24); - return days + "d " + hours + "h"; + /*function humanize(duration){ + if (duration.days() > 0) { + return duration.days() + "d " + duration.hours() + "h" } if (duration >= 1) { minutes = Math.floor(hours % 60); @@ -168,11 +153,11 @@ $(function() { seconds = Math.floor(duration * 60 * 60); return seconds.toFixed(1) + "s"; } - return Math.floor(duration * 60 * 60).toFixed(1) + "s"; - } + return duration.asSeconds().toFixed(1) + "s" + }*/ // Build the trace table - function make_index0(ms, type){ + function make_co2e(ms, type){ if (type === 'sort') { return parseInt(ms); } @@ -182,7 +167,7 @@ $(function() { if (ms == '-' || ms == 0){ return ms; } - return readable_units(ms, 4) + 'g'; + return readable_units(ms, 3) + 'g'; } function make_energy(ms, type){ if (type === 'sort') { @@ -194,9 +179,9 @@ $(function() { if (ms == '-' || ms == 0){ return ms; } - return readable_units(ms, 5) + 'Wh'; + return readable_units(ms, 3) + 'Wh'; } - function make_time(ms, type){ + /*function make_duration(ms, type){ if (type === 'sort') { return parseInt(ms); } @@ -206,9 +191,9 @@ $(function() { if (ms == '-' || ms == 0){ return ms; } - return humanize(ms); + return readable_units(ms, 4) + 'g'; } - function make_memory(ms, type){ + function make_energy(ms, type){ if (type === 'sort') { return parseInt(ms); } @@ -218,14 +203,46 @@ $(function() { if (ms == '-' || ms == 0){ return ms; } - return readable_units(ms, 8) + 'B'; + return readable_units(ms, 5) + 'Wh'; } - + function make_time(ms, type){ + if (type === 'sort') { + return parseInt(ms); + } + if($('#nf-table-humanreadable').val() == 'false'){ + return ms; + } + if (ms == '-' || ms == 0){ + return ms; + } + return humanize(ms); + } + function make_memory(ms, type){ + if (type === 'sort') { + return parseInt(ms); + } + var units = ['kB','MB','GB','TB','PB','EB','ZB','YB']; + var u = -1; + do { + bytes /= thresh; + ++u; + } while(Math.abs(bytes) >= thresh && u < units.length - 1); + return bytes.toFixed(3)+' '+units[u]; + }*/ function make_tasks_table(){ // reset if ( $.fn.dataTable.isDataTable( '#tasks_table' ) ) { $('#tasks_table').DataTable().destroy(); } + + // Column titles + var energyConsumptionTitle = 'energy consumption (mWh)'; // Default column title + var co2EmissionsTitle = 'CO2 emissions (mg)'; + if ($('#nf-table-humanreadable').val() == 'true') { + energyConsumptionTitle = 'energy consumption'; // Change the column title if the button is selected + co2EmissionsTitle = 'CO2 emissions'; + } + var table = $('#tasks_table').DataTable({ data: window.data.trace, columns: [ @@ -257,8 +274,8 @@ $(function() { return ''+script+''; } }, - { title: 'CO2 emissions', data: 'co2e', render: make_index0 }, - { title: 'energy consumption', data: 'energy', render: make_energy }, + { title: co2EmissionsTitle, data: 'co2e', render: make_co2e }, + { title: energyConsumptionTitle, data: 'energy', render: make_energy }, { title: 'Time', data: 'time', render: make_time }, { title: 'Number of cores', data: 'cores' }, { title: 'Power draw of a computing core', data: 'core_power' }, @@ -300,7 +317,7 @@ $(function() { // Insert column filter button group table.buttons().container() - .prependTo( $('#tasks_table_filter') ); + .prependTo( $('#tasks_table_filter') ); // Column filter button group onClick event to highlight active filter $('.buttons-colvisGroup').click(function(){ diff --git a/plugins/nf-co2footprint/src/resources/ci_values.csv b/plugins/nf-co2footprint/src/resources/ci_values.csv new file mode 100644 index 0000000..5aad182 --- /dev/null +++ b/plugins/nf-co2footprint/src/resources/ci_values.csv @@ -0,0 +1,2 @@ +country,ci +Germany,338.66 \ No newline at end of file diff --git a/plugins/nf-co2footprint/src/resources/cpu_tdp_values.csv b/plugins/nf-co2footprint/src/resources/cpu_tdp_values.csv index e537fed..eca2c76 100644 --- a/plugins/nf-co2footprint/src/resources/cpu_tdp_values.csv +++ b/plugins/nf-co2footprint/src/resources/cpu_tdp_values.csv @@ -1,2 +1,4 @@ model_name,tdp,cpus,tdp_per_core -AMD Ryzen Threadripper 2990WX,250,32,7.8 \ No newline at end of file +AMD Ryzen Threadripper 2990WX,250,32,7.8 +AMD EPYC 7343,190,16,11.9 +AMD EPYC 7513,200,32,6.3 diff --git a/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintDslTest.groovy b/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintDslTest.groovy index f8e89a5..d0d1dcd 100644 --- a/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintDslTest.groovy +++ b/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintDslTest.groovy @@ -51,47 +51,4 @@ class CO2FootprintDslTest extends Dsl2Spec{ pluginsMode ? System.setProperty('pf4j.mode',pluginsMode) : System.clearProperty('pf4j.mode') } - def 'should perform a hi and create a channel' () { - when: - def SCRIPT = ''' - include {reverse} from 'plugin/nf-co2footprint' - channel.reverse('hi!') - ''' - and: - def result = new MockScriptRunner([hello:[prefix:'>>']]).setScript(SCRIPT).execute() - then: - result.val == 'hi!'.reverse() - result.val == Channel.STOP - } - - def 'should store a goodbye' () { - when: - def SCRIPT = ''' - include {goodbye} from 'plugin/nf-co2footprint' - channel - .of('folks') - .goodbye() - ''' - and: - def result = new MockScriptRunner([:]).setScript(SCRIPT).execute() - then: - result.val == 'Goodbye folks' - result.val == Channel.STOP - - } - - def 'can use an imported function' () { - when: - def SCRIPT = ''' - include {randomString} from 'plugin/nf-co2footprint' - channel - .of( randomString(20) ) - ''' - and: - def result = new MockScriptRunner([:]).setScript(SCRIPT).execute() - then: - result.val.size() == 20 - result.val == Channel.STOP - } - } diff --git a/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintFactoryTest.groovy b/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintFactoryTest.groovy index f18b2ee..0bab854 100644 --- a/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintFactoryTest.groovy +++ b/plugins/nf-co2footprint/src/test/nextflow/co2footprint/CO2FootprintFactoryTest.groovy @@ -17,14 +17,19 @@ package nextflow.co2footprint import nextflow.Session +import nextflow.trace.TraceRecord import spock.lang.Specification /** * - * @author Sabrina Krakau + * @author Júlia Mir Pedrol , Sabrina Krakau */ class CO2FootprintFactoryTest extends Specification { + private BigDecimal round( double value ) { + Math.round( value * 100 ) / 100 + } + def 'should return observer' () { when: def session = Mock(Session) { getConfig() >> [:] } @@ -35,4 +40,88 @@ class CO2FootprintFactoryTest extends Specification { result[1] instanceof CO2FootprintFactory.CO2FootprintReportObserver } + def 'test co2e calculation' () { + given: + def traceRecord = new TraceRecord() + traceRecord.realtime = (1 as Long) * (3600000 as Long) + traceRecord.cpus = 1 + traceRecord.cpu_model = "Unknown model" + traceRecord.'%cpu' = 100.0 + traceRecord.memory = (7 as Long) * (1000000000 as Long) + + def session = Mock(Session) { getConfig() >> [:] } + def factory = new CO2FootprintFactory() + factory.create(session) + def results = factory.computeTaskCO2footprint(traceRecord) + + expect: + // Energy consumption converted to Wh and compared to result from www.green-algorithms.org + round(results[0]/1000) == 24.39 + // CO2 converted to g + round(results[1]/1000) == 11.59 + } + + def 'test co2e calculation with non-default pue' () { + given: + def traceRecord = new TraceRecord() + traceRecord.realtime = (1 as Long) * (3600000 as Long) + traceRecord.cpus = 1 + traceRecord.cpu_model = "Unknown model" + traceRecord.'%cpu' = 100.0 + traceRecord.memory = (7 as Long) * (1000000000 as Long) + + def session = Mock(Session) { getConfig() >> [co2footprint: [pue: 1.4]] } + def factory = new CO2FootprintFactory() + factory.create(session) + def results = factory.computeTaskCO2footprint(traceRecord) + + expect: + // Energy consumption converted to Wh and compared to result from www.green-algorithms.org + round(results[0]/1000) == 20.45 + // CO2 in g + round(results[1]/1000) == 9.71 + } + + def 'test co2e calculation with CI value retrieved for Germany' () { + given: + def traceRecord = new TraceRecord() + traceRecord.realtime = (1 as Long) * (3600000 as Long) + traceRecord.cpus = 1 + traceRecord.cpu_model = "Unknown model" + traceRecord.'%cpu' = 100.0 + traceRecord.memory = (7 as Long) * (1000000000 as Long) + + def session = Mock(Session) { getConfig() >> [co2footprint: [country: 'Germany']] } + def factory = new CO2FootprintFactory() + factory.create(session) + def results = factory.computeTaskCO2footprint(traceRecord) + + expect: + // Energy consumption converted to Wh and compared to result from www.green-algorithms.org + round(results[0]/1000) == 24.39 + // CO2 in g + round(results[1]/1000) == 8.26 + } + + def 'test co2e calculation for custom CI value' () { + given: + def traceRecord = new TraceRecord() + traceRecord.realtime = (1 as Long) * (3600000 as Long) + traceRecord.cpus = 1 + traceRecord.cpu_model = "Unknown model" + traceRecord.'%cpu' = 100.0 + traceRecord.memory = (7 as Long) * (1000000000 as Long) + + // Using current CI value for Germany, but passed over directly as CI value + def session = Mock(Session) { getConfig() >> [co2footprint: [ci: 338.66]] } + def factory = new CO2FootprintFactory() + factory.create(session) + def results = factory.computeTaskCO2footprint(traceRecord) + + expect: + // Energy consumption converted to Wh and compared to result from www.green-algorithms.org (for location Germany) + round(results[0]/1000) == 24.39 + // CO2 in g + round(results[1]/1000) == 8.26 + } }