summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozpower/tests/test_intelpowergadget.py
blob: f508c83915ad4c7e781c15c655d6ec0a0de80f13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
#!/usr/bin/env python

from __future__ import absolute_import

import datetime
import mock
import mozunit
import os
import pytest
import time

from mozpower.intel_power_gadget import (
    IPGEmptyFileError,
    IPGMissingOutputFileError,
    IPGTimeoutError,
    IPGUnknownValueTypeError,
)

import six


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-firefox_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, "r") 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, "r") 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, "r") 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, "r") 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()