├── .gitignore ├── Makefile ├── README.md ├── bytes.rs ├── driver ├── experiments.sh ├── hasher ├── .cargo │ └── config ├── Cargo.toml └── src │ ├── lib.rs │ └── time.rs ├── hasherlat ├── .cargo │ └── config ├── Cargo.toml └── src │ ├── lib.rs │ └── time.rs ├── host.rs ├── ipc.c ├── ipc.rs ├── job.rs ├── launcher.rs ├── mkuls ├── pgroup.c ├── pgroup.rs ├── ringbuf.rs ├── runtime.c ├── runtime.rs ├── shasum.rs ├── signaling ├── sleep.rs ├── spc.rs ├── stats ├── test.rs ├── time.rs └── time_utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | /host 2 | /launcher 3 | /test 4 | 5 | target/ 6 | *.a 7 | *.lock 8 | *.o 9 | *.rlib 10 | *.so 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | override CPPFLAGS := $(CPPFLAGS) -DNDEBUG 2 | override CFLAGS := $(if $(NOPTS),-Og,-O2) -g -std=c99 -Wall -Wextra -Werror $(CFLAGS) 3 | override LDFLAGS := $(LDFLAGS) 4 | override LDLIBS := $(LDLIBS) 5 | override RUSTFLAGS := $(if $(NOPTS),,-O) -g $(RUSTFLAGS) 6 | 7 | CARGO := cargo 8 | RUSTC := rustc 9 | 10 | ifdef NOPTS 11 | $(warning \vvvvvvvvvvvvvvvvvvvvvvvvvvvvv/) 12 | $(warning = PRODUCING UNOPTIMIZED BUILD =) 13 | $(warning /^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\) 14 | endif 15 | 16 | host: private LDLIBS += -lstatic=ipc 17 | host: private RUSTFLAGS += -L. --cfg 'feature="invoke_$(INVOCATION)"' 18 | host: bytes.rs ipc.rs libipc.a job.rs pgroup.rs libpgroup.a ringbuf.rs 19 | 20 | launcher: private LDLIBS += -ldl -lstatic=ipc 21 | launcher: private RUSTFLAGS += -L. --cfg 'feature="$(UNLOADING)_loaded"' 22 | launcher: ipc.rs libipc.a job.rs runtime.rs libruntime.a 23 | 24 | shasum: private RUSTFLAGS += -Lhasher/target/release/deps 25 | shasum: hasher/rlib 26 | 27 | test: private RUSTFLAGS += -L. -Crpath -Funsafe-code 28 | test: libbytes.rlib libipc.so time.rs 29 | 30 | ipc.o: private CPPFLAGS += -D_XOPEN_SOURCE 31 | 32 | runtime.o: private CPPFLAGS += -D_GNU_SOURCE 33 | runtime.o: time_utils.h 34 | 35 | libipc.so: private LDLIBS += -lstatic=ipc 36 | libipc.so: private RUSTFLAGS += -L. --crate-type dylib -Cprefer-dynamic 37 | libipc.so: libipc.a 38 | 39 | libsleep.so: private RUSTFLAGS += -L. -Funsafe-code 40 | libsleep.so: libspc.rlib time.rs 41 | 42 | libtest.so: private RUSTFLAGS += -L. -Funsafe-code 43 | libtest.so: libbytes.rlib libspc.rlib time.rs 44 | 45 | .PHONY: clean 46 | clean: 47 | $(RM) $(filter-out $(shell grep -H ^/ $(shell git ls-files .gitignore '*/.gitignore') | sed 's/\.gitignore:\///'),$(shell git clean -nX | cut -d" " -f3-)) 48 | -rm -r $(shell git clean -ndX | cut -d" " -f3- | grep '/$$') 49 | 50 | .PHONY: distclean 51 | distclean: clean 52 | git clean -fX 53 | 54 | %/rlib: 55 | cd $* && $(CARGO) rustc --release --no-default-features -- --crate-type rlib 56 | 57 | %/so: libspc.rlib 58 | cd $* && $(CARGO) build --release 59 | 60 | %: %.rs 61 | $(RUSTC) $(RUSTFLAGS) -Clink-args="$(LDFLAGS)" $< $(LDLIBS) 62 | 63 | lib%.a: %.o 64 | $(AR) rs $@ $^ 65 | 66 | lib%.rlib: %.rs 67 | $(RUSTC) --crate-type rlib --cfg 'feature="no_mangle_main"' $(RUSTFLAGS) -Clink-args="$(LDFLAGS)" $< $(LDLIBS) 68 | 69 | lib%.so: %.rs 70 | $(RUSTC) --crate-type cdylib --cfg 'feature="no_mangle_main"' $(RUSTFLAGS) -Clink-args="$(LDFLAGS)" $< $(LDLIBS) 71 | @objdump -t $@ | grep '\.*\' >/dev/null 2>&1 || { \ 72 | echo "IT ONLY MAKES SENSE TO BUILD $(basename $@) AS A STATIC LIBRARY" >&2; \ 73 | rm $@; \ 74 | false; \ 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Microservices microbenchmarks 2 | ============================= 3 | This repository contains the code for the benchmarks presented in our ATC '18 short paper, "Putting the 'Micro' Back in Microservice." 4 | 5 | License 6 | ------- 7 | The entire contents and history of this repository are distributed under the following license: 8 | 9 | Copyright 2018 Carnegie Mellon University 10 | 11 | Licensed under the Apache License, Version 2.0 (the "License"); 12 | you may not use this file except in compliance with the License. 13 | You may obtain a copy of the License at 14 | 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, 19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | See the License for the specific language governing permissions and 21 | limitations under the License. 22 | 23 | Dependencies 24 | ------------ 25 | To obtain the results in the paper, we used the following versions: 26 | * Linux 4.13.0 built from upstream 27 | * GCC 7.3.0 from Debian 28 | * Rust 1.23.0 installed using rustup 29 | 30 | System components 31 | ----------------- 32 | In addition to the experiment driver, this suite consists of the following programs: 33 | * `host`: referred to in the paper as the "dispatcher process" 34 | * `launcher`: referred to in the paper as the "worker process" 35 | * Microservices: 36 | * `test.rs`: timestamp recorder used for section 2.1 of the paper 37 | * `sleep.rs`: used for signal predictability study in section 2.2 of the paper 38 | * `hasher/`: hashing workload used for section 4 of the paper 39 | 40 | Modes of operation 41 | ------------------ 42 | The `host` and `launcher` each have two different modes of operation, selected at compile time. 43 | 44 | When doing a `make host`, provide the environment variable `INVOCATION="..."` to specify how microservices should be invoked: 45 | * `launcher` uses worker processes to demonstrate what the paper refers to as "language-based isolation" 46 | * Otherwise, the process launches microservices directly ("process-based isolation") according to the other two possible values: 47 | * `forkjoin` launches a new microservice process every time a request must be handled ("cold-start invocation" in the paper) 48 | * `sendmsg` launches a blocking process for each microservice at the start and forwards requests using loopback UDP ("cold-start invocation") 49 | 50 | If the dispatcher is configured to use worker processes, do a `make launcher` with `UNLOADING="..."`: 51 | * `cleanup` to `dlopen()`, `dlsym()`, `dlclose()` each time any microservice must be invoked ("cold-start invocation" in the paper) 52 | * `memoize` to keep microservice libraries loaded into the workers after the initial `dlopen()` ("warm-start invocation" in the paper) 53 | 54 | Microservices need to be built as shared libraries if using `launcher`, and as executables otherwise. 55 | 56 | Data files with reported numbers 57 | -------------------------------- 58 | The data files containing the full results from the experimental runs presented in the paper may be downloaded from: 59 | https://github.com/efficient/microservices_microbenchmarks/releases 60 | 61 | Each archive contains a script that can be used to easily rerun that experiment. 62 | 63 | Invocation latency experiment (section 2.1) 64 | ------------------------------------------- 65 | First build the core components: 66 | 1. Do a `make distclean` if you already have build artifacts in the checkout. 67 | 2. Start by building the `host` and `launcher` (if applicable) as described above. 68 | 69 | Now it's time to build the microservice. 70 | To simulate running a large number of diverse microservices, we make 5000 copies of a compiled microservice; 71 | this prevents the kernel from sharing memory pages between them. 72 | To build and copy the necessary microservice, do a 73 | 74 | ./mkuls test 5000 bins 75 | 76 | for a "process-based isolation" run, or a 77 | 78 | ./mkuls libtest.so 5000 libs 79 | 80 | for a "language-based isolation" one. 81 | 82 | Download and extract the `invocation.tar` archive from the above link. 83 | Notice that there's a folder for each mode of the experiment presented in the paper. 84 | Choose one such folder (we'll call it "src") and the name of a new output folder to be created (we'll call it "dest"), then do: 85 | 86 | src/repeat dest 87 | 88 | Once the experiment finishes, the results can be found in a text file within the new folder. 89 | 90 | Preemption throughput degradation experiment (section 3) 91 | -------------------------------------------------------- 92 | First build the core components using this specific configuration: 93 | 1. Do a `make distclean` if you already have build artifacts in the checkout. 94 | 2. Run: `make host INVOCATION="launcher"` 95 | 3. Run: `make launcher UNLOADING="cleanup"` 96 | 97 | Now build the SHA-512 hasher microservice as a shared object: `make hasher/so` 98 | 99 | Download and extract the `preemption.tar` archive from the above link. 100 | Decide on the name of some new output folder (here, "out") and do: 101 | 102 | preemption/repeat out 103 | 104 | Once the experiment finishes, the results can be found in a series of text files within the new folder. 105 | 106 | Signal predictability experiment (section 2.2) 107 | ---------------------------------------------- 108 | No data files are provided for this experiment. 109 | 110 | Start by building the core components using the same steps as in the previous section. 111 | Next, build the microservice using: `make libsleep.so` 112 | 113 | Decide on the desired SIGALRM period (which we'll refer to as "quantum"), in microseconds. 114 | Now do: 115 | 116 | ./stats 1 taskset 0x1 ./signaling 3 117 | 118 | This will run the experiment, then print the absolute values of recorded deviations followed by a statistical summary. 119 | We recommend treating the very first invocation as a warmup round. 120 | -------------------------------------------------------------------------------- /bytes.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of_val; 2 | use std::slice::from_raw_parts_mut; 3 | 4 | pub trait Bytes { 5 | // Because this trait is intended to provide zero-copy raw access, it would be racy if it 6 | // accepted non-mut refs. 7 | fn bytes<'a>(&'a mut self) -> &'a mut [u8]; 8 | } 9 | 10 | pub trait DefaultBytes {} 11 | 12 | impl Bytes for T { 13 | fn bytes<'a>(&'a mut self) -> &'a mut [u8] { 14 | unsafe { 15 | from_raw_parts_mut(self as *mut T as *mut u8, size_of_val(self)) 16 | } 17 | } 18 | } 19 | 20 | impl DefaultBytes for () {} 21 | 22 | impl DefaultBytes for i64 {} 23 | impl DefaultBytes for usize {} 24 | 25 | impl DefaultBytes for (S, T) {} 26 | 27 | #[test] 28 | fn unit() { 29 | assert_eq!(0, ().bytes().len()); 30 | } 31 | 32 | impl Bytes for String { 33 | fn bytes<'a>(&'a mut self) -> &'a mut [u8] { 34 | unsafe { 35 | self.as_bytes_mut() 36 | } 37 | } 38 | } 39 | 40 | #[test] 41 | fn string() { 42 | let msg = "The quick brown fox jumps over the lazy hen."; 43 | assert_eq!(msg.len(), msg.bytes().len()); 44 | } 45 | -------------------------------------------------------------------------------- /driver: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -le 3 ] || [ "$2" -le "0" ] || [ $# -lt $((3 + $2)) ] 4 | then 5 | echo "USAGE: $0 ... [args]..." 6 | exit 1 7 | fi 8 | destdir="$1" 9 | shift 10 | 11 | if ! mkdir "$destdir" 12 | then 13 | echo "OOPS: Couldn't create destination directory '$destdir'" >&2 14 | exit 2 15 | fi 16 | cat <<-tac >"$destdir/repeat" 17 | #!/bin/sh 18 | 19 | if [ \$# -ne 1 ] 20 | then 21 | $(printf "\t")echo "USAGE: \$0 " 22 | $(printf "\t")exit 1 23 | fi 24 | 25 | exec "$0" "\$1"$(printf " \"%s\"" "$@") 26 | tac 27 | chmod +x "$destdir/repeat" 28 | 29 | nlists="$1" 30 | shift 31 | for list in `seq "$nlists"` 32 | do 33 | eval list_$list="'$1'" 34 | shift 35 | done 36 | 37 | . ./experiments.sh 38 | 39 | done="" 40 | runs="1" 41 | times="" 42 | explore() { 43 | local nfixed="$1" 44 | shift 45 | 46 | local fixed="" 47 | local n 48 | for n in `seq "$nfixed"` 49 | do 50 | fixed="$fixed $1" 51 | shift 52 | done 53 | fixed="`tail -c+2 <<-dexif 54 | $fixed 55 | dexif`" 56 | 57 | local nlist="$((nlists - nfixed))" 58 | if [ "$nlist" -eq "0" ] 59 | then 60 | done="runs" 61 | 62 | printf "%s\n" "RUN:$fixed" 63 | echo === 64 | 65 | local filename="`sed 's/ /_/g' <<-lacol 66 | $fixed 67 | lacol`" 68 | filename="$destdir/$filename.txt" 69 | 70 | local err 71 | err="`( time -p ./stats 1 "$@" $fixed 2>&1 >"$filename" || printf "\n%s\n" "ABORTING RUN" ) | tee /tmp/experimentprogress`" 72 | if grep '^ABORTING RUN$' /tmp/experimentprogress >/dev/null 73 | then 74 | echo "Trial FAILED with exit status $status!" 75 | cat <<-tac 76 | $err 77 | tac 78 | else 79 | local line="`stats_first_line "$filename"`" 80 | tail -n"+$line" "$filename" 81 | fi 82 | echo 83 | 84 | cp /tmp/experimentprogress "$filename.log" 85 | 86 | local time="`tail -n3 <<-lacol | head -n1 | cut -d" " -f2 | cut -d. -f1 87 | $err 88 | lacol`" 89 | times="`cat <<-semit 90 | $times 91 | $time 92 | semit`" 93 | local stats="`tail -n+2 <<-lacol | ./stats 1 cat 94 | $times 95 | `" 96 | local run="`printf "%s\n" "$stats" | statistic 1 Count -`" 97 | local ave="`printf "%s\n" "$stats" | statistic 1 Mean -`" 98 | echo "`date`: Trial ($run/$runs) took $time s / Experiment has taken `printf "%s\n" "$stats" | statistic 1 Sum -` s / ETA in `echo "($runs - $run) * $ave" | bc -l | cut -d. -f1` s" 99 | 100 | echo 101 | echo 102 | return 103 | fi 104 | eval local list="\"\$list_$nlist\"" 105 | if [ -z "$done" ] 106 | then 107 | runs=$((runs * `echo "$list" | sed 's/[^ ]\+//g' | wc -c`)) 108 | fi 109 | 110 | local item 111 | for item in $list 112 | do 113 | explore $((nfixed + 1)) $item $fixed "$@" 114 | done 115 | } 116 | 117 | explore 0 "$@" 118 | rm -f /tmp/experimentprogress 119 | -------------------------------------------------------------------------------- /experiments.sh: -------------------------------------------------------------------------------- 1 | repeat_numlists() { 2 | local dirname="$1" 3 | tail -n1 "$dirname/repeat" | cut -d\" -f6 4 | } 5 | 6 | repeat_list() { 7 | local dirname="$1" 8 | local nlist="$2" 9 | tail -n1 "$dirname/repeat" | cut -d\" -f$((6 + 2 * nlist)) 10 | } 11 | 12 | statistic() { 13 | local statidx="$1" 14 | local statname="$2" 15 | local filename="$3" 16 | 17 | local input="" 18 | if [ "$filename" = "-" ] 19 | then 20 | input="`cat`" 21 | fi 22 | tail -n"+`stats_first_line "$filename" <<-enil_tsrif_stats 23 | $input 24 | enil_tsrif_stats`" "$filename" <<-liat | grep "^$statname" | tail -n"+$statidx" | head -n1 | rev | cut -d" " -f1 | rev 25 | $input 26 | liat 27 | } 28 | 29 | unstats() { 30 | local filename="$1" 31 | 32 | local input="" 33 | if [ "$filename" = "-" ] 34 | then 35 | input="`cat`" 36 | fi 37 | head -n"`stats_first_line "$filename" <<-enil_tsrif_stats 38 | $input 39 | enil_tsrif_stats`" "$filename" <<-liat | tail -n+5 | head -n-2 40 | $input 41 | liat 42 | } 43 | 44 | stats_first_line() { 45 | local filename="$1" 46 | printf "%s\n" $((`grep -n "^$" "$filename" | tail -n+2 | head -n1 | cut -d: -f1` + 1)) 47 | } 48 | -------------------------------------------------------------------------------- /hasher/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-Funsafe-code", "-L.."] 3 | -------------------------------------------------------------------------------- /hasher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hasher" 3 | version = "0.0.0" 4 | 5 | [lib] 6 | crate-type = ["cdylib"] 7 | 8 | [features] 9 | default = ["no_mangle_main"] 10 | no_mangle_main = [] 11 | 12 | [dependencies] 13 | rand = "0.4.2" 14 | ring = "0.12.1" 15 | -------------------------------------------------------------------------------- /hasher/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | extern crate ring; 3 | extern crate spc; 4 | 5 | mod time; 6 | 7 | use rand::Rng; 8 | use rand::thread_rng; 9 | use ring::digest::SHA512; 10 | use ring::digest::digest; 11 | use spc::sbox; 12 | use time::nsnow; 13 | 14 | const COMPUTE_WIDTH: usize = 64; 15 | const DATA_BYTES: usize = 1_024 * 1_024 * 1024; 16 | const REPORTS_PER_SEC: i64 = 2; 17 | 18 | #[cfg_attr(feature = "no_mangle_main", no_mangle)] 19 | pub fn main() { 20 | *sbox() = nsnow().unwrap(); 21 | 22 | let data: Vec = (0..DATA_BYTES).map(|_| thread_rng().gen()).collect(); 23 | let data = data.into_boxed_slice(); 24 | 25 | let mut ts = nsnow().unwrap(); 26 | let mut count = 0; 27 | 28 | for lo in (0..DATA_BYTES / COMPUTE_WIDTH).map(|num| num * 16) { 29 | digest(&SHA512, &data[lo..lo + COMPUTE_WIDTH]); 30 | 31 | count += 1; 32 | if nsnow().unwrap() - ts >= 1_000_000_000 / REPORTS_PER_SEC { 33 | println!("{}", count * REPORTS_PER_SEC); 34 | count = 0; 35 | ts = nsnow().unwrap(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /hasher/src/time.rs: -------------------------------------------------------------------------------- 1 | ../../time.rs -------------------------------------------------------------------------------- /hasherlat/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-Funsafe-code", "-L.."] 3 | -------------------------------------------------------------------------------- /hasherlat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hasher" 3 | version = "0.0.0" 4 | 5 | [lib] 6 | crate-type = ["cdylib"] 7 | 8 | [features] 9 | default = ["no_mangle_main"] 10 | no_mangle_main = [] 11 | 12 | [dependencies] 13 | rand = "0.4.2" 14 | ring = "0.12.1" 15 | -------------------------------------------------------------------------------- /hasherlat/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | extern crate ring; 3 | extern crate spc; 4 | 5 | mod time; 6 | 7 | use rand::Rng; 8 | use rand::thread_rng; 9 | use ring::digest::SHA512; 10 | use ring::digest::digest; 11 | use spc::sbox; 12 | use time::nsnow; 13 | 14 | const COMPUTE_WIDTH: usize = 64; 15 | const DATA_BYTES: usize = 10 * 1_024 * 1024; 16 | 17 | #[cfg_attr(feature = "no_mangle_main", no_mangle)] 18 | pub fn main() { 19 | *sbox() = nsnow().unwrap(); 20 | 21 | let data: Vec = (0..DATA_BYTES).map(|_| thread_rng().gen()).collect(); 22 | let data = data.into_boxed_slice(); 23 | 24 | let times = (0..DATA_BYTES / COMPUTE_WIDTH).map(|num| { 25 | let lo = num * 16; 26 | let ts = nsnow().unwrap(); 27 | digest(&SHA512, &data[lo..lo + COMPUTE_WIDTH]); 28 | nsnow().unwrap() - ts 29 | }); 30 | 31 | for time in times { 32 | println!("{}", time); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hasherlat/src/time.rs: -------------------------------------------------------------------------------- 1 | use std::time::UNIX_EPOCH; 2 | use std::time::SystemTime; 3 | use std::time::SystemTimeError; 4 | 5 | pub fn nsnow() -> Result { 6 | let time = SystemTime::now().duration_since(UNIX_EPOCH)?; 7 | 8 | Ok((time.as_secs() * 1_000_000_000 + time.subsec_nanos() as u64) as i64) 9 | } 10 | -------------------------------------------------------------------------------- /host.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "invoke_sendmsg")] 2 | mod bytes; 3 | #[allow(dead_code)] 4 | #[cfg(any(feature = "invoke_forkexec", feature = "invoke_launcher"))] 5 | mod ipc; 6 | #[cfg_attr(not(feature = "invoke_launcher"), allow(dead_code))] 7 | mod job; 8 | #[allow(dead_code)] 9 | mod pgroup; 10 | #[allow(dead_code)] 11 | #[cfg(any(feature = "invoke_sendmsg", feature = "invoke_launcher"))] 12 | mod ringbuf; 13 | mod time; 14 | 15 | #[cfg(feature = "invoke_sendmsg")] 16 | use bytes::Bytes; 17 | #[cfg(any(feature = "invoke_forkexec", feature = "invoke_launcher"))] 18 | use ipc::SMem; 19 | #[cfg(feature = "invoke_launcher")] 20 | use job::FixedCString; 21 | use job::Job; 22 | use job::args; 23 | use job::printstats; 24 | use pgroup::exit; 25 | #[cfg(any(feature = "invoke_sendmsg", feature = "invoke_launcher"))] 26 | use pgroup::kill_at_exit; 27 | #[cfg(any(feature = "invoke_sendmsg", feature = "invoke_launcher"))] 28 | use pgroup::setpgid; 29 | #[cfg(feature = "invoke_launcher")] 30 | use pgroup::term; 31 | #[cfg(any(feature = "invoke_sendmsg", feature = "invoke_launcher"))] 32 | use ringbuf::RingBuffer; 33 | use std::cell::Cell; 34 | use std::cell::RefCell; 35 | use std::env::Args; 36 | use std::fmt::Display; 37 | #[cfg(feature = "invoke_launcher")] 38 | use std::iter::repeat; 39 | use std::mem::replace; 40 | #[cfg(feature = "invoke_sendmsg")] 41 | use std::net::SocketAddr; 42 | #[cfg(feature = "invoke_sendmsg")] 43 | use std::net::UdpSocket; 44 | #[cfg(any(feature = "invoke_sendmsg", feature = "invoke_launcher"))] 45 | use std::os::unix::process::CommandExt; 46 | #[cfg(any(feature = "invoke_sendmsg", feature = "invoke_launcher"))] 47 | use std::process::Child; 48 | use std::process::Command; 49 | use std::process::Stdio; 50 | #[cfg(feature = "invoke_launcher")] 51 | use std::sync::atomic::AtomicBool; 52 | #[cfg(feature = "invoke_launcher")] 53 | use std::sync::atomic::Ordering; 54 | use time::nsnow; 55 | 56 | const DEFAULT_USERVICE_MASK: &str = "0x4"; 57 | 58 | #[cfg(not(feature = "invoke_launcher"))] 59 | const USAGE_EXTENDED: &str = "[cpumask]"; 60 | #[cfg(feature = "invoke_launcher")] 61 | const USAGE_EXTENDED: &str = "[cpumask] [quantum] [limit]"; 62 | 63 | thread_local! { 64 | static ATTACH_STREAMS: Cell = Cell::new(false); 65 | static USERVICE_MASK: RefCell = RefCell::new(String::from(DEFAULT_USERVICE_MASK)); 66 | } 67 | 68 | fn main() { 69 | let (svcname, numobjs, numjobs, attach_streams, mut args) = args(USAGE_EXTENDED).unwrap_or_else(|(retcode, errmsg)| { 70 | println!("{}", errmsg); 71 | exit(retcode); 72 | }); 73 | if numjobs < numobjs { 74 | println!(" may not be greater than "); 75 | exit(2); 76 | } 77 | ATTACH_STREAMS.with(|streams| streams.set(attach_streams)); 78 | if let Some(mask) = args.next() { 79 | if &mask[0..2] != "0x" { 80 | println!("[cpumask], if provided, must be a hex mask starting with '0x'"); 81 | exit(2); 82 | } 83 | 84 | USERVICE_MASK.with(|uservice_mask| replace(&mut *uservice_mask.borrow_mut(), mask)); 85 | } 86 | 87 | let mut jobs = joblist(&svcname, numobjs, numjobs); 88 | let comm_handles = handshake(&jobs, numobjs, &mut args).unwrap_or_else(|msg| { 89 | eprintln!("During initialization: {}", msg); 90 | exit(3); 91 | }); 92 | 93 | let tput = invoke(&mut jobs, numobjs, comm_handles).unwrap_or_else(|err| { 94 | eprintln!("While invoking microservice: {}", err); 95 | exit(4); 96 | }); 97 | 98 | if ! attach_streams { 99 | printstats(&jobs, tput); 100 | } 101 | } 102 | 103 | #[cfg(not(any(feature = "invoke_forkexec", feature = "invoke_sendmsg", feature = "invoke_launcher")))] 104 | compile_error!("Must select an invoke_* personality via '--feature' or '--cfg feature='!"); 105 | 106 | #[cfg(feature = "invoke_sendmsg")] 107 | type Comms = (UdpSocket, RingBuffer<(Child, SocketAddr)>); 108 | 109 | #[cfg(feature = "invoke_launcher")] 110 | type Comms<'a, 'b> = RingBuffer<(Child, Option<&'a mut Job>, SMem<'b, (AtomicBool, Job)>)>; 111 | 112 | fn window() -> u32 { 113 | USERVICE_MASK.with(|uservice_mask| { 114 | usize::from_str_radix(&uservice_mask.borrow()[2..], 16) 115 | }).unwrap().count_ones() 116 | } 117 | 118 | #[cfg(not(feature = "invoke_launcher"))] 119 | fn joblist(svcname: &str, numobjs: usize, numjobs: usize) -> Box<[Job]> { 120 | use job::joblist; 121 | 122 | joblist(|index| format!("{}{}", svcname, index), numobjs, numjobs) 123 | } 124 | 125 | #[cfg(feature = "invoke_launcher")] 126 | fn joblist(svcname: &str, numobjs: usize, numjobs: usize) -> Box<[Job]> { 127 | use job::joblist; 128 | 129 | joblist(|index| { 130 | FixedCString::from(&format!("{}{}.so", svcname, index)) 131 | }, numobjs, numjobs) 132 | } 133 | 134 | #[cfg(feature = "invoke_forkexec")] 135 | fn handshake<'a>(_: &[Job], _: usize, _: &mut Args) -> Result]>, String> { 136 | let mut maybe_comms = (0..window()).map(|_| 137 | SMem::new(0).map_err(|or| format!("Initializing shared memory: {}", or)) 138 | ); 139 | 140 | let comms: Vec<_> = maybe_comms.by_ref().take_while(|each| 141 | each.is_ok() 142 | ).map(|each| 143 | each.unwrap() 144 | ).collect(); 145 | 146 | if let Some(error) = maybe_comms.next() { 147 | error?; 148 | } 149 | 150 | Ok(comms.into_boxed_slice()) 151 | } 152 | 153 | #[cfg(feature = "invoke_sendmsg")] 154 | fn handshake(jobs: &[Job], nprocs: usize, _: &mut Args) -> Result { 155 | const BATCH_SIZE: usize = 100; 156 | 157 | let socket = UdpSocket::bind("127.0.0.1:0").map_err(|or| format!("Initializing UDP socket: {}", or))?; 158 | let addr = socket.local_addr().map_err(|or| format!("Determining socket address: {}", or))?; 159 | 160 | let mut pgroup = 0; 161 | let handles: Vec<_> = (0..nprocs / BATCH_SIZE + 1).flat_map(|group| { 162 | let procs: Vec<_> = (group * BATCH_SIZE..nprocs.min((group + 1) * BATCH_SIZE)).map(|job| { 163 | let mut handle = process(&jobs[job].uservice_path, &format!("{} 127.0.0.{}:0 {}", addr, group + 2, job)); 164 | handle.before_exec(move || setpgid(pgroup).map(|_| ())); 165 | 166 | let handle = handle.spawn().unwrap_or_else(|msg| { 167 | eprintln!("Spawning child process '{}': {}", jobs[job].uservice_path, msg); 168 | exit(5); 169 | }); 170 | if job == 0 { 171 | pgroup = handle.id(); 172 | kill_at_exit(-(pgroup as i32)); 173 | } 174 | 175 | handle 176 | }).collect(); 177 | 178 | let mut ports: Vec<_> = (0..procs.len()).map(|_| { 179 | let mut process = 0usize; 180 | let (_, addr) = socket.recv_from(process.bytes()).unwrap_or_else(|msg| { 181 | eprintln!("Socket handshake: {}", msg); 182 | exit(6); 183 | }); 184 | 185 | (process, addr) 186 | }).collect(); 187 | ports.sort_by_key(|&(process, _)| process); 188 | 189 | procs.into_iter().zip(ports.into_iter().map(|(_, addr)| addr)) 190 | }).collect(); 191 | 192 | Ok((socket, RingBuffer::new(handles.into_boxed_slice()))) 193 | } 194 | 195 | #[cfg(feature = "invoke_launcher")] 196 | fn handshake<'a, 'b>(_: &[Job], _: usize, args: &mut Args) -> Result, String> { 197 | let ones = window(); 198 | 199 | let mut pgroup = 0; 200 | let them: Vec<_> = (0..ones as i64).map(|count| { 201 | let mut index = Job::new(FixedCString::new()); 202 | index.completion_time = count; 203 | let mem = SMem::new((AtomicBool::new(false), index)).unwrap_or_else(|msg| { 204 | eprintln!("Initializing shared memory: {}", msg); 205 | exit(5); 206 | }); 207 | 208 | let mut handle = process("./launcher", format!("{}", mem.id())); 209 | for arg in args.chain(repeat(String::from("0"))).take(2) { 210 | handle.arg(arg); 211 | } 212 | handle.before_exec(move || setpgid(pgroup).map(|_| ())); 213 | let handle = handle.spawn().unwrap_or_else(|msg| { 214 | eprintln!("Spawning launcher process: {}", msg); 215 | exit(6); 216 | }); 217 | if count == 0 { 218 | pgroup = handle.id(); 219 | kill_at_exit(-(pgroup as i32)); 220 | } 221 | 222 | (handle, None, mem) 223 | }).collect(); 224 | 225 | Ok(RingBuffer::new(them.into_boxed_slice())) 226 | } 227 | 228 | #[cfg(feature = "invoke_forkexec")] 229 | fn invoke(jobs: &mut [Job], warmup: usize, comms: Box<[SMem]>) -> Result { 230 | use pgroup::nowait; 231 | 232 | let window = window(); 233 | let mut index = 0; 234 | let mut finished = 0; 235 | 236 | debug_assert!(comms.len() == window as usize); 237 | let mut comm_to_job = vec![usize::max_value(); comms.len()].into_boxed_slice(); 238 | 239 | let mut duration = 0; 240 | while finished < jobs.len() { 241 | if let Some(comm) = nowait().unwrap_or(None) { 242 | let ts = nsnow().unwrap(); 243 | let comm = comm as usize; 244 | 245 | debug_assert!(comm_to_job[comm] != usize::max_value()); 246 | let job = &mut jobs[comm_to_job[comm]]; 247 | comm_to_job[comm] = usize::max_value(); 248 | 249 | job.completion_time = ts - job.invocation_latency; 250 | job.invocation_latency = *comms[comm] - job.invocation_latency; 251 | 252 | finished += 1; 253 | } 254 | 255 | while { 256 | index < jobs.len() && if let Some(comm) = comm_to_job.iter().position(|it| *it == usize::max_value()) { 257 | if index == warmup { 258 | duration = nsnow().unwrap(); 259 | } 260 | 261 | let job = &mut jobs[index]; 262 | debug_assert!(comm_to_job[comm] == usize::max_value()); 263 | comm_to_job[comm] = index; 264 | 265 | let mut process = process(&job.uservice_path, comms[comm].id()); 266 | process.arg(&format!("{}", comm)); 267 | job.invocation_latency = nsnow().unwrap(); 268 | process.spawn().map_err(|or| format!("Spawning child {}: {}", index, or))?; 269 | 270 | index += 1; 271 | 272 | true 273 | } else { 274 | false 275 | } 276 | } {} 277 | } 278 | 279 | Ok(1_000_000_000.0 * (jobs.len() - warmup) as f64 / duration as f64) 280 | } 281 | 282 | #[cfg(feature = "invoke_sendmsg")] 283 | fn invoke(jobs: &mut [Job], warmup: usize, comms: Comms) -> Result { 284 | use std::io::ErrorKind; 285 | 286 | let (me, mut them) = comms; 287 | me.set_nonblocking(true).map_err(|or| format!("Switching to nonblocking: {}", or))?; 288 | 289 | let window = window(); 290 | let mut index = 0; 291 | let mut inflight = 0; 292 | let mut finished = 0; 293 | 294 | let mut duration = 0; 295 | while finished < jobs.len() { 296 | let mut fin = (0usize, 0i64); 297 | match me.recv(fin.bytes()) { 298 | Ok(len) => { 299 | let ts = nsnow().unwrap(); 300 | 301 | debug_assert!(len == std::mem::size_of_val(&fin)); 302 | let (job, fin) = fin; 303 | let job = &mut jobs[job]; 304 | job.completion_time = ts - job.invocation_latency; 305 | job.invocation_latency = fin - job.invocation_latency; 306 | 307 | inflight -= 1; 308 | finished += 1; 309 | }, 310 | Err(or) => if or.kind() != ErrorKind::WouldBlock { 311 | Err(format!("Receiving from child: {}", or))?; 312 | }, 313 | } 314 | 315 | while inflight < window && index < jobs.len() { 316 | if index == warmup { 317 | duration = nsnow().unwrap(); 318 | } 319 | 320 | let job = &mut jobs[index]; 321 | let &mut (_, addr) = &mut them[index]; 322 | 323 | job.invocation_latency = nsnow().unwrap(); 324 | me.send_to(index.bytes(), &addr).map_err(|or| format!("Sending to child {}: {}", index, or))?; 325 | 326 | index += 1; 327 | inflight += 1; 328 | } 329 | } 330 | duration = nsnow().unwrap() - duration; 331 | 332 | for &mut (ref mut child, _) in &mut *them { 333 | child.kill().map_err(|err| format!("Killing child: {}", err))?; 334 | } 335 | 336 | for &mut (ref mut child, _) in &mut *them { 337 | child.wait().map_err(|err| format!("Waiting on child: {}", err))?; 338 | } 339 | 340 | Ok(1_000_000_000.0 * (jobs.len() - warmup) as f64 / duration as f64) 341 | } 342 | 343 | #[cfg(feature = "invoke_launcher")] 344 | fn invoke<'a, 'b>(jobs: &'a mut [Job], warmup: usize, mut comms: Comms<'a, 'b>) -> Result { 345 | let mut jobs = jobs.iter_mut(); 346 | for job in jobs.by_ref().take(warmup) { 347 | for &mut (_, _, ref mut task) in &mut *comms { 348 | task.1 = job.clone(); 349 | 350 | let ts = nsnow().unwrap(); 351 | *task.0.get_mut() = true; 352 | while task.0.load(Ordering::Relaxed) {} 353 | job.completion_time = nsnow().unwrap() - ts; 354 | job.invocation_latency = task.1.invocation_latency - ts; 355 | } 356 | } 357 | 358 | let nonwarmup = jobs.len(); 359 | let mut finished = 0; 360 | let mut duration = nsnow().unwrap(); 361 | while finished < nonwarmup { 362 | for &mut (_, ref mut job, ref mut region) in &mut *comms { 363 | let &mut (ref mut running, ref mut task) = &mut **region; 364 | 365 | if ! running.load(Ordering::Relaxed) { 366 | if let &mut Some(ref mut job) = job { 367 | job.completion_time = nsnow().unwrap() - job.invocation_latency; 368 | job.invocation_latency = task.invocation_latency - job.invocation_latency; 369 | finished += 1; 370 | } 371 | 372 | *job = if let Some(job) = jobs.next() { 373 | *task = job.clone(); 374 | 375 | job.invocation_latency = nsnow().unwrap(); 376 | *running.get_mut() = true; 377 | 378 | Some(job) 379 | } else { 380 | None 381 | }; 382 | } 383 | } 384 | } 385 | duration = nsnow().unwrap() - duration; 386 | 387 | for &mut (ref mut launcher, _, _) in &mut *comms { 388 | term(launcher.id() as i32).map_err(|err| format!("Terminating child: {}", err))?; 389 | launcher.wait().map_err(|err| format!("Waiting on child: {}", err))?; 390 | } 391 | 392 | Ok(1_000_000_000.0 * nonwarmup as f64 / duration as f64) 393 | } 394 | 395 | fn process(path: &str, arg: T) -> Command { 396 | let mut process = Command::new("taskset"); 397 | 398 | USERVICE_MASK.with(|uservice_mask| process.arg(&*uservice_mask.borrow())); 399 | process.arg(path).arg(format!("{}", arg)); 400 | 401 | process.stdin(Stdio::null()); 402 | if ! ATTACH_STREAMS.with(|attach_streams| attach_streams.get()) { 403 | if cfg!(debug_assertions) { 404 | process.stdout(Stdio::piped()).stderr(Stdio::piped()); 405 | } else { 406 | process.stdout(Stdio::null()).stderr(Stdio::null()); 407 | } 408 | } 409 | 410 | process 411 | } 412 | -------------------------------------------------------------------------------- /ipc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct smeminternal { 6 | int id; 7 | void *data; 8 | }; 9 | 10 | bool sretrieve(struct smeminternal *region) { 11 | return (region->data = shmat(region->id, NULL, 0)) != (void *) -1; 12 | } 13 | 14 | bool salloc(struct smeminternal *region, size_t size, int flags) { 15 | if((region->id = shmget(0, size, IPC_CREAT | flags)) < 0) 16 | return false; 17 | return sretrieve(region); 18 | } 19 | 20 | void sdealloc(struct smeminternal region) { 21 | shmdt(region.data); 22 | } 23 | -------------------------------------------------------------------------------- /ipc.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | use std::io::Result; 3 | use std::mem::size_of; 4 | use std::ops::Deref; 5 | use std::ops::DerefMut; 6 | use std::os::raw::c_int; 7 | use std::os::raw::c_ulong; 8 | use std::os::raw::c_void; 9 | use std::ptr::null_mut; 10 | 11 | const PERMS: c_int = 0o600; 12 | 13 | pub struct SMem<'a, T: 'a> { 14 | id: u32, 15 | data: &'a mut T, 16 | } 17 | 18 | impl<'a, T> SMem<'a, T> { 19 | pub fn new(val: T) -> Result { 20 | let mut region = SMemInternal { 21 | id: 0, 22 | data: null_mut(), 23 | }; 24 | 25 | if unsafe { salloc(&mut region, size_of::() as c_ulong, PERMS) } { 26 | let mut region = smem_external(region).unwrap(); 27 | 28 | *region = val; 29 | 30 | Ok(region) 31 | } else { 32 | Err(Error::last_os_error()) 33 | } 34 | } 35 | 36 | pub fn from(id: u32) -> Result { 37 | let mut region = SMemInternal { 38 | id: id as i32, 39 | data: null_mut(), 40 | }; 41 | 42 | if unsafe { sretrieve(&mut region) } { 43 | Ok(smem_external(region).unwrap()) 44 | } else { 45 | Err(Error::last_os_error()) 46 | } 47 | } 48 | 49 | pub fn id(&self) -> u32 { 50 | self.id 51 | } 52 | } 53 | 54 | impl<'a, T> Drop for SMem<'a, T> { 55 | fn drop(&mut self) { 56 | unsafe { 57 | sdealloc(SMemInternal { 58 | id: self.id as i32, 59 | data: self.data as *mut T as *mut c_void, 60 | }) 61 | } 62 | } 63 | } 64 | 65 | impl<'a, T> Deref for SMem<'a, T> { 66 | type Target = T; 67 | 68 | fn deref(&self) -> &Self::Target { 69 | self.data 70 | } 71 | } 72 | 73 | impl<'a, T> DerefMut for SMem<'a, T> { 74 | fn deref_mut(&mut self) -> &mut Self::Target { 75 | self.data 76 | } 77 | } 78 | 79 | fn smem_external<'a, T>(internal: SMemInternal) -> Option> { 80 | debug_assert!(internal.id >= 0); 81 | 82 | let rfc = internal.data as *mut T; 83 | let rfc = unsafe { 84 | rfc.as_mut() 85 | }?; 86 | 87 | Some(SMem { 88 | id: internal.id as u32, 89 | data: rfc, 90 | }) 91 | } 92 | 93 | #[repr(C)] 94 | pub struct SMemInternal { 95 | id: c_int, 96 | data: *mut c_void, 97 | } 98 | 99 | #[link(name = "ipc")] 100 | extern "C" { 101 | fn sretrieve(region: *mut SMemInternal) -> bool; 102 | fn salloc(region: *mut SMemInternal, size: c_ulong, flags: c_int) -> bool; 103 | fn sdealloc(region: SMemInternal); 104 | } 105 | -------------------------------------------------------------------------------- /job.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::env::Args; 3 | use std::fmt::Display; 4 | use std::fmt::Error; 5 | use std::fmt::Formatter; 6 | use std::hash::Hash; 7 | use std::hash::Hasher; 8 | use std::ops::Deref; 9 | use std::ops::DerefMut; 10 | 11 | const OBJS_PER_DIR: usize = 10_000; 12 | const WARMUP_TRIALS: usize = 0; // 0 means the number of distinct object files 13 | 14 | #[derive(Clone)] 15 | pub struct FixedCString ([u8; 48]); 16 | 17 | impl FixedCString { 18 | pub fn new() -> Self { 19 | FixedCString ([0; 48]) 20 | } 21 | 22 | pub fn from(content: &str) -> Self { 23 | let mut container = Self::new(); 24 | 25 | let content = content.as_bytes(); 26 | assert!(content.len() + 1 < container.len()); 27 | 28 | for index in 0..content.len() { 29 | container[index] = content[index]; 30 | } 31 | container[content.len()] = b'\0'; 32 | 33 | container 34 | } 35 | } 36 | 37 | impl Deref for FixedCString { 38 | type Target = [u8]; 39 | 40 | fn deref(&self) -> &Self::Target { 41 | &self.0 42 | } 43 | } 44 | 45 | impl DerefMut for FixedCString { 46 | fn deref_mut(&mut self) -> &mut Self::Target { 47 | &mut self.0 48 | } 49 | } 50 | 51 | impl Eq for FixedCString {} 52 | 53 | impl Hash for FixedCString { 54 | fn hash(&self, hasher: &mut T) { 55 | (**self).hash(hasher) 56 | } 57 | } 58 | 59 | impl PartialEq for FixedCString { 60 | fn eq(&self, other: &Self) -> bool { 61 | **self == **other 62 | } 63 | } 64 | 65 | thread_local! { 66 | static WARMUP: Cell = Cell::new(WARMUP_TRIALS); 67 | } 68 | 69 | #[derive(Clone)] 70 | pub struct Job { 71 | pub uservice_path: T, 72 | pub invocation_latency: i64, 73 | pub completion_time: i64, 74 | } 75 | 76 | impl Job { 77 | pub fn new(path: T) -> Self { 78 | Job { 79 | uservice_path: path, 80 | invocation_latency: 0, 81 | completion_time: 0, 82 | } 83 | } 84 | } 85 | 86 | impl Display for Job { 87 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 88 | write!(f, "{} {}", self.invocation_latency as f64 / 1_000.0, self.completion_time as f64 / 1_000.0) 89 | } 90 | } 91 | 92 | pub fn joblist T>(svcnames: F, numobjs: usize, numjobs: usize) -> Box<[Job]> { 93 | let oneshot = |_| Job::new(svcnames("")); 94 | let multishot = |index| Job::new(svcnames(&format!("{}/{}", index / OBJS_PER_DIR, index % OBJS_PER_DIR))); 95 | let fun: &Fn(_) -> Job = match numobjs { 96 | 1 => &oneshot, 97 | _ => &multishot, 98 | }; 99 | 100 | let warmup = WARMUP.with(|warmup| { 101 | let res = warmup.get(); 102 | if res == 0 { 103 | warmup.set(numobjs); 104 | numobjs 105 | } else { 106 | res 107 | } 108 | }); 109 | 110 | let jobs: Vec<_> = (0..numobjs).map(fun).cycle().take(numjobs + warmup).collect(); 111 | 112 | jobs.into_boxed_slice() 113 | } 114 | 115 | pub fn printstats(jobs: &[Job], tput: f64) { 116 | let warmup = WARMUP.with(|warmup| { 117 | warmup.get() 118 | }); 119 | for job in jobs.iter().skip(warmup) { 120 | println!("{}", job); 121 | } 122 | 123 | if tput != 0.0 { 124 | println!("\nThroughput: {}", tput); 125 | } 126 | } 127 | 128 | pub fn args(extra_usage: &str) -> Result<(String, usize, usize, bool, Args), (i32, String)> { 129 | use std::env::args; 130 | 131 | let mut args = args(); 132 | let prog = args.next().unwrap_or(String::from("")); 133 | let usage = format!("USAGE: {} [-s] [ [numtrials{}{}]]", prog, if extra_usage.is_empty() { "" } else { " " }, extra_usage); 134 | 135 | let streams = args.next().ok_or((1, usage.clone()))?; 136 | let svcname; 137 | let streams = if streams == "-s" { 138 | svcname = args.next().ok_or((1, usage.clone()))?; 139 | true 140 | } else { 141 | svcname = streams; 142 | false 143 | }; 144 | 145 | Ok(if let Some(numobjs) = args.next() { 146 | ( 147 | svcname, 148 | numobjs.parse().or((Err((2, String::from(", if provided, must be a nonnegative integer")))))?, 149 | args.next().unwrap_or(numobjs).parse().or(Err((2, String::from("[numtrials], if provided, must be a nonnegative integer"))))?, 150 | streams, 151 | args, 152 | ) 153 | } else { 154 | (svcname, 1, 1, streams, args) 155 | }) 156 | } 157 | -------------------------------------------------------------------------------- /launcher.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod ipc; 3 | mod job; 4 | #[allow(dead_code)] 5 | mod runtime; 6 | mod time; 7 | 8 | use ipc::SMem; 9 | use job::FixedCString; 10 | use job::Job; 11 | use job::args; 12 | use job::joblist; 13 | use job::printstats; 14 | use runtime::LibFun; 15 | use runtime::query_preemption; 16 | use runtime::setup_preemption; 17 | use std::process::exit; 18 | use std::sync::atomic::AtomicBool; 19 | use std::sync::atomic::Ordering; 20 | use time::nsnow; 21 | 22 | fn main() { 23 | let (svcname, numobjs, numjobs, _, _) = args("").unwrap_or_else(|(retcode, errmsg)| { 24 | println!("{}", errmsg); 25 | exit(retcode); 26 | }); 27 | 28 | if let Ok(shmid) = svcname.parse() { 29 | let mut job = SMem::from(shmid).unwrap_or_else(|msg| { 30 | eprintln!("Setting up shared memory: {}", msg); 31 | exit(3); 32 | }); 33 | let (_, Job {completion_time: worker, ..}): (_, Job<_>) = *job; 34 | let quantum = numobjs as i64; 35 | let limit = if numjobs == 0 { i64::max_value() } else { numjobs as i64 * 1_000 }; 36 | 37 | let mut ts = nsnow().unwrap(); 38 | if quantum != 0 { 39 | if let Err(or) = setup_preemption(quantum, limit, &ts) { 40 | eprintln!("Setting up preemption: {}", or); 41 | exit(4); 42 | } 43 | } 44 | let mut preemptions = None; 45 | while preemptions.is_none() { 46 | let &mut (ref mut ready, ref mut job): &mut (AtomicBool, _) = &mut *job; 47 | if ready.load(Ordering::Relaxed) { 48 | invoke(job, worker, &mut ts, false); 49 | *ready.get_mut() = false; 50 | } 51 | 52 | preemptions = query_preemption(); 53 | } 54 | let preemptions = preemptions.unwrap(); 55 | 56 | if preemptions.iter().any(|nonzero| *nonzero != 0) { 57 | for preemption in preemptions { 58 | eprintln!("{}", preemption.abs() as f64 / 1_000.0); 59 | } 60 | } 61 | } else { 62 | if numjobs < numobjs { 63 | println!(" may not be greater than "); 64 | exit(2); 65 | } 66 | 67 | let mut ts = 0; 68 | let mut jobs = joblist(|index| FixedCString::from(&format!("{}{}.so", svcname, index)), numobjs, numjobs); 69 | 70 | for job in &mut *jobs { 71 | invoke(job, 0, &mut ts, true); 72 | } 73 | 74 | printstats(&jobs, 0.0); 75 | } 76 | } 77 | 78 | #[cfg(not(feature = "memoize_loaded"))] 79 | fn invoke(job: &mut Job, arg: i64, ts: &mut i64, ts_before: bool) { 80 | let mut fun = LibFun::new_from_ptr(job.uservice_path.as_ptr() as *const i8).unwrap_or_else(|msg| { 81 | eprintln!("{}", msg); 82 | exit(2); 83 | }); 84 | 85 | call(job, arg, ts, &mut *fun, ts_before); 86 | } 87 | 88 | #[cfg(feature = "memoize_loaded")] 89 | fn invoke(job: &mut Job, arg: i64, ts: &mut i64, ts_before: bool) { 90 | use std::cell::RefCell; 91 | use std::collections::HashMap; 92 | 93 | thread_local! { 94 | static MEMO: RefCell> = RefCell::new(HashMap::new()); 95 | } 96 | 97 | MEMO.with(|memo| { 98 | let mut memo = memo.borrow_mut(); 99 | let fun = memo.entry(job.uservice_path.clone()).or_insert_with(|| LibFun::new_from_ptr(job.uservice_path.as_ptr() as *const i8).unwrap_or_else(|or| { 100 | eprintln!("{}", or); 101 | exit(2); 102 | })); 103 | 104 | call(job, arg, ts, &mut **fun, ts_before); 105 | }); 106 | } 107 | 108 | fn call Option>(job: &mut Job, arg: i64, ts: &mut i64, mut fun: T, ts_before: bool) { 109 | *ts = nsnow().unwrap(); 110 | let ts = if ts_before { 111 | *ts 112 | } else { 113 | 0 114 | }; 115 | if let Some(fin) = fun(arg) { 116 | job.invocation_latency = fin - ts; 117 | } else { 118 | eprintln!("While invoking microservice: child '"); 119 | for each in &*job.uservice_path { 120 | if *each == b'\0' { 121 | break; 122 | } 123 | eprint!("{}", each); 124 | } 125 | eprintln!("' died or was killed"); 126 | } 127 | } 128 | 129 | #[cfg(not(any(feature = "cleanup_loaded", feature = "memoize_loaded", feature = "preserve_loaded")))] 130 | compile_error!("Must select an *_loaded personality via '--feature' or '--cfg feature='!"); 131 | -------------------------------------------------------------------------------- /mkuls: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly DIR="uls" 4 | readonly GROUPING="10000" 5 | 6 | if [ $# -lt 2 -o $# -gt 3 ] 7 | then 8 | echo "USAGE: $0 [subdir]" 9 | exit 1 10 | fi 11 | file="$1" 12 | numcopies="$2" 13 | shift 2 14 | dir="$DIR" 15 | if [ $# -ne 0 ] 16 | then 17 | dir="$1" 18 | shift 19 | fi 20 | 21 | if [ -e "$dir" ] 22 | then 23 | echo "OOPS: Directory '$dir' already exists!" >&2 24 | exit 2 25 | elif ! make "$file" 26 | then 27 | echo "OOPS: Failed to build recipe '$file'!" >&2 28 | exit 3 29 | fi 30 | strip -g "$file" 31 | 32 | suffix="`printf %s "$file" | sed -n 's/.*\(\..\+\)/\1/p'`" 33 | libs="`ldd "$file" | sed -n "s:.*$PWD/./\([^ ]\+\).*:../../\1:p"`" 34 | 35 | mkdir "$dir" 36 | last="$((GROUPING - 1))" 37 | for subdir in `seq 0 $((numcopies / GROUPING)) | sed '$i\last'` 38 | do 39 | if [ "$subdir" = "last" ] 40 | then 41 | last="$((numcopies % GROUPING - 1))" 42 | continue 43 | fi 44 | 45 | mkdir "$dir/$subdir" 46 | for nondir in `seq 0 "$last"` 47 | do 48 | cp "$file" "$dir/$subdir/$nondir$suffix" 49 | done 50 | [ -n "$libs" ] && ln -s $libs "$dir/$subdir" 51 | done 52 | -------------------------------------------------------------------------------- /pgroup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int *wexitstatus(int *wstatus) { 5 | if(!wstatus || !WIFEXITED(*wstatus)) 6 | return NULL; 7 | 8 | *wstatus = WEXITSTATUS(*wstatus); 9 | return wstatus; 10 | } 11 | -------------------------------------------------------------------------------- /pgroup.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::io::Error; 3 | use std::io::Result; 4 | use std::mem::replace; 5 | use std::os::raw::c_int; 6 | use std::panic::PanicInfo; 7 | use std::panic::set_hook; 8 | use std::panic::take_hook; 9 | 10 | const SIGKILL: i32 = 9; 11 | const SIGTERM: i32 = 15; 12 | const WNOHANG: i32 = 1; 13 | 14 | thread_local! { 15 | static DEFAULT_HOOK: RefCell>> = RefCell::new(None); 16 | static HOOK: RefCell> = RefCell::new(Box::new(|| ())); 17 | } 18 | 19 | pub fn exit(code: i32) -> ! { 20 | use std::process::exit; 21 | 22 | HOOK.with(|hook| hook.borrow()()); 23 | exit(code); 24 | } 25 | 26 | pub fn term(id: i32) -> Result<()> { 27 | kill(id, SIGTERM) 28 | } 29 | 30 | fn kill(id: i32, sig: i32) -> Result<()> { 31 | extern "C" { 32 | fn kill(pid: c_int, sig: c_int) -> i32; 33 | } 34 | 35 | if unsafe { 36 | kill(id, sig) 37 | } != 0 { 38 | Err(Error::last_os_error())? 39 | } 40 | 41 | Ok(()) 42 | } 43 | 44 | pub fn kill_at_exit(id: i32) { 45 | HOOK.with(|hook| { 46 | replace(&mut *hook.borrow_mut(), Box::new(move || kill(id, SIGKILL).unwrap_or_else(|err| eprintln!("Failed to kill all child processes: {}", err)))); 47 | }); 48 | 49 | DEFAULT_HOOK.with(|default_hook| { 50 | if default_hook.borrow().is_none() { 51 | replace(&mut *default_hook.borrow_mut(), Some(take_hook())); 52 | } 53 | }); 54 | 55 | set_hook(Box::new(|crash| { 56 | HOOK.with(|hook| hook.borrow_mut()()); 57 | DEFAULT_HOOK.with(|default_hook| default_hook.borrow().as_ref().unwrap()(crash)); 58 | })); 59 | } 60 | 61 | pub fn setpgid(gid: u32) -> Result { 62 | extern "C" { 63 | fn setpgid(pid: c_int, gid: c_int) -> c_int; 64 | } 65 | 66 | let id = unsafe { 67 | setpgid(0, gid as c_int) 68 | }; 69 | 70 | if id != 0 { 71 | Err(Error::last_os_error())? 72 | } 73 | 74 | Ok(id as u32) 75 | } 76 | 77 | pub fn nowait() -> Result> { 78 | #[link(name = "pgroup")] 79 | extern "C" { 80 | fn waitpid(pid: c_int, status: Option<&mut c_int>, opts: c_int) -> c_int; 81 | fn wexitstatus(wstatus: Option<&mut c_int>) -> Option<&mut c_int>; 82 | } 83 | 84 | let mut status = 0; 85 | let pid = unsafe { 86 | waitpid(0, Some(&mut status), WNOHANG) 87 | }; 88 | 89 | if pid == -1 { 90 | Err(Error::last_os_error())? 91 | } 92 | 93 | Ok(if pid != 0 { 94 | unsafe { 95 | wexitstatus(Some(&mut status)) 96 | } 97 | } else { 98 | None 99 | }.map(|status| *status)) 100 | } 101 | -------------------------------------------------------------------------------- /ringbuf.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::ops::DerefMut; 3 | use std::ops::Index; 4 | use std::ops::IndexMut; 5 | 6 | pub struct RingBuffer (Box<[T]>, usize); 7 | 8 | impl RingBuffer { 9 | pub fn new(buffer: Box<[T]>) -> Self { 10 | let len = buffer.len(); 11 | 12 | RingBuffer (buffer, len) 13 | } 14 | 15 | pub fn with_alignment(buffer: Box<[T]>, alignment: usize) -> Self { 16 | assert!(alignment >= buffer.len()); 17 | 18 | RingBuffer (buffer, alignment) 19 | } 20 | } 21 | 22 | impl Deref for RingBuffer { 23 | type Target = [T]; 24 | 25 | fn deref(&self) -> &Self::Target { 26 | &*self.0 27 | } 28 | } 29 | 30 | impl DerefMut for RingBuffer { 31 | fn deref_mut(&mut self) -> &mut Self::Target { 32 | &mut *self.0 33 | } 34 | } 35 | 36 | impl Index for RingBuffer { 37 | type Output = T; 38 | 39 | fn index(&self, index: usize) -> &Self::Output { 40 | &self.0[index % self.1 % self.0.len()] 41 | } 42 | } 43 | 44 | impl IndexMut for RingBuffer { 45 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 46 | let len = self.0.len(); 47 | 48 | &mut self.0[index % self.1 % len] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /runtime.c: -------------------------------------------------------------------------------- 1 | #ifndef RUNTIME_C_ 2 | #define RUNTIME_C_ 3 | 4 | #include "time_utils.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | struct libfunny { 17 | void *lib; 18 | void (*fun)(void); 19 | int *sbox; 20 | }; 21 | 22 | #define BYTES_STACK 4096 23 | 24 | static const char *ENTRY_POINT = "main"; 25 | static const char *STORAGE_LOC = "SBOX"; 26 | 27 | uint8_t stack[BYTES_STACK]; 28 | static struct { 29 | volatile const bool *enforcing; 30 | void (*response)(void); 31 | volatile const long long *checkpoint; 32 | volatile long long limit; 33 | long long quantum; 34 | } preempt_conf; 35 | 36 | static struct { 37 | bool finished; 38 | uint16_t offset; 39 | long long last; 40 | long long overages[1 << 16]; 41 | } preempt_stat; 42 | 43 | static void sigalrm(int signum, siginfo_t *siginfo, void *sigctxt) { 44 | (void) signum; 45 | (void) siginfo; 46 | 47 | int errnot = errno; 48 | long long ts = nsnow(); 49 | errno = errnot; 50 | 51 | preempt_stat.overages[preempt_stat.offset++] = ts - preempt_stat.last - preempt_conf.quantum; 52 | preempt_stat.last = ts; 53 | 54 | if(!*preempt_conf.enforcing) 55 | return; 56 | 57 | if(ts - *preempt_conf.checkpoint >= preempt_conf.limit) { 58 | mcontext_t *ctx = &((ucontext_t *) sigctxt)->uc_mcontext; 59 | long long *rsp = (long long *) ctx->gregs[REG_RSP]; 60 | --*rsp; 61 | *rsp = ctx->gregs[REG_RIP]; 62 | ctx->gregs[REG_RIP] = (long long) preempt_conf.response; 63 | } 64 | } 65 | 66 | static void sigterm(int signum) { 67 | (void) signum; 68 | int errnot = errno; 69 | 70 | sigset_t alrm; 71 | sigemptyset(&alrm); 72 | sigaddset(&alrm, SIGALRM); 73 | sigprocmask(SIG_BLOCK, &alrm, NULL); 74 | 75 | preempt_stat.finished = true; 76 | 77 | errno = errnot; 78 | } 79 | 80 | bool preempt_setup(long quantum, long long limit, volatile const bool *enforcing, volatile const long long *checkpoint, void (*response)(void)) { 81 | preempt_conf.enforcing = enforcing; 82 | preempt_conf.response = response; 83 | preempt_conf.checkpoint = checkpoint; 84 | preempt_conf.limit = limit; 85 | preempt_conf.quantum = quantum * 1000; 86 | 87 | stack_t storage = { 88 | .ss_sp = stack, 89 | .ss_size = BYTES_STACK, 90 | }; 91 | if(sigaltstack(&storage, NULL)) 92 | return false; 93 | 94 | struct sigaction handler = { 95 | .sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART, 96 | .sa_sigaction = sigalrm, 97 | }; 98 | if(sigaction(SIGALRM, &handler, NULL)) 99 | return false; 100 | 101 | preempt_stat.last = nsnow(); 102 | 103 | struct itimerval interval = { 104 | .it_value.tv_usec = quantum, 105 | .it_interval.tv_usec = quantum, 106 | }; 107 | if(setitimer(ITIMER_REAL, &interval, NULL)) 108 | return false; 109 | 110 | handler.sa_flags = 0; 111 | handler.sa_handler = sigterm; 112 | sigaction(SIGTERM, &handler, NULL); 113 | 114 | return true; 115 | } 116 | 117 | const long long *preempt_recent_ns(void) { 118 | return preempt_stat.finished ? preempt_stat.overages : NULL; 119 | } 120 | 121 | const char *dl_load(struct libfunny *exec, const char *sofile, bool preserve) { 122 | exec->lib = dlopen(sofile, RTLD_LAZY | RTLD_LOCAL | (-preserve & RTLD_NODELETE)); 123 | if(!exec->lib) 124 | return dlerror(); 125 | 126 | *(void **) &exec->fun = dlsym(exec->lib, ENTRY_POINT); 127 | if(!exec->fun) 128 | return dlerror(); 129 | 130 | exec->sbox = dlsym(exec->lib, STORAGE_LOC); 131 | if(!exec->sbox) 132 | return dlerror(); 133 | 134 | return NULL; 135 | } 136 | 137 | void dl_unload(struct libfunny exec) { 138 | dlclose(exec.lib); 139 | } 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /runtime.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::ffi::CStr; 3 | use std::ffi::CString; 4 | use std::io::Error; 5 | use std::mem::transmute; 6 | use std::ops::Deref; 7 | use std::ops::DerefMut; 8 | use std::os::raw::c_char; 9 | use std::os::raw::c_long; 10 | use std::os::raw::c_void; 11 | use std::ptr::null; 12 | use std::ptr::null_mut; 13 | use std::panic::catch_unwind; 14 | use std::slice; 15 | 16 | #[cfg(feature = "preserve_loaded")] 17 | const PRESERVE_LOADED_LIBS: bool = true; 18 | #[cfg(not(feature = "preserve_loaded"))] 19 | const PRESERVE_LOADED_LIBS: bool = false; 20 | 21 | thread_local! { 22 | static PREEMPTIBLE: Cell = Cell::new(false); 23 | } 24 | 25 | pub fn setup_preemption(quantum_us: i64, limit_ns: i64, start_time: &i64) -> Result<(), String> { 26 | if unsafe { 27 | preempt_setup(quantum_us, limit_ns, PREEMPTIBLE.with(|preemptible| preemptible.as_ptr()), start_time, unwind as *const c_void) 28 | } { 29 | Ok(()) 30 | } else { 31 | Err(format!("{}", Error::last_os_error())) 32 | } 33 | } 34 | 35 | pub fn query_preemption() -> Option<&'static [i64]> { 36 | let ptr = unsafe { 37 | preempt_recent_ns() 38 | }; 39 | 40 | if ! ptr.is_null() { 41 | Some(unsafe { 42 | slice::from_raw_parts(preempt_recent_ns(), 1 << 16) 43 | }) 44 | } else { 45 | None 46 | } 47 | } 48 | 49 | pub struct LibFun { 50 | lib: *const c_void, 51 | fun: Box Option>, 52 | } 53 | 54 | impl LibFun { 55 | pub fn new(sofile: &CStr) -> Result { 56 | let mut exec = LibFunny { 57 | lib: null(), 58 | fun: null(), 59 | sbox: null_mut(), 60 | }; 61 | 62 | let errmsg = unsafe { 63 | dl_load(&mut exec, sofile.as_ptr(), PRESERVE_LOADED_LIBS) 64 | }; 65 | 66 | if errmsg.is_null() { 67 | debug_assert!(! exec.lib.is_null()); 68 | debug_assert!(! exec.fun.is_null()); 69 | 70 | let fun: fn() = unsafe { 71 | transmute(exec.fun) 72 | }; 73 | let sbox = unsafe { 74 | exec.sbox.as_mut() 75 | }.expect("Library has no static storage space!"); 76 | 77 | Ok(LibFun { 78 | lib: exec.lib, 79 | fun: Box::new(move |sboxed| { 80 | *sbox = sboxed; 81 | 82 | PREEMPTIBLE.with(|preemptible| { 83 | preemptible.set(true); 84 | let success = catch_unwind(fun); 85 | preemptible.set(false); 86 | 87 | success 88 | }).ok()?; 89 | 90 | Some(*sbox) 91 | }), 92 | }) 93 | } else { 94 | let msg = unsafe { 95 | CStr::from_ptr(errmsg) 96 | }; 97 | let msg = msg.to_str().map_err(|or| format!("{}", or))?; 98 | 99 | Err(String::from(msg)) 100 | } 101 | } 102 | 103 | pub fn new_from_str(libname: &str) -> Result { 104 | let sofile = format!("./lib{}.so", libname); 105 | let sofile = CString::new(&*sofile).map_err(|or| format!("{}", or))?; 106 | 107 | Self::new(&sofile) 108 | } 109 | 110 | pub fn new_from_ptr(libname: *const c_char) -> Result { 111 | Self::new(unsafe { 112 | CStr::from_ptr(libname) 113 | }) 114 | } 115 | } 116 | 117 | impl Drop for LibFun { 118 | fn drop(&mut self) { 119 | unsafe { 120 | dl_unload(LibFunny { 121 | lib: self.lib, 122 | fun: null(), 123 | sbox: null_mut(), 124 | }); 125 | } 126 | } 127 | } 128 | 129 | impl Deref for LibFun { 130 | type Target = FnMut(i64) -> Option; 131 | 132 | fn deref(&self) -> &Self::Target { 133 | &*self.fun 134 | } 135 | } 136 | 137 | impl DerefMut for LibFun { 138 | fn deref_mut(&mut self) -> &mut Self::Target { 139 | &mut *self.fun 140 | } 141 | } 142 | 143 | #[repr(C)] 144 | struct LibFunny { 145 | lib: *const c_void, 146 | fun: *const c_void, 147 | sbox: *mut c_long, 148 | } 149 | 150 | #[link(name = "runtime")] 151 | extern "C" { 152 | fn preempt_setup(quantum_us: c_long, limit_ns: c_long, enforcing: *const bool, checkpoint: *const c_long, punishment: *const c_void) -> bool; 153 | fn preempt_recent_ns() -> *const c_long; 154 | 155 | fn dl_load(exec: *mut LibFunny, sofile: *const c_char, preserve: bool) -> *const c_char; 156 | fn dl_unload(exec: LibFunny); 157 | } 158 | 159 | extern "C" fn unwind() { 160 | panic!(); 161 | } 162 | -------------------------------------------------------------------------------- /shasum.rs: -------------------------------------------------------------------------------- 1 | extern crate hasher; 2 | 3 | fn main() { 4 | use hasher::main; 5 | 6 | main(); 7 | } 8 | -------------------------------------------------------------------------------- /signaling: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -ne 1 ] 4 | then 5 | echo "USAGE: $0 " 6 | exit 1 7 | fi 8 | 9 | exec ./host -s ./libsleep 1 1 0x10 "$1" 2>&1 10 | -------------------------------------------------------------------------------- /sleep.rs: -------------------------------------------------------------------------------- 1 | extern crate spc; 2 | 3 | mod time; 4 | 5 | use spc::sbox; 6 | use time::nsnow; 7 | 8 | const DURATION_NS: i64 = 1_000_000_000; 9 | 10 | #[no_mangle] 11 | pub fn main() { 12 | *sbox() = nsnow().unwrap(); 13 | while nsnow().unwrap() - *sbox() < DURATION_NS {} 14 | } 15 | -------------------------------------------------------------------------------- /spc.rs: -------------------------------------------------------------------------------- 1 | #[link_section = ".data"] 2 | #[no_mangle] 3 | pub static mut SBOX: i64 = 0; 4 | 5 | pub fn sbox() -> &'static mut i64 { 6 | unsafe { 7 | &mut SBOX 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /stats: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -lt 2 ] 4 | then 5 | echo "USAGE: $0 [args]..." 6 | exit 1 7 | fi 8 | 9 | printf %s "Command:" 10 | printf " %s" "$0" "$@" 11 | echo 12 | printf %s "Revision: " 13 | git log --oneline --abbrev-commit --no-decorate -1 14 | printf %s "Kernel: " 15 | uname -a 16 | 17 | iters="$1" 18 | shift 19 | 20 | outputs="" 21 | for iter in `seq "$iters"` 22 | do 23 | thisput="`"$@"`" 24 | code="$?" 25 | if [ "$code" -ne "0" ] 26 | then 27 | exit "$code" 28 | fi 29 | outputs="`cat <<-tuptou 30 | $outputs 31 | $thisput 32 | tuptou`" 33 | done 34 | printf "%s\n" "$outputs" 35 | 36 | . ./experiments.sh 37 | 38 | removeline="`stats_first_line - <<-enilevomer 39 | $outputs 40 | enilevomer`" 41 | outputs="`cat <<-tuptous | tail -n+2 42 | $outputs 43 | tuptous`" 44 | if [ "$removeline" -gt "1" ] 45 | then 46 | outputs="`cat <<-tuptous | sed "$((removeline - 2))q" 47 | $outputs 48 | tuptous`" 49 | fi 50 | noutput="`cat <<-tuptoun | wc -l 51 | $outputs 52 | tuptoun`" 53 | stats="`cat <<-stats | head -n1 | sed 's/[^ ]\+//g' | wc -c 54 | $outputs 55 | stats`" 56 | 57 | for field in `seq "$stats"` 58 | do 59 | output="`cat <<-tuptou | cut -d" " -f"$field" | sort -n 60 | $outputs 61 | tuptou`" 62 | 63 | total="`cat <<-naem | paste -sd+ | bc -l 64 | $output 65 | latot`" 66 | mean="`printf "%s/%s\n" "$total" "$noutput" | bc -l`" 67 | 68 | echo 69 | printf "%s\n" "Count: $noutput" 70 | printf "%s\n" "Sum: $total" 71 | printf %s "Min: " 72 | cat <<-tac | head -n1 73 | $output 74 | tac 75 | printf %s "Max: " 76 | cat <<-tac | tail -n1 77 | $output 78 | tac 79 | printf "%s\n" "Mean: $mean" 80 | printf %s "Median: " 81 | cat <<-tac | tail -n+$((noutput / 2)) | head -n1 82 | $output 83 | tac 84 | printf %s "Standard deviation: " 85 | case "$mean" in 86 | -*) 87 | printf -- "%s\n" -1 88 | ;; 89 | *) 90 | cat <<-tac | sed "s:.*:(&-$mean)^2:" | paste -sd+ | sed "s:.*:sqrt((&)/$noutput):" | bc -l 91 | $output 92 | tac 93 | ;; 94 | esac 95 | printf %s "95% tail: " 96 | cat <<-tac | tail -n+$((noutput * 95 / 100)) | head -n1 97 | $output 98 | tac 99 | printf %s "99% tail: " 100 | cat <<-tac | tail -n+$((noutput * 99 / 100)) | head -n1 101 | $output 102 | tac 103 | printf %s "99.9% tail: " 104 | cat <<-tac | tail -n+$((noutput * 999 / 1000)) | head -n1 105 | $output 106 | tac 107 | done 108 | -------------------------------------------------------------------------------- /test.rs: -------------------------------------------------------------------------------- 1 | extern crate bytes; 2 | #[cfg(not(feature = "no_mangle_main"))] 3 | extern crate ipc; 4 | #[cfg(feature = "no_mangle_main")] 5 | extern crate spc; 6 | 7 | mod time; 8 | 9 | use bytes::Bytes; 10 | #[cfg(not(feature = "no_mangle_main"))] 11 | use ipc::SMem; 12 | use std::net::UdpSocket; 13 | use std::process::exit; 14 | use time::nsnow; 15 | 16 | enum Argument { 17 | MutableReference((T, Option)), 18 | #[cfg_attr(feature = "no_mangle_main", allow(dead_code))] 19 | UnparseableString(String), 20 | } 21 | 22 | #[cfg_attr(feature = "no_mangle_main", allow(unused_mut))] 23 | #[cfg_attr(feature = "no_mangle_main", no_mangle)] 24 | pub fn main() { 25 | match sbox() { 26 | Argument::MutableReference((mut loc, status)) => { 27 | *loc = nsnow().unwrap(); 28 | 29 | if let Some(status) = status { 30 | exit(status); 31 | } 32 | }, 33 | Argument::UnparseableString(addrs) => { 34 | let mut addrs = addrs.split_whitespace(); 35 | let them = addrs.next().unwrap(); 36 | let me = addrs.next().unwrap(); 37 | let index = addrs.next().unwrap(); 38 | let mut index: usize = index.parse().unwrap(); 39 | 40 | let socket = UdpSocket::bind(&me).unwrap(); 41 | socket.send_to(index.bytes(), &them).unwrap(); 42 | 43 | loop { 44 | let mut index = 0usize; 45 | socket.recv(index.bytes()).unwrap(); 46 | 47 | let mut mess = (index, nsnow().unwrap()); 48 | socket.send_to(mess.bytes(), &them).unwrap(); 49 | } 50 | }, 51 | } 52 | } 53 | 54 | #[cfg(not(feature = "no_mangle_main"))] 55 | fn sbox<'a>() -> Argument> { 56 | use std::env::args; 57 | 58 | let mut args = args(); 59 | let s = args.by_ref().skip(1).next().unwrap(); 60 | 61 | if let Ok(u) = s.parse() { 62 | Argument::MutableReference((SMem::from(u).unwrap(), args.next().map(|it| it.parse().unwrap()))) 63 | } else { 64 | Argument::UnparseableString(s) 65 | } 66 | } 67 | 68 | #[cfg(feature = "no_mangle_main")] 69 | fn sbox() -> Argument<&'static mut i64> { 70 | use spc::sbox; 71 | 72 | Argument::MutableReference((sbox(), None)) 73 | } 74 | -------------------------------------------------------------------------------- /time.rs: -------------------------------------------------------------------------------- 1 | use std::time::UNIX_EPOCH; 2 | use std::time::SystemTime; 3 | use std::time::SystemTimeError; 4 | 5 | pub fn nsnow() -> Result { 6 | let time = SystemTime::now().duration_since(UNIX_EPOCH)?; 7 | 8 | Ok((time.as_secs() * 1_000_000_000 + time.subsec_nanos() as u64) as i64) 9 | } 10 | -------------------------------------------------------------------------------- /time_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef TIME_UTILS_H_ 2 | #define TIME_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | static inline long long ns(const struct timespec *split) { 8 | return split->tv_sec * 1000000000 + split->tv_nsec; 9 | } 10 | 11 | static inline long long nsnow(void) { 12 | struct timespec stamp; 13 | int failure = clock_gettime(CLOCK_REALTIME, &stamp); 14 | assert(!failure); 15 | (void) failure; 16 | return ns(&stamp); 17 | } 18 | 19 | static inline long long nscpu(void) { 20 | struct timespec stamp; 21 | int failure = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &stamp); 22 | assert(!failure); 23 | (void) failure; 24 | return ns(&stamp); 25 | } 26 | 27 | #endif 28 | --------------------------------------------------------------------------------