diff --git a/analysis/src/main/java/net/laprun/sustainability/power/analysis/MeanComponentProcessor.java b/analysis/src/main/java/net/laprun/sustainability/power/analysis/MeanComponentProcessor.java
index ecaf70e..a037232 100644
--- a/analysis/src/main/java/net/laprun/sustainability/power/analysis/MeanComponentProcessor.java
+++ b/analysis/src/main/java/net/laprun/sustainability/power/analysis/MeanComponentProcessor.java
@@ -10,4 +10,18 @@ public void recordComponentValue(double value, long timestamp) {
         count++;
         mean = mean == 0 ? value : (previousSize * mean + value) / count;
     }
+
+    public double mean() {
+        return mean;
+    }
+
+    @Override
+    public String name() {
+        return "mean";
+    }
+
+    @Override
+    public String output() {
+        return "" + mean;
+    }
 }
diff --git a/analysis/src/test/java/ComputeTest.java b/analysis/src/test/java/ComputeTest.java
index ad5c333..d601634 100644
--- a/analysis/src/test/java/ComputeTest.java
+++ b/analysis/src/test/java/ComputeTest.java
@@ -1,6 +1,5 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import java.util.List;
 import java.util.Random;
 import java.util.random.RandomGenerator;
 
@@ -8,16 +7,16 @@
 
 import net.laprun.sustainability.power.SensorMetadata;
 import net.laprun.sustainability.power.analysis.Compute;
+import net.laprun.sustainability.power.analysis.MeanComponentProcessor;
 import net.laprun.sustainability.power.measure.OngoingPowerMeasure;
