1#ifdef TEST_NIGHTMARE_ENABLED
2#include "internal.h"
3
4#include <cmdline.h>
5#include <global.h>
6#include <nightmare/perturb.h>
7#include <nightmare/record.h>
8#include <sch/sched.h>
9#include <string.h>
10#include <test/perturb.h>
11#include <thread/thread.h>
12#include <time/time.h>
13
14/* Scheduler rounds quiesce waits give before failing */
15#define NIGHTMARE_QUIESCE_MIN_ROUNDS 8
16
17static struct test_perturb_config *perturb_resolve_config(const char *path,
18 size_t path_len) {
19 static const char prefix[] = "perturb.";
20 if (path_len <= sizeof(prefix) - 1 ||
21 strncmp(s1: path, s2: prefix, n: sizeof(prefix) - 1) != 0)
22 return NULL;
23
24 return test_perturb_config_lookup_n(name: path + sizeof(prefix) - 1,
25 len: path_len - (sizeof(prefix) - 1));
26}
27
28static void *perturb_interval_resolve(const char *path, size_t path_len) {
29 struct test_perturb_config *config = perturb_resolve_config(path, path_len);
30 return config && strcmp(str1: config->name, str2: "stutter") != 0 ? config : NULL;
31}
32
33static void *perturb_stutter_resolve(const char *path, size_t path_len) {
34 struct test_perturb_config *config = perturb_resolve_config(path, path_len);
35 return config && strcmp(str1: config->name, str2: "stutter") == 0 ? config : NULL;
36}
37
38CMDLINE_SCHEMA_DEFINE(
39 nightmare_perturb_interval, "nightmare", "perturb.<interval-svc>",
40 "Nightmare interval perturbation options", perturb_interval_resolve,
41 CMDLINE_SCHEMA_PROP(struct test_perturb_config, interval_us,
42 .types = CMDLINE_TYPES(CMDLINE_TYPE_DURATION),
43 .range = RANGE(US_TO_NS(1), TIME_NS_MAX)));
44
45CMDLINE_SCHEMA_DEFINE(
46 nightmare_perturb_stutter, "nightmare", "perturb.stutter",
47 "Nightmare stutter perturbation options", perturb_stutter_resolve,
48 CMDLINE_SCHEMA_PROP(struct test_perturb_config, period_ms,
49 .types = CMDLINE_TYPES(CMDLINE_TYPE_DURATION),
50 .range = RANGE(MS_TO_NS(1), TIME_NS_MAX)),
51 CMDLINE_SCHEMA_PROP(struct test_perturb_config, gap_ms,
52 .types = CMDLINE_TYPES(CMDLINE_TYPE_DURATION),
53 .range = RANGE(MS_TO_NS(1), TIME_NS_MAX)));
54
55void nightmare_perturb_stutter(const struct test_perturb_env *env,
56 struct test_conc_worker *worker) {
57 cc_ignore(env, worker);
58 struct nightmare_ctx *ctx = &nightmare_runtime.ctx;
59 const struct test_perturb_config *cfg =
60 test_perturb_config_lookup(name: "stutter");
61 time_ms_t period_ms =
62 (cfg && cfg->period_ms) ? NS_TO_MS(cfg->period_ms) : 100;
63 time_ms_t gap_ms = (cfg && cfg->gap_ms) ? NS_TO_MS(cfg->gap_ms) : 5;
64
65 while (!nightmare_must_stop()) {
66 thread_sleep_for_ms(ms: period_ms);
67 if (nightmare_must_stop())
68 break;
69
70 /* Request quiesce */
71 atomic_store_release(&nightmare_runtime.conc.quiesce_requested, true);
72
73 /* Wait for subject workers to park, with signs of workers
74 * parking preventing the panic from firing so that VMs
75 * with oversubscribed vCPUs don't false positive */
76 time_ms_t grace_ms = gap_ms ? gap_ms : 10;
77 time_ms_t cap_ms = grace_ms * (ctx->worker_count + 1);
78 time_ms_t started_ms = time_get_ms();
79 time_ms_t last_park_ms = started_ms;
80 size_t parked_seen = 0;
81 size_t rounds = 0;
82 bool all_subjects_parked = false;
83 while (!nightmare_must_stop()) {
84 size_t parked = 0;
85 for (size_t i = 0; i < ctx->worker_count; i++) {
86 if (atomic_load_acq(&nightmare_runtime.conc.workers[i].parked))
87 parked++;
88 }
89 all_subjects_parked = parked == ctx->worker_count;
90 if (all_subjects_parked)
91 break;
92
93 time_ms_t now = time_get_ms();
94 if (parked > parked_seen) {
95 parked_seen = parked;
96 last_park_ms = now;
97 }
98
99 /* Give a few rounds of leeway */
100 if (rounds++ >= NIGHTMARE_QUIESCE_MIN_ROUNDS &&
101 (now - last_park_ms >= grace_ms || now - started_ms >= cap_ms))
102 break;
103
104 scheduler_yield();
105 }
106
107 if (nightmare_must_stop()) {
108 nightmare_record_quiesce(record: &(struct nightmare_quiesce_record){
109 .result = "aborted",
110 .checks = 0,
111 });
112 atomic_store_release(&nightmare_runtime.conc.quiesce_requested,
113 false);
114 break;
115 }
116
117 struct nightmare_verdict verdict = NIGHTMARE_OK;
118 uint64_t checks = 0;
119 if (!all_subjects_parked) {
120 verdict = NIGHTMARE_FAIL(
121 "quiesce_timeout",
122 "not all subject workers parked before the stutter deadline");
123 } else if (ctx->nm && ctx->nm->ops && ctx->nm->ops->quiesce_check) {
124 verdict = ctx->nm->ops->quiesce_check(ctx);
125 checks = 1;
126 }
127
128 nightmare_record_quiesce(record: &(struct nightmare_quiesce_record){
129 .result = nightmare_result_to_str(stringify_in: verdict.result),
130 .checks = checks,
131 });
132
133 if (verdict.result != NIGHTMARE_RESULT_OK)
134 nightmare_publish_perturb_verdict(verdict);
135
136 /* Release the herd */
137 atomic_store_release(&nightmare_runtime.conc.quiesce_requested, false);
138
139 if (verdict.result != NIGHTMARE_RESULT_OK) {
140 nightmare_publish_stop(reason: TEST_STOP_FAIL);
141 break;
142 }
143 }
144}
145
146static const struct test_perturb_desc stutter_desc = {
147 .name = "stutter",
148 .fn = nightmare_perturb_stutter,
149};
150
151const struct test_perturb_desc *nightmare_perturb_lookup(const char *name) {
152 if (name && strcmp(str1: name, str2: stutter_desc.name) == 0)
153 return &stutter_desc;
154
155 return test_perturb_lookup(name);
156}
157#endif
158