1
+ # Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Test that Firecracker works correctly when instrumented with tracing and trace level logs are enabled.
5
+
6
+ This test verifies that the log-instrument crate can successfully instrument the actual Firecracker
7
+ binary and that the instrumented Firecracker works correctly with trace-level logging.
8
+
9
+ Test Coverage:
10
+ 1. Uses clippy-tracing tool to add instrumentation to Firecracker source files
11
+ 2. Builds Firecracker with tracing features enabled
12
+ 3. Verifies basic functionality (API calls, VM lifecycle) works with instrumentation
13
+ 4. Confirms trace-level logs are generated and contain meaningful information
14
+ 5. Checks performance impact is within acceptable bounds
15
+ """
16
+
17
+ import os
18
+ import re
19
+ import tempfile
20
+ from pathlib import Path
21
+
22
+ import pytest
23
+
24
+ from framework import utils
25
+ from framework .microvm import MicroVMFactory
26
+ from framework .properties import global_props
27
+ from host_tools .cargo_build import cargo , get_binary
28
+
29
+
30
+ def build_instrumented_firecracker ():
31
+ """Build Firecracker with tracing instrumentation enabled."""
32
+ # First, add instrumentation using clippy-tracing
33
+ clippy_tracing = get_binary ("clippy-tracing" )
34
+
35
+ # Add instrumentation to a subset of files to avoid performance issues
36
+ # We'll instrument just the API server and main entry points for meaningful traces
37
+ cargo_args = [
38
+ "--action" , "fix" ,
39
+ "--path" , "./src/firecracker/src/main.rs" ,
40
+ "--path" , "./src/firecracker/src/api_server" ,
41
+ "--path" , "./src/vmm/src/lib.rs" ,
42
+ "--path" , "./src/vmm/src/builder.rs"
43
+ ]
44
+
45
+ utils .check_output (f"{ clippy_tracing } { ' ' .join (cargo_args )} " )
46
+
47
+ # Build Firecracker with tracing feature enabled
48
+ cargo ("build" , "--features tracing --bin firecracker" )
49
+
50
+ return get_binary ("firecracker" )
51
+
52
+
53
+ def cleanup_instrumentation ():
54
+ """Remove instrumentation from source files."""
55
+ clippy_tracing = get_binary ("clippy-tracing" )
56
+
57
+ # Strip instrumentation from the files we modified
58
+ strip_args = [
59
+ "--action" , "strip" ,
60
+ "--path" , "./src/firecracker/src/main.rs" ,
61
+ "--path" , "./src/firecracker/src/api_server" ,
62
+ "--path" , "./src/vmm/src/lib.rs" ,
63
+ "--path" , "./src/vmm/src/builder.rs"
64
+ ]
65
+
66
+ utils .check_output (f"{ clippy_tracing } { ' ' .join (strip_args )} " )
67
+
68
+
69
+ @pytest .fixture (scope = "module" )
70
+ def instrumented_firecracker_binary ():
71
+ """Fixture that builds an instrumented Firecracker binary and cleans up after tests."""
72
+ try :
73
+ binary_path = build_instrumented_firecracker ()
74
+ yield binary_path
75
+ finally :
76
+ cleanup_instrumentation ()
77
+
78
+
79
+ def test_firecracker_tracing_basic_functionality (instrumented_firecracker_binary ):
80
+ """Test that instrumented Firecracker can start and handle basic API calls with trace logging."""
81
+ # Create a temporary directory for this test
82
+ with tempfile .TemporaryDirectory () as temp_dir :
83
+ temp_path = Path (temp_dir )
84
+
85
+ # Create a MicroVM factory with the instrumented binary
86
+ factory = MicroVMFactory (instrumented_firecracker_binary )
87
+
88
+ # Build a microVM
89
+ vm = factory .build ()
90
+
91
+ # Configure basic VM settings
92
+ vm .basic_config (vcpu_count = 1 , mem_size_mib = 128 )
93
+
94
+ # Spawn the VM with trace level logging
95
+ vm .spawn (log_level = "Trace" , log_show_level = True , log_show_origin = True )
96
+
97
+ try :
98
+ # Wait for the API socket to be available
99
+ vm ._wait_for_api_socket ()
100
+
101
+ # Make some basic API calls to generate trace logs
102
+ # Get instance info
103
+ response = vm .api .describe_instance .get ()
104
+ assert response .status_code == 200
105
+
106
+ # Get machine config
107
+ response = vm .api .machine_config .get ()
108
+ assert response .status_code == 200
109
+
110
+ # Set logger to trace level to ensure we capture instrumentation logs
111
+ logger_config = {
112
+ "level" : "Trace"
113
+ }
114
+ response = vm .api .logger .put (** logger_config )
115
+ assert response .status_code == 204
116
+
117
+ # Make another API call after setting trace level
118
+ response = vm .api .describe_instance .get ()
119
+ assert response .status_code == 200
120
+
121
+ # Verify that the VM is working correctly
122
+ assert vm .state == "Not started"
123
+
124
+ finally :
125
+ vm .kill ()
126
+
127
+ # Check the logs for instrumentation traces
128
+ log_data = vm .log_data
129
+
130
+ # Verify that trace level logs are present
131
+ assert "TRACE" in log_data , "Expected TRACE level logs in output"
132
+
133
+ # Look for log-instrument traces (function entry/exit)
134
+ # These should have the format: ThreadId(X)>>function_name or ThreadId(X)<<function_name
135
+ trace_pattern = r"ThreadId\(\d+\)(?:::[^>]*)?(?:>>|<<)\w+"
136
+ trace_matches = re .findall (trace_pattern , log_data )
137
+
138
+ assert len (trace_matches ) > 0 , f"Expected to find log-instrument traces in logs, but found none. Log data: { log_data [:1000 ]} ..."
139
+
140
+ # Verify we see function entry and exit traces
141
+ entry_traces = [match for match in trace_matches if ">>" in match ]
142
+ exit_traces = [match for match in trace_matches if "<<" in match ]
143
+
144
+ assert len (entry_traces ) > 0 , "Expected to find function entry traces (>>)"
145
+ assert len (exit_traces ) > 0 , "Expected to find function exit traces (<<)"
146
+
147
+ # Verify that meaningful functions are being traced
148
+ # Look for traces from main, API handling, or VM management functions
149
+ meaningful_functions = ["main" , "api" , "vmm" , "request" , "response" ]
150
+ found_meaningful = False
151
+
152
+ for trace in trace_matches :
153
+ for func in meaningful_functions :
154
+ if func .lower () in trace .lower ():
155
+ found_meaningful = True
156
+ break
157
+ if found_meaningful :
158
+ break
159
+
160
+ assert found_meaningful , f"Expected to find traces from meaningful functions, but traces were: { trace_matches [:10 ]} "
161
+
162
+
163
+
164
+ def test_firecracker_tracing_performance_impact ():
165
+ """Test that instrumented Firecracker still performs reasonably (basic smoke test)."""
166
+ import time
167
+
168
+ # This is a basic performance smoke test to ensure tracing doesn't break functionality
169
+ # We're not doing detailed performance analysis, just ensuring it doesn't hang or crash
170
+
171
+ # Build instrumented binary
172
+ instrumented_binary = build_instrumented_firecracker ()
173
+
174
+ try :
175
+ factory = MicroVMFactory (instrumented_binary )
176
+ vm = factory .build ()
177
+
178
+ # Time the basic configuration and startup
179
+ start_time = time .time ()
180
+
181
+ vm .basic_config (vcpu_count = 1 , mem_size_mib = 128 , add_root_device = False )
182
+ vm .spawn (log_level = "Trace" )
183
+
184
+ # Make several API calls
185
+ for _ in range (5 ):
186
+ response = vm .api .describe_instance .get ()
187
+ assert response .status_code == 200
188
+
189
+ elapsed = time .time () - start_time
190
+
191
+ # Should complete within reasonable time (30 seconds is very generous)
192
+ # This is just to catch major performance regressions or hangs
193
+ assert elapsed < 30 , f"Instrumented Firecracker took too long to start and handle API calls: { elapsed } s"
194
+
195
+ vm .kill ()
196
+
197
+ finally :
198
+ cleanup_instrumentation ()
199
+
200
+
201
+ def test_trace_log_filtering ():
202
+ """Test that trace log filtering works correctly with instrumented Firecracker."""
203
+ instrumented_binary = build_instrumented_firecracker ()
204
+
205
+ try :
206
+ factory = MicroVMFactory (instrumented_binary )
207
+ vm = factory .build ()
208
+
209
+ vm .basic_config (vcpu_count = 1 , mem_size_mib = 128 , add_root_device = False )
210
+ vm .spawn (log_level = "Info" ) # Start with Info level
211
+
212
+ try :
213
+ # Initially should not have trace logs
214
+ initial_log_data = vm .log_data
215
+
216
+ # Set logger to trace level
217
+ logger_config = {"level" : "Trace" }
218
+ response = vm .api .logger .put (** logger_config )
219
+ assert response .status_code == 204
220
+
221
+ # Make API calls to generate traces
222
+ for _ in range (3 ):
223
+ response = vm .api .describe_instance .get ()
224
+ assert response .status_code == 200
225
+
226
+ # Now should have trace logs
227
+ final_log_data = vm .log_data
228
+
229
+ # Verify no TRACE logs were present initially
230
+ assert "TRACE" not in initial_log_data , "Expected no TRACE logs before setting log level to Trace"
231
+
232
+ # The new log data should contain trace information
233
+ new_log_data = final_log_data [len (initial_log_data ):]
234
+ assert "TRACE" in new_log_data , "Expected TRACE logs after setting log level to Trace"
235
+
236
+ finally :
237
+ vm .kill ()
238
+
239
+ finally :
240
+ cleanup_instrumentation ()
0 commit comments