348 lines
11 KiB
Python
348 lines
11 KiB
Python
#!/usr/bin/env python
|
|
|
|
import datetime
|
|
import os
|
|
import time
|
|
from unittest import mock
|
|
|
|
import mozunit
|
|
import pytest
|
|
import six
|
|
from mozpower.intel_power_gadget import (
|
|
IPGEmptyFileError,
|
|
IPGMissingOutputFileError,
|
|
IPGTimeoutError,
|
|
IPGUnknownValueTypeError,
|
|
)
|
|
|
|
|
|
def thread_is_alive(thread):
|
|
if six.PY2:
|
|
return thread.isAlive()
|
|
return thread.is_alive()
|
|
|
|
|
|
def test_ipg_pathsplitting(ipg_obj):
|
|
"""Tests that the output file path and prefix was properly split.
|
|
This test assumes that it is in the same directory as the conftest.py file.
|
|
"""
|
|
assert (
|
|
ipg_obj.output_dir_path == os.path.abspath(os.path.dirname(__file__)) + "/files"
|
|
)
|
|
assert ipg_obj.output_file_prefix == "raptor-tp6-amazon-icecat_powerlog"
|
|
|
|
|
|
def test_ipg_get_output_file_path(ipg_obj):
|
|
"""Tests that the output file path is constantly changing
|
|
based on the file_counter value.
|
|
"""
|
|
test_path = "/test_path/"
|
|
test_ext = ".txt"
|
|
ipg_obj._file_counter = 1
|
|
ipg_obj._output_dir_path = test_path
|
|
ipg_obj._output_file_ext = test_ext
|
|
|
|
for i in range(1, 6):
|
|
fpath = ipg_obj._get_output_file_path()
|
|
|
|
assert fpath.startswith(test_path)
|
|
assert fpath.endswith(test_ext)
|
|
assert str(i) in fpath
|
|
|
|
|
|
def test_ipg_start_and_stop(ipg_obj):
|
|
"""Tests that the IPG thread can start and stop properly."""
|
|
|
|
def subprocess_side_effect(*args, **kwargs):
|
|
time.sleep(1)
|
|
|
|
with mock.patch("subprocess.check_output") as m:
|
|
m.side_effect = subprocess_side_effect
|
|
|
|
# Start recording IPG measurements
|
|
ipg_obj.start_ipg()
|
|
assert not ipg_obj._stop
|
|
|
|
# Wait a bit for thread to start, then check it
|
|
timeout = 10
|
|
start = time.time()
|
|
while time.time() - start < timeout and not ipg_obj._running:
|
|
time.sleep(1)
|
|
|
|
assert ipg_obj._running
|
|
assert thread_is_alive(ipg_obj._thread)
|
|
|
|
# Stop recording IPG measurements
|
|
ipg_obj.stop_ipg(wait_interval=1, timeout=30)
|
|
assert ipg_obj._stop
|
|
assert not ipg_obj._running
|
|
|
|
|
|
def test_ipg_stopping_timeout(ipg_obj):
|
|
"""Tests that an IPGTimeoutError is raised when
|
|
the thread is still "running" and the wait in _wait_for_ipg
|
|
has exceeded the timeout value.
|
|
"""
|
|
with pytest.raises(IPGTimeoutError):
|
|
ipg_obj._running = True
|
|
ipg_obj._wait_for_ipg(wait_interval=1, timeout=2)
|
|
|
|
|
|
def test_ipg_rh_combine_cumulatives(ipg_rh_obj):
|
|
"""Tests that cumulatives are correctly combined in
|
|
the _combine_cumulative_rows function.
|
|
"""
|
|
cumulatives_to_combine = [
|
|
[0, 1, 2, 3, 4, 5],
|
|
[0, 1, 2, 3, 4, 5],
|
|
[0, 1, 2, 3, 4, 5],
|
|
[0, 1, 2, 3, 4, 5],
|
|
]
|
|
|
|
combined_cumulatives = ipg_rh_obj._combine_cumulative_rows(cumulatives_to_combine)
|
|
|
|
# Check that accumulation worked, final value must be the maximum
|
|
assert combined_cumulatives[-1] == max(combined_cumulatives)
|
|
|
|
# Check that the cumulative values are monotonically increasing
|
|
for count, val in enumerate(combined_cumulatives[:-1]):
|
|
assert combined_cumulatives[count + 1] - val >= 0
|
|
|
|
|
|
def test_ipg_rh_clean_file(ipg_rh_obj):
|
|
"""Tests that IPGResultsHandler correctly cleans the data
|
|
from one file.
|
|
"""
|
|
file = ipg_rh_obj._output_files[0]
|
|
linecount = 0
|
|
with open(file) as f:
|
|
for line in f:
|
|
linecount += 1
|
|
|
|
results, summary, clean_file = ipg_rh_obj._clean_ipg_file(file)
|
|
|
|
# Check that each measure from the csv header
|
|
# is in the results dict and that the clean file output
|
|
# exists.
|
|
for measure in results:
|
|
assert measure in ipg_rh_obj._csv_header
|
|
assert os.path.exists(clean_file)
|
|
|
|
clean_rows = []
|
|
with open(clean_file) as f:
|
|
for line in f:
|
|
if line.strip():
|
|
clean_rows.append(line)
|
|
|
|
# Make sure that the results and summary entries
|
|
# have the expected lengths.
|
|
for measure in results:
|
|
# Add 6 for new lines that were removed
|
|
assert len(results[measure]) + len(summary) + 6 == linecount
|
|
# Subtract 1 for the csv header
|
|
assert len(results[measure]) == len(clean_rows) - 1
|
|
|
|
|
|
def test_ipg_rh_clean_ipg_data_no_files(ipg_rh_obj):
|
|
"""Tests that IPGResultsHandler correctly handles the case
|
|
when no output files exist.
|
|
"""
|
|
ipg_rh_obj._output_files = []
|
|
clean_data = ipg_rh_obj.clean_ipg_data()
|
|
assert clean_data is None
|
|
|
|
|
|
def test_ipg_rh_clean_ipg_data(ipg_rh_obj):
|
|
"""Tests that IPGResultsHandler correctly handles cleaning
|
|
all known files and that the results and the merged output
|
|
are correct.
|
|
"""
|
|
clean_data = ipg_rh_obj.clean_ipg_data()
|
|
clean_files = ipg_rh_obj.cleaned_files
|
|
merged_output_path = ipg_rh_obj.merged_output_path
|
|
|
|
# Check that the expected output exists
|
|
assert clean_data is not None
|
|
assert len(clean_files) == len(ipg_rh_obj._output_files)
|
|
assert os.path.exists(merged_output_path)
|
|
|
|
# Check that the merged file length and results length
|
|
# is correct, and that no lines were lost and no extra lines
|
|
# were added.
|
|
expected_merged_line_count = 0
|
|
for file in clean_files:
|
|
with open(file) as f:
|
|
for count, line in enumerate(f):
|
|
if count == 0:
|
|
continue
|
|
if line.strip():
|
|
expected_merged_line_count += 1
|
|
|
|
merged_line_count = 0
|
|
with open(merged_output_path) as f:
|
|
for count, line in enumerate(f):
|
|
if count == 0:
|
|
continue
|
|
if line.strip():
|
|
merged_line_count += 1
|
|
|
|
assert merged_line_count == expected_merged_line_count
|
|
for measure in clean_data:
|
|
assert len(clean_data[measure]) == merged_line_count
|
|
|
|
# Check that the clean data rows are ordered in increasing time
|
|
times_in_seconds = []
|
|
for sys_time in clean_data["System Time"]:
|
|
split_sys_time = sys_time.split(":")
|
|
hour_min_sec = ":".join(split_sys_time[:-1])
|
|
millis = float(split_sys_time[-1]) / 1000
|
|
|
|
timestruct = time.strptime(hour_min_sec, "%H:%M:%S")
|
|
times_in_seconds.append(
|
|
datetime.timedelta(
|
|
hours=timestruct.tm_hour,
|
|
minutes=timestruct.tm_min,
|
|
seconds=timestruct.tm_sec,
|
|
).total_seconds()
|
|
+ millis
|
|
)
|
|
|
|
for count, val in enumerate(times_in_seconds[:-1]):
|
|
assert times_in_seconds[count + 1] - val >= 0
|
|
|
|
|
|
def test_ipg_rh_format_to_perfherder_with_no_results(ipg_rh_obj):
|
|
"""Tests that formatting the data to a perfherder-like format
|
|
fails when clean_ipg_data was not called beforehand.
|
|
"""
|
|
formatted_data = ipg_rh_obj.format_ipg_data_to_partial_perfherder(
|
|
1000, ipg_rh_obj._output_file_prefix
|
|
)
|
|
assert formatted_data is None
|
|
|
|
|
|
def test_ipg_rh_format_to_perfherder_without_cutoff(ipg_rh_obj):
|
|
"""Tests that formatting the data to a perfherder-like format
|
|
works as expected.
|
|
"""
|
|
ipg_rh_obj.clean_ipg_data()
|
|
formatted_data = ipg_rh_obj.format_ipg_data_to_partial_perfherder(
|
|
1000, ipg_rh_obj._output_file_prefix
|
|
)
|
|
|
|
# Check that the expected entries exist
|
|
assert len(formatted_data.keys()) == 5
|
|
assert "utilization" in formatted_data and "power-usage" in formatted_data
|
|
|
|
assert (
|
|
formatted_data["power-usage"]["test"]
|
|
== ipg_rh_obj._output_file_prefix + "-cumulative"
|
|
)
|
|
assert (
|
|
formatted_data["utilization"]["test"]
|
|
== ipg_rh_obj._output_file_prefix + "-utilization"
|
|
)
|
|
assert (
|
|
formatted_data["frequency-gpu"]["test"]
|
|
== ipg_rh_obj._output_file_prefix + "-frequency-gpu"
|
|
)
|
|
assert (
|
|
formatted_data["frequency-cpu"]["test"]
|
|
== ipg_rh_obj._output_file_prefix + "-frequency-cpu"
|
|
)
|
|
assert (
|
|
formatted_data["power-watts"]["test"]
|
|
== ipg_rh_obj._output_file_prefix + "-watts"
|
|
)
|
|
|
|
for measure in formatted_data:
|
|
# Make sure that the data exists
|
|
assert len(formatted_data[measure]["values"]) >= 1
|
|
|
|
for valkey in formatted_data[measure]["values"]:
|
|
# Make sure the names were simplified
|
|
assert "(" not in valkey
|
|
assert ")" not in valkey
|
|
|
|
# Check that gpu utilization doesn't exist but cpu does
|
|
utilization_vals = formatted_data["utilization"]["values"]
|
|
assert "cpu" in utilization_vals
|
|
assert "gpu" not in utilization_vals
|
|
|
|
expected_fields = ["processor-cores", "processor-package", "gpu", "dram"]
|
|
consumption_vals = formatted_data["power-usage"]["values"]
|
|
|
|
consumption_vals_measures = list(consumption_vals.keys())
|
|
|
|
# This assertion ensures that the consumption values contain the expected
|
|
# fields and nothing more.
|
|
assert not list(set(consumption_vals_measures) - set(expected_fields))
|
|
|
|
|
|
def test_ipg_rh_format_to_perfherder_with_cutoff(ipg_rh_obj):
|
|
"""Tests that formatting the data to a perfherder-like format
|
|
works as expected.
|
|
"""
|
|
ipg_rh_obj.clean_ipg_data()
|
|
formatted_data = ipg_rh_obj.format_ipg_data_to_partial_perfherder(
|
|
2.5, ipg_rh_obj._output_file_prefix
|
|
)
|
|
|
|
# Check that the formatted data was cutoff at the correct point,
|
|
# expecting that only the first row of merged will exist.
|
|
utilization_vals = formatted_data["utilization"]["values"]
|
|
assert utilization_vals["cpu"] == 14
|
|
|
|
# Expected vals are ordered in this way: [processor, cores, dram, gpu]
|
|
expected_vals = [6.517, 5.847, 0.244, 0.006]
|
|
consumption_vals = [
|
|
formatted_data["power-usage"]["values"][measure]
|
|
for measure in formatted_data["power-usage"]["values"]
|
|
]
|
|
assert not list(set(expected_vals) - set(consumption_vals))
|
|
|
|
|
|
def test_ipg_rh_missingoutputfile(ipg_rh_obj):
|
|
"""Tests that the IPGMissingOutputFileError is raised
|
|
when a bad file path is passed to _clean_ipg_file.
|
|
"""
|
|
bad_files = ["non-existent-file"]
|
|
with pytest.raises(IPGMissingOutputFileError):
|
|
ipg_rh_obj._clean_ipg_file(bad_files[0])
|
|
|
|
ipg_rh_obj._output_files = bad_files
|
|
with pytest.raises(IPGMissingOutputFileError):
|
|
ipg_rh_obj.clean_ipg_data()
|
|
|
|
|
|
def test_ipg_rh_emptyfile(ipg_rh_obj):
|
|
"""Tests that the empty file error is raised when
|
|
a file exists, but does not contain any results in
|
|
it.
|
|
"""
|
|
base_path = os.path.abspath(os.path.dirname(__file__)) + "/files/"
|
|
bad_files = [base_path + "emptyfile.txt"]
|
|
with pytest.raises(IPGEmptyFileError):
|
|
ipg_rh_obj._clean_ipg_file(bad_files[0])
|
|
|
|
ipg_rh_obj._output_files = bad_files
|
|
with pytest.raises(IPGEmptyFileError):
|
|
ipg_rh_obj.clean_ipg_data()
|
|
|
|
|
|
def test_ipg_rh_valuetypeerrorfile(ipg_rh_obj):
|
|
"""Tests that the IPGUnknownValueTypeError is raised
|
|
when a bad entry is encountered in a file that is cleaned.
|
|
"""
|
|
base_path = os.path.abspath(os.path.dirname(__file__)) + "/files/"
|
|
bad_files = [base_path + "valueerrorfile.txt"]
|
|
with pytest.raises(IPGUnknownValueTypeError):
|
|
ipg_rh_obj._clean_ipg_file(bad_files[0])
|
|
|
|
ipg_rh_obj._output_files = bad_files
|
|
with pytest.raises(IPGUnknownValueTypeError):
|
|
ipg_rh_obj.clean_ipg_data()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
mozunit.main()
|