| 1 | #ifdef TEST_BIO_SCHED |
| 2 | #include <block/block.h> |
| 3 | #include <block/sched.h> |
| 4 | #include <crypto/prng.h> |
| 5 | #include <fs/ext2.h> |
| 6 | #include <fs/vfs.h> |
| 7 | #include <mem/alloc.h> |
| 8 | #include <stdbool.h> |
| 9 | #include <stddef.h> |
| 10 | #include <stdint.h> |
| 11 | #include <string.h> |
| 12 | #include <test.h> |
| 13 | #include <time/spin_sleep.h> |
| 14 | |
| 15 | #include "fs/detect.h" |
| 16 | #define EXT2_INIT \ |
| 17 | if (global.root_node->fs_type != FS_EXT2) { \ |
| 18 | test_info("the mounted root is not ext2"); \ |
| 19 | return TEST_SKIP(TEST_SKIP_NONE); \ |
| 20 | } \ |
| 21 | struct vfs_node *root = global.root_node; |
| 22 | |
| 23 | static bool done2 = false; |
| 24 | static atomic_bool cb1d = false, cb2d = false; |
| 25 | static uint64_t avg_complete_time[BIO_SCHED_LEVELS] = {0}; |
| 26 | static uint64_t total_complete_time[BIO_SCHED_LEVELS] = {0}; |
| 27 | static _Atomic uint32_t runs = 0; |
| 28 | |
| 29 | static void bio_sch_callback(struct bio_request *req) { |
| 30 | (void) req; |
| 31 | |
| 32 | done2 = true; |
| 33 | uint64_t q_ms = (uint64_t) req->user_data >> 12; |
| 34 | uint64_t q_lvl = (uint64_t) req->user_data & 7; |
| 35 | time_t time = time_get_ms() - q_ms; |
| 36 | total_complete_time[q_lvl] += time; |
| 37 | req->user_data = NULL; |
| 38 | atomic_fetch_add(&runs, 1); |
| 39 | TEST_ASSERT_VOID(req->status == BIO_STATUS_OK); |
| 40 | } |
| 41 | |
| 42 | static void bio_sch_callback1(struct bio_request *req) { |
| 43 | (void) req; |
| 44 | |
| 45 | atomic_store(&cb1d, true); |
| 46 | test_info("cb 1 success" ); |
| 47 | } |
| 48 | |
| 49 | static void bio_sch_callback2(struct bio_request *req) { |
| 50 | (void) req; |
| 51 | |
| 52 | atomic_store(&cb2d, true); |
| 53 | test_info("cb 2 success" ); |
| 54 | } |
| 55 | |
| 56 | TEST_DECLARE(bio_sched_coalesce_test, .tier = TEST_TIER_UNIT) { |
| 57 | EXT2_INIT; |
| 58 | struct ext2_fs *fs = root->fs_data; |
| 59 | struct block_device *d = fs->drive; |
| 60 | |
| 61 | struct bio_request *bio = kmalloc(sizeof(*bio)); |
| 62 | *bio = (struct bio_request){ |
| 63 | .lba = 0, |
| 64 | .disk = d, |
| 65 | .buffer = kmalloc_aligned(512, 4096), |
| 66 | .size = 512, |
| 67 | .sector_count = 1, |
| 68 | .write = false, |
| 69 | .done = false, |
| 70 | .status = -1, |
| 71 | .on_complete = bio_sch_callback1, |
| 72 | .priority = BIO_RQ_MEDIUM, |
| 73 | .user_data = (void *) BIO_RQ_MEDIUM, |
| 74 | }; |
| 75 | |
| 76 | struct bio_request *bio2 = kmalloc(sizeof(*bio2)); |
| 77 | *bio2 = (struct bio_request){ |
| 78 | .lba = 1, |
| 79 | .disk = d, |
| 80 | .buffer = kmalloc_aligned(512, 4096), |
| 81 | .size = 512, |
| 82 | .sector_count = 1, |
| 83 | .write = false, |
| 84 | .done = false, |
| 85 | .status = -1, |
| 86 | .on_complete = bio_sch_callback2, |
| 87 | .priority = BIO_RQ_MEDIUM, |
| 88 | .user_data = (void *) BIO_RQ_MEDIUM, |
| 89 | }; |
| 90 | |
| 91 | bio->on_complete = bio_sch_callback1; |
| 92 | bio2->on_complete = bio_sch_callback2; |
| 93 | |
| 94 | char *name = kmalloc(100); |
| 95 | uint64_t t = time_get_us(); |
| 96 | bio_sched_enqueue(disk: d, req: bio); |
| 97 | bio_sched_enqueue(disk: d, req: bio2); |
| 98 | snprintf(buffer: name, buffer_len: 100, format: "enqueues took %d us" , time_get_us() - t); |
| 99 | test_info(name); |
| 100 | |
| 101 | bio_sched_dispatch_all(disk: d); |
| 102 | |
| 103 | for (int i = 0; i < 5000; i++) |
| 104 | scheduler_yield(); |
| 105 | return TEST_SUCCESS; |
| 106 | } |
| 107 | |
| 108 | #define BIO_SCHED_TEST_RUNS 1024 |
| 109 | static uint64_t runs_per_lvl[BIO_SCHED_LEVELS] = {0}; |
| 110 | static struct bio_request *rqs[BIO_SCHED_TEST_RUNS] = {0}; |
| 111 | static uint8_t *buffers[BIO_SCHED_TEST_RUNS] = {0}; |
| 112 | static volatile int send_dispatch = 0; |
| 113 | |
| 114 | TEST_DECLARE(bio_sched_delay_enqueue_test, .tier = TEST_TIER_INTEGRATION) { |
| 115 | EXT2_INIT; |
| 116 | ABORT_IF_RAM_LOW(); |
| 117 | |
| 118 | struct ext2_fs *fs = root->fs_data; |
| 119 | struct block_device *d = fs->drive; |
| 120 | kassert(d); |
| 121 | |
| 122 | prng_seed(seed: time_get_us()); |
| 123 | |
| 124 | for (uint64_t i = 0; i < BIO_SCHED_TEST_RUNS; i++) { |
| 125 | uint8_t *buf = kmalloc_aligned(PAGE_SIZE, PAGE_SIZE); |
| 126 | struct bio_request *rq = |
| 127 | kmalloc(sizeof(struct bio_request), ALLOC_FLAGS_ZERO); |
| 128 | TEST_ASSERT(rq && buf); |
| 129 | TEST_ASSERT(IS_ALIGNED((vaddr_t) buf, PAGE_SIZE)); |
| 130 | |
| 131 | rq->disk = d; |
| 132 | rq->lba = (i * 2) % 512; |
| 133 | rq->sector_count = 1; |
| 134 | rq->size = 512; |
| 135 | rq->on_complete = bio_sch_callback; |
| 136 | rq->buffer = buffers[i]; |
| 137 | rq->priority = prng_next() % BIO_SCHED_LEVELS; |
| 138 | rq->write = false; |
| 139 | INIT_LIST_HEAD(list: &rq->list); |
| 140 | |
| 141 | rqs[i] = rq; |
| 142 | buffers[i] = buf; |
| 143 | } |
| 144 | |
| 145 | for (size_t i = 0; i < BIO_SCHED_TEST_RUNS; i++) |
| 146 | for (size_t j = 0; j < BIO_SCHED_TEST_RUNS; j++) |
| 147 | if (i != j && rqs[i] == rqs[j]) |
| 148 | test_err("duplicate at %u and %u\n" , i, j); |
| 149 | |
| 150 | for (size_t i = 0; i < BIO_SCHED_TEST_RUNS; i++) { |
| 151 | if (!rqs[i]->disk) { |
| 152 | test_err("rq %p %u\n" , rqs[i], i); |
| 153 | return TEST_SUCCESS; |
| 154 | } |
| 155 | |
| 156 | kassert(rqs[i]->disk); |
| 157 | } |
| 158 | |
| 159 | uint64_t ms = time_get_ms(); |
| 160 | for (uint64_t i = 0; i < BIO_SCHED_TEST_RUNS; i++) { |
| 161 | struct bio_request *rq = rqs[i]; |
| 162 | runs_per_lvl[rq->priority]++; |
| 163 | rq->user_data = (void *) ((time_get_ms() << 12) | rq->priority); |
| 164 | bio_sched_enqueue(disk: d, req: rq); |
| 165 | } |
| 166 | ms = time_get_ms() - ms; |
| 167 | |
| 168 | char *msg = kmalloc(100); |
| 169 | TEST_ASSERT(msg); |
| 170 | snprintf(buffer: msg, buffer_len: 100, format: "Total time spent enqueuing is %d ms" , ms); |
| 171 | test_info(msg); |
| 172 | send_dispatch = 1; |
| 173 | bio_sched_dispatch_all(disk: d); |
| 174 | send_dispatch = 2; |
| 175 | |
| 176 | for (uint64_t i = 0; i < 150000; i++) |
| 177 | cpu_relax(); |
| 178 | |
| 179 | for (uint64_t i = 0; i < BIO_SCHED_LEVELS; i++) { |
| 180 | avg_complete_time[i] = total_complete_time[i] / runs_per_lvl[i]; |
| 181 | char *msg = kmalloc(100, ALLOC_FLAGS_ZERO); |
| 182 | TEST_ASSERT(msg); |
| 183 | snprintf(buffer: msg, buffer_len: 100, format: "Average completion time of level %d is %d ms" , i, |
| 184 | avg_complete_time[i]); |
| 185 | test_info(msg); |
| 186 | } |
| 187 | |
| 188 | char *m2 = kmalloc(100); |
| 189 | TEST_ASSERT(m2); |
| 190 | snprintf(buffer: m2, buffer_len: 100, format: "Runs is %d, test_runs is %d" , atomic_load(&runs), |
| 191 | BIO_SCHED_TEST_RUNS); |
| 192 | test_info(m2); |
| 193 | TEST_ASSERT(atomic_load(&runs) <= BIO_SCHED_TEST_RUNS); |
| 194 | |
| 195 | return TEST_SUCCESS; |
| 196 | } |
| 197 | #endif |
| 198 | |