Skip to content

Commit 1e8ad32

Browse files
committed
initial
0 parents  commit 1e8ad32

File tree

6 files changed

+1555
-0
lines changed

6 files changed

+1555
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
project(autofsync)
2+
cmake_minimum_required(VERSION 3.6)
3+
4+
set(CMAKE_C_STANDARD 99)
5+
6+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
7+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=format")
8+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,--unresolved-symbols=report-all")
9+
10+
add_library(autofsync SHARED
11+
autofsync.c
12+
)
13+
14+
set_target_properties(autofsync PROPERTIES PREFIX "")
15+
target_link_libraries(autofsync PRIVATE ${CMAKE_DL_LIBS})

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
The MIT License (MIT)
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
autofsync
2+
=========
3+
4+
Intercepts `write()` call, and calls `fdatasync()` when certain amount of data
5+
were written to a file. Limit size is adjusted at run time to keep `fdatasync()`
6+
durations around predefined value. The goal is to express writeback cache size
7+
limit in seconds rather than in bytes.
8+
9+
Library is supposed to be injected into applications with the help of
10+
`LD_PRELOAD`.

autofsync.c

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
// Copyright © 2018 Rinat Ibragimov
2+
//
3+
// This file is part of autofsync.
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
#define _GNU_SOURCE
24+
25+
#include "uthash.h"
26+
#include <dlfcn.h>
27+
#include <fcntl.h>
28+
#include <stdarg.h>
29+
#include <stdbool.h>
30+
#include <stdio.h>
31+
#include <sys/stat.h>
32+
#include <sys/types.h>
33+
#include <time.h>
34+
#include <unistd.h>
35+
36+
static const size_t g_dirty_limit_initial = 1024 * 1024;
37+
static const size_t g_dirty_limit_low = 1024 * 1024;
38+
static const size_t g_dirty_limit_high = 2ull * 1024 * 1024 * 1024;
39+
40+
static const double g_target_latency_high = 1.1;
41+
static const double g_target_latency_low = 0.9;
42+
43+
#if 0
44+
#define LOG(fmt, ...) printf("autofsync: " fmt "\n", __VA_ARGS__)
45+
#define LOG_(fmt) printf("autofsync: " fmt "\n")
46+
#else
47+
#define LOG(...)
48+
#define LOG_(...)
49+
#endif
50+
51+
#define unlikely(x) __builtin_expect((x), 0)
52+
53+
#define WEAK_SYMBOL __attribute__((weak))
54+
55+
#define ensure_entry_points_initialized() \
56+
do { \
57+
if (unlikely(!real_entry_points_initialized)) \
58+
initialize_real_entry_points(); \
59+
} while (0)
60+
61+
#define get_mode() \
62+
({ \
63+
int mode; \
64+
if (__OPEN_NEEDS_MODE(oflag)) { \
65+
va_list a; \
66+
va_start(a, oflag); \
67+
mode = va_arg(a, int); \
68+
va_end(a); \
69+
} \
70+
mode; \
71+
})
72+
73+
struct file {
74+
int fd;
75+
size_t dirty;
76+
size_t dirty_limit;
77+
UT_hash_handle hh;
78+
};
79+
80+
static struct file *g_files = NULL;
81+
82+
static bool real_entry_points_initialized = false;
83+
84+
static int (*real_open)(const char *fname, int oflag, ...);
85+
static int (*real_open64)(const char *fname, int oflag, ...);
86+
static int (*real_openat)(int atfd, const char *fname, int oflag, ...);
87+
static int (*real_openat64)(int atfd, const char *fname, int oflag, ...);
88+
static int (*real_close)(int);
89+
static ssize_t (*real_write)(int, const void *, ssize_t);
90+
91+
static void
92+
initialize_real_entry_points(void)
93+
{
94+
real_open = dlsym(RTLD_NEXT, "open");
95+
real_open64 = dlsym(RTLD_NEXT, "open64");
96+
real_openat = dlsym(RTLD_NEXT, "openat");
97+
real_openat64 = dlsym(RTLD_NEXT, "openat64");
98+
real_write = dlsym(RTLD_NEXT, "write");
99+
real_close = dlsym(RTLD_NEXT, "close");
100+
101+
real_entry_points_initialized = true;
102+
}
103+
104+
static size_t
105+
align_4k(size_t sz)
106+
{
107+
return sz / 4096 * 4096;
108+
}
109+
110+
static void
111+
account_opened_fd(int fd)
112+
{
113+
struct stat sb;
114+
int ret = fstat(fd, &sb);
115+
if (ret != 0)
116+
return;
117+
118+
if (!S_ISREG(sb.st_mode))
119+
return;
120+
121+
struct file *new_file = calloc(sizeof(*new_file), 1);
122+
if (!new_file)
123+
return;
124+
125+
new_file->fd = fd;
126+
new_file->dirty_limit = g_dirty_limit_initial;
127+
new_file->dirty = 0;
128+
129+
struct file *old_file = NULL;
130+
HASH_REPLACE_INT(g_files, fd, new_file, old_file);
131+
132+
if (old_file != NULL) {
133+
LOG_(" unexpected old_file");
134+
free(old_file);
135+
}
136+
}
137+
138+
static int
139+
do_open(int (*open_func)(const char *fname, int oflag, ...), const char *fname,
140+
int oflag, int mode)
141+
142+
{
143+
int fd = open_func(fname, oflag, mode);
144+
if (fd == -1)
145+
return -1;
146+
147+
account_opened_fd(fd);
148+
return fd;
149+
}
150+
151+
static int
152+
do_openat(int (*open_func)(int atfd, const char *fname, int oflag, ...),
153+
int atfd, const char *fname, int oflag, int mode)
154+
155+
{
156+
int fd = open_func(atfd, fname, oflag, mode);
157+
if (fd == -1)
158+
return -1;
159+
160+
account_opened_fd(fd);
161+
return fd;
162+
}
163+
164+
WEAK_SYMBOL
165+
int
166+
open(const char *fname, int oflag, ...)
167+
{
168+
int mode = get_mode();
169+
LOG("open: fname=%s, oflag=%d, mode=%d", fname, oflag, mode);
170+
ensure_entry_points_initialized();
171+
return do_open(real_open, fname, oflag, mode);
172+
}
173+
174+
WEAK_SYMBOL
175+
int
176+
open64(const char *fname, int oflag, ...)
177+
{
178+
int mode = get_mode();
179+
LOG("open64: fname=%s, oflag=%d, mode=%d", fname, oflag, mode);
180+
ensure_entry_points_initialized();
181+
return do_open(real_open64, fname, oflag, mode);
182+
}
183+
184+
WEAK_SYMBOL
185+
int
186+
openat(int atfd, const char *fname, int oflag, ...)
187+
{
188+
int mode = get_mode();
189+
LOG("openat: atfd=%d, fname=%s, oflag=%d, mode=%d", atfd, fname, oflag,
190+
mode);
191+
ensure_entry_points_initialized();
192+
return do_openat(real_openat, atfd, fname, oflag, mode);
193+
}
194+
195+
WEAK_SYMBOL
196+
int
197+
openat64(int atfd, const char *fname, int oflag, ...)
198+
{
199+
int mode = get_mode();
200+
LOG("openat64: atfd=%d, fname=%s, oflag=%d, mode=%d", atfd, fname, oflag,
201+
mode);
202+
ensure_entry_points_initialized();
203+
return do_openat(real_openat64, atfd, fname, oflag, mode);
204+
}
205+
206+
WEAK_SYMBOL
207+
int
208+
close(int fd)
209+
{
210+
LOG("close: fd = %d", fd);
211+
ensure_entry_points_initialized();
212+
213+
struct file *a_file = NULL;
214+
HASH_FIND_INT(g_files, &fd, a_file);
215+
if (a_file) {
216+
HASH_DEL(g_files, a_file);
217+
free(a_file);
218+
219+
} else {
220+
LOG_(" mismatched close");
221+
}
222+
223+
return real_close(fd);
224+
}
225+
226+
static void
227+
write_throttle(int fd, ssize_t bytes_written)
228+
{
229+
struct file *a_file = NULL;
230+
HASH_FIND_INT(g_files, &fd, a_file);
231+
if (a_file == NULL)
232+
return;
233+
234+
a_file->dirty += bytes_written;
235+
LOG(" dirty = %zu, dirty_limit = %zu", a_file->dirty, a_file->dirty_limit);
236+
if (a_file->dirty < a_file->dirty_limit)
237+
return;
238+
239+
a_file->dirty = 0;
240+
struct timespec t1;
241+
if (clock_gettime(CLOCK_MONOTONIC, &t1) != 0)
242+
return;
243+
244+
if (fdatasync(fd) != 0)
245+
return;
246+
247+
struct timespec t2;
248+
if (clock_gettime(CLOCK_MONOTONIC, &t2) != 0)
249+
return;
250+
251+
double elapsed = t2.tv_sec - t1.tv_sec + (t2.tv_nsec - t1.tv_nsec) * 1e-9;
252+
LOG(" fdatasync took %f seconds", elapsed);
253+
254+
if (elapsed > g_target_latency_high) {
255+
double slowdown = elapsed / g_target_latency_high;
256+
LOG(" slowdown = %f", slowdown);
257+
if (slowdown > 2) {
258+
a_file->dirty_limit /= slowdown;
259+
260+
} else {
261+
a_file->dirty_limit *= 0.7;
262+
}
263+
264+
if (a_file->dirty_limit < g_dirty_limit_low)
265+
a_file->dirty_limit = g_dirty_limit_low;
266+
267+
a_file->dirty_limit = align_4k(a_file->dirty_limit);
268+
LOG(" decreasing dirty_limit to %zu", a_file->dirty_limit);
269+
270+
} else if (elapsed < g_target_latency_low) {
271+
a_file->dirty_limit *= 1.3;
272+
if (a_file->dirty_limit > g_dirty_limit_high)
273+
a_file->dirty_limit = g_dirty_limit_high;
274+
275+
a_file->dirty_limit = align_4k(a_file->dirty_limit);
276+
LOG(" increasing dirty_limit to %zu", a_file->dirty_limit);
277+
}
278+
}
279+
280+
WEAK_SYMBOL
281+
ssize_t
282+
write(int fd, const void *buf, size_t count)
283+
{
284+
LOG("write: fd = %d, buf = %p, count = %zu", fd, buf, count);
285+
ensure_entry_points_initialized();
286+
287+
const ssize_t bytes_written = real_write(fd, buf, count);
288+
if (bytes_written == -1)
289+
return -1;
290+
291+
write_throttle(fd, bytes_written);
292+
return bytes_written;
293+
}

0 commit comments

Comments
 (0)