| 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com> |
| 4 | # |
| 5 | # SPDX-License-Identifier: GPL-2.0-only |
| 6 | |
| 7 | from cgi import test |
| 8 | import pathlib |
| 9 | import sys |
| 10 | import os |
| 11 | from typing import Any, Callable, Type |
| 12 | |
| 13 | """ |
| 14 | Test the addition of various user space contexts. |
| 15 | |
| 16 | This test successively sets up a session with a certain context enabled, traces |
| 17 | a test application, and then reads the resulting trace to determine if: |
| 18 | - the context field is present in the trace |
| 19 | - the context field has the expected value. |
| 20 | |
| 21 | The vpid, vuid, vgid and java application contexts are validated by this test. |
| 22 | """ |
| 23 | |
| 24 | # Import in-tree test utils |
| 25 | test_utils_import_path = pathlib.Path(__file__).absolute().parents[3] / "utils" |
| 26 | sys.path.append(str(test_utils_import_path)) |
| 27 | |
| 28 | import lttngtest |
| 29 | import bt2 |
| 30 | |
| 31 | |
| 32 | def context_trace_field_name(context_type): |
| 33 | # type: (Type[lttngtest.ContextType]) -> str |
| 34 | if isinstance(context_type, lttngtest.VpidContextType): |
| 35 | return "vpid" |
| 36 | elif isinstance(context_type, lttngtest.VuidContextType): |
| 37 | return "vuid" |
| 38 | elif isinstance(context_type, lttngtest.VgidContextType): |
| 39 | return "vgid" |
| 40 | elif isinstance(context_type, lttngtest.JavaApplicationContextType): |
| 41 | # Depends on the trace format and will need to be adapted for CTF 2. |
| 42 | return "_app_{retriever}_{name}".format( |
| 43 | retriever=context_type.retriever_name, name=context_type.field_name |
| 44 | ) |
| 45 | else: |
| 46 | raise NotImplementedError |
| 47 | |
| 48 | |
| 49 | def trace_stream_class_has_context_field_in_event_context( |
| 50 | trace_location, context_field_name |
| 51 | ): |
| 52 | # type: (pathlib.Path, str) -> bool |
| 53 | iterator = bt2.TraceCollectionMessageIterator(str(trace_location)) |
| 54 | |
| 55 | # A bt2 message sequence is guaranteed to begin with a StreamBeginningMessage. |
| 56 | # Since we only have one channel (one stream class) and one trace, it is |
| 57 | # safe to use it to determine if the stream class contains the expected |
| 58 | # context field. |
| 59 | stream_begin_msg = next(iterator) |
| 60 | |
| 61 | trace_class = stream_begin_msg.stream.trace.cls |
| 62 | # Ensure the trace class has only one stream class. |
| 63 | assert len(trace_class) |
| 64 | |
| 65 | stream_class_id = next(iter(trace_class)) |
| 66 | stream_class = trace_class[stream_class_id] |
| 67 | event_common_context_field_class = stream_class.event_common_context_field_class |
| 68 | |
| 69 | return context_field_name in event_common_context_field_class |
| 70 | |
| 71 | |
| 72 | def trace_events_have_context_value(trace_location, context_field_name, value): |
| 73 | # type: (pathlib.Path, str, Any) -> bool |
| 74 | for msg in bt2.TraceCollectionMessageIterator(str(trace_location)): |
| 75 | if type(msg) is not bt2._EventMessageConst: |
| 76 | continue |
| 77 | |
| 78 | if msg.event.common_context_field[context_field_name] != value: |
| 79 | print(msg.event.common_context_field[context_field_name]) |
| 80 | return False |
| 81 | return True |
| 82 | |
| 83 | |
| 84 | def test_static_context(tap, test_env, context_type, context_value_retriever): |
| 85 | # type: (lttngtest.TapGenerator, lttngtest._Environment, lttngtest.ContextType, Callable[[lttngtest.WaitTraceTestApplication], Any]) -> None |
| 86 | tap.diagnostic( |
| 87 | "Test presence and expected value of context `{context_name}`".format( |
| 88 | context_name=type(context_type).__name__ |
| 89 | ) |
| 90 | ) |
| 91 | |
| 92 | session_output_location = lttngtest.LocalSessionOutputLocation( |
| 93 | test_env.create_temporary_directory("trace") |
| 94 | ) |
| 95 | |
| 96 | client = lttngtest.LTTngClient(test_env, log=tap.diagnostic) |
| 97 | |
| 98 | with tap.case("Create a session") as test_case: |
| 99 | session = client.create_session(output=session_output_location) |
| 100 | tap.diagnostic("Created session `{session_name}`".format(session_name=session.name)) |
| 101 | |
| 102 | with tap.case( |
| 103 | "Add a channel to session `{session_name}`".format(session_name=session.name) |
| 104 | ) as test_case: |
| 105 | channel = session.add_channel(lttngtest.TracingDomain.User) |
| 106 | tap.diagnostic("Created channel `{channel_name}`".format(channel_name=channel.name)) |
| 107 | |
| 108 | with tap.case( |
| 109 | "Add {context_type} context to channel `{channel_name}`".format( |
| 110 | context_type=type(context_type).__name__, channel_name=channel.name |
| 111 | ) |
| 112 | ) as test_case: |
| 113 | channel.add_context(context_type) |
| 114 | |
| 115 | test_app = test_env.launch_wait_trace_test_application(50) |
| 116 | |
| 117 | # Only track the test application |
| 118 | session.user_vpid_process_attribute_tracker.track(test_app.vpid) |
| 119 | expected_context_value = context_value_retriever(test_app) |
| 120 | |
| 121 | # Enable all user space events, the default for a user tracepoint event rule. |
| 122 | channel.add_recording_rule(lttngtest.UserTracepointEventRule()) |
| 123 | |
| 124 | session.start() |
| 125 | test_app.trace() |
| 126 | test_app.wait_for_exit() |
| 127 | session.stop() |
| 128 | session.destroy() |
| 129 | |
| 130 | tap.test( |
| 131 | trace_stream_class_has_context_field_in_event_context( |
| 132 | session_output_location.path, context_trace_field_name(context_type) |
| 133 | ), |
| 134 | "Stream class contains field `{context_field_name}`".format( |
| 135 | context_field_name=context_trace_field_name(context_type) |
| 136 | ), |
| 137 | ) |
| 138 | |
| 139 | tap.test( |
| 140 | trace_events_have_context_value( |
| 141 | session_output_location.path, |
| 142 | context_trace_field_name(context_type), |
| 143 | expected_context_value, |
| 144 | ), |
| 145 | "Trace's events contain the expected `{context_field_name}` value `{expected_context_value}`".format( |
| 146 | context_field_name=context_trace_field_name(context_type), |
| 147 | expected_context_value=expected_context_value, |
| 148 | ), |
| 149 | ) |
| 150 | |
| 151 | |
| 152 | tap = lttngtest.TapGenerator(20) |
| 153 | tap.diagnostic("Test user space context tracing") |
| 154 | |
| 155 | with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env: |
| 156 | test_static_context( |
| 157 | tap, test_env, lttngtest.VpidContextType(), lambda test_app: test_app.vpid |
| 158 | ) |
| 159 | test_static_context( |
| 160 | tap, test_env, lttngtest.VuidContextType(), lambda test_app: os.getuid() |
| 161 | ) |
| 162 | test_static_context( |
| 163 | tap, test_env, lttngtest.VgidContextType(), lambda test_app: os.getgid() |
| 164 | ) |
| 165 | test_static_context( |
| 166 | tap, |
| 167 | test_env, |
| 168 | lttngtest.JavaApplicationContextType("mayo", "ketchup"), |
| 169 | lambda test_app: {}, |
| 170 | ) |
| 171 | |
| 172 | sys.exit(0 if tap.is_successful else 1) |