+import net.laprun.sustainability.power.measure.PowerMeasure;
 
 public class ComputeTest {
-    private final static SensorMetadata metadata = new SensorMetadata(List.of(), null) {
-
-        @Override
-        public int componentCardinality() {
-            return 3;
-        }
-    };
+    private final static SensorMetadata metadata = SensorMetadata
+            .withNewComponent("cp1", null, true, null, false)
+            .withNewComponent("cp2", null, true, null, false)
+            .withNewComponent("cp3", null, true, null, false)
+            .build();
 
     @Test
     void standardDeviationShouldWork() {
@@ -79,6 +78,9 @@ void averageShouldWork() {
         final var m3c3 = 0.0;
 
         final var measure = new OngoingPowerMeasure(metadata);
+        measure.registerProcessorFor(0, new MeanComponentProcessor());
+        measure.registerProcessorFor(1, new MeanComponentProcessor());
+        measure.registerProcessorFor(2, new MeanComponentProcessor());
 
         final var components = new double[metadata.componentCardinality()];
         components[0] = m1c1;
@@ -102,9 +104,18 @@ void averageShouldWork() {
 
         assertEquals((m1c1 + m2c1 + m3c1) / 3, c1Avg, 0.0001,
                 "Average did not match the expected value");
+        final var processors = measure.processors();
+        assertEquals(c1Avg,
+                processors.processorFor(0, MeanComponentProcessor.class).map(MeanComponentProcessor::mean).orElseThrow());
         assertEquals((m1c2 + m2c2 + m3c2) / 3, c2Avg, 0.0001,
                 "Average did not match the expected value");
+        assertEquals(c2Avg,
+                processors.processorFor(1, MeanComponentProcessor.class).map(MeanComponentProcessor::mean).orElseThrow());
         assertEquals(0, c3Avg, 0.0001,
                 "Average did not match the expected value");
+        assertEquals(0,
+                processors.processorFor(2, MeanComponentProcessor.class).map(MeanComponentProcessor::mean).orElseThrow());
+
+        System.out.println(PowerMeasure.asString(measure));
     }
 }
diff --git a/if-manifest-export/src/main/java/net/laprun/sustainability/power/impactframework/export/IFExporter.java b/if-manifest-export/src/main/java/net/laprun/sustainability/power/impactframework/export/IFExporter.java
index 9bfdcd1..45d8e62 100644
--- a/if-manifest-export/src/main/java/net/laprun/sustainability/power/impactframework/export/IFExporter.java
+++ b/if-manifest-export/src/main/java/net/laprun/sustainability/power/impactframework/export/IFExporter.java
@@ -2,10 +2,10 @@
 
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 
 import net.laprun.sustainability.impactframework.manifest.Child;
 import net.laprun.sustainability.impactframework.manifest.Initialize;
@@ -14,7 +14,6 @@
 import net.laprun.sustainability.impactframework.manifest.Metadata;
 import net.laprun.sustainability.impactframework.manifest.Plugin;
 import net.laprun.sustainability.impactframework.manifest.Tree;
-import net.laprun.sustainability.power.SensorMetadata;
 import net.laprun.sustainability.power.measure.PowerMeasure;
 import net.laprun.sustainability.power.measure.StoppedPowerMeasure;
 
@@ -32,11 +31,13 @@ public static Manifest export(StoppedPowerMeasure measure) {
         final List inputs = new ArrayList<>(samples);
         final var sensorMetadata = measure.metadata();
         final int samplingFrequency = (int) measure.duration().dividedBy(samples).toMillis();
-
         final var components = sensorMetadata.components();
+        final String[] inputNames = new String[components.size()];
+        components.values().forEach(component -> inputNames[component.index()] = getInputValueName(component.name()));
+
         for (int i = 0; i < samples; i++) {
             final var values = measure.getNthTimestampedMeasures(i);
-            inputs.add(toInput(samplingFrequency, values, components));
+            inputs.add(toInput(samplingFrequency, values, inputNames));
         }
 
         return new Manifest(ifMetadata, defaultInitialize,
@@ -48,10 +49,12 @@ public static String getInputValueName(String componentName) {
     }
 
     private static Input toInput(int samplingFrequency, PowerMeasure.TimestampedMeasures values,
-            Map components) {
-        final var inputValues = new TreeMap();
-        components.values().forEach(
-                component -> inputValues.put(getInputValueName(component.name()), values.measures()[component.index()]));
+            String[] inputNames) {
+        final var inputValues = new LinkedHashMap(inputNames.length);
+        final var measures = values.measures();
+        for (int i = 0; i < inputNames.length; i++) {
+            inputValues.put(inputNames[i], measures[i]);
+        }
         return new Input(Instant.ofEpochMilli(values.timestamp()), samplingFrequency, inputValues);
     }
 }
diff --git a/measure/pom.xml b/measure/pom.xml
index 72b6248..5914d17 100644
--- a/measure/pom.xml
+++ b/measure/pom.xml
@@ -21,8 +21,6 @@
     
       org.assertj
       assertj-core
-      3.26.3
-      test
     
   
 
diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/Analyzers.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/Analyzers.java
deleted file mode 100644
index b0eed83..0000000
--- a/measure/src/main/java/net/laprun/sustainability/power/analysis/Analyzers.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package net.laprun.sustainability.power.analysis;
-
-public interface Analyzers {
-}
diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/ComponentProcessor.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/ComponentProcessor.java
index 00beaac..716b978 100644
--- a/measure/src/main/java/net/laprun/sustainability/power/analysis/ComponentProcessor.java
+++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/ComponentProcessor.java
@@ -3,4 +3,12 @@
 public interface ComponentProcessor {
     default void recordComponentValue(double value, long timestamp) {
     }
+
+    default String name() {
+        return this.getClass().getSimpleName();
+    }
+
+    default String output() {
+        return "";
+    }
 }
diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/DefaultProcessors.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/DefaultProcessors.java
new file mode 100644
index 0000000..0caf66f
--- /dev/null
+++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/DefaultProcessors.java
@@ -0,0 +1,89 @@
+package net.laprun.sustainability.power.analysis;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import net.laprun.sustainability.power.SensorMetadata;
+
+public class DefaultProcessors implements Processors {
+    private final List[] processors;
+    private ComponentProcessor totalProcessor;
+
+    @SuppressWarnings("unchecked")
+    public DefaultProcessors(int componentCardinality) {
+        this.processors = new List[componentCardinality];
+    }
+
+    @Override
+    public void recordMeasure(double[] components, long timestamp) {
+        for (var index = 0; index < components.length; index++) {
+            final var fIndex = index;
+            final var componentProcessors = processors[index];
+            if (componentProcessors != null) {
+                componentProcessors.forEach(proc -> proc.recordComponentValue(components[fIndex], timestamp));
+            }
+        }
+    }
+
+    @Override
+    public void registerProcessorFor(int componentIndex, ComponentProcessor processor) {
+        if (processor != null) {
+            var processorsForComponent = processors[componentIndex];
+            if (processorsForComponent == null) {
+                processorsForComponent = new ArrayList<>();
+                processors[componentIndex] = processorsForComponent;
+            }
+            processorsForComponent.add(processor);
+        }
+    }
+
+    @Override
+    public void registerTotalProcessor(ComponentProcessor processor) {
+        this.totalProcessor = processor;
+    }
+
+    @Override
+    public void recordTotal(double total, long timestamp) {
+        if (totalProcessor != null) {
+            totalProcessor.recordComponentValue(total, timestamp);
+        }
+    }
+
+    @Override
+    public Optional totalProcessor() {
+        return Optional.ofNullable(totalProcessor);
+    }
+
+    @Override
+    public List processorsFor(int componentIndex) {
+        final var componentProcessors = processors[componentIndex];
+        return Objects.requireNonNullElseGet(componentProcessors, List::of);
+    }
+
+    @Override
+    public  Optional processorFor(int componentIndex, Class expectedType) {
+        final var componentProcessors = processors[componentIndex];
+        if (componentProcessors != null) {
+            return componentProcessors.stream().filter(expectedType::isInstance).map(expectedType::cast).findFirst();
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    @Override
+    public String output(SensorMetadata metadata) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("# Processors\n");
+        for (int i = 0; i < processors.length; i++) {
+            final var name = metadata.metadataFor(i).name();
+            builder.append("  - ").append(name).append(" component:\n");
+            for (var processor : processorsFor(i)) {
+                builder.append("    * ").append(processor.name())
+                        .append(": ").append(processor.output()).append("\n");
+            }
+        }
+        return builder.toString();
+    }
+}
diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/Processors.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/Processors.java
new file mode 100644
index 0000000..372bf57
--- /dev/null
+++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/Processors.java
@@ -0,0 +1,39 @@
+package net.laprun.sustainability.power.analysis;
+
+import java.util.List;
+import java.util.Optional;
+
+import net.laprun.sustainability.power.SensorMetadata;
+
+public interface Processors {
+    Processors empty = new Processors() {
+    };
+
+    default void recordMeasure(double[] components, long timestamp) {
+    }
+
+    default void recordTotal(double total, long timestamp) {
+    }
+
+    default void registerProcessorFor(int componentIndex, ComponentProcessor processor) {
+    }
+
+    default void registerTotalProcessor(ComponentProcessor processor) {
+    }
+
+    default Optional totalProcessor() {
+        return Optional.empty();
+    }
+
+    default List processorsFor(int componentIndex) {
+        return List.of();
+    }
+
+    default  Optional processorFor(int componentIndex, Class expectedType) {
+        return Optional.empty();
+    }
+
+    default String output(SensorMetadata metadata) {
+        return "";
+    }
+}
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java
index 2d2ebf4..04a2b2f 100644
--- a/measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java
@@ -1,45 +1,66 @@
 package net.laprun.sustainability.power.measure;
 
 import java.time.Duration;
+import java.util.Arrays;
 import java.util.BitSet;
-import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import java.util.stream.DoubleStream;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import net.laprun.sustainability.power.SensorMetadata;
 import net.laprun.sustainability.power.analysis.ComponentProcessor;
+import net.laprun.sustainability.power.analysis.DefaultProcessors;
+import net.laprun.sustainability.power.analysis.Processors;
 
 public class OngoingPowerMeasure implements PowerMeasure {
     private static final int DEFAULT_SIZE = 32;
-    private final SensorMetadata sensorMetadata;
+    private final SensorMetadata metadata;
     private final long startedAt;
     private final BitSet nonZeroComponents;
-    private final int[] totalComponents;
     private final int totalIndex;
     private final double[][] measures;
-    private final ComponentProcessor[] analyzers;
+    private final boolean shouldComputeTotals;
     private double minTotal = Double.MAX_VALUE;
     private double maxTotal;
     private double accumulatedTotal;
     private int samples;
     private long[] timestamps;
+    private Processors processors = Processors.empty;
 
-    public OngoingPowerMeasure(SensorMetadata sensorMetadata, ComponentProcessor... analyzers) {
-        this.sensorMetadata = sensorMetadata;
+    public OngoingPowerMeasure(SensorMetadata metadata) {
         startedAt = System.currentTimeMillis();
 
-        final var numComponents = sensorMetadata.componentCardinality();
-        // we also record the aggregated total for each component participating in the aggregated value
-        final var measuresNb = numComponents + 1;
+        final var numComponents = metadata.componentCardinality();
+
+        // check if we need to add an aggregated total component
+        final var totalComponents = metadata.totalComponents();
+        final int measuresNb;
+        if (totalComponents.length > 0) {
+            shouldComputeTotals = true;
+            measuresNb = numComponents + 1;
+
+            final var sumMsg = Arrays.stream(totalComponents)
+                    .mapToObj(i -> metadata.metadataFor(i).name())
+                    .collect(Collectors.joining("+", "Aggregated total from (", ")"));
+
+            // todo: compute total component properly (same unit, convert to base unit all components)
+            this.metadata = SensorMetadata.from(metadata)
+                    .withNewComponent("total", sumMsg, true, "mW", false)
+                    .build();
+            totalIndex = numComponents;
+        } else {
+            shouldComputeTotals = false;
+            measuresNb = numComponents;
+            this.metadata = metadata;
+            totalIndex = -1;
+        }
+
         measures = new double[measuresNb][DEFAULT_SIZE];
         timestamps = new long[DEFAULT_SIZE];
-        totalIndex = numComponents;
         // we don't need to record the total component as a non-zero component since it's almost never zero and we compute the std dev separately
         nonZeroComponents = new BitSet(numComponents);
-        totalComponents = sensorMetadata.totalComponents();
-        this.analyzers = Objects.requireNonNullElseGet(analyzers, () -> new ComponentProcessor[0]);
     }
 
     @Override
@@ -49,33 +70,39 @@ public int numberOfSamples() {
 
     @Override
     public SensorMetadata metadata() {
-        return sensorMetadata;
+        return metadata;
     }
 
     public void recordMeasure(double[] components) {
         samples++;
+        final var timestamp = System.currentTimeMillis();
         for (int component = 0; component < components.length; component++) {
             final var componentValue = components[component];
             // record that the value is not zero
             if (componentValue != 0) {
                 nonZeroComponents.set(component);
             }
-            recordComponentValue(component, componentValue);
+            recordComponentValue(component, componentValue, timestamp);
         }
 
         // record min / max totals
-        final var recordedTotal = Compute.sumOfSelectedComponents(components, totalComponents);
-        recordComponentValue(totalIndex, recordedTotal);
-        accumulatedTotal += recordedTotal;
-        if (recordedTotal < minTotal) {
-            minTotal = recordedTotal;
-        }
-        if (recordedTotal > maxTotal) {
-            maxTotal = recordedTotal;
+        if (shouldComputeTotals) {
+            final double recordedTotal = Compute.sumOfSelectedComponents(components, metadata.totalComponents());
+            recordComponentValue(totalIndex, recordedTotal, timestamp);
+            accumulatedTotal += recordedTotal;
+            if (recordedTotal < minTotal) {
+                minTotal = recordedTotal;
+            }
+            if (recordedTotal > maxTotal) {
+                maxTotal = recordedTotal;
+            }
+            processors.recordTotal(recordedTotal, timestamp);
         }
+
+        processors.recordMeasure(components, timestamp);
     }
 
-    private void recordComponentValue(int component, double value) {
+    private void recordComponentValue(int component, double value, long timestamp) {
         final var currentSize = measures[component].length;
         if (currentSize <= samples) {
             final var newSize = currentSize * 2;
@@ -88,12 +115,8 @@ private void recordComponentValue(int component, double value) {
             System.arraycopy(timestamps, 0, newTimestamps, 0, currentSize);
             timestamps = newTimestamps;
         }
-        final var timestamp = System.currentTimeMillis();
-        timestamps[component] = timestamp;
+        timestamps[samples - 1] = timestamp;
         measures[component][samples - 1] = value;
-        for (var analyzer : analyzers) {
-            analyzer.recordComponentValue(value, timestamp);
-        }
     }
 
     @Override
@@ -163,7 +186,27 @@ public TimestampedMeasures getNthTimestampedMeasures(int n) {
     }
 
     @Override
-    public ComponentProcessor[] analyzers() {
-        return analyzers;
+    public Processors processors() {
+        return processors;
+    }
+
+    @Override
+    public void registerProcessorFor(int component, ComponentProcessor processor) {
+        if (processor != null) {
+            if (Processors.empty == processors) {
+                processors = new DefaultProcessors(metadata.componentCardinality());
+            }
+            processors.registerProcessorFor(component, processor);
+        }
+    }
+
+    @Override
+    public void registerTotalProcessor(ComponentProcessor processor) {
+        if (processor != null) {
+            if (Processors.empty == processors) {
+                processors = new DefaultProcessors(metadata.componentCardinality());
+            }
+            processors.registerTotalProcessor(processor);
+        }
     }
 }
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
index 81dc449..a8688d2 100644
--- a/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
@@ -7,6 +7,7 @@
 
 import net.laprun.sustainability.power.SensorMetadata;
 import net.laprun.sustainability.power.analysis.ComponentProcessor;
+import net.laprun.sustainability.power.analysis.Processors;
 
 public interface PowerMeasure {
 
@@ -15,9 +16,10 @@ static String asString(PowerMeasure measure) {
         final var durationInSeconds = measure.duration().getSeconds();
         final var samples = measure.numberOfSamples();
         final var measuredMilliWatts = measure.total();
-        return String.format("%s [min: %.3f, max: %.3f] (%ds, %s samples)",
+        return String.format("%s [min: %.3f, max: %.3f] (%ds, %s samples)\n---\n%s",
                 readableWithUnit(measuredMilliWatts),
-                measure.minMeasuredTotal(), measure.maxMeasuredTotal(), durationInSeconds, samples);
+                measure.minMeasuredTotal(), measure.maxMeasuredTotal(), durationInSeconds, samples,
+                measure.processors().output(measure.metadata()));
     }
 
     static String readableWithUnit(double milliWatts) {
@@ -52,5 +54,9 @@ record TimestampedValue(long timestamp, double value) {
     record TimestampedMeasures(long timestamp, double[] measures) {
     }
 
-    ComponentProcessor[] analyzers();
+    Processors processors();
+
+    void registerProcessorFor(int component, ComponentProcessor processor);
+
+    void registerTotalProcessor(ComponentProcessor processor);
 }
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/StoppedPowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/StoppedPowerMeasure.java
index cc826e9..74d2c92 100644
--- a/measure/src/main/java/net/laprun/sustainability/power/measure/StoppedPowerMeasure.java
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/StoppedPowerMeasure.java
@@ -7,6 +7,8 @@
 
 import net.laprun.sustainability.power.SensorMetadata;
 import net.laprun.sustainability.power.analysis.ComponentProcessor;
+import net.laprun.sustainability.power.analysis.DefaultProcessors;
+import net.laprun.sustainability.power.analysis.Processors;
 
 @SuppressWarnings("unused")
 public class StoppedPowerMeasure implements PowerMeasure {
@@ -16,7 +18,7 @@ public class StoppedPowerMeasure implements PowerMeasure {
     private final double total;
     private final double min;
     private final double max;
-    private final ComponentProcessor[] processors;
+    private Processors processors;
 
     public StoppedPowerMeasure(OngoingPowerMeasure powerMeasure) {
         this.measure = powerMeasure;
@@ -26,7 +28,7 @@ public StoppedPowerMeasure(OngoingPowerMeasure powerMeasure) {
         this.max = powerMeasure.maxMeasuredTotal();
         this.samples = powerMeasure.numberOfSamples();
         final var cardinality = metadata().componentCardinality();
-        processors = powerMeasure.analyzers();
+        processors = powerMeasure.processors();
     }
 
     @Override
@@ -87,7 +89,27 @@ private int ensureIndex(int upToIndex) {
     }
 
     @Override
-    public ComponentProcessor[] analyzers() {
+    public Processors processors() {
         return processors;
     }
+
+    @Override
+    public void registerProcessorFor(int component, ComponentProcessor processor) {
+        if (processor != null) {
+            if (Processors.empty == processors) {
+                processors = new DefaultProcessors(metadata().componentCardinality());
+            }
+            processors.registerProcessorFor(component, processor);
+        }
+    }
+
+    @Override
+    public void registerTotalProcessor(ComponentProcessor processor) {
+        if (processor != null) {
+            if (Processors.empty == processors) {
+                processors = new DefaultProcessors(metadata().componentCardinality());
+            }
+            processors.registerTotalProcessor(processor);
+        }
+    }
 }
diff --git a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java
index 939a582..bab89cf 100644
--- a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java
+++ b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java
@@ -3,21 +3,34 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
+import java.util.random.RandomGenerator;
 import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
 
 import net.laprun.sustainability.power.SensorMetadata;
+import net.laprun.sustainability.power.analysis.ComponentProcessor;
 
 public class OngoingPowerMeasureTest {
-    private final static SensorMetadata metadata = new SensorMetadata(List.of(), null) {
+    private final static SensorMetadata metadata = SensorMetadata
+            .withNewComponent("cp1", null, true, "mW", true)
+            .withNewComponent("cp2", null, true, "W", true)
+            .withNewComponent("cp3", null, true, "kW", true)
+            .build();
 
-        @Override
-        public int componentCardinality() {
-            return 3;
-        }
-    };
+    @Test
+    void checkThatTotalComponentIsProperlyAdded() {
+        final var metadata = SensorMetadata
+                .withNewComponent("cp1", null, true, null, false)
+                .withNewComponent("cp2", null, true, null, false)
+                .withNewComponent("cp3", null, true, null, false)
+                .build();
+        var measure = new OngoingPowerMeasure(metadata);
+        assertThat(measure.metadata().totalComponents()).isEmpty();
+    }
 
     @Test
     void testBasics() {
@@ -56,8 +69,65 @@ void testBasics() {
         assertThat(measure.getMeasuresFor(1)).hasValue(new double[] { m1c2, m2c2, m3c2 });
         assertThat(measure.getMeasuresFor(2)).isEmpty();
 
+        var measures = measure.getNthTimestampedMeasures(0);
+        assertThat(measures.measures()).isEqualTo(new double[] { m1c1, m1c2, m1c3, m1total });
+        measures = measure.getNthTimestampedMeasures(1);
+        assertThat(measures.measures()).isEqualTo(new double[] { m2c1, m2c2, m2c3, m2total });
+        measures = measure.getNthTimestampedMeasures(2);
+        assertThat(measures.measures()).isEqualTo(new double[] { m3c1, m3c2, m3c3, m3total });
+
         assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, measure.total());
         assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), measure.minMeasuredTotal());
         assertEquals(Stream.of(m1total, m2total, m3total).max(Double::compareTo).orElseThrow(), measure.maxMeasuredTotal());
     }
+
+    @Test
+    void processorsShouldBeCalled() {
+        final var random = Random.from(RandomGenerator.getDefault());
+        final var m1c1 = random.nextDouble();
+        final var m1c2 = random.nextDouble();
+        final var m2c1 = random.nextDouble();
+        final var m2c2 = random.nextDouble();
+
+        final var measure = new OngoingPowerMeasure(metadata);
+        measure.registerProcessorFor(0, new TestComponentProcessor());
+
+        // check that we can also associate a processor for the totals
+        measure.registerTotalProcessor(new TestComponentProcessor());
+
+        final var components = new double[metadata.componentCardinality()];
+        components[0] = m1c1;
+        components[1] = m1c2;
+        measure.recordMeasure(components);
+
+        components[0] = m2c1;
+        components[1] = m2c2;
+        measure.recordMeasure(components);
+
+        final var processors = measure.processors();
+        assertThat(processors.processorsFor(0)).hasSize(1);
+        assertThat(processors.processorsFor(1)).isEmpty();
+        assertThat(processors.processorsFor(2)).isEmpty();
+
+        var maybeProc = processors.processorFor(0, TestComponentProcessor.class);
+        assertThat(maybeProc).isPresent();
+        var processor = maybeProc.get();
+        assertThat(processor.values.getFirst().value()).isEqualTo(m1c1);
+        assertThat(processor.values.getLast().value()).isEqualTo(m2c1);
+
+        maybeProc = processors.totalProcessor().map(TestComponentProcessor.class::cast);
+        assertThat(maybeProc).isPresent();
+        processor = maybeProc.get();
+        assertThat(processor.values.getFirst().value()).isEqualTo(m1c1 + m1c2);
+        assertThat(processor.values.getLast().value()).isEqualTo(m2c1 + m2c2);
+    }
+
+    private static class TestComponentProcessor implements ComponentProcessor {
+        final List values = new ArrayList<>();
+
+        @Override
+        public void recordComponentValue(double value, long timestamp) {
+            values.add(new PowerMeasure.TimestampedValue(timestamp, value));
+        }
+    }
 }
diff --git a/metadata/pom.xml b/metadata/pom.xml
index 682585c..afb4bdb 100644
--- a/metadata/pom.xml
+++ b/metadata/pom.xml
@@ -16,7 +16,10 @@
         
             eu.hoefel
             units
-            4.1.1
+        
+        
+            org.assertj
+            assertj-core
         
     
 
@@ -37,4 +40,4 @@
             
         
     
-
\ No newline at end of file
+
diff --git a/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java b/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java
index 6f294d6..44d0393 100644
--- a/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java
+++ b/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java
@@ -42,23 +42,33 @@ public SensorMetadata(List components, String documentation)
         this.documentation = documentation;
         final var errors = new Errors();
         final var indices = new BitSet(cardinality);
+        final var totalIndices = new BitSet(cardinality);
         components.forEach(component -> {
             // check that index is valid
             final var index = component.index;
+            boolean indexValid = true;
             if (index < 0 || index >= cardinality) {
                 errors.addError(index + " is not a valid index: must be between 0 and " + (cardinality - 1));
+                indexValid = false;
             } else if (indices.get(index)) {
                 errors.addError("Multiple components are using index " + index + ": "
                         + components.stream().filter(cm -> index == cm.index).toList());
+                indexValid = false;
             } else {
                 // record index as known
                 indices.set(index);
             }
 
             // check that component's unit is commensurable to Watts if included in total
-            if (component.isIncludedInTotal && !component.isWattCommensurable()) {
-                errors.addError("Component " + component.name
-                        + " is not commensurate with a power measure. It needs to be expressible in Watts.");
+            if (component.isIncludedInTotal) {
+                if (indexValid) {
+                    totalIndices.set(index);
+                }
+
+                if (!component.isWattCommensurable()) {
+                    errors.addError("Component " + component.name
+                            + " is not commensurate with a power measure. It needs to be expressible in Watts.");
+                }
             }
 
             if (this.components.containsKey(component.name)) {
@@ -81,7 +91,7 @@ public SensorMetadata(List components, String documentation)
             throw new IllegalArgumentException(errors.formatErrors());
         }
 
-        this.totalComponents = indices.stream().toArray();
+        this.totalComponents = totalIndices.stream().toArray();
     }
 
     @JsonCreator
@@ -96,6 +106,15 @@ public static SensorMetadata.Builder withNewComponent(String name, String descri
         return new SensorMetadata.Builder().withNewComponent(name, description, isAttributed, unit, participatesInTotal);
     }
 
+    public static SensorMetadata.Builder from(SensorMetadata sensorMetadata) {
+        final var builder = new Builder();
+        sensorMetadata.components.values().stream().sorted(Comparator.comparing(ComponentMetadata::index))
+                .forEach(component -> builder.withNewComponent(component.name, component.description, component.isAttributed,
+                        component.unit,
+                        component.isIncludedInTotal));
+        return builder;
+    }
+
     @Override
     public String toString() {
         final var sb = new StringBuilder();
@@ -169,6 +188,20 @@ public int[] totalComponents() {
         return totalComponents;
     }
 
+    /**
+     * Retrieves the metadata associated with the specified component index if it exists.
+     *
+     * @param componentIndex the index of the component we want to retrieve the metadata for
+     * @return the {@link ComponentMetadata} associated with the specified index if it exists
+     * @throws IllegalArgumentException if no component is associated with the specified index
+     */
+    public ComponentMetadata metadataFor(int componentIndex) {
+        return components.values().stream()
+                .filter(cm -> componentIndex == cm.index)
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("No component was found for index " + componentIndex));
+    }
+
     public static class Builder {
         private final List components = new ArrayList<>();
         private int currentIndex = 0;
diff --git a/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java b/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java
index 39a3414..d395f78 100644
--- a/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java
+++ b/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java
@@ -1,5 +1,6 @@
 package net.laprun.sustainability.power;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.List;
@@ -69,4 +70,26 @@ void shouldFailIfNoComponentsAreProvided() {
         final var message = e.getMessage();
         assertTrue(message.contains("Must provide components"));
     }
+
+    @Test
+    void shouldProperlyRecordTotalComponents() {
+        final String COMPONENT1_NAME = "c1";
+        final String COMPONENT2_NAME = "c2";
+        final String COMPONENT3_NAME = "c3";
+        var metadata = SensorMetadata
+                .withNewComponent(COMPONENT1_NAME, "component 1", true, "mW", true)
+                .withNewComponent(COMPONENT2_NAME, "component 2", true, "mW", false)
+                .withNewComponent(COMPONENT3_NAME, "component 3", true, "mW", true)
+                .build();
+
+        assertArrayEquals(new int[] { 0, 2 }, metadata.totalComponents());
+
+        metadata = SensorMetadata
+                .withNewComponent("cp1", null, true, null, false)
+                .withNewComponent("cp2", null, true, null, false)
+                .withNewComponent("cp3", null, true, null, false)
+                .build();
+
+        assertThat(metadata.totalComponents()).isEmpty();
+    }
 }
diff --git a/pom.xml b/pom.xml
index 48c9a6f..b32c9c6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,8 +44,12 @@
         UTF-8
         quarkus-bom
         io.quarkus
-        3.17.4
         true
+
+        3.17.4
+        3.26.3
+        4.1.1
+
         3.5.2
         3.5.2
         2.24.1
@@ -82,6 +86,17 @@
                 pom
                 import
             
+            
+                eu.hoefel
+                units
+                ${units.version}
+            
+            
+                org.assertj
+                assertj-core
+                ${assertj.version}
+                test
+