carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "fmt"
6 "log"
7 "os"
8 "path/filepath"
9 "strconv"
10 "time"
11
12 "github.com/carlosframework/platform/internal/lease"
13 "github.com/carlosframework/platform/internal/moves"
14)
15
16// measureDiskSynced is the disk measurement without O_DIRECT: write, fsync,
17// then read measureDiskSize. The read may be answered from the page cache,
18// so it overstates the read rate; it is the fallback where O_DIRECT is
19// unavailable (movesmeasure_other.go, and tmpfs on linux).
20func measureDiskSynced(dir string, now func() time.Time) (moves.MeasuredRate, error) {
21 path := filepath.Join(dir, "disk-synced")
22 f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0o600)
23 if err != nil {
24 return moves.MeasuredRate{}, err
25 }
26 defer f.Close()
27 buf := make([]byte, 1<<20)
28 start := now()
29 for n := 0; n < measureDiskSize; n += len(buf) {
30 if _, err := f.Write(buf); err != nil {
31 return moves.MeasuredRate{}, err
32 }
33 }
34 if err := f.Sync(); err != nil {
35 return moves.MeasuredRate{}, err
36 }
37 if _, err := f.Seek(0, 0); err != nil {
38 return moves.MeasuredRate{}, err
39 }
40 for n := 0; n < measureDiskSize; n += len(buf) {
41 if _, err := f.Read(buf); err != nil {
42 return moves.MeasuredRate{}, err
43 }
44 }
45 return moves.RateOf(2*measureDiskSize, now().Sub(start), now().UTC()), nil
46}
47
48// measureS3 times a 16 MiB put and get of the probe object (moves.ProbeKey,
49// outside moves/, F11) in this box's own bucket with its own credentials,
50// and deletes it.
51func measureS3(mirror func(ctx context.Context) (lease.MirrorStore, error), bucket string, now func() time.Time) func(context.Context, string) (moves.MeasuredRate, error) {
52 return func(ctx context.Context, key string) (moves.MeasuredRate, error) {
53 m, err := mirror(ctx)
54 if err != nil {
55 return moves.MeasuredRate{}, err
56 }
57 const size = 16 << 20
58 body := make([]byte, size)
59 start := now()
60 if err := m.Put(ctx, bucket, key, body); err != nil {
61 return moves.MeasuredRate{}, err
62 }
63 got, err := m.Get(ctx, bucket, key)
64 elapsed := now().Sub(start)
65 if derr := m.Delete(ctx, bucket, key); derr != nil {
66 log.Printf("moves: delete the S3 probe object %s: %v", key, derr)
67 }
68 if err != nil {
69 return moves.MeasuredRate{}, err
70 }
71 if len(got) != size {
72 return moves.MeasuredRate{}, fmt.Errorf("the probe object read back %d bytes of %d", len(got), size)
73 }
74 return moves.RateOf(2*size, elapsed, now().UTC()), nil
75 }
76}
77
78// measureDiskIn measures this box's disk under <dataDir>/.moves (the
79// platform-specific write and read are movesmeasure_*.go).
80func measureDiskIn(dataDir string, now func() time.Time) func(context.Context) (moves.MeasuredRate, moves.FilesRate, error) {
81 return func(ctx context.Context) (moves.MeasuredRate, moves.FilesRate, error) {
82 dir, err := os.MkdirTemp(filepath.Join(dataDir, ".moves"), "measure-")
83 if err != nil {
84 if err := os.MkdirAll(filepath.Join(dataDir, ".moves"), 0o755); err != nil {
85 return moves.MeasuredRate{}, moves.FilesRate{}, err
86 }
87 if dir, err = os.MkdirTemp(filepath.Join(dataDir, ".moves"), "measure-"); err != nil {
88 return moves.MeasuredRate{}, moves.FilesRate{}, err
89 }
90 }
91 defer os.RemoveAll(dir)
92 disk, err := measureDiskRate(dir, now)
93 if err != nil {
94 return moves.MeasuredRate{}, moves.FilesRate{}, err
95 }
96 files, err := measureFsyncRate(dir, now)
97 if err != nil {
98 return moves.MeasuredRate{}, moves.FilesRate{}, err
99 }
100 return disk, files, nil
101 }
102}
103
104// measureFsyncFiles and measureFsyncWorkers are the per-file sync probe:
105// a few hundred small files synced by the extract's 16-worker pattern
106// (ruling T7-budget).
107const (
108 measureFsyncFiles = 512
109 measureFsyncWorkers = 16
110)
111
112func measureFsyncRate(dir string, now func() time.Time) (moves.FilesRate, error) {
113 jobs := make(chan int)
114 errs := make(chan error, measureFsyncWorkers)
115 start := now()
116 for i := 0; i < measureFsyncWorkers; i++ {
117 go func() {
118 var first error
119 for n := range jobs {
120 if first != nil {
121 continue
122 }
123 f, err := os.OpenFile(filepath.Join(dir, "f"+strconv.Itoa(n)), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
124 if err == nil {
125 _, err = f.Write(make([]byte, 4096))
126 if err == nil {
127 err = f.Sync()
128 }
129 if cerr := f.Close(); err == nil {
130 err = cerr
131 }
132 }
133 first = err
134 }
135 errs <- first
136 }()
137 }
138 for n := 0; n < measureFsyncFiles; n++ {
139 jobs <- n
140 }
141 close(jobs)
142 for i := 0; i < measureFsyncWorkers; i++ {
143 if err := <-errs; err != nil {
144 return moves.FilesRate{}, err
145 }
146 }
147 d := now().Sub(start)
148 if d <= 0 {
149 d = time.Microsecond
150 }
151 return moves.FilesRate{PerS: measureFsyncFiles / d.Seconds(), At: now().UTC(), Files: measureFsyncFiles}, nil
152}
153