github.com/dshulyak/uring@v0.0.0-20210209113719-1b2ec51f1542/fs/file_test.go (about) 1 package fs 2 3 import ( 4 "crypto/rand" 5 "encoding/binary" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "runtime" 10 "sync" 11 "sync/atomic" 12 "syscall" 13 "testing" 14 "time" 15 "unsafe" 16 17 "github.com/dshulyak/uring" 18 "github.com/dshulyak/uring/fixed" 19 "github.com/dshulyak/uring/loop" 20 "github.com/stretchr/testify/require" 21 "golang.org/x/sys/unix" 22 ) 23 24 func aligned(bsize int) []byte { 25 if bsize == 0 { 26 return nil 27 } 28 if bsize%512 != 0 { 29 panic("block size must be multiple of 512") 30 } 31 block := make([]byte, bsize+512) 32 a := uintptr(unsafe.Pointer(&block[0])) & uintptr(511) 33 var offset uintptr 34 if a != 0 { 35 offset = 512 - a 36 } 37 return block[offset : int(offset)+bsize] 38 } 39 40 func TestFixedBuffersIO(t *testing.T) { 41 tester := func(t *testing.T, fsm *Filesystem, pool *fixed.Pool) { 42 t.Helper() 43 f, err := TempFile(fsm, "testing-io-", 0) 44 require.NoError(t, err) 45 t.Cleanup(func() { os.Remove(f.Name()) }) 46 47 in, out := pool.Get(), pool.Get() 48 rand.Read(out.B) 49 50 _, err = f.WriteAtFixed(out, 0) 51 require.NoError(t, err) 52 require.NoError(t, f.Datasync()) 53 _, err = f.ReadAtFixed(in, 0) 54 require.NoError(t, err) 55 require.Equal(t, in.B, out.B) 56 } 57 t.Run("registered", func(t *testing.T) { 58 q, err := loop.Setup(32, nil, nil) 59 require.NoError(t, err) 60 t.Cleanup(func() { q.Close() }) 61 fsm := NewFilesystem(q, RegisterFiles(32)) 62 pool, err := fixed.New(q, 100, 2) 63 require.NoError(t, err) 64 t.Cleanup(func() { pool.Close() }) 65 tester(t, fsm, pool) 66 }) 67 t.Run("unregistered", func(t *testing.T) { 68 q, err := loop.Setup(32, nil, nil) 69 require.NoError(t, err) 70 t.Cleanup(func() { q.Close() }) 71 fsm := NewFilesystem(q) 72 pool, err := fixed.New(q, 100, 2) 73 require.NoError(t, err) 74 t.Cleanup(func() { pool.Close() }) 75 tester(t, fsm, pool) 76 }) 77 } 78 79 func TestRegularIO(t *testing.T) { 80 tester := func(t *testing.T, fsm *Filesystem) { 81 t.Helper() 82 f, err := TempFile(fsm, "testing-io-", 0) 83 require.NoError(t, err) 84 t.Cleanup(func() { os.Remove(f.Name()) }) 85 86 in, out := make([]byte, 100), make([]byte, 100) 87 rand.Read(out) 88 89 _, err = f.WriteAt(out, 0) 90 require.NoError(t, err) 91 require.NoError(t, f.Sync()) 92 _, err = f.ReadAt(in, 0) 93 require.NoError(t, err) 94 require.Equal(t, in, out) 95 } 96 t.Run("registered", func(t *testing.T) { 97 q, err := loop.Setup(32, nil, nil) 98 require.NoError(t, err) 99 t.Cleanup(func() { q.Close() }) 100 fsm := NewFilesystem(q, RegisterFiles(32)) 101 tester(t, fsm) 102 }) 103 t.Run("unregistered", func(t *testing.T) { 104 q, err := loop.Setup(32, nil, nil) 105 require.NoError(t, err) 106 t.Cleanup(func() { q.Close() }) 107 fsm := NewFilesystem(q) 108 tester(t, fsm) 109 }) 110 } 111 112 func benchmarkOSWriteAt(b *testing.B, size int64, fflags int) { 113 f, err := ioutil.TempFile("", "testing-write-os-") 114 require.NoError(b, err) 115 require.NoError(b, f.Close()) 116 f, err = os.OpenFile(f.Name(), os.O_RDWR|fflags, 0644) 117 require.NoError(b, err) 118 b.Cleanup(func() { 119 os.Remove(f.Name()) 120 }) 121 122 buf := make([]byte, size) 123 offset := int64(0) 124 125 b.SetBytes(size) 126 b.ReportAllocs() 127 b.ResetTimer() 128 129 b.RunParallel(func(pb *testing.PB) { 130 for pb.Next() { 131 _, err := f.WriteAt(buf, atomic.AddInt64(&offset, size)-size) 132 if err != nil { 133 b.Error(err) 134 } 135 } 136 }) 137 } 138 139 func benchmarkOSReadAt(b *testing.B, size int64) { 140 f, err := ioutil.TempFile("", "testing-write-os-") 141 require.NoError(b, err) 142 require.NoError(b, f.Close()) 143 b.Cleanup(func() { 144 os.Remove(f.Name()) 145 }) 146 f, err = os.OpenFile(f.Name(), os.O_RDWR|unix.O_DIRECT, 0644) 147 require.NoError(b, err) 148 149 buf := aligned(int(size)) 150 offset := int64(0) 151 152 wbuf := aligned(int(size) * (b.N + 1)) 153 _, err = f.WriteAt(wbuf, 0) 154 require.NoError(b, err) 155 156 b.SetBytes(size) 157 b.ReportAllocs() 158 b.ResetTimer() 159 160 b.RunParallel(func(pb *testing.PB) { 161 for pb.Next() { 162 _, err := f.ReadAt(buf, atomic.AddInt64(&offset, size)-size) 163 if err != nil { 164 b.Error(err) 165 } 166 } 167 }) 168 } 169 170 func BenchmarkWriteAt(b *testing.B) { 171 for _, size := range []int64{512, 4 << 10, 8 << 10, 16 << 10, 64 << 10, 256 << 10} { 172 b.Run(fmt.Sprintf("uring %d", size), func(b *testing.B) { 173 q, err := loop.Setup( 174 512, 175 &uring.IOUringParams{ 176 CQEntries: 8 * 4096, 177 Flags: uring.IORING_SETUP_CQSIZE, 178 }, 179 nil, 180 ) 181 require.NoError(b, err) 182 benchmarkWriteAt(b, q, size, 0) 183 }) 184 b.Run(fmt.Sprintf("os %d", size), func(b *testing.B) { 185 benchmarkOSWriteAt(b, size, 0) 186 }) 187 } 188 } 189 190 func BenchmarkReadAt(b *testing.B) { 191 for _, size := range []int64{512, 4 << 10, 8 << 10, 16 << 10, 64 << 10, 256 << 10} { 192 b.Run(fmt.Sprintf("uring %d", size), func(b *testing.B) { 193 q, err := loop.Setup( 194 4096, 195 &uring.IOUringParams{ 196 CQEntries: 2 * 4096, 197 Flags: uring.IORING_SETUP_CQSIZE, 198 }, 199 nil, 200 ) 201 require.NoError(b, err) 202 benchmarkReadAt(b, q, size) 203 }) 204 205 b.Run(fmt.Sprintf("enter %d", size), func(b *testing.B) { 206 q, err := loop.Setup( 207 4096, 208 &uring.IOUringParams{ 209 CQEntries: 2 * 4096, 210 Flags: uring.IORING_SETUP_CQSIZE, 211 }, &loop.Params{ 212 Rings: runtime.NumCPU(), 213 WaitMethod: loop.WaitEnter, 214 Flags: loop.FlagSharedWorkers | loop.FlagBatchSubmission, 215 SubmissionTimer: 50 * time.Microsecond, 216 }, 217 ) 218 require.NoError(b, err) 219 benchmarkReadAt(b, q, size) 220 }) 221 b.Run(fmt.Sprintf("os %d", size), func(b *testing.B) { 222 benchmarkOSReadAt(b, size) 223 }) 224 } 225 } 226 227 func benchmarkWriteAt(b *testing.B, q *loop.Loop, size int64, fflags int) { 228 b.Cleanup(func() { q.Close() }) 229 fsm := NewFilesystem(q) 230 231 f, err := TempFile(fsm, "testing-fs-file-", fflags) 232 require.NoError(b, err) 233 b.Cleanup(func() { os.Remove(f.Name()) }) 234 235 offset := int64(0) 236 buf := make([]byte, size) 237 238 b.SetBytes(int64(size)) 239 b.ReportAllocs() 240 b.ResetTimer() 241 242 runConcurrently(20_000, b.N, func() { 243 n, err := f.WriteAt(buf, atomic.AddInt64(&offset, size)-size) 244 if err != nil { 245 b.Error(err) 246 } 247 if n != len(buf) { 248 b.Errorf("short write %d < %d", n, len(buf)) 249 } 250 }) 251 } 252 253 func runConcurrently(c, n int, f func()) { 254 var wg sync.WaitGroup 255 quo, rem := n/c, n%c 256 for i := 0; i < c; i++ { 257 wg.Add(1) 258 n := quo 259 if i < rem { 260 n = quo + 1 261 } 262 go func() { 263 defer wg.Done() 264 for j := 0; j < n; j++ { 265 f() 266 } 267 }() 268 } 269 wg.Wait() 270 } 271 272 func benchmarkReadAt(b *testing.B, q *loop.Loop, size int64) { 273 b.Cleanup(func() { 274 q.Close() 275 }) 276 277 fsm := NewFilesystem(q) 278 279 f, err := TempFile(fsm, "testing-fs-file-", unix.O_DIRECT) 280 require.NoError(b, err) 281 b.Cleanup(func() { 282 f.Close() 283 os.Remove(f.Name()) 284 }) 285 286 total := 0 287 wbuf := aligned(int(size) * (b.N + 1)) 288 for total != len(wbuf) { 289 n, err := f.WriteAt(wbuf[total:], int64(total)) 290 require.NoError(b, err) 291 total += n 292 } 293 294 offset := int64(0) 295 buf := aligned(int(size)) 296 297 b.SetBytes(int64(size)) 298 b.ReportAllocs() 299 b.ResetTimer() 300 301 b.SetBytes(int64(size)) 302 b.ReportAllocs() 303 b.ResetTimer() 304 runConcurrently(100_000, b.N, func() { 305 off := atomic.AddInt64(&offset, size) - size 306 for { 307 rn, err := f.ReadAt(buf, off) 308 if err != nil { 309 b.Error(err) 310 } 311 if rn == len(buf) { 312 break 313 } 314 } 315 }) 316 } 317 318 func TestEmptyWrite(t *testing.T) { 319 queue, err := loop.Setup(1024, nil, nil) 320 require.NoError(t, err) 321 t.Cleanup(func() { queue.Close() }) 322 323 fsm := NewFilesystem(queue) 324 325 f, err := TempFile(fsm, "testing-fs-file-", 0) 326 require.NoError(t, err) 327 t.Cleanup(func() { 328 os.Remove(f.Name()) 329 }) 330 331 n, err := f.WriteAt(nil, 0) 332 require.Equal(t, 0, n) 333 require.NoError(t, err) 334 } 335 336 func TestClose(t *testing.T) { 337 queue, err := loop.Setup(8, nil, nil) 338 require.NoError(t, err) 339 t.Cleanup(func() { queue.Close() }) 340 341 fsm := NewFilesystem(queue) 342 343 f, err := TempFile(fsm, "test-concurrent-writes", 0) 344 require.NoError(t, err) 345 t.Cleanup(func() { 346 os.Remove(f.Name()) 347 }) 348 require.NoError(t, f.Close()) 349 buf := []byte{1, 2} 350 _, err = f.WriteAt(buf, 0) 351 require.Equal(t, syscall.EBADF, err) 352 } 353 354 func TestConcurrentWritesIntegrity(t *testing.T) { 355 queue, err := loop.Setup(1024, nil, nil) 356 require.NoError(t, err) 357 t.Cleanup(func() { queue.Close() }) 358 359 fsm := NewFilesystem(queue) 360 361 f, err := TempFile(fsm, "test-concurrent-writes", 0) 362 require.NoError(t, err) 363 t.Cleanup(func() { 364 os.Remove(f.Name()) 365 }) 366 367 var wg sync.WaitGroup 368 var n int64 = 30000 369 370 for i := int64(0); i < n; i++ { 371 wg.Add(1) 372 go func(i uint64) { 373 buf := make([]byte, 8) 374 binary.BigEndian.PutUint64(buf, i) 375 _, _ = f.WriteAt(buf, int64(i*8)) 376 wg.Done() 377 }(uint64(i)) 378 } 379 wg.Wait() 380 381 buf2 := make([]byte, 8) 382 for i := int64(0); i < n; i++ { 383 _, err := f.ReadAt(buf2, i*8) 384 require.NoError(t, err) 385 rst := binary.BigEndian.Uint64(buf2[:]) 386 require.Equal(t, i, int64(rst)) 387 } 388 }