├── .editorconfig ├── .github ├── mailbot.json └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── TODO ├── alloctxn └── alloctxn.go ├── artifact ├── .gitignore ├── README.md ├── Vagrantfile ├── vm-init.sh └── vm-setup.sh ├── bench ├── app-bench.sh ├── run-fscq.sh ├── run-go-clnt.sh ├── run-go-nfsd.sh ├── run-linux.sh ├── start-go-nfsd.sh ├── start-linux.sh ├── stop-go-nfsd.sh └── stop-linux.sh ├── cache └── cache.go ├── cmd ├── clnt-null │ └── main.go ├── clnt-smallfile │ └── main.go ├── fs-largefile │ └── main.go ├── fs-smallfile │ └── main.go ├── go-nfsd │ └── main.go ├── largefile │ └── main.go ├── lookup │ └── main.go ├── simple-nfsd │ ├── main.go │ └── start.go ├── smallfile │ └── main.go └── txn-bench │ └── main.go ├── dcache └── dcache.go ├── dir ├── dcache.go ├── dir.go └── dir_test.go ├── eval ├── aggregate-times.py ├── bench.plot ├── bench.py ├── bench.sh ├── data │ └── .gitignore ├── eval.sh ├── fig │ └── .gitignore ├── global-txn-lock.patch ├── largefile.plot ├── largefile.sh ├── latency-tcp.sh ├── latency.sh ├── loc.py ├── nfsdist.bt ├── plot.sh ├── scale.plot ├── scale.py ├── scale.sh ├── serial.patch └── tests.sh ├── fh └── nfs_fh.go ├── fstxn ├── commit.go ├── fsstate.go └── fstxn.go ├── go.mod ├── go.sum ├── inode ├── inode.go └── shrink.go ├── kvs ├── kvs.go └── kvs_test.go ├── n.txt ├── nfs ├── lorder.go ├── mount.go ├── nfs.go ├── nfs_clnt.go ├── nfs_ls.go ├── nfs_ops.go ├── nfs_test.go ├── stats.go └── stats_test.go ├── nfstypes ├── nfs_types.go └── nfs_xdr.go ├── shrinker └── shrinker.go ├── simple ├── 0super.go ├── fh.go ├── inode.go ├── mkfs.go ├── mount.go ├── ops.go ├── recover_example.go └── simple_test.go ├── super └── super.go └── util ├── stats └── stats.go └── timed_disk └── disk.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.sh] 8 | indent_style = space 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /.github/mailbot.json: -------------------------------------------------------------------------------- 1 | { 2 | "commitEmailFormat": "html", 3 | "commitList": "nickolai@csail.mit.edu,kaashoek@mit.edu,tchajed@mit.edu" 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | strategy: 13 | matrix: 14 | go-version: ['stable'] 15 | fail-fast: false 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | - name: Install Goose 24 | run: | 25 | go install github.com/goose-lang/goose/cmd/goose@latest 26 | - name: Install dependencies 27 | run: | 28 | go get -t ./... 29 | - name: Check style 30 | run: | 31 | gofmt -w . 32 | git diff --exit-code 33 | go vet ./... 34 | - name: Test 35 | run: | 36 | go test -v ./... 37 | make goose-output 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Goose 2 | # created by benchmarking scripts 3 | nfs.out 4 | # compiled binaries 5 | fs-smallfile 6 | clnt-smallfile 7 | fs-largefile 8 | go-nfsd 9 | txn-bench 10 | # profiling output 11 | cpu.prof 12 | # per-iteration timing 13 | *-[0-9]*.txt 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOPATH := $(shell go env GOPATH) 2 | GOOSE_DIRS := super cache fh fstxn kvs nfstypes simple 3 | 4 | # Things that don't goose yet: 5 | # . 6 | # dcache: map with string keys 7 | # inode: time package 8 | # nfstypes: need to ignore nfs_xdr.go 9 | # dir 10 | 11 | COQ_PKGDIR := Goose/github_com/mit_pdos/go_nfsd 12 | 13 | all: check goose-output 14 | 15 | check: 16 | test -z "$$(gofmt -d .)" 17 | go vet ./... 18 | 19 | goose-output: $(patsubst %,${COQ_PKGDIR}/%.v,$(GOOSE_DIRS)) 20 | 21 | ${COQ_PKGDIR}/%.v: % %/* 22 | $(GOPATH)/bin/goose -out Goose ./$< 23 | 24 | clean: 25 | rm -rf Goose 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoNFS 2 | 3 | [![CI](https://github.com/mit-pdos/go-nfsd/actions/workflows/build.yml/badge.svg)](https://github.com/mit-pdos/go-nfsd/actions/workflows/build.yml) 4 | 5 | An NFSv3 server that uses [GoJournal](https://github.com/mit-pdos/go-journal) to 6 | write operations atomically to disk. Unlike many other NFS servers, GoNFS 7 | implements the file-system abstraction on top of a disk (which can be any file, 8 | including an in-memory file in `/tmp`, an ordinary file in another file system, 9 | or a block device). 10 | 11 | ## GoJournal artifact 12 | 13 | The artifact for the OSDI 2021 GoJournal paper is in this repo at 14 | [artifact](artifact/). See the README there for detailed instructions on 15 | obtaining and using the artifact. 16 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Potential improvements: 2 | 3 | - The txn layer could implement more fine-grained locking for CommitWait. 4 | For example, if a transaction is writing full blocks (no inodes or 5 | bits), it's not necessary to acquire the txn commit lock. Even more 6 | aggressively, txn could acquire locks (using a lockmap) for every disk 7 | block that requires an installation read (sorting the disk block addrs 8 | to avoid deadlock). Not clear if that optimization is worth it, though. 9 | -------------------------------------------------------------------------------- /alloctxn/alloctxn.go: -------------------------------------------------------------------------------- 1 | package alloctxn 2 | 3 | import ( 4 | "github.com/mit-pdos/go-journal/addr" 5 | "github.com/mit-pdos/go-journal/alloc" 6 | "github.com/mit-pdos/go-journal/buf" 7 | "github.com/mit-pdos/go-journal/common" 8 | "github.com/mit-pdos/go-journal/jrnl" 9 | "github.com/mit-pdos/go-journal/obj" 10 | "github.com/mit-pdos/go-journal/util" 11 | "github.com/mit-pdos/go-nfsd/super" 12 | ) 13 | 14 | // 15 | // alloctxn implements transactions using buftxn. It adds to buftxn 16 | // support for (1) block and inode allocation. 17 | // 18 | 19 | type AllocTxn struct { 20 | Super *super.FsSuper 21 | Op *jrnl.Op 22 | Balloc *alloc.Alloc 23 | Ialloc *alloc.Alloc 24 | allocInums []common.Inum 25 | freeInums []common.Inum 26 | allocBnums []common.Bnum 27 | freeBnums []common.Bnum 28 | } 29 | 30 | func Begin(super *super.FsSuper, log *obj.Log, balloc *alloc.Alloc, ialloc *alloc.Alloc) *AllocTxn { 31 | atxn := &AllocTxn{ 32 | Super: super, 33 | Op: jrnl.Begin(log), 34 | Ialloc: ialloc, 35 | Balloc: balloc, 36 | allocInums: make([]common.Inum, 0), 37 | freeInums: make([]common.Inum, 0), 38 | allocBnums: make([]common.Bnum, 0), 39 | freeBnums: make([]common.Bnum, 0), 40 | } 41 | return atxn 42 | } 43 | 44 | // Id returns a pointer to the Op for debug printing only 45 | func (atxn *AllocTxn) Id() *jrnl.Op { 46 | return atxn.Op 47 | } 48 | 49 | func (atxn *AllocTxn) AllocINum() common.Inum { 50 | inum := common.Inum(atxn.Ialloc.AllocNum()) 51 | util.DPrintf(1, "AllocINum -> # %v\n", inum) 52 | if inum != common.NULLINUM { 53 | atxn.allocInums = append(atxn.allocInums, inum) 54 | } 55 | return inum 56 | } 57 | 58 | func (atxn *AllocTxn) FreeINum(inum common.Inum) { 59 | util.DPrintf(1, "FreeINum -> # %v\n", inum) 60 | atxn.freeInums = append(atxn.freeInums, inum) 61 | } 62 | 63 | func (atxn *AllocTxn) WriteBits(nums []uint64, blk uint64, alloc bool) { 64 | for _, n := range nums { 65 | a := addr.MkBitAddr(blk, n) 66 | var b = byte(1 << (n % 8)) 67 | if !alloc { 68 | b = ^b 69 | } 70 | atxn.Op.OverWrite(a, 1, []byte{b}) 71 | } 72 | } 73 | 74 | // Write allocated/free bits to the on-disk bit maps 75 | func (atxn *AllocTxn) PreCommit() { 76 | util.DPrintf(1, "commitBitmaps: alloc inums %v blks %v\n", atxn.allocInums, 77 | atxn.allocBnums) 78 | 79 | atxn.WriteBits(atxn.allocInums, atxn.Super.BitmapInodeStart(), true) 80 | atxn.WriteBits(atxn.allocBnums, atxn.Super.BitmapBlockStart(), true) 81 | 82 | util.DPrintf(1, "commitBitmaps: free inums %v blks %v\n", atxn.freeInums, 83 | atxn.freeBnums) 84 | 85 | atxn.WriteBits(atxn.freeInums, atxn.Super.BitmapInodeStart(), false) 86 | atxn.WriteBits(atxn.freeBnums, atxn.Super.BitmapBlockStart(), false) 87 | } 88 | 89 | // On-disk bitmap has been updated; update in-memory state for free bits 90 | func (atxn *AllocTxn) PostCommit() { 91 | util.DPrintf(1, "updateFree: inums %v blks %v\n", atxn.freeInums, atxn.freeBnums) 92 | for _, inum := range atxn.freeInums { 93 | atxn.Ialloc.FreeNum(uint64(inum)) 94 | } 95 | for _, bn := range atxn.freeBnums { 96 | atxn.Balloc.FreeNum(bn) 97 | } 98 | } 99 | 100 | // Abort: free allocated inums and bnums. Nothing to do for freed 101 | // ones, because in-memory state hasn't been updated by freeINum()/freeBlock(). 102 | func (atxn *AllocTxn) PostAbort() { 103 | util.DPrintf(1, "Abort: inums %v blks %v\n", atxn.allocInums, atxn.allocBnums) 104 | for _, inum := range atxn.allocInums { 105 | atxn.Ialloc.FreeNum(uint64(inum)) 106 | } 107 | for _, bn := range atxn.allocBnums { 108 | atxn.Balloc.FreeNum(bn) 109 | } 110 | } 111 | 112 | func (atxn *AllocTxn) AssertValidBlock(blkno common.Bnum) { 113 | if blkno > 0 && (blkno < atxn.Super.DataStart() || 114 | blkno >= atxn.Super.MaxBnum()) { 115 | util.DPrintf(0, "bad blkno %v (max=%v)\n", blkno, atxn.Super.MaxBnum()) 116 | panic("invalid blkno") 117 | } 118 | } 119 | 120 | func (atxn *AllocTxn) AllocBlock() common.Bnum { 121 | util.DPrintf(5, "alloc block\n") 122 | bn := common.Bnum(atxn.Balloc.AllocNum()) 123 | atxn.AssertValidBlock(bn) 124 | util.DPrintf(1, "alloc block -> %v\n", bn) 125 | if bn != common.NULLBNUM { 126 | atxn.allocBnums = append(atxn.allocBnums, bn) 127 | } 128 | return bn 129 | } 130 | 131 | func (atxn *AllocTxn) FreeBlock(blkno common.Bnum) { 132 | util.DPrintf(1, "free block %v\n", blkno) 133 | atxn.AssertValidBlock(blkno) 134 | if blkno == 0 { 135 | return 136 | } 137 | atxn.ZeroBlock(blkno) 138 | atxn.freeBnums = append(atxn.freeBnums, blkno) 139 | } 140 | 141 | func (atxn *AllocTxn) ReadBlock(blkno common.Bnum) *buf.Buf { 142 | util.DPrintf(5, "ReadBlock %d\n", blkno) 143 | atxn.AssertValidBlock(blkno) 144 | addr := atxn.Super.Block2addr(blkno) 145 | return atxn.Op.ReadBuf(addr, common.NBITBLOCK) 146 | } 147 | 148 | func (atxn *AllocTxn) ZeroBlock(blkno common.Bnum) { 149 | util.DPrintf(5, "zero block %d\n", blkno) 150 | buf := atxn.ReadBlock(blkno) 151 | for i := range buf.Data { 152 | buf.Data[i] = 0 153 | } 154 | buf.SetDirty() 155 | } 156 | -------------------------------------------------------------------------------- /artifact/.gitignore: -------------------------------------------------------------------------------- 1 | /.vagrant/ 2 | -------------------------------------------------------------------------------- /artifact/README.md: -------------------------------------------------------------------------------- 1 | # GoJournal: a verified, concurrent, crash-safe journaling system (Artifact) 2 | 3 | [![License: CC BY 4 | 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) 5 | 6 | The text of this artifact is licensed under the Creative Commons Attribution 4.0 7 | license. The code is under the same MIT license as the parent repo. 8 | 9 | # Getting started 10 | 11 | Read the description below on downloading and using the VM. You should be able 12 | to run all the commands in this artifact quickly (within 30 minutes) with the 13 | exception of compiling Perennial, so to get started we recommend gathering all 14 | the data and skipping the compilation step. Comparing the results and compiling 15 | the proofs in Perennial will take a bit more time. 16 | 17 | ### About the VM 18 | 19 | You can get the VM from Zenodo via DOI 20 | [10.5281/zenodo.4657115](https://zenodo.org/record/4657115). The download is a 21 | little over 2GB. 22 | 23 | The VM was created by using vagrant, as specified in the 24 | [Vagrantfile](Vagrantfile). 25 | **The user account is `vagrant` with no password** (that is, the 26 | empty password). The user account has sudo access without a password. The setup 27 | consists of running [vm-init.sh](vm-init.sh) and [vm-setup.sh](vm-setup.sh). 28 | 29 | You can launch the VM headless and then SSH to it. There's a port forwarding 30 | rule so that `ssh -p 10322 vagrant@localhost` should work, without a password 31 | prompt. 32 | 33 | The artifact's README and setup code are located at `~/go-nfsd/artifact`. The 34 | README.md file there might be out-of-date by the time you read this; please run 35 | `git pull` when you start, or follow the README on GitHub rather than in the VM. 36 | Most of the work happens in `~/go-nfsd/eval`. 37 | 38 | We've configured the VM with 8GB of RAM and 4 cores. You'll want more cores for 39 | the scalability experiment, although we found the benchmark to be I/O 40 | bottlenecked. Less RAM also might work but could lower performance. 41 | 42 | ## Claims 43 | 44 | The artifact concerns four claims in the paper: 45 | 46 | 1. GoJournal's proof overhead is about 20x (in the tricky concurrent parts), 47 | while SimpleNFS is only 8x. Measured by lines of code. 48 | 2. The proofs for Perennial, GoJournal, and SimpleNFS are complete. 49 | 3. GoJournal is functional when compared against ext4 (using journaled data and 50 | over NFS for a fair comparison). We demonstrate this by showing GoNFS gets 51 | close throughput in the benchmarks in Figure 16, which use a RAM-backed disk. 52 | 4. GoJournal is scalable. We demonstrate this by showing performance for the 53 | smallfile benchmark scales with the number of clients, on an SSD (Figure 17). 54 | 55 | # Detailed instructions 56 | 57 | ## Performance evaluation 58 | 59 | We've cloned several repositories for you into the VM, most notably: 60 | 61 | - https://github.com/mit-pdos/go-journal (located at `~/code/go-journal`) 62 | implements GoJournal on top of a disk. The `jrnl` package as the top-level 63 | API. 64 | - https://github.com/mit-pdos/go-nfsd (located at `~/go-nfsd`): includes 65 | SimpleNFS and GoNFS. SimpleNFS is in `simple/`, and the binary for `GoNFS` is 66 | `cmd/go-nfsd`. The artifact is implemented with several scripts in `eval` in 67 | this repo. 68 | - https://github.com/mit-pdos/perennial (located at `~/perennial`): the 69 | Perennial framework and all program proofs for GoJournal and SimpleNFS. 70 | 71 | ### Gather data 72 | 73 | This should all be done in the eval directory: 74 | 75 | ```sh 76 | cd ~/go-nfsd/eval 77 | ``` 78 | 79 | ```sh 80 | ./loc.py | tee data/lines-of-code.txt 81 | ``` 82 | 83 | Instantaneous. The numbers won't match up exactly with the paper (see below 84 | under "Check output"). 85 | 86 | ```sh 87 | ./bench.sh -ssd ~/disk.img 88 | ``` 89 | 90 | Takes 2-3 minutes. You can manually inspect the output file at 91 | `eval/data/bench-raw.txt` (which is fairly readable) if you'd like. 92 | 93 | ```sh 94 | ./scale.sh 12 95 | ``` 96 | 97 | **Takes a few minutes** (the 12 is the number of clients to run till; you can use a 98 | smaller number of you want it to finish faster). Outputs to `eval/data/scale-raw.txt`. 99 | 100 | ### Produce graphs 101 | 102 | ```sh 103 | ./plot.sh 104 | ``` 105 | 106 | Read the script to see exactly how each plot is generated. 107 | 108 | ### Check output 109 | 110 | Read `data/lines-of-code.txt` for the lines of code. 111 | 112 | The exact performance results will vary depending on your machine, and differ 113 | from the paper results because they are run in a VM. 114 | 115 | You can get the figures out of the VM by running (from your host machine): 116 | 117 | ```sh 118 | rsync -a -e 'ssh -p 10322' vagrant@localhost:./go-nfsd/eval/fig ./ 119 | ``` 120 | 121 | The graphs there are: 122 | 123 | - `bench.pdf` benchmarks run on a RAMdisk, where `app` clones and compiles the 124 | xv6 operating system 125 | - `bench-ssd.pdf` same benchmarks but on an SSD (whatever the host disk is, 126 | actually). 127 | - `largefile.pdf` just the largefile benchmark run on a variety of 128 | configurations (not shown in the paper). 129 | - `scale.pdf` scalability of the smallfile benchmark with a varying number of 130 | clients, on an SSD. 131 | 132 | ## Compile the proofs 133 | 134 | The paper claims to have verified the GoJournal implementation. You should check 135 | this by compiling the proofs in the `perennial` repo: 136 | 137 | ```sh 138 | cd ~/perennial 139 | make -j4 src/program_proof/simple/print_assumptions.vo 140 | ``` 141 | 142 | **This will take 30-40 minutes, and 60-70 CPU minutes.** 143 | 144 | This only compiles the SimpleNFS top-level proof and all its dependencies, 145 | including the GoJournal proofs (in `src/program_proof/buftxn/sep_buftxn_proof.v` 146 | and `sep_buftxn_recovery_proof.v`). The repository has a bunch of other research 147 | code in it that isn't related to this paper. 148 | 149 | We do proofs over Go using [Goose](https://github.com/tchajed/goose), which 150 | compiles Go code to a Coq model. The output is checked in to the Perennial repo 151 | for simplicity, but you can re-generate it from the go-nfsd code: 152 | 153 | ```sh 154 | cd ~/perennial 155 | rm -r external/Goose/github_com/mit_pdos/go_journal 156 | rm -r external/Goose/github_com/mit_pdos/go_nfsd 157 | ./etc/update-goose.py --goose $GOOSE_PATH --journal $GO_JOURNAL_PATH --nfsd $GO_NFSD_PATH --skip-goose-examples --verbose 158 | git status 159 | ``` 160 | 161 | The final `git status` command should report that the working tree has been 162 | restored to its previous state. 163 | 164 | ## Under-documented features 165 | 166 | These are features that we didn't have reviewers run but which we thought were 167 | useful code to have. 168 | 169 | [`./eval.sh`](eval.sh) just has all the commands in one file. 170 | 171 | `./tests.sh` runs the fsstress and fsx-linux test suites from the Linux test 172 | project (all the setup for these is included in `vm-setup.sh`). 173 | 174 | The VM hosted on Zenodo was not compiled with the setup to run DFSCQ to save 175 | space in the image, but you should be able to do this yourself. Run the commands 176 | in [vm-setup.sh](vm-setup.sh) for DFSCQ (this takes about 5 minutes). Then run 177 | `bench.sh` as above, and it will include results for FSCQ. None of the plots 178 | include this data but after running `bench.py` you can easily look at the raw 179 | data including FSCQ results with `column -t eval/data/bench.data`. 180 | -------------------------------------------------------------------------------- /artifact/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | # The most common configuration options are documented and commented below. 6 | # For a complete reference, please see the online documentation at 7 | # https://docs.vagrantup.com. 8 | 9 | config.vm.box = "bento/ubuntu-21.04" 10 | 11 | config.vm.hostname = "gojournal-vm" 12 | 13 | # support ssh -p 10322 vagrant@localhost to tunnel into guest 14 | config.vm.network "forwarded_port", guest: 22, host: 10322, host_ip: "127.0.0.1" 15 | 16 | # configure the VirtualBox VM 17 | config.vm.provider "virtualbox" do |vb| 18 | vb.name = "gojournal-vm" 19 | 20 | vb.memory = 8192 21 | vb.cpus = 4 22 | 23 | # Use Host I/O cache (speeds up disk performance) 24 | vb.customize ["storagectl", :id, "--name", "SATA Controller", 25 | "--hostiocache", "on"] 26 | # Mark disk as an SSD (enables TRIM support) 27 | vb.customize ["storageattach", :id, "--storagectl", "SATA Controller", 28 | "--port", "0", 29 | "--discard", "on", 30 | "--nonrotational", "on"] 31 | end 32 | 33 | # provision the machine 34 | # 35 | # NOTE: these scripts take 10-15 minutes to run 36 | config.vm.provision "shell", path: "vm-init.sh", privileged: false 37 | config.vm.provision "shell" do |s| 38 | s.path = "vm-setup.sh" 39 | s.privileged = false 40 | # you can also use -no-ocaml or -no-coq to skip those steps 41 | # (those are the slowest; FSCQ's dependencies take a lot of space) 42 | s.args = ["-no-fscq"] 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /artifact/vm-init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd 6 | 7 | sudo apt-get -y update 8 | sudo apt-get -y upgrade 9 | sudo apt-get -y install zsh 10 | 11 | if [ ! -e ~/.oh-my-zsh ]; then 12 | wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh 13 | sh install.sh --unattended 14 | rm install.sh 15 | fi 16 | sudo chsh -s /usr/bin/zsh "$USER" 17 | sed -i 's/ZSH_THEME=.*/ZSH_THEME="dst"/' ~/.zshrc 18 | rm -f ~/.bashrc 19 | 20 | sudo passwd -d "$USER" 21 | sudo sed -e 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' -i /etc/ssh/sshd_config 22 | sudo systemctl restart sshd 23 | 24 | if [ ! -e go-nfsd ]; then 25 | git clone https://github.com/mit-pdos/go-nfsd 26 | fi 27 | 28 | git config --global pull.ff only 29 | -------------------------------------------------------------------------------- /artifact/vm-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | install_ocaml=true 6 | install_coq=true 7 | install_fscq=true 8 | while [[ "$#" -gt 0 ]]; do 9 | case "$1" in 10 | -no-ocaml) 11 | install_ocaml=false 12 | shift 13 | ;; 14 | -ocaml) 15 | install_ocaml=true 16 | shift 17 | ;; 18 | -no-coq) 19 | install_coq=false 20 | shift 21 | ;; 22 | -coq) 23 | install_ocaml=true 24 | shift 25 | ;; 26 | -no-fscq) 27 | install_fscq=false 28 | shift 29 | ;; 30 | -fscq) 31 | install_fscq=true 32 | shift 33 | ;; 34 | *) 35 | echo "Unexpected argument $1" 1>&2 36 | exit 1 37 | ;; 38 | esac 39 | done 40 | 41 | if [ "$install_fscq" = true ]; then 42 | install_coq=true 43 | fi 44 | 45 | if [ "$install_coq" = true ]; then 46 | install_ocaml=true 47 | fi 48 | 49 | cd 50 | 51 | # Install really basic dependencies 52 | 53 | sudo apt-get update 54 | sudo apt-get install -y git python3-pip wget unzip psmisc sudo time 55 | 56 | # Get source code 57 | 58 | ## assumes https://github.com/mit-pdos/go-nfsd has already been cloned to 59 | ## ~/go-nfsd (since this is the easiest way to run this script) 60 | ln -s ~/go-nfsd/artifact ~/artifact 61 | ln -s ~/go-nfsd/eval ~/eval 62 | 63 | git clone \ 64 | --branch osdi21 \ 65 | --recurse-submodules \ 66 | https://github.com/mit-pdos/perennial 67 | 68 | mkdir ~/code 69 | cd ~/code 70 | git clone --branch v0.4.0 https://github.com/mit-pdos/go-journal & 71 | git clone https://github.com/mit-pdos/xv6-public & 72 | git clone --branch v0.1.0 https://github.com/tchajed/marshal & 73 | git clone --branch v0.3.1 https://github.com/tchajed/goose & 74 | git clone https://github.com/mit-pdos/fscq & 75 | wait 76 | git clone --depth=1 https://github.com/linux-test-project/ltp 77 | cd 78 | 79 | # These are set in ~/.zshenv so that they are available even over ssh without a 80 | # login shell. This makes it so passing ssh the eval scripts to run 81 | # non-interactively works. 82 | cat >>~/.zshenv <>~/.zshenv 141 | export PATH=/usr/local/go/bin:$PATH 142 | 143 | go install github.com/tchajed/goose/cmd/goose@v0.3.1 144 | 145 | cd ~/go-nfsd 146 | # fetch dependencies 147 | go build ./cmd/go-nfsd && rm go-nfsd 148 | cd 149 | 150 | # Install Coq 151 | 152 | # opam dependencies 153 | sudo apt-get install -y m4 bubblewrap 154 | # coq dependencies 155 | sudo apt-get install -y libgmp-dev 156 | 157 | # use binary installer for opam since it has fewer dependencies than Ubuntu 158 | # package 159 | wget https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh 160 | # echo is to answer question about where to install opam 161 | echo "" | sh install.sh --no-backup 162 | rm install.sh 163 | 164 | opam init --auto-setup --bare 165 | if [ "$install_ocaml" = true ]; then 166 | # takes ~5 minutes (compiles OCaml) 167 | opam switch create 4.11.0+flambda 168 | 169 | # shellcheck disable=2046 170 | eval $(opam env) 171 | 172 | if [ "$install_coq" = "true" ]; then 173 | # takes ~5 minutes 174 | opam install -y -j4 coq.8.13.2 175 | fi 176 | # opam sets up .profile, so make sure it's sourced 177 | echo -e "\nsource ~/.profile" >>~/.zshrc 178 | fi 179 | 180 | # these take a lot of space in the VM 181 | if [ "$install_fscq" = "true" ]; then 182 | # Dependencies for DFSCQ 183 | sudo apt-get install -y ghc cabal-install libfuse-dev 184 | cabal update 185 | cabal install --lib rdtsc digest 186 | cd ~/code/fscq/src 187 | # takes ~3 minutes 188 | make J=4 mkfs fscq 189 | cd 190 | fi 191 | 192 | sudo apt clean 193 | opam clean 194 | 195 | # zeroing the free space reduces the size of the exported disk 196 | # 197 | # it doesn't particularly help for the original vagrant image 198 | dd if=/dev/zero of=zeros bs=1M || true 199 | sync zeros 200 | rm zeros 201 | sync 202 | # wait in case ext4 needs to free in the background 203 | sleep 10 204 | -------------------------------------------------------------------------------- /bench/app-bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: app-bench.sh xv6-repo path-to-fs 4 | # 5 | # path-to-fs should have the file system being tested mounted. 6 | # 7 | # Clones xv6-repo to path-to-fs/xv6, then compiles the kernel. 8 | # 9 | 10 | set -eu 11 | 12 | if [ $# -ne 2 ]; then 13 | echo "$0 xv6-repo top-dir" 14 | exit 1 15 | fi 16 | xv6_repo="$1" 17 | fs_dir="$2" 18 | 19 | echo "=== app-bench $xv6_repo $fs_dir ===" 1>&2 20 | cd "$fs_dir" 21 | 22 | # run benchmark out of /tmp to avoid overhead of reading source repo 23 | tmp_dir="/tmp" 24 | if [ -d "/dev/shm" ]; then 25 | tmp_dir="/dev/shm" 26 | fi 27 | xv6_tmp="$tmp_dir/xv6" 28 | rm -rf "$xv6_tmp" 29 | cp -r "$xv6_repo" "$xv6_tmp" 30 | # just in case, do one clone for warmup 31 | git clone --quiet "$xv6_tmp" "$tmp_dir/xv6-copy" 32 | rm -rf "$tmp_dir/xv6-copy" 33 | 34 | time_file="/tmp/time" 35 | 36 | #echo "=== git clone ===" 37 | /usr/bin/time -f "clone real %e user %U sys %S" -o "$time_file" git clone --quiet "$xv6_tmp" xv6 38 | clone_time="$(cut -d ' ' -f3 <"$time_file")" 39 | cat "$time_file" 1>&2 40 | 41 | #echo "=== compile xv6 ===" 42 | cd xv6 43 | /usr/bin/time -f "compile real %e user %U sys %S" -o "$time_file" make --quiet kernel 44 | compile_time="$(cut -d ' ' -f3 <"$time_file")" 45 | cat "$time_file" 1>&2 46 | rm -f "$time_file" 47 | 48 | total_time=$(awk "BEGIN{ print $clone_time + $compile_time }") 49 | throughput=$(awk "BEGIN{ print 1.0 / $total_time }") 50 | echo "total real $total_time s" 1>&2 51 | echo "app-bench $throughput app/s" 52 | 53 | rm -rf "$xv6_tmp" 54 | -------------------------------------------------------------------------------- /bench/run-fscq.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | disk_file=/dev/shm/disk.img 5 | fscq_path="" 6 | mnt_path="/mnt/nfs" 7 | 8 | while [[ "$#" -gt 0 ]]; do 9 | case "$1" in 10 | -disk) 11 | shift 12 | disk_file="$1" 13 | shift 14 | ;; 15 | -fscq) 16 | shift 17 | fscq_path="$1" 18 | shift 19 | ;; 20 | -mnt) 21 | shift 22 | mnt_path="$1" 23 | shift 24 | ;; 25 | -*) 26 | echo "Unexpected argument $1" 2>&1 27 | exit 1 28 | ;; 29 | *) 30 | break 31 | ;; 32 | esac 33 | done 34 | 35 | if [[ -z "$fscq_path" && -n "$FSCQ_PATH" ]]; then 36 | fscq_path="$FSCQ_PATH" 37 | fi 38 | 39 | if [ ! -d "$fscq_path" ]; then 40 | echo "Please set FSCQ_PATH or pass -fscq" 2>&1 41 | exit 1 42 | fi 43 | 44 | set -u 45 | 46 | function cleanup { 47 | fusermount -u "$mnt_path" 48 | if [ -f "$disk_file" ]; then 49 | rm -f "$disk_file" 50 | fi 51 | } 52 | trap cleanup EXIT 53 | 54 | "$fscq_path"/src/mkfs "$disk_file" 55 | "$fscq_path"/src/fscq "$disk_file" \ 56 | -o big_writes,atomic_o_trunc,use_ino,kernel_cache \ 57 | "$mnt_path" 58 | sleep 1 59 | echo "# fscq -disk $disk_file" 60 | echo "run $*" 1>&2 61 | "$@" 62 | -------------------------------------------------------------------------------- /bench/run-go-clnt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Usage: ./run-go-clnt.sh go run ./cmd/clnt-smallfile/main.go 5 | # 6 | 7 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 8 | # root of repo 9 | cd $DIR/.. 10 | 11 | # taskset 0xc go run ./cmd/go-nfsd/ -disk /dev/shm/goose.img & 12 | go run ./cmd/go-nfsd/ -disk /dev/shm/goose.img & 13 | sleep 1 14 | killall -0 go-nfsd # make sure server is running 15 | # taskset 0x3 $1 /mnt/nfs 16 | echo "$@" 17 | "$@" 18 | killall go-nfsd 19 | -------------------------------------------------------------------------------- /bench/run-go-nfsd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Usage: ./run-go-nfsd.sh go run ./cmd/fs-smallfile/main.go 5 | # 6 | 7 | # taskset 0xc go run ./cmd/go-nfsd/ -disk /dev/shm/goose.img & 8 | 9 | # go run ./cmd/go-nfsd/ -disk /dev/shm/goose.img -cpuprofile=nfsd.prof & 10 | 11 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 12 | # root of repo 13 | cd "$DIR"/.. 14 | 15 | disk_file=/dev/shm/goose.img 16 | cpu_list="" 17 | extra_args=() 18 | while true; do 19 | case "$1" in 20 | -disk) 21 | shift 22 | disk_file="$1" 23 | shift 24 | ;; 25 | --cpu-list) 26 | shift 27 | cpu_list="$1" 28 | shift 29 | ;; 30 | # some argument in -foo=value syntax 31 | -*=*) 32 | extra_args+=("$1") 33 | shift 34 | ;; 35 | -*) 36 | extra_args+=("$1" "$2") 37 | shift 38 | shift 39 | ;; 40 | *) 41 | break 42 | ;; 43 | esac 44 | done 45 | 46 | set -eu 47 | 48 | if [ -e "$disk_file" ]; then 49 | dd status=none if=/dev/zero of="$disk_file" bs=4K count=200000 conv=notrunc 50 | sync "$disk_file" 51 | fi 52 | 53 | if [ -z "$cpu_list" ]; then 54 | ./bench/start-go-nfsd.sh -disk "$disk_file" "${extra_args[@]}" || exit 1 55 | else 56 | taskset --cpu-list "$cpu_list" ./bench/start-go-nfsd.sh -disk "$disk_file" "${extra_args[@]}" || exit 1 57 | fi 58 | 59 | function cleanup { 60 | ./bench/stop-go-nfsd.sh 61 | if [ -f "$disk_file" ]; then 62 | rm -f "$disk_file" 63 | fi 64 | } 65 | trap cleanup EXIT 66 | 67 | # taskset 0x3 $1 /mnt/nfs 68 | echo "# go-nfsd -disk $disk_file ${extra_args[*]}" 69 | echo "run $*" 1>&2 70 | "$@" 71 | -------------------------------------------------------------------------------- /bench/run-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # 6 | # Usage: ./run-linux.sh go run ./cmd/fs-smallfile/main.go 7 | # 8 | # takes same flags as start-linux.sh but uses /dev/shm/nfs3.img as default disk 9 | # 10 | 11 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 12 | # root of repo 13 | cd "$DIR"/.. 14 | 15 | disk_file="/dev/shm/disk.img" 16 | extra_args=() 17 | native=false 18 | while true; do 19 | case "$1" in 20 | -disk) 21 | shift 22 | disk_file="$1" 23 | shift 24 | ;; 25 | -native) 26 | shift 27 | native=true 28 | ;; 29 | -*) 30 | extra_args+=("$1" "$2") 31 | shift 32 | shift 33 | ;; 34 | # stop gathering start-linux.sh flags as soon as a non-option is reached 35 | # remainder of command line is treated as command to run 36 | *) 37 | break 38 | ;; 39 | esac 40 | done 41 | 42 | ./bench/start-linux.sh -native="$native" -disk "$disk_file" "${extra_args[@]}" || exit 1 43 | 44 | function cleanup { 45 | ./bench/stop-linux.sh -native="$native" "$disk_file" 46 | } 47 | trap cleanup EXIT 48 | 49 | native_text="" 50 | if [ "$native" = "true" ]; then 51 | native_text="-native" 52 | fi 53 | 54 | echo "# Linux "$native_text" -disk $disk_file ${extra_args[*]}" 55 | echo "run $*" 1>&2 56 | "$@" 57 | -------------------------------------------------------------------------------- /bench/start-go-nfsd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | # 6 | # Usage: ./start-go-nfsd.sh 7 | # 8 | # default disk is /dev/shm/goose.img but can be overriden by passing -disk again 9 | # 10 | 11 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 12 | # root of repo 13 | cd "$DIR"/.. 14 | 15 | nfs_mount_opts="" 16 | extra_args=() 17 | while [[ "$#" -gt 0 ]]; do 18 | case "$1" in 19 | -nfs-mount-opts) 20 | shift 21 | nfs_mount_opts="$1" 22 | shift 23 | ;; 24 | -*=*) 25 | extra_args+=("$1") 26 | shift 27 | ;; 28 | -*) 29 | extra_args+=("$1" "$2") 30 | shift 31 | shift 32 | ;; 33 | esac 34 | done 35 | 36 | go build ./cmd/go-nfsd 37 | ./go-nfsd -disk /dev/shm/goose.img "${extra_args[@]}" >nfs.out 2>&1 & 38 | sleep 2 39 | killall -0 go-nfsd # make sure server is running 40 | killall -SIGUSR1 go-nfsd # reset stats after recovery 41 | sudo mount -t nfs -o "$nfs_mount_opts" localhost:/ /mnt/nfs 42 | -------------------------------------------------------------------------------- /bench/start-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # 6 | # Usage: ./start-linux.sh [-disk path] [-mount-opts opts] [-fs fs] 7 | # 8 | # set to /dev/shm/nfs3.img to use tmpfs, or a file to use the disk (through the host 9 | # file system), or a block device to use a partition directly (NOTE: it will be 10 | # overwritten; don't run as root) 11 | # 12 | # fs defaults to ext4 13 | # 14 | # opts defaults to data=journal if fs is ext3 or ext4 if not passed (use 15 | # data=ordered for the default mode where metadata is journaled but not data) 16 | 17 | # Requires /srv/nfs/bench to be set up for NFS export, otherwise you will get 18 | # 19 | # mount.nfs: access denied by server while mounting localhost:/srv/nfs/bench. 20 | # 21 | # 1. Create /srv/nfs/bench if it doesn't exist. 22 | # 2. Edit /etc/exports and add the line: 23 | # /srv/nfs/bench localhost(rw,sync,no_subtree_check,fsid=0) 24 | # 3. Run 25 | # sudo exportfs -arv 26 | # to reload the export table 27 | # 28 | 29 | fs="ext4" 30 | disk_file="" 31 | mount_opts="" 32 | nfs_mount_opts="" 33 | size_mb=400 34 | # run without NFS 35 | native=false 36 | 37 | while true; do 38 | case "$1" in 39 | -disk) 40 | shift 41 | disk_file="$1" 42 | shift 43 | ;; 44 | -mount-opts) 45 | shift 46 | mount_opts="$1" 47 | shift 48 | ;; 49 | -nfs-mount-opts) 50 | shift 51 | nfs_mount_opts="$1" 52 | shift 53 | ;; 54 | -fs) 55 | shift 56 | fs="$1" 57 | shift 58 | ;; 59 | -size) 60 | shift 61 | size_mb="$1" 62 | shift 63 | ;; 64 | -native=true) 65 | shift 66 | native=true 67 | ;; 68 | -native=false) 69 | shift 70 | native=false 71 | ;; 72 | *) 73 | break 74 | ;; 75 | esac 76 | done 77 | 78 | set -u 79 | 80 | if [[ "$fs" == "ext4" ]] || [[ "$fs" = "ext3" ]]; then 81 | if [ -z "$mount_opts" ]; then 82 | mount_opts="data=journal" 83 | fi 84 | fi 85 | 86 | if [ -z "$disk_file" ]; then 87 | echo "-disk not provided" >&2 88 | exit 1 89 | fi 90 | 91 | conv_arg=() 92 | if [ ! -b "$disk_file" ]; then 93 | conv_arg+=("conv=notrunc") 94 | fi 95 | 96 | _nfs_mount="vers=3,wsize=131072,rsize=131072" 97 | if [ -n "$nfs_mount_opts" ]; then 98 | _nfs_mount="${_nfs_mount},$nfs_mount_opts" 99 | fi 100 | 101 | mkfs_args=() 102 | if [ "$fs" == "ext4" ]; then 103 | # explicitly set a block size of 4k (ext4 will use a 1k block size if the 104 | # disk is small) 105 | mkfs_args+=("-b" "4096") 106 | # do initialization during mkfs, not after mount 107 | mkfs_args+=("-E" "lazy_itable_init=0,lazy_journal_init=0") 108 | fi 109 | if [ "$fs" == "btrfs" ]; then 110 | mkfs_args+=("--byte-count" "${size_mb}m") 111 | fi 112 | # NOTE: size is not passed to XFS 113 | 114 | cached_fs="/dev/shm/init-$fs-$size_mb.img" 115 | if [ ! -f "$cached_fs" ]; then 116 | # count is in units of 4KB blocks 117 | dd status=none if=/dev/zero of="$cached_fs" bs=4K count=$((size_mb * 1024 / 4)) 118 | if [[ "$fs" == "ext4" || "$fs" == "ext3" ]]; then 119 | # mke2fs takes size as a final argument 120 | mkfs."$fs" -q "${mkfs_args[@]}" "$cached_fs" "${size_mb}M" 121 | else 122 | mkfs."$fs" -q "${mkfs_args[@]}" "$cached_fs" 123 | fi 124 | fi 125 | 126 | dd status=none if="$cached_fs" of="$disk_file" bs=8K "${conv_arg[@]}" 127 | sync "$disk_file" 128 | 129 | if [ "$native" = "true" ]; then 130 | sudo mount -t "$fs" -o "$mount_opts" "$disk_file" /mnt/nfs 131 | sudo chown $USER /mnt/nfs 132 | else 133 | sudo mount -t "$fs" -o "$mount_opts" "$disk_file" /srv/nfs/bench 134 | sudo systemctl start nfs-server.service 135 | sudo mount -t nfs -o "${_nfs_mount}" localhost:/srv/nfs/bench /mnt/nfs 136 | sudo chmod 777 /srv/nfs/bench 137 | fi 138 | -------------------------------------------------------------------------------- /bench/stop-go-nfsd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Usage: ./stop-go-nfsd.sh 5 | # 6 | 7 | killall -s SIGINT go-nfsd 8 | sudo umount -f /mnt/nfs 9 | -------------------------------------------------------------------------------- /bench/stop-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Usage: ./stop-linux.sh [disk file] 5 | # 6 | 7 | native=false 8 | 9 | while true; do 10 | case "$1" in 11 | -native=true) 12 | shift 13 | native=true 14 | ;; 15 | -native=false) 16 | shift 17 | native=false 18 | ;; 19 | *) 20 | break 21 | ;; 22 | esac 23 | done 24 | 25 | disk_file=/dev/shm/nfs3.img 26 | if [ ! -z "$1" ]; then 27 | disk_file="$1" 28 | fi 29 | 30 | sudo umount -f /mnt/nfs 31 | 32 | if [ "$native" = "false" ]; then 33 | sudo systemctl stop nfs-server.service 34 | sudo umount /srv/nfs/bench 35 | fi 36 | 37 | # do not attempt to remove block devices 38 | if [ -f "$disk_file" ]; then 39 | rm -f "$disk_file" 40 | fi 41 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | 7 | "github.com/mit-pdos/go-journal/util" 8 | ) 9 | 10 | type Cslot struct { 11 | Obj interface{} 12 | } 13 | 14 | type entry struct { 15 | slot Cslot 16 | lru *list.Element 17 | id uint64 18 | } 19 | 20 | type Cache struct { 21 | mu *sync.Mutex 22 | entries map[uint64]*entry 23 | lru *list.List 24 | sz uint64 25 | cnt uint64 26 | } 27 | 28 | func MkCache(sz uint64) *Cache { 29 | entries := make(map[uint64]*entry, sz) 30 | return &Cache{ 31 | mu: new(sync.Mutex), 32 | entries: entries, 33 | lru: list.New(), 34 | cnt: 0, 35 | sz: sz, 36 | } 37 | } 38 | 39 | func (c *Cache) PrintCache() { 40 | for k, v := range c.entries { 41 | util.DPrintf(0, "Entry %v %v\n", k, v) 42 | } 43 | } 44 | 45 | func (c *Cache) evict() { 46 | e := c.lru.Front() 47 | if e == nil { 48 | c.PrintCache() 49 | panic("evict") 50 | } 51 | entry := e.Value.(*entry) 52 | c.lru.Remove(e) 53 | util.DPrintf(5, "evict: %d\n", entry.id) 54 | delete(c.entries, entry.id) 55 | c.cnt = c.cnt - 1 56 | } 57 | 58 | func (c *Cache) LookupSlot(id uint64) *Cslot { 59 | c.mu.Lock() 60 | e := c.entries[id] 61 | if e != nil { 62 | if id != e.id { 63 | panic("LookupSlot") 64 | } 65 | if e.lru != nil { 66 | c.lru.Remove(e.lru) 67 | e.lru = c.lru.PushBack(e) 68 | } 69 | c.mu.Unlock() 70 | return &e.slot 71 | } 72 | if c.cnt >= c.sz { 73 | c.evict() 74 | } 75 | enew := &entry{ 76 | slot: Cslot{Obj: nil}, 77 | lru: nil, 78 | id: id, 79 | } 80 | c.entries[id] = enew 81 | enew.lru = c.lru.PushBack(enew) 82 | c.cnt = c.cnt + 1 83 | c.mu.Unlock() 84 | return &enew.slot 85 | } 86 | -------------------------------------------------------------------------------- /cmd/clnt-null/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "time" 9 | 10 | "github.com/zeldovich/go-rpcgen/rfc1057" 11 | "github.com/zeldovich/go-rpcgen/rfc1813" 12 | "github.com/zeldovich/go-rpcgen/xdr" 13 | ) 14 | 15 | var N time.Duration 16 | 17 | func pmap_client(host string, prog, vers uint32) *rfc1057.Client { 18 | var cred rfc1057.Opaque_auth 19 | cred.Flavor = rfc1057.AUTH_NONE 20 | 21 | pmapc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, rfc1057.PMAP_PORT)) 22 | if err != nil { 23 | panic(err) 24 | } 25 | defer pmapc.Close() 26 | pmap := rfc1057.MakeClient(pmapc, rfc1057.PMAP_PROG, rfc1057.PMAP_VERS) 27 | 28 | arg := rfc1057.Mapping{ 29 | Prog: prog, 30 | Vers: vers, 31 | Prot: rfc1057.IPPROTO_TCP, 32 | } 33 | var res xdr.Uint32 34 | err = pmap.Call(rfc1057.PMAPPROC_GETPORT, cred, cred, &arg, &res) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | svcc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, res)) 40 | if err != nil { 41 | panic(err) 42 | } 43 | return rfc1057.MakeClient(svcc, prog, vers) 44 | } 45 | 46 | type nfsclnt struct { 47 | clnt *rfc1057.Client 48 | cred rfc1057.Opaque_auth 49 | verf rfc1057.Opaque_auth 50 | } 51 | 52 | func (c *nfsclnt) null() { 53 | var arg xdr.Void 54 | var res xdr.Void 55 | 56 | err := c.clnt.Call(rfc1813.NFSPROC3_NULL, c.cred, c.verf, &arg, &res) 57 | if err != nil { 58 | panic(err) 59 | } 60 | } 61 | 62 | func client(cred_unix rfc1057.Opaque_auth) (n int, elapsed time.Duration) { 63 | var cred_none rfc1057.Opaque_auth 64 | cred_none.Flavor = rfc1057.AUTH_NONE 65 | nfs := pmap_client("localhost", rfc1813.NFS_PROGRAM, rfc1813.NFS_V3) 66 | clnt := &nfsclnt{clnt: nfs, cred: cred_unix, verf: cred_none} 67 | start := time.Now() 68 | for { 69 | clnt.null() 70 | n++ 71 | elapsed = time.Now().Sub(start) 72 | if elapsed >= N { 73 | return 74 | } 75 | } 76 | } 77 | 78 | func main() { 79 | flag.DurationVar(&N, "benchtime", 10*time.Second, "time to run each iteration for") 80 | flag.Parse() 81 | 82 | var err error 83 | 84 | var unix rfc1057.Auth_unix 85 | var cred_unix rfc1057.Opaque_auth 86 | cred_unix.Flavor = rfc1057.AUTH_UNIX 87 | cred_unix.Body, err = xdr.EncodeBuf(&unix) 88 | if err != nil { 89 | panic(err) 90 | } 91 | 92 | rand.Seed(time.Now().UnixNano()) 93 | 94 | n, elapsed := client(cred_unix) 95 | fmt.Printf("null-bench: NULL takes %.1f us\n", float64(elapsed.Microseconds())/float64(n)) 96 | } 97 | -------------------------------------------------------------------------------- /cmd/clnt-smallfile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/zeldovich/go-rpcgen/rfc1057" 12 | "github.com/zeldovich/go-rpcgen/rfc1813" 13 | "github.com/zeldovich/go-rpcgen/xdr" 14 | ) 15 | 16 | var N time.Duration 17 | var STARTTHREAD int 18 | var NTHREAD int 19 | 20 | func pmap_client(host string, prog, vers uint32) *rfc1057.Client { 21 | var cred rfc1057.Opaque_auth 22 | cred.Flavor = rfc1057.AUTH_NONE 23 | 24 | pmapc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, rfc1057.PMAP_PORT)) 25 | if err != nil { 26 | panic(err) 27 | } 28 | defer pmapc.Close() 29 | pmap := rfc1057.MakeClient(pmapc, rfc1057.PMAP_PROG, rfc1057.PMAP_VERS) 30 | 31 | arg := rfc1057.Mapping{ 32 | Prog: prog, 33 | Vers: vers, 34 | Prot: rfc1057.IPPROTO_TCP, 35 | } 36 | var res xdr.Uint32 37 | err = pmap.Call(rfc1057.PMAPPROC_GETPORT, cred, cred, &arg, &res) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | svcc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, res)) 43 | if err != nil { 44 | panic(err) 45 | } 46 | return rfc1057.MakeClient(svcc, prog, vers) 47 | } 48 | 49 | type nfsclnt struct { 50 | clnt *rfc1057.Client 51 | cred rfc1057.Opaque_auth 52 | verf rfc1057.Opaque_auth 53 | } 54 | 55 | func (c *nfsclnt) getattr(fh rfc1813.Nfs_fh3) *rfc1813.GETATTR3res { 56 | var arg rfc1813.GETATTR3args 57 | var res rfc1813.GETATTR3res 58 | 59 | arg.Object = fh 60 | err := c.clnt.Call(rfc1813.NFSPROC3_GETATTR, c.cred, c.verf, &arg, &res) 61 | if err != nil { 62 | panic(err) 63 | } 64 | return &res 65 | } 66 | 67 | func (c *nfsclnt) lookup(fh rfc1813.Nfs_fh3, name string) *rfc1813.LOOKUP3res { 68 | var res rfc1813.LOOKUP3res 69 | 70 | what := rfc1813.Diropargs3{Dir: fh, Name: rfc1813.Filename3(name)} 71 | arg := rfc1813.LOOKUP3args{What: what} 72 | err := c.clnt.Call(rfc1813.NFSPROC3_LOOKUP, c.cred, c.verf, &arg, &res) 73 | if err != nil { 74 | panic(err) 75 | } 76 | return &res 77 | } 78 | 79 | func (c *nfsclnt) create(fh rfc1813.Nfs_fh3, name string) *rfc1813.CREATE3res { 80 | var res rfc1813.CREATE3res 81 | 82 | where := rfc1813.Diropargs3{Dir: fh, Name: rfc1813.Filename3(name)} 83 | how := rfc1813.Createhow3{} 84 | arg := rfc1813.CREATE3args{Where: where, How: how} 85 | err := c.clnt.Call(rfc1813.NFSPROC3_CREATE, c.cred, c.verf, &arg, &res) 86 | if err != nil { 87 | panic(err) 88 | } 89 | return &res 90 | } 91 | 92 | func (c *nfsclnt) remove(fh rfc1813.Nfs_fh3, name string) *rfc1813.REMOVE3res { 93 | var res rfc1813.REMOVE3res 94 | what := rfc1813.Diropargs3{Dir: fh, Name: rfc1813.Filename3(name)} 95 | arg := rfc1813.REMOVE3args{ 96 | Object: what, 97 | } 98 | 99 | err := c.clnt.Call(rfc1813.NFSPROC3_REMOVE, c.cred, c.verf, &arg, &res) 100 | if err != nil { 101 | panic(err) 102 | } 103 | return &res 104 | } 105 | 106 | func (c *nfsclnt) write(fh rfc1813.Nfs_fh3, off uint64, data []byte, how rfc1813.Stable_how) *rfc1813.WRITE3res { 107 | var res rfc1813.WRITE3res 108 | 109 | arg := rfc1813.WRITE3args{ 110 | File: fh, 111 | Offset: rfc1813.Offset3(off), 112 | Count: rfc1813.Count3(len(data)), 113 | Stable: how, 114 | Data: data} 115 | err := c.clnt.Call(rfc1813.NFSPROC3_WRITE, c.cred, c.verf, &arg, &res) 116 | if err != nil { 117 | panic(err) 118 | } 119 | return &res 120 | } 121 | 122 | func (c *nfsclnt) mkdir(fh rfc1813.Nfs_fh3, name string) *rfc1813.MKDIR3res { 123 | var res rfc1813.MKDIR3res 124 | 125 | where := rfc1813.Diropargs3{Dir: fh, Name: rfc1813.Filename3(name)} 126 | sattr := rfc1813.Sattr3{} 127 | args := rfc1813.MKDIR3args{Where: where, Attributes: sattr} 128 | err := c.clnt.Call(rfc1813.NFSPROC3_MKDIR, c.cred, c.verf, &args, &res) 129 | if err != nil { 130 | panic(err) 131 | } 132 | return &res 133 | } 134 | 135 | func smallfile(clnt *nfsclnt, dirfh rfc1813.Nfs_fh3, name string, data []byte) { 136 | reply := clnt.lookup(dirfh, name) 137 | if reply.Status == rfc1813.NFS3_OK { 138 | panic("smallfile") 139 | } 140 | clnt.create(dirfh, name) 141 | reply = clnt.lookup(dirfh, name) 142 | if reply.Status != rfc1813.NFS3_OK { 143 | panic("smallfile") 144 | } 145 | attr := clnt.getattr(reply.Resok.Object) 146 | if attr.Status != rfc1813.NFS3_OK { 147 | panic("SmallFile") 148 | } 149 | clnt.write(reply.Resok.Object, 0, data, rfc1813.FILE_SYNC) 150 | attr = clnt.getattr(reply.Resok.Object) 151 | if attr.Status != rfc1813.NFS3_OK { 152 | panic("smallfile") 153 | } 154 | res := clnt.remove(dirfh, name) 155 | if res.Status != rfc1813.NFS3_OK { 156 | panic("smallfile") 157 | } 158 | } 159 | 160 | func mkdata(sz uint64) []byte { 161 | data := make([]byte, sz) 162 | for i := range data { 163 | data[i] = byte(i % 128) 164 | } 165 | return data 166 | } 167 | 168 | func client(i int, root_fh rfc1813.Nfs_fh3, cred_unix rfc1057.Opaque_auth, cred_none rfc1057.Opaque_auth, count chan int) { 169 | nfs := pmap_client("localhost", rfc1813.NFS_PROGRAM, rfc1813.NFS_V3) 170 | clnt := &nfsclnt{clnt: nfs, cred: cred_unix, verf: cred_none} 171 | 172 | data := mkdata(uint64(100)) 173 | name := "d" + strconv.Itoa(int(rand.Int31())) 174 | r := clnt.mkdir(root_fh, name) 175 | if r.Status != rfc1813.NFS3_OK { 176 | fmt.Printf("r %v\n", r.Status) 177 | panic("client: mkdir") 178 | } 179 | reply := clnt.lookup(root_fh, name) 180 | if reply.Status != rfc1813.NFS3_OK { 181 | panic("client: lookup") 182 | } 183 | dirfh := reply.Resok.Object 184 | start := time.Now() 185 | n := 0 186 | s := strconv.Itoa(int(i)) 187 | for true { 188 | smallfile(clnt, dirfh, "x"+s, data) 189 | n++ 190 | t := time.Now() 191 | elapsed := t.Sub(start) 192 | if elapsed >= N { 193 | count <- n 194 | break 195 | } 196 | } 197 | } 198 | 199 | func pclient(root_fh rfc1813.Nfs_fh3, cred_unix rfc1057.Opaque_auth, cred_none rfc1057.Opaque_auth) { 200 | for t := STARTTHREAD; t <= NTHREAD; t++ { 201 | count := make(chan int) 202 | for i := 1; i <= t; i++ { 203 | go client(i, root_fh, cred_unix, cred_none, count) 204 | } 205 | n := 0 206 | for i := 0; i < t; i++ { 207 | c := <-count 208 | n += c 209 | } 210 | fmt.Printf("clnt-smallfile: %v %v file/s\n", t, float64(n)/N.Seconds()) 211 | } 212 | } 213 | 214 | func main() { 215 | flag.DurationVar(&N, "benchtime", 10*time.Second, "time to run each iteration for") 216 | flag.IntVar(&STARTTHREAD, "start", 1, "number of threads to start at") 217 | flag.IntVar(&NTHREAD, "threads", 20, "number of threads to run till") 218 | flag.Parse() 219 | 220 | if STARTTHREAD < 1 { 221 | panic("invalid start") 222 | } 223 | 224 | var err error 225 | 226 | var unix rfc1057.Auth_unix 227 | var cred_unix rfc1057.Opaque_auth 228 | cred_unix.Flavor = rfc1057.AUTH_UNIX 229 | cred_unix.Body, err = xdr.EncodeBuf(&unix) 230 | if err != nil { 231 | panic(err) 232 | } 233 | 234 | rand.Seed(time.Now().UnixNano()) 235 | 236 | var cred_none rfc1057.Opaque_auth 237 | cred_none.Flavor = rfc1057.AUTH_NONE 238 | 239 | mnt := pmap_client("localhost", rfc1813.MOUNT_PROGRAM, rfc1813.MOUNT_V3) 240 | 241 | // for Linux: arg := rfc1813.Dirpath3("/srv/nfs/") 242 | arg := rfc1813.Dirpath3("/") 243 | var res rfc1813.Mountres3 244 | err = mnt.Call(rfc1813.MOUNTPROC3_MNT, cred_none, cred_none, &arg, &res) 245 | if err != nil { 246 | panic(err) 247 | } 248 | 249 | if res.Fhs_status != rfc1813.MNT3_OK { 250 | panic(fmt.Sprintf("mount status %d", res.Fhs_status)) 251 | } 252 | 253 | var root_fh rfc1813.Nfs_fh3 254 | root_fh.Data = res.Mountinfo.Fhandle 255 | 256 | for _, flavor := range res.Mountinfo.Auth_flavors { 257 | fmt.Printf("flavor %d\n", flavor) 258 | } 259 | 260 | pclient(root_fh, cred_unix, cred_none) 261 | } 262 | -------------------------------------------------------------------------------- /cmd/fs-largefile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path" 8 | "time" 9 | ) 10 | 11 | const ( 12 | MB uint64 = 1024 * 1024 13 | WSIZE = 16 * 4096 14 | ) 15 | 16 | // makefile directly reports throughput in MB/s 17 | func makefile(name string, data []byte, size uint64) float64 { 18 | start := time.Now() 19 | f, err := os.Create(name) 20 | if err != nil { 21 | panic(err) 22 | } 23 | for i := uint64(0); i < size/WSIZE; i++ { 24 | _, err = f.Write(data) 25 | if err != nil { 26 | panic(err) 27 | } 28 | } 29 | err = f.Sync() 30 | if err != nil { 31 | panic(err) 32 | } 33 | err = f.Close() 34 | if err != nil { 35 | panic(err) 36 | } 37 | elapsed := time.Now().Sub(start) 38 | return float64(size) / float64(MB) / elapsed.Seconds() 39 | } 40 | 41 | func mkdata(sz uint64) []byte { 42 | data := make([]byte, sz) 43 | for i := range data { 44 | data[i] = byte(i % 128) 45 | } 46 | return data 47 | } 48 | 49 | func main() { 50 | mnt := flag.String("mnt", "/mnt/nfs", "directory to write files to") 51 | sizeMB := flag.Uint64("file-size", 100, "file size (in MB)") 52 | deleteAfter := flag.Bool("delete", false, "delete files after running benchmark") 53 | warmup := flag.Bool("warmup", true, "run warmup first") 54 | flag.Parse() 55 | 56 | warmupFile := path.Join(*mnt, "large.warmup") 57 | file := path.Join(*mnt, "large") 58 | filesize := *sizeMB * MB 59 | warmupsize := 100 * MB 60 | if filesize < warmupsize { 61 | warmupsize = filesize 62 | } 63 | 64 | data := mkdata(WSIZE) 65 | 66 | if *warmup { 67 | tput := makefile(warmupFile, data, warmupsize) 68 | fmt.Printf("# warmup %d MB throughput %.2f MB/s\n", warmupsize/MB, tput) 69 | } 70 | 71 | tput := makefile(file, data, filesize) 72 | fmt.Printf("fs-largefile: %v MB throughput %.2f MB/s\n", filesize/MB, tput) 73 | 74 | if *deleteAfter { 75 | os.Remove(warmupFile) 76 | os.Remove(file) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cmd/fs-smallfile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path" 8 | "runtime/pprof" 9 | "strconv" 10 | "time" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | // smallfile represents one iteration of this benchmark: it creates a file, 16 | // write data to it, and deletes it. 17 | func smallfile(dirFd int, name string, data []byte) { 18 | f, err := unix.Openat(dirFd, name, unix.O_CREAT|unix.O_RDWR, 0777) 19 | if err != nil { 20 | panic(err) 21 | } 22 | _, err = unix.Write(f, data) 23 | if err != nil { 24 | panic(err) 25 | } 26 | unix.Fsync(f) 27 | unix.Close(f) 28 | err = unix.Unlinkat(dirFd, name, 0) 29 | if err != nil { 30 | panic(err) 31 | } 32 | } 33 | 34 | func mkdata(sz uint64) []byte { 35 | data := make([]byte, sz) 36 | for i := range data { 37 | data[i] = byte(i % 128) 38 | } 39 | return data 40 | } 41 | 42 | type result struct { 43 | iters int 44 | times []time.Duration 45 | } 46 | 47 | func client(duration time.Duration, iters int, allTimes bool, rootDirFd int, path string) result { 48 | data := mkdata(uint64(100)) 49 | var times []time.Duration 50 | if allTimes { 51 | times = make([]time.Duration, 0, int(duration.Seconds()*1000)) 52 | } 53 | start := time.Now() 54 | i := 0 55 | var elapsed time.Duration 56 | for { 57 | s := strconv.Itoa(i) 58 | before := elapsed 59 | smallfile(rootDirFd, path+"/x"+s, data) 60 | i++ 61 | elapsed = time.Since(start) 62 | if allTimes { 63 | times = append(times, (elapsed - before)) 64 | } 65 | // stopping condition depends on if iters > 0 (which overrides duration) 66 | if (iters > 0 && i >= iters) || (iters <= 0 && elapsed >= duration) { 67 | return result{iters: i, times: times} 68 | } 69 | } 70 | } 71 | 72 | type config struct { 73 | dir string // root for all clients 74 | duration time.Duration 75 | iters int // if > 0, overrides duration 76 | allTimes bool // whether to record individual iteration timings 77 | } 78 | 79 | func run(c config, nt int) (elapsed time.Duration, iters int, times []time.Duration) { 80 | start := time.Now() 81 | count := make(chan result) 82 | rootDirFd, err := unix.Open(c.dir, unix.O_DIRECTORY, 0) 83 | if err != nil { 84 | panic(fmt.Errorf("could not open root directory fd: %v", err)) 85 | } 86 | for i := 0; i < nt; i++ { 87 | i := i 88 | subdir := "d" + strconv.Itoa(i) 89 | p := path.Join(c.dir, subdir) 90 | go func() { 91 | err := os.MkdirAll(p, 0700) 92 | if err != nil { 93 | panic(err) 94 | } 95 | if err != nil { 96 | panic(err) 97 | } 98 | allTimes := c.allTimes && i == 0 99 | count <- client(c.duration, c.iters, allTimes, rootDirFd, subdir) 100 | }() 101 | } 102 | for i := 0; i < nt; i++ { 103 | r := <-count 104 | iters += r.iters 105 | if r.times != nil { 106 | times = r.times 107 | } 108 | } 109 | elapsed = time.Since(start) 110 | return 111 | } 112 | 113 | func cleanup(c config, nt int) { 114 | for i := 0; i < nt; i++ { 115 | p := path.Join(c.dir, "d"+strconv.Itoa(i)) 116 | os.Remove(p) 117 | } 118 | } 119 | 120 | func main() { 121 | var c config 122 | var start int 123 | var nthread int 124 | var timingFile string 125 | flag.StringVar(&c.dir, "dir", "/mnt/nfs", "root directory to run in") 126 | flag.DurationVar(&c.duration, "benchtime", 10*time.Second, "time to run each iteration for") 127 | flag.IntVar(&c.iters, "iters", 0, "exact iterations to run (overrides benchtime)") 128 | flag.StringVar(&timingFile, "time-iters", "", "prefix for individual timing files") 129 | flag.IntVar(&start, "start", 1, "number of threads to start at") 130 | flag.IntVar(&nthread, "threads", 1, "number of threads to run till") 131 | 132 | cpuprofile := flag.String("cpuprofile", "", "write cpu profile to file") 133 | 134 | flag.Parse() 135 | if start < 1 { 136 | panic("invalid start") 137 | } 138 | 139 | // warmup (skip if running for very little time, for example when using a 140 | // duration of 0s to run just one iteration) 141 | if c.duration > 500*time.Millisecond { 142 | run(config{ 143 | duration: 500 * time.Millisecond, 144 | dir: c.dir, 145 | allTimes: false}, 146 | nthread) 147 | } 148 | 149 | if *cpuprofile != "" { 150 | f, err := os.Create(*cpuprofile) 151 | if err != nil { 152 | panic(err) 153 | } 154 | pprof.StartCPUProfile(f) 155 | defer pprof.StopCPUProfile() 156 | } 157 | 158 | for nt := start; nt <= nthread; nt++ { 159 | if timingFile != "" { 160 | c.allTimes = true 161 | } 162 | 163 | elapsed, count, times := run(c, nt) 164 | fmt.Printf("fs-smallfile: %v %0.4f file/sec\n", nt, 165 | float64(count)/elapsed.Seconds()) 166 | if len(times) > 0 { 167 | f, err := os.Create(fmt.Sprintf("%s-%d.txt", timingFile, nt)) 168 | if err != nil { 169 | panic(fmt.Errorf("could not create timing file: %v", err)) 170 | } 171 | for _, t := range times { 172 | fmt.Fprintf(f, "%f\n", t.Seconds()) 173 | } 174 | f.Close() 175 | } 176 | } 177 | 178 | cleanup(c, nthread) 179 | } 180 | -------------------------------------------------------------------------------- /cmd/go-nfsd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | "os/signal" 11 | "runtime/pprof" 12 | "syscall" 13 | 14 | "github.com/goose-lang/goose/machine/disk" 15 | "github.com/zeldovich/go-rpcgen/rfc1057" 16 | "github.com/zeldovich/go-rpcgen/xdr" 17 | 18 | "github.com/mit-pdos/go-journal/util" 19 | go_nfs "github.com/mit-pdos/go-nfsd/nfs" 20 | "github.com/mit-pdos/go-nfsd/nfstypes" 21 | "github.com/mit-pdos/go-nfsd/util/timed_disk" 22 | ) 23 | 24 | func pmap_set_unset(prog, vers, port uint32, setit bool) error { 25 | var cred rfc1057.Opaque_auth 26 | cred.Flavor = rfc1057.AUTH_NONE 27 | 28 | pmapc, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", rfc1057.PMAP_PORT)) 29 | if err != nil { 30 | return err 31 | } 32 | defer pmapc.Close() 33 | pmap := rfc1057.MakeClient(pmapc, rfc1057.PMAP_PROG, rfc1057.PMAP_VERS) 34 | 35 | arg := rfc1057.Mapping{ 36 | Prog: prog, 37 | Vers: vers, 38 | Prot: rfc1057.IPPROTO_TCP, 39 | Port: port, 40 | } 41 | 42 | var res xdr.Bool 43 | var proc uint32 44 | if setit { 45 | proc = rfc1057.PMAPPROC_SET 46 | } else { 47 | proc = rfc1057.PMAPPROC_UNSET 48 | } 49 | 50 | err = pmap.Call(proc, cred, cred, &arg, &res) 51 | if err != nil { 52 | return err 53 | } 54 | if bool(res) { 55 | return nil 56 | } 57 | if setit { 58 | return errors.New("failed to set; is program already registered?") 59 | } else { 60 | return errors.New("failed to unset") 61 | } 62 | } 63 | 64 | func main() { 65 | cpuprofile := flag.String("cpuprofile", "", "write cpu profile to file") 66 | 67 | var unstable bool 68 | flag.BoolVar(&unstable, "unstable", true, "use unstable writes if requested") 69 | 70 | var filesizeMegabytes uint64 71 | flag.Uint64Var(&filesizeMegabytes, "size", 400, "size of file system (in MB)") 72 | 73 | var diskfile string 74 | flag.StringVar(&diskfile, "disk", "", "disk image (empty for MemDisk)") 75 | 76 | var dumpStats bool 77 | flag.BoolVar(&dumpStats, "stats", false, "dump stats to stderr at end") 78 | 79 | flag.Uint64Var(&util.Debug, "debug", 0, "debug level (higher is more verbose)") 80 | flag.Parse() 81 | 82 | diskBlocks := 1500 + filesizeMegabytes*1024/4 83 | 84 | if *cpuprofile != "" { 85 | f, err := os.Create(*cpuprofile) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | pprof.StartCPUProfile(f) 90 | defer pprof.StopCPUProfile() 91 | } 92 | 93 | listener, err := net.Listen("tcp", ":0") 94 | if err != nil { 95 | panic(err) 96 | } 97 | port := uint32(listener.Addr().(*net.TCPAddr).Port) 98 | 99 | err = pmap_set_unset(nfstypes.MOUNT_PROGRAM, nfstypes.MOUNT_V3, 0, false) 100 | if err != nil { 101 | fmt.Fprintf(os.Stderr, "Could not unset mount - is rpcbind service running?\n") 102 | fmt.Fprintf(os.Stderr, "%v\n", err.Error()) 103 | os.Exit(1) 104 | } 105 | err = pmap_set_unset(nfstypes.MOUNT_PROGRAM, nfstypes.MOUNT_V3, port, true) 106 | if err != nil { 107 | panic(err) 108 | } 109 | defer pmap_set_unset(nfstypes.MOUNT_PROGRAM, nfstypes.MOUNT_V3, port, false) 110 | 111 | pmap_set_unset(nfstypes.NFS_PROGRAM, nfstypes.NFS_V3, 0, false) 112 | err = pmap_set_unset(nfstypes.NFS_PROGRAM, nfstypes.NFS_V3, port, true) 113 | if err != nil { 114 | panic(err) 115 | } 116 | defer pmap_set_unset(nfstypes.NFS_PROGRAM, nfstypes.NFS_V3, port, false) 117 | 118 | var d disk.Disk 119 | if diskfile == "" { 120 | d = disk.NewMemDisk(diskBlocks) 121 | } else { 122 | d, err = disk.NewFileDisk(diskfile, diskBlocks) 123 | if err != nil { 124 | panic(fmt.Errorf("could not create disk: %w", err)) 125 | } 126 | } 127 | if dumpStats { 128 | d = timed_disk.New(d) 129 | } 130 | server := go_nfs.MakeNfs(d) 131 | server.Unstable = unstable 132 | defer server.ShutdownNfs() 133 | 134 | srv := rfc1057.MakeServer() 135 | srv.RegisterMany(nfstypes.MOUNT_PROGRAM_MOUNT_V3_regs(server)) 136 | srv.RegisterMany(nfstypes.NFS_PROGRAM_NFS_V3_regs(server)) 137 | 138 | interruptSig := make(chan os.Signal, 1) 139 | shutdown := false 140 | signal.Notify(interruptSig, os.Interrupt) 141 | go func() { 142 | <-interruptSig 143 | shutdown = true 144 | listener.Close() 145 | if dumpStats { 146 | server.WriteOpStats(os.Stderr) 147 | d.(*timed_disk.Disk).WriteStats(os.Stderr) 148 | } 149 | }() 150 | 151 | if dumpStats { 152 | statSig := make(chan os.Signal, 1) 153 | signal.Notify(statSig, syscall.SIGUSR1) 154 | go func() { 155 | for { 156 | <-statSig 157 | server.WriteOpStats(os.Stderr) 158 | server.ResetOpStats() 159 | d := d.(*timed_disk.Disk) 160 | d.WriteStats(os.Stderr) 161 | d.ResetStats() 162 | } 163 | }() 164 | } 165 | 166 | for { 167 | conn, err := listener.Accept() 168 | if err != nil { 169 | if errors.Is(err, net.ErrClosed) && shutdown { 170 | util.DPrintf(1, "Shutting down server") 171 | break 172 | } 173 | fmt.Printf("accept: %v\n", err) 174 | break 175 | } 176 | 177 | go srv.Run(conn) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /cmd/largefile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "runtime/pprof" 9 | "time" 10 | 11 | "github.com/goose-lang/primitive/disk" 12 | 13 | "github.com/mit-pdos/go-nfsd/fh" 14 | go_nfs "github.com/mit-pdos/go-nfsd/nfs" 15 | "github.com/mit-pdos/go-nfsd/nfstypes" 16 | ) 17 | 18 | const ( 19 | FILESIZE uint64 = 50 * 1024 * 1024 20 | WSIZE uint64 = disk.BlockSize 21 | MB uint64 = 1024 * 1024 22 | BENCHDISKSZ uint64 = 100 * 1000 23 | ) 24 | 25 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 26 | 27 | func main() { 28 | flag.Parse() 29 | if *cpuprofile != "" { 30 | f, err := os.Create(*cpuprofile) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | pprof.StartCPUProfile(f) 35 | defer pprof.StopCPUProfile() 36 | } 37 | largeFile() 38 | } 39 | 40 | func mkdata(sz uint64) []byte { 41 | data := make([]byte, sz) 42 | for i := range data { 43 | data[i] = byte(i % 128) 44 | } 45 | return data 46 | } 47 | 48 | func largeFile() { 49 | data := mkdata(WSIZE) 50 | clnt := go_nfs.MkNfsClient(BENCHDISKSZ) 51 | defer clnt.Shutdown() 52 | dir := fh.MkRootFh3() 53 | 54 | start := time.Now() 55 | 56 | name := "largefile" 57 | clnt.CreateOp(dir, name) 58 | reply := clnt.LookupOp(dir, name) 59 | fh := reply.Resok.Object 60 | n := FILESIZE / WSIZE 61 | for j := uint64(0); j < n; j++ { 62 | clnt.WriteOp(fh, j*WSIZE, data, nfstypes.UNSTABLE) 63 | } 64 | clnt.CommitOp(fh, n*WSIZE) 65 | attr := clnt.GetattrOp(fh) 66 | if uint64(attr.Resok.Obj_attributes.Size) != FILESIZE { 67 | panic("large") 68 | } 69 | 70 | t := time.Now() 71 | elapsed := t.Sub(start) 72 | tput := float64(FILESIZE/MB) / elapsed.Seconds() 73 | fmt.Printf("largefile: %v MB throughput %.2f MB/s\n", FILESIZE/MB, tput) 74 | 75 | clnt.RemoveOp(dir, name) 76 | } 77 | -------------------------------------------------------------------------------- /cmd/lookup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "runtime/pprof" 9 | "strconv" 10 | "time" 11 | 12 | go_nfs "github.com/mit-pdos/go-nfsd/nfs" 13 | "github.com/mit-pdos/go-nfsd/nfstypes" 14 | ) 15 | 16 | const BENCHDISKSZ uint64 = 100 * 1000 17 | 18 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 19 | 20 | func main() { 21 | flag.Parse() 22 | if *cpuprofile != "" { 23 | f, err := os.Create(*cpuprofile) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | pprof.StartCPUProfile(f) 28 | defer pprof.StopCPUProfile() 29 | } 30 | PLookup() 31 | } 32 | 33 | func Lookup(clnt *go_nfs.NfsClient, dirfh nfstypes.Nfs_fh3, name string) { 34 | reply := clnt.LookupOp(dirfh, name) 35 | if reply.Status != nfstypes.NFS3_OK { 36 | panic("Lookup") 37 | } 38 | fh := reply.Resok.Object 39 | attr := clnt.GetattrOp(fh) 40 | if attr.Status != nfstypes.NFS3_OK { 41 | panic("Lookup") 42 | } 43 | } 44 | 45 | func PLookup() { 46 | const N = 1 * time.Second 47 | const NTHREAD = 4 48 | for i := 1; i <= NTHREAD; i++ { 49 | res := go_nfs.Parallel(i, BENCHDISKSZ, 50 | func(clnt *go_nfs.NfsClient, dirfh nfstypes.Nfs_fh3) int { 51 | s := strconv.Itoa(i) 52 | name := "x" + s 53 | clnt.CreateOp(dirfh, name) 54 | start := time.Now() 55 | i := 0 56 | for true { 57 | Lookup(clnt, dirfh, name) 58 | i++ 59 | t := time.Now() 60 | elapsed := t.Sub(start) 61 | if elapsed >= N { 62 | break 63 | } 64 | } 65 | return i 66 | }) 67 | fmt.Printf("Lookup: %d file in %d usec with %d threads\n", 68 | res, N.Nanoseconds()/1e3, i) 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cmd/simple-nfsd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "os" 9 | "os/signal" 10 | "runtime/pprof" 11 | 12 | "github.com/zeldovich/go-rpcgen/rfc1057" 13 | "github.com/zeldovich/go-rpcgen/xdr" 14 | 15 | "github.com/mit-pdos/go-nfsd/nfstypes" 16 | ) 17 | 18 | func pmap_set_unset(prog, vers, port uint32, setit bool) bool { 19 | var cred rfc1057.Opaque_auth 20 | cred.Flavor = rfc1057.AUTH_NONE 21 | 22 | pmapc, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", rfc1057.PMAP_PORT)) 23 | if err != nil { 24 | panic(err) 25 | } 26 | defer pmapc.Close() 27 | pmap := rfc1057.MakeClient(pmapc, rfc1057.PMAP_PROG, rfc1057.PMAP_VERS) 28 | 29 | arg := rfc1057.Mapping{ 30 | Prog: prog, 31 | Vers: vers, 32 | Prot: rfc1057.IPPROTO_TCP, 33 | Port: port, 34 | } 35 | 36 | var res xdr.Bool 37 | var proc uint32 38 | if setit { 39 | proc = rfc1057.PMAPPROC_SET 40 | } else { 41 | proc = rfc1057.PMAPPROC_UNSET 42 | } 43 | 44 | err = pmap.Call(proc, cred, cred, &arg, &res) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | return bool(res) 50 | } 51 | 52 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 53 | var diskfile = flag.String("disk", "", "disk image") 54 | 55 | func main() { 56 | var name string 57 | flag.Parse() 58 | if *diskfile != "" { 59 | name = *diskfile 60 | } else { 61 | fmt.Printf("Argument '-disk ' required\n") 62 | return 63 | } 64 | if *cpuprofile != "" { 65 | f, err := os.Create(*cpuprofile) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | pprof.StartCPUProfile(f) 70 | defer pprof.StopCPUProfile() 71 | } 72 | 73 | listener, err := net.Listen("tcp", ":0") 74 | if err != nil { 75 | panic(err) 76 | } 77 | port := uint32(listener.Addr().(*net.TCPAddr).Port) 78 | 79 | pmap_set_unset(nfstypes.MOUNT_PROGRAM, nfstypes.MOUNT_V3, 0, false) 80 | ok := pmap_set_unset(nfstypes.MOUNT_PROGRAM, nfstypes.MOUNT_V3, port, true) 81 | if !ok { 82 | panic("Could not set pmap mapping for mount") 83 | } 84 | defer pmap_set_unset(nfstypes.MOUNT_PROGRAM, nfstypes.MOUNT_V3, port, false) 85 | 86 | pmap_set_unset(nfstypes.NFS_PROGRAM, nfstypes.NFS_V3, 0, false) 87 | ok = pmap_set_unset(nfstypes.NFS_PROGRAM, nfstypes.NFS_V3, port, true) 88 | if !ok { 89 | panic("Could not set pmap mapping for NFS") 90 | } 91 | defer pmap_set_unset(nfstypes.NFS_PROGRAM, nfstypes.NFS_V3, port, false) 92 | 93 | nfs := MakeNfs(name) 94 | 95 | srv := rfc1057.MakeServer() 96 | srv.RegisterMany(nfstypes.MOUNT_PROGRAM_MOUNT_V3_regs(nfs)) 97 | srv.RegisterMany(nfstypes.NFS_PROGRAM_NFS_V3_regs(nfs)) 98 | 99 | sigs := make(chan os.Signal, 1) 100 | signal.Notify(sigs, os.Interrupt) 101 | go func() { 102 | <-sigs 103 | listener.Close() 104 | }() 105 | 106 | for { 107 | conn, err := listener.Accept() 108 | if err != nil { 109 | fmt.Printf("accept: %v\n", err) 110 | break 111 | } 112 | 113 | go srv.Run(conn) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /cmd/simple-nfsd/start.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/goose-lang/primitive/disk" 7 | 8 | "github.com/mit-pdos/go-journal/util" 9 | "github.com/mit-pdos/go-nfsd/simple" 10 | ) 11 | 12 | func MakeNfs(name string) *simple.Nfs { 13 | sz := uint64(100 * 1024) 14 | 15 | var d disk.Disk 16 | util.DPrintf(1, "MakeNfs: creating file disk at %s", name) 17 | var err error 18 | d, err = disk.NewFileDisk(name, sz) 19 | if err != nil { 20 | panic(fmt.Errorf("could not create file disk: %v", err)) 21 | } 22 | 23 | nfs := simple.MakeNfs(d) 24 | if nfs == nil { 25 | panic("could not initialize nfs") 26 | } 27 | 28 | return nfs 29 | } 30 | -------------------------------------------------------------------------------- /cmd/smallfile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "runtime/pprof" 9 | "strconv" 10 | "time" 11 | 12 | go_nfs "github.com/mit-pdos/go-nfsd/nfs" 13 | "github.com/mit-pdos/go-nfsd/nfstypes" 14 | ) 15 | 16 | const BENCHDISKSZ uint64 = 100 * 1000 17 | 18 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 19 | 20 | func main() { 21 | flag.Parse() 22 | if *cpuprofile != "" { 23 | f, err := os.Create(*cpuprofile) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | pprof.StartCPUProfile(f) 28 | defer pprof.StopCPUProfile() 29 | } 30 | PSmallFile() 31 | } 32 | 33 | func SmallFile(clnt *go_nfs.NfsClient, dirfh nfstypes.Nfs_fh3, name string, data []byte) { 34 | reply := clnt.LookupOp(dirfh, name) 35 | if reply.Status == nfstypes.NFS3_OK { 36 | panic("SmallFile") 37 | } 38 | clnt.CreateOp(dirfh, name) 39 | reply = clnt.LookupOp(dirfh, name) 40 | if reply.Status != nfstypes.NFS3_OK { 41 | panic("SmallFile") 42 | } 43 | attr := clnt.GetattrOp(reply.Resok.Object) 44 | if attr.Status != nfstypes.NFS3_OK { 45 | panic("SmallFile") 46 | } 47 | clnt.WriteOp(reply.Resok.Object, 0, data, nfstypes.FILE_SYNC) 48 | attr = clnt.GetattrOp(reply.Resok.Object) 49 | if attr.Status != nfstypes.NFS3_OK { 50 | panic("SmallFile") 51 | } 52 | res := clnt.RemoveOp(dirfh, name) 53 | if res.Status != nfstypes.NFS3_OK { 54 | panic("SmallFile") 55 | } 56 | } 57 | 58 | func mkdata(sz uint64) []byte { 59 | data := make([]byte, sz) 60 | for i := range data { 61 | data[i] = byte(i % 128) 62 | } 63 | return data 64 | } 65 | 66 | func PSmallFile() { 67 | const N = 10 * time.Second 68 | const NTHREAD = 20 69 | for i := 1; i <= NTHREAD; i++ { 70 | res := go_nfs.Parallel(i, BENCHDISKSZ, 71 | func(clnt *go_nfs.NfsClient, dirfh nfstypes.Nfs_fh3) int { 72 | data := mkdata(uint64(100)) 73 | start := time.Now() 74 | i := 0 75 | for true { 76 | s := strconv.Itoa(i) 77 | SmallFile(clnt, dirfh, "x"+s, data) 78 | i++ 79 | t := time.Now() 80 | elapsed := t.Sub(start) 81 | if elapsed >= N { 82 | break 83 | } 84 | } 85 | return i 86 | }) 87 | fmt.Printf("smallfile: %v file/s with %d threads\n", 88 | float64(res)/N.Seconds(), i) 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /cmd/txn-bench/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/goose-lang/goose/machine/disk" 9 | 10 | "github.com/mit-pdos/go-journal/addr" 11 | "github.com/mit-pdos/go-journal/txn" 12 | ) 13 | 14 | func testSequence(tsys *txn.Log, data []byte, tid uint64) { 15 | txnbuf := txn.Begin(tsys) 16 | for i := uint64(0); i < 16; i++ { 17 | txnbuf.OverWrite(addr.MkAddr(i+513, 8*tid), 8, data) 18 | } 19 | txnbuf.Commit(true) 20 | } 21 | 22 | func mkdata(sz uint64) []byte { 23 | data := make([]byte, sz) 24 | for i := range data { 25 | data[i] = byte(i % 128) 26 | } 27 | return data 28 | } 29 | 30 | func client(tsys *txn.Log, duration time.Duration, tid uint64) int { 31 | data := mkdata(1) 32 | start := time.Now() 33 | i := 0 34 | for { 35 | testSequence(tsys, data, tid) 36 | i++ 37 | t := time.Now() 38 | elapsed := t.Sub(start) 39 | if elapsed >= duration { 40 | break 41 | } 42 | } 43 | return i 44 | } 45 | 46 | func run(tsys *txn.Log, duration time.Duration, nt int) int { 47 | count := make(chan int) 48 | for i := 0; i < nt; i++ { 49 | go func(tid int) { 50 | count <- client(tsys, duration, uint64(tid)) 51 | }(i) 52 | } 53 | n := 0 54 | for i := 0; i < nt; i++ { 55 | n += <-count 56 | } 57 | return n 58 | } 59 | 60 | func zeroDisk(d disk.Disk) { 61 | zeroblock := make([]byte, 4096) 62 | sz := d.Size() 63 | for i := uint64(0); i < sz; i++ { 64 | d.Write(i, zeroblock) 65 | } 66 | d.Barrier() 67 | } 68 | 69 | func main() { 70 | var err error 71 | var duration time.Duration 72 | var nthread int 73 | var diskfile string 74 | var filesizeMegabytes uint64 75 | flag.DurationVar(&duration, "benchtime", 10*time.Second, "time to run each iteration for") 76 | flag.IntVar(&nthread, "threads", 1, "number of threads to run till") 77 | flag.StringVar(&diskfile, "disk", "", "disk image (empty for MemDisk)") 78 | flag.Uint64Var(&filesizeMegabytes, "size", 400, "size of file system (in MB)") 79 | flag.Parse() 80 | if nthread < 1 { 81 | panic("invalid start") 82 | } 83 | 84 | diskBlocks := 1500 + filesizeMegabytes*1024/4 85 | var d disk.Disk 86 | if diskfile == "" { 87 | d = disk.NewMemDisk(diskBlocks) 88 | } else { 89 | d, err = disk.NewFileDisk(diskfile, diskBlocks) 90 | if err != nil { 91 | panic(fmt.Errorf("could not create disk: %w", err)) 92 | } 93 | } 94 | zeroDisk(d) 95 | 96 | tsys := txn.Init(d) 97 | 98 | // warmup (skip if running for very little time, for example when using a 99 | // duration of 0s to run just one iteration) 100 | if duration > 500*time.Millisecond { 101 | run(tsys, 500*time.Millisecond, nthread) 102 | } 103 | 104 | count := run(tsys, duration, nthread) 105 | fmt.Printf("txn-bench: %v %v txn/sec\n", nthread, float64(count)/duration.Seconds()) 106 | } 107 | -------------------------------------------------------------------------------- /dcache/dcache.go: -------------------------------------------------------------------------------- 1 | package dcache 2 | 3 | import ( 4 | "github.com/mit-pdos/go-journal/common" 5 | ) 6 | 7 | type Dentry struct { 8 | Inum common.Inum 9 | Off uint64 10 | } 11 | 12 | type Dcache struct { 13 | cache map[string]Dentry 14 | Lastoff uint64 15 | } 16 | 17 | func MkDcache() *Dcache { 18 | return &Dcache{ 19 | cache: make(map[string]Dentry), 20 | Lastoff: uint64(0), 21 | } 22 | } 23 | 24 | func (dc *Dcache) Add(name string, inum common.Inum, off uint64) { 25 | dc.cache[name] = Dentry{Inum: inum, Off: off} 26 | } 27 | 28 | func (dc *Dcache) Lookup(name string) (Dentry, bool) { 29 | d, ok := dc.cache[name] 30 | return d, ok 31 | } 32 | 33 | func (dc *Dcache) Del(name string) bool { 34 | _, ok := dc.cache[name] 35 | if ok { 36 | delete(dc.cache, name) 37 | } 38 | return ok 39 | } 40 | -------------------------------------------------------------------------------- /dir/dcache.go: -------------------------------------------------------------------------------- 1 | package dir 2 | 3 | import ( 4 | "github.com/mit-pdos/go-journal/common" 5 | "github.com/mit-pdos/go-nfsd/dcache" 6 | "github.com/mit-pdos/go-nfsd/fstxn" 7 | "github.com/mit-pdos/go-nfsd/inode" 8 | "github.com/mit-pdos/go-nfsd/nfstypes" 9 | ) 10 | 11 | func mkDcache(dip *inode.Inode, op *fstxn.FsTxn) { 12 | dip.Dcache = dcache.MkDcache() 13 | Apply(dip, op, 0, dip.Size, 100000000, 14 | func(ip *inode.Inode, name string, inum common.Inum, off uint64) { 15 | dip.Dcache.Add(name, inum, off) 16 | }) 17 | } 18 | 19 | func LookupName(dip *inode.Inode, op *fstxn.FsTxn, name nfstypes.Filename3) (common.Inum, uint64) { 20 | if dip.Kind != nfstypes.NF3DIR { 21 | return common.NULLINUM, 0 22 | } 23 | var inum = common.NULLINUM 24 | var finalOffset uint64 = 0 25 | if dip.Dcache == nil { 26 | mkDcache(dip, op) 27 | } 28 | dentry, ok := dip.Dcache.Lookup(string(name)) 29 | if ok { 30 | inum = dentry.Inum 31 | finalOffset = dentry.Off 32 | } 33 | return inum, finalOffset 34 | } 35 | 36 | func AddName(dip *inode.Inode, op *fstxn.FsTxn, inum common.Inum, name nfstypes.Filename3) bool { 37 | if dip.Kind != nfstypes.NF3DIR || uint64(len(name)) >= MAXNAMELEN { 38 | return false 39 | } 40 | if dip.Dcache == nil { 41 | mkDcache(dip, op) 42 | } 43 | off, ok := AddNameDir(dip, op, inum, name, dip.Dcache.Lastoff) 44 | if ok { 45 | dip.Dcache.Lastoff = off 46 | dip.Dcache.Add(string(name), inum, off) 47 | } 48 | return ok 49 | } 50 | 51 | func RemName(dip *inode.Inode, op *fstxn.FsTxn, name nfstypes.Filename3) bool { 52 | if dip.Kind != nfstypes.NF3DIR || uint64(len(name)) >= MAXNAMELEN { 53 | return false 54 | } 55 | if dip.Dcache == nil { 56 | mkDcache(dip, op) 57 | } 58 | off, ok := RemNameDir(dip, op, name) 59 | if ok { 60 | dip.Dcache.Lastoff = off 61 | ok := dip.Dcache.Del(string(name)) 62 | if !ok { 63 | panic("RemName") 64 | } 65 | return true 66 | } 67 | return false 68 | } 69 | -------------------------------------------------------------------------------- /dir/dir.go: -------------------------------------------------------------------------------- 1 | package dir 2 | 3 | import ( 4 | "github.com/tchajed/marshal" 5 | 6 | "github.com/mit-pdos/go-journal/common" 7 | "github.com/mit-pdos/go-journal/util" 8 | "github.com/mit-pdos/go-nfsd/fstxn" 9 | "github.com/mit-pdos/go-nfsd/inode" 10 | "github.com/mit-pdos/go-nfsd/nfstypes" 11 | ) 12 | 13 | const DIRENTSZ uint64 = 128 14 | const MAXNAMELEN = DIRENTSZ - 16 // uint64 for inum + uint64 for len(name) 15 | 16 | type dirEnt struct { 17 | inum common.Inum 18 | name string // <= MAXNAMELEN 19 | } 20 | 21 | func IllegalName(name nfstypes.Filename3) bool { 22 | n := name 23 | return n == "." || n == ".." 24 | } 25 | 26 | func ScanName(dip *inode.Inode, op *fstxn.FsTxn, name nfstypes.Filename3) (common.Inum, uint64) { 27 | if dip.Kind != nfstypes.NF3DIR { 28 | return common.NULLINUM, 0 29 | } 30 | var inum = common.NULLINUM 31 | var finalOffset uint64 = 0 32 | for off := uint64(0); off < dip.Size; off += DIRENTSZ { 33 | data, _ := dip.Read(op.Atxn, off, DIRENTSZ) 34 | if uint64(len(data)) != DIRENTSZ { 35 | break 36 | } 37 | de := decodeDirEnt(data) 38 | if de.inum == common.NULLINUM { 39 | continue 40 | } 41 | if de.name == string(name) { 42 | inum = de.inum 43 | finalOffset = off 44 | break 45 | } 46 | } 47 | return inum, finalOffset 48 | } 49 | 50 | func AddNameDir(dip *inode.Inode, op *fstxn.FsTxn, inum common.Inum, 51 | name nfstypes.Filename3, lastoff uint64) (uint64, bool) { 52 | var finalOff uint64 53 | 54 | for off := uint64(lastoff); off < dip.Size; off += DIRENTSZ { 55 | data, _ := dip.Read(op.Atxn, off, DIRENTSZ) 56 | de := decodeDirEnt(data) 57 | if de.inum == common.NULLINUM { 58 | finalOff = off 59 | break 60 | } 61 | } 62 | if finalOff == 0 { 63 | finalOff = dip.Size 64 | } 65 | de := &dirEnt{inum: inum, name: string(name)} 66 | ent := encodeDirEnt(de) 67 | util.DPrintf(5, "AddNameDir # %v: %v %v %v off %d\n", dip.Inum, name, de, ent, finalOff) 68 | n, _ := dip.Write(op.Atxn, finalOff, DIRENTSZ, ent) 69 | return finalOff, n == DIRENTSZ 70 | } 71 | 72 | func RemNameDir(dip *inode.Inode, op *fstxn.FsTxn, name nfstypes.Filename3) (uint64, bool) { 73 | inum, off := LookupName(dip, op, name) 74 | if inum == common.NULLINUM { 75 | return 0, false 76 | } 77 | util.DPrintf(5, "RemNameDir # %v: %v %v off %d\n", dip.Inum, name, inum, off) 78 | de := &dirEnt{inum: common.NULLINUM, name: ""} 79 | ent := encodeDirEnt(de) 80 | n, _ := dip.Write(op.Atxn, off, DIRENTSZ, ent) 81 | return off, n == DIRENTSZ 82 | } 83 | 84 | func IsDirEmpty(dip *inode.Inode, op *fstxn.FsTxn) bool { 85 | var empty bool = true 86 | 87 | // check all entries after . and .. 88 | for off := uint64(2 * DIRENTSZ); off < dip.Size; { 89 | data, _ := dip.Read(op.Atxn, off, DIRENTSZ) 90 | de := decodeDirEnt(data) 91 | if de.inum == common.NULLINUM { 92 | off = off + DIRENTSZ 93 | continue 94 | } 95 | empty = false 96 | break 97 | } 98 | util.DPrintf(10, "IsDirEmpty: %v -> %v\n", dip, empty) 99 | return empty 100 | } 101 | 102 | func InitDir(dip *inode.Inode, op *fstxn.FsTxn, parent common.Inum) bool { 103 | if !AddName(dip, op, dip.Inum, ".") { 104 | return false 105 | } 106 | return AddName(dip, op, parent, "..") 107 | } 108 | 109 | func MkRootDir(dip *inode.Inode, op *fstxn.FsTxn) bool { 110 | if !AddName(dip, op, dip.Inum, ".") { 111 | return false 112 | } 113 | return AddName(dip, op, dip.Inum, "..") 114 | } 115 | 116 | const fattr3XDRsize uint64 = 4 + 4 + 4 + // type, mode, nlink 117 | 4 + 4 + // uid, gid 118 | 8 + 8 + // size, used 119 | 8 + // rdev (specdata3) 120 | 8 + 8 + // fsid, fileid 121 | (3 * 8) // atime, mtime, ctime 122 | 123 | // best estimate of entryplus3 size, excluding name 124 | const entryplus3Baggage uint64 = 8 + // fileid 125 | 4 + // name length 126 | 8 + // cookie 127 | 4 + fattr3XDRsize + // post_op_attr header + fattr3 128 | 16 + // name_handle 129 | 8 // pointer 130 | 131 | // XXX inode locking order violated 132 | func Apply(dip *inode.Inode, op *fstxn.FsTxn, start uint64, 133 | dircount uint64, maxcount uint64, 134 | f func(*inode.Inode, string, common.Inum, uint64)) bool { 135 | var eof bool = true 136 | var ip *inode.Inode 137 | var begin = uint64(start) 138 | if begin != 0 { 139 | begin += DIRENTSZ 140 | } 141 | // TODO: arbitrary estimate of constant XDR overhead 142 | var n uint64 = uint64(64) 143 | var dirbytes uint64 = uint64(0) 144 | for off := begin; off < dip.Size; { 145 | data, _ := dip.Read(op.Atxn, off, DIRENTSZ) 146 | de := decodeDirEnt(data) 147 | util.DPrintf(5, "Apply: # %v %v off %d\n", dip.Inum, de, off) 148 | if de.inum == common.NULLINUM { 149 | off = off + DIRENTSZ 150 | continue 151 | } 152 | 153 | // Lock inode, if this transaction doesn't own it already 154 | var own bool = false 155 | if op.OwnInum(de.inum) { 156 | own = true 157 | ip = op.GetInodeUnlocked(de.inum) 158 | } else { 159 | ip = op.GetInodeInum(de.inum) 160 | 161 | } 162 | 163 | f(ip, de.name, de.inum, off) 164 | 165 | // Release inode early, if this trans didn't own it before. 166 | if !own { 167 | op.ReleaseInode(ip) 168 | } 169 | 170 | off = off + DIRENTSZ 171 | // TODO: unclear what dircount is supposed to included so we pad it with 172 | // 8 bytes per entry 173 | dirbytes += uint64(8 + len(de.name)) 174 | n += entryplus3Baggage + uint64(len(de.name)) 175 | if dirbytes >= dircount || n >= maxcount { 176 | eof = false 177 | break 178 | } 179 | } 180 | return eof 181 | } 182 | 183 | func ApplyEnts(dip *inode.Inode, op *fstxn.FsTxn, start uint64, count uint64, 184 | f func(string, common.Inum, uint64)) bool { 185 | var eof bool = true 186 | var begin = uint64(start) 187 | if begin != 0 { 188 | begin += DIRENTSZ 189 | } 190 | // TODO: this is supposed to track the size of the XDR-encoded reply in 191 | // bytes, and we somewhat arbitrarily use 64 as the constant overhead 192 | var n uint64 = uint64(64) 193 | for off := begin; off < dip.Size; { 194 | data, _ := dip.Read(op.Atxn, off, DIRENTSZ) 195 | de := decodeDirEnt(data) 196 | util.DPrintf(5, "Apply: # %v %v off %d\n", dip.Inum, de, off) 197 | if de.inum == common.NULLINUM { 198 | off = off + DIRENTSZ 199 | continue 200 | } 201 | f(de.name, de.inum, off) 202 | 203 | off = off + DIRENTSZ 204 | // TODO: estimate of XDR overhead, 16-byte file id, name, cookie, and 205 | // pointer for linked list 206 | n += uint64(16 + len(de.name) + 8 + 8) 207 | if n >= count { 208 | eof = false 209 | break 210 | } 211 | } 212 | return eof 213 | } 214 | 215 | // Caller must ensure de.Name fits 216 | func encodeDirEnt(de *dirEnt) []byte { 217 | enc := marshal.NewEnc(DIRENTSZ) 218 | enc.PutInt(uint64(de.inum)) 219 | enc.PutInt(uint64(len(de.name))) 220 | enc.PutBytes([]byte(de.name)) 221 | return enc.Finish() 222 | } 223 | 224 | func decodeDirEnt(d []byte) *dirEnt { 225 | dec := marshal.NewDec(d) 226 | inum := dec.GetInt() 227 | l := dec.GetInt() 228 | name := string(dec.GetBytes(l)) 229 | return &dirEnt{ 230 | inum: common.Inum(inum), 231 | name: name, 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /dir/dir_test.go: -------------------------------------------------------------------------------- 1 | package dir 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mit-pdos/go-nfsd/nfstypes" 7 | "github.com/zeldovich/go-rpcgen/xdr" 8 | ) 9 | 10 | func TestFattr3Size(t *testing.T) { 11 | var e nfstypes.Fattr3 12 | bs, err := xdr.EncodeBuf(&e) 13 | if err != nil { 14 | panic(err) 15 | } 16 | if len(bs) != int(fattr3XDRsize) { 17 | t.Fatalf("size of fattr3 is %d != %d", len(bs), fattr3XDRsize) 18 | } 19 | } 20 | 21 | func TestNameBaggage(t *testing.T) { 22 | var e nfstypes.Filename3 23 | bs, err := xdr.EncodeBuf(&e) 24 | if err != nil { 25 | panic(err) 26 | } 27 | if len(bs) != 4 { 28 | t.Fatalf("size of empty filename is %d != 4", len(bs)) 29 | } 30 | } 31 | 32 | func TestEntryPlus3Baggage(t *testing.T) { 33 | var e nfstypes.Entryplus3 34 | e.Name_attributes.Attributes_follow = true 35 | e.Name_handle.Handle_follows = true 36 | bs, err := xdr.EncodeBuf(&e) 37 | if err != nil { 38 | panic(err) 39 | } 40 | if len(bs) > int(entryplus3Baggage) { 41 | t.Fatalf("size of entryplus3 is %d > %d", len(bs), entryplus3Baggage) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /eval/aggregate-times.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # aggregate the results of running tshark over an NFS packet capture 4 | # 5 | # gather data with 6 | # tshark -i lo -f tcp -w nfs.pcap 7 | # 8 | # then process with 9 | # tshark -Tfields -e 'nfs.procedure_v3' -e 'rpc.time' -r nfs.pcap '(nfs && rpc.time)' | ./aggregate-times.py 10 | # 11 | # note that running tshark over a trace takes a while 12 | 13 | import re 14 | import sys 15 | import numpy as np 16 | 17 | proc_mapping = { 18 | 0: "NULL", 19 | 1: "GETATTR", 20 | 2: "SETATTR", 21 | 3: "LOOKUP", 22 | 4: "ACCESS", 23 | 6: "READ", 24 | 7: "WRITE", 25 | 8: "CREATE", 26 | 9: "MKDIR", 27 | 10: "SYMLINK", 28 | 12: "REMOVE", 29 | 13: "RMDIR", 30 | 14: "RENAME", 31 | 15: "LINK", 32 | 16: "READDIR", 33 | 17: "READDIRPLUS", 34 | 18: "FSSTAT", 35 | 19: "FSINFO", 36 | 20: "PATHCONF", 37 | 21: "COMMIT", 38 | } 39 | 40 | 41 | def proc_latencies(f): 42 | latencies_s = {} 43 | for line in f: 44 | m = re.match(r"""(?P.*)\t(?P