Skip to content

Commit 78712c4

Browse files
dperf support --sync flag for synchronous I/O
1 parent bd4f1aa commit 78712c4

File tree

3 files changed

+71
-17
lines changed

3 files changed

+71
-17
lines changed

cmd/cmd.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ var (
4545
serial = false
4646
writeOnly = false
4747
verbose = false
48+
syncMode = false
4849
blockSize = "4MiB"
4950
fileSize = "1GiB"
5051
cpuNode = 0
@@ -61,32 +62,44 @@ var dperfCmd = &cobra.Command{
6162
MinIO drive performance utility
6263
--------------------------------
6364
dperf measures throughput of each of the drives mounted at PATH...
65+
66+
By default, dperf uses O_DIRECT for block sizes >= 4KiB to bypass the page cache.
67+
For block sizes < 4KiB, it automatically switches to O_DSYNC/O_SYNC mode.
68+
You can explicitly enable sync mode for any block size using the --sync flag.
6469
`,
6570
SilenceUsage: true,
6671
SilenceErrors: true,
6772
Args: cobra.MinimumNArgs(1),
6873
Version: Version,
6974
Example: `
70-
# run dpref on drive mounted at /mnt/drive1
75+
# run dperf on drive mounted at /mnt/drive1
7176
$ dperf /mnt/drive1
7277
7378
# run dperf on drives 1 to 6. Output will be sorted by throughput. Fastest drive is at the top.
7479
$ dperf /mnt/drive{1..6}
7580
7681
# run dperf on drives one-by-one
7782
$ dperf --serial /mnt/drive{1..6}
83+
84+
# run dperf with 1KiB block size (automatically uses O_DSYNC)
85+
$ dperf -b 1KiB /mnt/drive1
86+
87+
# run dperf with 4KiB block size using O_DSYNC instead of O_DIRECT
88+
$ dperf --sync -b 4KiB /mnt/drive1
7889
`,
7990
RunE: func(c *cobra.Command, args []string) error {
8091
bs, err := humanize.ParseBytes(blockSize)
8192
if err != nil {
8293
return fmt.Errorf("Invalid blocksize format: %v", err)
8394
}
8495

85-
if bs < alignSize {
86-
return fmt.Errorf("Invalid blocksize must greater than 4k: %d", bs)
96+
if bs == 0 {
97+
return fmt.Errorf("Invalid blocksize must be greater than 0: %d", bs)
8798
}
8899

89-
if bs%alignSize != 0 {
100+
// For block sizes < 4KiB, we'll use O_DSYNC instead of O_DIRECT
101+
// For block sizes >= 4KiB, we require alignment for O_DIRECT
102+
if bs >= alignSize && bs%alignSize != 0 {
90103
return fmt.Errorf("Invalid blocksize must be multiples of 4k: %d", bs)
91104
}
92105

@@ -95,25 +108,31 @@ $ dperf --serial /mnt/drive{1..6}
95108
return fmt.Errorf("Invalid filesize format: %v", err)
96109
}
97110

98-
if fs < alignSize {
99-
return fmt.Errorf("Invalid filesize must greater than 4k: %d", fs)
111+
if fs == 0 {
112+
return fmt.Errorf("Invalid filesize must be greater than 0: %d", fs)
100113
}
101114

102-
if fs%alignSize != 0 {
115+
// For file sizes with small block sizes, we relax the alignment requirement
116+
// For file sizes with block sizes >= 4KiB, we require alignment for O_DIRECT
117+
if bs >= alignSize && fs%alignSize != 0 {
103118
return fmt.Errorf("Invalid filesize must multiples of 4k: %d", fs)
104119
}
105120

106121
if ioPerDrive <= 0 {
107122
return fmt.Errorf("Invalid ioperdrive must greater than 0: %d", ioPerDrive)
108123
}
109124

125+
// Use sync mode if explicitly requested or if block size < 4KiB
126+
useSyncMode := syncMode || bs < alignSize
127+
110128
perf := &dperf.DrivePerf{
111129
Serial: serial,
112130
BlockSize: bs,
113131
FileSize: fs,
114132
Verbose: verbose,
115133
IOPerDrive: ioPerDrive,
116134
WriteOnly: writeOnly,
135+
SyncMode: useSyncMode,
117136
}
118137
paths := make([]string, 0, len(args))
119138
for _, arg := range args {
@@ -215,6 +234,8 @@ func init() {
215234
"write-only", "", writeOnly, "run write only tests")
216235
dperfCmd.PersistentFlags().BoolVarP(&verbose,
217236
"verbose", "v", verbose, "print READ/WRITE for each paths independently, default only prints aggregated")
237+
dperfCmd.PersistentFlags().BoolVarP(&syncMode,
238+
"sync", "", syncMode, "use O_DSYNC for writes and O_SYNC for reads instead of O_DIRECT (automatically enabled for block sizes < 4KiB)")
218239
dperfCmd.PersistentFlags().StringVarP(&blockSize,
219240
"blocksize", "b", blockSize, "read/write block size")
220241
dperfCmd.PersistentFlags().StringVarP(&fileSize,

pkg/dperf/perf.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type DrivePerf struct {
3535
FileSize uint64
3636
IOPerDrive int
3737
WriteOnly bool
38+
SyncMode bool // Use O_DSYNC/O_SYNC instead of O_DIRECT
3839
}
3940

4041
// mustGetUUID - get a random UUID.
@@ -54,8 +55,13 @@ func (d *DrivePerf) runTests(ctx context.Context, path string, testUUID string)
5455

5556
dataBuffers := make([][]byte, d.IOPerDrive)
5657
for i := 0; i < d.IOPerDrive; i++ {
57-
// Read Aligned block upto a multiple of BlockSize
58-
dataBuffers[i] = alignedBlock(int(d.BlockSize))
58+
// In sync mode or with small block sizes, use regular buffer allocation
59+
// Otherwise, use aligned blocks for O_DIRECT
60+
if d.SyncMode {
61+
dataBuffers[i] = make([]byte, d.BlockSize)
62+
} else {
63+
dataBuffers[i] = alignedBlock(int(d.BlockSize))
64+
}
5965
}
6066

6167
testUUIDPath := filepath.Join(path, testUUID)

pkg/dperf/run_linux.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,24 @@ func (n nullWriter) Write(b []byte) (int, error) {
3939

4040
func (d *DrivePerf) runReadTest(ctx context.Context, path string, data []byte) (uint64, error) {
4141
startTime := time.Now()
42-
r, err := os.OpenFile(path, syscall.O_DIRECT|os.O_RDONLY, 0o400)
42+
43+
// Choose flags based on sync mode
44+
var flags int
45+
if d.SyncMode {
46+
// Use O_SYNC for synchronized reads (for small block sizes or when --sync is specified)
47+
flags = syscall.O_SYNC | os.O_RDONLY
48+
} else {
49+
// Use O_DIRECT for direct I/O (bypasses page cache)
50+
flags = syscall.O_DIRECT | os.O_RDONLY
51+
}
52+
53+
r, err := os.OpenFile(path, flags, 0o400)
4354
if err != nil {
4455
return 0, err
4556
}
4657
unix.Fadvise(int(r.Fd()), 0, int64(d.FileSize), unix.FADV_SEQUENTIAL)
4758

48-
n, err := copyAligned(&nullWriter{}, r, data, int64(d.FileSize), r.Fd())
59+
n, err := copyAligned(&nullWriter{}, r, data, int64(d.FileSize), r.Fd(), d.SyncMode)
4960
r.Close()
5061
if err != nil {
5162
return 0, err
@@ -125,7 +136,10 @@ const DirectioAlignSize = 4096
125136
// used with DIRECT I/O based file descriptor and it is expected that
126137
// input writer *os.File not a generic io.Writer. Make sure to have
127138
// the file opened for writes with syscall.O_DIRECT flag.
128-
func copyAligned(w io.Writer, r io.Reader, alignedBuf []byte, totalSize int64, fd uintptr) (int64, error) {
139+
//
140+
// When syncMode is true, alignment checks are skipped as O_DSYNC/O_SYNC
141+
// is used instead of O_DIRECT.
142+
func copyAligned(w io.Writer, r io.Reader, alignedBuf []byte, totalSize int64, fd uintptr, syncMode bool) (int64, error) {
129143
if totalSize == 0 {
130144
return 0, nil
131145
}
@@ -140,7 +154,8 @@ func copyAligned(w io.Writer, r io.Reader, alignedBuf []byte, totalSize int64, f
140154
}
141155
}
142156

143-
if len(buf)%DirectioAlignSize != 0 {
157+
// In sync mode, we don't need to worry about alignment since we're not using O_DIRECT
158+
if !syncMode && len(buf)%DirectioAlignSize != 0 {
144159
// Disable O_DIRECT on fd's on unaligned buffer
145160
// perform an amortized Fdatasync(fd) on the fd at
146161
// the end, this is performed by the caller before
@@ -164,8 +179,9 @@ func copyAligned(w io.Writer, r io.Reader, alignedBuf []byte, totalSize int64, f
164179
)
165180

166181
remain := len(buf) % DirectioAlignSize
167-
if remain == 0 {
168-
// buf is aligned for directio write()
182+
// In sync mode, treat all buffers as "aligned" (no special handling needed)
183+
if syncMode || remain == 0 {
184+
// buf is aligned for directio write() or we're in sync mode
169185
n, err = w.Write(buf)
170186
nw = int64(n)
171187
} else {
@@ -225,12 +241,23 @@ func (d *DrivePerf) runWriteTest(ctx context.Context, path string, data []byte)
225241
}
226242

227243
startTime := time.Now()
228-
w, err := os.OpenFile(path, syscall.O_DIRECT|os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
244+
245+
// Choose flags based on sync mode
246+
var flags int
247+
if d.SyncMode {
248+
// Use O_DSYNC for synchronized writes (for small block sizes or when --sync is specified)
249+
flags = syscall.O_DSYNC | os.O_RDWR | os.O_CREATE | os.O_TRUNC
250+
} else {
251+
// Use O_DIRECT for direct I/O (bypasses page cache)
252+
flags = syscall.O_DIRECT | os.O_RDWR | os.O_CREATE | os.O_TRUNC
253+
}
254+
255+
w, err := os.OpenFile(path, flags, 0o600)
229256
if err != nil {
230257
return 0, err
231258
}
232259

233-
n, err := copyAligned(w, newRandomReader(ctx), data, int64(d.FileSize), w.Fd())
260+
n, err := copyAligned(w, newRandomReader(ctx), data, int64(d.FileSize), w.Fd(), d.SyncMode)
234261
if err != nil {
235262
w.Close()
236263
return 0, err

0 commit comments

Comments
 (0)