mirror of
https://github.com/Zygo/bees.git
synced 2025-08-02 13:53:28 +02:00
Compare commits
130 Commits
fanotify-w
...
v0.5
Author | SHA1 | Date | |
---|---|---|---|
|
9d295fab4e | ||
|
dc7360397e | ||
|
305ab5dbfa | ||
|
80e4302958 | ||
|
07751885d2 | ||
|
77614a0e99 | ||
|
649ae5bb40 | ||
|
40112faf0f | ||
|
bfb768a079 | ||
|
71514e7229 | ||
|
78d04b1417 | ||
|
47805253e6 | ||
|
629e33b4f3 | ||
|
58157d03dd | ||
|
9d67329ef7 | ||
|
c6be07e158 | ||
|
c6bf6bfe1d | ||
|
29d2d51c47 | ||
|
893595190f | ||
|
0455827989 | ||
|
62626aef7f | ||
|
f59e311809 | ||
|
3bf4e69c4d | ||
|
5622ebd411 | ||
|
04cb25bd04 | ||
|
06b8fd8697 | ||
|
94ab477b90 | ||
|
cceb0480a5 | ||
|
23749eb634 | ||
|
5afbcb99e3 | ||
|
5275249396 | ||
|
a07728bc7e | ||
|
732896b471 | ||
|
5cc5a44661 | ||
|
f6a6992ac9 | ||
|
ceda8ee6c3 | ||
|
18ae15658e | ||
|
339579096f | ||
|
702a8eec8c | ||
|
5f18fcda52 | ||
|
088cbd24ff | ||
|
8c9a44998d | ||
|
a5e2bdff47 | ||
|
703bb7c1a3 | ||
|
4f66d1cb44 | ||
|
3901962379 | ||
|
48aac8a99a | ||
|
b0ba4c4f38 | ||
|
74d256f0fe | ||
|
8cde833863 | ||
|
e0951ed4ba | ||
|
c479b361cd | ||
|
c6c3990d19 | ||
|
3fdc217b4f | ||
|
6c8d2bf428 | ||
|
d6f97edf4a | ||
|
312254a47b | ||
|
5350b0f113 | ||
|
5a3f1be09e | ||
|
4b592ec2a3 | ||
|
dc00dce842 | ||
|
82b3ba76fa | ||
|
4113a171be | ||
|
5713fcd770 | ||
|
db8ea92133 | ||
|
6099bf0b01 | ||
|
c58e5cd75b | ||
|
123d4e83c5 | ||
|
5de3b15daa | ||
|
38fffa8e27 | ||
|
50417e961f | ||
|
6cc9b267ef | ||
|
9f120e326b | ||
|
382f8bf06a | ||
|
5e91529ad2 | ||
|
bddc07bd28 | ||
|
ffe2a767d3 | ||
|
845267821c | ||
|
3138002a1f | ||
|
4a57c5f499 | ||
|
bda4638048 | ||
|
fa8607bae0 | ||
|
1b261b1ba7 | ||
|
6980935463 | ||
|
cf04fb17de | ||
|
4a9f26d12e | ||
|
e8eaa7e471 | ||
|
22e601912e | ||
|
badfa6e9b9 | ||
|
03609f73db | ||
|
7f92f22dea | ||
|
f7c71a7a25 | ||
|
37713c2dd4 | ||
|
65a950bc41 | ||
|
ef8d92a3cb | ||
|
6e7137f282 | ||
|
c1e31004b6 | ||
|
7ecead1700 | ||
|
efda609f66 | ||
|
abd696c524 | ||
|
e835e8766e | ||
|
7782b79e4b | ||
|
d7c065e17e | ||
|
334f5f83ee | ||
|
8abdeabddc | ||
|
f5f4d69ba3 | ||
|
ec9d4a1d15 | ||
|
77c11bb90f | ||
|
b5c01c1985 | ||
|
d82909387d | ||
|
1cd6263552 | ||
|
eec80944cd | ||
|
5a4ff9a0b8 | ||
|
9506406cff | ||
|
1c4af5ce5a | ||
|
642581e89a | ||
|
fdfa78a81b | ||
|
6fa8de660b | ||
|
d58de9b76d | ||
|
ea0910ee6c | ||
|
dd21e6f848 | ||
|
06e111c229 | ||
|
606d48acc1 | ||
|
bf4e31ae71 | ||
|
03c116c3f1 | ||
|
a384cd976a | ||
|
38bb70f5d0 | ||
|
a57404442c | ||
|
1e621cf4e7 | ||
|
1303fb9da8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,3 +10,5 @@ html/
|
|||||||
latex/
|
latex/
|
||||||
make.log
|
make.log
|
||||||
make.log.new
|
make.log.new
|
||||||
|
localconf
|
||||||
|
scripts/beesd
|
||||||
|
43
Makefile
43
Makefile
@@ -1,19 +1,52 @@
|
|||||||
default install all: lib src test README.html
|
PREFIX ?= /
|
||||||
|
LIBEXEC_PREFIX ?= $(PREFIX)/usr/lib/bees
|
||||||
|
|
||||||
clean:
|
MARKDOWN := $(firstword $(shell which markdown markdown2 markdown_py 2>/dev/null))
|
||||||
|
MARKDOWN ?= markdown
|
||||||
|
|
||||||
|
# allow local configuration to override above variables
|
||||||
|
-include localconf
|
||||||
|
|
||||||
|
default all: lib src test README.html
|
||||||
|
|
||||||
|
clean: ## Cleanup
|
||||||
git clean -dfx
|
git clean -dfx
|
||||||
|
|
||||||
.PHONY: lib src
|
.PHONY: lib src test
|
||||||
|
|
||||||
lib:
|
lib: ## Build libs
|
||||||
$(MAKE) -C lib
|
$(MAKE) -C lib
|
||||||
|
|
||||||
|
src: ## Build bins
|
||||||
src: lib
|
src: lib
|
||||||
$(MAKE) -C src
|
$(MAKE) -C src
|
||||||
|
|
||||||
|
test: ## Run tests
|
||||||
test: lib src
|
test: lib src
|
||||||
$(MAKE) -C test
|
$(MAKE) -C test
|
||||||
|
|
||||||
|
scripts/beesd: scripts/beesd.in
|
||||||
|
sed -e's#@LIBEXEC_PREFIX@#$(LIBEXEC_PREFIX)#' -e's#@PREFIX@#$(PREFIX)#' "$<" >"$@"
|
||||||
|
|
||||||
|
scripts/beesd@.service: scripts/beesd@.service.in
|
||||||
|
sed -e's#@LIBEXEC_PREFIX@#$(LIBEXEC_PREFIX)#' -e's#@PREFIX@#$(PREFIX)#' "$<" >"$@"
|
||||||
|
|
||||||
|
scripts: scripts/beesd scripts/beesd@.service
|
||||||
|
|
||||||
README.html: README.md
|
README.html: README.md
|
||||||
markdown README.md > README.html.new
|
$(MARKDOWN) README.md > README.html.new
|
||||||
mv -f README.html.new README.html
|
mv -f README.html.new README.html
|
||||||
|
|
||||||
|
install: ## Install bees + libs
|
||||||
|
install: lib src test
|
||||||
|
install -Dm644 lib/libcrucible.so $(PREFIX)/usr/lib/libcrucible.so
|
||||||
|
install -Dm755 bin/bees $(LIBEXEC_PREFIX)/bees
|
||||||
|
|
||||||
|
install_scripts: ## Install scipts
|
||||||
|
install_scripts:
|
||||||
|
install -Dm755 scripts/beesd $(PREFIX)/usr/sbin/beesd
|
||||||
|
install -Dm644 scripts/beesd.conf.sample $(PREFIX)/etc/bees/beesd.conf.sample
|
||||||
|
install -Dm644 scripts/beesd@.service $(PREFIX)/lib/systemd/system/beesd@.service
|
||||||
|
|
||||||
|
help: ## Show help
|
||||||
|
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##/\t/'
|
||||||
|
327
README.md
327
README.md
@@ -1,30 +1,52 @@
|
|||||||
BEES
|
BEES
|
||||||
====
|
====
|
||||||
|
|
||||||
Best-Effort Extent-Same, a btrfs deduplication daemon.
|
Best-Effort Extent-Same, a btrfs dedup agent.
|
||||||
|
|
||||||
About Bees
|
About Bees
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Bees is a daemon designed to run continuously on live file servers.
|
Bees is a block-oriented userspace dedup agent designed to avoid
|
||||||
Bees scans and deduplicates whole filesystems in a single pass instead
|
scalability problems on large filesystems.
|
||||||
of separate scan and dedup phases. RAM usage does _not_ depend on
|
|
||||||
unique data size or the number of input files. Hash tables and scan
|
|
||||||
progress are stored persistently so the daemon can resume after a reboot.
|
|
||||||
Bees uses the Linux kernel's `dedupe_file_range` feature to ensure data
|
|
||||||
is handled safely even if other applications concurrently modify it.
|
|
||||||
|
|
||||||
Bees is intentionally btrfs-specific for performance and capability.
|
Bees is designed to degrade gracefully when underprovisioned with RAM.
|
||||||
Bees uses the btrfs `SEARCH_V2` ioctl to scan for new data without the
|
Bees does not use more RAM or storage as filesystem data size increases.
|
||||||
overhead of repeatedly walking filesystem trees with the POSIX API.
|
The dedup hash table size is fixed at creation time and does not change.
|
||||||
Bees uses `LOGICAL_INO` and `INO_PATHS` to leverage btrfs's existing
|
The effective dedup block size is dynamic and adjusts automatically to
|
||||||
metadata instead of building its own redundant data structures.
|
fit the hash table into the configured RAM limit. Hash table overflow
|
||||||
Bees can cope with Btrfs filesystem compression. Bees can reassemble
|
is not implemented to eliminate the IO overhead of hash table overflow.
|
||||||
Btrfs extents to deduplicate extents that contain a mix of duplicate
|
Hash table entries are only 16 bytes per dedup block to keep the average
|
||||||
and unique data blocks.
|
dedup block size small.
|
||||||
|
|
||||||
Bees includes a number of workarounds for Btrfs kernel bugs to (try to)
|
Bees does not require alignment between dedup blocks or extent boundaries
|
||||||
avoid ruining your day. You're welcome.
|
(i.e. it can handle any multiple-of-4K offset between dup block pairs).
|
||||||
|
Bees rearranges blocks into shared and unique extents if required to
|
||||||
|
work within current btrfs kernel dedup limitations.
|
||||||
|
|
||||||
|
Bees can dedup any combination of compressed and uncompressed extents.
|
||||||
|
|
||||||
|
Bees operates in a single pass which removes duplicate extents immediately
|
||||||
|
during scan. There are no separate scanning and dedup phases.
|
||||||
|
|
||||||
|
Bees uses only data-safe btrfs kernel operations, so it can dedup live
|
||||||
|
data (e.g. build servers, sqlite databases, VM disk images). It does
|
||||||
|
not modify file attributes or timestamps.
|
||||||
|
|
||||||
|
Bees does not store any information about filesystem structure, so it is
|
||||||
|
not affected by the number or size of files (except to the extent that
|
||||||
|
these cause performance problems for btrfs in general). It retrieves such
|
||||||
|
information on demand through btrfs SEARCH_V2 and LOGICAL_INO ioctls.
|
||||||
|
This eliminates the storage required to maintain the equivalents of
|
||||||
|
these functions in userspace. It's also why bees has no XFS support.
|
||||||
|
|
||||||
|
Bees is a daemon designed to run continuously and maintain its state
|
||||||
|
across crahes and reboots. Bees uses checkpoints for persistence to
|
||||||
|
eliminate the IO overhead of a transactional data store. On restart,
|
||||||
|
bees will dedup any data that was added to the filesystem since the
|
||||||
|
last checkpoint.
|
||||||
|
|
||||||
|
Bees is used to dedup filesystems ranging in size from 16GB to 35TB, with
|
||||||
|
hash tables ranging in size from 128MB to 11GB.
|
||||||
|
|
||||||
How Bees Works
|
How Bees Works
|
||||||
--------------
|
--------------
|
||||||
@@ -78,18 +100,16 @@ and some metadata bits). Each entry represents a minimum of 4K on disk.
|
|||||||
1TB 16MB 1024K
|
1TB 16MB 1024K
|
||||||
64TB 1GB 1024K
|
64TB 1GB 1024K
|
||||||
|
|
||||||
It is possible to resize the hash table by changing the size of
|
To change the size of the hash table, use 'truncate' to change the hash
|
||||||
`beeshash.dat` (e.g. with `truncate`) and restarting `bees`. This
|
table size, delete `beescrawl.dat` so that bees will start over with a
|
||||||
does not preserve all the existing hash table entries, but it does
|
fresh full-filesystem rescan, and restart `bees'.
|
||||||
preserve more than zero of them--especially if the old and new sizes
|
|
||||||
are a power-of-two multiple of each other.
|
|
||||||
|
|
||||||
Things You Might Expect That Bees Doesn't Have
|
Things You Might Expect That Bees Doesn't Have
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
* There's no configuration file or getopt command line option processing
|
* There's no configuration file (patches welcome!). There are some tunables
|
||||||
(patches welcome!). There are some tunables hardcoded in the source
|
hardcoded in the source that could eventually become configuration options.
|
||||||
that could eventually become configuration options.
|
There's also an incomplete option parser (patches welcome!).
|
||||||
|
|
||||||
* There's no way to *stop* the Bees daemon. Use SIGKILL, SIGTERM, or
|
* There's no way to *stop* the Bees daemon. Use SIGKILL, SIGTERM, or
|
||||||
Ctrl-C for now. Some of the destructors are unreachable and have never
|
Ctrl-C for now. Some of the destructors are unreachable and have never
|
||||||
@@ -114,11 +134,6 @@ performance by caching, but really fixing this requires rewriting the
|
|||||||
crawler to scan the btrfs extent tree directly instead of the subvol
|
crawler to scan the btrfs extent tree directly instead of the subvol
|
||||||
FS trees.
|
FS trees.
|
||||||
|
|
||||||
* Bees had support for multiple worker threads in the past; however,
|
|
||||||
this was removed because it made Bees too aggressive to coexist with
|
|
||||||
other applications on the same machine. It also hit the *slow backrefs*
|
|
||||||
on N CPU cores instead of just one.
|
|
||||||
|
|
||||||
* Block reads are currently more allocation- and CPU-intensive than they
|
* Block reads are currently more allocation- and CPU-intensive than they
|
||||||
should be, especially for filesystems on SSD where the IO overhead is
|
should be, especially for filesystems on SSD where the IO overhead is
|
||||||
much smaller. This is a problem for power-constrained environments
|
much smaller. This is a problem for power-constrained environments
|
||||||
@@ -129,6 +144,9 @@ blocks, but has no defragmentation capability yet. When possible, Bees
|
|||||||
will attempt to work with existing extent boundaries, but it will not
|
will attempt to work with existing extent boundaries, but it will not
|
||||||
aggregate blocks together from multiple extents to create larger ones.
|
aggregate blocks together from multiple extents to create larger ones.
|
||||||
|
|
||||||
|
* It is possible to resize the hash table without starting over with
|
||||||
|
a new full-filesystem scan; however, this has not been implemented yet.
|
||||||
|
|
||||||
Good Btrfs Feature Interactions
|
Good Btrfs Feature Interactions
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
@@ -144,11 +162,11 @@ Bees has been tested in combination with the following:
|
|||||||
* IO errors during dedup (read errors will throw exceptions, Bees will catch them and skip over the affected extent)
|
* IO errors during dedup (read errors will throw exceptions, Bees will catch them and skip over the affected extent)
|
||||||
* Filesystems mounted *with* the flushoncommit option
|
* Filesystems mounted *with* the flushoncommit option
|
||||||
* 4K filesystem data block size / clone alignment
|
* 4K filesystem data block size / clone alignment
|
||||||
* 64-bit CPUs (amd64)
|
* 64-bit and 32-bit host CPUs (amd64, x86, arm)
|
||||||
* Large (>16M) extents
|
* Large (>16M) extents
|
||||||
* Huge files (>1TB--although Btrfs performance on such files isn't great in general)
|
* Huge files (>1TB--although Btrfs performance on such files isn't great in general)
|
||||||
* filesystems up to 25T bytes, 100M+ files
|
* filesystems up to 25T bytes, 100M+ files
|
||||||
|
* btrfs read-only snapshots
|
||||||
|
|
||||||
Bad Btrfs Feature Interactions
|
Bad Btrfs Feature Interactions
|
||||||
------------------------------
|
------------------------------
|
||||||
@@ -156,16 +174,14 @@ Bad Btrfs Feature Interactions
|
|||||||
Bees has not been tested with the following, and undesirable interactions may occur:
|
Bees has not been tested with the following, and undesirable interactions may occur:
|
||||||
|
|
||||||
* Non-4K filesystem data block size (should work if recompiled)
|
* Non-4K filesystem data block size (should work if recompiled)
|
||||||
* 32-bit CPUs (x86, arm)
|
|
||||||
* Non-equal hash (SUM) and filesystem data block (CLONE) sizes (probably never will work)
|
* Non-equal hash (SUM) and filesystem data block (CLONE) sizes (probably never will work)
|
||||||
* btrfs read-only snapshots (never tested, probably wouldn't work well)
|
* btrfs send/receive (receive is probably OK, but send could be confused?)
|
||||||
* btrfs send/receive (receive is probably OK, but send requires RO snapshots. See above)
|
|
||||||
* btrfs qgroups (never tested, no idea what might happen)
|
* btrfs qgroups (never tested, no idea what might happen)
|
||||||
* btrfs seed filesystems (does anyone even use those?)
|
* btrfs seed filesystems (does anyone even use those?)
|
||||||
* btrfs autodefrag mount option (never tested, could fight with Bees)
|
* btrfs autodefrag mount option (never tested, could fight with Bees)
|
||||||
* btrfs nodatacow mount option or inode attribute (*could* work, but might not)
|
* btrfs nodatacow/nodatasum inode attribute or mount option (bees skips all nodatasum files)
|
||||||
* btrfs out-of-tree kernel patches (e.g. in-band dedup or encryption)
|
* btrfs out-of-tree kernel patches (e.g. in-band dedup or encryption)
|
||||||
* btrfs-convert from ext2/3/4 (never tested)
|
* btrfs-convert from ext2/3/4 (never tested, might run out of space or ignore significant portions of the filesystem due to sanity checks)
|
||||||
* btrfs mixed block groups (don't know a reason why it would *not* work, but never tested)
|
* btrfs mixed block groups (don't know a reason why it would *not* work, but never tested)
|
||||||
* open(O_DIRECT)
|
* open(O_DIRECT)
|
||||||
* Filesystems mounted *without* the flushoncommit option
|
* Filesystems mounted *without* the flushoncommit option
|
||||||
@@ -173,7 +189,7 @@ Bees has not been tested with the following, and undesirable interactions may oc
|
|||||||
Other Caveats
|
Other Caveats
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
* btrfs balance will invalidate parts of the dedup table. Bees will
|
* btrfs balance will invalidate parts of the dedup hash table. Bees will
|
||||||
happily rebuild the table, but it will have to scan all the blocks
|
happily rebuild the table, but it will have to scan all the blocks
|
||||||
again.
|
again.
|
||||||
|
|
||||||
@@ -184,41 +200,79 @@ Other Caveats
|
|||||||
|
|
||||||
* Bees creates temporary files (with O_TMPFILE) and uses them to split
|
* Bees creates temporary files (with O_TMPFILE) and uses them to split
|
||||||
and combine extents elsewhere in btrfs. These will take up to 2GB
|
and combine extents elsewhere in btrfs. These will take up to 2GB
|
||||||
during normal operation.
|
of disk space per thread during normal operation.
|
||||||
|
|
||||||
* Like all deduplicators, Bees will replace data blocks with metadata
|
* Like all deduplicators, Bees will replace data blocks with metadata
|
||||||
references. It is a good idea to ensure there are several GB of
|
references. It is a good idea to ensure there is sufficient unallocated
|
||||||
unallocated space (see `btrfs fi df`) on the filesystem before running
|
space (see `btrfs fi usage`) on the filesystem to allow the metadata
|
||||||
Bees for the first time. Use
|
to multiply in size by the number of snapshots before running Bees
|
||||||
|
for the first time. Use
|
||||||
|
|
||||||
btrfs balance start -dusage=100,limit=1 /your/filesystem
|
btrfs balance start -dusage=100,limit=N /your/filesystem
|
||||||
|
|
||||||
If possible, raise the `limit` parameter to the current size of metadata
|
where the `limit` parameter 'N' should be calculated as follows:
|
||||||
usage (from `btrfs fi df`) plus 1.
|
|
||||||
|
* start with the current size of metadata usage (from `btrfs fi
|
||||||
|
df`) in GB, plus 1
|
||||||
|
|
||||||
|
* multiply by the proportion of disk space in subvols with
|
||||||
|
snapshots (i.e. if there are no snapshots, multiply by 0;
|
||||||
|
if all of the data is shared between at least one origin
|
||||||
|
and one snapshot subvol, multiply by 1)
|
||||||
|
|
||||||
|
* multiply by the number of snapshots (i.e. if there is only
|
||||||
|
one subvol, multiply by 0; if there are 3 snapshots and one
|
||||||
|
origin subvol, multiply by 3)
|
||||||
|
|
||||||
|
`limit = GB_metadata * (disk_space_in_snapshots / total_disk_space) * number_of_snapshots`
|
||||||
|
|
||||||
|
Monitor unallocated space to ensure that the filesystem never runs out
|
||||||
|
of metadata space (whether Bees is running or not--this is a general
|
||||||
|
btrfs requirement).
|
||||||
|
|
||||||
|
|
||||||
A Brief List Of Btrfs Kernel Bugs
|
A Brief List Of Btrfs Kernel Bugs
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Fixed bugs:
|
Missing features (usually not available in older LTS kernels):
|
||||||
|
|
||||||
* 3.13: `FILE_EXTENT_SAME` ioctl added. No way to reliably dedup with
|
* 3.13: `FILE_EXTENT_SAME` ioctl added. No way to reliably dedup with
|
||||||
concurrent modifications before this.
|
concurrent modifications before this.
|
||||||
* 3.16: `SEARCH_V2` ioctl added. Bees could use `SEARCH` instead.
|
* 3.16: `SEARCH_V2` ioctl added. Bees could use `SEARCH` instead.
|
||||||
* 4.2: `FILE_EXTENT_SAME` no longer updates mtime, can be used at EOF.
|
* 4.2: `FILE_EXTENT_SAME` no longer updates mtime, can be used at EOF.
|
||||||
Kernel deadlock bugs fixed.
|
|
||||||
|
Future features (kernel features Bees does not yet use, but may rely on
|
||||||
|
in the future):
|
||||||
|
|
||||||
|
* 4.14: `LOGICAL_INO_V2` allows userspace to create forward and backward
|
||||||
|
reference maps to entire physical extents with a single ioctl call,
|
||||||
|
and raises the limit of 2730 references per extent. Bees has not yet
|
||||||
|
been rewritten to take full advantage of these features.
|
||||||
|
|
||||||
|
Bug fixes (sometimes included in older LTS kernels):
|
||||||
|
|
||||||
|
* Bugs fixed prior to 4.4.3 are not listed here.
|
||||||
|
* 4.5: hang in the `INO_PATHS` ioctl used by Bees.
|
||||||
|
* 4.5: use-after-free in the `FILE_EXTENT_SAME` ioctl used by Bees.
|
||||||
|
* 4.6: lost inodes after a rename, crash, and log tree replay
|
||||||
|
(triggered by the fsync() while writing `beescrawl.dat`).
|
||||||
* 4.7: *slow backref* bug no longer triggers a softlockup panic. It still
|
* 4.7: *slow backref* bug no longer triggers a softlockup panic. It still
|
||||||
too long to resolve a block address to a root/inode/offset triple.
|
takes too long to resolve a block address to a root/inode/offset triple.
|
||||||
|
* 4.10: reduced CPU time cost of the LOGICAL_INO ioctl and dedup
|
||||||
|
backref processing in general.
|
||||||
|
* 4.11: yet another dedup deadlock case is fixed.
|
||||||
|
* 4.14: backref performance improvements make LOGICAL_INO even faster.
|
||||||
|
|
||||||
Unfixed kernel bugs (as of 4.5.7) with workarounds in Bees:
|
Unfixed kernel bugs (as of 4.11.9) with workarounds in Bees:
|
||||||
|
|
||||||
* *slow backref*: If the number of references to a single shared extent
|
* *slow backrefs* (aka toxic extents): If the number of references to a
|
||||||
within a single file grows above a few thousand, the kernel consumes CPU
|
single shared extent within a single file grows above a few thousand,
|
||||||
for up to 40 uninterruptible minutes while holding various locks that
|
the kernel consumes CPU for minutes at a time while holding various
|
||||||
block access to the filesystem. Bees avoids this bug by measuring the
|
locks that block access to the filesystem. Bees avoids this bug by
|
||||||
time the kernel spends performing certain operations and permanently
|
measuring the time the kernel spends performing certain operations
|
||||||
blacklisting any extent or hash where the kernel starts to get slow.
|
and permanently blacklisting any extent or hash where the kernel
|
||||||
Inside Bees, such blocks are marked as 'toxic' hash/block addresses.
|
starts to get slow. Inside Bees, such blocks are marked as 'toxic'
|
||||||
|
hash/block addresses. *Needs to be retested after v4.14.*
|
||||||
|
|
||||||
* `LOGICAL_INO` output is arbitrarily limited to 2730 references
|
* `LOGICAL_INO` output is arbitrarily limited to 2730 references
|
||||||
even if more buffer space is provided for results. Once this number
|
even if more buffer space is provided for results. Once this number
|
||||||
@@ -229,32 +283,29 @@ Unfixed kernel bugs (as of 4.5.7) with workarounds in Bees:
|
|||||||
This places an obvious limit on dedup efficiency for extremely common
|
This places an obvious limit on dedup efficiency for extremely common
|
||||||
blocks or filesystems with many snapshots (although this limit is
|
blocks or filesystems with many snapshots (although this limit is
|
||||||
far greater than the effective limit imposed by the *slow backref* bug).
|
far greater than the effective limit imposed by the *slow backref* bug).
|
||||||
|
*Fixed in v4.14.*
|
||||||
|
|
||||||
|
* `LOGICAL_INO` on compressed extents returns a list of root/inode/offset
|
||||||
|
tuples matching the extent bytenr of its argument. On uncompressed
|
||||||
|
extents, any r/i/o tuple whose extent offset does not match the
|
||||||
|
argument's extent offset is discarded, i.e. only the single 4K block
|
||||||
|
matching the argument is returned, so a complete map of the extent
|
||||||
|
references requires calling `LOGICAL_INO` for every single block of
|
||||||
|
the extent. This is undesirable behavior for Bees, which wants a
|
||||||
|
list of all extent refs referencing a data extent (i.e. Bees wants
|
||||||
|
the compressed-extent behavior in all cases). *Fixed in v4.14.*
|
||||||
|
|
||||||
|
* `LOGICAL_INO` is only called from one thread at any time per process.
|
||||||
|
This means at most one core is irretrievably stuck in this ioctl.
|
||||||
|
|
||||||
* `FILE_EXTENT_SAME` is arbitrarily limited to 16MB. This is less than
|
* `FILE_EXTENT_SAME` is arbitrarily limited to 16MB. This is less than
|
||||||
128MB which is the maximum extent size that can be created by defrag
|
128MB which is the maximum extent size that can be created by defrag
|
||||||
or prealloc. Bees avoids feedback loops this can generate while
|
or prealloc. Bees avoids feedback loops this can generate while
|
||||||
attempting to replace extents over 16MB in length.
|
attempting to replace extents over 16MB in length.
|
||||||
|
|
||||||
* `DEFRAG_RANGE` is useless. The ioctl attempts to implement `btrfs
|
* If the `fsync()` in `BeesTempFile::make_copy` is removed, the filesystem
|
||||||
fi defrag` in the kernel, and will arbitrarily defragment more or
|
hangs within a few hours, requiring a reboot to recover. On the other
|
||||||
less than the range requested to match the behavior expected from the
|
hand, the `fsync()` only costs about 8% of overall performance.
|
||||||
userspace tool. Bees implements its own defrag instead, copying data
|
|
||||||
to a temporary file and using the `FILE_EXTENT_SAME` ioctl to replace
|
|
||||||
precisely the specified range of offending fragmented blocks.
|
|
||||||
|
|
||||||
* When writing BeesStringFile, a crash can cause the directory entry
|
|
||||||
`beescrawl.UUID.dat.tmp` to exist without a corresponding inode.
|
|
||||||
This directory entry cannot be renamed or removed; however, it does
|
|
||||||
not prevent the creation of a second directory entry with the same
|
|
||||||
name that functions normally, so it doesn't prevent Bees operation.
|
|
||||||
|
|
||||||
The orphan directory entry can be removed by deleting its subvol,
|
|
||||||
so place BEESHOME on a separate subvol so you can delete these orphan
|
|
||||||
directory entries when they occur (or use btrfs zero-log before mounting
|
|
||||||
the filesystem after a crash).
|
|
||||||
|
|
||||||
* If the fsync() BeesTempFile::make_copy is removed, the filesystem
|
|
||||||
hangs within a few hours, requiring a reboot to recover.
|
|
||||||
|
|
||||||
Not really a bug, but a gotcha nonetheless:
|
Not really a bug, but a gotcha nonetheless:
|
||||||
|
|
||||||
@@ -265,14 +316,32 @@ Not really a bug, but a gotcha nonetheless:
|
|||||||
children* until the FD is closed. Bees avoids this gotcha by closing
|
children* until the FD is closed. Bees avoids this gotcha by closing
|
||||||
all of the FDs in its directory FD cache every 15 minutes.
|
all of the FDs in its directory FD cache every 15 minutes.
|
||||||
|
|
||||||
|
* If a file is deleted while Bees is caching an open FD to the file,
|
||||||
|
Bees continues to scan the file. For very large files (e.g. VM
|
||||||
|
images), the deletion of the file can be delayed indefinitely.
|
||||||
|
To limit this delay, Bees closes all FDs in its file FD cache every
|
||||||
|
15 minutes.
|
||||||
|
|
||||||
|
Build
|
||||||
|
-----
|
||||||
|
|
||||||
Requirements
|
Build with `make`. The build produces `bin/bees` and `lib/libcrucible.so`,
|
||||||
|
which must be copied to somewhere in `$PATH` and `$LD_LIBRARY_PATH`
|
||||||
|
on the target system respectively.
|
||||||
|
|
||||||
|
### Ubuntu 16.04 - 17.04:
|
||||||
|
`$ apt -y install build-essential btrfs-tools uuid-dev markdown && make`
|
||||||
|
|
||||||
|
### Ubuntu 14.04:
|
||||||
|
You can try to carry on the work done here: https://gist.github.com/dagelf/99ee07f5638b346adb8c058ab3d57492
|
||||||
|
|
||||||
|
Dependencies
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* C++11 compiler (tested with GCC 4.9)
|
* C++11 compiler (tested with GCC 4.9 and 6.2.0)
|
||||||
|
|
||||||
Sorry. I really like closures.
|
Sorry. I really like closures and shared_ptr, so support
|
||||||
|
for earlier compiler versions is unlikely.
|
||||||
|
|
||||||
* btrfs-progs (tested with 4.1..4.7)
|
* btrfs-progs (tested with 4.1..4.7)
|
||||||
|
|
||||||
@@ -281,30 +350,16 @@ Requirements
|
|||||||
|
|
||||||
* libuuid-dev
|
* libuuid-dev
|
||||||
|
|
||||||
TODO: remove the one function used from this library.
|
This library is only required for a feature that was removed after v0.1.
|
||||||
It supports a feature Bees no longer implements.
|
The lingering support code can be removed.
|
||||||
|
|
||||||
* Linux kernel 4.2 or later
|
* Linux kernel 4.4.3 or later
|
||||||
|
|
||||||
Don't bother trying to make Bees work with older kernels.
|
Don't bother trying to make Bees work with older kernels.
|
||||||
It won't end well.
|
It won't end well.
|
||||||
|
|
||||||
* 64-bit host and target CPU
|
* markdown
|
||||||
|
|
||||||
This code has never been tested on a 32-bit target CPU.
|
|
||||||
|
|
||||||
A 64-bit host CPU may be required for the self-tests.
|
|
||||||
Some of the ioctls don't work properly with a 64-bit
|
|
||||||
kernel and 32-bit userspace.
|
|
||||||
|
|
||||||
Build
|
|
||||||
-----
|
|
||||||
|
|
||||||
Build with `make`.
|
|
||||||
|
|
||||||
The build produces `bin/bees` and `lib/libcrucible.so`, which must be
|
|
||||||
copied to somewhere in `$PATH` and `$LD_LIBRARY_PATH` on the target
|
|
||||||
system respectively.
|
|
||||||
|
|
||||||
Setup
|
Setup
|
||||||
-----
|
-----
|
||||||
@@ -320,17 +375,49 @@ of 16M). This example creates a 1GB hash table:
|
|||||||
truncate -s 1g "$BEESHOME/beeshash.dat"
|
truncate -s 1g "$BEESHOME/beeshash.dat"
|
||||||
chmod 700 "$BEESHOME/beeshash.dat"
|
chmod 700 "$BEESHOME/beeshash.dat"
|
||||||
|
|
||||||
|
bees can only process the root subvol of a btrfs (seriously--if the
|
||||||
|
argument is not the root subvol directory, Bees will just throw an
|
||||||
|
exception and stop).
|
||||||
|
|
||||||
|
Use a bind mount, and let only bees access it:
|
||||||
|
|
||||||
|
UUID=3399e413-695a-4b0b-9384-1b0ef8f6c4cd
|
||||||
|
mkdir -p /var/lib/bees/$UUID
|
||||||
|
mount /dev/disk/by-uuid/$UUID /var/lib/bees/$UUID -osubvol=/
|
||||||
|
|
||||||
|
If you don't set BEESHOME, the path ".beeshome" will be used relative
|
||||||
|
to the root subvol of the filesystem. For example:
|
||||||
|
|
||||||
|
btrfs sub create /var/lib/bees/$UUID/.beeshome
|
||||||
|
truncate -s 1g /var/lib/bees/$UUID/.beeshome/beeshash.dat
|
||||||
|
chmod 700 /var/lib/bees/$UUID/.beeshome/beeshash.dat
|
||||||
|
|
||||||
|
You can use any relative path in BEESHOME. The path will be taken
|
||||||
|
relative to the root of the deduped filesystem (in other words it can
|
||||||
|
be the name of a subvol):
|
||||||
|
|
||||||
|
export BEESHOME=@my-beeshome
|
||||||
|
btrfs sub create /var/lib/bees/$UUID/$BEESHOME
|
||||||
|
truncate -s 1g /var/lib/bees/$UUID/$BEESHOME/beeshash.dat
|
||||||
|
chmod 700 /var/lib/bees/$UUID/$BEESHOME/beeshash.dat
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The only runtime configurable options are environment variables:
|
The only runtime configurable options are environment variables:
|
||||||
|
|
||||||
* BEESHOME: Directory containing Bees state files:
|
* BEESHOME: Directory containing Bees state files:
|
||||||
* beeshash.dat | persistent hash table (must be a multiple of 16M)
|
* beeshash.dat | persistent hash table. Must be a multiple of 16M.
|
||||||
* beescrawl.`UUID`.dat | state of SEARCH_V2 crawlers
|
This contains 16-byte records: 8 bytes for CRC64,
|
||||||
* beesstats.txt | statistics and performance counters
|
8 bytes for physical address and some metadata bits.
|
||||||
* BEESSTATS: File containing a snapshot of current Bees state (performance
|
* beescrawl.dat | state of SEARCH_V2 crawlers. ASCII text.
|
||||||
counters and current status of each thread).
|
* beesstats.txt | statistics and performance counters. ASCII text.
|
||||||
|
* BEESSTATUS: File containing a snapshot of current Bees state: performance
|
||||||
|
counters and current status of each thread. The file is meant to be
|
||||||
|
human readable, but understanding it probably requires reading the source.
|
||||||
|
You can watch bees run in realtime with a command like:
|
||||||
|
|
||||||
|
watch -n1 cat $BEESSTATUS
|
||||||
|
|
||||||
Other options (e.g. interval between filesystem crawls) can be configured
|
Other options (e.g. interval between filesystem crawls) can be configured
|
||||||
in src/bees.h.
|
in src/bees.h.
|
||||||
@@ -338,39 +425,27 @@ in src/bees.h.
|
|||||||
Running
|
Running
|
||||||
-------
|
-------
|
||||||
|
|
||||||
We created this directory in the previous section:
|
Reduce CPU and IO priority to be kinder to other applications sharing
|
||||||
|
this host (or raise them for more aggressive disk space recovery). If you
|
||||||
export BEESHOME=/some/path
|
use cgroups, put `bees` in its own cgroup, then reduce the `blkio.weight`
|
||||||
|
and `cpu.shares` parameters. You can also use `schedtool` and `ionice`
|
||||||
Use a tmpfs for BEESSTATUS, it updates once per second:
|
in the shell script that launches `bees`:
|
||||||
|
|
||||||
export BEESSTATUS=/run/bees.status
|
|
||||||
|
|
||||||
bees can only process the root subvol of a btrfs (seriously--if the
|
|
||||||
argument is not the root subvol directory, Bees will just throw an
|
|
||||||
exception and stop).
|
|
||||||
|
|
||||||
Use a bind mount, and let only bees access it:
|
|
||||||
|
|
||||||
mount -osubvol=/ /dev/<your-filesystem> /var/lib/bees/root
|
|
||||||
|
|
||||||
Reduce CPU and IO priority to be kinder to other applications
|
|
||||||
sharing this host (or raise them for more aggressive disk space
|
|
||||||
recovery). If you use cgroups, put `bees` in its own cgroup, then reduce
|
|
||||||
the `blkio.weight` and `cpu.shares` parameters. You can also use
|
|
||||||
`schedtool` and `ionice` in the shell script that launches `bees`:
|
|
||||||
|
|
||||||
schedtool -D -n20 $$
|
schedtool -D -n20 $$
|
||||||
ionice -c3 -p $$
|
ionice -c3 -p $$
|
||||||
|
|
||||||
Let the bees fly:
|
Let the bees fly:
|
||||||
|
|
||||||
bees /var/lib/bees/root >> /var/log/bees.log 2>&1
|
for fs in /var/lib/bees/*-*-*-*-*/; do
|
||||||
|
bees "$fs" >> "$fs/.beeshome/bees.log" 2>&1 &
|
||||||
|
done
|
||||||
|
|
||||||
You'll probably want to arrange for /var/log/bees.log to be rotated
|
You'll probably want to arrange for /var/log/bees.log to be rotated
|
||||||
periodically. You may also want to set umask to 077 to prevent disclosure
|
periodically. You may also want to set umask to 077 to prevent disclosure
|
||||||
of information about the contents of the filesystem through the log file.
|
of information about the contents of the filesystem through the log file.
|
||||||
|
|
||||||
|
There are also some shell wrappers in the `scripts/` directory.
|
||||||
|
|
||||||
|
|
||||||
Bug Reports and Contributions
|
Bug Reports and Contributions
|
||||||
-----------------------------
|
-----------------------------
|
||||||
@@ -386,6 +461,6 @@ You can also use Github:
|
|||||||
Copyright & License
|
Copyright & License
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Copyright 2015-2016 Zygo Blaxell <bees@furryterror.org>.
|
Copyright 2015-2017 Zygo Blaxell <bees@furryterror.org>.
|
||||||
|
|
||||||
GPL (version 3 or later).
|
GPL (version 3 or later).
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
#ifndef CRUCIBLE_BOOL_H
|
|
||||||
#define CRUCIBLE_BOOL_H
|
|
||||||
|
|
||||||
namespace crucible {
|
|
||||||
struct DefaultBool {
|
|
||||||
bool m_b;
|
|
||||||
DefaultBool(bool init = false) : m_b(init) {}
|
|
||||||
operator bool() const { return m_b; }
|
|
||||||
bool &operator=(const bool &that) { return m_b = that; }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // CRUCIBLE_BOOL_H
|
|
@@ -130,7 +130,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef BTRFS_IOC_CLONE_RANGE
|
#ifndef BTRFS_IOC_CLONE_RANGE
|
||||||
|
|
||||||
struct btrfs_ioctl_clone_range_args {
|
struct btrfs_ioctl_clone_range_args {
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace crucible {
|
namespace crucible {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -17,7 +18,7 @@ namespace crucible {
|
|||||||
public:
|
public:
|
||||||
using Key = tuple<Arguments...>;
|
using Key = tuple<Arguments...>;
|
||||||
using Func = function<Return(Arguments...)>;
|
using Func = function<Return(Arguments...)>;
|
||||||
using Time = unsigned;
|
using Time = size_t;
|
||||||
using Value = pair<Time, Return>;
|
using Value = pair<Time, Return>;
|
||||||
private:
|
private:
|
||||||
Func m_fn;
|
Func m_fn;
|
||||||
@@ -27,7 +28,7 @@ namespace crucible {
|
|||||||
size_t m_max_size;
|
size_t m_max_size;
|
||||||
mutex m_mutex;
|
mutex m_mutex;
|
||||||
|
|
||||||
void check_overflow();
|
bool check_overflow();
|
||||||
public:
|
public:
|
||||||
LRUCache(Func f = Func(), size_t max_size = 100);
|
LRUCache(Func f = Func(), size_t max_size = 100);
|
||||||
|
|
||||||
@@ -51,21 +52,24 @@ namespace crucible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class Return, class... Arguments>
|
template <class Return, class... Arguments>
|
||||||
void
|
bool
|
||||||
LRUCache<Return, Arguments...>::check_overflow()
|
LRUCache<Return, Arguments...>::check_overflow()
|
||||||
{
|
{
|
||||||
if (m_map.size() <= m_max_size) return;
|
if (m_map.size() <= m_max_size) {
|
||||||
vector<pair<Key, Time>> map_contents;
|
return false;
|
||||||
map_contents.reserve(m_map.size());
|
|
||||||
for (auto i : m_map) {
|
|
||||||
map_contents.push_back(make_pair(i.first, i.second.first));
|
|
||||||
}
|
}
|
||||||
sort(map_contents.begin(), map_contents.end(), [](const pair<Key, Time> &a, const pair<Key, Time> &b) {
|
vector<pair<Key, Time>> key_times;
|
||||||
|
key_times.reserve(m_map.size());
|
||||||
|
for (auto i : m_map) {
|
||||||
|
key_times.push_back(make_pair(i.first, i.second.first));
|
||||||
|
}
|
||||||
|
sort(key_times.begin(), key_times.end(), [](const pair<Key, Time> &a, const pair<Key, Time> &b) {
|
||||||
return a.second < b.second;
|
return a.second < b.second;
|
||||||
});
|
});
|
||||||
for (size_t i = 0; i < map_contents.size() / 2; ++i) {
|
for (size_t i = 0; i < key_times.size() / 2; ++i) {
|
||||||
m_map.erase(map_contents[i].first);
|
m_map.erase(key_times[i].first);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Return, class... Arguments>
|
template <class Return, class... Arguments>
|
||||||
@@ -120,7 +124,7 @@ namespace crucible {
|
|||||||
if (found == m_map.end()) {
|
if (found == m_map.end()) {
|
||||||
// No, release cache lock and acquire key lock
|
// No, release cache lock and acquire key lock
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
typename LockSet<Key>::Lock key_lock(m_lockset, k);
|
auto key_lock = m_lockset.make_lock(k);
|
||||||
|
|
||||||
// Did item appear in cache while we were waiting for key?
|
// Did item appear in cache while we were waiting for key?
|
||||||
lock.lock();
|
lock.lock();
|
||||||
@@ -140,9 +144,14 @@ namespace crucible {
|
|||||||
// We hold a lock on this key so we are the ones to insert it
|
// We hold a lock on this key so we are the ones to insert it
|
||||||
THROW_CHECK0(runtime_error, inserted);
|
THROW_CHECK0(runtime_error, inserted);
|
||||||
|
|
||||||
// Release key lock and clean out overflow
|
// Release key lock, keep the cache lock
|
||||||
key_lock.unlock();
|
key_lock.unlock();
|
||||||
check_overflow();
|
|
||||||
|
// Check to see if we have too many items and reduce if so.
|
||||||
|
if (check_overflow()) {
|
||||||
|
// Reset iterator
|
||||||
|
found = m_map.find(k);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +162,9 @@ namespace crucible {
|
|||||||
if (!inserted) {
|
if (!inserted) {
|
||||||
found->second.first = m_ctr++;
|
found->second.first = m_ctr++;
|
||||||
}
|
}
|
||||||
return found->second.second;
|
// Make copy before releasing lock
|
||||||
|
auto rv = found->second.second;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Return, class... Arguments>
|
template<class Return, class... Arguments>
|
||||||
@@ -186,7 +197,7 @@ namespace crucible {
|
|||||||
if (found == m_map.end()) {
|
if (found == m_map.end()) {
|
||||||
// No, release cache lock and acquire key lock
|
// No, release cache lock and acquire key lock
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
typename LockSet<Key>::Lock key_lock(m_lockset, k);
|
auto key_lock = m_lockset.make_lock(k);
|
||||||
|
|
||||||
// Did item appear in cache while we were waiting for key?
|
// Did item appear in cache while we were waiting for key?
|
||||||
lock.lock();
|
lock.lock();
|
||||||
@@ -204,7 +215,12 @@ namespace crucible {
|
|||||||
|
|
||||||
// Release key lock and clean out overflow
|
// Release key lock and clean out overflow
|
||||||
key_lock.unlock();
|
key_lock.unlock();
|
||||||
check_overflow();
|
|
||||||
|
// Check to see if we have too many items and reduce if so.
|
||||||
|
if (check_overflow()) {
|
||||||
|
// Reset iterator
|
||||||
|
found = m_map.find(k);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -45,6 +45,8 @@ namespace crucible {
|
|||||||
template <class T> Chatter &operator<<(const T& arg);
|
template <class T> Chatter &operator<<(const T& arg);
|
||||||
|
|
||||||
~Chatter();
|
~Chatter();
|
||||||
|
|
||||||
|
static void enable_timestamp(bool prefix_timestamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class Argument>
|
template <class Argument>
|
||||||
@@ -86,16 +88,6 @@ namespace crucible {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
|
||||||
struct ChatterTraits<ostream &> {
|
|
||||||
Chatter &
|
|
||||||
operator()(Chatter &c, ostream & arg)
|
|
||||||
{
|
|
||||||
c.get_os() << arg;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ChatterBox {
|
class ChatterBox {
|
||||||
string m_file;
|
string m_file;
|
||||||
int m_line;
|
int m_line;
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace crucible {
|
namespace crucible {
|
||||||
namespace Digest {
|
namespace Digest {
|
||||||
namespace CRC {
|
namespace CRC {
|
||||||
uint64_t crc64(const char *s);
|
|
||||||
uint64_t crc64(const void *p, size_t len);
|
uint64_t crc64(const void *p, size_t len);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -100,12 +100,6 @@ namespace crucible {
|
|||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
// macros for checking a constraint
|
// macros for checking a constraint
|
||||||
#define CHECK_CONSTRAINT(value, expr) do { \
|
|
||||||
if (!(expr)) { \
|
|
||||||
THROW_ERROR(out_of_range, #value << " = " << value << " failed constraint check (" << #expr << ")"); \
|
|
||||||
} \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define THROW_CHECK0(type, expr) do { \
|
#define THROW_CHECK0(type, expr) do { \
|
||||||
if (!(expr)) { \
|
if (!(expr)) { \
|
||||||
THROW_ERROR(type, "failed constraint check (" << #expr << ")"); \
|
THROW_ERROR(type, "failed constraint check (" << #expr << ")"); \
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
#ifndef CRUCIBLE_EXECPIPE_H
|
|
||||||
#define CRUCIBLE_EXECPIPE_H
|
|
||||||
|
|
||||||
#include "crucible/fd.h"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <limits>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace crucible {
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
void redirect_stdin(const Fd &child_fd);
|
|
||||||
void redirect_stdin_stdout(const Fd &child_fd);
|
|
||||||
void redirect_stdin_stdout_stderr(const Fd &child_fd);
|
|
||||||
void redirect_stdout(const Fd &child_fd);
|
|
||||||
void redirect_stdout_stderr(const Fd &child_fd);
|
|
||||||
|
|
||||||
// Open a pipe (actually socketpair) to child process, then execute code in that process.
|
|
||||||
// e.g. popen([] () { system("echo Hello, World!"); });
|
|
||||||
// Forked process will exit when function returns.
|
|
||||||
Fd popen(function<int()> f, function<void(const Fd &child_fd)> import_fd_fn = redirect_stdin_stdout);
|
|
||||||
|
|
||||||
// Read all the data from fd into a string
|
|
||||||
string read_all(Fd fd, size_t max_bytes = numeric_limits<size_t>::max(), size_t chunk_bytes = 4096);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // CRUCIBLE_EXECPIPE_H
|
|
@@ -8,15 +8,15 @@ namespace crucible {
|
|||||||
|
|
||||||
// FIXME: ExtentCursor is probably a better name
|
// FIXME: ExtentCursor is probably a better name
|
||||||
struct Extent {
|
struct Extent {
|
||||||
off_t m_begin;
|
off_t m_begin = 0;
|
||||||
off_t m_end;
|
off_t m_end = 0;
|
||||||
uint64_t m_physical;
|
uint64_t m_physical = 0;
|
||||||
uint64_t m_flags;
|
uint64_t m_flags = 0;
|
||||||
|
|
||||||
// Btrfs extent reference details
|
// Btrfs extent reference details
|
||||||
off_t m_physical_len;
|
off_t m_physical_len = 0;
|
||||||
off_t m_logical_len;
|
off_t m_logical_len = 0;
|
||||||
off_t m_offset;
|
off_t m_offset = 0;
|
||||||
|
|
||||||
// fiemap flags are uint32_t, so bits 32..63 are OK for us
|
// fiemap flags are uint32_t, so bits 32..63 are OK for us
|
||||||
|
|
||||||
@@ -38,10 +38,12 @@ namespace crucible {
|
|||||||
off_t physical_len() const { return m_physical_len; }
|
off_t physical_len() const { return m_physical_len; }
|
||||||
off_t logical_len() const { return m_logical_len; }
|
off_t logical_len() const { return m_logical_len; }
|
||||||
off_t offset() const { return m_offset; }
|
off_t offset() const { return m_offset; }
|
||||||
|
bool compressed() const;
|
||||||
|
uint64_t bytenr() const;
|
||||||
bool operator==(const Extent &that) const;
|
bool operator==(const Extent &that) const;
|
||||||
bool operator!=(const Extent &that) const { return !(*this == that); }
|
bool operator!=(const Extent &that) const { return !(*this == that); }
|
||||||
|
|
||||||
Extent();
|
Extent() = default;
|
||||||
Extent(const Extent &e) = default;
|
Extent(const Extent &e) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -13,6 +13,10 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
// ioctl
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
|
||||||
// socket
|
// socket
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
|
||||||
@@ -70,10 +74,11 @@ namespace crucible {
|
|||||||
string mmap_flags_ntoa(int flags);
|
string mmap_flags_ntoa(int flags);
|
||||||
|
|
||||||
// Unlink, rename
|
// Unlink, rename
|
||||||
void unlink_or_die(const string &file);
|
|
||||||
void rename_or_die(const string &from, const string &to);
|
void rename_or_die(const string &from, const string &to);
|
||||||
void renameat_or_die(int fromfd, const string &frompath, int tofd, const string &topath);
|
void renameat_or_die(int fromfd, const string &frompath, int tofd, const string &topath);
|
||||||
|
|
||||||
|
void ftruncate_or_die(int fd, off_t size);
|
||||||
|
|
||||||
// Read or write structs:
|
// Read or write structs:
|
||||||
// There is a template specialization to read or write strings
|
// There is a template specialization to read or write strings
|
||||||
// Three-arg version of read_or_die/write_or_die throws an error on incomplete read/writes
|
// Three-arg version of read_or_die/write_or_die throws an error on incomplete read/writes
|
||||||
@@ -120,6 +125,9 @@ namespace crucible {
|
|||||||
template<> void pread_or_die<string>(int fd, string& str, off_t offset);
|
template<> void pread_or_die<string>(int fd, string& str, off_t offset);
|
||||||
template<> void pread_or_die<vector<char>>(int fd, vector<char>& str, off_t offset);
|
template<> void pread_or_die<vector<char>>(int fd, vector<char>& str, off_t offset);
|
||||||
template<> void pread_or_die<vector<uint8_t>>(int fd, vector<uint8_t>& str, off_t offset);
|
template<> void pread_or_die<vector<uint8_t>>(int fd, vector<uint8_t>& str, off_t offset);
|
||||||
|
template<> void pwrite_or_die<string>(int fd, const string& str, off_t offset);
|
||||||
|
template<> void pwrite_or_die<vector<char>>(int fd, const vector<char>& str, off_t offset);
|
||||||
|
template<> void pwrite_or_die<vector<uint8_t>>(int fd, const vector<uint8_t>& str, off_t offset);
|
||||||
|
|
||||||
// A different approach to reading a simple string
|
// A different approach to reading a simple string
|
||||||
string read_string(int fd, size_t size);
|
string read_string(int fd, size_t size);
|
||||||
@@ -137,6 +145,9 @@ namespace crucible {
|
|||||||
Stat &lstat(const string &filename);
|
Stat &lstat(const string &filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int ioctl_iflags_get(int fd);
|
||||||
|
void ioctl_iflags_set(int fd, int attr);
|
||||||
|
|
||||||
string st_mode_ntoa(mode_t mode);
|
string st_mode_ntoa(mode_t mode);
|
||||||
|
|
||||||
// Because it's not trivial to do correctly
|
// Because it's not trivial to do correctly
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -111,8 +112,8 @@ namespace crucible {
|
|||||||
BTRFS_COMPRESS_NONE = 0,
|
BTRFS_COMPRESS_NONE = 0,
|
||||||
BTRFS_COMPRESS_ZLIB = 1,
|
BTRFS_COMPRESS_ZLIB = 1,
|
||||||
BTRFS_COMPRESS_LZO = 2,
|
BTRFS_COMPRESS_LZO = 2,
|
||||||
BTRFS_COMPRESS_TYPES = 2,
|
BTRFS_COMPRESS_ZSTD = 3,
|
||||||
BTRFS_COMPRESS_LAST = 3,
|
BTRFS_COMPRESS_TYPES = 3
|
||||||
} btrfs_compression_type;
|
} btrfs_compression_type;
|
||||||
|
|
||||||
struct FiemapExtent : public fiemap_extent {
|
struct FiemapExtent : public fiemap_extent {
|
||||||
@@ -150,13 +151,14 @@ namespace crucible {
|
|||||||
BtrfsIoctlSearchHeader();
|
BtrfsIoctlSearchHeader();
|
||||||
vector<char> m_data;
|
vector<char> m_data;
|
||||||
size_t set_data(const vector<char> &v, size_t offset);
|
size_t set_data(const vector<char> &v, size_t offset);
|
||||||
|
bool operator<(const BtrfsIoctlSearchHeader &that) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
ostream & operator<<(ostream &os, const btrfs_ioctl_search_header &hdr);
|
ostream & operator<<(ostream &os, const btrfs_ioctl_search_header &hdr);
|
||||||
ostream & operator<<(ostream &os, const BtrfsIoctlSearchHeader &hdr);
|
ostream & operator<<(ostream &os, const BtrfsIoctlSearchHeader &hdr);
|
||||||
|
|
||||||
struct BtrfsIoctlSearchKey : public btrfs_ioctl_search_key {
|
struct BtrfsIoctlSearchKey : public btrfs_ioctl_search_key {
|
||||||
BtrfsIoctlSearchKey(size_t buf_size = 1024 * 1024);
|
BtrfsIoctlSearchKey(size_t buf_size = 4096);
|
||||||
virtual bool do_ioctl_nothrow(int fd);
|
virtual bool do_ioctl_nothrow(int fd);
|
||||||
virtual void do_ioctl(int fd);
|
virtual void do_ioctl(int fd);
|
||||||
|
|
||||||
@@ -164,14 +166,15 @@ namespace crucible {
|
|||||||
void next_min(const BtrfsIoctlSearchHeader& ref);
|
void next_min(const BtrfsIoctlSearchHeader& ref);
|
||||||
|
|
||||||
size_t m_buf_size;
|
size_t m_buf_size;
|
||||||
vector<BtrfsIoctlSearchHeader> m_result;
|
set<BtrfsIoctlSearchHeader> m_result;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ostream & operator<<(ostream &os, const btrfs_ioctl_search_key &key);
|
ostream & operator<<(ostream &os, const btrfs_ioctl_search_key &key);
|
||||||
ostream & operator<<(ostream &os, const BtrfsIoctlSearchKey &key);
|
ostream & operator<<(ostream &os, const BtrfsIoctlSearchKey &key);
|
||||||
|
|
||||||
string btrfs_search_type_ntoa(unsigned type);
|
string btrfs_search_type_ntoa(unsigned type);
|
||||||
string btrfs_search_objectid_ntoa(unsigned objectid);
|
string btrfs_search_objectid_ntoa(uint64_t objectid);
|
||||||
|
|
||||||
uint64_t btrfs_get_root_id(int fd);
|
uint64_t btrfs_get_root_id(int fd);
|
||||||
uint64_t btrfs_get_root_transid(int fd);
|
uint64_t btrfs_get_root_transid(int fd);
|
||||||
|
@@ -1,106 +0,0 @@
|
|||||||
#ifndef CRUCIBLE_INTERP_H
|
|
||||||
#define CRUCIBLE_INTERP_H
|
|
||||||
|
|
||||||
#include "crucible/error.h"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace crucible {
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
struct ArgList : public vector<string> {
|
|
||||||
ArgList(const char **argv);
|
|
||||||
// using vector<string>::vector ... doesn't work:
|
|
||||||
// error: ‘std::vector<std::basic_string<char> >::vector’ names constructor
|
|
||||||
// Still doesn't work in 4.9 because it can't manage a conversion
|
|
||||||
ArgList(const vector<string> &&that);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ArgActor {
|
|
||||||
struct ArgActorBase {
|
|
||||||
virtual void predicate(void *obj, string arg);
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
struct ArgActorDerived {
|
|
||||||
function<void(T, string)> m_func;
|
|
||||||
|
|
||||||
ArgActorDerived(decltype(m_func) func) :
|
|
||||||
m_func(func)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void predicate(void *obj, string arg) override
|
|
||||||
{
|
|
||||||
T &op = *(reinterpret_cast<T*>(obj));
|
|
||||||
m_func(op, obj);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
ArgActor(T, function<void(T, string)> func) :
|
|
||||||
m_actor(make_shared(ArgActorDerived<T>(func)))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgActor() = default;
|
|
||||||
|
|
||||||
void predicate(void *t, string arg)
|
|
||||||
{
|
|
||||||
if (m_actor) {
|
|
||||||
m_actor->predicate(t, arg);
|
|
||||||
} else {
|
|
||||||
THROW_ERROR(invalid_argument, "null m_actor for predicate arg '" << arg << "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
shared_ptr<ArgActorBase> m_actor;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ArgParser {
|
|
||||||
~ArgParser();
|
|
||||||
ArgParser();
|
|
||||||
|
|
||||||
void add_opt(string opt, ArgActor actor);
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
void
|
|
||||||
parse(T t, const ArgList &args)
|
|
||||||
{
|
|
||||||
void *vt = &t;
|
|
||||||
parse_backend(vt, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void parse_backend(void *t, const ArgList &args);
|
|
||||||
map<string, ArgActor> m_string_opts;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Command {
|
|
||||||
virtual ~Command();
|
|
||||||
virtual int exec(const ArgList &args) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Proc : public Command {
|
|
||||||
int exec(const ArgList &args) override;
|
|
||||||
Proc(const function<int(const ArgList &)> &f);
|
|
||||||
private:
|
|
||||||
function<int(const ArgList &)> m_cmd;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Interp {
|
|
||||||
virtual ~Interp();
|
|
||||||
Interp(const map<string, shared_ptr<Command> > &cmdlist);
|
|
||||||
void add_command(const string &name, const shared_ptr<Command> &command);
|
|
||||||
int exec(const ArgList &args);
|
|
||||||
private:
|
|
||||||
Interp(const Interp &) = delete;
|
|
||||||
map<string, shared_ptr<Command> > m_commands;
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
#endif // CRUCIBLE_INTERP_H
|
|
@@ -2,13 +2,16 @@
|
|||||||
#define CRUCIBLE_LOCKSET_H
|
#define CRUCIBLE_LOCKSET_H
|
||||||
|
|
||||||
#include <crucible/error.h>
|
#include <crucible/error.h>
|
||||||
|
#include <crucible/process.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
|
||||||
|
|
||||||
namespace crucible {
|
namespace crucible {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -17,14 +20,36 @@ namespace crucible {
|
|||||||
class LockSet {
|
class LockSet {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using key_type = T;
|
using set_type = map<T, pid_t>;
|
||||||
using set_type = set<T>;
|
using key_type = typename set_type::key_type;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
set_type m_set;
|
set_type m_set;
|
||||||
mutex m_mutex;
|
mutex m_mutex;
|
||||||
condition_variable m_condvar;
|
condition_variable m_condvar;
|
||||||
|
size_t m_max_size = numeric_limits<size_t>::max();
|
||||||
|
|
||||||
|
bool full();
|
||||||
|
bool locked(const key_type &name);
|
||||||
|
|
||||||
|
class Lock {
|
||||||
|
LockSet &m_lockset;
|
||||||
|
key_type m_name;
|
||||||
|
bool m_locked;
|
||||||
|
|
||||||
|
Lock() = delete;
|
||||||
|
Lock(const Lock &) = delete;
|
||||||
|
Lock& operator=(const Lock &) = delete;
|
||||||
|
Lock(Lock &&that) = delete;
|
||||||
|
Lock& operator=(Lock &&that) = delete;
|
||||||
|
public:
|
||||||
|
~Lock();
|
||||||
|
Lock(LockSet &lockset, const key_type &name, bool start_locked = true);
|
||||||
|
void lock();
|
||||||
|
void unlock();
|
||||||
|
bool try_lock();
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~LockSet();
|
~LockSet();
|
||||||
@@ -38,24 +63,20 @@ namespace crucible {
|
|||||||
set_type copy();
|
set_type copy();
|
||||||
void wait_unlock(double interval);
|
void wait_unlock(double interval);
|
||||||
|
|
||||||
class Lock {
|
void max_size(size_t max);
|
||||||
LockSet &m_lockset;
|
|
||||||
key_type m_name;
|
class LockHandle {
|
||||||
bool m_locked;
|
shared_ptr<Lock> m_lock;
|
||||||
|
|
||||||
Lock() = delete;
|
|
||||||
Lock(const Lock &) = delete;
|
|
||||||
Lock& operator=(const Lock &) = delete;
|
|
||||||
public:
|
public:
|
||||||
~Lock();
|
LockHandle(LockSet &lockset, const key_type &name, bool start_locked = true) :
|
||||||
Lock(LockSet &lockset, const key_type &m_name, bool start_locked = true);
|
m_lock(make_shared<Lock>(lockset, name, start_locked)) {}
|
||||||
Lock(Lock &&that);
|
void lock() { m_lock->lock(); }
|
||||||
Lock& operator=(Lock &&that);
|
void unlock() { m_lock->unlock(); }
|
||||||
void lock();
|
bool try_lock() { return m_lock->try_lock(); }
|
||||||
void unlock();
|
|
||||||
bool try_lock();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LockHandle make_lock(const key_type &name, bool start_locked = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@@ -68,15 +89,36 @@ namespace crucible {
|
|||||||
assert(m_set.empty());
|
assert(m_set.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool
|
||||||
|
LockSet<T>::full()
|
||||||
|
{
|
||||||
|
return m_set.size() >= m_max_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool
|
||||||
|
LockSet<T>::locked(const key_type &name)
|
||||||
|
{
|
||||||
|
return m_set.count(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void
|
||||||
|
LockSet<T>::max_size(size_t s)
|
||||||
|
{
|
||||||
|
m_max_size = s;
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void
|
void
|
||||||
LockSet<T>::lock(const key_type &name)
|
LockSet<T>::lock(const key_type &name)
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
while (m_set.count(name)) {
|
while (full() || locked(name)) {
|
||||||
m_condvar.wait(lock);
|
m_condvar.wait(lock);
|
||||||
}
|
}
|
||||||
auto rv = m_set.insert(name);
|
auto rv = m_set.insert(make_pair(name, gettid()));
|
||||||
THROW_CHECK0(runtime_error, rv.second);
|
THROW_CHECK0(runtime_error, rv.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,10 +127,10 @@ namespace crucible {
|
|||||||
LockSet<T>::try_lock(const key_type &name)
|
LockSet<T>::try_lock(const key_type &name)
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
if (m_set.count(name)) {
|
if (full() || locked(name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto rv = m_set.insert(name);
|
auto rv = m_set.insert(make_pair(name, gettid()));
|
||||||
THROW_CHECK1(runtime_error, name, rv.second);
|
THROW_CHECK1(runtime_error, name, rv.second);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -98,8 +140,8 @@ namespace crucible {
|
|||||||
LockSet<T>::unlock(const key_type &name)
|
LockSet<T>::unlock(const key_type &name)
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
m_condvar.notify_all();
|
|
||||||
auto erase_count = m_set.erase(name);
|
auto erase_count = m_set.erase(name);
|
||||||
|
m_condvar.notify_all();
|
||||||
THROW_CHECK1(invalid_argument, erase_count, erase_count == 1);
|
THROW_CHECK1(invalid_argument, erase_count, erase_count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +175,10 @@ namespace crucible {
|
|||||||
LockSet<T>::copy()
|
LockSet<T>::copy()
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
return m_set;
|
// Make temporary copy of set while protected by mutex
|
||||||
|
auto rv = m_set;
|
||||||
|
// Return temporary copy after releasing lock
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@@ -183,26 +228,10 @@ namespace crucible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
LockSet<T>::Lock::Lock(Lock &&that) :
|
typename LockSet<T>::LockHandle
|
||||||
m_lockset(that.lockset),
|
LockSet<T>::make_lock(const key_type &name, bool start_locked)
|
||||||
m_name(that.m_name),
|
|
||||||
m_locked(that.m_locked)
|
|
||||||
{
|
{
|
||||||
that.m_locked = false;
|
return LockHandle(*this, name, start_locked);
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
typename LockSet<T>::Lock &
|
|
||||||
LockSet<T>::Lock::operator=(Lock &&that)
|
|
||||||
{
|
|
||||||
THROW_CHECK2(invalid_argument, &m_lockset, &that.m_lockset, &m_lockset == &that.m_lockset);
|
|
||||||
if (m_locked && that.m_name != m_name) {
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
m_name = that.m_name;
|
|
||||||
m_locked = that.m_locked;
|
|
||||||
that.m_locked = false;
|
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,12 @@ namespace crucible {
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
struct bits_ntoa_table {
|
struct bits_ntoa_table {
|
||||||
unsigned long n;
|
unsigned long long n;
|
||||||
unsigned long mask;
|
unsigned long long mask;
|
||||||
const char *a;
|
const char *a;
|
||||||
};
|
};
|
||||||
|
|
||||||
string bits_ntoa(unsigned long n, const bits_ntoa_table *a);
|
string bits_ntoa(unsigned long long n, const bits_ntoa_table *a);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
#include "crucible/error.h"
|
#include "crucible/error.h"
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace crucible {
|
namespace crucible {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -44,36 +44,30 @@ namespace crucible {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
using traits_type = ResourceTraits<Key, Resource>;
|
using traits_type = ResourceTraits<Key, Resource>;
|
||||||
|
using weak_ptr_type = weak_ptr<Resource>;
|
||||||
class ResourceHolder {
|
using map_type = map<key_type, weak_ptr_type>;
|
||||||
resource_ptr_type m_ptr;
|
|
||||||
public:
|
|
||||||
~ResourceHolder();
|
|
||||||
ResourceHolder(resource_ptr_type that);
|
|
||||||
ResourceHolder(const ResourceHolder &that) = default;
|
|
||||||
ResourceHolder(ResourceHolder &&that) = default;
|
|
||||||
ResourceHolder& operator=(ResourceHolder &&that) = default;
|
|
||||||
ResourceHolder& operator=(const ResourceHolder &that) = default;
|
|
||||||
resource_ptr_type get_resource_ptr() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
using holder_ptr_type = shared_ptr<ResourceHolder>;
|
|
||||||
using weak_holder_ptr_type = weak_ptr<ResourceHolder>;
|
|
||||||
using map_type = map<key_type, weak_holder_ptr_type>;
|
|
||||||
|
|
||||||
// The only instance variable
|
// The only instance variable
|
||||||
holder_ptr_type m_ptr;
|
resource_ptr_type m_ptr;
|
||||||
|
|
||||||
// A bunch of static variables and functions
|
// A bunch of static variables and functions
|
||||||
static mutex &s_mutex();
|
static mutex s_map_mutex;
|
||||||
static shared_ptr<map_type> s_map();
|
static mutex s_ptr_mutex;
|
||||||
static holder_ptr_type insert(const key_type &key);
|
static map_type s_map;
|
||||||
static holder_ptr_type insert(const resource_ptr_type &res);
|
static resource_ptr_type insert(const key_type &key);
|
||||||
static void erase(const key_type &key);
|
static resource_ptr_type insert(const resource_ptr_type &res);
|
||||||
|
static void clean_locked();
|
||||||
static ResourceTraits<Key, Resource> s_traits;
|
static ResourceTraits<Key, Resource> s_traits;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
// Exceptions
|
||||||
|
struct duplicate_resource : public invalid_argument {
|
||||||
|
key_type m_key;
|
||||||
|
key_type get_key() const;
|
||||||
|
duplicate_resource(const key_type &key);
|
||||||
|
};
|
||||||
|
|
||||||
// test for resource. A separate operator because key_type could be confused with bool.
|
// test for resource. A separate operator because key_type could be confused with bool.
|
||||||
bool operator!() const;
|
bool operator!() const;
|
||||||
|
|
||||||
@@ -89,9 +83,16 @@ namespace crucible {
|
|||||||
ResourceHandle(const resource_ptr_type &res);
|
ResourceHandle(const resource_ptr_type &res);
|
||||||
ResourceHandle& operator=(const resource_ptr_type &res);
|
ResourceHandle& operator=(const resource_ptr_type &res);
|
||||||
|
|
||||||
// default constructor is public
|
// default constructor is public and mostly harmless
|
||||||
ResourceHandle() = default;
|
ResourceHandle() = default;
|
||||||
|
|
||||||
|
// copy/assign/move/move-assign - with a mutex to help shared_ptr be atomic
|
||||||
|
ResourceHandle(const ResourceHandle &that);
|
||||||
|
ResourceHandle(ResourceHandle &&that);
|
||||||
|
ResourceHandle& operator=(const ResourceHandle &that);
|
||||||
|
ResourceHandle& operator=(ResourceHandle &&that);
|
||||||
|
~ResourceHandle();
|
||||||
|
|
||||||
// forward anything else to the Resource constructor
|
// forward anything else to the Resource constructor
|
||||||
// if we can do so unambiguously
|
// if we can do so unambiguously
|
||||||
template<class A1, class A2, class... Args>
|
template<class A1, class A2, class... Args>
|
||||||
@@ -109,7 +110,7 @@ namespace crucible {
|
|||||||
|
|
||||||
// get pointer to Resource object (nothrow, result may be null)
|
// get pointer to Resource object (nothrow, result may be null)
|
||||||
resource_ptr_type get_resource_ptr() const;
|
resource_ptr_type get_resource_ptr() const;
|
||||||
// this version throws and is probably not thread safe
|
// this version throws
|
||||||
resource_ptr_type operator->() const;
|
resource_ptr_type operator->() const;
|
||||||
|
|
||||||
// dynamic casting of the resource (throws if cast fails)
|
// dynamic casting of the resource (throws if cast fails)
|
||||||
@@ -145,144 +146,100 @@ namespace crucible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
ResourceHandle<Key, Resource>::ResourceHolder::ResourceHolder(resource_ptr_type that) :
|
ResourceHandle<Key, Resource>::duplicate_resource::duplicate_resource(const key_type &key) :
|
||||||
m_ptr(that)
|
invalid_argument("duplicate resource"),
|
||||||
|
m_key(key)
|
||||||
{
|
{
|
||||||
// Cannot insert ourselves here since our shared_ptr does not exist yet.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
mutex &
|
auto
|
||||||
ResourceHandle<Key, Resource>::s_mutex()
|
ResourceHandle<Key, Resource>::duplicate_resource::get_key() const -> key_type
|
||||||
{
|
{
|
||||||
static mutex gcc_won_t_instantiate_this_either;
|
return m_key;
|
||||||
return gcc_won_t_instantiate_this_either;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Key, class Resource>
|
|
||||||
shared_ptr<typename ResourceHandle<Key, Resource>::map_type>
|
|
||||||
ResourceHandle<Key, Resource>::s_map()
|
|
||||||
{
|
|
||||||
static shared_ptr<map_type> gcc_won_t_instantiate_the_damn_static_vars;
|
|
||||||
if (!gcc_won_t_instantiate_the_damn_static_vars) {
|
|
||||||
gcc_won_t_instantiate_the_damn_static_vars = make_shared<map_type>();
|
|
||||||
}
|
|
||||||
return gcc_won_t_instantiate_the_damn_static_vars;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
void
|
void
|
||||||
ResourceHandle<Key, Resource>::erase(const key_type &key)
|
ResourceHandle<Key, Resource>::clean_locked()
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(s_mutex());
|
// Must be called with lock held
|
||||||
// Resources are allowed to set their Keys to null.
|
for (auto i = s_map.begin(); i != s_map.end(); ) {
|
||||||
if (s_traits.is_null_key(key)) {
|
auto this_i = i;
|
||||||
// Clean out any dead weak_ptr objects.
|
++i;
|
||||||
for (auto i = s_map()->begin(); i != s_map()->end(); ) {
|
if (this_i->second.expired()) {
|
||||||
if (! (*i).second.lock()) {
|
s_map.erase(this_i);
|
||||||
i = s_map()->erase(i);
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto erased = s_map()->erase(key);
|
|
||||||
if (erased != 1) {
|
|
||||||
cerr << __PRETTY_FUNCTION__ << ": WARNING: s_map()->erase(" << key << ") returned " << erased << " != 1" << endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
ResourceHandle<Key, Resource>::ResourceHolder::~ResourceHolder()
|
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
||||||
{
|
|
||||||
if (!m_ptr) {
|
|
||||||
// Probably something harmless like a failed constructor.
|
|
||||||
cerr << __PRETTY_FUNCTION__ << ": WARNING: destroying null m_ptr" << endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Key key = s_traits.get_key(*m_ptr);
|
|
||||||
ResourceHandle::erase(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Key, class Resource>
|
|
||||||
typename ResourceHandle<Key, Resource>::holder_ptr_type
|
|
||||||
ResourceHandle<Key, Resource>::insert(const key_type &key)
|
ResourceHandle<Key, Resource>::insert(const key_type &key)
|
||||||
{
|
{
|
||||||
// no Resources for null keys
|
// no Resources for null keys
|
||||||
if (s_traits.is_null_key(key)) {
|
if (s_traits.is_null_key(key)) {
|
||||||
return holder_ptr_type();
|
return resource_ptr_type();
|
||||||
}
|
}
|
||||||
unique_lock<mutex> lock(s_mutex());
|
unique_lock<mutex> lock(s_map_mutex);
|
||||||
// find ResourceHolder for non-null key
|
auto found = s_map.find(key);
|
||||||
auto found = s_map()->find(key);
|
if (found != s_map.end()) {
|
||||||
if (found != s_map()->end()) {
|
resource_ptr_type rv = found->second.lock();
|
||||||
holder_ptr_type rv = (*found).second.lock();
|
|
||||||
// a weak_ptr may have expired
|
|
||||||
if (rv) {
|
if (rv) {
|
||||||
|
// Use existing Resource
|
||||||
return rv;
|
return rv;
|
||||||
|
} else {
|
||||||
|
// It's OK for the map to temporarily contain an expired weak_ptr to some dead Resource
|
||||||
|
clean_locked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// not found or expired, throw any existing ref away and make a new one
|
// not found or expired, throw any existing ref away and make a new one
|
||||||
resource_ptr_type rpt = s_traits.make_resource(key);
|
resource_ptr_type rpt = s_traits.make_resource(key);
|
||||||
holder_ptr_type hpt = make_shared<ResourceHolder>(rpt);
|
|
||||||
// store weak_ptr in map
|
// store weak_ptr in map
|
||||||
(*s_map())[key] = hpt;
|
s_map[key] = rpt;
|
||||||
// return shared_ptr
|
// return shared_ptr
|
||||||
return hpt;
|
return rpt;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
typename ResourceHandle<Key, Resource>::holder_ptr_type
|
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
||||||
ResourceHandle<Key, Resource>::insert(const resource_ptr_type &res)
|
ResourceHandle<Key, Resource>::insert(const resource_ptr_type &res)
|
||||||
{
|
{
|
||||||
// no Resource, no ResourceHolder.
|
// no Resources for null keys
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return holder_ptr_type();
|
return resource_ptr_type();
|
||||||
}
|
}
|
||||||
// no ResourceHolders for null keys either.
|
|
||||||
key_type key = s_traits.get_key(*res);
|
key_type key = s_traits.get_key(*res);
|
||||||
if (s_traits.is_null_key(key)) {
|
if (s_traits.is_null_key(key)) {
|
||||||
return holder_ptr_type();
|
return resource_ptr_type();
|
||||||
}
|
}
|
||||||
unique_lock<mutex> lock(s_mutex());
|
unique_lock<mutex> lock(s_map_mutex);
|
||||||
// find ResourceHolder for non-null key
|
// find Resource for non-null key
|
||||||
auto found = s_map()->find(key);
|
auto found = s_map.find(key);
|
||||||
if (found != s_map()->end()) {
|
if (found != s_map.end()) {
|
||||||
holder_ptr_type rv = (*found).second.lock();
|
resource_ptr_type rv = found->second.lock();
|
||||||
// The map doesn't own the ResourceHolders, the ResourceHandles do.
|
// It's OK for the map to temporarily contain an expired weak_ptr to some dead Resource...
|
||||||
// It's OK for the map to contain an expired weak_ptr to some dead ResourceHolder...
|
|
||||||
if (rv) {
|
if (rv) {
|
||||||
// found ResourceHolder, look at pointer
|
// ...but not a duplicate Resource.
|
||||||
resource_ptr_type rp = rv->get_resource_ptr();
|
if (rv.owner_before(res) || res.owner_before(rv)) {
|
||||||
// We do not store references to null Resources.
|
throw duplicate_resource(key);
|
||||||
assert(rp);
|
|
||||||
// Key retrieved for an existing object must match key searched or be null.
|
|
||||||
key_type found_key = s_traits.get_key(*rp);
|
|
||||||
bool found_key_is_null = s_traits.is_null_key(found_key);
|
|
||||||
assert(found_key_is_null || found_key == key);
|
|
||||||
if (!found_key_is_null) {
|
|
||||||
// We do not store references to duplicate resources.
|
|
||||||
if (rp.owner_before(res) || res.owner_before(rp)) {
|
|
||||||
cerr << "inserting new Resource with existing Key " << key << " not allowed at " << __PRETTY_FUNCTION__ << endl;;
|
|
||||||
abort();
|
|
||||||
// THROW_ERROR(out_of_range, "inserting new Resource with existing Key " << key << " not allowed at " << __PRETTY_FUNCTION__);
|
|
||||||
}
|
|
||||||
// rv is good, return it
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
// Use the existing Resource (discard the caller's).
|
||||||
|
return rv;
|
||||||
|
} else {
|
||||||
|
// Clean out expired weak_ptrs
|
||||||
|
clean_locked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// not found or expired, make a new one
|
// not found or expired, make a new one or replace old one
|
||||||
holder_ptr_type rv = make_shared<ResourceHolder>(res);
|
s_map[key] = res;
|
||||||
s_map()->insert(make_pair(key, weak_holder_ptr_type(rv)));
|
return res;
|
||||||
// no need to check s_map result, we are either replacing a dead weak_ptr or adding a new one
|
|
||||||
return rv;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
ResourceHandle<Key, Resource>::ResourceHandle(const key_type &key)
|
ResourceHandle<Key, Resource>::ResourceHandle(const key_type &key)
|
||||||
{
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
m_ptr = insert(key);
|
m_ptr = insert(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +247,7 @@ namespace crucible {
|
|||||||
ResourceHandle<Key, Resource>&
|
ResourceHandle<Key, Resource>&
|
||||||
ResourceHandle<Key, Resource>::operator=(const key_type &key)
|
ResourceHandle<Key, Resource>::operator=(const key_type &key)
|
||||||
{
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
m_ptr = insert(key);
|
m_ptr = insert(key);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -297,6 +255,7 @@ namespace crucible {
|
|||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
ResourceHandle<Key, Resource>::ResourceHandle(const resource_ptr_type &res)
|
ResourceHandle<Key, Resource>::ResourceHandle(const resource_ptr_type &res)
|
||||||
{
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
m_ptr = insert(res);
|
m_ptr = insert(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,36 +263,91 @@ namespace crucible {
|
|||||||
ResourceHandle<Key, Resource>&
|
ResourceHandle<Key, Resource>&
|
||||||
ResourceHandle<Key, Resource>::operator=(const resource_ptr_type &res)
|
ResourceHandle<Key, Resource>::operator=(const resource_ptr_type &res)
|
||||||
{
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
m_ptr = insert(res);
|
m_ptr = insert(res);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
ResourceHandle<Key, Resource>::ResourceHandle(const ResourceHandle &that)
|
||||||
ResourceHandle<Key, Resource>::ResourceHolder::get_resource_ptr() const
|
|
||||||
{
|
{
|
||||||
return m_ptr;
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
|
m_ptr = that.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Key, class Resource>
|
||||||
|
ResourceHandle<Key, Resource>::ResourceHandle(ResourceHandle &&that)
|
||||||
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
|
swap(m_ptr, that.m_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Key, class Resource>
|
||||||
|
ResourceHandle<Key, Resource> &
|
||||||
|
ResourceHandle<Key, Resource>::operator=(ResourceHandle &&that)
|
||||||
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
|
m_ptr = that.m_ptr;
|
||||||
|
that.m_ptr.reset();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Key, class Resource>
|
||||||
|
ResourceHandle<Key, Resource> &
|
||||||
|
ResourceHandle<Key, Resource>::operator=(const ResourceHandle &that)
|
||||||
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
|
m_ptr = that.m_ptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Key, class Resource>
|
||||||
|
ResourceHandle<Key, Resource>::~ResourceHandle()
|
||||||
|
{
|
||||||
|
unique_lock<mutex> lock_ptr(s_ptr_mutex);
|
||||||
|
// No pointer, nothing to do
|
||||||
|
if (!m_ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Save key so we can clean the map
|
||||||
|
auto key = s_traits.get_key(*m_ptr);
|
||||||
|
// Save pointer so we can release lock before deleting
|
||||||
|
auto ptr_copy = m_ptr;
|
||||||
|
m_ptr.reset();
|
||||||
|
// Release lock
|
||||||
|
lock_ptr.unlock();
|
||||||
|
// Delete our (possibly last) reference to pointer
|
||||||
|
ptr_copy.reset();
|
||||||
|
// Remove weak_ptr from map if it has expired
|
||||||
|
// (and not been replaced in the meantime)
|
||||||
|
unique_lock<mutex> lock_map(s_map_mutex);
|
||||||
|
auto found = s_map.find(key);
|
||||||
|
if (found != s_map.end() && found->second.expired()) {
|
||||||
|
s_map.erase(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
||||||
ResourceHandle<Key, Resource>::get_resource_ptr() const
|
ResourceHandle<Key, Resource>::get_resource_ptr() const
|
||||||
{
|
{
|
||||||
if (!m_ptr) {
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
return resource_ptr_type();
|
// Make isolated copy of pointer with lock held, and return the copy
|
||||||
}
|
auto rv = m_ptr;
|
||||||
return m_ptr->get_resource_ptr();
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
||||||
ResourceHandle<Key, Resource>::operator->() const
|
ResourceHandle<Key, Resource>::operator->() const
|
||||||
{
|
{
|
||||||
resource_ptr_type rp = get_resource_ptr();
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
if (!rp) {
|
if (!m_ptr) {
|
||||||
THROW_ERROR(out_of_range, __PRETTY_FUNCTION__ << " called on null Resource");
|
THROW_ERROR(out_of_range, __PRETTY_FUNCTION__ << " called on null Resource");
|
||||||
}
|
}
|
||||||
return rp;
|
// Make isolated copy of pointer with lock held, and return the copy
|
||||||
|
auto rv = m_ptr;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
@@ -341,12 +355,12 @@ namespace crucible {
|
|||||||
shared_ptr<T>
|
shared_ptr<T>
|
||||||
ResourceHandle<Key, Resource>::cast() const
|
ResourceHandle<Key, Resource>::cast() const
|
||||||
{
|
{
|
||||||
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
shared_ptr<T> dp;
|
shared_ptr<T> dp;
|
||||||
resource_ptr_type rp = get_resource_ptr();
|
if (!m_ptr) {
|
||||||
if (!rp) {
|
|
||||||
return dp;
|
return dp;
|
||||||
}
|
}
|
||||||
dp = dynamic_pointer_cast<T>(rp);
|
dp = dynamic_pointer_cast<T>(m_ptr);
|
||||||
if (!dp) {
|
if (!dp) {
|
||||||
throw bad_cast();
|
throw bad_cast();
|
||||||
}
|
}
|
||||||
@@ -357,11 +371,11 @@ namespace crucible {
|
|||||||
typename ResourceHandle<Key, Resource>::key_type
|
typename ResourceHandle<Key, Resource>::key_type
|
||||||
ResourceHandle<Key, Resource>::get_key() const
|
ResourceHandle<Key, Resource>::get_key() const
|
||||||
{
|
{
|
||||||
resource_ptr_type rp = get_resource_ptr();
|
unique_lock<mutex> lock(s_ptr_mutex);
|
||||||
if (!rp) {
|
if (!m_ptr) {
|
||||||
return s_traits.get_null_key();
|
return s_traits.get_null_key();
|
||||||
} else {
|
} else {
|
||||||
return s_traits.get_key(*rp);
|
return s_traits.get_key(*m_ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,9 +392,19 @@ namespace crucible {
|
|||||||
return s_traits.is_null_key(operator key_type());
|
return s_traits.is_null_key(operator key_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apparently GCC wants these to be used before they are defined.
|
||||||
template <class Key, class Resource>
|
template <class Key, class Resource>
|
||||||
ResourceTraits<Key, Resource> ResourceHandle<Key, Resource>::s_traits;
|
ResourceTraits<Key, Resource> ResourceHandle<Key, Resource>::s_traits;
|
||||||
|
|
||||||
|
template <class Key, class Resource>
|
||||||
|
mutex ResourceHandle<Key, Resource>::s_map_mutex;
|
||||||
|
|
||||||
|
template <class Key, class Resource>
|
||||||
|
mutex ResourceHandle<Key, Resource>::s_ptr_mutex;
|
||||||
|
|
||||||
|
template <class Key, class Resource>
|
||||||
|
typename ResourceHandle<Key, Resource>::map_type ResourceHandle<Key, Resource>::s_map;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -32,10 +32,11 @@ namespace crucible {
|
|||||||
Timer m_timer;
|
Timer m_timer;
|
||||||
double m_rate;
|
double m_rate;
|
||||||
double m_burst;
|
double m_burst;
|
||||||
double m_tokens;
|
double m_tokens = 0.0;
|
||||||
mutex m_mutex;
|
mutex m_mutex;
|
||||||
|
|
||||||
void update_tokens();
|
void update_tokens();
|
||||||
|
RateLimiter() = delete;
|
||||||
public:
|
public:
|
||||||
RateLimiter(double rate, double burst);
|
RateLimiter(double rate, double burst);
|
||||||
RateLimiter(double rate);
|
RateLimiter(double rate);
|
||||||
|
@@ -23,7 +23,7 @@ namespace crucible {
|
|||||||
private:
|
private:
|
||||||
struct Item {
|
struct Item {
|
||||||
Timestamp m_time;
|
Timestamp m_time;
|
||||||
unsigned m_id;
|
unsigned long m_id;
|
||||||
Task m_task;
|
Task m_task;
|
||||||
|
|
||||||
bool operator<(const Item &that) const {
|
bool operator<(const Item &that) const {
|
||||||
@@ -77,7 +77,7 @@ namespace crucible {
|
|||||||
void
|
void
|
||||||
TimeQueue<Task>::push(const Task &task, double delay)
|
TimeQueue<Task>::push(const Task &task, double delay)
|
||||||
{
|
{
|
||||||
Timestamp time = chrono::high_resolution_clock::now() +
|
Timestamp time = chrono::high_resolution_clock::now() +
|
||||||
chrono::duration_cast<chrono::high_resolution_clock::duration>(chrono::duration<double>(delay));
|
chrono::duration_cast<chrono::high_resolution_clock::duration>(chrono::duration<double>(delay));
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
while (m_set.size() > m_max_queue_depth) {
|
while (m_set.size() > m_max_queue_depth) {
|
||||||
@@ -91,7 +91,7 @@ namespace crucible {
|
|||||||
void
|
void
|
||||||
TimeQueue<Task>::push_nowait(const Task &task, double delay)
|
TimeQueue<Task>::push_nowait(const Task &task, double delay)
|
||||||
{
|
{
|
||||||
Timestamp time = chrono::high_resolution_clock::now() +
|
Timestamp time = chrono::high_resolution_clock::now() +
|
||||||
chrono::duration_cast<chrono::high_resolution_clock::duration>(chrono::duration<double>(delay));
|
chrono::duration_cast<chrono::high_resolution_clock::duration>(chrono::duration<double>(delay));
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
m_set.insert(Item(time, task));
|
m_set.insert(Item(time, task));
|
||||||
|
8
include/crucible/version.h
Normal file
8
include/crucible/version.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef CRUCIBLE_VERSION_H
|
||||||
|
#define CRUCIBLE_VERSION_H
|
||||||
|
|
||||||
|
namespace crucible {
|
||||||
|
extern const char *VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif CRUCIBLE_VERSION_H
|
@@ -124,7 +124,9 @@ namespace crucible {
|
|||||||
if (m_set.empty()) {
|
if (m_set.empty()) {
|
||||||
return key_type();
|
return key_type();
|
||||||
} else {
|
} else {
|
||||||
return *m_set.begin();
|
// Make copy with lock held
|
||||||
|
auto rv = *m_set.begin();
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +151,8 @@ namespace crucible {
|
|||||||
WorkQueue<Task>::copy()
|
WorkQueue<Task>::copy()
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
return m_set;
|
auto rv = m_set;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Task>
|
template <class Task>
|
||||||
|
1
lib/.gitignore
vendored
Normal file
1
lib/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.version.*
|
17
lib/Makefile
17
lib/Makefile
@@ -4,34 +4,31 @@ OBJS = \
|
|||||||
crc64.o \
|
crc64.o \
|
||||||
chatter.o \
|
chatter.o \
|
||||||
error.o \
|
error.o \
|
||||||
execpipe.o \
|
|
||||||
extentwalker.o \
|
extentwalker.o \
|
||||||
fd.o \
|
fd.o \
|
||||||
fs.o \
|
fs.o \
|
||||||
interp.o \
|
|
||||||
ntoa.o \
|
ntoa.o \
|
||||||
path.o \
|
path.o \
|
||||||
process.o \
|
process.o \
|
||||||
string.o \
|
string.o \
|
||||||
time.o \
|
time.o \
|
||||||
uuid.o \
|
uuid.o \
|
||||||
|
.version.o \
|
||||||
|
|
||||||
include ../makeflags
|
include ../makeflags
|
||||||
|
|
||||||
LDFLAGS = -shared -luuid
|
depends.mk: *.cc
|
||||||
|
|
||||||
depends.mk: *.c *.cc
|
|
||||||
for x in *.c; do $(CC) $(CFLAGS) -M "$$x"; done > depends.mk.new
|
|
||||||
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done >> depends.mk.new
|
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done >> depends.mk.new
|
||||||
mv -fv depends.mk.new depends.mk
|
mv -fv depends.mk.new depends.mk
|
||||||
|
|
||||||
-include depends.mk
|
.version.cc: Makefile ../makeflags *.cc ../include/crucible/*.h
|
||||||
|
echo "namespace crucible { const char *VERSION = \"$(shell git describe --always --dirty || echo UNKNOWN)\"; }" > .version.new.cc
|
||||||
|
mv -f .version.new.cc .version.cc
|
||||||
|
|
||||||
%.o: %.c
|
-include depends.mk
|
||||||
$(CC) $(CFLAGS) -o $@ -c $<
|
|
||||||
|
|
||||||
%.o: %.cc ../include/crucible/%.h
|
%.o: %.cc ../include/crucible/%.h
|
||||||
$(CXX) $(CXXFLAGS) -o $@ -c $<
|
$(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||||
|
|
||||||
libcrucible.so: $(OBJS) Makefile
|
libcrucible.so: $(OBJS) Makefile
|
||||||
$(CXX) $(LDFLAGS) -o $@ $(OBJS)
|
$(CXX) $(LDFLAGS) -o $@ $(OBJS) -shared -luuid
|
||||||
|
@@ -15,8 +15,9 @@
|
|||||||
namespace crucible {
|
namespace crucible {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
static auto_ptr<set<string>> chatter_names;
|
static shared_ptr<set<string>> chatter_names;
|
||||||
static const char *SPACETAB = " \t";
|
static const char *SPACETAB = " \t";
|
||||||
|
static bool add_prefix_timestamp = true;
|
||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
@@ -48,20 +49,31 @@ namespace crucible {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Chatter::enable_timestamp(bool prefix_timestamp)
|
||||||
|
{
|
||||||
|
add_prefix_timestamp = prefix_timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
Chatter::~Chatter()
|
Chatter::~Chatter()
|
||||||
{
|
{
|
||||||
ostringstream header_stream;
|
ostringstream header_stream;
|
||||||
|
|
||||||
time_t ltime;
|
if (add_prefix_timestamp) {
|
||||||
DIE_IF_MINUS_ONE(time(<ime));
|
time_t ltime;
|
||||||
struct tm ltm;
|
DIE_IF_MINUS_ONE(time(<ime));
|
||||||
DIE_IF_ZERO(localtime_r(<ime, <m));
|
struct tm ltm;
|
||||||
|
DIE_IF_ZERO(localtime_r(<ime, <m));
|
||||||
|
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
DIE_IF_ZERO(strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", <m));
|
DIE_IF_ZERO(strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", <m));
|
||||||
|
|
||||||
|
header_stream << buf;
|
||||||
|
header_stream << " " << getpid() << "." << gettid();
|
||||||
|
} else {
|
||||||
|
header_stream << "tid " << gettid();
|
||||||
|
}
|
||||||
|
|
||||||
header_stream << buf;
|
|
||||||
header_stream << " " << getpid() << "." << gettid();
|
|
||||||
if (!m_name.empty()) {
|
if (!m_name.empty()) {
|
||||||
header_stream << " " << m_name;
|
header_stream << " " << m_name;
|
||||||
}
|
}
|
||||||
|
95
lib/crc64.cc
95
lib/crc64.cc
@@ -1,3 +1,31 @@
|
|||||||
|
/* crc64.c -- compute CRC-64
|
||||||
|
* Copyright (C) 2013 Mark Adler
|
||||||
|
* Version 1.4 16 Dec 2013 Mark Adler
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the author be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
|
Mark Adler
|
||||||
|
madler@alumni.caltech.edu
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Substantially modified by Paul Jones for usage in bees */
|
||||||
|
|
||||||
#include "crucible/crc64.h"
|
#include "crucible/crc64.h"
|
||||||
|
|
||||||
#define POLY64REV 0xd800000000000000ULL
|
#define POLY64REV 0xd800000000000000ULL
|
||||||
@@ -5,13 +33,16 @@
|
|||||||
namespace crucible {
|
namespace crucible {
|
||||||
|
|
||||||
static bool init = false;
|
static bool init = false;
|
||||||
static uint64_t CRCTable[256];
|
static uint64_t CRCTable[8][256];
|
||||||
|
|
||||||
static void init_crc64_table()
|
static void init_crc64_table()
|
||||||
{
|
{
|
||||||
if (!init) {
|
if (!init) {
|
||||||
for (int i = 0; i <= 255; i++) {
|
uint64_t crc;
|
||||||
uint64_t part = i;
|
|
||||||
|
// Generate CRCs for all single byte sequences
|
||||||
|
for (int n = 0; n < 256; n++) {
|
||||||
|
uint64_t part = n;
|
||||||
for (int j = 0; j < 8; j++) {
|
for (int j = 0; j < 8; j++) {
|
||||||
if (part & 1) {
|
if (part & 1) {
|
||||||
part = (part >> 1) ^ POLY64REV;
|
part = (part >> 1) ^ POLY64REV;
|
||||||
@@ -19,37 +50,53 @@ namespace crucible {
|
|||||||
part >>= 1;
|
part >>= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CRCTable[i] = part;
|
CRCTable[0][n] = part;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate nested CRC table for slice-by-8 lookup
|
||||||
|
for (int n = 0; n < 256; n++) {
|
||||||
|
crc = CRCTable[0][n];
|
||||||
|
for (int k = 1; k < 8; k++) {
|
||||||
|
crc = CRCTable[0][crc & 0xff] ^ (crc >> 8);
|
||||||
|
CRCTable[k][n] = crc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
init = true;
|
init = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t
|
|
||||||
Digest::CRC::crc64(const char *s)
|
|
||||||
{
|
|
||||||
init_crc64_table();
|
|
||||||
|
|
||||||
uint64_t crc = 0;
|
|
||||||
for (; *s; s++) {
|
|
||||||
uint64_t temp1 = crc >> 8;
|
|
||||||
uint64_t temp2 = CRCTable[(crc ^ static_cast<uint64_t>(*s)) & 0xff];
|
|
||||||
crc = temp1 ^ temp2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t
|
uint64_t
|
||||||
Digest::CRC::crc64(const void *p, size_t len)
|
Digest::CRC::crc64(const void *p, size_t len)
|
||||||
{
|
{
|
||||||
init_crc64_table();
|
init_crc64_table();
|
||||||
|
const unsigned char *next = static_cast<const unsigned char *>(p);
|
||||||
uint64_t crc = 0;
|
uint64_t crc = 0;
|
||||||
for (const unsigned char *s = static_cast<const unsigned char *>(p); len; --len) {
|
|
||||||
uint64_t temp1 = crc >> 8;
|
// Process individual bytes until we reach an 8-byte aligned pointer
|
||||||
uint64_t temp2 = CRCTable[(crc ^ *s++) & 0xff];
|
while (len && (reinterpret_cast<uintptr_t>(next) & 7) != 0) {
|
||||||
crc = temp1 ^ temp2;
|
crc = CRCTable[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast middle processing, 8 bytes (aligned!) per loop
|
||||||
|
while (len >= 8) {
|
||||||
|
crc ^= *(reinterpret_cast< const uint64_t *>(next));
|
||||||
|
crc = CRCTable[7][crc & 0xff] ^
|
||||||
|
CRCTable[6][(crc >> 8) & 0xff] ^
|
||||||
|
CRCTable[5][(crc >> 16) & 0xff] ^
|
||||||
|
CRCTable[4][(crc >> 24) & 0xff] ^
|
||||||
|
CRCTable[3][(crc >> 32) & 0xff] ^
|
||||||
|
CRCTable[2][(crc >> 40) & 0xff] ^
|
||||||
|
CRCTable[1][(crc >> 48) & 0xff] ^
|
||||||
|
CRCTable[0][crc >> 56];
|
||||||
|
next += 8;
|
||||||
|
len -= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process remaining bytes (can't be larger than 8)
|
||||||
|
while (len) {
|
||||||
|
crc = CRCTable[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||||
|
len--;
|
||||||
}
|
}
|
||||||
|
|
||||||
return crc;
|
return crc;
|
||||||
|
104
lib/execpipe.cc
104
lib/execpipe.cc
@@ -1,104 +0,0 @@
|
|||||||
#include "crucible/execpipe.h"
|
|
||||||
|
|
||||||
#include "crucible/chatter.h"
|
|
||||||
#include "crucible/error.h"
|
|
||||||
#include "crucible/process.h"
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
namespace crucible {
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
void
|
|
||||||
redirect_stdin(const Fd &child_fd)
|
|
||||||
{
|
|
||||||
dup2_or_die(child_fd, STDIN_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
redirect_stdin_stdout(const Fd &child_fd)
|
|
||||||
{
|
|
||||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
|
||||||
dup2_or_die(child_fd, STDIN_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
redirect_stdin_stdout_stderr(const Fd &child_fd)
|
|
||||||
{
|
|
||||||
dup2_or_die(child_fd, STDERR_FILENO);
|
|
||||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
|
||||||
dup2_or_die(child_fd, STDIN_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
redirect_stdout_stderr(const Fd &child_fd)
|
|
||||||
{
|
|
||||||
dup2_or_die(child_fd, STDERR_FILENO);
|
|
||||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
redirect_stdout(const Fd &child_fd)
|
|
||||||
{
|
|
||||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
redirect_stderr(const Fd &child_fd)
|
|
||||||
{
|
|
||||||
dup2_or_die(child_fd, STDERR_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
Fd popen(function<int()> f, function<void(const Fd &child_fd)> import_fd_fn)
|
|
||||||
{
|
|
||||||
Fd parent_fd, child_fd;
|
|
||||||
{
|
|
||||||
pair<Fd, Fd> fd_pair = socketpair_or_die();
|
|
||||||
parent_fd = fd_pair.first;
|
|
||||||
child_fd = fd_pair.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t fv;
|
|
||||||
DIE_IF_MINUS_ONE(fv = fork());
|
|
||||||
|
|
||||||
if (fv) {
|
|
||||||
child_fd->close();
|
|
||||||
return parent_fd;
|
|
||||||
} else {
|
|
||||||
int rv = EXIT_FAILURE;
|
|
||||||
catch_all([&]() {
|
|
||||||
parent_fd->close();
|
|
||||||
import_fd_fn(child_fd);
|
|
||||||
// system("ls -l /proc/$$/fd/ >&2");
|
|
||||||
|
|
||||||
rv = f();
|
|
||||||
});
|
|
||||||
_exit(rv);
|
|
||||||
cerr << "PID " << getpid() << " TID " << gettid() << "STILL ALIVE" << endl;
|
|
||||||
system("ls -l /proc/$$/task/ >&2");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
read_all(Fd fd, size_t max_bytes, size_t chunk_bytes)
|
|
||||||
{
|
|
||||||
char buf[chunk_bytes];
|
|
||||||
string str;
|
|
||||||
size_t rv;
|
|
||||||
while (1) {
|
|
||||||
read_partial_or_die(fd, static_cast<void *>(buf), chunk_bytes, rv);
|
|
||||||
if (rv == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (max_bytes - str.size() < rv) {
|
|
||||||
THROW_ERROR(out_of_range, "Output size limit " << max_bytes << " exceeded by appending " << rv << " bytes read to " << str.size() << " already in string");
|
|
||||||
}
|
|
||||||
str.append(buf, rv);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -79,17 +79,6 @@ namespace crucible {
|
|||||||
<< "] }";
|
<< "] }";
|
||||||
}
|
}
|
||||||
|
|
||||||
Extent::Extent() :
|
|
||||||
m_begin(0),
|
|
||||||
m_end(0),
|
|
||||||
m_physical(0),
|
|
||||||
m_flags(0),
|
|
||||||
m_physical_len(0),
|
|
||||||
m_logical_len(0),
|
|
||||||
m_offset(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Extent::operator bool() const
|
Extent::operator bool() const
|
||||||
{
|
{
|
||||||
THROW_CHECK2(invalid_argument, m_begin, m_end, m_end >= m_begin);
|
THROW_CHECK2(invalid_argument, m_begin, m_end, m_end >= m_begin);
|
||||||
@@ -109,6 +98,18 @@ namespace crucible {
|
|||||||
return m_begin == that.m_begin && m_end == that.m_end && m_physical == that.m_physical && m_flags == that.m_flags;
|
return m_begin == that.m_begin && m_end == that.m_end && m_physical == that.m_physical && m_flags == that.m_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Extent::compressed() const
|
||||||
|
{
|
||||||
|
return m_flags & FIEMAP_EXTENT_ENCODED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t
|
||||||
|
Extent::bytenr() const
|
||||||
|
{
|
||||||
|
return compressed() ? m_physical : m_physical - m_offset;
|
||||||
|
}
|
||||||
|
|
||||||
ExtentWalker::ExtentWalker(Fd fd) :
|
ExtentWalker::ExtentWalker(Fd fd) :
|
||||||
m_fd(fd),
|
m_fd(fd),
|
||||||
m_current(m_extents.begin())
|
m_current(m_extents.begin())
|
||||||
@@ -468,7 +469,7 @@ namespace crucible {
|
|||||||
BtrfsExtentWalker::Vec
|
BtrfsExtentWalker::Vec
|
||||||
BtrfsExtentWalker::get_extent_map(off_t pos)
|
BtrfsExtentWalker::get_extent_map(off_t pos)
|
||||||
{
|
{
|
||||||
BtrfsIoctlSearchKey sk;
|
BtrfsIoctlSearchKey sk(sc_extent_fetch_max * (sizeof(btrfs_file_extent_item) + sizeof(btrfs_ioctl_search_header)));
|
||||||
if (!m_root_fd) {
|
if (!m_root_fd) {
|
||||||
m_root_fd = m_fd;
|
m_root_fd = m_fd;
|
||||||
}
|
}
|
||||||
@@ -519,25 +520,26 @@ namespace crucible {
|
|||||||
|
|
||||||
auto type = call_btrfs_get(btrfs_stack_file_extent_type, i.m_data);
|
auto type = call_btrfs_get(btrfs_stack_file_extent_type, i.m_data);
|
||||||
off_t len = -1;
|
off_t len = -1;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
default:
|
default:
|
||||||
cerr << "Unhandled file extent type " << type << " in root " << m_tree_id << " ino " << m_stat.st_ino << endl;
|
cerr << "Unhandled file extent type " << type << " in root " << m_tree_id << " ino " << m_stat.st_ino << endl;
|
||||||
break;
|
break;
|
||||||
case BTRFS_FILE_EXTENT_INLINE:
|
case BTRFS_FILE_EXTENT_INLINE:
|
||||||
len = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data));
|
len = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data));
|
||||||
e.m_flags |= FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_NOT_ALIGNED;
|
e.m_flags |= FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_NOT_ALIGNED;
|
||||||
// Inline extents are never obscured, so don't bother filling in m_physical_len, etc.
|
// Inline extents are never obscured, so don't bother filling in m_physical_len, etc.
|
||||||
break;
|
break;
|
||||||
case BTRFS_FILE_EXTENT_PREALLOC:
|
case BTRFS_FILE_EXTENT_PREALLOC:
|
||||||
e.m_flags |= Extent::PREALLOC;
|
e.m_flags |= Extent::PREALLOC;
|
||||||
case BTRFS_FILE_EXTENT_REG: {
|
// fallthrough
|
||||||
|
case BTRFS_FILE_EXTENT_REG: {
|
||||||
e.m_physical = call_btrfs_get(btrfs_stack_file_extent_disk_bytenr, i.m_data);
|
e.m_physical = call_btrfs_get(btrfs_stack_file_extent_disk_bytenr, i.m_data);
|
||||||
|
|
||||||
// This is the length of the full extent (decompressed)
|
// This is the length of the full extent (decompressed)
|
||||||
off_t ram = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data));
|
off_t ram = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data));
|
||||||
|
|
||||||
// This is the length of the part of the extent appearing in the file (decompressed)
|
// This is the length of the part of the extent appearing in the file (decompressed)
|
||||||
len = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_num_bytes, i.m_data));
|
len = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_num_bytes, i.m_data));
|
||||||
|
|
||||||
// This is the offset from start of on-disk extent to the part we see in the file (decompressed)
|
// This is the offset from start of on-disk extent to the part we see in the file (decompressed)
|
||||||
// May be negative due to the kind of bug we're stuck with forever, so no cast range check
|
// May be negative due to the kind of bug we're stuck with forever, so no cast range check
|
||||||
|
43
lib/fd.cc
43
lib/fd.cc
@@ -230,6 +230,14 @@ namespace crucible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ftruncate_or_die(int fd, off_t size)
|
||||||
|
{
|
||||||
|
if (::ftruncate(fd, size)) {
|
||||||
|
THROW_ERRNO("ftruncate: " << name_fd(fd) << " size " << size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
socket_domain_ntoa(int domain)
|
socket_domain_ntoa(int domain)
|
||||||
{
|
{
|
||||||
@@ -426,6 +434,27 @@ namespace crucible {
|
|||||||
return pread_or_die(fd, text.data(), text.size(), offset);
|
return pread_or_die(fd, text.data(), text.size(), offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void
|
||||||
|
pwrite_or_die<vector<uint8_t>>(int fd, const vector<uint8_t> &text, off_t offset)
|
||||||
|
{
|
||||||
|
return pwrite_or_die(fd, text.data(), text.size(), offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void
|
||||||
|
pwrite_or_die<vector<char>>(int fd, const vector<char> &text, off_t offset)
|
||||||
|
{
|
||||||
|
return pwrite_or_die(fd, text.data(), text.size(), offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void
|
||||||
|
pwrite_or_die<string>(int fd, const string &text, off_t offset)
|
||||||
|
{
|
||||||
|
return pwrite_or_die(fd, text.data(), text.size(), offset);
|
||||||
|
}
|
||||||
|
|
||||||
Stat::Stat()
|
Stat::Stat()
|
||||||
{
|
{
|
||||||
memset_zero<stat>(this);
|
memset_zero<stat>(this);
|
||||||
@@ -459,6 +488,20 @@ namespace crucible {
|
|||||||
lstat(filename);
|
lstat(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ioctl_iflags_get(int fd)
|
||||||
|
{
|
||||||
|
int attr = 0;
|
||||||
|
DIE_IF_MINUS_ONE(ioctl(fd, FS_IOC_GETFLAGS, &attr));
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ioctl_iflags_set(int fd, int attr)
|
||||||
|
{
|
||||||
|
DIE_IF_MINUS_ONE(ioctl(fd, FS_IOC_SETFLAGS, &attr));
|
||||||
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
readlink_or_die(const string &path)
|
readlink_or_die(const string &path)
|
||||||
{
|
{
|
||||||
|
34
lib/fs.cc
34
lib/fs.cc
@@ -468,6 +468,7 @@ namespace crucible {
|
|||||||
static const bits_ntoa_table table[] = {
|
static const bits_ntoa_table table[] = {
|
||||||
NTOA_TABLE_ENTRY_ENUM(BTRFS_COMPRESS_ZLIB),
|
NTOA_TABLE_ENTRY_ENUM(BTRFS_COMPRESS_ZLIB),
|
||||||
NTOA_TABLE_ENTRY_ENUM(BTRFS_COMPRESS_LZO),
|
NTOA_TABLE_ENTRY_ENUM(BTRFS_COMPRESS_LZO),
|
||||||
|
NTOA_TABLE_ENTRY_ENUM(BTRFS_COMPRESS_ZSTD),
|
||||||
NTOA_TABLE_ENTRY_END()
|
NTOA_TABLE_ENTRY_END()
|
||||||
};
|
};
|
||||||
return bits_ntoa(compress_type, table);
|
return bits_ntoa(compress_type, table);
|
||||||
@@ -625,7 +626,7 @@ namespace crucible {
|
|||||||
void
|
void
|
||||||
Fiemap::do_ioctl(int fd)
|
Fiemap::do_ioctl(int fd)
|
||||||
{
|
{
|
||||||
CHECK_CONSTRAINT(m_min_count, m_min_count <= m_max_count);
|
THROW_CHECK1(out_of_range, m_min_count, m_min_count <= m_max_count);
|
||||||
|
|
||||||
auto extent_count = m_min_count;
|
auto extent_count = m_min_count;
|
||||||
vector<char> ioctl_arg = vector_copy_struct<fiemap>(this);
|
vector<char> ioctl_arg = vector_copy_struct<fiemap>(this);
|
||||||
@@ -707,11 +708,29 @@ namespace crucible {
|
|||||||
return offset + len;
|
return offset + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BtrfsIoctlSearchHeader::operator<(const BtrfsIoctlSearchHeader &that) const
|
||||||
|
{
|
||||||
|
return tie(objectid, type, offset, len, transid) < tie(that.objectid, that.type, that.offset, that.len, that.transid);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
BtrfsIoctlSearchKey::do_ioctl_nothrow(int fd)
|
BtrfsIoctlSearchKey::do_ioctl_nothrow(int fd)
|
||||||
{
|
{
|
||||||
vector<char> ioctl_arg = vector_copy_struct<btrfs_ioctl_search_key>(this);
|
// Normally we like to be paranoid and fill empty bytes with zero,
|
||||||
ioctl_arg.resize(sizeof(btrfs_ioctl_search_args_v2) + m_buf_size, 0);
|
// but these buffers can be huge. 80% of a 4GHz CPU huge.
|
||||||
|
|
||||||
|
// Keep the ioctl buffer from one run to the next to save on malloc costs
|
||||||
|
size_t target_buf_size = sizeof(btrfs_ioctl_search_args_v2) + m_buf_size;
|
||||||
|
|
||||||
|
thread_local vector<char> ioctl_arg;
|
||||||
|
if (ioctl_arg.size() < m_buf_size) {
|
||||||
|
ioctl_arg = vector_copy_struct<btrfs_ioctl_search_key>(this);
|
||||||
|
ioctl_arg.resize(target_buf_size);
|
||||||
|
} else {
|
||||||
|
memcpy(ioctl_arg.data(), static_cast<btrfs_ioctl_search_key*>(this), sizeof(btrfs_ioctl_search_key));
|
||||||
|
}
|
||||||
|
|
||||||
btrfs_ioctl_search_args_v2 *ioctl_ptr = reinterpret_cast<btrfs_ioctl_search_args_v2 *>(ioctl_arg.data());
|
btrfs_ioctl_search_args_v2 *ioctl_ptr = reinterpret_cast<btrfs_ioctl_search_args_v2 *>(ioctl_arg.data());
|
||||||
|
|
||||||
ioctl_ptr->buf_size = m_buf_size;
|
ioctl_ptr->buf_size = m_buf_size;
|
||||||
@@ -725,13 +744,12 @@ namespace crucible {
|
|||||||
static_cast<btrfs_ioctl_search_key&>(*this) = ioctl_ptr->key;
|
static_cast<btrfs_ioctl_search_key&>(*this) = ioctl_ptr->key;
|
||||||
|
|
||||||
m_result.clear();
|
m_result.clear();
|
||||||
m_result.reserve(nr_items);
|
|
||||||
|
|
||||||
size_t offset = pointer_distance(ioctl_ptr->buf, ioctl_ptr);
|
size_t offset = pointer_distance(ioctl_ptr->buf, ioctl_ptr);
|
||||||
for (decltype(nr_items) i = 0; i < nr_items; ++i) {
|
for (decltype(nr_items) i = 0; i < nr_items; ++i) {
|
||||||
BtrfsIoctlSearchHeader item;
|
BtrfsIoctlSearchHeader item;
|
||||||
offset = item.set_data(ioctl_arg, offset);
|
offset = item.set_data(ioctl_arg, offset);
|
||||||
m_result.push_back(item);
|
m_result.insert(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -834,7 +852,7 @@ namespace crucible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
btrfs_search_objectid_ntoa(unsigned objectid)
|
btrfs_search_objectid_ntoa(uint64_t objectid)
|
||||||
{
|
{
|
||||||
static const bits_ntoa_table table[] = {
|
static const bits_ntoa_table table[] = {
|
||||||
NTOA_TABLE_ENTRY_ENUM(BTRFS_ROOT_TREE_OBJECTID),
|
NTOA_TABLE_ENTRY_ENUM(BTRFS_ROOT_TREE_OBJECTID),
|
||||||
@@ -906,7 +924,7 @@ namespace crucible {
|
|||||||
ostream &
|
ostream &
|
||||||
operator<<(ostream &os, const BtrfsIoctlSearchHeader &hdr)
|
operator<<(ostream &os, const BtrfsIoctlSearchHeader &hdr)
|
||||||
{
|
{
|
||||||
os << "BtrfsIoctlSearchHeader { "
|
os << "BtrfsIoctlSearchHeader { "
|
||||||
<< static_cast<const btrfs_ioctl_search_header &>(hdr)
|
<< static_cast<const btrfs_ioctl_search_header &>(hdr)
|
||||||
<< ", data = ";
|
<< ", data = ";
|
||||||
hexdump(os, hdr.m_data);
|
hexdump(os, hdr.m_data);
|
||||||
@@ -916,7 +934,7 @@ namespace crucible {
|
|||||||
ostream &
|
ostream &
|
||||||
operator<<(ostream &os, const BtrfsIoctlSearchKey &key)
|
operator<<(ostream &os, const BtrfsIoctlSearchKey &key)
|
||||||
{
|
{
|
||||||
os << "BtrfsIoctlSearchKey { "
|
os << "BtrfsIoctlSearchKey { "
|
||||||
<< static_cast<const btrfs_ioctl_search_key &>(key)
|
<< static_cast<const btrfs_ioctl_search_key &>(key)
|
||||||
<< ", buf_size = " << key.m_buf_size
|
<< ", buf_size = " << key.m_buf_size
|
||||||
<< ", buf[" << key.m_result.size() << "] = {";
|
<< ", buf[" << key.m_result.size() << "] = {";
|
||||||
|
@@ -1,96 +0,0 @@
|
|||||||
#include "crucible/interp.h"
|
|
||||||
|
|
||||||
#include "crucible/chatter.h"
|
|
||||||
|
|
||||||
namespace crucible {
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
int
|
|
||||||
Proc::exec(const ArgList &args)
|
|
||||||
{
|
|
||||||
return m_cmd(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
Proc::Proc(const function<int(const ArgList &)> &f) :
|
|
||||||
m_cmd(f)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::~Command()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgList::ArgList(const char **argv)
|
|
||||||
{
|
|
||||||
while (argv && *argv) {
|
|
||||||
push_back(*argv++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgList::ArgList(const vector<string> &&that) :
|
|
||||||
vector<string>(that)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Interp::~Interp()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Interp::Interp(const map<string, shared_ptr<Command> > &cmdlist) :
|
|
||||||
m_commands(cmdlist)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Interp::add_command(const string &name, const shared_ptr<Command> &command)
|
|
||||||
{
|
|
||||||
m_commands[name] = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
Interp::exec(const ArgList &args)
|
|
||||||
{
|
|
||||||
auto next_arg = args.begin();
|
|
||||||
++next_arg;
|
|
||||||
return m_commands.at(args[0])->exec(vector<string>(next_arg, args.end()));
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgParser::~ArgParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgParser::ArgParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ArgParser::add_opt(string opt, ArgActor actor)
|
|
||||||
{
|
|
||||||
m_string_opts[opt] = actor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ArgParser::parse_backend(void *t, const ArgList &args)
|
|
||||||
{
|
|
||||||
bool quote_args = false;
|
|
||||||
for (string arg : args) {
|
|
||||||
if (quote_args) {
|
|
||||||
cerr << "arg: '" << arg << "'" << endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg == "--") {
|
|
||||||
quote_args = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg.compare(0, 2, "--") == 0) {
|
|
||||||
auto found = m_string_opts.find(arg.substr(2, string::npos));
|
|
||||||
if (found != m_string_opts.end()) {
|
|
||||||
found->second.predicate(t, "foo");
|
|
||||||
}
|
|
||||||
(void)t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
@@ -7,7 +7,7 @@
|
|||||||
namespace crucible {
|
namespace crucible {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
string bits_ntoa(unsigned long n, const bits_ntoa_table *table)
|
string bits_ntoa(unsigned long long n, const bits_ntoa_table *table)
|
||||||
{
|
{
|
||||||
string out;
|
string out;
|
||||||
while (n && table->a) {
|
while (n && table->a) {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
CCFLAGS = -Wall -Wextra -Werror -O3 -I../include -ggdb -fpic
|
CCFLAGS = -Wall -Wextra -Werror -O3 -march=native -I../include -ggdb -fpic -D_FILE_OFFSET_BITS=64
|
||||||
# CCFLAGS = -Wall -Wextra -Werror -O0 -I../include -ggdb -fpic
|
# CCFLAGS = -Wall -Wextra -Werror -O0 -I../include -ggdb -fpic -D_FILE_OFFSET_BITS=64
|
||||||
CFLAGS = $(CCFLAGS) -std=c99
|
CFLAGS = $(CCFLAGS) -std=c99
|
||||||
CXXFLAGS = $(CCFLAGS) -std=c++11 -Wold-style-cast
|
CXXFLAGS = $(CCFLAGS) -std=c++11 -Wold-style-cast
|
||||||
|
37
scripts/beesd.conf.sample
Normal file
37
scripts/beesd.conf.sample
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
## Config for Bees: /etc/bees/beesd.conf.sample
|
||||||
|
## https://github.com/Zygo/bees
|
||||||
|
## It's a default values, change it, if needed
|
||||||
|
|
||||||
|
# How to use?
|
||||||
|
# Copy this file to a new file name and adjust the UUID below
|
||||||
|
|
||||||
|
# Which FS will be used
|
||||||
|
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
|
||||||
|
## System Vars
|
||||||
|
# Change carefully
|
||||||
|
# WORK_DIR=/run/bees/
|
||||||
|
# MNT_DIR="$WORK_DIR/mnt/$UUID"
|
||||||
|
# BEESHOME="$MNT_DIR/.beeshome"
|
||||||
|
# BEESSTATUS="$WORK_DIR/$UUID.status"
|
||||||
|
|
||||||
|
## Make path shorter in logs
|
||||||
|
# LOG_SHORT_PATH=N
|
||||||
|
|
||||||
|
## Remove timestamp from bees output
|
||||||
|
# LOG_FILTER_TIME=N
|
||||||
|
|
||||||
|
## Bees DB size
|
||||||
|
# Hash Table Sizing
|
||||||
|
# sHash table entries are 16 bytes each
|
||||||
|
# (64-bit hash, 52-bit block number, and some metadata bits)
|
||||||
|
# Each entry represents a minimum of 4K on disk.
|
||||||
|
# unique data size hash table size average dedup block size
|
||||||
|
# 1TB 4GB 4K
|
||||||
|
# 1TB 1GB 16K
|
||||||
|
# 1TB 256MB 64K
|
||||||
|
# 1TB 16MB 1024K
|
||||||
|
# 64TB 1GB 1024K
|
||||||
|
#
|
||||||
|
# Size MUST be power of 16M
|
||||||
|
# DB_SIZE=$((64*$AL16M)) # 1G in bytes
|
129
scripts/beesd.in
Executable file
129
scripts/beesd.in
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
## Helpful functions
|
||||||
|
INFO(){ echo "INFO:" "$@"; }
|
||||||
|
ERRO(){ echo "ERROR:" "$@"; exit 1; }
|
||||||
|
YN(){ [[ "$1" =~ (1|Y|y) ]]; }
|
||||||
|
|
||||||
|
## Global vars
|
||||||
|
export BEESHOME BEESSTATUS
|
||||||
|
export WORK_DIR CONFIG_DIR
|
||||||
|
export CONFIG_FILE
|
||||||
|
export UUID AL16M
|
||||||
|
|
||||||
|
readonly AL16M="$((16*1024*1024))"
|
||||||
|
readonly CONFIG_DIR=@PREFIX@/etc/bees/
|
||||||
|
|
||||||
|
## Pre checks
|
||||||
|
{
|
||||||
|
[ ! -d "$CONFIG_DIR" ] && ERRO "Missing: $CONFIG_DIR"
|
||||||
|
[ "$UID" == "0" ] || ERRO "Must be run as root"
|
||||||
|
}
|
||||||
|
|
||||||
|
command -v @LIBEXEC_PREFIX@/bees &> /dev/null || ERRO "Missing 'bees' agent"
|
||||||
|
|
||||||
|
## Parse args
|
||||||
|
ARGUMENTS=()
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-*)
|
||||||
|
ARGUMENTS+=($1)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -z "$UUID" ]; then
|
||||||
|
UUID="$1"
|
||||||
|
else
|
||||||
|
ERRO "Only one filesystem may be supplied"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$UUID" in
|
||||||
|
*-*-*-*-*)
|
||||||
|
FILE_CONFIG=""
|
||||||
|
for file in "$CONFIG_DIR"/*.conf; do
|
||||||
|
[ ! -f "$file" ] && continue
|
||||||
|
if grep -q "$UUID" "$file"; then
|
||||||
|
INFO "Find $UUID in $file, use as conf"
|
||||||
|
FILE_CONFIG="$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ ! -f "$FILE_CONFIG" ] && ERRO "No config for $UUID"
|
||||||
|
source "$FILE_CONFIG"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "beesd [options] <btrfs_uuid>"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
WORK_DIR="${WORK_DIR:-/run/bees/}"
|
||||||
|
MNT_DIR="${MNT_DIR:-$WORK_DIR/mnt/$UUID}"
|
||||||
|
BEESHOME="${BEESHOME:-$MNT_DIR/.beeshome}"
|
||||||
|
BEESSTATUS="${BEESSTATUS:-$WORK_DIR/$UUID.status}"
|
||||||
|
DB_SIZE="${DB_SIZE:-$((64*AL16M))}"
|
||||||
|
LOG_SHORT_PATH="${LOG_SHORT_PATH:-N}"
|
||||||
|
|
||||||
|
INFO "Check: Disk exists"
|
||||||
|
if [ ! -b "/dev/disk/by-uuid/$UUID" ]; then
|
||||||
|
ERRO "Missing disk: /dev/disk/by-uuid/$UUID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_btrfs(){ [ "$(blkid -s TYPE -o value "$1")" == "btrfs" ]; }
|
||||||
|
|
||||||
|
INFO "Check: Disk with btrfs"
|
||||||
|
if ! is_btrfs "/dev/disk/by-uuid/$UUID"; then
|
||||||
|
ERRO "Disk not contain btrfs: /dev/disk/by-uuid/$UUID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
INFO "WORK DIR: $WORK_DIR"
|
||||||
|
mkdir -p "$WORK_DIR" || exit 1
|
||||||
|
|
||||||
|
INFO "MOUNT DIR: $MNT_DIR"
|
||||||
|
mkdir -p "$MNT_DIR" || exit 1
|
||||||
|
|
||||||
|
umount_w(){ mountpoint -q "$1" && umount -l "$1"; }
|
||||||
|
force_umount(){ umount_w "$MNT_DIR"; }
|
||||||
|
trap force_umount SIGINT SIGTERM EXIT
|
||||||
|
|
||||||
|
mount -osubvolid=5 /dev/disk/by-uuid/$UUID "$MNT_DIR" || exit 1
|
||||||
|
|
||||||
|
if [ ! -d "$BEESHOME" ]; then
|
||||||
|
INFO "Create subvol $BEESHOME for store bees data"
|
||||||
|
btrfs sub cre "$BEESHOME"
|
||||||
|
else
|
||||||
|
btrfs sub show "$BEESHOME" &> /dev/null || ERRO "$BEESHOME MUST BE A SUBVOL!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check DB size
|
||||||
|
{
|
||||||
|
DB_PATH="$BEESHOME/beeshash.dat"
|
||||||
|
touch "$DB_PATH"
|
||||||
|
OLD_SIZE="$(du -b "$DB_PATH" | sed 's/\t/ /g' | cut -d' ' -f1)"
|
||||||
|
NEW_SIZE="$DB_SIZE"
|
||||||
|
if (( "$NEW_SIZE"%AL16M > 0 )); then
|
||||||
|
ERRO "DB_SIZE Must be multiple of 16M"
|
||||||
|
fi
|
||||||
|
if (( "$OLD_SIZE" != "$NEW_SIZE" )); then
|
||||||
|
INFO "Resize db: $OLD_SIZE -> $NEW_SIZE"
|
||||||
|
[ -f "$BEESHOME/beescrawl.$UUID.dat" ] && rm "$BEESHOME/beescrawl.$UUID.dat"
|
||||||
|
truncate -s $NEW_SIZE $DB_PATH
|
||||||
|
fi
|
||||||
|
chmod 700 "$DB_PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
MNT_DIR="${MNT_DIR//\/\//\/}"
|
||||||
|
|
||||||
|
filter_path(){
|
||||||
|
if YN $LOG_SHORT_PATH; then
|
||||||
|
sed -e "s#$MNT_DIR##g"
|
||||||
|
else
|
||||||
|
cat
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
@LIBEXEC_PREFIX@/bees ${ARGUMENTS[@]} $OPTIONS "$MNT_DIR" 3>&1 2>&1 | filter_path
|
||||||
|
|
||||||
|
exit 0
|
24
scripts/beesd@.service.in
Normal file
24
scripts/beesd@.service.in
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bees - Best-Effort Extent-Same, a btrfs deduplicator daemon: %i
|
||||||
|
After=local-fs.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/sbin/beesd %i
|
||||||
|
Nice=19
|
||||||
|
KillMode=control-group
|
||||||
|
KillSignal=SIGTERM
|
||||||
|
CPUShares=128
|
||||||
|
StartupCPUShares=256
|
||||||
|
BlockIOWeight=100
|
||||||
|
StartupBlockIOWeight=250
|
||||||
|
IOSchedulingClass=idle
|
||||||
|
IOSchedulingPriority=7
|
||||||
|
CPUSchedulingPolicy=batch
|
||||||
|
Nice=19
|
||||||
|
Restart=on-abnormal
|
||||||
|
CPUAccounting=true
|
||||||
|
MemoryAccounting=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=local-fs.target
|
1
src/.gitignore
vendored
Normal file
1
src/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bees-version.[ch]
|
@@ -14,6 +14,10 @@ depends.mk: Makefile *.cc
|
|||||||
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done > depends.mk.new
|
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done > depends.mk.new
|
||||||
mv -fv depends.mk.new depends.mk
|
mv -fv depends.mk.new depends.mk
|
||||||
|
|
||||||
|
bees-version.c: Makefile *.cc *.h
|
||||||
|
echo "const char *BEES_VERSION = \"$(shell git describe --always --dirty || echo UNKNOWN)\";" > bees-version.new.c
|
||||||
|
mv -f bees-version.new.c bees-version.c
|
||||||
|
|
||||||
-include depends.mk
|
-include depends.mk
|
||||||
|
|
||||||
%.o: %.cc %.h
|
%.o: %.cc %.h
|
||||||
@@ -31,9 +35,11 @@ BEES_OBJS = \
|
|||||||
bees-roots.o \
|
bees-roots.o \
|
||||||
bees-thread.o \
|
bees-thread.o \
|
||||||
bees-types.o \
|
bees-types.o \
|
||||||
|
bees-version.o \
|
||||||
|
|
||||||
../bin/bees: $(BEES_OBJS)
|
../bin/bees: $(BEES_OBJS)
|
||||||
$(CXX) $(CXXFLAGS) -o "$@" $(BEES_OBJS) $(LDFLAGS) $(LIBS)
|
$(CXX) $(CXXFLAGS) -o "$@" $(BEES_OBJS) $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-rm -fv *.o
|
-rm -fv bees-version.h
|
||||||
|
-rm -fv *.o bees-version.c
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace crucible;
|
using namespace crucible;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -23,11 +24,18 @@ getenv_or_die(const char *name)
|
|||||||
BeesFdCache::BeesFdCache()
|
BeesFdCache::BeesFdCache()
|
||||||
{
|
{
|
||||||
m_root_cache.func([&](shared_ptr<BeesContext> ctx, uint64_t root) -> Fd {
|
m_root_cache.func([&](shared_ptr<BeesContext> ctx, uint64_t root) -> Fd {
|
||||||
return ctx->roots()->open_root_nocache(root);
|
Timer open_timer;
|
||||||
|
auto rv = ctx->roots()->open_root_nocache(root);
|
||||||
|
BEESCOUNTADD(open_root_ms, open_timer.age() * 1000);
|
||||||
|
return rv;
|
||||||
});
|
});
|
||||||
m_file_cache.func([&](shared_ptr<BeesContext> ctx, uint64_t root, uint64_t ino) -> Fd {
|
m_file_cache.func([&](shared_ptr<BeesContext> ctx, uint64_t root, uint64_t ino) -> Fd {
|
||||||
return ctx->roots()->open_root_ino_nocache(root, ino);
|
Timer open_timer;
|
||||||
|
auto rv = ctx->roots()->open_root_ino_nocache(root, ino);
|
||||||
|
BEESCOUNTADD(open_ino_ms, open_timer.age() * 1000);
|
||||||
|
return rv;
|
||||||
});
|
});
|
||||||
|
m_file_cache.max_size(BEES_FD_CACHE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
Fd
|
Fd
|
||||||
@@ -48,6 +56,12 @@ BeesFdCache::open_root(shared_ptr<BeesContext> ctx, uint64_t root)
|
|||||||
Fd
|
Fd
|
||||||
BeesFdCache::open_root_ino(shared_ptr<BeesContext> ctx, uint64_t root, uint64_t ino)
|
BeesFdCache::open_root_ino(shared_ptr<BeesContext> ctx, uint64_t root, uint64_t ino)
|
||||||
{
|
{
|
||||||
|
if (m_file_cache_timer.age() > BEES_COMMIT_INTERVAL) {
|
||||||
|
BEESINFO("Clearing open FD cache to enable file delete");
|
||||||
|
m_file_cache.clear();
|
||||||
|
m_file_cache_timer.reset();
|
||||||
|
BEESCOUNT(open_clear);
|
||||||
|
}
|
||||||
return m_file_cache(ctx, root, ino);
|
return m_file_cache(ctx, root, ino);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,97 +72,6 @@ BeesFdCache::insert_root_ino(shared_ptr<BeesContext> ctx, Fd fd)
|
|||||||
return m_file_cache.insert(fd, ctx, fid.root(), fid.ino());
|
return m_file_cache.insert(fd, ctx, fid.root(), fid.ino());
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex BeesWorkQueueBase::s_mutex;
|
|
||||||
set<BeesWorkQueueBase*> BeesWorkQueueBase::s_all_workers;
|
|
||||||
|
|
||||||
BeesWorkQueueBase::BeesWorkQueueBase(const string &name) :
|
|
||||||
m_name(name)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
BeesWorkQueueBase::~BeesWorkQueueBase()
|
|
||||||
{
|
|
||||||
unique_lock<mutex> lock(s_mutex);
|
|
||||||
s_all_workers.erase(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
BeesWorkQueueBase::for_each_work_queue(std::function<void (BeesWorkQueueBase*)> f)
|
|
||||||
{
|
|
||||||
unique_lock<mutex> lock(s_mutex);
|
|
||||||
for (auto i : s_all_workers) {
|
|
||||||
f(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
BeesWorkQueueBase::name() const
|
|
||||||
{
|
|
||||||
return m_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
BeesWorkQueueBase::name(const string &new_name)
|
|
||||||
{
|
|
||||||
m_name = new_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
BeesWorkQueue<Task>::~BeesWorkQueue()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
BeesWorkQueue<Task>::BeesWorkQueue(const string &name) :
|
|
||||||
BeesWorkQueueBase(name)
|
|
||||||
{
|
|
||||||
unique_lock<mutex> lock(s_mutex);
|
|
||||||
s_all_workers.insert(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
void
|
|
||||||
BeesWorkQueue<Task>::push_active(const Task &t)
|
|
||||||
{
|
|
||||||
BEESNOTE("pushing task " << t);
|
|
||||||
m_active_queue.push(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
void
|
|
||||||
BeesWorkQueue<Task>::push_active(const Task &t, size_t limit)
|
|
||||||
{
|
|
||||||
// BEESNOTE("pushing limit " << limit << " task " << t);
|
|
||||||
m_active_queue.push_wait(t, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
size_t
|
|
||||||
BeesWorkQueue<Task>::active_size() const
|
|
||||||
{
|
|
||||||
return m_active_queue.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
list<string>
|
|
||||||
BeesWorkQueue<Task>::peek_active(size_t count) const
|
|
||||||
{
|
|
||||||
list<string> rv;
|
|
||||||
for (auto i : m_active_queue.peek(count)) {
|
|
||||||
ostringstream oss;
|
|
||||||
oss << i;
|
|
||||||
rv.push_back(oss.str());
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
Task
|
|
||||||
BeesWorkQueue<Task>::pop()
|
|
||||||
{
|
|
||||||
return m_active_queue.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
BeesContext::dump_status()
|
BeesContext::dump_status()
|
||||||
{
|
{
|
||||||
@@ -175,12 +98,6 @@ BeesContext::dump_status()
|
|||||||
ofs << "\ttid " << t.first << ": " << t.second << "\n";
|
ofs << "\ttid " << t.first << ": " << t.second << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
BeesWorkQueueBase::for_each_work_queue([&](BeesWorkQueueBase *worker) {
|
|
||||||
ofs << "QUEUE: " << worker->name() << " active: " << worker->active_size() << "\n";
|
|
||||||
for (auto t : worker->peek_active(10)) {
|
|
||||||
ofs << "\t" << t << "\n";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ofs.close();
|
ofs.close();
|
||||||
|
|
||||||
BEESNOTE("renaming status file '" << status_file << "'");
|
BEESNOTE("renaming status file '" << status_file << "'");
|
||||||
@@ -216,10 +133,6 @@ BeesContext::show_progress()
|
|||||||
};
|
};
|
||||||
lastProgressStats = thisStats;
|
lastProgressStats = thisStats;
|
||||||
|
|
||||||
BeesWorkQueueBase::for_each_work_queue([&](BeesWorkQueueBase *worker) {
|
|
||||||
BEESLOG("QUEUE: " << worker->name() << " active: " << worker->active_size());
|
|
||||||
});
|
|
||||||
|
|
||||||
BEESLOG("THREADS:");
|
BEESLOG("THREADS:");
|
||||||
|
|
||||||
for (auto t : BeesNote::get_status()) {
|
for (auto t : BeesNote::get_status()) {
|
||||||
@@ -228,15 +141,24 @@ BeesContext::show_progress()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Fd
|
||||||
|
BeesContext::home_fd()
|
||||||
|
{
|
||||||
|
const char *base_dir = getenv("BEESHOME");
|
||||||
|
if (!base_dir) {
|
||||||
|
base_dir = ".beeshome";
|
||||||
|
}
|
||||||
|
m_home_fd = openat(root_fd(), base_dir, FLAGS_OPEN_DIR);
|
||||||
|
if (!m_home_fd) {
|
||||||
|
THROW_ERRNO("openat: " << name_fd(root_fd()) << " / " << base_dir);
|
||||||
|
}
|
||||||
|
return m_home_fd;
|
||||||
|
}
|
||||||
|
|
||||||
BeesContext::BeesContext(shared_ptr<BeesContext> parent) :
|
BeesContext::BeesContext(shared_ptr<BeesContext> parent) :
|
||||||
m_parent_ctx(parent)
|
m_parent_ctx(parent)
|
||||||
{
|
{
|
||||||
auto base_dir = getenv_or_die("BEESHOME");
|
|
||||||
BEESLOG("BEESHOME = " << base_dir);
|
|
||||||
m_home_fd = open_or_die(base_dir, FLAGS_OPEN_DIR);
|
|
||||||
if (m_parent_ctx) {
|
if (m_parent_ctx) {
|
||||||
m_hash_table = m_parent_ctx->hash_table();
|
|
||||||
m_hash_table->set_shared(true);
|
|
||||||
m_fd_cache = m_parent_ctx->fd_cache();
|
m_fd_cache = m_parent_ctx->fd_cache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,29 +181,16 @@ BeesContext::dedup(const BeesRangePair &brp)
|
|||||||
|
|
||||||
BEESTOOLONG("dedup " << brp);
|
BEESTOOLONG("dedup " << brp);
|
||||||
|
|
||||||
thread_local BeesFileId tl_first_fid, tl_second_fid;
|
|
||||||
if (tl_first_fid != brp.first.fid()) {
|
|
||||||
BEESLOG("dedup: src " << name_fd(brp.first.fd()));
|
|
||||||
tl_first_fid = brp.first.fid();
|
|
||||||
tl_second_fid = BeesFileId();
|
|
||||||
}
|
|
||||||
ostringstream dst_line;
|
|
||||||
dst_line << " dst " << pretty(brp.first.size()) << " [" << to_hex(brp.first.begin()) << ".." << to_hex(brp.first.end()) << "]";
|
|
||||||
if (brp.first.begin() != brp.second.begin()) {
|
|
||||||
dst_line << " [" << to_hex(brp.second.begin()) << ".." << to_hex(brp.second.end()) << "]";
|
|
||||||
}
|
|
||||||
BeesAddress first_addr(brp.first.fd(), brp.first.begin());
|
BeesAddress first_addr(brp.first.fd(), brp.first.begin());
|
||||||
BeesAddress second_addr(brp.second.fd(), brp.second.begin());
|
BeesAddress second_addr(brp.second.fd(), brp.second.begin());
|
||||||
dst_line << " (" << first_addr << "->" << second_addr << ")";
|
|
||||||
|
BEESLOG("dedup: src " << pretty(brp.first.size()) << " [" << to_hex(brp.first.begin()) << ".." << to_hex(brp.first.end()) << "] {" << first_addr << "} " << name_fd(brp.first.fd()));
|
||||||
|
BEESLOG(" dst " << pretty(brp.second.size()) << " [" << to_hex(brp.second.begin()) << ".." << to_hex(brp.second.end()) << "] {" << second_addr << "} " << name_fd(brp.second.fd()));
|
||||||
|
|
||||||
if (first_addr.get_physical_or_zero() == second_addr.get_physical_or_zero()) {
|
if (first_addr.get_physical_or_zero() == second_addr.get_physical_or_zero()) {
|
||||||
BEESLOGTRACE("equal physical addresses in dedup");
|
BEESLOGTRACE("equal physical addresses in dedup");
|
||||||
BEESCOUNT(bug_dedup_same_physical);
|
BEESCOUNT(bug_dedup_same_physical);
|
||||||
}
|
}
|
||||||
if (tl_second_fid != brp.second.fid()) {
|
|
||||||
dst_line << " " << name_fd(brp.second.fd());
|
|
||||||
tl_second_fid = brp.second.fid();
|
|
||||||
}
|
|
||||||
BEESLOG(dst_line.str());
|
|
||||||
|
|
||||||
THROW_CHECK1(invalid_argument, brp, !brp.first.overlaps(brp.second));
|
THROW_CHECK1(invalid_argument, brp, !brp.first.overlaps(brp.second));
|
||||||
THROW_CHECK1(invalid_argument, brp, brp.first.size() == brp.second.size());
|
THROW_CHECK1(invalid_argument, brp, brp.first.size() == brp.second.size());
|
||||||
@@ -326,6 +235,7 @@ BeesContext::rewrite_file_range(const BeesFileRange &bfr)
|
|||||||
// BEESLOG("\torig_bbd " << orig_bbd);
|
// BEESLOG("\torig_bbd " << orig_bbd);
|
||||||
BeesBlockData dup_bbd(dup_brp.first.fd(), dup_brp.first.begin(), min(BLOCK_SIZE_SUMS, dup_brp.first.size()));
|
BeesBlockData dup_bbd(dup_brp.first.fd(), dup_brp.first.begin(), min(BLOCK_SIZE_SUMS, dup_brp.first.size()));
|
||||||
// BEESLOG("BeesResolver br(..., " << bfr << ")");
|
// BEESLOG("BeesResolver br(..., " << bfr << ")");
|
||||||
|
BEESTRACE("BeesContext::rewrite_file_range calling BeesResolver " << bfr);
|
||||||
BeesResolver br(m_ctx, BeesAddress(bfr.fd(), bfr.begin()));
|
BeesResolver br(m_ctx, BeesAddress(bfr.fd(), bfr.begin()));
|
||||||
// BEESLOG("\treplace_src " << dup_bbd);
|
// BEESLOG("\treplace_src " << dup_bbd);
|
||||||
br.replace_src(dup_bbd);
|
br.replace_src(dup_bbd);
|
||||||
@@ -521,6 +431,7 @@ BeesContext::scan_one_extent(const BeesFileRange &bfr, const Extent &e)
|
|||||||
if (found_addr.is_toxic()) {
|
if (found_addr.is_toxic()) {
|
||||||
BEESINFO("WORKAROUND: abandoned toxic match for hash " << hash << " addr " << found_addr);
|
BEESINFO("WORKAROUND: abandoned toxic match for hash " << hash << " addr " << found_addr);
|
||||||
// Don't push these back in because we'll never delete them.
|
// Don't push these back in because we'll never delete them.
|
||||||
|
// Extents may become non-toxic so give them a chance to expire.
|
||||||
// hash_table->push_front_hash_addr(hash, found_addr);
|
// hash_table->push_front_hash_addr(hash, found_addr);
|
||||||
BEESCOUNT(scan_toxic_hash);
|
BEESCOUNT(scan_toxic_hash);
|
||||||
return bfr;
|
return bfr;
|
||||||
@@ -531,17 +442,16 @@ BeesContext::scan_one_extent(const BeesFileRange &bfr, const Extent &e)
|
|||||||
catch_all([&]() {
|
catch_all([&]() {
|
||||||
BEESNOTE("resolving " << found_addr << " matched " << bbd);
|
BEESNOTE("resolving " << found_addr << " matched " << bbd);
|
||||||
BEESTRACE("resolving " << found_addr << " matched " << bbd);
|
BEESTRACE("resolving " << found_addr << " matched " << bbd);
|
||||||
|
BEESTRACE("BeesContext::scan_one_extent calling BeesResolver " << found_addr);
|
||||||
BeesResolver resolved(m_ctx, found_addr);
|
BeesResolver resolved(m_ctx, found_addr);
|
||||||
// Toxic extents are really toxic
|
// Toxic extents are really toxic
|
||||||
if (resolved.is_toxic()) {
|
if (resolved.is_toxic()) {
|
||||||
BEESINFO("WORKAROUND: abandoned toxic match at found_addr " << found_addr << " matching bbd " << bbd);
|
BEESINFO("WORKAROUND: abandoned toxic match at found_addr " << found_addr << " matching bbd " << bbd);
|
||||||
BEESCOUNT(scan_toxic_match);
|
BEESCOUNT(scan_toxic_match);
|
||||||
#if 0
|
// Make sure we never see this hash again.
|
||||||
// Don't push these back in because we'll never delete them.
|
// It has become toxic since it was inserted into the hash table.
|
||||||
// Make sure we never see this hash again
|
|
||||||
found_addr.set_toxic();
|
found_addr.set_toxic();
|
||||||
hash_table->push_front_hash_addr(hash, found_addr);
|
hash_table->push_front_hash_addr(hash, found_addr);
|
||||||
#endif
|
|
||||||
abandon_extent = true;
|
abandon_extent = true;
|
||||||
} else if (!resolved.count()) {
|
} else if (!resolved.count()) {
|
||||||
BEESCOUNT(scan_resolve_zero);
|
BEESCOUNT(scan_resolve_zero);
|
||||||
@@ -763,13 +673,7 @@ BeesContext::scan_one_extent(const BeesFileRange &bfr, const Extent &e)
|
|||||||
|
|
||||||
// Visualize
|
// Visualize
|
||||||
if (bar != string(block_count, '.')) {
|
if (bar != string(block_count, '.')) {
|
||||||
thread_local BeesFileId last_fid;
|
BEESLOG("scan: " << pretty(e.size()) << " " << to_hex(e.begin()) << " [" << bar << "] " << to_hex(e.end()) << ' ' << name_fd(bfr.fd()));
|
||||||
string file_name;
|
|
||||||
if (bfr.fid() != last_fid) {
|
|
||||||
last_fid = bfr.fid();
|
|
||||||
file_name = " " + name_fd(bfr.fd());
|
|
||||||
}
|
|
||||||
BEESLOG("scan: " << pretty(e.size()) << " " << to_hex(e.begin()) << " [" << bar << "] " << to_hex(e.end()) << file_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bfr;
|
return bfr;
|
||||||
@@ -953,7 +857,8 @@ BeesContext::tmpfile()
|
|||||||
if (!m_tmpfiles[this_thread::get_id()]) {
|
if (!m_tmpfiles[this_thread::get_id()]) {
|
||||||
m_tmpfiles[this_thread::get_id()] = make_shared<BeesTempFile>(shared_from_this());
|
m_tmpfiles[this_thread::get_id()] = make_shared<BeesTempFile>(shared_from_this());
|
||||||
}
|
}
|
||||||
return m_tmpfiles[this_thread::get_id()];
|
auto rv = m_tmpfiles[this_thread::get_id()];
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<BeesFdCache>
|
shared_ptr<BeesFdCache>
|
||||||
@@ -964,7 +869,8 @@ BeesContext::fd_cache()
|
|||||||
if (!m_fd_cache) {
|
if (!m_fd_cache) {
|
||||||
m_fd_cache = make_shared<BeesFdCache>();
|
m_fd_cache = make_shared<BeesFdCache>();
|
||||||
}
|
}
|
||||||
return m_fd_cache;
|
auto rv = m_fd_cache;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<BeesRoots>
|
shared_ptr<BeesRoots>
|
||||||
@@ -975,7 +881,8 @@ BeesContext::roots()
|
|||||||
if (!m_roots) {
|
if (!m_roots) {
|
||||||
m_roots = make_shared<BeesRoots>(shared_from_this());
|
m_roots = make_shared<BeesRoots>(shared_from_this());
|
||||||
}
|
}
|
||||||
return m_roots;
|
auto rv = m_roots;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<BeesHashTable>
|
shared_ptr<BeesHashTable>
|
||||||
@@ -986,7 +893,8 @@ BeesContext::hash_table()
|
|||||||
if (!m_hash_table) {
|
if (!m_hash_table) {
|
||||||
m_hash_table = make_shared<BeesHashTable>(shared_from_this(), "beeshash.dat");
|
m_hash_table = make_shared<BeesHashTable>(shared_from_this(), "beeshash.dat");
|
||||||
}
|
}
|
||||||
return m_hash_table;
|
auto rv = m_hash_table;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1002,8 +910,3 @@ BeesContext::insert_root_ino(Fd fd)
|
|||||||
{
|
{
|
||||||
fd_cache()->insert_root_ino(shared_from_this(), fd);
|
fd_cache()->insert_root_ino(shared_from_this(), fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// instantiate templates for linkage ----------------------------------------
|
|
||||||
|
|
||||||
template class BeesWorkQueue<BeesFileRange>;
|
|
||||||
template class BeesWorkQueue<BeesRangePair>;
|
|
||||||
|
149
src/bees-hash.cc
149
src/bees-hash.cc
@@ -11,13 +11,6 @@
|
|||||||
using namespace crucible;
|
using namespace crucible;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
static inline
|
|
||||||
bool
|
|
||||||
using_any_madvise()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ostream &
|
ostream &
|
||||||
operator<<(ostream &os, const BeesHash &bh)
|
operator<<(ostream &os, const BeesHash &bh)
|
||||||
{
|
{
|
||||||
@@ -101,8 +94,6 @@ BeesHashTable::get_extent_range(HashType hash)
|
|||||||
void
|
void
|
||||||
BeesHashTable::flush_dirty_extents()
|
BeesHashTable::flush_dirty_extents()
|
||||||
{
|
{
|
||||||
if (using_shared_map()) return;
|
|
||||||
|
|
||||||
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
||||||
|
|
||||||
unique_lock<mutex> lock(m_extent_mutex);
|
unique_lock<mutex> lock(m_extent_mutex);
|
||||||
@@ -124,16 +115,12 @@ BeesHashTable::flush_dirty_extents()
|
|||||||
uint8_t *dirty_extent_end = m_extent_ptr[extent_number + 1].p_byte;
|
uint8_t *dirty_extent_end = m_extent_ptr[extent_number + 1].p_byte;
|
||||||
THROW_CHECK1(out_of_range, dirty_extent, dirty_extent >= m_byte_ptr);
|
THROW_CHECK1(out_of_range, dirty_extent, dirty_extent >= m_byte_ptr);
|
||||||
THROW_CHECK1(out_of_range, dirty_extent_end, dirty_extent_end <= m_byte_ptr_end);
|
THROW_CHECK1(out_of_range, dirty_extent_end, dirty_extent_end <= m_byte_ptr_end);
|
||||||
if (using_shared_map()) {
|
THROW_CHECK2(out_of_range, dirty_extent_end, dirty_extent, dirty_extent_end - dirty_extent == BLOCK_SIZE_HASHTAB_EXTENT);
|
||||||
BEESTOOLONG("flush extent " << extent_number);
|
BEESTOOLONG("pwrite(fd " << m_fd << " '" << name_fd(m_fd)<< "', length " << to_hex(dirty_extent_end - dirty_extent) << ", offset " << to_hex(dirty_extent - m_byte_ptr) << ")");
|
||||||
copy(dirty_extent, dirty_extent_end, dirty_extent);
|
// Page locks slow us down more than copying the data does
|
||||||
} else {
|
vector<uint8_t> extent_copy(dirty_extent, dirty_extent_end);
|
||||||
BEESTOOLONG("pwrite(fd " << m_fd << " '" << name_fd(m_fd)<< "', length " << to_hex(dirty_extent_end - dirty_extent) << ", offset " << to_hex(dirty_extent - m_byte_ptr) << ")");
|
pwrite_or_die(m_fd, extent_copy, dirty_extent - m_byte_ptr);
|
||||||
// Page locks slow us down more than copying the data does
|
BEESCOUNT(hash_extent_out);
|
||||||
vector<uint8_t> extent_copy(dirty_extent, dirty_extent_end);
|
|
||||||
pwrite_or_die(m_fd, extent_copy, dirty_extent - m_byte_ptr);
|
|
||||||
BEESCOUNT(hash_extent_out);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
BEESNOTE("flush rate limited at extent #" << extent_number << " (" << extent_counter << " of " << dirty_extent_copy.size() << ")");
|
BEESNOTE("flush rate limited at extent #" << extent_number << " (" << extent_counter << " of " << dirty_extent_copy.size() << ")");
|
||||||
m_flush_rate_limit.sleep_for(BLOCK_SIZE_HASHTAB_EXTENT);
|
m_flush_rate_limit.sleep_for(BLOCK_SIZE_HASHTAB_EXTENT);
|
||||||
@@ -143,7 +130,6 @@ BeesHashTable::flush_dirty_extents()
|
|||||||
void
|
void
|
||||||
BeesHashTable::set_extent_dirty(HashType hash)
|
BeesHashTable::set_extent_dirty(HashType hash)
|
||||||
{
|
{
|
||||||
if (using_shared_map()) return;
|
|
||||||
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
||||||
auto pr = get_extent_range(hash);
|
auto pr = get_extent_range(hash);
|
||||||
uint64_t extent_number = reinterpret_cast<Extent *>(pr.first) - m_extent_ptr;
|
uint64_t extent_number = reinterpret_cast<Extent *>(pr.first) - m_extent_ptr;
|
||||||
@@ -156,10 +142,8 @@ BeesHashTable::set_extent_dirty(HashType hash)
|
|||||||
void
|
void
|
||||||
BeesHashTable::writeback_loop()
|
BeesHashTable::writeback_loop()
|
||||||
{
|
{
|
||||||
if (!using_shared_map()) {
|
while (true) {
|
||||||
while (1) {
|
flush_dirty_extents();
|
||||||
flush_dirty_extents();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,9 +259,10 @@ BeesHashTable::prefetch_loop()
|
|||||||
|
|
||||||
graph_blob << "Now: " << format_time(time(NULL)) << "\n";
|
graph_blob << "Now: " << format_time(time(NULL)) << "\n";
|
||||||
graph_blob << "Uptime: " << m_ctx->total_timer().age() << " seconds\n";
|
graph_blob << "Uptime: " << m_ctx->total_timer().age() << " seconds\n";
|
||||||
|
graph_blob << "Version: " << BEES_VERSION << "\n";
|
||||||
|
|
||||||
graph_blob
|
graph_blob
|
||||||
<< "\nHash table page occupancy histogram (" << occupied_count << "/" << total_count << " cells occupied, " << (occupied_count * 100 / total_count) << "%)\n"
|
<< "\nHash table page occupancy histogram (" << occupied_count << "/" << total_count << " cells occupied, " << (occupied_count * 100 / total_count) << "%)\n"
|
||||||
<< out.str() << "0% | 25% | 50% | 75% | 100% page fill\n"
|
<< out.str() << "0% | 25% | 50% | 75% | 100% page fill\n"
|
||||||
<< "compressed " << compressed_count << " (" << percent(compressed_count, occupied_count) << ")"
|
<< "compressed " << compressed_count << " (" << percent(compressed_count, occupied_count) << ")"
|
||||||
<< " new-style " << compressed_offset_count << " (" << percent(compressed_offset_count, occupied_count) << ")"
|
<< " new-style " << compressed_offset_count << " (" << percent(compressed_offset_count, occupied_count) << ")"
|
||||||
@@ -310,7 +295,6 @@ void
|
|||||||
BeesHashTable::fetch_missing_extent(HashType hash)
|
BeesHashTable::fetch_missing_extent(HashType hash)
|
||||||
{
|
{
|
||||||
BEESTOOLONG("fetch_missing_extent for hash " << to_hex(hash));
|
BEESTOOLONG("fetch_missing_extent for hash " << to_hex(hash));
|
||||||
if (using_shared_map()) return;
|
|
||||||
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
||||||
auto pr = get_extent_range(hash);
|
auto pr = get_extent_range(hash);
|
||||||
uint64_t extent_number = reinterpret_cast<Extent *>(pr.first) - m_extent_ptr;
|
uint64_t extent_number = reinterpret_cast<Extent *>(pr.first) - m_extent_ptr;
|
||||||
@@ -324,10 +308,10 @@ BeesHashTable::fetch_missing_extent(HashType hash)
|
|||||||
size_t missing_buckets = m_buckets_missing.size();
|
size_t missing_buckets = m_buckets_missing.size();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
BEESNOTE("fetch waiting for hash extent #" << extent_number << ", " << missing_buckets << " left to fetch");
|
BEESNOTE("waiting to fetch hash extent #" << extent_number << ", " << missing_buckets << " left to fetch");
|
||||||
|
|
||||||
// Acquire blocking lock on this extent only
|
// Acquire blocking lock on this extent only
|
||||||
LockSet<uint64_t>::Lock extent_lock(m_extent_lock_set, extent_number);
|
auto extent_lock = m_extent_lock_set.make_lock(extent_number);
|
||||||
|
|
||||||
// Check missing again because someone else might have fetched this
|
// Check missing again because someone else might have fetched this
|
||||||
// extent for us while we didn't hold any locks
|
// extent for us while we didn't hold any locks
|
||||||
@@ -351,9 +335,6 @@ BeesHashTable::fetch_missing_extent(HashType hash)
|
|||||||
}
|
}
|
||||||
|
|
||||||
BEESCOUNT(hash_extent_in);
|
BEESCOUNT(hash_extent_in);
|
||||||
// We don't block when fetching an extent but we do slow down the
|
|
||||||
// prefetch thread.
|
|
||||||
m_prefetch_rate_limit.borrow(BLOCK_SIZE_HASHTAB_EXTENT);
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
m_buckets_missing.erase(extent_number);
|
m_buckets_missing.erase(extent_number);
|
||||||
}
|
}
|
||||||
@@ -396,7 +377,6 @@ BeesHashTable::find_cell(HashType hash)
|
|||||||
void
|
void
|
||||||
BeesHashTable::erase_hash_addr(HashType hash, AddrType addr)
|
BeesHashTable::erase_hash_addr(HashType hash, AddrType addr)
|
||||||
{
|
{
|
||||||
// if (m_shared) return;
|
|
||||||
fetch_missing_extent(hash);
|
fetch_missing_extent(hash);
|
||||||
BEESTOOLONG("erase hash " << to_hex(hash) << " addr " << addr);
|
BEESTOOLONG("erase hash " << to_hex(hash) << " addr " << addr);
|
||||||
unique_lock<mutex> lock(m_bucket_mutex);
|
unique_lock<mutex> lock(m_bucket_mutex);
|
||||||
@@ -574,12 +554,36 @@ BeesHashTable::try_mmap_flags(int flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
BeesHashTable::set_shared(bool shared)
|
BeesHashTable::open_file()
|
||||||
{
|
{
|
||||||
m_shared = shared;
|
// OK open hash table
|
||||||
|
BEESNOTE("opening hash table '" << m_filename << "' target size " << m_size << " (" << pretty(m_size) << ")");
|
||||||
|
|
||||||
|
// Try to open existing hash table
|
||||||
|
Fd new_fd = openat(m_ctx->home_fd(), m_filename.c_str(), FLAGS_OPEN_FILE_RW, 0700);
|
||||||
|
|
||||||
|
// If that doesn't work, try to make a new one
|
||||||
|
if (!new_fd) {
|
||||||
|
string tmp_filename = m_filename + ".tmp";
|
||||||
|
BEESLOGNOTE("creating new hash table '" << tmp_filename << "'");
|
||||||
|
unlinkat(m_ctx->home_fd(), tmp_filename.c_str(), 0);
|
||||||
|
new_fd = openat_or_die(m_ctx->home_fd(), tmp_filename, FLAGS_CREATE_FILE, 0700);
|
||||||
|
BEESLOGNOTE("truncating new hash table '" << tmp_filename << "' size " << m_size << " (" << pretty(m_size) << ")");
|
||||||
|
ftruncate_or_die(new_fd, m_size);
|
||||||
|
BEESLOGNOTE("truncating new hash table '" << tmp_filename << "' -> '" << m_filename << "'");
|
||||||
|
renameat_or_die(m_ctx->home_fd(), tmp_filename, m_ctx->home_fd(), m_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stat st(new_fd);
|
||||||
|
off_t new_size = st.st_size;
|
||||||
|
|
||||||
|
THROW_CHECK1(invalid_argument, new_size, new_size > 0);
|
||||||
|
THROW_CHECK1(invalid_argument, new_size, (new_size % BLOCK_SIZE_HASHTAB_EXTENT) == 0);
|
||||||
|
m_size = new_size;
|
||||||
|
m_fd = new_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
BeesHashTable::BeesHashTable(shared_ptr<BeesContext> ctx, string filename) :
|
BeesHashTable::BeesHashTable(shared_ptr<BeesContext> ctx, string filename, off_t size) :
|
||||||
m_ctx(ctx),
|
m_ctx(ctx),
|
||||||
m_size(0),
|
m_size(0),
|
||||||
m_void_ptr(nullptr),
|
m_void_ptr(nullptr),
|
||||||
@@ -587,35 +591,29 @@ BeesHashTable::BeesHashTable(shared_ptr<BeesContext> ctx, string filename) :
|
|||||||
m_buckets(0),
|
m_buckets(0),
|
||||||
m_cells(0),
|
m_cells(0),
|
||||||
m_writeback_thread("hash_writeback"),
|
m_writeback_thread("hash_writeback"),
|
||||||
m_prefetch_thread("hash_prefetch " + m_ctx->root_path()),
|
m_prefetch_thread("hash_prefetch"),
|
||||||
m_flush_rate_limit(BEES_FLUSH_RATE),
|
m_flush_rate_limit(BEES_FLUSH_RATE),
|
||||||
m_prefetch_rate_limit(BEES_FLUSH_RATE),
|
|
||||||
m_stats_file(m_ctx->home_fd(), "beesstats.txt")
|
m_stats_file(m_ctx->home_fd(), "beesstats.txt")
|
||||||
{
|
{
|
||||||
BEESNOTE("opening hash table " << filename);
|
// Sanity checks to protect the implementation from its weaknesses
|
||||||
|
|
||||||
m_fd = openat_or_die(m_ctx->home_fd(), filename, FLAGS_OPEN_FILE_RW, 0700);
|
|
||||||
Stat st(m_fd);
|
|
||||||
m_size = st.st_size;
|
|
||||||
|
|
||||||
BEESTRACE("hash table size " << m_size);
|
|
||||||
BEESTRACE("hash table bucket size " << BLOCK_SIZE_HASHTAB_BUCKET);
|
|
||||||
BEESTRACE("hash table extent size " << BLOCK_SIZE_HASHTAB_EXTENT);
|
|
||||||
|
|
||||||
THROW_CHECK2(invalid_argument, BLOCK_SIZE_HASHTAB_BUCKET, BLOCK_SIZE_HASHTAB_EXTENT, (BLOCK_SIZE_HASHTAB_EXTENT % BLOCK_SIZE_HASHTAB_BUCKET) == 0);
|
THROW_CHECK2(invalid_argument, BLOCK_SIZE_HASHTAB_BUCKET, BLOCK_SIZE_HASHTAB_EXTENT, (BLOCK_SIZE_HASHTAB_EXTENT % BLOCK_SIZE_HASHTAB_BUCKET) == 0);
|
||||||
|
|
||||||
// Does the union work?
|
|
||||||
THROW_CHECK2(runtime_error, m_void_ptr, m_cell_ptr, m_void_ptr == m_cell_ptr);
|
|
||||||
THROW_CHECK2(runtime_error, m_void_ptr, m_byte_ptr, m_void_ptr == m_byte_ptr);
|
|
||||||
THROW_CHECK2(runtime_error, m_void_ptr, m_bucket_ptr, m_void_ptr == m_bucket_ptr);
|
|
||||||
THROW_CHECK2(runtime_error, m_void_ptr, m_extent_ptr, m_void_ptr == m_extent_ptr);
|
|
||||||
|
|
||||||
// There's more than one union
|
// There's more than one union
|
||||||
THROW_CHECK2(runtime_error, sizeof(Bucket), BLOCK_SIZE_HASHTAB_BUCKET, BLOCK_SIZE_HASHTAB_BUCKET == sizeof(Bucket));
|
THROW_CHECK2(runtime_error, sizeof(Bucket), BLOCK_SIZE_HASHTAB_BUCKET, BLOCK_SIZE_HASHTAB_BUCKET == sizeof(Bucket));
|
||||||
THROW_CHECK2(runtime_error, sizeof(Bucket::p_byte), BLOCK_SIZE_HASHTAB_BUCKET, BLOCK_SIZE_HASHTAB_BUCKET == sizeof(Bucket::p_byte));
|
THROW_CHECK2(runtime_error, sizeof(Bucket::p_byte), BLOCK_SIZE_HASHTAB_BUCKET, BLOCK_SIZE_HASHTAB_BUCKET == sizeof(Bucket::p_byte));
|
||||||
THROW_CHECK2(runtime_error, sizeof(Extent), BLOCK_SIZE_HASHTAB_EXTENT, BLOCK_SIZE_HASHTAB_EXTENT == sizeof(Extent));
|
THROW_CHECK2(runtime_error, sizeof(Extent), BLOCK_SIZE_HASHTAB_EXTENT, BLOCK_SIZE_HASHTAB_EXTENT == sizeof(Extent));
|
||||||
THROW_CHECK2(runtime_error, sizeof(Extent::p_byte), BLOCK_SIZE_HASHTAB_EXTENT, BLOCK_SIZE_HASHTAB_EXTENT == sizeof(Extent::p_byte));
|
THROW_CHECK2(runtime_error, sizeof(Extent::p_byte), BLOCK_SIZE_HASHTAB_EXTENT, BLOCK_SIZE_HASHTAB_EXTENT == sizeof(Extent::p_byte));
|
||||||
|
|
||||||
|
m_filename = filename;
|
||||||
|
m_size = size;
|
||||||
|
open_file();
|
||||||
|
|
||||||
|
// Now we know size we can compute stuff
|
||||||
|
|
||||||
|
BEESTRACE("hash table size " << m_size);
|
||||||
|
BEESTRACE("hash table bucket size " << BLOCK_SIZE_HASHTAB_BUCKET);
|
||||||
|
BEESTRACE("hash table extent size " << BLOCK_SIZE_HASHTAB_EXTENT);
|
||||||
|
|
||||||
BEESLOG("opened hash table filename '" << filename << "' length " << m_size);
|
BEESLOG("opened hash table filename '" << filename << "' length " << m_size);
|
||||||
m_buckets = m_size / BLOCK_SIZE_HASHTAB_BUCKET;
|
m_buckets = m_size / BLOCK_SIZE_HASHTAB_BUCKET;
|
||||||
m_cells = m_buckets * c_cells_per_bucket;
|
m_cells = m_buckets * c_cells_per_bucket;
|
||||||
@@ -624,29 +622,40 @@ BeesHashTable::BeesHashTable(shared_ptr<BeesContext> ctx, string filename) :
|
|||||||
|
|
||||||
BEESLOG("\tflush rate limit " << BEES_FLUSH_RATE);
|
BEESLOG("\tflush rate limit " << BEES_FLUSH_RATE);
|
||||||
|
|
||||||
if (using_shared_map()) {
|
// Try to mmap that much memory
|
||||||
try_mmap_flags(MAP_SHARED);
|
try_mmap_flags(MAP_PRIVATE | MAP_ANONYMOUS);
|
||||||
} else {
|
|
||||||
try_mmap_flags(MAP_PRIVATE | MAP_ANONYMOUS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_cell_ptr) {
|
if (!m_cell_ptr) {
|
||||||
THROW_ERROR(runtime_error, "unable to mmap " << filename);
|
THROW_ERRNO("unable to mmap " << filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!using_shared_map()) {
|
// Do unions work the way we think (and rely on)?
|
||||||
// madvise fails if MAP_SHARED
|
THROW_CHECK2(runtime_error, m_void_ptr, m_cell_ptr, m_void_ptr == m_cell_ptr);
|
||||||
if (using_any_madvise()) {
|
THROW_CHECK2(runtime_error, m_void_ptr, m_byte_ptr, m_void_ptr == m_byte_ptr);
|
||||||
// DONTFORK because we sometimes do fork,
|
THROW_CHECK2(runtime_error, m_void_ptr, m_bucket_ptr, m_void_ptr == m_bucket_ptr);
|
||||||
// but the child doesn't touch any of the many, many pages
|
THROW_CHECK2(runtime_error, m_void_ptr, m_extent_ptr, m_void_ptr == m_extent_ptr);
|
||||||
BEESTOOLONG("madvise(MADV_HUGEPAGE | MADV_DONTFORK)");
|
|
||||||
DIE_IF_NON_ZERO(madvise(m_byte_ptr, m_size, MADV_HUGEPAGE | MADV_DONTFORK));
|
// Give all the madvise hints that the kernel understands
|
||||||
}
|
const struct madv_flag {
|
||||||
for (uint64_t i = 0; i < m_size / sizeof(Extent); ++i) {
|
const char *name;
|
||||||
m_buckets_missing.insert(i);
|
int value;
|
||||||
|
} madv_flags[] = {
|
||||||
|
{ .name = "MADV_HUGEPAGE", .value = MADV_HUGEPAGE },
|
||||||
|
{ .name = "MADV_DONTFORK", .value = MADV_DONTFORK },
|
||||||
|
{ .name = "MADV_DONTDUMP", .value = MADV_DONTDUMP },
|
||||||
|
{ .name = "", .value = 0 },
|
||||||
|
};
|
||||||
|
for (auto fp = madv_flags; fp->value; ++fp) {
|
||||||
|
BEESTOOLONG("madvise(" << fp->name << ")");
|
||||||
|
if (madvise(m_byte_ptr, m_size, fp->value)) {
|
||||||
|
BEESLOG("madvise(..., " << fp->name << "): " << strerror(errno) << " (ignored)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < m_size / sizeof(Extent); ++i) {
|
||||||
|
m_buckets_missing.insert(i);
|
||||||
|
}
|
||||||
|
|
||||||
m_writeback_thread.exec([&]() {
|
m_writeback_thread.exec([&]() {
|
||||||
writeback_loop();
|
writeback_loop();
|
||||||
});
|
});
|
||||||
|
@@ -105,7 +105,7 @@ BeesResolver::adjust_offset(const BeesFileRange &haystack, const BeesBlockData &
|
|||||||
bool is_legacy = false;
|
bool is_legacy = false;
|
||||||
if (m_addr.is_compressed()) {
|
if (m_addr.is_compressed()) {
|
||||||
BtrfsExtentWalker ew(haystack.fd(), haystack.begin(), m_ctx->root_fd());
|
BtrfsExtentWalker ew(haystack.fd(), haystack.begin(), m_ctx->root_fd());
|
||||||
BEESTRACE("haystack extent data " << ew);
|
BEESTRACE("haystack extent data " << ew);
|
||||||
Extent e = ew.current();
|
Extent e = ew.current();
|
||||||
if (m_addr.has_compressed_offset()) {
|
if (m_addr.has_compressed_offset()) {
|
||||||
off_t coff = m_addr.get_compressed_offset();
|
off_t coff = m_addr.get_compressed_offset();
|
||||||
@@ -196,7 +196,7 @@ BeesResolver::chase_extent_ref(const BtrfsInodeOffsetRoot &bior, BeesBlockData &
|
|||||||
|
|
||||||
Fd file_fd = m_ctx->roots()->open_root_ino(bior.m_root, bior.m_inum);
|
Fd file_fd = m_ctx->roots()->open_root_ino(bior.m_root, bior.m_inum);
|
||||||
if (!file_fd) {
|
if (!file_fd) {
|
||||||
// Delete snapshots generate craptons of these
|
// Deleted snapshots generate craptons of these
|
||||||
// BEESINFO("No FD in chase_extent_ref " << bior);
|
// BEESINFO("No FD in chase_extent_ref " << bior);
|
||||||
BEESCOUNT(chase_no_fd);
|
BEESCOUNT(chase_no_fd);
|
||||||
return BeesFileRange();
|
return BeesFileRange();
|
||||||
@@ -378,7 +378,10 @@ BeesResolver::for_each_extent_ref(BeesBlockData bbd, function<bool(const BeesFil
|
|||||||
// We have reliable block addresses now, so we guarantee we can hit the desired block.
|
// We have reliable block addresses now, so we guarantee we can hit the desired block.
|
||||||
// Failure in chase_extent_ref means we are done, and don't need to look up all the
|
// Failure in chase_extent_ref means we are done, and don't need to look up all the
|
||||||
// other references.
|
// other references.
|
||||||
stop_now = true;
|
// Or...not? If we have a compressed extent, some refs will not match
|
||||||
|
// if there is are two references to the same extent with a reference
|
||||||
|
// to a different extent between them.
|
||||||
|
// stop_now = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -477,11 +480,6 @@ BeesResolver::find_all_matches(BeesBlockData &bbd)
|
|||||||
bool
|
bool
|
||||||
BeesResolver::operator<(const BeesResolver &that) const
|
BeesResolver::operator<(const BeesResolver &that) const
|
||||||
{
|
{
|
||||||
if (that.m_bior_count < m_bior_count) {
|
// Lowest count, highest address
|
||||||
return true;
|
return tie(that.m_bior_count, m_addr) < tie(m_bior_count, that.m_addr);
|
||||||
} else if (m_bior_count < that.m_bior_count) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return m_addr < that.m_addr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,17 +42,26 @@ BeesCrawlState::BeesCrawlState() :
|
|||||||
bool
|
bool
|
||||||
BeesCrawlState::operator<(const BeesCrawlState &that) const
|
BeesCrawlState::operator<(const BeesCrawlState &that) const
|
||||||
{
|
{
|
||||||
return tie(m_root, m_objectid, m_offset, m_min_transid, m_max_transid)
|
return tie(m_objectid, m_offset, m_root, m_min_transid, m_max_transid)
|
||||||
< tie(that.m_root, that.m_objectid, that.m_offset, that.m_min_transid, that.m_max_transid);
|
< tie(that.m_objectid, that.m_offset, that.m_root, that.m_min_transid, that.m_max_transid);
|
||||||
}
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
BeesRoots::crawl_state_filename() const
|
BeesRoots::crawl_state_filename() const
|
||||||
{
|
{
|
||||||
string rv;
|
string rv;
|
||||||
|
|
||||||
|
// Legacy filename included UUID
|
||||||
rv += "beescrawl.";
|
rv += "beescrawl.";
|
||||||
rv += m_ctx->root_uuid();
|
rv += m_ctx->root_uuid();
|
||||||
rv += ".dat";
|
rv += ".dat";
|
||||||
|
|
||||||
|
struct stat buf;
|
||||||
|
if (fstatat(m_ctx->home_fd(), rv.c_str(), &buf, AT_SYMLINK_NOFOLLOW)) {
|
||||||
|
// Use new filename
|
||||||
|
rv = "beescrawl.dat";
|
||||||
|
}
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +110,12 @@ BeesRoots::state_save()
|
|||||||
|
|
||||||
m_crawl_state_file.write(ofs.str());
|
m_crawl_state_file.write(ofs.str());
|
||||||
|
|
||||||
|
// Renaming things is hard after release
|
||||||
|
if (m_crawl_state_file.name() != "beescrawl.dat") {
|
||||||
|
renameat(m_ctx->home_fd(), m_crawl_state_file.name().c_str(), m_ctx->home_fd(), "beescrawl.dat");
|
||||||
|
m_crawl_state_file.name("beescrawl.dat");
|
||||||
|
}
|
||||||
|
|
||||||
BEESNOTE("relocking crawl state");
|
BEESNOTE("relocking crawl state");
|
||||||
lock.lock();
|
lock.lock();
|
||||||
// Not really correct but probably close enough
|
// Not really correct but probably close enough
|
||||||
@@ -159,9 +174,9 @@ BeesRoots::transid_min()
|
|||||||
uint64_t
|
uint64_t
|
||||||
BeesRoots::transid_max()
|
BeesRoots::transid_max()
|
||||||
{
|
{
|
||||||
BEESNOTE("Calculating transid_max");
|
|
||||||
uint64_t rv = 0;
|
uint64_t rv = 0;
|
||||||
uint64_t root = 0;
|
uint64_t root = 0;
|
||||||
|
BEESNOTE("Calculating transid_max (" << rv << " as of root " << root << ")");
|
||||||
BEESTRACE("Calculating transid_max...");
|
BEESTRACE("Calculating transid_max...");
|
||||||
do {
|
do {
|
||||||
root = next_root(root);
|
root = next_root(root);
|
||||||
@@ -193,15 +208,15 @@ BeesRoots::crawl_roots()
|
|||||||
auto crawl_map_copy = m_root_crawl_map;
|
auto crawl_map_copy = m_root_crawl_map;
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// Scan the same inode/offset tuple in each subvol (good for snapshots)
|
||||||
BeesFileRange first_range;
|
BeesFileRange first_range;
|
||||||
shared_ptr<BeesCrawl> first_crawl;
|
shared_ptr<BeesCrawl> first_crawl;
|
||||||
for (auto i : crawl_map_copy) {
|
for (auto i : crawl_map_copy) {
|
||||||
auto this_crawl = i.second;
|
auto this_crawl = i.second;
|
||||||
auto this_range = this_crawl->peek_front();
|
auto this_range = this_crawl->peek_front();
|
||||||
if (this_range) {
|
if (this_range) {
|
||||||
auto tuple_this = make_tuple(this_range.fid().ino(), this_range.fid().root(), this_range.begin());
|
if (!first_range || this_range < first_range) {
|
||||||
auto tuple_first = make_tuple(first_range.fid().ino(), first_range.fid().root(), first_range.begin());
|
|
||||||
if (!first_range || tuple_this < tuple_first) {
|
|
||||||
first_crawl = this_crawl;
|
first_crawl = this_crawl;
|
||||||
first_range = this_range;
|
first_range = this_range;
|
||||||
}
|
}
|
||||||
@@ -219,6 +234,27 @@ BeesRoots::crawl_roots()
|
|||||||
THROW_CHECK2(runtime_error, first_range, first_range_popped, first_range == first_range_popped);
|
THROW_CHECK2(runtime_error, first_range, first_range_popped, first_range == first_range_popped);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// Scan each subvol one extent at a time (good for continuous forward progress)
|
||||||
|
bool crawled = false;
|
||||||
|
for (auto i : crawl_map_copy) {
|
||||||
|
auto this_crawl = i.second;
|
||||||
|
auto this_range = this_crawl->peek_front();
|
||||||
|
if (this_range) {
|
||||||
|
catch_all([&]() {
|
||||||
|
// BEESINFO("scan_forward " << this_range);
|
||||||
|
m_ctx->scan_forward(this_range);
|
||||||
|
});
|
||||||
|
crawled = true;
|
||||||
|
BEESCOUNT(crawl_scan);
|
||||||
|
m_crawl_current = this_crawl->get_state();
|
||||||
|
auto this_range_popped = this_crawl->pop_front();
|
||||||
|
THROW_CHECK2(runtime_error, this_range, this_range_popped, this_range == this_range_popped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crawled) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
BEESLOG("Crawl ran out of data after " << m_crawl_timer.lap() << "s, waiting for more...");
|
BEESLOG("Crawl ran out of data after " << m_crawl_timer.lap() << "s, waiting for more...");
|
||||||
BEESCOUNT(crawl_done);
|
BEESCOUNT(crawl_done);
|
||||||
@@ -343,8 +379,8 @@ BeesRoots::state_load()
|
|||||||
BeesRoots::BeesRoots(shared_ptr<BeesContext> ctx) :
|
BeesRoots::BeesRoots(shared_ptr<BeesContext> ctx) :
|
||||||
m_ctx(ctx),
|
m_ctx(ctx),
|
||||||
m_crawl_state_file(ctx->home_fd(), crawl_state_filename()),
|
m_crawl_state_file(ctx->home_fd(), crawl_state_filename()),
|
||||||
m_crawl_thread("crawl " + ctx->root_path()),
|
m_crawl_thread("crawl"),
|
||||||
m_writeback_thread("crawl_writeback " + ctx->root_path())
|
m_writeback_thread("crawl_writeback")
|
||||||
{
|
{
|
||||||
m_crawl_thread.exec([&]() {
|
m_crawl_thread.exec([&]() {
|
||||||
catch_all([&]() {
|
catch_all([&]() {
|
||||||
@@ -361,7 +397,6 @@ Fd
|
|||||||
BeesRoots::open_root_nocache(uint64_t rootid)
|
BeesRoots::open_root_nocache(uint64_t rootid)
|
||||||
{
|
{
|
||||||
BEESTRACE("open_root_nocache " << rootid);
|
BEESTRACE("open_root_nocache " << rootid);
|
||||||
BEESNOTE("open_root_nocache " << rootid);
|
|
||||||
|
|
||||||
// Stop recursion at the root of the filesystem tree
|
// Stop recursion at the root of the filesystem tree
|
||||||
if (rootid == BTRFS_FS_TREE_OBJECTID) {
|
if (rootid == BTRFS_FS_TREE_OBJECTID) {
|
||||||
@@ -558,6 +593,27 @@ BeesRoots::open_root_ino_nocache(uint64_t root, uint64_t ino)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As of 4.12 the kernel rejects dedup requests with
|
||||||
|
// src and dst that have different datasum flags.
|
||||||
|
//
|
||||||
|
// We can't detect those from userspace reliably, but
|
||||||
|
// we can detect the common case where one file is
|
||||||
|
// marked with the nodatasum (which implies nodatacow)
|
||||||
|
// on a filesystem that is mounted with datacow.
|
||||||
|
// These are arguably out of scope for dedup.
|
||||||
|
//
|
||||||
|
// To fix this properly, we have to keep track of which
|
||||||
|
// pairs of inodes failed to dedup, guess that the reason
|
||||||
|
// for failure was a mismatch of datasum flags, and
|
||||||
|
// create temporary files with the right flags somehow.
|
||||||
|
int attr = ioctl_iflags_get(rv);
|
||||||
|
if (attr & FS_NOCOW_FL) {
|
||||||
|
BEESLOG("Opening " << name_fd(rv) << " found FS_NOCOW_FL flag in " << to_hex(attr));
|
||||||
|
rv = Fd();
|
||||||
|
BEESCOUNT(open_wrong_flags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
BEESTRACE("mapped " << BeesFileId(root, ino));
|
BEESTRACE("mapped " << BeesFileId(root, ino));
|
||||||
BEESTRACE("\tto " << name_fd(rv));
|
BEESTRACE("\tto " << name_fd(rv));
|
||||||
BEESCOUNT(open_hit);
|
BEESCOUNT(open_hit);
|
||||||
@@ -629,7 +685,7 @@ BeesCrawl::fetch_extents()
|
|||||||
|
|
||||||
Timer crawl_timer;
|
Timer crawl_timer;
|
||||||
|
|
||||||
BtrfsIoctlSearchKey sk;
|
BtrfsIoctlSearchKey sk(BEES_MAX_CRAWL_SIZE * (sizeof(btrfs_file_extent_item) + sizeof(btrfs_ioctl_search_header)));
|
||||||
sk.tree_id = old_state.m_root;
|
sk.tree_id = old_state.m_root;
|
||||||
sk.min_objectid = old_state.m_objectid;
|
sk.min_objectid = old_state.m_objectid;
|
||||||
sk.min_type = sk.max_type = BTRFS_EXTENT_DATA_KEY;
|
sk.min_type = sk.max_type = BTRFS_EXTENT_DATA_KEY;
|
||||||
@@ -646,7 +702,9 @@ BeesCrawl::fetch_extents()
|
|||||||
{
|
{
|
||||||
BEESNOTE("searching crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
BEESNOTE("searching crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
||||||
BEESTOOLONG("Searching crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
BEESTOOLONG("Searching crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
||||||
|
Timer crawl_timer;
|
||||||
ioctl_ok = sk.do_ioctl_nothrow(m_ctx->root_fd());
|
ioctl_ok = sk.do_ioctl_nothrow(m_ctx->root_fd());
|
||||||
|
BEESCOUNTADD(crawl_ms, crawl_timer.age() * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ioctl_ok) {
|
if (ioctl_ok) {
|
||||||
@@ -730,6 +788,7 @@ BeesCrawl::fetch_extents()
|
|||||||
break;
|
break;
|
||||||
case BTRFS_FILE_EXTENT_PREALLOC:
|
case BTRFS_FILE_EXTENT_PREALLOC:
|
||||||
BEESCOUNT(crawl_prealloc);
|
BEESCOUNT(crawl_prealloc);
|
||||||
|
// fallthrough
|
||||||
case BTRFS_FILE_EXTENT_REG: {
|
case BTRFS_FILE_EXTENT_REG: {
|
||||||
auto physical = call_btrfs_get(btrfs_stack_file_extent_disk_bytenr, i.m_data);
|
auto physical = call_btrfs_get(btrfs_stack_file_extent_disk_bytenr, i.m_data);
|
||||||
auto ram = call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data);
|
auto ram = call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data);
|
||||||
@@ -784,7 +843,8 @@ BeesCrawl::peek_front()
|
|||||||
if (m_extents.empty()) {
|
if (m_extents.empty()) {
|
||||||
return BeesFileRange();
|
return BeesFileRange();
|
||||||
}
|
}
|
||||||
return *m_extents.begin();
|
auto rv = *m_extents.begin();
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
BeesFileRange
|
BeesFileRange
|
||||||
@@ -810,7 +870,8 @@ BeesCrawlState
|
|||||||
BeesCrawl::get_state()
|
BeesCrawl::get_state()
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_state_mutex);
|
unique_lock<mutex> lock(m_state_mutex);
|
||||||
return m_state;
|
auto rv = m_state;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -71,7 +71,18 @@ operator<<(ostream &os, const BeesFileRange &bfr)
|
|||||||
if (bfr.end() == numeric_limits<off_t>::max()) {
|
if (bfr.end() == numeric_limits<off_t>::max()) {
|
||||||
os << "- [" << to_hex(bfr.begin()) << "..eof]";
|
os << "- [" << to_hex(bfr.begin()) << "..eof]";
|
||||||
} else {
|
} else {
|
||||||
os << pretty(bfr.size()) << " [" << to_hex(bfr.begin()) << ".." << to_hex(bfr.end()) << "]";
|
os << pretty(bfr.size()) << " ";
|
||||||
|
if (bfr.begin() != 0) {
|
||||||
|
os << "[" << to_hex(bfr.begin());
|
||||||
|
} else {
|
||||||
|
os << "(";
|
||||||
|
}
|
||||||
|
os << ".." << to_hex(bfr.end());
|
||||||
|
if (!!bfr.m_fd && bfr.end() >= bfr.file_size()) {
|
||||||
|
os << ")";
|
||||||
|
} else {
|
||||||
|
os << "]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (bfr.m_fid) {
|
if (bfr.m_fid) {
|
||||||
os << " fid = " << bfr.m_fid;
|
os << " fid = " << bfr.m_fid;
|
||||||
@@ -92,8 +103,6 @@ operator<<(ostream &os, const BeesRangePair &brp)
|
|||||||
<< "\ndst = " << brp.second.fd() << " " << name_fd(brp.second.fd());
|
<< "\ndst = " << brp.second.fd() << " " << name_fd(brp.second.fd());
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex BeesFileRange::s_mutex;
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
BeesFileRange::operator<(const BeesFileRange &that) const
|
BeesFileRange::operator<(const BeesFileRange &that) const
|
||||||
{
|
{
|
||||||
@@ -145,7 +154,6 @@ off_t
|
|||||||
BeesFileRange::file_size() const
|
BeesFileRange::file_size() const
|
||||||
{
|
{
|
||||||
if (m_file_size <= 0) {
|
if (m_file_size <= 0) {
|
||||||
// Use method fd() not member m_fd() so we hold lock
|
|
||||||
Stat st(fd());
|
Stat st(fd());
|
||||||
m_file_size = st.st_size;
|
m_file_size = st.st_size;
|
||||||
// These checks could trigger on valid input, but that would mean we have
|
// These checks could trigger on valid input, but that would mean we have
|
||||||
@@ -178,31 +186,21 @@ BeesFileRange::grow_begin(off_t delta)
|
|||||||
BeesFileRange::BeesFileRange(const BeesBlockData &bbd) :
|
BeesFileRange::BeesFileRange(const BeesBlockData &bbd) :
|
||||||
m_fd(bbd.fd()),
|
m_fd(bbd.fd()),
|
||||||
m_begin(bbd.begin()),
|
m_begin(bbd.begin()),
|
||||||
m_end(bbd.end()),
|
m_end(bbd.end())
|
||||||
m_file_size(-1)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
BeesFileRange::BeesFileRange(Fd fd, off_t begin, off_t end) :
|
BeesFileRange::BeesFileRange(Fd fd, off_t begin, off_t end) :
|
||||||
m_fd(fd),
|
m_fd(fd),
|
||||||
m_begin(begin),
|
m_begin(begin),
|
||||||
m_end(end),
|
m_end(end)
|
||||||
m_file_size(-1)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
BeesFileRange::BeesFileRange(const BeesFileId &fid, off_t begin, off_t end) :
|
BeesFileRange::BeesFileRange(const BeesFileId &fid, off_t begin, off_t end) :
|
||||||
m_fid(fid),
|
m_fid(fid),
|
||||||
m_begin(begin),
|
m_begin(begin),
|
||||||
m_end(end),
|
m_end(end)
|
||||||
m_file_size(-1)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
BeesFileRange::BeesFileRange() :
|
|
||||||
m_begin(0),
|
|
||||||
m_end(0),
|
|
||||||
m_file_size(-1)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,22 +283,18 @@ BeesFileRange::operator BeesBlockData() const
|
|||||||
Fd
|
Fd
|
||||||
BeesFileRange::fd() const
|
BeesFileRange::fd() const
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(s_mutex);
|
|
||||||
return m_fd;
|
return m_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
Fd
|
Fd
|
||||||
BeesFileRange::fd(const shared_ptr<BeesContext> &ctx) const
|
BeesFileRange::fd(const shared_ptr<BeesContext> &ctx) const
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(s_mutex);
|
|
||||||
// If we don't have a fid we can't do much here
|
// If we don't have a fid we can't do much here
|
||||||
if (m_fid) {
|
if (m_fid) {
|
||||||
if (!m_fd) {
|
if (!m_fd) {
|
||||||
// If we don't have a fd, open by fid
|
// If we don't have a fd, open by fid
|
||||||
if (m_fid && ctx) {
|
if (m_fid && ctx) {
|
||||||
lock.unlock();
|
|
||||||
Fd new_fd = ctx->roots()->open_root_ino(m_fid);
|
Fd new_fd = ctx->roots()->open_root_ino(m_fid);
|
||||||
lock.lock();
|
|
||||||
m_fd = new_fd;
|
m_fd = new_fd;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -936,6 +930,7 @@ BeesBlockData::data() const
|
|||||||
{
|
{
|
||||||
if (m_data.empty()) {
|
if (m_data.empty()) {
|
||||||
THROW_CHECK1(invalid_argument, size(), size() > 0);
|
THROW_CHECK1(invalid_argument, size(), size() > 0);
|
||||||
|
BEESNOTE("Reading BeesBlockData " << *this);
|
||||||
BEESTOOLONG("Reading BeesBlockData " << *this);
|
BEESTOOLONG("Reading BeesBlockData " << *this);
|
||||||
Timer read_timer;
|
Timer read_timer;
|
||||||
|
|
||||||
|
185
src/bees.cc
185
src/bees.cc
@@ -1,6 +1,5 @@
|
|||||||
#include "bees.h"
|
#include "bees.h"
|
||||||
|
|
||||||
#include "crucible/interp.h"
|
|
||||||
#include "crucible/limits.h"
|
#include "crucible/limits.h"
|
||||||
#include "crucible/process.h"
|
#include "crucible/process.h"
|
||||||
#include "crucible/string.h"
|
#include "crucible/string.h"
|
||||||
@@ -20,27 +19,31 @@
|
|||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
using namespace crucible;
|
using namespace crucible;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
int
|
int
|
||||||
do_cmd_help(const ArgList &argv)
|
do_cmd_help(char *argv[])
|
||||||
{
|
{
|
||||||
cerr << "Usage: " << argv[0] << " fs-root-path [fs-root-path-2...]\n"
|
cerr << "Usage: " << argv[0] << " [options] fs-root-path [fs-root-path-2...]\n"
|
||||||
"Performs best-effort extent-same deduplication on btrfs.\n"
|
"Performs best-effort extent-same deduplication on btrfs.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"fs-root-path MUST be the root of a btrfs filesystem tree (id 5).\n"
|
"fs-root-path MUST be the root of a btrfs filesystem tree (id 5).\n"
|
||||||
"Other directories will be rejected.\n"
|
"Other directories will be rejected.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Multiple filesystems can share a single hash table (BEESHOME)\n"
|
"Options:\n"
|
||||||
"but this only works well if the content of each filesystem\n"
|
"\t-h, --help\t\tShow this help\n"
|
||||||
"is distinct from all the others.\n"
|
"\t-t, --timestamps\tShow timestamps in log output (default)\n"
|
||||||
"\n"
|
"\t-T, --notimestamps\tOmit timestamps in log output\n"
|
||||||
"Required environment variables:\n"
|
|
||||||
"\tBEESHOME\tPath to hash table and configuration files\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"Optional environment variables:\n"
|
"Optional environment variables:\n"
|
||||||
"\tBEESSTATUS\tFile to write status to (tmpfs recommended, e.g. /run)\n"
|
"\tBEESHOME\tPath to hash table and configuration files\n"
|
||||||
|
"\t\t\t(default is .beeshome/ in the root of each filesystem).\n"
|
||||||
|
"\n"
|
||||||
|
"\tBEESSTATUS\tFile to write status to (tmpfs recommended, e.g. /run).\n"
|
||||||
|
"\t\t\tNo status is written if this variable is unset.\n"
|
||||||
"\n"
|
"\n"
|
||||||
<< endl;
|
<< endl;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -50,30 +53,36 @@ do_cmd_help(const ArgList &argv)
|
|||||||
|
|
||||||
RateLimiter bees_info_rate_limit(BEES_INFO_RATE, BEES_INFO_BURST);
|
RateLimiter bees_info_rate_limit(BEES_INFO_RATE, BEES_INFO_BURST);
|
||||||
|
|
||||||
thread_local BeesTracer *BeesTracer::s_next_tracer = nullptr;
|
thread_local BeesTracer *BeesTracer::tl_next_tracer = nullptr;
|
||||||
|
|
||||||
BeesTracer::~BeesTracer()
|
BeesTracer::~BeesTracer()
|
||||||
{
|
{
|
||||||
if (uncaught_exception()) {
|
if (uncaught_exception()) {
|
||||||
m_func();
|
try {
|
||||||
|
m_func();
|
||||||
|
} catch (exception &e) {
|
||||||
|
BEESLOG("Nested exception: " << e.what());
|
||||||
|
} catch (...) {
|
||||||
|
BEESLOG("Nested exception ...");
|
||||||
|
}
|
||||||
if (!m_next_tracer) {
|
if (!m_next_tracer) {
|
||||||
BEESLOG("--- END TRACE --- exception ---");
|
BEESLOG("--- END TRACE --- exception ---");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s_next_tracer = m_next_tracer;
|
tl_next_tracer = m_next_tracer;
|
||||||
}
|
}
|
||||||
|
|
||||||
BeesTracer::BeesTracer(function<void()> f) :
|
BeesTracer::BeesTracer(function<void()> f) :
|
||||||
m_func(f)
|
m_func(f)
|
||||||
{
|
{
|
||||||
m_next_tracer = s_next_tracer;
|
m_next_tracer = tl_next_tracer;
|
||||||
s_next_tracer = this;
|
tl_next_tracer = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
BeesTracer::trace_now()
|
BeesTracer::trace_now()
|
||||||
{
|
{
|
||||||
BeesTracer *tp = s_next_tracer;
|
BeesTracer *tp = tl_next_tracer;
|
||||||
BEESLOG("--- BEGIN TRACE ---");
|
BEESLOG("--- BEGIN TRACE ---");
|
||||||
while (tp) {
|
while (tp) {
|
||||||
tp->m_func();
|
tp->m_func();
|
||||||
@@ -82,17 +91,17 @@ BeesTracer::trace_now()
|
|||||||
BEESLOG("--- END TRACE ---");
|
BEESLOG("--- END TRACE ---");
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local BeesNote *BeesNote::s_next = nullptr;
|
thread_local BeesNote *BeesNote::tl_next = nullptr;
|
||||||
mutex BeesNote::s_mutex;
|
mutex BeesNote::s_mutex;
|
||||||
map<pid_t, BeesNote*> BeesNote::s_status;
|
map<pid_t, BeesNote*> BeesNote::s_status;
|
||||||
thread_local string BeesNote::s_name;
|
thread_local string BeesNote::tl_name;
|
||||||
|
|
||||||
BeesNote::~BeesNote()
|
BeesNote::~BeesNote()
|
||||||
{
|
{
|
||||||
|
tl_next = m_prev;
|
||||||
unique_lock<mutex> lock(s_mutex);
|
unique_lock<mutex> lock(s_mutex);
|
||||||
s_next = m_prev;
|
if (tl_next) {
|
||||||
if (s_next) {
|
s_status[gettid()] = tl_next;
|
||||||
s_status[gettid()] = s_next;
|
|
||||||
} else {
|
} else {
|
||||||
s_status.erase(gettid());
|
s_status.erase(gettid());
|
||||||
}
|
}
|
||||||
@@ -101,28 +110,26 @@ BeesNote::~BeesNote()
|
|||||||
BeesNote::BeesNote(function<void(ostream &os)> f) :
|
BeesNote::BeesNote(function<void(ostream &os)> f) :
|
||||||
m_func(f)
|
m_func(f)
|
||||||
{
|
{
|
||||||
|
m_name = tl_name;
|
||||||
|
m_prev = tl_next;
|
||||||
|
tl_next = this;
|
||||||
unique_lock<mutex> lock(s_mutex);
|
unique_lock<mutex> lock(s_mutex);
|
||||||
m_name = s_name;
|
s_status[gettid()] = tl_next;
|
||||||
m_prev = s_next;
|
|
||||||
s_next = this;
|
|
||||||
s_status[gettid()] = s_next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
BeesNote::set_name(const string &name)
|
BeesNote::set_name(const string &name)
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(s_mutex);
|
tl_name = name;
|
||||||
s_name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
BeesNote::get_name()
|
BeesNote::get_name()
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(s_mutex);
|
if (tl_name.empty()) {
|
||||||
if (s_name.empty()) {
|
|
||||||
return "bees";
|
return "bees";
|
||||||
} else {
|
} else {
|
||||||
return s_name;
|
return tl_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,11 +215,10 @@ template <class T>
|
|||||||
T&
|
T&
|
||||||
BeesStatTmpl<T>::at(string idx)
|
BeesStatTmpl<T>::at(string idx)
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
if (!m_stats_map.count(idx)) {
|
||||||
if (!m_stats_map.count(idx)) {
|
|
||||||
m_stats_map[idx] = 0;
|
m_stats_map[idx] = 0;
|
||||||
}
|
}
|
||||||
return m_stats_map[idx];
|
return m_stats_map[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@@ -220,7 +226,8 @@ T
|
|||||||
BeesStatTmpl<T>::at(string idx) const
|
BeesStatTmpl<T>::at(string idx) const
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
return m_stats_map.at(idx);
|
auto rv = m_stats_map.at(idx);
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@@ -228,7 +235,7 @@ void
|
|||||||
BeesStatTmpl<T>::add_count(string idx, size_t amount)
|
BeesStatTmpl<T>::add_count(string idx, size_t amount)
|
||||||
{
|
{
|
||||||
unique_lock<mutex> lock(m_mutex);
|
unique_lock<mutex> lock(m_mutex);
|
||||||
if (!m_stats_map.count(idx)) {
|
if (!m_stats_map.count(idx)) {
|
||||||
m_stats_map[idx] = 0;
|
m_stats_map[idx] = 0;
|
||||||
}
|
}
|
||||||
m_stats_map.at(idx) += amount;
|
m_stats_map.at(idx) += amount;
|
||||||
@@ -260,14 +267,17 @@ BeesStats
|
|||||||
BeesStats::operator-(const BeesStats &that) const
|
BeesStats::operator-(const BeesStats &that) const
|
||||||
{
|
{
|
||||||
if (&that == this) return BeesStats();
|
if (&that == this) return BeesStats();
|
||||||
|
|
||||||
unique_lock<mutex> this_lock(m_mutex);
|
unique_lock<mutex> this_lock(m_mutex);
|
||||||
BeesStats this_copy;
|
BeesStats this_copy;
|
||||||
this_copy.m_stats_map = m_stats_map;
|
this_copy.m_stats_map = m_stats_map;
|
||||||
|
this_lock.unlock();
|
||||||
|
|
||||||
unique_lock<mutex> that_lock(that.m_mutex);
|
unique_lock<mutex> that_lock(that.m_mutex);
|
||||||
BeesStats that_copy;
|
BeesStats that_copy;
|
||||||
that_copy.m_stats_map = that.m_stats_map;
|
that_copy.m_stats_map = that.m_stats_map;
|
||||||
this_lock.unlock();
|
|
||||||
that_lock.unlock();
|
that_lock.unlock();
|
||||||
|
|
||||||
for (auto i : that.m_stats_map) {
|
for (auto i : that.m_stats_map) {
|
||||||
if (i.second != 0) {
|
if (i.second != 0) {
|
||||||
this_copy.at(i.first) -= i.second;
|
this_copy.at(i.first) -= i.second;
|
||||||
@@ -351,6 +361,18 @@ BeesStringFile::BeesStringFile(Fd dir_fd, string name, size_t limit) :
|
|||||||
BEESLOG("BeesStringFile " << name_fd(m_dir_fd) << "/" << m_name << " max size " << pretty(m_limit));
|
BEESLOG("BeesStringFile " << name_fd(m_dir_fd) << "/" << m_name << " max size " << pretty(m_limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BeesStringFile::name(const string &new_name)
|
||||||
|
{
|
||||||
|
m_name = new_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
string
|
||||||
|
BeesStringFile::name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
BeesStringFile::read()
|
BeesStringFile::read()
|
||||||
{
|
{
|
||||||
@@ -384,8 +406,13 @@ BeesStringFile::write(string contents)
|
|||||||
Fd ofd = openat_or_die(m_dir_fd, tmpname, FLAGS_CREATE_FILE, S_IRUSR | S_IWUSR);
|
Fd ofd = openat_or_die(m_dir_fd, tmpname, FLAGS_CREATE_FILE, S_IRUSR | S_IWUSR);
|
||||||
BEESNOTE("writing " << tmpname << " in " << name_fd(m_dir_fd));
|
BEESNOTE("writing " << tmpname << " in " << name_fd(m_dir_fd));
|
||||||
write_or_die(ofd, contents);
|
write_or_die(ofd, contents);
|
||||||
|
#if 0
|
||||||
|
// This triggers too many btrfs bugs. I wish I was kidding.
|
||||||
|
// Forget snapshots, balance, compression, and dedup:
|
||||||
|
// the system call you have to fear on btrfs is fsync().
|
||||||
BEESNOTE("fsyncing " << tmpname << " in " << name_fd(m_dir_fd));
|
BEESNOTE("fsyncing " << tmpname << " in " << name_fd(m_dir_fd));
|
||||||
DIE_IF_NON_ZERO(fsync(ofd));
|
DIE_IF_NON_ZERO(fsync(ofd));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
BEESNOTE("renaming " << tmpname << " to " << m_name << " in FD " << name_fd(m_dir_fd));
|
BEESNOTE("renaming " << tmpname << " to " << m_name << " in FD " << name_fd(m_dir_fd));
|
||||||
BEESTRACE("renaming " << tmpname << " to " << m_name << " in FD " << name_fd(m_dir_fd));
|
BEESTRACE("renaming " << tmpname << " to " << m_name << " in FD " << name_fd(m_dir_fd));
|
||||||
@@ -399,6 +426,7 @@ BeesTempFile::create()
|
|||||||
BEESNOTE("creating temporary file in " << m_ctx->root_path());
|
BEESNOTE("creating temporary file in " << m_ctx->root_path());
|
||||||
BEESTOOLONG("creating temporary file in " << m_ctx->root_path());
|
BEESTOOLONG("creating temporary file in " << m_ctx->root_path());
|
||||||
|
|
||||||
|
Timer create_timer;
|
||||||
DIE_IF_MINUS_ONE(m_fd = openat(m_ctx->root_fd(), ".", FLAGS_OPEN_TMPFILE, S_IRUSR | S_IWUSR));
|
DIE_IF_MINUS_ONE(m_fd = openat(m_ctx->root_fd(), ".", FLAGS_OPEN_TMPFILE, S_IRUSR | S_IWUSR));
|
||||||
BEESCOUNT(tmp_create);
|
BEESCOUNT(tmp_create);
|
||||||
|
|
||||||
@@ -406,18 +434,22 @@ BeesTempFile::create()
|
|||||||
// Resolves won't work there anyway. There are lots of tempfiles
|
// Resolves won't work there anyway. There are lots of tempfiles
|
||||||
// and they're short-lived, so this ends up being just a memory leak
|
// and they're short-lived, so this ends up being just a memory leak
|
||||||
// m_ctx->blacklist_add(BeesFileId(m_fd));
|
// m_ctx->blacklist_add(BeesFileId(m_fd));
|
||||||
|
|
||||||
|
// Put this inode in the cache so we can resolve it later
|
||||||
m_ctx->insert_root_ino(m_fd);
|
m_ctx->insert_root_ino(m_fd);
|
||||||
|
|
||||||
// Set compression attribute
|
// Set compression attribute
|
||||||
int flags = 0;
|
BEESTRACE("Getting FS_COMPR_FL on m_fd " << name_fd(m_fd));
|
||||||
BEESTRACE("Getting FS_COMPR_FL on m_fd " << name_fd(m_fd) << " flags " << to_hex(flags));
|
int flags = ioctl_iflags_get(m_fd);
|
||||||
DIE_IF_MINUS_ONE(ioctl(m_fd, FS_IOC_GETFLAGS, &flags));
|
|
||||||
flags |= FS_COMPR_FL;
|
flags |= FS_COMPR_FL;
|
||||||
BEESTRACE("Setting FS_COMPR_FL on m_fd " << name_fd(m_fd) << " flags " << to_hex(flags));
|
BEESTRACE("Setting FS_COMPR_FL on m_fd " << name_fd(m_fd) << " flags " << to_hex(flags));
|
||||||
DIE_IF_MINUS_ONE(ioctl(m_fd, FS_IOC_SETFLAGS, &flags));
|
ioctl_iflags_set(m_fd, flags);
|
||||||
|
|
||||||
// Always leave first block empty to avoid creating a file with an inline extent
|
// Always leave first block empty to avoid creating a file with an inline extent
|
||||||
m_end_offset = BLOCK_SIZE_CLONE;
|
m_end_offset = BLOCK_SIZE_CLONE;
|
||||||
|
|
||||||
|
// Count time spent here
|
||||||
|
BEESCOUNTADD(tmp_create_ms, create_timer.age() * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -431,11 +463,15 @@ BeesTempFile::resize(off_t offset)
|
|||||||
THROW_CHECK2(invalid_argument, m_end_offset, offset, m_end_offset < offset);
|
THROW_CHECK2(invalid_argument, m_end_offset, offset, m_end_offset < offset);
|
||||||
|
|
||||||
// Truncate
|
// Truncate
|
||||||
|
Timer resize_timer;
|
||||||
DIE_IF_NON_ZERO(ftruncate(m_fd, offset));
|
DIE_IF_NON_ZERO(ftruncate(m_fd, offset));
|
||||||
BEESCOUNT(tmp_resize);
|
BEESCOUNT(tmp_resize);
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
m_end_offset = offset;
|
m_end_offset = offset;
|
||||||
|
|
||||||
|
// Count time spent here
|
||||||
|
BEESCOUNTADD(tmp_resize_ms, resize_timer.age() * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
BeesTempFile::BeesTempFile(shared_ptr<BeesContext> ctx) :
|
BeesTempFile::BeesTempFile(shared_ptr<BeesContext> ctx) :
|
||||||
@@ -447,7 +483,7 @@ BeesTempFile::BeesTempFile(shared_ptr<BeesContext> ctx) :
|
|||||||
|
|
||||||
void
|
void
|
||||||
BeesTempFile::realign()
|
BeesTempFile::realign()
|
||||||
{
|
{
|
||||||
if (m_end_offset > BLOCK_SIZE_MAX_TEMP_FILE) {
|
if (m_end_offset > BLOCK_SIZE_MAX_TEMP_FILE) {
|
||||||
BEESLOG("temporary file size " << to_hex(m_end_offset) << " > max " << BLOCK_SIZE_MAX_TEMP_FILE);
|
BEESLOG("temporary file size " << to_hex(m_end_offset) << " > max " << BLOCK_SIZE_MAX_TEMP_FILE);
|
||||||
BEESCOUNT(tmp_trunc);
|
BEESCOUNT(tmp_trunc);
|
||||||
@@ -489,8 +525,13 @@ BeesTempFile::make_copy(const BeesFileRange &src)
|
|||||||
|
|
||||||
THROW_CHECK1(invalid_argument, src, src.size() > 0);
|
THROW_CHECK1(invalid_argument, src, src.size() > 0);
|
||||||
|
|
||||||
// FIXME: don't know where these come from, but we can't handle them.
|
// FIEMAP used to give us garbage data, e.g. distinct adjacent
|
||||||
// Grab a trace for the log.
|
// extents merged into a single entry in the FIEMAP output.
|
||||||
|
// FIEMAP didn't stop giving us garbage data, we just stopped
|
||||||
|
// using FIEMAP.
|
||||||
|
// We shouldn't get absurdly large extents any more; however,
|
||||||
|
// it's still a problem if we do, so bail out and leave a trace
|
||||||
|
// in the log.
|
||||||
THROW_CHECK1(invalid_argument, src, src.size() < BLOCK_SIZE_MAX_TEMP_FILE);
|
THROW_CHECK1(invalid_argument, src, src.size() < BLOCK_SIZE_MAX_TEMP_FILE);
|
||||||
|
|
||||||
realign();
|
realign();
|
||||||
@@ -499,6 +540,7 @@ BeesTempFile::make_copy(const BeesFileRange &src)
|
|||||||
auto end = m_end_offset + src.size();
|
auto end = m_end_offset + src.size();
|
||||||
resize(end);
|
resize(end);
|
||||||
|
|
||||||
|
Timer copy_timer;
|
||||||
BeesFileRange rv(m_fd, begin, end);
|
BeesFileRange rv(m_fd, begin, end);
|
||||||
BEESTRACE("copying to: " << rv);
|
BEESTRACE("copying to: " << rv);
|
||||||
BEESNOTE("copying " << src << " to " << rv);
|
BEESNOTE("copying " << src << " to " << rv);
|
||||||
@@ -524,10 +566,15 @@ BeesTempFile::make_copy(const BeesFileRange &src)
|
|||||||
src_p += len;
|
src_p += len;
|
||||||
dst_p += len;
|
dst_p += len;
|
||||||
}
|
}
|
||||||
|
BEESCOUNTADD(tmp_copy_ms, copy_timer.age() * 1000);
|
||||||
|
|
||||||
// We seem to get lockups without this!
|
// We seem to get lockups without this!
|
||||||
if (did_block_write) {
|
if (did_block_write) {
|
||||||
|
#if 1
|
||||||
|
// Is this fixed by "Btrfs: fix deadlock between dedup on same file and starting writeback"?
|
||||||
|
// No.
|
||||||
bees_sync(m_fd);
|
bees_sync(m_fd);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
BEESCOUNT(tmp_copy);
|
BEESCOUNT(tmp_copy);
|
||||||
@@ -535,7 +582,7 @@ BeesTempFile::make_copy(const BeesFileRange &src)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
bees_main(ArgList args)
|
bees_main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
set_catch_explainer([&](string s) {
|
set_catch_explainer([&](string s) {
|
||||||
BEESLOG("\n\n*** EXCEPTION ***\n\t" << s << "\n***\n");
|
BEESLOG("\n\n*** EXCEPTION ***\n\t" << s << "\n***\n");
|
||||||
@@ -548,12 +595,48 @@ bees_main(ArgList args)
|
|||||||
list<shared_ptr<BeesContext>> all_contexts;
|
list<shared_ptr<BeesContext>> all_contexts;
|
||||||
shared_ptr<BeesContext> bc;
|
shared_ptr<BeesContext> bc;
|
||||||
|
|
||||||
// Subscribe to fanotify events
|
THROW_CHECK1(invalid_argument, argc, argc >= 0);
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
bool chatter_prefix_timestamp = true;
|
||||||
|
|
||||||
|
// Parse options
|
||||||
|
int c;
|
||||||
|
while (1) {
|
||||||
|
int option_index = 0;
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{ "timestamps", no_argument, NULL, 't' },
|
||||||
|
{ "notimestamps", no_argument, NULL, 'T' },
|
||||||
|
{ "help", no_argument, NULL, 'h' }
|
||||||
|
};
|
||||||
|
|
||||||
|
c = getopt_long(argc, argv, "Tth", long_options, &option_index);
|
||||||
|
if (-1 == c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case 'T':
|
||||||
|
chatter_prefix_timestamp = false;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
chatter_prefix_timestamp = true;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
do_cmd_help(argv); // fallthrough
|
||||||
|
default:
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Chatter::enable_timestamp(chatter_prefix_timestamp);
|
||||||
|
|
||||||
|
// Create a context and start crawlers
|
||||||
bool did_subscription = false;
|
bool did_subscription = false;
|
||||||
for (string arg : args) {
|
while (optind < argc) {
|
||||||
catch_all([&]() {
|
catch_all([&]() {
|
||||||
bc = make_shared<BeesContext>(bc);
|
bc = make_shared<BeesContext>(bc);
|
||||||
bc->set_root_path(arg);
|
bc->set_root_path(argv[optind++]);
|
||||||
did_subscription = true;
|
did_subscription = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -574,18 +657,18 @@ bees_main(ArgList args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, const char **argv)
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
cerr << "bees version " << BEES_VERSION << endl;
|
||||||
|
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
do_cmd_help(argv);
|
do_cmd_help(argv);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgList args(argv + 1);
|
|
||||||
|
|
||||||
int rv = 1;
|
int rv = 1;
|
||||||
catch_and_explain([&]() {
|
catch_and_explain([&]() {
|
||||||
rv = bees_main(args);
|
rv = bees_main(argc, argv);
|
||||||
});
|
});
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
116
src/bees.h
116
src/bees.h
@@ -1,7 +1,6 @@
|
|||||||
#ifndef BEES_H
|
#ifndef BEES_H
|
||||||
#define BEES_H
|
#define BEES_H
|
||||||
|
|
||||||
#include "crucible/bool.h"
|
|
||||||
#include "crucible/cache.h"
|
#include "crucible/cache.h"
|
||||||
#include "crucible/chatter.h"
|
#include "crucible/chatter.h"
|
||||||
#include "crucible/error.h"
|
#include "crucible/error.h"
|
||||||
@@ -40,13 +39,6 @@ const off_t BLOCK_SIZE_MAX_EXTENT_SAME = 4096 * 4096;
|
|||||||
// Maximum length of a compressed extent in bytes
|
// Maximum length of a compressed extent in bytes
|
||||||
const off_t BLOCK_SIZE_MAX_COMPRESSED_EXTENT = 128 * 1024;
|
const off_t BLOCK_SIZE_MAX_COMPRESSED_EXTENT = 128 * 1024;
|
||||||
|
|
||||||
// Try to combine smaller extents into larger ones
|
|
||||||
const off_t BLOCK_SIZE_MIN_EXTENT_DEFRAG = BLOCK_SIZE_MAX_COMPRESSED_EXTENT;
|
|
||||||
|
|
||||||
// Avoid splitting extents that are already too small
|
|
||||||
const off_t BLOCK_SIZE_MIN_EXTENT_SPLIT = BLOCK_SIZE_MAX_COMPRESSED_EXTENT;
|
|
||||||
// const off_t BLOCK_SIZE_MIN_EXTENT_SPLIT = 1024LL * 1024 * 1024 * 1024;
|
|
||||||
|
|
||||||
// Maximum length of any extent in bytes
|
// Maximum length of any extent in bytes
|
||||||
// except we've seen 1.03G extents...
|
// except we've seen 1.03G extents...
|
||||||
// ...FIEMAP is slow and full of lies
|
// ...FIEMAP is slow and full of lies
|
||||||
@@ -55,8 +47,6 @@ const off_t BLOCK_SIZE_MAX_EXTENT = 128 * 1024 * 1024;
|
|||||||
// Masks, so we don't have to write "(BLOCK_SIZE_CLONE - 1)" everywhere
|
// Masks, so we don't have to write "(BLOCK_SIZE_CLONE - 1)" everywhere
|
||||||
const off_t BLOCK_MASK_CLONE = BLOCK_SIZE_CLONE - 1;
|
const off_t BLOCK_MASK_CLONE = BLOCK_SIZE_CLONE - 1;
|
||||||
const off_t BLOCK_MASK_SUMS = BLOCK_SIZE_SUMS - 1;
|
const off_t BLOCK_MASK_SUMS = BLOCK_SIZE_SUMS - 1;
|
||||||
const off_t BLOCK_MASK_MMAP = BLOCK_SIZE_MMAP - 1;
|
|
||||||
const off_t BLOCK_MASK_MAX_COMPRESSED_EXTENT = BLOCK_SIZE_MAX_COMPRESSED_EXTENT * 2 - 1;
|
|
||||||
|
|
||||||
// Maximum temporary file size
|
// Maximum temporary file size
|
||||||
const off_t BLOCK_SIZE_MAX_TEMP_FILE = 1024 * 1024 * 1024;
|
const off_t BLOCK_SIZE_MAX_TEMP_FILE = 1024 * 1024 * 1024;
|
||||||
@@ -70,29 +60,32 @@ const off_t BLOCK_SIZE_HASHTAB_EXTENT = 16 * 1024 * 1024;
|
|||||||
// Bytes per second we want to flush (8GB every two hours)
|
// Bytes per second we want to flush (8GB every two hours)
|
||||||
const double BEES_FLUSH_RATE = 8.0 * 1024 * 1024 * 1024 / 7200.0;
|
const double BEES_FLUSH_RATE = 8.0 * 1024 * 1024 * 1024 / 7200.0;
|
||||||
|
|
||||||
// Interval between writing non-hash-table things to disk (15 minutes)
|
// How long we should wait for new btrfs transactions
|
||||||
const int BEES_WRITEBACK_INTERVAL = 900;
|
const double BEES_COMMIT_INTERVAL = 900;
|
||||||
|
|
||||||
|
// Interval between writing non-hash-table things to disk, and starting new subvol crawlers
|
||||||
|
const int BEES_WRITEBACK_INTERVAL = BEES_COMMIT_INTERVAL;
|
||||||
|
|
||||||
// Statistics reports while scanning
|
// Statistics reports while scanning
|
||||||
const int BEES_STATS_INTERVAL = 3600;
|
const int BEES_STATS_INTERVAL = 3600;
|
||||||
|
|
||||||
// Progress shows instantaneous rates and thread status
|
// Progress shows instantaneous rates and thread status
|
||||||
const int BEES_PROGRESS_INTERVAL = 3600;
|
const int BEES_PROGRESS_INTERVAL = BEES_STATS_INTERVAL;
|
||||||
|
|
||||||
// Status is output every freakin second. Use a ramdisk.
|
// Status is output every freakin second. Use a ramdisk.
|
||||||
const int BEES_STATUS_INTERVAL = 1;
|
const int BEES_STATUS_INTERVAL = 1;
|
||||||
|
|
||||||
|
// Number of FDs to open (not counting 100 roots)
|
||||||
|
const size_t BEES_FD_CACHE_SIZE = 384;
|
||||||
|
|
||||||
// Log warnings when an operation takes too long
|
// Log warnings when an operation takes too long
|
||||||
const double BEES_TOO_LONG = 2.5;
|
const double BEES_TOO_LONG = 2.5;
|
||||||
|
|
||||||
// Avoid any extent where LOGICAL_INO takes this long
|
// Avoid any extent where LOGICAL_INO takes this long
|
||||||
const double BEES_TOXIC_DURATION = 9.9;
|
const double BEES_TOXIC_DURATION = 9.9;
|
||||||
|
|
||||||
// How long we should wait for new btrfs transactions
|
|
||||||
const double BEES_COMMIT_INTERVAL = 900;
|
|
||||||
|
|
||||||
// How long between hash table histograms
|
// How long between hash table histograms
|
||||||
const double BEES_HASH_TABLE_ANALYZE_INTERVAL = 3600;
|
const double BEES_HASH_TABLE_ANALYZE_INTERVAL = BEES_STATS_INTERVAL;
|
||||||
|
|
||||||
// Rate limiting of informational messages
|
// Rate limiting of informational messages
|
||||||
const double BEES_INFO_RATE = 10.0;
|
const double BEES_INFO_RATE = 10.0;
|
||||||
@@ -136,6 +129,8 @@ const int FLAGS_OPEN_FANOTIFY = O_RDWR | O_NOATIME | O_CLOEXEC | O_LARGEFILE;
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
#define BEESLOGNOTE(x) BEESLOG(x); BEESNOTE(x)
|
||||||
|
|
||||||
#define BEESCOUNT(stat) do { \
|
#define BEESCOUNT(stat) do { \
|
||||||
BeesStats::s_global.add_count(#stat); \
|
BeesStats::s_global.add_count(#stat); \
|
||||||
} while (0)
|
} while (0)
|
||||||
@@ -154,12 +149,12 @@ class BeesStatTmpl {
|
|||||||
map<string, T> m_stats_map;
|
map<string, T> m_stats_map;
|
||||||
mutable mutex m_mutex;
|
mutable mutex m_mutex;
|
||||||
|
|
||||||
|
T& at(string idx);
|
||||||
public:
|
public:
|
||||||
BeesStatTmpl() = default;
|
BeesStatTmpl() = default;
|
||||||
BeesStatTmpl(const BeesStatTmpl &that);
|
BeesStatTmpl(const BeesStatTmpl &that);
|
||||||
BeesStatTmpl &operator=(const BeesStatTmpl &that);
|
BeesStatTmpl &operator=(const BeesStatTmpl &that);
|
||||||
void add_count(string idx, size_t amount = 1);
|
void add_count(string idx, size_t amount = 1);
|
||||||
T& at(string idx);
|
|
||||||
T at(string idx) const;
|
T at(string idx) const;
|
||||||
|
|
||||||
friend ostream& operator<< <>(ostream &os, const BeesStatTmpl<T> &bs);
|
friend ostream& operator<< <>(ostream &os, const BeesStatTmpl<T> &bs);
|
||||||
@@ -183,7 +178,7 @@ class BeesTracer {
|
|||||||
function<void()> m_func;
|
function<void()> m_func;
|
||||||
BeesTracer *m_next_tracer = 0;
|
BeesTracer *m_next_tracer = 0;
|
||||||
|
|
||||||
thread_local static BeesTracer *s_next_tracer;
|
thread_local static BeesTracer *tl_next_tracer;
|
||||||
public:
|
public:
|
||||||
BeesTracer(function<void()> f);
|
BeesTracer(function<void()> f);
|
||||||
~BeesTracer();
|
~BeesTracer();
|
||||||
@@ -199,8 +194,8 @@ class BeesNote {
|
|||||||
static mutex s_mutex;
|
static mutex s_mutex;
|
||||||
static map<pid_t, BeesNote*> s_status;
|
static map<pid_t, BeesNote*> s_status;
|
||||||
|
|
||||||
thread_local static BeesNote *s_next;
|
thread_local static BeesNote *tl_next;
|
||||||
thread_local static string s_name;
|
thread_local static string tl_name;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BeesNote(function<void(ostream &)> f);
|
BeesNote(function<void(ostream &)> f);
|
||||||
@@ -250,15 +245,14 @@ ostream& operator<<(ostream &os, const BeesFileId &bfi);
|
|||||||
|
|
||||||
class BeesFileRange {
|
class BeesFileRange {
|
||||||
protected:
|
protected:
|
||||||
static mutex s_mutex;
|
|
||||||
mutable Fd m_fd;
|
mutable Fd m_fd;
|
||||||
mutable BeesFileId m_fid;
|
mutable BeesFileId m_fid;
|
||||||
off_t m_begin, m_end;
|
off_t m_begin = 0, m_end = 0;
|
||||||
mutable off_t m_file_size;
|
mutable off_t m_file_size = -1;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
BeesFileRange();
|
BeesFileRange() = default;
|
||||||
BeesFileRange(Fd fd, off_t begin, off_t end);
|
BeesFileRange(Fd fd, off_t begin, off_t end);
|
||||||
BeesFileRange(const BeesFileId &fid, off_t begin, off_t end);
|
BeesFileRange(const BeesFileId &fid, off_t begin, off_t end);
|
||||||
BeesFileRange(const BeesBlockData &bbd);
|
BeesFileRange(const BeesBlockData &bbd);
|
||||||
@@ -374,6 +368,8 @@ public:
|
|||||||
BeesStringFile(Fd dir_fd, string name, size_t limit = 1024 * 1024);
|
BeesStringFile(Fd dir_fd, string name, size_t limit = 1024 * 1024);
|
||||||
string read();
|
string read();
|
||||||
void write(string contents);
|
void write(string contents);
|
||||||
|
void name(const string &new_name);
|
||||||
|
string name() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BeesHashTable {
|
class BeesHashTable {
|
||||||
@@ -407,7 +403,7 @@ public:
|
|||||||
uint8_t p_byte[BLOCK_SIZE_HASHTAB_EXTENT];
|
uint8_t p_byte[BLOCK_SIZE_HASHTAB_EXTENT];
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
BeesHashTable(shared_ptr<BeesContext> ctx, string filename);
|
BeesHashTable(shared_ptr<BeesContext> ctx, string filename, off_t size = BLOCK_SIZE_HASHTAB_EXTENT);
|
||||||
~BeesHashTable();
|
~BeesHashTable();
|
||||||
|
|
||||||
vector<Cell> find_cell(HashType hash);
|
vector<Cell> find_cell(HashType hash);
|
||||||
@@ -415,8 +411,6 @@ public:
|
|||||||
void erase_hash_addr(HashType hash, AddrType addr);
|
void erase_hash_addr(HashType hash, AddrType addr);
|
||||||
bool push_front_hash_addr(HashType hash, AddrType addr);
|
bool push_front_hash_addr(HashType hash, AddrType addr);
|
||||||
|
|
||||||
void set_shared(bool shared);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string m_filename;
|
string m_filename;
|
||||||
Fd m_fd;
|
Fd m_fd;
|
||||||
@@ -443,7 +437,6 @@ private:
|
|||||||
BeesThread m_writeback_thread;
|
BeesThread m_writeback_thread;
|
||||||
BeesThread m_prefetch_thread;
|
BeesThread m_prefetch_thread;
|
||||||
RateLimiter m_flush_rate_limit;
|
RateLimiter m_flush_rate_limit;
|
||||||
RateLimiter m_prefetch_rate_limit;
|
|
||||||
mutex m_extent_mutex;
|
mutex m_extent_mutex;
|
||||||
mutex m_bucket_mutex;
|
mutex m_bucket_mutex;
|
||||||
condition_variable m_condvar;
|
condition_variable m_condvar;
|
||||||
@@ -452,8 +445,7 @@ private:
|
|||||||
|
|
||||||
LockSet<uint64_t> m_extent_lock_set;
|
LockSet<uint64_t> m_extent_lock_set;
|
||||||
|
|
||||||
DefaultBool m_shared;
|
void open_file();
|
||||||
|
|
||||||
void writeback_loop();
|
void writeback_loop();
|
||||||
void prefetch_loop();
|
void prefetch_loop();
|
||||||
void try_mmap_flags(int flags);
|
void try_mmap_flags(int flags);
|
||||||
@@ -464,8 +456,6 @@ private:
|
|||||||
void flush_dirty_extents();
|
void flush_dirty_extents();
|
||||||
bool is_toxic_hash(HashType h) const;
|
bool is_toxic_hash(HashType h) const;
|
||||||
|
|
||||||
bool using_shared_map() const { return false; }
|
|
||||||
|
|
||||||
BeesHashTable(const BeesHashTable &) = delete;
|
BeesHashTable(const BeesHashTable &) = delete;
|
||||||
BeesHashTable &operator=(const BeesHashTable &) = delete;
|
BeesHashTable &operator=(const BeesHashTable &) = delete;
|
||||||
};
|
};
|
||||||
@@ -488,7 +478,7 @@ class BeesCrawl {
|
|||||||
|
|
||||||
mutex m_mutex;
|
mutex m_mutex;
|
||||||
set<BeesFileRange> m_extents;
|
set<BeesFileRange> m_extents;
|
||||||
DefaultBool m_deferred;
|
bool m_deferred = false;
|
||||||
|
|
||||||
mutex m_state_mutex;
|
mutex m_state_mutex;
|
||||||
BeesCrawlState m_state;
|
BeesCrawlState m_state;
|
||||||
@@ -513,7 +503,7 @@ class BeesRoots {
|
|||||||
map<uint64_t, shared_ptr<BeesCrawl>> m_root_crawl_map;
|
map<uint64_t, shared_ptr<BeesCrawl>> m_root_crawl_map;
|
||||||
mutex m_mutex;
|
mutex m_mutex;
|
||||||
condition_variable m_condvar;
|
condition_variable m_condvar;
|
||||||
DefaultBool m_crawl_dirty;
|
bool m_crawl_dirty = false;
|
||||||
Timer m_crawl_timer;
|
Timer m_crawl_timer;
|
||||||
BeesThread m_crawl_thread;
|
BeesThread m_crawl_thread;
|
||||||
BeesThread m_writeback_thread;
|
BeesThread m_writeback_thread;
|
||||||
@@ -569,7 +559,7 @@ class BeesBlockData {
|
|||||||
mutable BeesAddress m_addr;
|
mutable BeesAddress m_addr;
|
||||||
mutable Blob m_data;
|
mutable Blob m_data;
|
||||||
mutable BeesHash m_hash;
|
mutable BeesHash m_hash;
|
||||||
mutable DefaultBool m_hash_done;
|
mutable bool m_hash_done = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor with the immutable fields
|
// Constructor with the immutable fields
|
||||||
@@ -607,42 +597,6 @@ public:
|
|||||||
friend ostream & operator<<(ostream &os, const BeesRangePair &brp);
|
friend ostream & operator<<(ostream &os, const BeesRangePair &brp);
|
||||||
};
|
};
|
||||||
|
|
||||||
class BeesWorkQueueBase {
|
|
||||||
string m_name;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static mutex s_mutex;
|
|
||||||
static set<BeesWorkQueueBase *> s_all_workers;
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual ~BeesWorkQueueBase();
|
|
||||||
BeesWorkQueueBase(const string &name);
|
|
||||||
|
|
||||||
string name() const;
|
|
||||||
void name(const string &new_name);
|
|
||||||
|
|
||||||
virtual size_t active_size() const = 0;
|
|
||||||
virtual list<string> peek_active(size_t count) const = 0;
|
|
||||||
|
|
||||||
static void for_each_work_queue(function<void(BeesWorkQueueBase *)> f);
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class Task>
|
|
||||||
class BeesWorkQueue : public BeesWorkQueueBase {
|
|
||||||
WorkQueue<Task> m_active_queue;
|
|
||||||
|
|
||||||
public:
|
|
||||||
BeesWorkQueue(const string &name);
|
|
||||||
~BeesWorkQueue();
|
|
||||||
void push_active(const Task &task, size_t limit);
|
|
||||||
void push_active(const Task &task);
|
|
||||||
|
|
||||||
size_t active_size() const override;
|
|
||||||
list<string> peek_active(size_t count) const override;
|
|
||||||
|
|
||||||
Task pop();
|
|
||||||
};
|
|
||||||
|
|
||||||
class BeesTempFile {
|
class BeesTempFile {
|
||||||
shared_ptr<BeesContext> m_ctx;
|
shared_ptr<BeesContext> m_ctx;
|
||||||
Fd m_fd;
|
Fd m_fd;
|
||||||
@@ -662,6 +616,7 @@ class BeesFdCache {
|
|||||||
LRUCache<Fd, shared_ptr<BeesContext>, uint64_t> m_root_cache;
|
LRUCache<Fd, shared_ptr<BeesContext>, uint64_t> m_root_cache;
|
||||||
LRUCache<Fd, shared_ptr<BeesContext>, uint64_t, uint64_t> m_file_cache;
|
LRUCache<Fd, shared_ptr<BeesContext>, uint64_t, uint64_t> m_file_cache;
|
||||||
Timer m_root_cache_timer;
|
Timer m_root_cache_timer;
|
||||||
|
Timer m_file_cache_timer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BeesFdCache();
|
BeesFdCache();
|
||||||
@@ -673,7 +628,7 @@ public:
|
|||||||
struct BeesResolveAddrResult {
|
struct BeesResolveAddrResult {
|
||||||
BeesResolveAddrResult();
|
BeesResolveAddrResult();
|
||||||
vector<BtrfsInodeOffsetRoot> m_biors;
|
vector<BtrfsInodeOffsetRoot> m_biors;
|
||||||
DefaultBool m_is_toxic;
|
bool m_is_toxic = false;
|
||||||
bool is_toxic() const { return m_is_toxic; }
|
bool is_toxic() const { return m_is_toxic; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -714,7 +669,7 @@ public:
|
|||||||
void set_root_path(string path);
|
void set_root_path(string path);
|
||||||
|
|
||||||
Fd root_fd() const { return m_root_fd; }
|
Fd root_fd() const { return m_root_fd; }
|
||||||
Fd home_fd() const { return m_home_fd; }
|
Fd home_fd();
|
||||||
string root_path() const { return m_root_path; }
|
string root_path() const { return m_root_path; }
|
||||||
string root_uuid() const { return m_root_uuid; }
|
string root_uuid() const { return m_root_uuid; }
|
||||||
|
|
||||||
@@ -751,22 +706,22 @@ class BeesResolver {
|
|||||||
unsigned m_bior_count;
|
unsigned m_bior_count;
|
||||||
|
|
||||||
// We found matching data, so we can dedup
|
// We found matching data, so we can dedup
|
||||||
DefaultBool m_found_data;
|
bool m_found_data = false;
|
||||||
|
|
||||||
// We found matching data, so we *did* dedup
|
// We found matching data, so we *did* dedup
|
||||||
DefaultBool m_found_dup;
|
bool m_found_dup = false;
|
||||||
|
|
||||||
// We found matching hash, so the hash table is still correct
|
// We found matching hash, so the hash table is still correct
|
||||||
DefaultBool m_found_hash;
|
bool m_found_hash = false;
|
||||||
|
|
||||||
// We found matching physical address, so the hash table isn't totally wrong
|
// We found matching physical address, so the hash table isn't totally wrong
|
||||||
DefaultBool m_found_addr;
|
bool m_found_addr = false;
|
||||||
|
|
||||||
// We found matching physical address, but data did not match
|
// We found matching physical address, but data did not match
|
||||||
DefaultBool m_wrong_data;
|
bool m_wrong_data = false;
|
||||||
|
|
||||||
// The whole thing is a placebo to avoid crippling btrfs performance bugs
|
// The whole thing is a placebo to avoid crippling btrfs performance bugs
|
||||||
DefaultBool m_is_toxic;
|
bool m_is_toxic = false;
|
||||||
|
|
||||||
BeesFileRange chase_extent_ref(const BtrfsInodeOffsetRoot &bior, BeesBlockData &needle_bbd);
|
BeesFileRange chase_extent_ref(const BtrfsInodeOffsetRoot &bior, BeesBlockData &needle_bbd);
|
||||||
BeesBlockData adjust_offset(const BeesFileRange &haystack, const BeesBlockData &needle);
|
BeesBlockData adjust_offset(const BeesFileRange &haystack, const BeesBlockData &needle);
|
||||||
@@ -820,6 +775,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// And now, a giant pile of extern declarations
|
// And now, a giant pile of extern declarations
|
||||||
|
extern const char *BEES_VERSION;
|
||||||
string pretty(double d);
|
string pretty(double d);
|
||||||
extern RateLimiter bees_info_rate_limit;
|
extern RateLimiter bees_info_rate_limit;
|
||||||
void bees_sync(int fd);
|
void bees_sync(int fd);
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
PROGRAMS = \
|
PROGRAMS = \
|
||||||
chatter \
|
chatter \
|
||||||
crc64 \
|
crc64 \
|
||||||
execpipe \
|
|
||||||
fd \
|
fd \
|
||||||
interp \
|
|
||||||
limits \
|
limits \
|
||||||
path \
|
path \
|
||||||
process \
|
process \
|
||||||
@@ -21,7 +19,7 @@ LDFLAGS = -L../lib -Wl,-rpath=$(shell realpath ../lib)
|
|||||||
depends.mk: *.cc
|
depends.mk: *.cc
|
||||||
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done >> depends.mk.new
|
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done >> depends.mk.new
|
||||||
mv -fv depends.mk.new depends.mk
|
mv -fv depends.mk.new depends.mk
|
||||||
|
|
||||||
-include depends.mk
|
-include depends.mk
|
||||||
|
|
||||||
%.o: %.cc %.h ../makeflags
|
%.o: %.cc %.h ../makeflags
|
||||||
|
@@ -5,18 +5,6 @@
|
|||||||
|
|
||||||
using namespace crucible;
|
using namespace crucible;
|
||||||
|
|
||||||
static
|
|
||||||
void
|
|
||||||
test_getcrc64_strings()
|
|
||||||
{
|
|
||||||
assert(Digest::CRC::crc64("John") == 5942451273432301568);
|
|
||||||
assert(Digest::CRC::crc64("Paul") == 5838402100630913024);
|
|
||||||
assert(Digest::CRC::crc64("George") == 6714394476893704192);
|
|
||||||
assert(Digest::CRC::crc64("Ringo") == 6038837226071130112);
|
|
||||||
assert(Digest::CRC::crc64("") == 0);
|
|
||||||
assert(Digest::CRC::crc64("\377\277\300\200") == 15615382887346470912ULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
test_getcrc64_byte_arrays()
|
test_getcrc64_byte_arrays()
|
||||||
@@ -32,7 +20,6 @@ test_getcrc64_byte_arrays()
|
|||||||
int
|
int
|
||||||
main(int, char**)
|
main(int, char**)
|
||||||
{
|
{
|
||||||
RUN_A_TEST(test_getcrc64_strings());
|
|
||||||
RUN_A_TEST(test_getcrc64_byte_arrays());
|
RUN_A_TEST(test_getcrc64_byte_arrays());
|
||||||
|
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
#include "tests.h"
|
|
||||||
|
|
||||||
#include "crucible/execpipe.h"
|
|
||||||
|
|
||||||
#include <ios>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
using namespace crucible;
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
#if 1 // Needs rework
|
|
||||||
static inline
|
|
||||||
void
|
|
||||||
test_hello_world()
|
|
||||||
{
|
|
||||||
// alarm(9);
|
|
||||||
Fd fd = popen([]() { return system("echo Hello, World!"); });
|
|
||||||
char buf[1024];
|
|
||||||
size_t rv = -1;
|
|
||||||
read_partial_or_die(fd, buf, rv);
|
|
||||||
assert(rv > 0);
|
|
||||||
string b(buf, buf + rv - 1);
|
|
||||||
// cerr << "hello_world says: '" << b << "'" << endl;
|
|
||||||
assert(b == "Hello, World!");
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void
|
|
||||||
test_read_limit(size_t limit = 4096)
|
|
||||||
{
|
|
||||||
alarm(9);
|
|
||||||
Fd fd = popen([]() { return system("yes Hello!"); });
|
|
||||||
try {
|
|
||||||
string b = read_all(fd, limit);
|
|
||||||
} catch (out_of_range &re) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(!"no exception thrown by read_all");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace crucible {
|
|
||||||
extern bool assert_no_leaked_fds();
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int, char**)
|
|
||||||
{
|
|
||||||
#if 1
|
|
||||||
RUN_A_TEST(test_hello_world());
|
|
||||||
assert(assert_no_leaked_fds());
|
|
||||||
RUN_A_TEST(test_read_limit(4095));
|
|
||||||
RUN_A_TEST(test_read_limit(4096));
|
|
||||||
RUN_A_TEST(test_read_limit(4097));
|
|
||||||
assert(assert_no_leaked_fds());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
}
|
|
@@ -1,88 +0,0 @@
|
|||||||
#include "tests.h"
|
|
||||||
|
|
||||||
#include "crucible/interp.h"
|
|
||||||
|
|
||||||
using namespace crucible;
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
/***********************************************************************
|
|
||||||
|
|
||||||
How this should work:
|
|
||||||
|
|
||||||
Interpreter reads an arg list:
|
|
||||||
|
|
||||||
argv[0] --method0args --method1arg arg1 --method1arg=arg1 -- args...
|
|
||||||
|
|
||||||
argv[0] should look up a shared_ptr<Command> which creates an object of
|
|
||||||
type shared_ptr<Process>. This object is used to receive args by
|
|
||||||
method calls or one at a time.
|
|
||||||
|
|
||||||
<Command> and <Process> can be the same object, or not.
|
|
||||||
|
|
||||||
Process p methods:
|
|
||||||
|
|
||||||
p->spawn(Interp*) -> Process
|
|
||||||
p->exec(ArgList) -> Process / Result
|
|
||||||
p->method (from ArgParser<>)
|
|
||||||
p->finish() -> void (destroys object without early destruction warnings...?)
|
|
||||||
p->~Process() -> complains loudly if finish() not called first...?
|
|
||||||
|
|
||||||
Result might be a pair of Process, string. Or just string.
|
|
||||||
|
|
||||||
ArgParser should be more like GetOpt:
|
|
||||||
|
|
||||||
build a dictionary and an arg list from arguments
|
|
||||||
Process methods should interrogate ArgParser
|
|
||||||
ArgParser might have a table of boolean and string option names so it can reject invalid options
|
|
||||||
but if it had that, we could also pass in Process and have it call methods on it
|
|
||||||
...but that is a _lot_ of pointer-hiding when we could KISS
|
|
||||||
...but if we had that solved, argparser tables look like lists of method names
|
|
||||||
ArgParser<T> has a table of names and methods on object of type T
|
|
||||||
ArgParser hides everything behind void* and hands off to a compiled implementation to do callbacks
|
|
||||||
|
|
||||||
Extreme simplification: arguments are themselves executable
|
|
||||||
|
|
||||||
so '--method_foo arg' really means construct MethodFoo(arg) and cast to shared_ptr<ProcArg>
|
|
||||||
then Process->invokeSomething(ProcArg)
|
|
||||||
too extreme, use argparser instead
|
|
||||||
|
|
||||||
***********************************************************************/
|
|
||||||
|
|
||||||
void
|
|
||||||
test_arg_parser()
|
|
||||||
{
|
|
||||||
ArgParser ap;
|
|
||||||
ArgList al( { "abc", "--def", "ghi" } );
|
|
||||||
ap.parse(NULL, al);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Thing {
|
|
||||||
int m_i;
|
|
||||||
double m_d;
|
|
||||||
string m_s;
|
|
||||||
|
|
||||||
void set_i(int i) { cerr << "i = " << i << endl; m_i = i; }
|
|
||||||
void set_d(double d) { cerr << "d = " << d << endl; m_d = d; }
|
|
||||||
void set_s(string s) { cerr << "s = " << s << endl; m_s = s; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename F, typename T, typename A>
|
|
||||||
void
|
|
||||||
assign(T& t, F f, A a)
|
|
||||||
{
|
|
||||||
cerr << __PRETTY_FUNCTION__ << " - a = " << a << endl;
|
|
||||||
(t.*f)(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int, char**)
|
|
||||||
{
|
|
||||||
RUN_A_TEST(test_arg_parser());
|
|
||||||
|
|
||||||
Thing p;
|
|
||||||
assign(p, &Thing::set_i, 5);
|
|
||||||
|
|
||||||
cerr << "p.m_i = " << p.m_i << endl;
|
|
||||||
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
}
|
|
@@ -141,7 +141,13 @@ test_cast_0x80000000_to_things()
|
|||||||
SHOULD_FAIL(ranged_cast<unsigned short>(uv));
|
SHOULD_FAIL(ranged_cast<unsigned short>(uv));
|
||||||
SHOULD_FAIL(ranged_cast<unsigned char>(uv));
|
SHOULD_FAIL(ranged_cast<unsigned char>(uv));
|
||||||
SHOULD_PASS(ranged_cast<signed long long>(sv), sv);
|
SHOULD_PASS(ranged_cast<signed long long>(sv), sv);
|
||||||
SHOULD_PASS(ranged_cast<signed long>(sv), sv);
|
if (sizeof(long) == 4) {
|
||||||
|
SHOULD_FAIL(ranged_cast<signed long>(sv));
|
||||||
|
} else if (sizeof(long) == 8) {
|
||||||
|
SHOULD_PASS(ranged_cast<signed long>(sv), sv);
|
||||||
|
} else {
|
||||||
|
assert(!"unhandled case, please add code for long here");
|
||||||
|
}
|
||||||
SHOULD_FAIL(ranged_cast<signed short>(sv));
|
SHOULD_FAIL(ranged_cast<signed short>(sv));
|
||||||
SHOULD_FAIL(ranged_cast<signed char>(sv));
|
SHOULD_FAIL(ranged_cast<signed char>(sv));
|
||||||
if (sizeof(int) == 4) {
|
if (sizeof(int) == 4) {
|
||||||
@@ -149,7 +155,7 @@ test_cast_0x80000000_to_things()
|
|||||||
} else if (sizeof(int) == 8) {
|
} else if (sizeof(int) == 8) {
|
||||||
SHOULD_PASS(ranged_cast<signed int>(sv), sv);
|
SHOULD_PASS(ranged_cast<signed int>(sv), sv);
|
||||||
} else {
|
} else {
|
||||||
assert(!"unhandled case, please add code here");
|
assert(!"unhandled case, please add code for int here");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +180,13 @@ test_cast_0xffffffff_to_things()
|
|||||||
SHOULD_FAIL(ranged_cast<unsigned short>(uv));
|
SHOULD_FAIL(ranged_cast<unsigned short>(uv));
|
||||||
SHOULD_FAIL(ranged_cast<unsigned char>(uv));
|
SHOULD_FAIL(ranged_cast<unsigned char>(uv));
|
||||||
SHOULD_PASS(ranged_cast<signed long long>(sv), sv);
|
SHOULD_PASS(ranged_cast<signed long long>(sv), sv);
|
||||||
SHOULD_PASS(ranged_cast<signed long>(sv), sv);
|
if (sizeof(long) == 4) {
|
||||||
|
SHOULD_FAIL(ranged_cast<signed long>(sv));
|
||||||
|
} else if (sizeof(long) == 8) {
|
||||||
|
SHOULD_PASS(ranged_cast<signed long>(sv), sv);
|
||||||
|
} else {
|
||||||
|
assert(!"unhandled case, please add code for long here");
|
||||||
|
}
|
||||||
SHOULD_FAIL(ranged_cast<signed short>(sv));
|
SHOULD_FAIL(ranged_cast<signed short>(sv));
|
||||||
SHOULD_FAIL(ranged_cast<signed char>(sv));
|
SHOULD_FAIL(ranged_cast<signed char>(sv));
|
||||||
if (sizeof(int) == 4) {
|
if (sizeof(int) == 4) {
|
||||||
@@ -182,7 +194,7 @@ test_cast_0xffffffff_to_things()
|
|||||||
} else if (sizeof(int) == 8) {
|
} else if (sizeof(int) == 8) {
|
||||||
SHOULD_PASS(ranged_cast<signed int>(sv), sv);
|
SHOULD_PASS(ranged_cast<signed int>(sv), sv);
|
||||||
} else {
|
} else {
|
||||||
assert(!"unhandled case, please add code here");
|
assert(!"unhandled case, please add code for int here");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user