diff --git a/Robot-Framework/config/variables.robot b/Robot-Framework/config/variables.robot index 6c006bf5..bff9c3ea 100644 --- a/Robot-Framework/config/variables.robot +++ b/Robot-Framework/config/variables.robot @@ -62,6 +62,12 @@ Set Variables ${result} Run Process sh -c cat /run/secrets/wifi-password shell=true Set Global Variable ${TEST_WIFI_PSWD} ${result.stdout} + Run Keyword And Ignore Error Set Global Variable ${RPI_IP_ADDRESS} ${config['addresses']['measurement_agent']['device_ip_address']} + ${result} Run Process sh -c cat /run/secrets/pi-login shell=true + Set Global Variable ${LOGIN_PI} ${result.stdout} + ${result} Run Process sh -c cat /run/secrets/pi-pass shell=true + Set Global Variable ${PASSWORD_PI} ${result.stdout} + Set Log Level INFO IF $BUILD_ID != '${EMPTY}' diff --git a/Robot-Framework/lib/parse_power_data.py b/Robot-Framework/lib/parse_power_data.py new file mode 100644 index 00000000..0baa6962 --- /dev/null +++ b/Robot-Framework/lib/parse_power_data.py @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: 2022-2024 Technology Innovation Institute (TII) +# SPDX-License-Identifier: Apache-2.0 + +import pandas as pd +import logging +import matplotlib.pyplot as plt +import csv + + +def extract_time_interval(csv_file, start_time, end_time): + columns = ['time', 'meas_counter', 'power'] + data = pd.read_csv(csv_file, names=columns) + interval = data.query("{} < time < {}".format(start_time, end_time)) + interval.to_csv('power_interval.csv', index=False) + + # Check if measurement frequency was within normal limits + # Reset the flag file + with open("low_frequency_flag", 'w'): + pass + normal_meas_frequency = 312 + tolerance = 50 + low_frequency = data[data['meas_counter'] < normal_meas_frequency - tolerance] + if not low_frequency.empty: + logging.info("Low measurement frequency detected:") + logging.info(low_frequency) + with open("low_frequency_timestamps.csv", 'a', newline='') as csvfile: + csvwriter = csv.writer(csvfile) + for index, row in low_frequency.iterrows(): + csvwriter.writerow(row) + with open("low_frequency_flag", 'w', newline='') as flag: + flag.write("Warning: unusually low measurement frequency detected") + return False + return True + +def generate_graph(csv_file, test_name): + data = pd.read_csv(csv_file) + start_time = data['time'].values[0] + end_time = data['time'].values[data.index.max()] + plt.figure(figsize=(20, 10)) + plt.set_loglevel('WARNING') + + # Show only hh-mm-ss part of the time at x-axis ticks + data['time'] = data['time'].str[11:19] + + plt.ticklabel_format(axis='y', style='plain') + plt.plot(data['time'], data['power'], marker='o', linestyle='-', color='b') + plt.yticks(fontsize=14) + + # Show full timestamps of the beginning and the end of the plotted time interval + plt.suptitle(f'Device power consumption {start_time} - {end_time}', fontsize=18, fontweight='bold') + + # Add note to plot in case of issues in measurement frequency + with open("low_frequency_flag", 'r') as flag: + low_frequency_note = flag.readline() + plt.title(f'During "{test_name}"\n{low_frequency_note}', loc='center', fontweight="bold", fontsize=16) + plt.ylabel('Power (mW)', fontsize=16) + plt.grid(True) + plt.xticks(data['time'], rotation=45, fontsize=14) + + # Set maximum for tick number + plt.locator_params(axis='x', nbins=40) + + plt.savefig(f'../test-suites/power_test.png') + return + +def mean_power(csv_file): + columns = ['time', 'meas_counter', 'power'] + data = pd.read_csv(csv_file, names=columns) + mean_value = data['power'].mean() + return mean_value diff --git a/Robot-Framework/resources/power_meas_keywords.resource b/Robot-Framework/resources/power_meas_keywords.resource new file mode 100644 index 00000000..640ed670 --- /dev/null +++ b/Robot-Framework/resources/power_meas_keywords.resource @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: 2022-2024 Technology Innovation Institute (TII) +# SPDX-License-Identifier: Apache-2.0 + +*** Settings *** +Resource ../config/variables.robot +Library ../lib/parse_power_data.py +Library SSHLibrary +Library DateTime + +*** Variables *** +${SSH_MEASUREMENT} ${EMPTY} +${start_timestamp} ${EMPTY} +${RPI_IP_ADDRESS} ${EMPTY} + + +*** Keywords *** + +Check variable availability + ${value}= Get Variable Value ${RPI_IP_ADDRESS} + IF $value!='${EMPTY}' + RETURN ${True} + ELSE + RETURN ${False} + END + +Start power measurement + [Documentation] Connect to the measurement agent and run script to start collecting measurement results + [Arguments] ${id}=power_data ${timeout}=200 + ${availability} Check variable availability + IF ${availability}==False + Log To Console Power measurement agent IP address not defined. Ignoring all power measurement related keywords. + Set Global Variable ${SSH_MEASUREMENT} ${EMPTY} + RETURN + END + ${status} ${connection} Run Keyword And Ignore Error Connect to measurement agent + IF '${status}'!='PASS' + Set Global Variable ${SSH_MEASUREMENT} ${EMPTY} + Log To Console Power measurement agent not found. Ignoring all power measurement related keywords. + RETURN + END + # Multiple logging processes not allowed (for now) + Stop recording power + Start recording power ${id} ${timeout} + +Connect to measurement agent + [Documentation] Set up SSH connection to the measurement agent + [Arguments] ${IP}=${RPI_IP_ADDRESS} ${PORT}=22 ${target_output}=ghaf@raspberrypi + # Use existing connection if available + ${status} ${output} Run Keyword And Ignore Error Switch Connection ${SSH_MEASUREMENT} + IF '${status}'=='PASS' + Log To Console Switched connection to measurement agent. + Set Global Variable ${SSH_MEASUREMENT} ${SSH_MEASUREMENT} + RETURN ${SSH_MEASUREMENT} + END + Log To Console Connecting to measurement agent + ${connection}= Open Connection ${IP} port=${PORT} prompt=\$ timeout=15 + ${output}= Login username=${LOGIN_PI} password=${PASSWORD_PI} + Should Contain ${output} ${target_output} + Set Global Variable ${SSH_MEASUREMENT} ${connection} + RETURN ${SSH_MEASUREMENT} + +Start recording power + [Arguments] ${file_name} ${timeout} + Log To Console Starting to record power measurements + Run Keyword And Ignore Error Execute Command nohup python /home/ghaf/ghaf/ghaf-power-measurement/measure_power.py ${file_name}.csv ${timeout} > output.log 2>&1 & timeout=3 + +Stop recording power + IF $SSH_MEASUREMENT=='${EMPTY}' + Log To Console No connection to power measurement device. Ignoring all power measurement related keywords. + RETURN + END + Log To Console Stopping power recording + Run Keyword And Ignore Error Execute Command pkill python timeout=3 + +Get power record + [Arguments] ${file_name}=power_data.csv + IF $SSH_MEASUREMENT=='${EMPTY}' + Log To Console No connection to power measurement device. Ignoring all power measurement related keywords. + RETURN + END + Run Keyword And Ignore Error Connect to measurement agent + Run Keyword And Ignore Error SSHLibrary.Get File /home/ghaf/ghaf/power_data/${file_name} ../../../power_measurements/ + +Save power measurement interval + [Documentation] Extract measurement data within given time interval + [Arguments] ${file_name} ${start_time} ${end_time} + IF $SSH_MEASUREMENT=='${EMPTY}' + Log To Console No connection to power measurement device. Ignoring all power measurement related keywords. + RETURN + END + Log To Console Extract power data from given time interval + ${time_interval} DateTime.Subtract Date From Date ${end_time} ${start_time} exclude_millis=True + IF ${time_interval} < 0 + Log To Console Invalid timestamp critera for extracting power data + RETURN + END + Run Keyword And Ignore Error Extract time interval ../../../power_measurements/${file_name} ${start_time} ${end_time} + +Generate power plot + [Documentation] Extract power data from start_timestamp to current time. + ... Plot power vs time and save to png file. + [Arguments] ${id} ${test_name} + IF $SSH_MEASUREMENT=='${EMPTY}' + Log To Console No connection to power measurement device. Ignoring all power measurement related keywords. + RETURN + END + ${end_timestamp} Get current timestamp + Run Keyword And Ignore Error Connect to measurement agent + Run Keyword And Ignore Error Get power record ${id}.csv + Run Keyword And Ignore Error Save power measurement interval ${id}.csv '${start_timestamp}' '${end_timestamp}' + Run Keyword And Ignore Error Generate graph power_interval.csv ${test_name} + Run Keyword And Ignore Error Log Power plot HTML + +Set start timestamp + ${current_time} DateTime.Get Current Date UTC exclude_millis=yes + Set Global Variable ${start_timestamp} ${current_time} + Log To Console ${start_timestamp} + +Get current timestamp + ${current_time} DateTime.Get Current Date UTC exclude_millis=yes + RETURN ${current_time} + +Log average power + [Arguments] ${file_name} + IF $SSH_MEASUREMENT=='${EMPTY}' + Log To Console No connection to power measurement device. Ignoring all power measurement related keywords. + RETURN + END + ${keyword_status} ${mean_P} Run Keyword And Ignore Error Mean power ${file_name} + # TODO: With the statistics tools of performance testing also average power values could be plotted and monitored diff --git a/flake.lock b/flake.lock index 00db58a8..0326b7a4 100644 --- a/flake.lock +++ b/flake.lock @@ -58,4 +58,4 @@ }, "root": "root", "version": 7 -} +} \ No newline at end of file