From a1e3ca835f8acfda2aa2c5502da5c1d305d59b03 Mon Sep 17 00:00:00 2001
From: Daniel Friesel <daniel.friesel@uos.de>
Date: Tue, 15 Dec 2020 18:56:35 +0100
Subject: add proof of concept for EnergyTrace++ based drift compensation

---
 lib/lennart/DataProcessor.py | 37 +++++++++++++++++++++++++++++++++++--
 lib/loader.py                | 28 ++++++++++++++++++++++++++--
 2 files changed, 61 insertions(+), 4 deletions(-)

(limited to 'lib')

diff --git a/lib/lennart/DataProcessor.py b/lib/lennart/DataProcessor.py
index dd45996..e2cfbd2 100644
--- a/lib/lennart/DataProcessor.py
+++ b/lib/lennart/DataProcessor.py
@@ -8,7 +8,9 @@ logger = logging.getLogger(__name__)
 
 
 class DataProcessor:
-    def __init__(self, sync_data, et_timestamps, et_power):
+    def __init__(
+        self, sync_data, et_timestamps, et_power, hw_statechange_indexes=list()
+    ):
         """
         Creates DataProcessor object.
 
@@ -22,6 +24,7 @@ class DataProcessor:
         self.et_timestamps = et_timestamps
         # energytrace power values
         self.et_power_values = et_power
+        self.hw_statechange_indexes = hw_statechange_indexes
         self.sync_data = sync_data
         self.start_offset = 0
 
@@ -140,7 +143,10 @@ class DataProcessor:
         # As the start and stop timestamps have already been synchronized, we only adjust
         # actual transition timestamps here.
         if os.getenv("DFATOOL_COMPENSATE_DRIFT"):
-            with_drift_compensation = self.compensateDrift(with_drift[4:-8])
+            if len(self.hw_statechange_indexes):
+                with_drift_compensation = self.compensateDriftPlusplus(with_drift[4:-8])
+            else:
+                with_drift_compensation = self.compensateDrift(with_drift[4:-8])
             self.sync_timestamps[4:-8] = with_drift_compensation
 
     def addDrift(self, input_timestamps, end_timestamp, end_offset, start_timestamp):
@@ -163,6 +169,33 @@ class DataProcessor:
         ) * endFactor + start_timestamp
         return sync_timestamps_with_drift
 
+    def compensateDriftPlusplus(self, sync_timestamps):
+        expected_transition_start_timestamps = sync_timestamps[::2]
+        compensated_timestamps = list()
+        drift = 0
+        for i, expected_start_ts in enumerate(expected_transition_start_timestamps):
+            expected_end_ts = sync_timestamps[i * 2 + 1]
+            et_timestamps_start = bisect_left(
+                self.et_timestamps, expected_start_ts - 5e-3
+            )
+            et_timestamps_end = bisect_right(
+                self.et_timestamps, expected_start_ts + 5e-3
+            )
+
+            candidate_indexes = list()
+            for index in self.hw_statechange_indexes:
+                if et_timestamps_start <= index <= et_timestamps_end:
+                    candidate_indexes.append(index)
+
+            if len(candidate_indexes) >= 2:
+                drift = self.et_timestamps[candidate_indexes[0]] - expected_start_ts
+
+            compensated_timestamps.append(expected_start_ts + drift)
+            compensated_timestamps.append(expected_end_ts + drift)
+            print(drift)
+
+        return compensated_timestamps
+
     def compensateDrift(self, sync_timestamps):
         from dfatool.pelt import PELT
 
diff --git a/lib/loader.py b/lib/loader.py
index 7a0fc4e..b5cfa97 100644
--- a/lib/loader.py
+++ b/lib/loader.py
@@ -1244,13 +1244,14 @@ def _load_energytrace(data_string):
     data_lines = filter(lambda x: len(x) > 0 and x[0] != "#", lines)
 
     data = np.empty((data_count, 4))
+    hardware_states = [None for i in range(data_count)]
 
     for i, line in enumerate(data_lines):
         fields = line.split(" ")
         if len(fields) == 4:
             timestamp, current, voltage, total_energy = map(int, fields)
         elif len(fields) == 5:
-            # cpustate = fields[0]
+            hardware_states[i] = fields[0]
             timestamp, current, voltage, total_energy = map(int, fields[1:])
         else:
             raise RuntimeError('cannot parse line "{}"'.format(line))
@@ -1264,13 +1265,32 @@ def _load_energytrace(data_string):
 
     sample_rate = data_count / (m_duration_us * 1e-6)
 
+    hardware_state_changes = list()
+    if hardware_states[0]:
+        prev_state = hardware_states[0]
+        for i, state in enumerate(hardware_states):
+            if (
+                state != prev_state
+                and state != "0000000000000000"
+                and prev_state != "0000000000000000"
+            ):
+                hardware_state_changes.append(i)
+            if state != "0000000000000000":
+                prev_state = state
+
     logger.debug(
         "got {} samples with {} seconds of log data ({} Hz)".format(
             data_count, m_duration_us * 1e-6, sample_rate
         )
     )
 
-    return (interval_start_timestamp, interval_duration, interval_power, sample_rate)
+    return (
+        interval_start_timestamp,
+        interval_duration,
+        interval_power,
+        sample_rate,
+        hardware_state_changes,
+    )
 
 
 class EnergyTraceWithBarcode:
@@ -1343,6 +1363,7 @@ class EnergyTraceWithBarcode:
             self.interval_duration,
             self.interval_power,
             self.sample_rate,
+            self.hw_statechange_indexes,
         ) = _load_energytrace(log_data[0])
 
     def ts_to_index(self, timestamp):
@@ -1723,6 +1744,7 @@ class EnergyTraceWithLogicAnalyzer:
             self.interval_duration,
             self.interval_power,
             self.sample_rate,
+            self.hw_statechange_indexes,
         ) = _load_energytrace(log_data[1])
 
     def analyze_states(self, traces, offline_index: int):
@@ -1761,6 +1783,7 @@ class EnergyTraceWithLogicAnalyzer:
             sync_data=self.sync_data,
             et_timestamps=self.interval_start_timestamp,
             et_power=self.interval_power,
+            hw_statechange_indexes=self.hw_statechange_indexes,
         )
         dp.run()
         energy_trace_new = dp.getStatesdfatool(
@@ -1838,6 +1861,7 @@ class EnergyTraceWithTimer(EnergyTraceWithLogicAnalyzer):
             self.interval_duration,
             self.interval_power,
             self.sample_rate,
+            self.hw_statechange_indexes,
         ) = _load_energytrace(log_data[0])
 
     def analyze_states(self, traces, offline_index: int):
-- 
cgit v1.2.3