1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * kvm_binary_stats_test |
4 | * |
5 | * Copyright (C) 2021, Google LLC. |
6 | * |
7 | * Test the fd-based interface for KVM statistics. |
8 | */ |
9 | |
10 | #define _GNU_SOURCE /* for program_invocation_short_name */ |
11 | #include <fcntl.h> |
12 | #include <stdio.h> |
13 | #include <stdlib.h> |
14 | #include <string.h> |
15 | #include <errno.h> |
16 | |
17 | #include "test_util.h" |
18 | |
19 | #include "kvm_util.h" |
20 | #include "asm/kvm.h" |
21 | #include "linux/kvm.h" |
22 | #include "kselftest.h" |
23 | |
24 | static void stats_test(int stats_fd) |
25 | { |
26 | ssize_t ret; |
27 | int i; |
28 | size_t size_desc; |
29 | size_t size_data = 0; |
30 | struct kvm_stats_header ; |
31 | char *id; |
32 | struct kvm_stats_desc *stats_desc; |
33 | u64 *stats_data; |
34 | struct kvm_stats_desc *pdesc; |
35 | u32 type, unit, base; |
36 | |
37 | /* Read kvm stats header */ |
38 | read_stats_header(stats_fd, &header); |
39 | |
40 | size_desc = get_stats_descriptor_size(&header); |
41 | |
42 | /* Read kvm stats id string */ |
43 | id = malloc(header.name_size); |
44 | TEST_ASSERT(id, "Allocate memory for id string" ); |
45 | |
46 | ret = pread(stats_fd, id, header.name_size, sizeof(header)); |
47 | TEST_ASSERT(ret == header.name_size, |
48 | "Expected header size '%u', read '%lu' bytes" , |
49 | header.name_size, ret); |
50 | |
51 | /* Check id string, that should start with "kvm" */ |
52 | TEST_ASSERT(!strncmp(id, "kvm" , 3) && strlen(id) < header.name_size, |
53 | "Invalid KVM stats type, id: %s" , id); |
54 | |
55 | /* Sanity check for other fields in header */ |
56 | if (header.num_desc == 0) { |
57 | ksft_print_msg("No KVM stats defined!\n" ); |
58 | return; |
59 | } |
60 | /* |
61 | * The descriptor and data offsets must be valid, they must not overlap |
62 | * the header, and the descriptor and data blocks must not overlap each |
63 | * other. Note, the data block is rechecked after its size is known. |
64 | */ |
65 | TEST_ASSERT(header.desc_offset && header.desc_offset >= sizeof(header) && |
66 | header.data_offset && header.data_offset >= sizeof(header), |
67 | "Invalid offset fields in header" ); |
68 | |
69 | TEST_ASSERT(header.desc_offset > header.data_offset || |
70 | (header.desc_offset + size_desc * header.num_desc <= header.data_offset), |
71 | "Descriptor block is overlapped with data block" ); |
72 | |
73 | /* Read kvm stats descriptors */ |
74 | stats_desc = read_stats_descriptors(stats_fd, &header); |
75 | |
76 | /* Sanity check for fields in descriptors */ |
77 | for (i = 0; i < header.num_desc; ++i) { |
78 | pdesc = get_stats_descriptor(stats_desc, i, &header); |
79 | type = pdesc->flags & KVM_STATS_TYPE_MASK; |
80 | unit = pdesc->flags & KVM_STATS_UNIT_MASK; |
81 | base = pdesc->flags & KVM_STATS_BASE_MASK; |
82 | |
83 | /* Check name string */ |
84 | TEST_ASSERT(strlen(pdesc->name) < header.name_size, |
85 | "KVM stats name (index: %d) too long" , i); |
86 | |
87 | /* Check type,unit,base boundaries */ |
88 | TEST_ASSERT(type <= KVM_STATS_TYPE_MAX, |
89 | "Unknown KVM stats (%s) type: %u" , pdesc->name, type); |
90 | TEST_ASSERT(unit <= KVM_STATS_UNIT_MAX, |
91 | "Unknown KVM stats (%s) unit: %u" , pdesc->name, unit); |
92 | TEST_ASSERT(base <= KVM_STATS_BASE_MAX, |
93 | "Unknown KVM stats (%s) base: %u" , pdesc->name, base); |
94 | |
95 | /* |
96 | * Check exponent for stats unit |
97 | * Exponent for counter should be greater than or equal to 0 |
98 | * Exponent for unit bytes should be greater than or equal to 0 |
99 | * Exponent for unit seconds should be less than or equal to 0 |
100 | * Exponent for unit clock cycles should be greater than or |
101 | * equal to 0 |
102 | * Exponent for unit boolean should be 0 |
103 | */ |
104 | switch (pdesc->flags & KVM_STATS_UNIT_MASK) { |
105 | case KVM_STATS_UNIT_NONE: |
106 | case KVM_STATS_UNIT_BYTES: |
107 | case KVM_STATS_UNIT_CYCLES: |
108 | TEST_ASSERT(pdesc->exponent >= 0, |
109 | "Unsupported KVM stats (%s) exponent: %i" , |
110 | pdesc->name, pdesc->exponent); |
111 | break; |
112 | case KVM_STATS_UNIT_SECONDS: |
113 | TEST_ASSERT(pdesc->exponent <= 0, |
114 | "Unsupported KVM stats (%s) exponent: %i" , |
115 | pdesc->name, pdesc->exponent); |
116 | break; |
117 | case KVM_STATS_UNIT_BOOLEAN: |
118 | TEST_ASSERT(pdesc->exponent == 0, |
119 | "Unsupported KVM stats (%s) exponent: %d" , |
120 | pdesc->name, pdesc->exponent); |
121 | break; |
122 | } |
123 | |
124 | /* Check size field, which should not be zero */ |
125 | TEST_ASSERT(pdesc->size, |
126 | "KVM descriptor(%s) with size of 0" , pdesc->name); |
127 | /* Check bucket_size field */ |
128 | switch (pdesc->flags & KVM_STATS_TYPE_MASK) { |
129 | case KVM_STATS_TYPE_LINEAR_HIST: |
130 | TEST_ASSERT(pdesc->bucket_size, |
131 | "Bucket size of Linear Histogram stats (%s) is zero" , |
132 | pdesc->name); |
133 | break; |
134 | default: |
135 | TEST_ASSERT(!pdesc->bucket_size, |
136 | "Bucket size of stats (%s) is not zero" , |
137 | pdesc->name); |
138 | } |
139 | size_data = max(size_data, pdesc->offset + pdesc->size * sizeof(*stats_data)); |
140 | } |
141 | |
142 | /* |
143 | * Now that the size of the data block is known, verify the data block |
144 | * doesn't overlap the descriptor block. |
145 | */ |
146 | TEST_ASSERT(header.data_offset >= header.desc_offset || |
147 | header.data_offset + size_data <= header.desc_offset, |
148 | "Data block is overlapped with Descriptor block" ); |
149 | |
150 | /* Check validity of all stats data size */ |
151 | TEST_ASSERT(size_data >= header.num_desc * sizeof(*stats_data), |
152 | "Data size is not correct" ); |
153 | |
154 | /* Allocate memory for stats data */ |
155 | stats_data = malloc(size_data); |
156 | TEST_ASSERT(stats_data, "Allocate memory for stats data" ); |
157 | /* Read kvm stats data as a bulk */ |
158 | ret = pread(stats_fd, stats_data, size_data, header.data_offset); |
159 | TEST_ASSERT(ret == size_data, "Read KVM stats data" ); |
160 | /* Read kvm stats data one by one */ |
161 | for (i = 0; i < header.num_desc; ++i) { |
162 | pdesc = get_stats_descriptor(stats_desc, i, &header); |
163 | read_stat_data(stats_fd, &header, pdesc, stats_data, |
164 | pdesc->size); |
165 | } |
166 | |
167 | free(stats_data); |
168 | free(stats_desc); |
169 | free(id); |
170 | |
171 | close(stats_fd); |
172 | TEST_ASSERT(fcntl(stats_fd, F_GETFD) == -1, "Stats fd not freed" ); |
173 | } |
174 | |
175 | #define DEFAULT_NUM_VM 4 |
176 | #define DEFAULT_NUM_VCPU 4 |
177 | |
178 | /* |
179 | * Usage: kvm_bin_form_stats [#vm] [#vcpu] |
180 | * The first parameter #vm set the number of VMs being created. |
181 | * The second parameter #vcpu set the number of VCPUs being created. |
182 | * By default, DEFAULT_NUM_VM VM and DEFAULT_NUM_VCPU VCPU for the VM would be |
183 | * created for testing. |
184 | */ |
185 | |
186 | int main(int argc, char *argv[]) |
187 | { |
188 | int vm_stats_fds, *vcpu_stats_fds; |
189 | int i, j; |
190 | struct kvm_vcpu **vcpus; |
191 | struct kvm_vm **vms; |
192 | int max_vm = DEFAULT_NUM_VM; |
193 | int max_vcpu = DEFAULT_NUM_VCPU; |
194 | |
195 | /* Get the number of VMs and VCPUs that would be created for testing. */ |
196 | if (argc > 1) { |
197 | max_vm = strtol(argv[1], NULL, 0); |
198 | if (max_vm <= 0) |
199 | max_vm = DEFAULT_NUM_VM; |
200 | } |
201 | if (argc > 2) { |
202 | max_vcpu = strtol(argv[2], NULL, 0); |
203 | if (max_vcpu <= 0) |
204 | max_vcpu = DEFAULT_NUM_VCPU; |
205 | } |
206 | |
207 | ksft_print_header(); |
208 | |
209 | /* Check the extension for binary stats */ |
210 | TEST_REQUIRE(kvm_has_cap(KVM_CAP_BINARY_STATS_FD)); |
211 | |
212 | ksft_set_plan(max_vm); |
213 | |
214 | /* Create VMs and VCPUs */ |
215 | vms = malloc(sizeof(vms[0]) * max_vm); |
216 | TEST_ASSERT(vms, "Allocate memory for storing VM pointers" ); |
217 | |
218 | vcpus = malloc(sizeof(struct kvm_vcpu *) * max_vm * max_vcpu); |
219 | TEST_ASSERT(vcpus, "Allocate memory for storing vCPU pointers" ); |
220 | |
221 | /* |
222 | * Not per-VM as the array is populated, used, and invalidated within a |
223 | * single for-loop iteration. |
224 | */ |
225 | vcpu_stats_fds = calloc(max_vm, sizeof(*vcpu_stats_fds)); |
226 | TEST_ASSERT(vcpu_stats_fds, "Allocate memory for VM stats fds" ); |
227 | |
228 | for (i = 0; i < max_vm; ++i) { |
229 | vms[i] = vm_create_barebones(); |
230 | for (j = 0; j < max_vcpu; ++j) |
231 | vcpus[i * max_vcpu + j] = __vm_vcpu_add(vms[i], j); |
232 | } |
233 | |
234 | /* |
235 | * Check stats read for every VM and vCPU, with a variety of flavors. |
236 | * Note, stats_test() closes the passed in stats fd. |
237 | */ |
238 | for (i = 0; i < max_vm; ++i) { |
239 | /* |
240 | * Verify that creating multiple userspace references to a |
241 | * single stats file works and doesn't cause explosions. |
242 | */ |
243 | vm_stats_fds = vm_get_stats_fd(vms[i]); |
244 | stats_test(stats_fd: dup(vm_stats_fds)); |
245 | |
246 | /* Verify userspace can instantiate multiple stats files. */ |
247 | stats_test(stats_fd: vm_get_stats_fd(vms[i])); |
248 | |
249 | for (j = 0; j < max_vcpu; ++j) { |
250 | vcpu_stats_fds[j] = vcpu_get_stats_fd(vcpus[i * max_vcpu + j]); |
251 | stats_test(stats_fd: dup(vcpu_stats_fds[j])); |
252 | stats_test(stats_fd: vcpu_get_stats_fd(vcpus[i * max_vcpu + j])); |
253 | } |
254 | |
255 | /* |
256 | * Close the VM fd and redo the stats tests. KVM should gift a |
257 | * reference (to the VM) to each stats fd, i.e. stats should |
258 | * still be accessible even after userspace has put its last |
259 | * _direct_ reference to the VM. |
260 | */ |
261 | kvm_vm_free(vms[i]); |
262 | |
263 | stats_test(stats_fd: vm_stats_fds); |
264 | for (j = 0; j < max_vcpu; ++j) |
265 | stats_test(stats_fd: vcpu_stats_fds[j]); |
266 | |
267 | ksft_test_result_pass("vm%i\n" , i); |
268 | } |
269 | |
270 | free(vms); |
271 | free(vcpus); |
272 | free(vcpu_stats_fds); |
273 | |
274 | ksft_finished(); /* Print results and exit() accordingly */ |
275 | } |
276 | |