diff --git a/cscripts/Makefile.am b/cscripts/Makefile.am
index ab76f3d..9e752e1 100755
--- a/cscripts/Makefile.am
+++ b/cscripts/Makefile.am
@@ -1,7 +1,7 @@
#############################################################################
# Makefile for cscripts scripts #
# ------------------------------------------------------------------------- #
-# Copyright (C) 2016-2021 Juergen Knoedlseder #
+# Copyright (C) 2016-2022 Juergen Knoedlseder #
# ------------------------------------------------------------------------- #
# #
# This program is free software: you can redistribute it and/or modify #
@@ -25,6 +25,7 @@ pythonscripts = $(top_builddir)/cscripts/__init__.py \
$(srcdir)/csbkgmodel.py \
$(srcdir)/cscaldb.py \
$(srcdir)/csebins.py \
+ $(srcdir)/csfootprint.py \
$(srcdir)/csinfo.py \
$(srcdir)/cslightcrv.py \
$(srcdir)/csmodelinfo.py \
@@ -88,6 +89,7 @@ parfiles = $(srcdir)/csadd2caldb.par \
$(srcdir)/cstsmapsplit.par \
$(srcdir)/csviscube.par \
$(srcdir)/csscs.par \
+ $(srcdir)/csfootprint.par \
$(srcdir)/csworkflow.par \
$(srcdir)/csfindobs.par \
$(srcdir)/csiactcopy.par \
@@ -123,6 +125,7 @@ linkscripts = csadd2caldb \
cstsmapsplit \
csviscube \
csscs \
+ csfootprint \
csworkflow \
csfindobs \
csiactcopy \
@@ -163,6 +166,7 @@ testscripts = $(srcdir)/tests/__init__.py \
$(top_srcdir)/test/test_cstsmapsplit.py \
$(top_srcdir)/test/test_csviscube.py \
$(top_srcdir)/test/test_csscs.py \
+ $(top_srcdir)/test/test_csfootprint.py \
$(top_srcdir)/test/test_csworkflow.py \
$(top_srcdir)/test/test_obsutils.py \
$(top_srcdir)/test/test_mputils.py \
diff --git a/cscripts/__init__.py.in b/cscripts/__init__.py.in
index 6d28d1d..9480f1d 100755
--- a/cscripts/__init__.py.in
+++ b/cscripts/__init__.py.in
@@ -1,7 +1,7 @@
# ==========================================================================
# cscripts Python module
#
-# Copyright (C) 2015-2021 Juergen Knoedlseder
+# Copyright (C) 2015-2022 Juergen Knoedlseder
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@ __all__ = [
"csbkgmodel",
"cscaldb",
"csebins",
+ "csfootprint",
"csinfo",
"cslightcrv",
"csmodelinfo",
@@ -94,6 +95,7 @@ from cscripts.cstsmapmerge import cstsmapmerge
from cscripts.cstsmapsplit import cstsmapsplit
from cscripts.csviscube import csviscube
from cscripts.csscs import csscs
+from cscripts.csfootprint import csfootprint
from cscripts.csworkflow import csworkflow
from cscripts import obsutils
from cscripts import modutils
diff --git a/cscripts/csfootprint.par b/cscripts/csfootprint.par
new file mode 100644
index 0000000..af3b6fc
--- /dev/null
+++ b/cscripts/csfootprint.par
@@ -0,0 +1,35 @@
+#############################################################################
+# #
+# Science Analysis Tools #
+# #
+# ------------------------------------------------------------------------- #
+# #
+# File: csfootprint.par #
+# #
+# Author: Juergen Knoedlseder #
+# Jurgen.Knodlseder@irap.omp.eu #
+# IRAP #
+# #
+# Purpose: Parameter file for the csfootprint script #
+# #
+#############################################################################
+#
+# Input and output parameters
+#============================
+infile, f, h, $HOME/.gamma/statistics.xml,,, "Input high-level statistics XML file"
+outfile, f, a, NONE,,, "Output graphics file (NONE if no graphics should be generated)"
+
+#
+# Script parameters
+#==================
+tmin, t, a, NONE,,, "Start time for report (UTC string, JD, MJD or MET in seconds)"
+tmax, t, a, NONE,,, "Stop time for report (UTC string, JD, MJD or MET in seconds)"
+
+#
+# Standard parameters
+#====================
+chatter, i, h, 2,0,4, "Chattiness of output"
+clobber, b, h, yes,,, "Overwrite existing output files with new output files?"
+debug, b, h, no,,, "Activate debugging mode?"
+mode, s, h, ql,,, "Mode of automatic parameters"
+logfile, f, h, csfootprint.log,,, "Log filename"
diff --git a/cscripts/csfootprint.py b/cscripts/csfootprint.py
new file mode 100755
index 0000000..57f549d
--- /dev/null
+++ b/cscripts/csfootprint.py
@@ -0,0 +1,551 @@
+#! /usr/bin/env python
+# ==========================================================================
+# Carbon footprint report script
+#
+# Copyright (C) 2022 Juergen Knoedlseder
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# ==========================================================================
+import sys
+import gammalib
+import ctools
+
+
+# ================= #
+# csfootprint class #
+# ================= #
+class csfootprint(ctools.cscript):
+ """
+ Carbon footprint report script
+ """
+
+ # Constructor
+ def __init__(self, *argv):
+ """
+ Constructor
+
+ Parameters
+ ----------
+ argv : list of str
+ List of IRAF command line parameter strings of the form
+ ``parameter=3``.
+ """
+ # Initialise application by calling the base class constructor
+ self._init_cscript(self.__class__.__name__, ctools.__version__, argv)
+
+ # Return
+ return
+
+ # State methods for pickling
+ def __getstate__(self):
+ """
+ Extend ctools.cscript __getstate__ method
+
+ Returns
+ -------
+ state : dict
+ Pickled instance
+ """
+ # Set pickled dictionary
+ state = {'base' : ctools.cscript.__getstate__(self)}
+
+ # Return pickled dictionary
+ return state
+
+ def __setstate__(self, state):
+ """
+ Extend ctools.cscript __setstate__ method
+
+ Parameters
+ ----------
+ state : dict
+ Pickled instance
+ """
+ # Set state
+ ctools.cscript.__setstate__(self, state['base'])
+
+ # Return
+ return
+
+
+ # Private methods
+ def _get_parameters(self):
+ """
+ Get parameters from parfile
+ """
+ # Query input parameters
+ self['infile'].filename()
+ if self['tmin'].is_valid():
+ self['tmin'].time()
+ if self['tmax'].is_valid():
+ self['tmax'].time()
+
+ # Query ahead output model filename
+ if self._read_ahead():
+ if self['outfile'].is_valid():
+ self['outfile'].filename()
+
+ # Write input parameters into logger
+ self._log_parameters(gammalib.TERSE)
+
+ # Return
+ return
+
+ # Load statistics
+ def _load_statistics(self):
+ """
+ Load statistics
+ """
+ # Initialise statistics
+ statistics = {}
+
+ # Get filename
+ infile = self['infile'].filename()
+
+ # Load XML file
+ xml = gammalib.GXml()
+ xml.load(infile)
+
+ # Get useful nodes
+ header = xml.element('statistics > header')
+ data = xml.element('statistics > data')
+ dates = header.element('dates')
+ countries = data.element('countries')
+ versions = data.element('versions')
+ daily = data.element('daily')
+
+ # Extract header dates information
+ statistics['creation'] = dates.element('creation').string()
+ statistics['modified'] = dates.element('modified').string()
+ statistics['start'] = dates.element('start').string()
+ statistics['stop'] = dates.element('stop').string()
+
+ # Set used time interval [start,stop]
+ if self['tmin'].is_valid():
+ start = self['tmin'].time().utc()
+ if start < statistics['start']:
+ start = statistics['start']
+ else:
+ start = statistics['start']
+ if self['tmax'].is_valid():
+ stop = self['tmax'].time().utc()
+ if stop > statistics['stop']:
+ stop = statistics['stop']
+ else:
+ stop = statistics['stop']
+
+ # Set used time interval
+ statistics['use_start'] = start
+ statistics['use_stop'] = stop
+
+ # Extract list of countries
+ statistics['countries'] = []
+ num = countries.elements()
+ for i in range(num):
+ element = countries.element(i)
+ country = element.name()
+ calls = element.attribute('calls')
+ wall = element.attribute('wall')
+ cpu = element.attribute('cpu')
+ gCO2e = element.attribute('gCO2e')
+ entry = {'country': country, 'calls': calls, 'wall': wall,
+ 'cpu': cpu, 'gCO2e': gCO2e}
+ statistics['countries'].append(entry)
+
+ # Extract list of versions
+ statistics['versions'] = []
+ num = versions.elements()
+ for i in range(num):
+ element = versions.element(i)
+ version = element.name()
+ calls = element.attribute('calls')
+ wall = element.attribute('wall')
+ cpu = element.attribute('cpu')
+ gCO2e = element.attribute('gCO2e')
+ entry = {'version': version, 'calls': calls, 'wall': wall,
+ 'cpu': cpu, 'gCO2e': gCO2e}
+ statistics['versions'].append(entry)
+
+ # Extract daily and tools statistics
+ statistics['daily'] = []
+ statistics['tools'] = []
+ num = daily.elements()
+ total_calls = 0
+ total_wall = 0.0
+ total_cpu = 0.0
+ total_gCO2e = 0.0
+ for i in range(num):
+
+ # Get date
+ element = daily.element(i)
+ date = element.element('value',0).string()
+
+ # Continue if date is out of range
+ if date < start[0:10] or date > stop[0:10]:
+ continue
+
+ # Collect information
+ tools = element.element('tools',0)
+ ntools = tools.elements()
+ sum_calls = 0
+ sum_wall = 0.0
+ sum_cpu = 0.0
+ sum_gCO2e = 0.0
+ for k in range(ntools):
+
+ # Extract information
+ tool = tools.element(k)
+ name = tool.name()
+ calls = int(tool.attribute('calls'))
+ wall = float(tool.attribute('wall'))
+ cpu = float(tool.attribute('cpu'))
+ gCO2e = float(tool.attribute('gCO2e'))
+
+ # Update sums
+ sum_calls += calls
+ sum_wall += wall
+ sum_cpu += cpu
+ sum_gCO2e += gCO2e
+
+ # Update tools data
+ if name in statistics['tools']:
+ statistics['tools'][name]['calls'] += calls
+ statistics['tools'][name]['wall'] += wall
+ statistics['tools'][name]['cpu'] += cpu
+ statistics['tools'][name]['gCO2e'] += gCO2e
+ else:
+ entry = {'name': name, 'calls': calls, 'wall': wall,
+ 'cpu': cpu, 'gCO2e': gCO2e}
+ statistics['tools'].append(entry)
+
+ # Update totals
+ total_calls += sum_calls
+ total_wall += sum_wall
+ total_cpu += sum_cpu
+ total_gCO2e += sum_gCO2e
+
+ # Set entry
+ entry = {'date': date, 'calls': sum_calls, 'wall': sum_wall,
+ 'cpu': sum_cpu, 'gCO2e': sum_gCO2e}
+ statistics['daily'].append(entry)
+
+ # Update total statistics
+ statistics['calls'] = total_calls
+ statistics['wall'] = total_wall
+ statistics['cpu'] = total_cpu
+ statistics['gCO2e'] = total_gCO2e
+
+ # Return statistics
+ return statistics
+
+ # Format time
+ def _format_time(self, seconds):
+ """
+ Format time
+
+ Parameters
+ ----------
+ seconds : float
+ Time in seconds
+ """
+ # Format according to precision
+ if seconds < 60.0:
+ format = '%.3f seconds' % (seconds)
+ elif seconds < 3600.0:
+ format = '%.3f minutes' % (seconds/60.0)
+ else:
+ format = '%.3f hours' % (seconds/3600.0)
+
+ # Return format
+ return format
+
+ # Format carbon footprint
+ def _format_footprint(self, gCO2e):
+ """
+ Format carbon footprint
+
+ Parameters
+ ----------
+ gCO2e : float
+ Carbon footprint in gCO2e
+ """
+ # Format according to precision
+ if gCO2e < 1000.0:
+ format = '%.3f g CO2e' % (gCO2e)
+ elif seconds < 1.0e6:
+ format = '%.3f kg CO2e' % (gCO2e/1000.0)
+ else:
+ format = '%.3f t CO2e' % (gCO2e/1.0e6)
+
+ # Return format
+ return format
+
+ # Log global statistics
+ def _global_statistics(self, statistics):
+ """
+ Log global statistics
+
+ Parameters
+ ----------
+ statistics : dict
+ Statistics dictionary
+ """
+ # Log header
+ self._log_header1(gammalib.TERSE, 'Global statistics')
+
+ # Derive information
+ tstart = gammalib.GTime(statistics['use_start'])
+ tstop = gammalib.GTime(statistics['use_stop'])
+ duration = tstop - tstart
+ if statistics['cpu'] != 0.0:
+ ci_cpu = statistics['gCO2e']/(statistics['cpu']/3600.0)
+ else:
+ ci_cpu = 0.0
+ if duration != 0.0:
+ fp_dur = statistics['gCO2e']/(duration * gammalib.sec2day)
+ else:
+ fp_dur = 0.0
+ fp_yr = fp_dur * 365.25
+ dur_str = self._format_time(duration)
+ if statistics['wall'] != 0.0:
+ load_str = '%.1f %%' % (statistics['cpu']/statistics['wall']*100.0)
+ else:
+ load_str = 'undefined'
+ ci_cpu_str = self._format_footprint(ci_cpu) + ' / CPU hour'
+ fp_dur_str = self._format_footprint(fp_dur) + ' / day'
+ fp_yr_str = self._format_footprint(fp_yr) + ' / year'
+ wall_str = self._format_time(statistics['wall'])
+ cpu_str = self._format_time(statistics['cpu'])
+ gCO2e_str = self._format_footprint(statistics['gCO2e'])
+ date_str = '%s - %s' % (statistics['start'], statistics['stop'])
+ use_str = '%s - %s' % (statistics['use_start'], statistics['use_stop'])
+
+ # Log report information
+ self._log_value(gammalib.NORMAL, 'Creation date', statistics['creation'])
+ self._log_value(gammalib.NORMAL, 'Last statistics update', statistics['modified'])
+ self._log_value(gammalib.NORMAL, 'Statistics date interval', date_str)
+ self._log_value(gammalib.NORMAL, 'Used date interval', use_str)
+ self._log_value(gammalib.NORMAL, 'Duration of used interval', dur_str)
+ self._log_value(gammalib.NORMAL, 'Total number of ctool runs', statistics['calls'])
+ self._log_value(gammalib.NORMAL, 'Total wall clock time', wall_str)
+ self._log_value(gammalib.NORMAL, 'Total CPU time', cpu_str)
+ self._log_value(gammalib.NORMAL, 'Average CPU load', load_str)
+ self._log_value(gammalib.NORMAL, 'Total carbon footprint', gCO2e_str)
+ self._log_value(gammalib.NORMAL, 'Average carbon intensity', ci_cpu_str)
+ self._log_value(gammalib.NORMAL, 'Average daily footprint', fp_dur_str)
+ self._log_value(gammalib.NORMAL, 'Expected annual footprint', fp_yr_str)
+
+ # Return
+ return
+
+ # Log daily statistics
+ def _daily_statistics(self, statistics):
+ """
+ Log daily statistics
+
+ Parameters
+ ----------
+ statistics : dict
+ Statistics dictionary
+ """
+ # Log header
+ self._log_header1(gammalib.TERSE, 'Daily statistics')
+
+ # Log daily carbon footprint statistics
+ self._log_header3(gammalib.NORMAL, 'Carbon footprint')
+
+ # Loop over daily entries
+ for entry in statistics['daily']:
+ self._log_value(gammalib.NORMAL, entry['date'], self._format_footprint(entry['gCO2e']))
+
+ # Log daily run statistics
+ self._log_header3(gammalib.NORMAL, 'ctools or cscript calls')
+
+ # Loop over daily entries
+ for entry in statistics['daily']:
+ self._log_value(gammalib.NORMAL, entry['date'], entry['calls'])
+
+ # Log daily carbon footprint statistics
+ self._log_header3(gammalib.NORMAL, 'Used wall clock time')
+
+ # Loop over daily entries
+ for entry in statistics['daily']:
+ self._log_value(gammalib.NORMAL, entry['date'], self._format_time(entry['wall']))
+
+ # Log daily carbon footprint statistics
+ self._log_header3(gammalib.NORMAL, 'Used CPU time')
+
+ # Loop over daily entries
+ for entry in statistics['daily']:
+ self._log_value(gammalib.NORMAL, entry['date'], self._format_time(entry['cpu']))
+
+ # Return
+ return
+
+ # Log tools statistics
+ def _tools_statistics(self, statistics):
+ """
+ Log tools statistics
+
+ Parameters
+ ----------
+ statistics : dict
+ Statistics dictionary
+ """
+ # Get Python version information
+ req_version = (2,4)
+ cur_version = sys.version_info
+
+ # Log header
+ self._log_header1(gammalib.TERSE, 'ctools and cscripts statistics')
+
+ # Log daily carbon footprint statistics
+ self._log_header3(gammalib.NORMAL, 'Carbon footprint')
+
+ # Optionally sort list
+ if cur_version > req_version:
+ sorted_entries = sorted(statistics['tools'], key=lambda d: d['gCO2e'], reverse=True)
+ else:
+ sorted_entries = statistics['tools']
+
+ # Loop over entries
+ for i, entry in enumerate(sorted_entries):
+ if i < 10:
+ level = gammalib.NORMAL
+ else:
+ level = gammalib.EXPLICIT
+ self._log_value(level, entry['name'], self._format_footprint(entry['gCO2e']))
+ if len(sorted_entries) > 9 and self['chatter'].integer() < 3:
+ self._log_string(gammalib.NORMAL, ' ... (list truncated after 10 entries) ...')
+
+ # Log daily run statistics
+ self._log_header3(gammalib.NORMAL, 'ctools or cscript calls')
+
+ # Optionally sort list
+ if cur_version > req_version:
+ sorted_entries = sorted(statistics['tools'], key=lambda d: d['calls'], reverse=True)
+ else:
+ sorted_entries = statistics['tools']
+
+ # Loop over entries
+ for i, entry in enumerate(sorted_entries):
+ if i < 10:
+ level = gammalib.NORMAL
+ else:
+ level = gammalib.EXPLICIT
+ self._log_value(level, entry['name'], entry['calls'])
+ if len(sorted_entries) > 9 and self['chatter'].integer() < 3:
+ self._log_string(gammalib.NORMAL, ' ... (list truncated after 10 entries) ...')
+
+ # Log daily carbon footprint statistics
+ self._log_header3(gammalib.NORMAL, 'Used wall clock time')
+
+ # Optionally sort list
+ if cur_version > req_version:
+ sorted_entries = sorted(statistics['tools'], key=lambda d: d['wall'], reverse=True)
+ else:
+ sorted_entries = statistics['tools']
+
+ # Loop over entries
+ for i, entry in enumerate(sorted_entries):
+ if i < 10:
+ level = gammalib.NORMAL
+ else:
+ level = gammalib.EXPLICIT
+ self._log_value(level, entry['name'], self._format_time(entry['wall']))
+ if len(sorted_entries) > 9 and self['chatter'].integer() < 3:
+ self._log_string(gammalib.NORMAL, ' ... (list truncated after 10 entries) ...')
+
+ # Log daily carbon footprint statistics
+ self._log_header3(gammalib.NORMAL, 'Used CPU time')
+
+ # Optionally sort list
+ if cur_version > req_version:
+ sorted_entries = sorted(statistics['tools'], key=lambda d: d['cpu'], reverse=True)
+ else:
+ sorted_entries = statistics['tools']
+
+ # Loop over entries
+ for i, entry in enumerate(sorted_entries):
+ if i < 10:
+ level = gammalib.NORMAL
+ else:
+ level = gammalib.EXPLICIT
+ self._log_value(level, entry['name'], self._format_time(entry['cpu']))
+ if len(sorted_entries) > 9 and self['chatter'].integer() < 3:
+ self._log_string(gammalib.NORMAL, ' ... (list truncated after 10 entries) ...')
+
+
+ # Return
+ return
+
+
+ # Public methods
+ def run(self):
+ """
+ Run the script
+ """
+ # Switch screen logging on in debug mode
+ if self._logDebug():
+ self._log.cout(True)
+
+ # Get parameters
+ self._get_parameters()
+
+ # Log header
+ self._log_header1(gammalib.TERSE, 'Load statistics data')
+
+ # Load statistics
+ statistics = self._load_statistics()
+
+ # Log statistics
+ self._global_statistics(statistics)
+ self._daily_statistics(statistics)
+ self._tools_statistics(statistics)
+
+ # Return
+ return
+
+ def save(self):
+ """
+ Save something
+ """
+ # Write header
+ self._log_header1(gammalib.TERSE, 'Save graphics')
+
+ # Continue only if filename is valid
+ if self['outfile'].is_valid():
+
+ # Implement saving of graphics file
+ pass
+
+ # ... signal that no graphics file was specified
+ else:
+ self._log_value(gammalib.NORMAL, 'Graphics file', 'not specified')
+
+ # Return
+ return
+
+
+# ======================== #
+# Main routine entry point #
+# ======================== #
+if __name__ == '__main__':
+
+ # Create instance of application
+ app = csfootprint(sys.argv)
+
+ # Execute application
+ app.execute()
diff --git a/doc/source/users/environment.rst b/doc/source/users/environment.rst
index 7c1d516..baf8640 100755
--- a/doc/source/users/environment.rst
+++ b/doc/source/users/environment.rst
@@ -60,4 +60,104 @@ computation took, and :math:`{\rm Carbon\,\,footprint}` is the carbon footprint
The carbon footprint of all ctools and cscript runs are stored in an ASCII file
that you will find in a folder under your home directory, named ``.gamma/statistics.csv``.
+This file is regularly scanned and purged by the GammaLib daemon that creates the
+high-level statistics file ``.gamma/statistics.xml`` that is in the same folder. You may
+display the content of this file using the ``csfootprint`` script:
+
+.. code-block:: bash
+
+ $ csfootprint debug=yes
+ Start time for report (UTC string, JD, MJD or MET in seconds) [NONE]
+ Stop time for report (UTC string, JD, MJD or MET in seconds) [NONE]
+ Output graphics file (NONE if no graphics should be generated) [NONE]
+ ...
+ 2022-02-20T14:38:29: +===================+
+ 2022-02-20T14:38:29: | Global statistics |
+ 2022-02-20T14:38:29: +===================+
+ 2022-02-20T14:38:29: Creation date .............: 2022-02-19T22:09:05
+ 2022-02-20T14:38:29: Last statistics update ....: 2022-02-20T14:35:20
+ 2022-02-20T14:38:29: Statistics date interval ..: 2022-02-18T22:21:56 - 2022-02-20T14:35:17
+ 2022-02-20T14:38:29: Used date interval ........: 2022-02-18T22:21:56 - 2022-02-20T14:35:17
+ 2022-02-20T14:38:29: Duration of used interval .: 40.222 hours
+ 2022-02-20T14:38:29: Total number of ctool runs : 3104
+ 2022-02-20T14:38:29: Total wall clock time .....: 34.200 minutes
+ 2022-02-20T14:38:29: Total CPU time ............: 27.543 minutes
+ 2022-02-20T14:38:29: Average CPU load ..........: 80.5 %
+ 2022-02-20T14:38:29: Total carbon footprint ....: 1.623 g CO2e
+ 2022-02-20T14:38:29: Average carbon intensity ..: 3.535 g CO2e / CPU hour
+ 2022-02-20T14:38:29: Average daily footprint ...: 0.968 g CO2e / day
+ 2022-02-20T14:38:29: Expected annual footprint .: 353.632 g CO2e / year
+ 2022-02-20T14:38:29:
+ 2022-02-20T14:38:29: +==================+
+ 2022-02-20T14:38:29: | Daily statistics |
+ 2022-02-20T14:38:29: +==================+
+ 2022-02-20T14:38:29: === Carbon footprint ===
+ 2022-02-20T14:38:29: 2022-02-18 ................: 1.336 g CO2e
+ 2022-02-20T14:38:29: 2022-02-19 ................: 0.010 g CO2e
+ 2022-02-20T14:38:29: 2022-02-20 ................: 0.277 g CO2e
+ 2022-02-20T14:38:29: === ctools or cscript calls ===
+ 2022-02-20T14:38:29: 2022-02-18 ................: 2895
+ 2022-02-20T14:38:29: 2022-02-19 ................: 80
+ 2022-02-20T14:38:29: 2022-02-20 ................: 129
+ 2022-02-20T14:38:29: === Used wall clock time ===
+ 2022-02-20T14:38:29: 2022-02-18 ................: 25.483 minutes
+ 2022-02-20T14:38:29: 2022-02-19 ................: 1.033 minutes
+ 2022-02-20T14:38:29: 2022-02-20 ................: 7.683 minutes
+ 2022-02-20T14:38:29: === Used CPU time ===
+ 2022-02-20T14:38:29: 2022-02-18 ................: 22.682 minutes
+ 2022-02-20T14:38:29: 2022-02-19 ................: 9.895 seconds
+ 2022-02-20T14:38:29: 2022-02-20 ................: 4.695 minutes
+ 2022-02-20T14:38:29:
+ 2022-02-20T14:38:29: +================================+
+ 2022-02-20T14:38:29: | ctools and cscripts statistics |
+ 2022-02-20T14:38:29: +================================+
+ 2022-02-20T14:38:29: === Carbon footprint ===
+ 2022-02-20T14:38:29: ctlike ....................: 0.318 g CO2e
+ 2022-02-20T14:38:29: cspull ....................: 0.186 g CO2e
+ 2022-02-20T14:38:29: csbkgmodel ................: 0.178 g CO2e
+ 2022-02-20T14:38:29: ctobssim ..................: 0.172 g CO2e
+ 2022-02-20T14:38:29: ctobssim ..................: 0.087 g CO2e
+ 2022-02-20T14:38:29: comobsbin .................: 0.081 g CO2e
+ 2022-02-20T14:38:29: comlixmap .................: 0.069 g CO2e
+ 2022-02-20T14:38:29: ctulimit ..................: 0.064 g CO2e
+ 2022-02-20T14:38:29: cstsdist ..................: 0.058 g CO2e
+ 2022-02-20T14:38:29: ctselect ..................: 0.044 g CO2e
+ 2022-02-20T14:38:29: ... (list truncated after 10 entries) ...
+ 2022-02-20T14:38:29: === ctools or cscript calls ===
+ 2022-02-20T14:38:29: ctobssim ..................: 1795
+ 2022-02-20T14:38:29: ctlike ....................: 220
+ 2022-02-20T14:38:29: ctselect ..................: 87
+ 2022-02-20T14:38:29: csfootprint ...............: 80
+ 2022-02-20T14:38:29: cscaldb ...................: 79
+ 2022-02-20T14:38:29: csscs .....................: 55
+ 2022-02-20T14:38:29: ctulimit ..................: 54
+ 2022-02-20T14:38:29: ctcubemask ................: 51
+ 2022-02-20T14:38:29: csphagen ..................: 46
+ 2022-02-20T14:38:29: ctbin .....................: 31
+ 2022-02-20T14:38:29: ... (list truncated after 10 entries) ...
+ 2022-02-20T14:38:29: === Used wall clock time ===
+ 2022-02-20T14:38:29: ctlike ....................: 5.367 minutes
+ 2022-02-20T14:38:29: cspull ....................: 3.817 minutes
+ 2022-02-20T14:38:29: csbkgmodel ................: 3.067 minutes
+ 2022-02-20T14:38:29: ctobssim ..................: 2.967 minutes
+ 2022-02-20T14:38:29: csfootprint ...............: 2.300 minutes
+ 2022-02-20T14:38:29: ctobssim ..................: 1.483 minutes
+ 2022-02-20T14:38:29: comobsbin .................: 1.367 minutes
+ 2022-02-20T14:38:29: cstsdist ..................: 1.300 minutes
+ 2022-02-20T14:38:29: comlixmap .................: 1.183 minutes
+ 2022-02-20T14:38:29: ctselect ..................: 1.083 minutes
+ 2022-02-20T14:38:29: ... (list truncated after 10 entries) ...
+ 2022-02-20T14:38:29: === Used CPU time ===
+ 2022-02-20T14:38:29: ctlike ....................: 5.392 minutes
+ 2022-02-20T14:38:29: cspull ....................: 3.157 minutes
+ 2022-02-20T14:38:29: csbkgmodel ................: 3.029 minutes
+ 2022-02-20T14:38:29: ctobssim ..................: 2.921 minutes
+ 2022-02-20T14:38:29: ctobssim ..................: 1.469 minutes
+ 2022-02-20T14:38:29: comobsbin .................: 1.367 minutes
+ 2022-02-20T14:38:29: comlixmap .................: 1.169 minutes
+ 2022-02-20T14:38:29: ctulimit ..................: 1.086 minutes
+ 2022-02-20T14:38:29: cstsdist ..................: 58.978 seconds
+ 2022-02-20T14:38:29: ctselect ..................: 45.005 seconds
+ 2022-02-20T14:38:29: ... (list truncated after 10 entries) ...
+
diff --git a/doc/source/users/reference_manual/csfootprint.rst b/doc/source/users/reference_manual/csfootprint.rst
new file mode 100644
index 0000000..5d66f46
--- /dev/null
+++ b/doc/source/users/reference_manual/csfootprint.rst
@@ -0,0 +1,76 @@
+.. _csfootprint:
+
+csfootprint
+===========
+
+Generates a carbon footprint report for the usage of ctools on your computed.
+
+
+Synopsis
+--------
+
+This script generates a carbon footprint report based on the usage of ctools
+on your computed. ctools usage statistics are collected in the file
+``~/.gamma/statistics.xml`` that is located in the user's home directory. The
+information file is updated using a dedicated daemon that works in the background
+and collects information about ctools usage.
+
+
+General parameters
+------------------
+
+``(infile = $HOME/.gamma/statistics.xml) [file]``
+ Input high-level statistics XML file.
+
+``outfile [file]``
+ Output graphics file (``NONE`` if no graphics should be generated).
+
+``tmin [time]``
+ Start time for footprint reporting (UTC string, JD, MJD or MET in seconds).
+ Start times given in MET seconds are counted with respect to the time
+ reference of the input observation(s).
+ If ``INDEF``, ``NONE``, ``UNDEF`` or ``UNDEFINED`` is passed as value, the
+ report will be generated for the full data provided in the statistics
+ file.
+
+``tmax [time]``
+ Stop time for footprint reporting (UTC string, JD, MJD or MET in seconds).
+ Stop times given in MET seconds are counted with respect to the time
+ reference of the input observation(s).
+ If ``INDEF``, ``NONE``, ``UNDEF`` or ``UNDEFINED`` is passed as value, the
+ report will be generated for the full data provided in the statistics
+ file.
+
+
+Standard parameters
+-------------------
+
+``(chatter = 2) [integer]``
+ Verbosity of the executable:
+ ``chatter = 0``: no information will be logged
+
+ ``chatter = 1``: only errors will be logged
+
+ ``chatter = 2``: errors and actions will be logged
+
+ ``chatter = 3``: report about the task execution
+
+ ``chatter = 4``: detailed report about the task execution
+
+``(clobber = yes) [boolean]``
+ Specifies whether an existing output file should be overwritten.
+
+``(debug = no) [boolean]``
+ Enables debug mode. In debug mode the executable will dump any log file output to the console.
+
+``(mode = ql) [string]``
+ Mode of automatic parameters (default is ``ql``, i.e. "query and learn").
+
+``(logfile = csfootprint.log) [string]``
+ Name of log file.
+
+
+Related tools or scripts
+------------------------
+
+None
diff --git a/doc/source/users/reference_manual/index.rst b/doc/source/users/reference_manual/index.rst
index c96967b..a272fda 100755
--- a/doc/source/users/reference_manual/index.rst
+++ b/doc/source/users/reference_manual/index.rst
@@ -57,6 +57,7 @@ CTA and IACT analysis tools and scripts
ctskymap --- Generates sky map
csbkgmodel --- Generates background model for 3D analysis
csebins --- Generates energy boundaries for stacked analysis
+ csfootprint --- Generates carbon footprint report
csobsdef --- Generates observation definition file
csobsselect --- Select observations from observation definition file
csphagen --- Generates PHA, ARF, RMF files based on source/background regions
diff --git a/test/data/statistics.xml b/test/data/statistics.xml
new file mode 100644
index 0000000..14bc974
--- /dev/null
+++ b/test/data/statistics.xml
@@ -0,0 +1,113 @@
+
+
+
+
+ 2022-02-19T22:09:05
+ 2022-02-20T14:35:20
+ 2022-02-18T22:21:56
+ 2022-02-20T14:35:17
+
+
+ FR
+
+
+
+
+
+ 2022-02-18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2022-02-19
+
+
+
+
+
+
+ 2022-02-20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <2.0.0.dev calls="82" wall="138" cpu="1.109517" gCO2e="0.00108955228" />
+
+
+
diff --git a/test/test_csfootprint.py b/test/test_csfootprint.py
new file mode 100644
index 0000000..ba59576
--- /dev/null
+++ b/test/test_csfootprint.py
@@ -0,0 +1,168 @@
+#! /usr/bin/env python
+# ==========================================================================
+# This scripts performs unit tests for the csfootprint script.
+#
+# Copyright (C) 2022 Juergen Knoedlseder
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# ==========================================================================
+import os
+import gammalib
+import cscripts
+from testing import test
+
+
+# ================================= #
+# Test class for csfootprint script #
+# ================================= #
+class Test(test):
+ """
+ Test class for csfootprint script
+
+ This test class makes unit tests for the csfootprint script by using it from
+ the command line and from Python.
+ """
+
+ # Constructor
+ def __init__(self):
+ """
+ Constructor
+ """
+ # Call base class constructor
+ test.__init__(self)
+
+ # Set members
+ self._infile = self._datadir + '/statistics.xml'
+
+ # Return
+ return
+
+ # Set test functions
+ def set(self):
+ """
+ Set all test functions
+ """
+ # Set test name
+ self.name('csfootprint')
+
+ # Append tests
+ self.append(self._test_cmd, 'Test csfootprint on command line')
+ self.append(self._test_python, 'Test csfootprint from Python')
+ self.append(self._test_pickeling, 'Test csfootprint pickeling')
+
+ # Return
+ return
+
+ # Test csfootprint on command line
+ def _test_cmd(self):
+ """
+ Test csfootprint on the command line
+ """
+ # Set script name
+ csfootprint = self._script('csfootprint')
+
+ # Setup csfootprint command
+ cmd = csfootprint+' infile="'+self._infile+'" outfile=NONE tmin=NONE'+ \
+ ' tmax=NONE logfile="csfootprint_cmd1.log" chatter=1'
+
+ # Check if execution was successful
+ self.test_value(self._execute(cmd), 0,
+ 'Check successful execution from command line')
+
+ # Setup csfootprint command
+ cmd = csfootprint+' infile="file_that_does_not_exist.xml" outfile=NONE tmin=NONE'+ \
+ ' tmax=NONE logfile="csfootprint_cmd1.log" chatter=1'
+
+ # Check if execution was successful
+ self.test_assert(self._execute(cmd, success=False) != 0,
+ 'Check invalid input file when executed from command line')
+
+ # Check csfootprint --help
+ self._check_help(csfootprint)
+
+ # Return
+ return
+
+ # Test csfootprint from Python
+ def _test_python(self):
+ """
+ Test csfootprint from Python
+ """
+ # Allocate csfootprint
+ script = cscripts.csfootprint()
+
+ # Check that saving does not nothing
+ script['infile'] = self._infile
+ script['outfile'] = 'NONE'
+ script['tmin'] = 'NONE'
+ script['tmax'] = 'NONE'
+ script['logfile'] = 'csfootprint_py0.log'
+ script['chatter'] = 2
+
+ # Run csfootprint script
+ script.logFileOpen()
+ script.run()
+
+ # Check result
+ self._check_result(script)
+
+ # Return
+ return
+
+ # Test csfootprint pickeling
+ def _test_pickeling(self):
+ """
+ Test csfootprint pickeling
+ """
+ # Perform pickeling test of empty class
+ self._pickeling(cscripts.csfootprint())
+
+ # Setup script for pickling text
+ script = cscripts.csfootprint()
+ script['infile'] = self._infile
+ script['outfile'] = 'NONE'
+ script['tmin'] = 'NONE'
+ script['tmax'] = 'NONE'
+ script['logfile'] = 'csfootprint_py1_pickle.log'
+ script['chatter'] = 2
+
+ # Perform pickeling tests of filled class
+ obj = self._pickeling(script)
+
+ # Run csfootprint script and save result
+ obj.logFileOpen() # Make sure we get a log file
+ obj.run()
+ obj.save()
+
+ # Check result
+ self._check_result(script)
+
+ # Return
+ return
+
+ # Check csfootprint result
+ def _check_result(self, script):
+ """
+ Check content of script
+
+ Parameters
+ ----------
+ script : `~cscripts.csfootprint`
+ csfootprint instance
+ """
+ # TODO: Implement test on script result
+
+ # Return
+ return
diff --git a/test/test_python_cscripts.py b/test/test_python_cscripts.py
index 1cb4aac..b6f811e 100755
--- a/test/test_python_cscripts.py
+++ b/test/test_python_cscripts.py
@@ -2,7 +2,7 @@
# ==========================================================================
# This scripts performs unit tests for cscripts
#
-# Copyright (C) 2016-2021 Juergen Knoedlseder
+# Copyright (C) 2016-2022 Juergen Knoedlseder
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@ import test_cscaldb
import test_csadd2caldb
import test_csbkgmodel
import test_csebins
+import test_csfootprint
import test_csfindobs
import test_csinfo
import test_cslightcrv
@@ -146,6 +147,7 @@ def test(installed=False, debug=False):
test_cstsmapsplit.Test(),
test_csviscube.Test(),
test_csscs.Test(),
+ test_csfootprint.Test(),
test_csworkflow.Test(),
test_obsutils.Test(),
test_mputils.Test()]