xref: /llvm-project/lldb/test/API/macosx/queues/TestQueues.py (revision 1eeeab82c6eb185f5139e633a59c2dbcb15616e4)
1"""Test queues inspection SB APIs."""
2
3import os
4import lldb
5from lldbsuite.test.decorators import *
6from lldbsuite.test.lldbtest import *
7from lldbsuite.test import lldbutil
8
9
10class TestQueues(TestBase):
11    @skipUnlessDarwin
12    @add_test_categories(["pyapi"])
13    def test_with_python_api_queues(self):
14        """Test queues inspection SB APIs."""
15        self.build()
16        self.queues()
17
18    @skipUnlessDarwin
19    @add_test_categories(["pyapi"])
20    def test_queue_specific_breakpoints(self):
21        self.build()
22        self.queue_specific_breakpoints()
23
24    @skipUnlessDarwin
25    @add_test_categories(["pyapi"])
26    def test_with_python_api_queues_with_backtrace(self):
27        """Test queues inspection SB APIs."""
28        self.build()
29        self.queues_with_libBacktraceRecording()
30
31    def setUp(self):
32        # Call super's setUp().
33        TestBase.setUp(self)
34        # Find the line numbers that we will step to in main:
35        self.main_source = "main.c"
36
37    def check_queue_for_valid_queue_id(self, queue):
38        self.assertNotEqual(
39            queue.GetQueueID(),
40            0,
41            "Check queue %s for valid QueueID (got 0x%x)"
42            % (queue.GetName(), queue.GetQueueID()),
43        )
44
45    def check_running_and_pending_items_on_queue(
46        self, queue, expected_running, expected_pending
47    ):
48        self.assertEqual(
49            queue.GetNumPendingItems(),
50            expected_pending,
51            "queue %s should have %d pending items, instead has %d pending items"
52            % (queue.GetName(), expected_pending, (queue.GetNumPendingItems())),
53        )
54        self.assertEqual(
55            queue.GetNumRunningItems(),
56            expected_running,
57            "queue %s should have %d running items, instead has %d running items"
58            % (queue.GetName(), expected_running, (queue.GetNumRunningItems())),
59        )
60
61    def describe_threads(self):
62        desc = []
63        for x in self.inferior_process:
64            id = x.GetIndexID()
65            reason_str = lldbutil.stop_reason_to_str(x.GetStopReason())
66
67            location = "\t".join(
68                [
69                    lldbutil.get_description(x.GetFrameAtIndex(i))
70                    for i in range(x.GetNumFrames())
71                ]
72            )
73            desc.append(
74                "thread %d: %s (queue id: %s) at\n\t%s"
75                % (id, reason_str, x.GetQueueID(), location)
76            )
77        print("\n".join(desc))
78
79    def check_number_of_threads_owned_by_queue(self, queue, number_threads):
80        if queue.GetNumThreads() != number_threads:
81            self.describe_threads()
82
83        self.assertEqual(
84            queue.GetNumThreads(),
85            number_threads,
86            "queue %s should have %d thread executing, but has %d"
87            % (queue.GetName(), number_threads, queue.GetNumThreads()),
88        )
89
90    def check_queue_kind(self, queue, kind):
91        expected_kind_string = "Unknown"
92        if kind == lldb.eQueueKindSerial:
93            expected_kind_string = "Serial queue"
94        if kind == lldb.eQueueKindConcurrent:
95            expected_kind_string = "Concurrent queue"
96        actual_kind_string = "Unknown"
97        if queue.GetKind() == lldb.eQueueKindSerial:
98            actual_kind_string = "Serial queue"
99        if queue.GetKind() == lldb.eQueueKindConcurrent:
100            actual_kind_string = "Concurrent queue"
101        self.assertEqual(
102            queue.GetKind(),
103            kind,
104            "queue %s is expected to be a %s but it is actually a %s"
105            % (queue.GetName(), expected_kind_string, actual_kind_string),
106        )
107
108    def check_queues_threads_match_queue(self, queue):
109        for idx in range(0, queue.GetNumThreads()):
110            t = queue.GetThreadAtIndex(idx)
111            self.assertTrue(
112                t.IsValid(),
113                "Queue %s's thread #%d must be valid" % (queue.GetName(), idx),
114            )
115            self.assertEqual(
116                t.GetQueueID(),
117                queue.GetQueueID(),
118                "Queue %s has a QueueID of %d but its thread #%d has a QueueID of %d"
119                % (queue.GetName(), queue.GetQueueID(), idx, t.GetQueueID()),
120            )
121            self.assertEqual(
122                t.GetQueueName(),
123                queue.GetName(),
124                "Queue %s has a QueueName of %s but its thread #%d has a QueueName of %s"
125                % (queue.GetName(), queue.GetName(), idx, t.GetQueueName()),
126            )
127            self.assertEqual(
128                t.GetQueue().GetQueueID(),
129                queue.GetQueueID(),
130                "Thread #%d's Queue's QueueID of %d is not the same as the QueueID of its owning queue %d"
131                % (idx, t.GetQueue().GetQueueID(), queue.GetQueueID()),
132            )
133
134    def check_queue_breakpoints(self, queue1, queue2, queue_breakpoint):
135        queue1_thread = queue1.GetThreadAtIndex(0)
136        queue2_thread = queue2.GetThreadAtIndex(0)
137
138        self.assertEqual(
139            queue_breakpoint.GetQueueName(),
140            queue1.GetName(),
141            "The breakpoint was set for queue %s, but the breakpoint's queue name is %s"
142            % (queue_breakpoint.GetQueueName(), queue1.GetName()),
143        )
144        self.assertEqual(
145            queue_breakpoint.GetHitCount(),
146            1,
147            "The breakpoint for queue %s has not been hit"
148            % (queue_breakpoint.GetQueueName()),
149        )
150        self.assertStopReason(
151            queue1_thread.GetStopReason(),
152            lldb.eStopReasonBreakpoint,
153            "Queue %s is not stopped at breakpoint %d"
154            % (queue1.GetName(), queue_breakpoint.GetID()),
155        )
156        self.assertNotEqual(
157            queue2_thread.GetStopReason(),
158            lldb.eStopReasonBreakpoint,
159            "Queue %s is stopped at breakpoint %d, but this breakpoint should only be hit for queue %s"
160            % (
161                queue2.GetName(),
162                queue_breakpoint.GetID(),
163                queue_breakpoint.GetQueueName(),
164            ),
165        )
166
167    def queues(self):
168        """Test queues inspection SB APIs without libBacktraceRecording."""
169        exe = self.getBuildArtifact("a.out")
170
171        target = self.dbg.CreateTarget(exe)
172        self.assertTrue(target, VALID_TARGET)
173        self.main_source_spec = lldb.SBFileSpec(self.main_source)
174        break1 = target.BreakpointCreateByName("stopper", "a.out")
175        self.assertTrue(break1, VALID_BREAKPOINT)
176        process = target.LaunchSimple([], None, self.get_process_working_directory())
177        self.assertTrue(process, PROCESS_IS_VALID)
178        threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1)
179        if len(threads) != 1:
180            self.fail("Failed to stop at breakpoint 1.")
181
182        self.inferior_process = process
183
184        queue_submittor_1 = lldb.SBQueue()
185        queue_performer_1 = lldb.SBQueue()
186        queue_performer_2 = lldb.SBQueue()
187        queue_performer_3 = lldb.SBQueue()
188        for idx in range(0, process.GetNumQueues()):
189            q = process.GetQueueAtIndex(idx)
190            if q.GetName() == "com.apple.work_submittor_1":
191                queue_submittor_1 = q
192            if q.GetName() == "com.apple.work_performer_1":
193                queue_performer_1 = q
194            if q.GetName() == "com.apple.work_performer_2":
195                queue_performer_2 = q
196            if q.GetName() == "com.apple.work_performer_3":
197                queue_performer_3 = q
198
199        self.assertTrue(
200            queue_submittor_1.IsValid()
201            and queue_performer_1.IsValid()
202            and queue_performer_2.IsValid()
203            and queue_performer_3.IsValid(),
204            "Got all four expected queues: %s %s %s %s"
205            % (
206                queue_submittor_1.IsValid(),
207                queue_performer_1.IsValid(),
208                queue_performer_2.IsValid(),
209                queue_performer_3.IsValid(),
210            ),
211        )
212
213        self.check_queue_for_valid_queue_id(queue_submittor_1)
214        self.check_queue_for_valid_queue_id(queue_performer_1)
215        self.check_queue_for_valid_queue_id(queue_performer_2)
216        self.check_queue_for_valid_queue_id(queue_performer_3)
217
218        self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1)
219        self.check_number_of_threads_owned_by_queue(queue_performer_1, 1)
220        self.check_number_of_threads_owned_by_queue(queue_performer_2, 1)
221        self.check_number_of_threads_owned_by_queue(queue_performer_3, 4)
222
223        self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial)
224        self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial)
225        self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial)
226        self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent)
227
228        self.check_queues_threads_match_queue(queue_submittor_1)
229        self.check_queues_threads_match_queue(queue_performer_1)
230        self.check_queues_threads_match_queue(queue_performer_2)
231        self.check_queues_threads_match_queue(queue_performer_3)
232
233        # We have threads running with all the different dispatch QoS service
234        # levels - find those threads and check that we can get the correct
235        # QoS name for each of them.
236
237        user_initiated_thread = lldb.SBThread()
238        user_interactive_thread = lldb.SBThread()
239        utility_thread = lldb.SBThread()
240        background_thread = lldb.SBThread()
241        for th in process.threads:
242            if th.GetName() == "user initiated QoS":
243                user_initiated_thread = th
244            if th.GetName() == "user interactive QoS":
245                user_interactive_thread = th
246            if th.GetName() == "utility QoS":
247                utility_thread = th
248            if th.GetName() == "background QoS":
249                background_thread = th
250
251        self.assertTrue(
252            user_initiated_thread.IsValid(), "Found user initiated QoS thread"
253        )
254        self.assertTrue(
255            user_interactive_thread.IsValid(), "Found user interactive QoS thread"
256        )
257        self.assertTrue(utility_thread.IsValid(), "Found utility QoS thread")
258        self.assertTrue(background_thread.IsValid(), "Found background QoS thread")
259
260        stream = lldb.SBStream()
261        self.assertTrue(
262            user_initiated_thread.GetInfoItemByPathAsString(
263                "requested_qos.printable_name", stream
264            ),
265            "Get QoS printable string for user initiated QoS thread",
266        )
267        self.assertEqual(
268            stream.GetData(),
269            "User Initiated",
270            "user initiated QoS thread name is valid",
271        )
272        stream.Clear()
273        self.assertTrue(
274            user_interactive_thread.GetInfoItemByPathAsString(
275                "requested_qos.printable_name", stream
276            ),
277            "Get QoS printable string for user interactive QoS thread",
278        )
279        self.assertEqual(
280            stream.GetData(),
281            "User Interactive",
282            "user interactive QoS thread name is valid",
283        )
284        stream.Clear()
285        self.assertTrue(
286            utility_thread.GetInfoItemByPathAsString(
287                "requested_qos.printable_name", stream
288            ),
289            "Get QoS printable string for utility QoS thread",
290        )
291        self.assertEqual(
292            stream.GetData(), "Utility", "utility QoS thread name is valid"
293        )
294        stream.Clear()
295        self.assertTrue(
296            background_thread.GetInfoItemByPathAsString(
297                "requested_qos.printable_name", stream
298            ),
299            "Get QoS printable string for background QoS thread",
300        )
301        self.assertEqual(
302            stream.GetData(), "Background", "background QoS thread name is valid"
303        )
304
305    @skipIfDarwin  # rdar://50379398
306    def queues_with_libBacktraceRecording(self):
307        """Test queues inspection SB APIs with libBacktraceRecording present."""
308        exe = self.getBuildArtifact("a.out")
309
310        if not os.path.isfile(
311            "/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib"
312        ):
313            self.skipTest(
314                "Skipped because libBacktraceRecording.dylib was present on the system."
315            )
316
317        if not os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"):
318            self.skipTest(
319                "Skipped because introspection libdispatch dylib is not present."
320            )
321
322        target = self.dbg.CreateTarget(exe)
323        self.assertTrue(target, VALID_TARGET)
324
325        self.main_source_spec = lldb.SBFileSpec(self.main_source)
326
327        break1 = target.BreakpointCreateByName("stopper", "a.out")
328        self.assertTrue(break1, VALID_BREAKPOINT)
329
330        # Now launch the process, and do not stop at entry point.
331        libbtr_path = "/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib"
332        if self.getArchitecture() in [
333            "arm",
334            "arm64",
335            "arm64e",
336            "arm64_32",
337            "armv7",
338            "armv7k",
339        ]:
340            libbtr_path = "/Developer/usr/lib/libBacktraceRecording.dylib"
341
342        process = target.LaunchSimple(
343            [],
344            [
345                "DYLD_INSERT_LIBRARIES=%s" % (libbtr_path),
346                "DYLD_LIBRARY_PATH=/usr/lib/system/introspection",
347            ],
348            self.get_process_working_directory(),
349        )
350
351        self.assertTrue(process, PROCESS_IS_VALID)
352
353        # The stop reason of the thread should be breakpoint.
354        threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1)
355        if len(threads) != 1:
356            self.fail("Failed to stop at breakpoint 1.")
357
358        self.inferior_process = process
359
360        libbtr_module_filespec = lldb.SBFileSpec("libBacktraceRecording.dylib")
361        libbtr_module = target.FindModule(libbtr_module_filespec)
362        if not libbtr_module.IsValid():
363            self.skipTest(
364                "Skipped because libBacktraceRecording.dylib was not loaded into the process."
365            )
366
367        self.assertGreaterEqual(
368            process.GetNumQueues(), 4, "Found the correct number of queues."
369        )
370
371        queue_submittor_1 = lldb.SBQueue()
372        queue_performer_1 = lldb.SBQueue()
373        queue_performer_2 = lldb.SBQueue()
374        queue_performer_3 = lldb.SBQueue()
375        for idx in range(0, process.GetNumQueues()):
376            q = process.GetQueueAtIndex(idx)
377            if "LLDB_COMMAND_TRACE" in os.environ:
378                print("Queue  with id %s has name %s" % (q.GetQueueID(), q.GetName()))
379            if q.GetName() == "com.apple.work_submittor_1":
380                queue_submittor_1 = q
381            if q.GetName() == "com.apple.work_performer_1":
382                queue_performer_1 = q
383            if q.GetName() == "com.apple.work_performer_2":
384                queue_performer_2 = q
385            if q.GetName() == "com.apple.work_performer_3":
386                queue_performer_3 = q
387            if q.GetName() == "com.apple.main-thread":
388                if q.GetNumThreads() == 0:
389                    print("Cannot get thread <=> queue associations")
390                    return
391
392        self.assertTrue(
393            queue_submittor_1.IsValid()
394            and queue_performer_1.IsValid()
395            and queue_performer_2.IsValid()
396            and queue_performer_3.IsValid(),
397            "Got all four expected queues: %s %s %s %s"
398            % (
399                queue_submittor_1.IsValid(),
400                queue_performer_1.IsValid(),
401                queue_performer_2.IsValid(),
402                queue_performer_3.IsValid(),
403            ),
404        )
405
406        self.check_queue_for_valid_queue_id(queue_submittor_1)
407        self.check_queue_for_valid_queue_id(queue_performer_1)
408        self.check_queue_for_valid_queue_id(queue_performer_2)
409        self.check_queue_for_valid_queue_id(queue_performer_3)
410
411        self.check_running_and_pending_items_on_queue(queue_submittor_1, 1, 0)
412        self.check_running_and_pending_items_on_queue(queue_performer_1, 1, 3)
413        self.check_running_and_pending_items_on_queue(queue_performer_2, 1, 9999)
414        self.check_running_and_pending_items_on_queue(queue_performer_3, 4, 0)
415
416        self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1)
417        self.check_number_of_threads_owned_by_queue(queue_performer_1, 1)
418        self.check_number_of_threads_owned_by_queue(queue_performer_2, 1)
419        self.check_number_of_threads_owned_by_queue(queue_performer_3, 4)
420
421        self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial)
422        self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial)
423        self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial)
424        self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent)
425
426        self.check_queues_threads_match_queue(queue_submittor_1)
427        self.check_queues_threads_match_queue(queue_performer_1)
428        self.check_queues_threads_match_queue(queue_performer_2)
429        self.check_queues_threads_match_queue(queue_performer_3)
430
431        self.assertTrue(
432            queue_performer_2.GetPendingItemAtIndex(0).IsValid(),
433            "queue 2's pending item #0 is valid",
434        )
435        self.assertEqual(
436            queue_performer_2.GetPendingItemAtIndex(0)
437            .GetAddress()
438            .GetSymbol()
439            .GetName(),
440            "doing_the_work_2",
441            "queue 2's pending item #0 should be doing_the_work_2",
442        )
443        self.assertEqual(
444            queue_performer_2.GetNumPendingItems(),
445            9999,
446            "verify that queue 2 still has 9999 pending items",
447        )
448        self.assertTrue(
449            queue_performer_2.GetPendingItemAtIndex(9998).IsValid(),
450            "queue 2's pending item #9998 is valid",
451        )
452        self.assertEqual(
453            queue_performer_2.GetPendingItemAtIndex(9998)
454            .GetAddress()
455            .GetSymbol()
456            .GetName(),
457            "doing_the_work_2",
458            "queue 2's pending item #0 should be doing_the_work_2",
459        )
460        self.assertFalse(
461            queue_performer_2.GetPendingItemAtIndex(9999).IsValid(),
462            "queue 2's pending item #9999 is invalid",
463        )
464
465    def queue_specific_breakpoints(self):
466        # Run the executable until the stopper function and get the breakpoint
467        # that's created from that. Then set the queue name of the breakpoint
468        # to be the name of the main thread
469        (
470            target,
471            process,
472            main_thread,
473            queue_breakpoint,
474        ) = lldbutil.run_to_name_breakpoint(self, "stopper", only_one_thread=False)
475        queue_breakpoint.SetQueueName(main_thread.GetQueue().GetName())
476
477        # Create a submittor queue
478        queue_submittor_1 = lldb.SBQueue()
479        for idx in range(0, process.GetNumQueues()):
480            q = process.GetQueueAtIndex(idx)
481            if q.GetName() == "com.apple.work_submittor_1":
482                queue_submittor_1 = q
483
484        self.assertTrue(
485            queue_submittor_1.IsValid(),
486            "Unable to get expected queue com.apple.work_submittor_1, instead got queue %s"
487            % (queue_submittor_1.GetName()),
488        )
489
490        self.check_queue_breakpoints(
491            main_thread.GetQueue(), queue_submittor_1, queue_breakpoint
492        )
493