diff --git a/README.md b/README.md index 78b309b..8309a8b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SusQL Operator -SusQL is a Kubernetes operator that aggregates energy data from pods tagged with SusQL specific labels. The energy measurements are taken from [Kepler](https://sustainable-computing.io/) which should be installed/deployed in the cluster before using SusQL. Watch a video with a demonstration by clicking on the image bellow. +SusQL is a Kubernetes operator that aggregates energy and estimated carbon dioxide emission data for pods tagged with SusQL specific labels. The energy measurements are taken from [Kepler](https://sustainable-computing.io/) which should be installed/deployed in the cluster before using SusQL. Watch a video with a demonstration by clicking on the image bellow. [![Watch the video](https://img.youtube.com/vi/NRVD7gJECfA/maxresdefault.jpg)](https://youtu.be/NRVD7gJECfA) @@ -8,6 +8,13 @@ SusQL is a Kubernetes operator that aggregates energy data from pods tagged with SusQL is an operator that can be deployed in a Kubernetes/OpenShift cluster. You can use [kind](https://sigs.k8s.io/kind) or [minikube](https://minikube.sigs.k8s.io/) to get a local cluster for testing, or run against a remote cluster. +## Carbon Dioxide Emission Calculation + +CO2 emission calculation is currently a work in progress. At the moment we use +a static conversion factor based on +[US EPA](https://www.epa.gov/energy/greenhouse-gases-equivalencies-calculator-calculations-and-references) +data. Although users can configure this value to match their runtime environment, we are working on providing +automatic up to date conversion functionality. ### Prerequisites diff --git a/VERSION b/VERSION index 818944f..df5db66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.22 +0.0.23 diff --git a/api/v1/labelgroup_types.go b/api/v1/labelgroup_types.go index f785127..589da94 100644 --- a/api/v1/labelgroup_types.go +++ b/api/v1/labelgroup_types.go @@ -52,6 +52,9 @@ type LabelGroupStatus struct { // TotalEnergy keeps track of the accumulated energy over time TotalEnergy string `json:"totalEnergy,omitempty"` + // TotalCarbon keeps track of the accumulated grams of carbon dioxide emission over time + TotalCarbon string `json:"totalCarbon,omitempty"` + // Prometheus query to get the total energy for this label group SusQLPrometheusQuery string `json:"susqlPrometheusQuery,omitempty"` diff --git a/bundle.Dockerfile b/bundle.Dockerfile index c69168a..8d94e13 100644 --- a/bundle.Dockerfile +++ b/bundle.Dockerfile @@ -6,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=susql-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.34.1 +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.36.1 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 diff --git a/bundle/manifests/susql-operator.clusterserviceversion.yaml b/bundle/manifests/susql-operator.clusterserviceversion.yaml index 544cd77..32daf2e 100644 --- a/bundle/manifests/susql-operator.clusterserviceversion.yaml +++ b/bundle/manifests/susql-operator.clusterserviceversion.yaml @@ -22,14 +22,15 @@ metadata: ] capabilities: Basic Install categories: Monitoring - containerImage: quay.io/sustainable_computing_io/susql_operator:0.0.22 - createdAt: "2024-07-09T05:01:04Z" - description: 'Aggregates energy data from pods tagged with SusQL labels ' - operators.operatorframework.io/builder: operator-sdk-v1.34.1 + containerImage: quay.io/sustainable_computing_io/susql_operator:0.0.23 + createdAt: "2024-08-22T02:22:46Z" + description: 'Aggregates energy and CO2 emission data for pods tagged with SusQL + labels ' + operators.operatorframework.io/builder: operator-sdk-v1.36.1 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 repository: https://github.com/sustainable-computing-io/susql-operator support: https://github.com/sustainable-computing-io/susql-operator/issues - name: susql-operator.v0.0.22 + name: susql-operator.v0.0.23 namespace: placeholder spec: apiservicedefinitions: {} @@ -43,10 +44,10 @@ spec: description: |- ### About this Operator - SusQL is a Kubernetes operator that aggregates energy data from pods - tagged with SusQL specific labels. The energy measurements are taken - from Kepler which should be deployed in the cluster before - using SusQL. + SusQL is a Kubernetes operator that aggregates energy and estimated + carbon dioxide emission data for pods tagged with SusQL specific + labels. The energy measurements are taken from Kepler which should + be deployed in the cluster before using SusQL. ### Prerequisites @@ -196,35 +197,70 @@ spec: capabilities: drop: - ALL - - args: - - --leader-elect=$(LEADER-ELECT) - - --kepler-prometheus-url=$(KEPLER-PROMETHEUS-URL) - - --kepler-metric-name=$(KEPLER-METRIC-NAME) - - --susql-prometheus-database-url=$(SUSQL-PROMETHEUS-DATABASE-URL) - - --susql-prometheus-metrics-url=$(SUSQL-PROMETHEUS-METRICS-URL) - - --sampling-rate=$(SAMPLING-RATE) - - --health-probe-bind-address=$(HEALTH-PROBE-BIND-ADDRESS) - - --metrics-bind-address=$(METRICS-BIND-ADDRESS) - command: + - command: - /manager env: - name: KEPLER-PROMETHEUS-URL - value: https://thanos-querier.openshift-monitoring.svc.cluster.local:9091 + valueFrom: + configMapKeyRef: + key: KEPLER-PROMETHEUS-URL + name: susql-config + optional: true - name: KEPLER-METRIC-NAME - value: kepler_container_joules_total + valueFrom: + configMapKeyRef: + key: KEPLER-METRIC-NAME + name: susql-config + optional: true - name: SUSQL-PROMETHEUS-DATABASE-URL - value: https://thanos-querier.openshift-monitoring.svc.cluster.local:9091 + valueFrom: + configMapKeyRef: + key: SUSQL-PROMETHEUS-DATABASE-URL + name: susql-config + optional: true - name: SUSQL-PROMETHEUS-METRICS-URL - value: http://0.0.0.0:8082 + valueFrom: + configMapKeyRef: + key: SUSQL-PROMETHEUS-METRICS-URL + name: susql-config + optional: true - name: SAMPLING-RATE - value: "2" + valueFrom: + configMapKeyRef: + key: SAMPLING-RATE + name: susql-config + optional: true - name: LEADER-ELECT - value: "true" + valueFrom: + configMapKeyRef: + key: LEADER-ELECT + name: susql-config + optional: true - name: HEALTH-PROBE-BIND-ADDRESS - value: :8081 + valueFrom: + configMapKeyRef: + key: HEALTH-PROBE-BIND-ADDRESS + name: susql-config + optional: true - name: METRICS-BIND-ADDRESS - value: 127.0.0.1:9999 - image: quay.io/sustainable_computing_io/susql_operator:0.0.22 + valueFrom: + configMapKeyRef: + key: METRICS-BIND-ADDRESS + name: susql-config + optional: true + - name: SUSQL-LOG-LEVEL + valueFrom: + configMapKeyRef: + key: SUSQL-LOG-LEVEL + name: susql-config + optional: true + - name: STATIC-CARBON-INTENSITY + valueFrom: + configMapKeyRef: + key: STATIC-CARBON-INTENSITY + name: susql-config + optional: true + image: quay.io/sustainable_computing_io/susql_operator:0.0.23 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -328,4 +364,4 @@ spec: provider: name: SusQL Operator Contributors url: https://github.com/sustainable-computing-io/susql-operator - version: 0.0.22 + version: 0.0.23 diff --git a/bundle/manifests/susql.ibm.com_labelgroups.yaml b/bundle/manifests/susql.ibm.com_labelgroups.yaml index a367730..b77d2bf 100644 --- a/bundle/manifests/susql.ibm.com_labelgroups.yaml +++ b/bundle/manifests/susql.ibm.com_labelgroups.yaml @@ -69,6 +69,10 @@ spec: description: Prometheus query to get the total energy for this label group type: string + totalCarbon: + description: TotalCarbon keeps track of the accumulated grams of carbon + dioxide emission over time + type: string totalEnergy: description: TotalEnergy keeps track of the accumulated energy over time diff --git a/bundle/metadata/annotations.yaml b/bundle/metadata/annotations.yaml index a510846..467be23 100644 --- a/bundle/metadata/annotations.yaml +++ b/bundle/metadata/annotations.yaml @@ -5,7 +5,7 @@ annotations: operators.operatorframework.io.bundle.metadata.v1: metadata/ operators.operatorframework.io.bundle.package.v1: susql-operator operators.operatorframework.io.bundle.channels.v1: alpha - operators.operatorframework.io.metrics.builder: operator-sdk-v1.34.1 + operators.operatorframework.io.metrics.builder: operator-sdk-v1.36.1 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 diff --git a/cmd/main.go b/cmd/main.go index 7b864ed..2e072f6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -51,25 +51,37 @@ func init() { //+kubebuilder:scaffold:scheme } +func getEnv(key, defval string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return defval +} + func main() { - var metricsAddr string - var enableLeaderElection bool - var probeAddr string - var keplerPrometheusUrl string - var keplerMetricName string - var susqlPrometheusMetricsUrl string - var susqlPrometheusDatabaseUrl string - var samplingRate string + var metricsAddr string = "127.0.0.1:9999" + var enableLeaderElection bool = true + var probeAddr string = ":8081" + var keplerPrometheusUrl string = "https://thanos-querier.openshift-monitoring.svc.cluster.local:9091" + var keplerMetricName string = "kepler_container_joules_total" + var susqlPrometheusMetricsUrl string = "http://0.0.0.0:8082" + var susqlPrometheusDatabaseUrl string = "https://thanos-querier.openshift-monitoring.svc.cluster.local:9091" + var samplingRate string = "2" + var susqlLogLevel string = "-5" + // Static Carbon Intensity Factor in grams CO2 / Joule + var staticCarbonIntensity string = "0.00000000011583333" // NOTE: these can be set as env or flag, flag takes precedence over env - keplerPrometheusUrlEnv := os.Getenv("KEPLER-PROMETHEUS-URL") - keplerMetricNameEnv := os.Getenv("KEPLER-METRIC-NAME") - susqlPrometheusDatabaseUrlEnv := os.Getenv("SUSQL-PROMETHEUS-DATABASE-URL") - susqlPrometheusMetricsUrlEnv := os.Getenv("SUSQL-PROMETHEUS-METRICS-URL") - samplingRateEnv := os.Getenv("SAMPLING-RATE") - metricsAddrEnv := os.Getenv("METRICS-BIND-ADDRESS") - probeAddrEnv := os.Getenv("HEALTH-PROBE-BIND-ADDRESS") - enableLeaderElectionEnv, err := strconv.ParseBool(os.Getenv("LEADER-ELECT")) + keplerPrometheusUrlEnv := getEnv("KEPLER-PROMETHEUS-URL", keplerPrometheusUrl) + keplerMetricNameEnv := getEnv("KEPLER-METRIC-NAME", keplerMetricName) + susqlPrometheusDatabaseUrlEnv := getEnv("SUSQL-PROMETHEUS-DATABASE-URL", susqlPrometheusDatabaseUrl) + susqlPrometheusMetricsUrlEnv := getEnv("SUSQL-PROMETHEUS-METRICS-URL", susqlPrometheusMetricsUrl) + samplingRateEnv := getEnv("SAMPLING-RATE", samplingRate) + metricsAddrEnv := getEnv("METRICS-BIND-ADDRESS", metricsAddr) + probeAddrEnv := getEnv("HEALTH-PROBE-BIND-ADDRESS", probeAddr) + susqlLogLevelEnv := getEnv("SUSQL-LOG-LEVEL", susqlLogLevel) + staticCarbonIntensityEnv := getEnv("STATIC-CARBON-INTENSITY", staticCarbonIntensity) + enableLeaderElectionEnv, err := strconv.ParseBool(getEnv("LEADER-ELECT", strconv.FormatBool(enableLeaderElection))) if err != nil { enableLeaderElectionEnv = false } @@ -81,13 +93,20 @@ func main() { flag.StringVar(&samplingRate, "sampling-rate", samplingRateEnv, "Sampling rate in seconds") flag.StringVar(&metricsAddr, "metrics-bind-address", metricsAddrEnv, "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", probeAddrEnv, "The address the probe endpoint binds to.") + flag.StringVar(&susqlLogLevel, "susql-log-level", susqlLogLevelEnv, "SusQL log level") + flag.StringVar(&staticCarbonIntensity, "static-carbon-intensity", staticCarbonIntensityEnv, "Static Carbon Intensity Factor in grams CO2 / Joule") flag.BoolVar(&enableLeaderElection, "leader-elect", enableLeaderElectionEnv, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") + susqlLogLevelInt, err := strconv.Atoi(susqlLogLevel) + if err != nil { + susqlLogLevelInt = -5 + } + opts := zap.Options{ Development: true, - Level: zapcore.Level(-5), + Level: zapcore.Level(susqlLogLevelInt), } opts.BindFlags(flag.CommandLine) flag.Parse() @@ -103,6 +122,8 @@ func main() { susqlLog.Info("susqlPrometheusMetricsUrl=" + susqlPrometheusMetricsUrl) susqlLog.Info("susqlPrometheusDatabaseUrl=" + susqlPrometheusDatabaseUrl) susqlLog.Info("samplingRate=" + samplingRate) + susqlLog.Info("susqlLogLevel=" + susqlLogLevel) + susqlLog.Info("staticCarbonIntensity=" + staticCarbonIntensity) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, @@ -134,6 +155,12 @@ func main() { samplingRateInteger = 2 } + staticCarbonIntensityFloat, err := strconv.ParseFloat(staticCarbonIntensity, 64) + if err != nil { + susqlLog.Error(err, "Unable to obtain static carbon intensity value. Using 0.0.") + staticCarbonIntensityFloat = 0.0 + } + susqlLog.Info("Setting up labelGroupReconciler.") if err = (&controller.LabelGroupReconciler{ @@ -144,6 +171,7 @@ func main() { SusQLPrometheusDatabaseUrl: susqlPrometheusDatabaseUrl, SusQLPrometheusMetricsUrl: susqlPrometheusMetricsUrl, SamplingRate: time.Duration(samplingRateInteger) * time.Second, + StaticCarbonIntensity: staticCarbonIntensityFloat, Logger: susqlLog, }).SetupWithManager(mgr); err != nil { susqlLog.Error(err, "unable to create controller", "controller", "LabelGroup") diff --git a/config/crd/bases/susql.ibm.com_labelgroups.yaml b/config/crd/bases/susql.ibm.com_labelgroups.yaml index 5afc71f..516d1f9 100644 --- a/config/crd/bases/susql.ibm.com_labelgroups.yaml +++ b/config/crd/bases/susql.ibm.com_labelgroups.yaml @@ -69,6 +69,10 @@ spec: description: Prometheus query to get the total energy for this label group type: string + totalCarbon: + description: TotalCarbon keeps track of the accumulated grams of carbon + dioxide emission over time + type: string totalEnergy: description: TotalEnergy keeps track of the accumulated energy over time diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 06c051c..b15702a 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: quay.io/sustainable_computing_io/susql_operator - newTag: 0.0.22 + newTag: 0.0.23 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 8339403..2d987ce 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -67,35 +67,68 @@ spec: containers: - command: - /manager -# - /debug-entrypoint.sh + # - /debug-entrypoint.sh env: - name: KEPLER-PROMETHEUS-URL - value: "https://thanos-querier.openshift-monitoring.svc.cluster.local:9091" + valueFrom: + configMapKeyRef: + name: susql-config + key: KEPLER-PROMETHEUS-URL + optional: true - name: KEPLER-METRIC-NAME - value: "kepler_container_joules_total" + valueFrom: + configMapKeyRef: + name: susql-config + key: KEPLER-METRIC-NAME + optional: true - name: SUSQL-PROMETHEUS-DATABASE-URL - # value: http://prometheus-susql.default.svc.cluster.local:9090 - value: https://thanos-querier.openshift-monitoring.svc.cluster.local:9091 + valueFrom: + configMapKeyRef: + name: susql-config + key: SUSQL-PROMETHEUS-DATABASE-URL + optional: true - name: SUSQL-PROMETHEUS-METRICS-URL - value: "http://0.0.0.0:8082" + valueFrom: + configMapKeyRef: + name: susql-config + key: SUSQL-PROMETHEUS-METRICS-URL + optional: true - name: SAMPLING-RATE - value: "2" + valueFrom: + configMapKeyRef: + name: susql-config + key: SAMPLING-RATE + optional: true - name: LEADER-ELECT - value: "true" + valueFrom: + configMapKeyRef: + name: susql-config + key: LEADER-ELECT + optional: true - name: HEALTH-PROBE-BIND-ADDRESS - value: ":8081" + valueFrom: + configMapKeyRef: + name: susql-config + key: HEALTH-PROBE-BIND-ADDRESS + optional: true - name: METRICS-BIND-ADDRESS - value: "127.0.0.1:9999" - args: - - --leader-elect=$(LEADER-ELECT) - # - --deployment-namespace=susql-operator - - --kepler-prometheus-url=$(KEPLER-PROMETHEUS-URL) - - --kepler-metric-name=$(KEPLER-METRIC-NAME) - - --susql-prometheus-database-url=$(SUSQL-PROMETHEUS-DATABASE-URL) - - --susql-prometheus-metrics-url=$(SUSQL-PROMETHEUS-METRICS-URL) - - --sampling-rate=$(SAMPLING-RATE) - - --health-probe-bind-address=$(HEALTH-PROBE-BIND-ADDRESS) - - --metrics-bind-address=$(METRICS-BIND-ADDRESS) + valueFrom: + configMapKeyRef: + name: susql-config + key: METRICS-BIND-ADDRESS + optional: true + - name: SUSQL-LOG-LEVEL + valueFrom: + configMapKeyRef: + name: susql-config + key: SUSQL-LOG-LEVEL + optional: true + - name: STATIC-CARBON-INTENSITY + valueFrom: + configMapKeyRef: + name: susql-config + key: STATIC-CARBON-INTENSITY + optional: true image: '' imagePullPolicy: IfNotPresent name: manager diff --git a/config/manifests/bases/susql-operator.clusterserviceversion.yaml b/config/manifests/bases/susql-operator.clusterserviceversion.yaml index 21b08db..3eea457 100644 --- a/config/manifests/bases/susql-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/susql-operator.clusterserviceversion.yaml @@ -6,7 +6,8 @@ metadata: capabilities: Basic Install categories: Monitoring containerImage: - description: 'Aggregates energy data from pods tagged with SusQL labels ' + description: 'Aggregates energy and CO2 emission data for pods tagged with SusQL + labels ' repository: https://github.com/sustainable-computing-io/susql-operator support: https://github.com/sustainable-computing-io/susql-operator/issues name: susql-operator.v0.0.0 @@ -23,10 +24,10 @@ spec: description: |- ### About this Operator - SusQL is a Kubernetes operator that aggregates energy data from pods - tagged with SusQL specific labels. The energy measurements are taken - from Kepler which should be deployed in the cluster before - using SusQL. + SusQL is a Kubernetes operator that aggregates energy and estimated + carbon dioxide emission data for pods tagged with SusQL specific + labels. The energy measurements are taken from Kepler which should + be deployed in the cluster before using SusQL. ### Prerequisites diff --git a/deployment/deploy.sh b/deployment/deploy.sh index f88075d..17e4907 100644 --- a/deployment/deploy.sh +++ b/deployment/deploy.sh @@ -75,6 +75,14 @@ if [[ -z ${SUSQL_SAMPLING_RATE} ]]; then SUSQL_SAMPLING_RATE="2" fi +if [[ -z ${SUSQL_LOG_LEVEL} ]]; then + SUSQL_LOG_LEVEL="-5" +fi + +if [[ -z ${STATIC_CARBON_INTENSITY} ]]; then + STATIC_CARBON_INTENSITY="0.00000000011583333" +fi + # Check if namespace exists if [[ -z $(kubectl get namespaces --no-headers -o custom-columns=':{.metadata.name}' | grep ${SUSQL_NAMESPACE}) ]]; then @@ -107,12 +115,14 @@ echo "PROMETHEUS_DOMAIN - '${PROMETHEUS_DOMAIN}'" echo "PROMETHEUS_PORT - '${PROMETHEUS_PORT}'" echo "KEPLER_PROMETHEUS_URL - '${KEPLER_PROMETHEUS_URL}'" echo "KEPLER_METRIC_NAME - '${KEPLER_METRIC_NAME}'" +echo "STATIC_CARBON_INTENSITY - '${STATIC_CARBON_INTENSITY}'" echo "SUSQL_PROMETHEUS_URL - '${SUSQL_PROMETHEUS_URL}'" echo "SUSQL_SAMPLING_RATE - '${SUSQL_SAMPLING_RATE}'" echo "SUSQL_ENHANCED - '${SUSQL_ENHANCED}'" echo "SUSQL_REGISTRY - '${SUSQL_REGISTRY}'" echo "SUSQL_IMAGE_NAME - '${SUSQL_IMAGE_NAME}'" echo "SUSQL_IMAGE_TAG - '${SUSQL_IMAGE_TAG}'" +echo "SUSQL_LOG_LEVEL - '${SUSQL_LOG_LEVEL}'" echo "===================================================================================================" # Actions to perform, separated by comma actions=${1:-"kepler-check,prometheus-undeploy,prometheus-deploy,susql-undeploy,susql-deploy"} @@ -136,12 +146,14 @@ echo "export PROMETHEUS_DOMAIN=${PROMETHEUS_DOMAIN}" >> ${LOGFILE} echo "export PROMETHEUS_PORT=${PROMETHEUS_PORT}" >> ${LOGFILE} echo "export KEPLER_PROMETHEUS_URL=${KEPLER_PROMETHEUS_URL}" >> ${LOGFILE} echo "export KEPLER_METRIC_NAME=${KEPLER_METRIC_NAME}" >> ${LOGFILE} +echo "export STATIC_CARBON_INTENSITY=${STATIC_CARBON_INTENSITY}" >> ${LOGFILE} echo "export SUSQL_PROMETHEUS_URL=${SUSQL_PROMETHEUS_URL}" >> ${LOGFILE} echo "export SUSQL_SAMPLING_RATE=${SUSQL_SAMPLING_RATE}" >> ${LOGFILE} echo "export SUSQL_ENHANCED=${SUSQL_ENHANCED}" >> ${LOGFILE} echo "export SUSQL_REGISTRY=${SUSQL_REGISTRY}" >> ${LOGFILE} echo "export SUSQL_IMAGE_NAME=${SUSQL_IMAGE_NAME}" >> ${LOGFILE} echo "export SUSQL_IMAGE_TAG=${SUSQL_IMAGE_TAG}" >> ${LOGFILE} +echo "export SUSQL_LOG_LEVEL=${SUSQL_LOG_LEVEL}" >> ${LOGFILE} for action in $(echo ${actions} | tr ',' '\n') do @@ -228,7 +240,9 @@ do --set keplerMetricName="${KEPLER_METRIC_NAME}" \ --set susqlPrometheusDatabaseUrl="${SUSQL_PROMETHEUS_URL}" \ --set susqlPrometheusMetricsUrl="http://0.0.0.0:8082" \ + --set staticCarbonIntensity="${STATIC_CARBON_INTENSITY}" \ --set samplingRate="${SUSQL_SAMPLING_RATE}" \ + --set susqlLogLevel="${SUSQL_LOG_LEVEL}" \ --set imagePullPolicy="Always" \ --set containerImage="${SUSQL_REGISTRY}/${SUSQL_IMAGE_NAME}:${SUSQL_IMAGE_TAG}" if [[ ! -z ${SUSQL_ENHANCED} ]]; then diff --git a/deployment/susql-controller/templates/deployment.yaml b/deployment/susql-controller/templates/deployment.yaml index 67f049d..0558bf5 100644 --- a/deployment/susql-controller/templates/deployment.yaml +++ b/deployment/susql-controller/templates/deployment.yaml @@ -28,7 +28,9 @@ spec: - "--kepler-metric-name={{ .Values.keplerMetricName }}" - "--susql-prometheus-database-url={{ .Values.susqlPrometheusDatabaseUrl }}" - "--susql-prometheus-metrics-url={{ .Values.susqlPrometheusMetricsUrl }}" + - "--susql-log-level={{ .Values.susqlLogLevel }}" - "--sampling-rate={{ .Values.samplingRate }}" + - "--static-carbon-intensity={{ .Values.staticCarbonIntensity }}" - "--metrics-bind-address={{ .Values.metricsAddr }}" - "--health-prove-bind-address={{ .Values.healthProbeAddr }}" - "--leader-elect={{ .Values.leaderElect }}" diff --git a/deployment/susql-controller/values.yaml b/deployment/susql-controller/values.yaml index 2edb672..070054f 100644 --- a/deployment/susql-controller/values.yaml +++ b/deployment/susql-controller/values.yaml @@ -27,3 +27,5 @@ samplingRate: "2" metricsAddr: "127.0.0.1:9999" healthProbeAddr: ":8081" leaderElect: "true" +susqlLogLevel: "-5" +staticCarbonIntensity: "0.00000000011583333" diff --git a/doc/helm-installation.md b/doc/helm-installation.md index 7ade03b..9b92ac8 100644 --- a/doc/helm-installation.md +++ b/doc/helm-installation.md @@ -60,10 +60,12 @@ The following environment variables will influence the way that the SusQL Operat | KEPLER_METRIC_NAME | kepler_container_joules_total | Metric queried in the Kepler Prometheus | | SUSQL_PROMETHEUS_URL | http://prometheus-susql.openshift-kepler-operator.svc.cluster.local:9090 | SusQL Prometheus URL | | SUSQL_SAMPLING_RATE | 2 | Sampling rate in seconds | +| SUSQL_LOG_LEVEL | -5 | Log level | | SUSQL_ENHANCED | | If set to any string, then use enhanced RBAC and SMON configuration | | SUSQL_REGISTRY | quay.io/sustainable_computing_io | Container registry that SusQL is stored in | | SUSQL_IMAGE_NAME | susql_operator | Image name used on SusQL container registry | | SUSQL_IMAGE_TAG | latest | Tag for SusQL container | +| STATIC_CARBON_INTENSITY | "0.00000000011583333" | Static carbon intensity in grams CO2 / Joule | ## License diff --git a/doc/operatorhub-installation.md b/doc/operatorhub-installation.md index ca3d4cd..cd017ef 100644 --- a/doc/operatorhub-installation.md +++ b/doc/operatorhub-installation.md @@ -40,21 +40,15 @@ Next, use the OpenShift web console to install the SusQL Operator: - Search for "SusQL" - Click on it, and follow the GUI prompts to install. -# Post Install Customization +# Customization -After installation, click "Operators->Installed Operators->SusQL->YAML", then edit one of the values under =spec.install.spec.deployments.labels.spec.template.spec.containers.resources.env.name.values` and save the YAML file when finished. -The operator will automatically restart with the newly specified value(s). +Before deploying the SusQL Operator create a `configMap` called `susql-config` in +the same namespace that the operator will run in. +[susql-config.yaml](susql-config.yaml) is a good starting point. If you download it first, you +could create the configMap with `oc apply -n YOURNAMESPACE -f susql-config.yaml`. +If you update (or create) the configMap after the SusQL Operator has been installed, then restarting the SusQL Operator controller pod will +enable the changes. (e.g., Delete the pod, and allow it to be recreated automatically.) -Currently the following variables can be modified. Searching directly for these variables may be more efficient than perusing the entire YAML. - -- `KEPLER-PROMETHEUS-URL` -- `KEPLER-METRIC-NAME` -- `SUSQL-PROMETHEUS-DATABASE-URL` -- `SUSQL-PROMETHEUS-METRICS-URL` -- `SAMPLING-RATE` -- `LEADER-ELECT` -- `HEALTH-PROBE-BIND-ADDRESS` -- `METRICS-BIND-ADDRESS` ## License diff --git a/doc/susql-config.yaml b/doc/susql-config.yaml new file mode 100644 index 0000000..690b427 --- /dev/null +++ b/doc/susql-config.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: susql-config +data: + KEPLER-PROMETHEUS-URL: "https://thanos-querier.openshift-monitoring.svc.cluster.local:9091" + KEPLER-METRIC-NAME: "kepler_container_joules_total" + SUSQL-PROMETHEUS-DATABASE-URL: "https://thanos-querier.openshift-monitoring.svc.cluster.local:9091" + SUSQL-PROMETHEUS-METRICS-URL: "http://0.0.0.0:8082" + SAMPLING-RATE: "2" + LEADER-ELECT: "true" + HEALTH-PROBE-BIND-ADDRESS: ":8081" + METRICS-BIND-ADDRESS: "127.0.0.1:9999" + SUSQL-LOG-LEVEL: "-5" + STATIC-CARBON-INTENSITY: "0.00000000011583333" diff --git a/internal/controller/labelgroup_controller.go b/internal/controller/labelgroup_controller.go index ca01e74..3e56ca2 100644 --- a/internal/controller/labelgroup_controller.go +++ b/internal/controller/labelgroup_controller.go @@ -42,12 +42,14 @@ type LabelGroupReconciler struct { SusQLPrometheusDatabaseUrl string SusQLPrometheusMetricsUrl string SamplingRate time.Duration // Sampling rate for all label groups + StaticCarbonIntensity float64 Logger logr.Logger } const ( susqlMetricName = "susql_total_energy_joules" // SusQL metric to query - fixingDelay = 15 * time.Second // Time to wait in the even the label group was badly constructed + fixingDelay = 15 * time.Second // Time to wait in the event the label group was badly constructed + nopodDelay = 15 * time.Second // Time to wait in the event no pods are found errorDelay = 1 * time.Second // Time to wait when an error happens due to network connectivity issues ) @@ -166,6 +168,8 @@ func (r *LabelGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) } labelGroup.Status.TotalEnergy = fmt.Sprintf("%f", totalEnergy) + + labelGroup.Status.TotalCarbon = fmt.Sprintf("%.15f", float64(totalEnergy)*r.StaticCarbonIntensity) } labelGroup.Status.Phase = susqlv1.Aggregating @@ -195,7 +199,7 @@ func (r *LabelGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Logger.V(0).Error(err, "[Reconcile-Aggregating] ERROR: Couldn't get pods for the labels provided.") } - return ctrl.Result{}, err + return ctrl.Result{RequeueAfter: nopodDelay}, nil } // Aggregate Kepler measurements for these set of pods @@ -245,12 +249,15 @@ func (r *LabelGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) // 4) Update ETCD with the values labelGroup.Status.TotalEnergy = fmt.Sprintf("%.2f", totalEnergy) + labelGroup.Status.TotalCarbon = fmt.Sprintf("%.15f", float64(totalEnergy)*r.StaticCarbonIntensity) + if err := r.Status().Update(ctx, labelGroup); err != nil { return ctrl.Result{}, err } // 5) Add energy aggregation to Prometheus table r.SetAggregatedEnergyForLabels(totalEnergy, labelGroup.Status.PrometheusLabels) + r.SetAggregatedCarbonForLabels(float64(totalEnergy)*r.StaticCarbonIntensity, labelGroup.Status.PrometheusLabels) // Requeue return ctrl.Result{RequeueAfter: r.SamplingRate}, nil diff --git a/internal/controller/prometheus_manager.go b/internal/controller/prometheus_manager.go index c0d9284..2fbe26a 100644 --- a/internal/controller/prometheus_manager.go +++ b/internal/controller/prometheus_manager.go @@ -157,6 +157,7 @@ func (r *LabelGroupReconciler) GetMetricValuesForPodNames(metricName string, pod type SusqlMetrics struct { totalEnergy *prometheus.GaugeVec + totalCarbon *prometheus.GaugeVec } var ( @@ -166,6 +167,11 @@ var ( Name: "total_energy_joules", Help: "Accumulated energy over time for set of labels", }, susqlPrometheusLabelNames), + totalCarbon: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "susql", + Name: "total_carbon_dioxide_grams", + Help: "Accumulated carbon dioxide grams over time for set of labels", + }, susqlPrometheusLabelNames), } prometheusRegistry *prometheus.Registry @@ -177,7 +183,7 @@ func (r *LabelGroupReconciler) InitializeMetricsExporter() { r.Logger.V(5).Info("Entering InitializeMetricsExporter().") if prometheusRegistry == nil { prometheusRegistry = prometheus.NewRegistry() - prometheusRegistry.MustRegister(susqlMetrics.totalEnergy) + prometheusRegistry.MustRegister(susqlMetrics.totalEnergy, susqlMetrics.totalCarbon) prometheusHandler = promhttp.HandlerFor(prometheusRegistry, promhttp.HandlerOpts{Registry: prometheusRegistry}) http.Handle("/metrics", prometheusHandler) @@ -208,3 +214,12 @@ func (r *LabelGroupReconciler) SetAggregatedEnergyForLabels(totalEnergy float64, return nil } + +func (r *LabelGroupReconciler) SetAggregatedCarbonForLabels(totalCarbon float64, prometheusLabels map[string]string) error { + // Save aggregated carbon to Prometheus table + susqlMetrics.totalCarbon.With(prometheusLabels).Set(totalCarbon) + + r.Logger.V(5).Info(fmt.Sprintf("[SetAggregatedEnergyForLabels] Setting carbon %f for %v.", totalCarbon, prometheusLabels)) // trace + + return nil +} diff --git a/test/labelgroups.sh b/test/labelgroups.sh index 3d4624d..afc4ba4 100644 --- a/test/labelgroups.sh +++ b/test/labelgroups.sh @@ -10,13 +10,15 @@ for labelgroup in $(echo ${alldata} | jq -cr '.items[].metadata.name') do newdata=$(echo ${alldata} | jq '.items[] | select(.metadata.name=="'${labelgroup}'")') totalEnergy=$(echo ${newdata} | jq -cr '.status.totalEnergy') + totalCarbon=$(echo ${newdata} | jq -cr '.status.totalCarbon') susqlPrometheusQuery=$(echo ${newdata} | jq -cr '.status.susqlPrometheusQuery') phase=$(echo ${newdata} | jq -cr '.status.phase') labels=$(echo ${newdata} | jq -cr '.spec.labels') echo "LabelGroup: ${labelgroup}" echo " - Labels: ${labels}" - echo " - Total Energy: ${totalEnergy}" + echo " - Total Energy (J): ${totalEnergy}" + echo " - Total CO2 (g): ${totalCarbon}" echo " - SusQL Query: ${susqlPrometheusQuery}" echo " - Phase: ${phase}" echo diff --git a/test/susqltop b/test/susqltop index 8c685b6..7b88f11 100755 --- a/test/susqltop +++ b/test/susqltop @@ -12,7 +12,7 @@ else ns=$(echo ${alldata} | jq -r '.items[].metadata.namespace' | uniq) fi -printf '%-20s%-40s%-50s%-20s\n' NameSpace LabelGroup Labels 'TotalEnergy (J)' +printf '%-20s%-40s%-50s%-20s%-15s\n' NameSpace LabelGroup Labels 'TotalEnergy (J)' 'Total CO2 (g)' for namespace in ${ns} do @@ -20,7 +20,8 @@ do do newdata=$(echo ${alldata} | jq '.items[] | select(.metadata.namespace=="'${namespace}'" and .metadata.name=="'${labelgroup}'")') totalEnergy=$(echo ${newdata} | jq -cr '.status.totalEnergy') + totalCarbon=$(echo ${newdata} | jq -cr '.status.totalCarbon') labels=$(echo ${newdata} | jq -cr '.spec.labels') - printf '%-20s%-40s%-50s%-20s\n' ${namespace} ${labelgroup} ${labels} ${totalEnergy} + printf '%-20s%-40s%-50s%-20s%-15s\n' ${namespace} ${labelgroup} ${labels} ${totalEnergy} ${totalCarbon} done done | sort -nr -k4 | head -n 20