xref: /aosp_15_r20/external/toolchain-utils/crosperf/schedv2_unittest.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# -*- coding: utf-8 -*-
3*760c253cSXin Li
4*760c253cSXin Li# Copyright 2015 The ChromiumOS Authors
5*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
6*760c253cSXin Li# found in the LICENSE file.
7*760c253cSXin Li
8*760c253cSXin Li"""This contains the unit tests for the new Crosperf task scheduler."""
9*760c253cSXin Li
10*760c253cSXin Li
11*760c253cSXin Liimport functools
12*760c253cSXin Liimport io
13*760c253cSXin Liimport unittest
14*760c253cSXin Liimport unittest.mock as mock
15*760c253cSXin Li
16*760c253cSXin Liimport benchmark_run
17*760c253cSXin Lifrom cros_utils.command_executer import CommandExecuter
18*760c253cSXin Lifrom experiment_factory import ExperimentFactory
19*760c253cSXin Lifrom experiment_file import ExperimentFile
20*760c253cSXin Lifrom experiment_runner_unittest import FakeLogger
21*760c253cSXin Lifrom schedv2 import Schedv2
22*760c253cSXin Liimport test_flag
23*760c253cSXin Li
24*760c253cSXin Li
25*760c253cSXin LiEXPERIMENT_FILE_1 = """\
26*760c253cSXin Liboard: daisy
27*760c253cSXin Liremote: chromeos-daisy1.cros chromeos-daisy2.cros
28*760c253cSXin Lilocks_dir: /tmp
29*760c253cSXin Li
30*760c253cSXin Libenchmark: kraken {
31*760c253cSXin Li  suite: telemetry_Crosperf
32*760c253cSXin Li  iterations: 3
33*760c253cSXin Li}
34*760c253cSXin Li
35*760c253cSXin Liimage1 {
36*760c253cSXin Li  chromeos_image: /chromeos/src/build/images/daisy/latest/cros_image1.bin
37*760c253cSXin Li  remote: chromeos-daisy3.cros
38*760c253cSXin Li}
39*760c253cSXin Li
40*760c253cSXin Liimage2 {
41*760c253cSXin Li  chromeos_image: /chromeos/src/build/imaages/daisy/latest/cros_image2.bin
42*760c253cSXin Li  remote: chromeos-daisy4.cros chromeos-daisy5.cros
43*760c253cSXin Li}
44*760c253cSXin Li"""
45*760c253cSXin Li
46*760c253cSXin LiEXPERIMENT_FILE_WITH_FORMAT = """\
47*760c253cSXin Liboard: daisy
48*760c253cSXin Liremote: chromeos-daisy1.cros chromeos-daisy2.cros
49*760c253cSXin Lilocks_dir: /tmp
50*760c253cSXin Li
51*760c253cSXin Libenchmark: kraken {{
52*760c253cSXin Li  suite: telemetry_Crosperf
53*760c253cSXin Li  iterations: {kraken_iterations}
54*760c253cSXin Li}}
55*760c253cSXin Li
56*760c253cSXin Liimage1 {{
57*760c253cSXin Li  chromeos_image: /chromeos/src/build/images/daisy/latest/cros_image1.bin
58*760c253cSXin Li  remote: chromeos-daisy3.cros
59*760c253cSXin Li}}
60*760c253cSXin Li
61*760c253cSXin Liimage2 {{
62*760c253cSXin Li  chromeos_image: /chromeos/src/build/imaages/daisy/latest/cros_image2.bin
63*760c253cSXin Li  remote: chromeos-daisy4.cros chromeos-daisy5.cros
64*760c253cSXin Li}}
65*760c253cSXin Li"""
66*760c253cSXin Li
67*760c253cSXin Li
68*760c253cSXin Liclass Schedv2Test(unittest.TestCase):
69*760c253cSXin Li    """Class for setting up and running the unit tests."""
70*760c253cSXin Li
71*760c253cSXin Li    def setUp(self):
72*760c253cSXin Li        self.exp = None
73*760c253cSXin Li
74*760c253cSXin Li    mock_logger = FakeLogger()
75*760c253cSXin Li    mock_cmd_exec = mock.Mock(spec=CommandExecuter)
76*760c253cSXin Li
77*760c253cSXin Li    @mock.patch(
78*760c253cSXin Li        "benchmark_run.BenchmarkRun", new=benchmark_run.MockBenchmarkRun
79*760c253cSXin Li    )
80*760c253cSXin Li    def _make_fake_experiment(self, expstr):
81*760c253cSXin Li        """Create fake experiment from string.
82*760c253cSXin Li
83*760c253cSXin Li        Note - we mock out BenchmarkRun in this step.
84*760c253cSXin Li        """
85*760c253cSXin Li        experiment_file = ExperimentFile(io.StringIO(expstr))
86*760c253cSXin Li        experiment = ExperimentFactory().GetExperiment(
87*760c253cSXin Li            experiment_file, working_directory="", log_dir=""
88*760c253cSXin Li        )
89*760c253cSXin Li        return experiment
90*760c253cSXin Li
91*760c253cSXin Li    def test_remote(self):
92*760c253cSXin Li        """Test that remotes in labels are aggregated into experiment.remote."""
93*760c253cSXin Li
94*760c253cSXin Li        self.exp = self._make_fake_experiment(EXPERIMENT_FILE_1)
95*760c253cSXin Li        self.exp.log_level = "verbose"
96*760c253cSXin Li        my_schedv2 = Schedv2(self.exp)
97*760c253cSXin Li        self.assertFalse(my_schedv2.is_complete())
98*760c253cSXin Li        self.assertIn("chromeos-daisy1.cros", self.exp.remote)
99*760c253cSXin Li        self.assertIn("chromeos-daisy2.cros", self.exp.remote)
100*760c253cSXin Li        self.assertIn("chromeos-daisy3.cros", self.exp.remote)
101*760c253cSXin Li        self.assertIn("chromeos-daisy4.cros", self.exp.remote)
102*760c253cSXin Li        self.assertIn("chromeos-daisy5.cros", self.exp.remote)
103*760c253cSXin Li
104*760c253cSXin Li    def test_unreachable_remote(self):
105*760c253cSXin Li        """Test unreachable remotes are removed from experiment and label."""
106*760c253cSXin Li
107*760c253cSXin Li        def MockIsReachable(cm):
108*760c253cSXin Li            return (
109*760c253cSXin Li                cm.name != "chromeos-daisy3.cros"
110*760c253cSXin Li                and cm.name != "chromeos-daisy5.cros"
111*760c253cSXin Li            )
112*760c253cSXin Li
113*760c253cSXin Li        with mock.patch(
114*760c253cSXin Li            "machine_manager.MockCrosMachine.IsReachable", new=MockIsReachable
115*760c253cSXin Li        ):
116*760c253cSXin Li            self.exp = self._make_fake_experiment(EXPERIMENT_FILE_1)
117*760c253cSXin Li            self.assertIn("chromeos-daisy1.cros", self.exp.remote)
118*760c253cSXin Li            self.assertIn("chromeos-daisy2.cros", self.exp.remote)
119*760c253cSXin Li            self.assertNotIn("chromeos-daisy3.cros", self.exp.remote)
120*760c253cSXin Li            self.assertIn("chromeos-daisy4.cros", self.exp.remote)
121*760c253cSXin Li            self.assertNotIn("chromeos-daisy5.cros", self.exp.remote)
122*760c253cSXin Li
123*760c253cSXin Li            for l in self.exp.labels:
124*760c253cSXin Li                if l.name == "image2":
125*760c253cSXin Li                    self.assertNotIn("chromeos-daisy5.cros", l.remote)
126*760c253cSXin Li                    self.assertIn("chromeos-daisy4.cros", l.remote)
127*760c253cSXin Li                elif l.name == "image1":
128*760c253cSXin Li                    self.assertNotIn("chromeos-daisy3.cros", l.remote)
129*760c253cSXin Li
130*760c253cSXin Li    @mock.patch("schedv2.BenchmarkRunCacheReader")
131*760c253cSXin Li    def test_BenchmarkRunCacheReader_1(self, reader):
132*760c253cSXin Li        """Test benchmarkrun set is split into 5 segments."""
133*760c253cSXin Li
134*760c253cSXin Li        self.exp = self._make_fake_experiment(
135*760c253cSXin Li            EXPERIMENT_FILE_WITH_FORMAT.format(kraken_iterations=9)
136*760c253cSXin Li        )
137*760c253cSXin Li        my_schedv2 = Schedv2(self.exp)
138*760c253cSXin Li        self.assertFalse(my_schedv2.is_complete())
139*760c253cSXin Li        # We have 9 * 2 == 18 brs, we use 5 threads, each reading 4, 4, 4,
140*760c253cSXin Li        # 4, 2 brs respectively.
141*760c253cSXin Li        # Assert that BenchmarkRunCacheReader() is called 5 times.
142*760c253cSXin Li        self.assertEqual(reader.call_count, 5)
143*760c253cSXin Li        # reader.call_args_list[n] - nth call.
144*760c253cSXin Li        # reader.call_args_list[n][0] - positioned args in nth call.
145*760c253cSXin Li        # reader.call_args_list[n][0][1] - the 2nd arg in nth call,
146*760c253cSXin Li        # that is 'br_list' in 'schedv2.BenchmarkRunCacheReader'.
147*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[0][0][1]), 4)
148*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[1][0][1]), 4)
149*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[2][0][1]), 4)
150*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[3][0][1]), 4)
151*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[4][0][1]), 2)
152*760c253cSXin Li
153*760c253cSXin Li    @mock.patch("schedv2.BenchmarkRunCacheReader")
154*760c253cSXin Li    def test_BenchmarkRunCacheReader_2(self, reader):
155*760c253cSXin Li        """Test benchmarkrun set is split into 4 segments."""
156*760c253cSXin Li
157*760c253cSXin Li        self.exp = self._make_fake_experiment(
158*760c253cSXin Li            EXPERIMENT_FILE_WITH_FORMAT.format(kraken_iterations=8)
159*760c253cSXin Li        )
160*760c253cSXin Li        my_schedv2 = Schedv2(self.exp)
161*760c253cSXin Li        self.assertFalse(my_schedv2.is_complete())
162*760c253cSXin Li        # We have 8 * 2 == 16 brs, we use 4 threads, each reading 4 brs.
163*760c253cSXin Li        self.assertEqual(reader.call_count, 4)
164*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[0][0][1]), 4)
165*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[1][0][1]), 4)
166*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[2][0][1]), 4)
167*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[3][0][1]), 4)
168*760c253cSXin Li
169*760c253cSXin Li    @mock.patch("schedv2.BenchmarkRunCacheReader")
170*760c253cSXin Li    def test_BenchmarkRunCacheReader_3(self, reader):
171*760c253cSXin Li        """Test benchmarkrun set is split into 2 segments."""
172*760c253cSXin Li
173*760c253cSXin Li        self.exp = self._make_fake_experiment(
174*760c253cSXin Li            EXPERIMENT_FILE_WITH_FORMAT.format(kraken_iterations=3)
175*760c253cSXin Li        )
176*760c253cSXin Li        my_schedv2 = Schedv2(self.exp)
177*760c253cSXin Li        self.assertFalse(my_schedv2.is_complete())
178*760c253cSXin Li        # We have 3 * 2 == 6 brs, we use 2 threads.
179*760c253cSXin Li        self.assertEqual(reader.call_count, 2)
180*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[0][0][1]), 3)
181*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[1][0][1]), 3)
182*760c253cSXin Li
183*760c253cSXin Li    @mock.patch("schedv2.BenchmarkRunCacheReader")
184*760c253cSXin Li    def test_BenchmarkRunCacheReader_4(self, reader):
185*760c253cSXin Li        """Test benchmarkrun set is not splitted."""
186*760c253cSXin Li
187*760c253cSXin Li        self.exp = self._make_fake_experiment(
188*760c253cSXin Li            EXPERIMENT_FILE_WITH_FORMAT.format(kraken_iterations=1)
189*760c253cSXin Li        )
190*760c253cSXin Li        my_schedv2 = Schedv2(self.exp)
191*760c253cSXin Li        self.assertFalse(my_schedv2.is_complete())
192*760c253cSXin Li        # We have 1 * 2 == 2 br, so only 1 instance.
193*760c253cSXin Li        self.assertEqual(reader.call_count, 1)
194*760c253cSXin Li        self.assertEqual(len(reader.call_args_list[0][0][1]), 2)
195*760c253cSXin Li
196*760c253cSXin Li    def test_cachehit(self):
197*760c253cSXin Li        """Test cache-hit and none-cache-hit brs are properly organized."""
198*760c253cSXin Li
199*760c253cSXin Li        def MockReadCache(br):
200*760c253cSXin Li            br.cache_hit = br.label.name == "image2"
201*760c253cSXin Li
202*760c253cSXin Li        with mock.patch(
203*760c253cSXin Li            "benchmark_run.MockBenchmarkRun.ReadCache", new=MockReadCache
204*760c253cSXin Li        ):
205*760c253cSXin Li            # We have 2 * 30 brs, half of which are put into _cached_br_list.
206*760c253cSXin Li            self.exp = self._make_fake_experiment(
207*760c253cSXin Li                EXPERIMENT_FILE_WITH_FORMAT.format(kraken_iterations=30)
208*760c253cSXin Li            )
209*760c253cSXin Li            my_schedv2 = Schedv2(self.exp)
210*760c253cSXin Li            self.assertEqual(len(my_schedv2.get_cached_run_list()), 30)
211*760c253cSXin Li            # The non-cache-hit brs are put into Schedv2._label_brl_map.
212*760c253cSXin Li            self.assertEqual(
213*760c253cSXin Li                functools.reduce(
214*760c253cSXin Li                    lambda a, x: a + len(x[1]),
215*760c253cSXin Li                    my_schedv2.get_label_map().items(),
216*760c253cSXin Li                    0,
217*760c253cSXin Li                ),
218*760c253cSXin Li                30,
219*760c253cSXin Li            )
220*760c253cSXin Li
221*760c253cSXin Li    def test_nocachehit(self):
222*760c253cSXin Li        """Test no cache-hit."""
223*760c253cSXin Li
224*760c253cSXin Li        def MockReadCache(br):
225*760c253cSXin Li            br.cache_hit = False
226*760c253cSXin Li
227*760c253cSXin Li        with mock.patch(
228*760c253cSXin Li            "benchmark_run.MockBenchmarkRun.ReadCache", new=MockReadCache
229*760c253cSXin Li        ):
230*760c253cSXin Li            # We have 2 * 30 brs, none of which are put into _cached_br_list.
231*760c253cSXin Li            self.exp = self._make_fake_experiment(
232*760c253cSXin Li                EXPERIMENT_FILE_WITH_FORMAT.format(kraken_iterations=30)
233*760c253cSXin Li            )
234*760c253cSXin Li            my_schedv2 = Schedv2(self.exp)
235*760c253cSXin Li            self.assertEqual(len(my_schedv2.get_cached_run_list()), 0)
236*760c253cSXin Li            # The non-cache-hit brs are put into Schedv2._label_brl_map.
237*760c253cSXin Li            self.assertEqual(
238*760c253cSXin Li                functools.reduce(
239*760c253cSXin Li                    lambda a, x: a + len(x[1]),
240*760c253cSXin Li                    my_schedv2.get_label_map().items(),
241*760c253cSXin Li                    0,
242*760c253cSXin Li                ),
243*760c253cSXin Li                60,
244*760c253cSXin Li            )
245*760c253cSXin Li
246*760c253cSXin Li
247*760c253cSXin Liif __name__ == "__main__":
248*760c253cSXin Li    test_flag.SetTestMode(True)
249*760c253cSXin Li    unittest.main()
250