diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 5969333..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** - -**To Reproduce** - -**Expected behavior** - -**Desktop (please complete the following information):** - - Distro: - - Linux version: - - gcc version: - - Rust version: - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 0086358..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md deleted file mode 100644 index 48d5f81..0000000 --- a/.github/ISSUE_TEMPLATE/custom.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Custom issue template -about: Describe this issue template's purpose here. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 68f8728..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,6 +0,0 @@ -Fixes # - -## Proposed Changes - - - - - - diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml deleted file mode 100644 index 54f57dc..0000000 --- a/.github/workflows/build_all.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Build full project -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -env: - CARGO_TERM_COLOR: always -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: preparation - run: | - rustup update stable - rustup default stable - npm i -g yarn - - name: build - run: make - diff --git a/.github/workflows/c.yml b/.github/workflows/c.yml new file mode 100644 index 0000000..a21a557 --- /dev/null +++ b/.github/workflows/c.yml @@ -0,0 +1,19 @@ +name: Everything C +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + static_analysis: + name: Run gcc with warnings + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Dependencies + run: sudo apt-get install gcc cppcheck + - name: Compile Library with Warnings + run: gcc -Wall -Wextra -Wpedantic -Werror -std=c11 -c -o /dev/null ./blackheap-benchmarker/src/c_code/benchmarker_internal.c + - name: Run cppcheck + # This is everything but `unusedFunction` enabled + run: cppcheck --enable=warning,style,performance,portability,information,missingInclude --error-exitcode=1 ./blackheap-benchmarker/src/c_code/benchmarker.c diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ec7e272..90d3857 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -7,21 +7,69 @@ on: env: CARGO_TERM_COLOR: always jobs: - build: + build_works: + name: Build Works runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - run: | - rustup update - rustup component add clippy - rustup component add rustfmt - - name: Check - working-directory: ./blackheap-modeller - run: cargo check - - name: Run clippy - working-directory: ./blackheap-modeller - run: cargo clippy -- -Dwarnings - - name: Run Fmt - working-directory: ./blackheap-modeller - run: cargo fmt --check + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: run cargo build --release + run: | + cargo build --release + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: run cargo test + run: | + cargo test + fmt: + name: fmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: run cargo fmt --check + run: | + cargo fmt --check + clippy: + name: clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: run cargo clippy + run: | + cargo clippy + doc: + name: Doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: run cargo doc + run: | + cargo doc diff --git a/.gitignore b/.gitignore index f8ae934..2ee01cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,15 @@ -.vscode -blackheap +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb -# If you just use blackheap locally, please don't commit -/**/default-model diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..29d4ec7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] + +members = [ + "blackheap", + "blackheap-benchmarker" +] + +resolver = "2" diff --git a/LICENSE b/LICENSE index baebc1b..36d2316 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,7 @@ -Copyright (c) 2022 Lars Quentin +Copyright (c) 2022-2024 Lars Quentin -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index f2b2e4e..0000000 --- a/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -all: clean build-rust - -run: build-rust - ./blackheap - -clean: - rm -f ./blackheap-modeller/assets/blackheap-benchmark.exe - rm -f ./blackheap-modeller/assets/index.html - rm -f blackheap - rm -rf ./blackheap-benchmark/build - rm -rf ./blackheap-frontend/{build,node_modules} - - -build-rust: build-react build-c - cd blackheap-modeller && \ - cargo build --release && \ - cd .. && \ - cp ./blackheap-modeller/target/release/blackheap . - -build-react: - cd blackheap-frontend && \ - yarn && \ - yarn build && \ - cd .. && \ - cp ./blackheap-frontend/build/index.html ./blackheap-modeller/assets - -build-c: - cd blackheap-benchmark && \ - make build && \ - cd .. && \ - cp ./blackheap-benchmark/build/blackheap-benchmark.exe ./blackheap-modeller/assets - -.phony: all clean build-rust build-react build-c diff --git a/README.md b/README.md index c569f27..aac525a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,65 @@ -# RECODE IN PROGRESS SEE BRANCH ETA MID 2024 +# Blackheap -![Blackheap Logo](./icon.png) +# BIG RECODE, `old-v0` is the old branch This tool creates a performance model of your I/O speed and further allows to predict future preformance. -# [Link to the Documentation](https://lquenti.github.io/blackheap/book/) +## Note + +There is a **big recode** right now. But how does one eat an elephant... + +## Progress +- [x] Start new repo +- [ ] Requirements Engineering + - [x] Find out how to formalize what the code should do + - fuck formalization, we use pseudocode + - [ ] Look what the code did before by looking through... + - [ ] + - [ ] + - [ ] `blackheap-benchmark` + - [ ] `blackheap-modeller` + - [ ] `blackheap-frontend` + - [ ] `preloadee` + - [ ] All open issues + - [ ] All closed isses + - Unstructured ideas: + - Has to be resumeable + - be able to re-analyze raw data again + - Provide machine generated README in the data + - `blackheap-benchmarker` does not rely on bindgen compile time since this would create LLVM as dep + - [ ] Also provide a standalone binary for the benchmarker? + - [ ] Find a way to have C linting (all warnings, formatter, pedantic C standard) +- [ ] Design a high level architecture based on the requirements +- [x] Finish the benchmarker +- [ ] Finish the Rust Code + +- [ ] Try to move stuff out of the `bin` into the `lib` crates + +## Issues for when the recode is done +- Add `O_DIRECT` support + +## Architecture +Cargo workspace with the following crates +``` +- blackheap-core (lib): stuff all other libraries need (like Definitions) +- blackheap-benchmarker (lib): C code with Rust wrapper +- blackheap-analyzer (lib): Analysis of the benchmarks +- blackheap (bin): The user facing code +``` + +High level workflow: +``` +- user starts blackheap with config parameters +- blackheap checks which benchmarks are already done + - If folder doesnt exist / was not provided, do all benchmarks +- based on those benchmarks, do the analysis +- Create (human-readable) plots +- Create (machine-readable) parsing data +``` + +Benchmarker: +``` +- get input from Rust struct +- give output in Rust struct +- Rust part should support a to json function for persistence +``` diff --git a/blackheap-benchmark/.gitignore b/blackheap-benchmark/.gitignore deleted file mode 100644 index 4a106c8..0000000 --- a/blackheap-benchmark/.gitignore +++ /dev/null @@ -1,81 +0,0 @@ -build -# Created by https://www.toptal.com/developers/gitignore/api/c,vim -# Edit at https://www.toptal.com/developers/gitignore?templates=c,vim - -### C ### -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -### Vim ### -# Swap -[._]*.s[a-v][a-z] -!*.svg # comment out if you don't need vector files -[._]*.sw[a-p] -[._]s[a-rt-v][a-z] -[._]ss[a-gi-z] -[._]sw[a-p] - -# Session -Session.vim -Sessionx.vim - -# Temporary -.netrwhist -*~ -# Auto-generated tag files -tags -# Persistent undo -[._]*.un~ - -# End of https://www.toptal.com/developers/gitignore/api/c,vim - diff --git a/blackheap-benchmark/Makefile b/blackheap-benchmark/Makefile deleted file mode 100644 index d32d086..0000000 --- a/blackheap-benchmark/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -all: clean make-build-folder build show-help - -clean: - rm -rf build - -make-build-folder: clean - mkdir build - -build: make-build-folder - gcc -Wall -Wextra -Wpedantic -std=gnu11 -O2 -D_GNU_SOURCE -o ./build/blackheap-benchmark.exe ./src/*.c -lrt - -show-help: build - ./build/blackheap-benchmark.exe --help - -example: build - ./build/blackheap-benchmark.exe --read --file=/dev/shm/example_benchmark.dat --mem-pattern=seq --file-pattern=seq --repeats=5 --mem-buf=5242880 --file-buf=67108864 --access-size=1048576 - -special-example: build - ./build/blackheap-benchmark.exe --read --file=/dev/zero --mem-pattern=seq --file-pattern=rnd --repeats=5 --mem-buf=5242880 --file-buf=67108864 --access-size=1048576 - -.PHONY: all clean make-build-folder build show-help diff --git a/blackheap-benchmark/README.md b/blackheap-benchmark/README.md deleted file mode 100644 index fa7d3b1..0000000 --- a/blackheap-benchmark/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Yet another file I/O Benchmarker - -## Technical Decisions -- `make` and `gcc` for compilation -- For IDE support: `compile_commands.json` generated via [bear](https://github.com/rizsotto/Bear) in order to support LSP-Servers like [clangd](https://clangd.llvm.org/). -- Program depends on glibc for argument parsing -- The program uses [SemVer](https://semver.org/) - -## License -2021-2022 MIT Lars Quentin diff --git a/blackheap-benchmark/src/arg_parser.c b/blackheap-benchmark/src/arg_parser.c deleted file mode 100644 index 3690ac4..0000000 --- a/blackheap-benchmark/src/arg_parser.c +++ /dev/null @@ -1,334 +0,0 @@ -// -// Created by lquenti on 02.12.21. -// - - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include"arg_parser.h" - -/* argp uses cool default indexing when one does not define some values. - * Thus, this is okay (and intended) in this file. - */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" - - -const char *DEFAULT_FILENAME = "blackheap_benchmark_test_file.dat"; -const char *DEFAULT_FILEPATH = "/tmp/blackheap_benchmark_test_file.dat"; -const char *argp_program_bug_address = "https://github.com/lquenti/blackheap/issues"; -const char *argp_program_version = "0.1.0"; - - -static inline access_pattern_t parse_access_pattern(char *p, struct argp_state *state) -{ - to_lower_str(p); - if (strcmp(p, "off0") == 0) - { - return ACCESS_PATTERN_CONST; - } - else if (strcmp(p, "seq") == 0) - { - return ACCESS_PATTERN_SEQUENTIAL; - } - else if (strcmp(p, "rnd") == 0) - { - return ACCESS_PATTERN_RANDOM; - } - argp_error(state, "Unknown access pattern '%s'\n", p); - exit(1); -} - -static inline void get_size_t_or_fail(const char *str, size_t *out, struct argp_state *state) -{ - int ret = sscanf(str, "%zu", out); - if (ret == EOF) - { - argp_error(state, "Could not read parameters; EOF encountered while parsing number '%s' with error '%s'.\n", - str, - strerror(errno)); - } - else if (ret == 0) - { - argp_error(state, "Could not read parameters; Could not convert number '%s'\n", str); - } -} - -/* This function does 2 things: - * 1. We have to check whether we have a normal file or not. - * If we do not have a normal file, we probably can't just - * write into it to change it's size. - * By normal files, I mean things like: - * - Character special devices - * - Block special devices - * - Pipes - * See: https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html - * 2. If they, by mistake or not, set the filepath to a directory, we create a file into - * that directory. - */ -static bool is_normal_file(char **path_ptr) -{ - struct stat st; - int res = stat(*path_ptr, &st); - - /* if it failed, it either does not exist or it REALLY failed. */ - if (res == -1) - { - if (errno != ENOENT) - { - fprintf(stderr, "ERROR: %s is an invalid path. Please check permissions.\n", *path_ptr); - exit(1); - } - /* So it was just non-existing */ - int fd = open_or_die(*path_ptr, O_CREAT | O_WRONLY, 0644); - close_or_die(fd); - /* We know it's a file, thus we are good */ - return true; - } - - /* The stat request worked. If it is a file, we are good */ - if (S_ISREG(st.st_mode)) - { - return true; - } - /* If it is a directory, let's create a file within it */ - if (S_ISDIR(st.st_mode)) - { - /* I know, I know this is formally a memory leak - * but I couldn't be less bothered because - * argvs are not freed anyways - */ - char *buffer = malloc(sizeof(char) * 128); - strcpy(buffer, *path_ptr); - strcat(buffer, "/"); - strcat(buffer, DEFAULT_FILENAME); - int fd = open_or_die(buffer, O_CREAT | O_WRONLY, 0644); - close_or_die(fd); - /* Here we are changing it to a file. - * So it looks like we never had a wrong input :) - */ - *path_ptr = buffer; - return true; - } - /* It exists and is neither a file nor a directory, we have to be careful. */ - return false; -} - -static inline void check_argument_numbers(const benchmark_config_parser_t *config, struct argp_state *state) -{ - if (config->number_of_missing_arugments != 0) - argp_error(state, "Wrong number of arguments:\n"); -} -static inline void check_read_and_write(const benchmark_config_parser_t *config, struct argp_state *state) -{ - if (config->read_was_selected && config->write_was_selected) - argp_error(state, "Both --read and --write were selected"); - if (!(config->read_was_selected || config->write_was_selected)) - argp_error(state, "Neither --read and --write were selected"); -} -/** - * Both strategies have the goal that the vfs page cache is bypassed. - * Thus, it doesn't make sense if both are activated. - */ -static inline void check_both_memory_restriction_were_set(const benchmark_config_parser_t *config, struct argp_state *state) { - if (config->o_direct_was_selected && config->free_ram_if_selected != 0) - argp_error(state, "Selecting both --free-ram and --o-direct will screw the results"); -} -static inline void check_access_size_larger_mem_buf(const benchmark_config_parser_t *config, struct argp_state *state) -{ - if (config->access_size_in_bytes > config->memory_buffer_in_bytes) - argp_error(state, "The access size of a single request can't be larger than than the whole memory buffer"); -} -static inline void check_access_size_larger_file_buf(const benchmark_config_parser_t *config, struct argp_state *state) -{ - if (config->access_size_in_bytes > config->file_size_in_bytes) - argp_error(state, "The access size of a single request can't be larger than than the whole file size"); -} - -/** Check whether we can sequentially read without wrapping. - * - * The big advantage of sequential reads (compared to let's say random ones) is that the - * kernel can prefetch accordingly. - * - * Let's say that we have n tests. The access size of each I/O read is b Bytes. - * Let's now say that either the file size is smaller than n*b. Then we have to - * start at the beginning, thus creating a spike. - * - * Storage is not scarce anymore, especially in our access sizes. - */ -static inline void check_needs_to_wrap(const benchmark_config_parser_t *config, struct argp_state *state) -{ - size_t size_needed = config->access_size_in_bytes * config->number_of_io_op_tests; - bool use_prefetching = config->access_pattern_in_file == ACCESS_PATTERN_SEQUENTIAL; - if (use_prefetching && size_needed > config->file_size_in_bytes) - argp_error(state, "When reading sequentially, we need a file buffer size of at least access_size * number_of_tests bytes"); -} - -static inline void check_does_not_try_to_delete_special_file(const benchmark_config_parser_t *config, struct argp_state *state) -{ - if ((!config->prepare_file_size) && config->delete_afterwards_was_selected) - argp_error(state, "You can't delete a special file like '%s'\n--delete-afterwards is invalid here", - config->filepath); -} - -static error_t parse_opt(int key, char *arg, struct argp_state *state) -{ - benchmark_config_parser_t *config = state->input; - - switch (key) - { - case 'r': - config->is_read_operation = true; - config->read_was_selected = true; - break; - case 'w': - config->is_read_operation = false; - config->write_was_selected = true; - break; - case ID_MEM_PATTERN: - config->access_pattern_in_file = parse_access_pattern(arg, state); - config->number_of_missing_arugments--; - break; - case ID_FILE_PATTERN: - config->access_pattern_in_memory = parse_access_pattern(arg, state); - config->number_of_missing_arugments--; - break; - case ID_FILEPATH: - config->filepath = arg; - config->prepare_file_size = is_normal_file(&config->filepath); - break; - case ID_REPEATS: - get_size_t_or_fail(arg, &config->number_of_io_op_tests, state); - config->number_of_missing_arugments--; - break; - case ID_MEM_SIZE: - get_size_t_or_fail(arg, &config->memory_buffer_in_bytes, state); - config->number_of_missing_arugments--; - break; - case ID_FILE_SIZE: - get_size_t_or_fail(arg, &config->file_size_in_bytes, state); - config->number_of_missing_arugments--; - break; - case ID_ACCESS_SIZE: - get_size_t_or_fail(arg, &config->access_size_in_bytes, state); - config->number_of_missing_arugments--; - break; - case ID_RESTRICT_FREE_RAM: - get_size_t_or_fail(arg, &config->free_ram_if_selected, state); - break; - case ID_USE_O_DIRECT: - config->o_direct_was_selected = true; - break; - case ID_DROP_CACHE: - config->drop_cache_was_selected = true; - break; - case ID_DO_REREAD: - config->reread_was_selected = true; - break; - case ID_DELETE_AFTERWARDS: - config->delete_afterwards_was_selected = true; - break; - case ARGP_KEY_END: - check_argument_numbers(config, state); - check_read_and_write(config, state); - check_both_memory_restriction_were_set(config, state); - check_access_size_larger_mem_buf(config, state); - check_access_size_larger_file_buf(config, state); - check_needs_to_wrap(config, state); - check_does_not_try_to_delete_special_file(config, state); - break; - } - return 0; -} - -static struct argp_option options[] = { - /* Define a group for all io_benchmark options in case someone wants to embed them. */ - {0, 0, 0, 0, "Benchmark Options:", GROUP_BENCHMARK_OPTIONS}, - - /* Read and write are mutually exclusive. */ - {0, 0, 0, 0, "Possible Benchmark Operations:", GROUP_BENCHMARK_OPERATIONS}, - {"read", 'r', 0, 0, "Benchmark a read operation."}, - {"write", 'w', 0, 0, "Benchmark a write operation."}, - - /* File Patterns */ - {0, 0, 0, 0, "Access Patterns:\nPossible MODES: off0,seq,rnd", GROUP_ACCESS_PATTERNS}, - {"mem-pattern", ID_MEM_PATTERN, "MODE", 0, "Pattern to access the memory buffer which interacts with the file."}, - {"file-pattern", ID_FILE_PATTERN, "MODE", 0, "Pattern to access the benchmarked file."}, - - /* Other Benchmark Parameters */ - {0, 0, 0, 0, "Other Parameters:", GROUP_OTHER_PARAMETERS}, - {"file", ID_FILEPATH, "PATH", OPTION_ARG_OPTIONAL, "Path to directory/file from where to benchmark.\nDefault: /tmp/"}, - {"repeats", ID_REPEATS, "N", 0, "Number of repititions for selected I/O operation."}, - /* Besides actual BYTES-Numbers we also allow KiB, MiB... */ - {"mem-buf", ID_MEM_SIZE, "BYTES", 0, "Size of the memory buffer to read from/write to."}, - {"file-buf", ID_FILE_SIZE, "BYTES", 0, "Size of the file to read from/write to."}, - {"access-size", ID_ACCESS_SIZE, "BYTES", 0, "Size of a single I/O operation."}, - - /* Different Toggles */ - {0, 0, 0, 0, "Mutually exclusive Toggles:", GROUP_TOGGLES}, - {"free-ram", ID_RESTRICT_FREE_RAM, "BYTES", OPTION_ARG_OPTIONAL, "Restrict the RAM to n available byte to force cache eviction."}, - {"o-direct", ID_USE_O_DIRECT, 0, OPTION_ARG_OPTIONAL, "Use O_DIRECT to bypass the VFS page cache"}, - {"drop-cache", ID_DROP_CACHE, 0, OPTION_ARG_OPTIONAL, "Before doing anything, ask Linux to drop the page cache"}, - {"reread", ID_DO_REREAD, 0, OPTION_ARG_OPTIONAL, "Read every block twice, just benchmark the second time."}, - {"delete-afterwards", ID_DELETE_AFTERWARDS, 0, OPTION_ARG_OPTIONAL, "Delete benchmarking file afterwards"}, - - /* -1 is the default group. Let's give it a name. */ - {0, 0, 0, 0, "Miscellaneous:", -1}, - {NULL}}; - -static struct argp argp = { - options, - parse_opt, - "--read|--write --file-pattern=MODE --mem-pattern=MODE --file=[PATH] --repeats=N --mem-buf=BYTES " - "--file-buf=BYTES --access-size=BYTES [--free-ram=BYTES] [--o-direct] [--drop-cache] [--reread] [--delete-afterwards]", - "Note: read or write are mutually exclusive\v" - "The file given will be overwritten, be careful when specifying it."}; - -void parse_benchmark_arguments(int argc, char **argv, benchmark_config_t *out) -{ - benchmark_config_parser_t parser = { - /* Default Initialization */ - .filepath = (char *)DEFAULT_FILEPATH, - .prepare_file_size = true, - .read_was_selected = false, - .write_was_selected = false, - .free_ram_if_selected = 0, - .o_direct_was_selected = false, - .drop_cache_was_selected = false, - .reread_was_selected = false, - .delete_afterwards_was_selected = false, - - /* |{pattern1,pattern2,repeats,mem_buf,file_buf,accss_size}| = 6 */ - .number_of_missing_arugments = 6, - }; - argp_parse(&argp, argc, argv, 0, 0, &parser); - - /* Extract config values */ - benchmark_config_t config = { - .filepath = parser.filepath, - .memory_buffer_in_bytes = parser.memory_buffer_in_bytes, - .file_size_in_bytes = parser.file_size_in_bytes, - .access_size_in_bytes = parser.access_size_in_bytes, - .number_of_io_op_tests = parser.number_of_io_op_tests, - .access_pattern_in_memory = parser.access_pattern_in_memory, - .access_pattern_in_file = parser.access_pattern_in_file, - .is_read_operation = parser.is_read_operation, - .prepare_file_size = parser.prepare_file_size, - .use_o_direct = parser.o_direct_was_selected, - .restrict_free_ram_to = parser.free_ram_if_selected, - .drop_cache_first = parser.drop_cache_was_selected, - .do_reread = parser.reread_was_selected, - .delete_afterwards = parser.delete_afterwards_was_selected, - }; - memcpy(out, &config, sizeof(benchmark_config_t)); -} - -#pragma GCC diagnostic pop diff --git a/blackheap-benchmark/src/arg_parser.h b/blackheap-benchmark/src/arg_parser.h deleted file mode 100644 index 50a28d6..0000000 --- a/blackheap-benchmark/src/arg_parser.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef IO_BENCHMARK_ARG_PARSER_H -#define IO_BENCHMARK_ARG_PARSER_H - -#include "io_benchmark.h" -#include "helper.h" - -typedef struct benchmark_config_parser_t -{ - char *filepath; - size_t memory_buffer_in_bytes; - size_t file_size_in_bytes; - size_t access_size_in_bytes; - size_t number_of_io_op_tests; - size_t available_bytes; - access_pattern_t access_pattern_in_memory; - access_pattern_t access_pattern_in_file; - bool is_read_operation; - bool prepare_file_size; - bool read_was_selected; - bool write_was_selected; - size_t free_ram_if_selected; - bool o_direct_was_selected; - bool drop_cache_was_selected; - bool reread_was_selected; - bool delete_afterwards_was_selected; - size_t number_of_missing_arugments; -} benchmark_config_parser_t; - -typedef enum argp_index_values_t -{ - GROUP_BENCHMARK_OPTIONS = 2000, - - GROUP_BENCHMARK_OPERATIONS = 2100, - - GROUP_ACCESS_PATTERNS = 2200, - ID_MEM_PATTERN = 2201, - ID_FILE_PATTERN = 2202, - - GROUP_OTHER_PARAMETERS = 2300, - ID_FILEPATH = 2301, - ID_REPEATS = 2302, - ID_MEM_SIZE = 2303, - ID_FILE_SIZE = 2304, - ID_ACCESS_SIZE = 2305, - - GROUP_TOGGLES = 2400, - ID_RESTRICT_FREE_RAM = 2401, - ID_USE_O_DIRECT = 2402, - ID_DROP_CACHE = 2403, - ID_DO_REREAD = 2404, - ID_DELETE_AFTERWARDS = 2405, -} argp_index_values_t; - -void parse_benchmark_arguments(int argc, char **argv, benchmark_config_t *out); - - -#endif //IO_BENCHMARK_ARG_PARSER_H diff --git a/blackheap-benchmark/src/helper.c b/blackheap-benchmark/src/helper.c deleted file mode 100644 index f970d44..0000000 --- a/blackheap-benchmark/src/helper.c +++ /dev/null @@ -1,242 +0,0 @@ -// -// Created by lquenti on 27.11.21. -// - - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include"helper.h" - -void *malloc_or_die(size_t size) -{ - void *res; - if (size == 0) - { - fprintf(stderr, "ERROR: malloc() called with length zero. Exiting...\n"); - exit(1); - } - res = malloc(size); - if (res == 0) - { - fprintf(stderr, "ERROR: malloc() failed.\n"); - exit(1); - } - return res; -} - -int open_or_die(const char *pathname, int flags, mode_t mode) -{ - int res = open(pathname, flags, mode); - if (res == -1) - { - fprintf(stderr, "ERROR: opening %s failed with '%s'\n", pathname, strerror(errno)); - exit(1); - } - return res; -} - -ssize_t read_or_die(int fd, void *buf, size_t count) -{ - if (count > MAX_IO_SIZE) - { - fprintf(stderr, "ERROR: Linux just supports reading up to 0x%x bytes per read\n", MAX_IO_SIZE); - exit(1); - } - ssize_t res = read(fd, buf, count); - if (res == -1) - { - fprintf(stderr, "ERROR: failed to read with '%s'\n", strerror(errno)); - exit(1); - } - if (((size_t)res) != count) - { - fprintf(stderr, "ERROR: Wrong number of bytes read. Expected: %zu Actual: %zu\n", count, res); - exit(1); - } - return res; -} - -ssize_t write_or_die(int fd, void *buf, size_t count) -{ - if (count > MAX_IO_SIZE) - { - fprintf(stderr, "ERROR: Linux just supports writing up to 0x%x bytes per read\n", MAX_IO_SIZE); - exit(1); - } - ssize_t res = write(fd, buf, count); - if (res == -1) - { - fprintf(stderr, "ERROR: failed to write with '%s'\n", strerror(errno)); - exit(1); - } - if (((size_t)res) != count) - { - fprintf(stderr, "ERROR: Wrong number of bytes writen. Expected: %zu Actual: %zu\n", count, res); - exit(1); - } - return res; -} - -int close_or_die(int fd) -{ - int res = close(fd); - if (res == -1) - { - fprintf(stderr, "ERROR: falied to close file with '%s'\n", strerror(errno)); - exit(1); - } - return res; -} - -off_t lseek_or_die(int fd, off_t offset, int whence) -{ - off_t res = lseek(fd, offset, whence); - if (res == -1) - { - fprintf(stderr, "ERROR: lseek failed with '%s'\n", strerror(errno)); - exit(1); - } - return res; -} - -int fsync_or_die(int fd) -{ - int res = fsync(fd); - if (res == -1) - { - fprintf(stderr, "ERROR: fsync failed with '%s'\n", strerror(errno)); - exit(1); - } - return res; -} - -int fstat_or_die(int fd, struct stat *st) -{ - int res = fstat(fd, st); - if (res == -1) - { - fprintf(stderr, "ERROR: fstat failed with '%s'\n", strerror(errno)); - exit(1); - } - return res; -} - -void io_op_worked_or_die(int res, bool is_read_operation) -{ - if (res == -1) - { - fprintf(stderr, "ERROR: %s failed with '%s'\n", (is_read_operation) ? "read" : "write", strerror(errno)); - exit(1); - } -} - -void remove_or_die(const char *pathname) { - int res = remove(pathname); - if (res == -1) { - fprintf(stderr, "ERROR: Could not delete '%s' with error '%s'\n", pathname, strerror(errno)); - exit(1); - } -} - -void strn_to_lower(char *str, size_t n) -{ - /* If n unspecified (i.e. 0) we use the full string. */ - if (n == 0) - n = strlen(str); - for (size_t i = 0; i < n; ++i) - str[i] = tolower(str[i]); -} - -long parse_from_meminfo(char *key) -{ - long res = -1; - size_t keylen = strlen(key); - strn_to_lower(key, keylen); - - /* Find the correct line */ - char buf[100]; - FILE *fp = fopen(MEMINFO, "r"); - while (fgets(buf, sizeof(buf), fp)) - { - strn_to_lower(buf, 0); - if (strncmp(buf, key, keylen)) - continue; - - char *colon = strchr(buf, ':'); - res = atol(colon + 1); - break; - } - fclose(fp); - - return res; -} - -size_t get_available_mem() -{ - /* Needed to allow case insensitive comparison */ - char free[] = "MemFree", cached[] = "Cached", buffers[] = "Buffers"; - return parse_from_meminfo(free) + parse_from_meminfo(cached) + - parse_from_meminfo(buffers); -} - -void allocate_memory_until(size_t space_left_in_kib) -{ - size_t current_available = get_available_mem(); - while (current_available > space_left_in_kib) - { - size_t delta = current_available - space_left_in_kib; - size_t n = (delta < 500 ? delta : 500) * 1024; - - char *p = malloc(n); - if (!p) - { - fprintf(stderr, "dummy malloc failed. available: %zu. Tried to alloc %zu. Quitting...", - current_available, n / 1024); - exit(1); - } - memset(p, '1', n); - current_available = get_available_mem(); - } -} - -/* See: https://unix.stackexchange.com/q/17936 */ -void drop_page_cache() { - /* sync first */ - sync(); - /* Write magic value */ - int fd = open(DROP_PAGE_CACHE, O_WRONLY); - /* Check whether we had the permissions */ - if (fd == -1) { - if (errno == EACCES) { - fprintf(stderr, "In order to clear the cache, we need permissions to open" DROP_PAGE_CACHE "\n"); - exit(1); - } else { - fprintf(stderr, "Unknown Error while opening" DROP_PAGE_CACHE ".\nError: %s\n", strerror(errno)); - exit(1); - } - } - char magic_value = '3'; - write_or_die(fd, &magic_value, sizeof(char)); - /* In case the OS does it non-blockingly */ - sleep(5); - close(fd); -} - -void to_lower_str(char *p) -{ - for (; *p; ++p) - { - *p = tolower(*p); - } -} \ No newline at end of file diff --git a/blackheap-benchmark/src/helper.h b/blackheap-benchmark/src/helper.h deleted file mode 100644 index 3ed9d01..0000000 --- a/blackheap-benchmark/src/helper.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef IO_BENCHMARK_HELPER_H -#define IO_BENCHMARK_HELPER_H - -#include -#include -#include -#include - -#define MEMINFO "/proc/meminfo" - -/* See: https://unix.stackexchange.com/q/17936 */ -#define DROP_PAGE_CACHE "/proc/sys/vm/drop_caches" - -/* See: https://stackoverflow.com/a/70370002/9958281 */ -#define MAX_IO_SIZE 0x7ffff000 - -/** Helper function to "handle" malloc failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - */ -void *malloc_or_die(size_t size); - - -/** Helper function to "handle" open failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - */ -int open_or_die(const char *pathname, int flags, mode_t mode); - - -/** Helper function to "handle" read failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - * - * Also: the maximum size which is checked aginst is defined in Linux man 2 read - */ -ssize_t read_or_die(int fd, void *buf, size_t count); - - -/** Helper function to "handle" write failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - * - * Also: the maximum size which is checked aginst is defined in Linux man 2 write - */ -ssize_t write_or_die(int fd, void *buf, size_t count); - -/** Helper function to "handle" close failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - */ -int close_or_die(int fd); - -/** Helper function to "handle" lseek failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - */ -off_t lseek_or_die(int fd, off_t offset, int whence); - -/** Helper function to "handle" fsync failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - */ -int fsync_or_die(int fd); - -/** Helper function to "handle" fstat failure. - * - * In this case, handling means logging failure and killing the whole program. - * This is fine because we have a job-based architecture and this job obviously failed. - */ -int fstat_or_die(int fd, struct stat *st); - - -/** Checks for io-failure outside of benchmark. - * - * This is done in order to minimize branching in the actual io-read, thus - * giving the best possible benchmark data. - */ -void io_op_worked_or_die(int res, bool is_read_operation); - - -void remove_or_die(const char *pathname); -void strn_to_lower(char *str, size_t n); -long parse_from_meminfo(char *key); -size_t get_available_mem(); -void allocate_memory_until(size_t space_left_in_kib); -/* See: https://unix.stackexchange.com/q/17936 */ -void drop_page_cache(); -void to_lower_str(char *p); - -#endif //IO_BENCHMARK_HELPER_H diff --git a/blackheap-benchmark/src/io_benchmark.c b/blackheap-benchmark/src/io_benchmark.c deleted file mode 100644 index b43376f..0000000 --- a/blackheap-benchmark/src/io_benchmark.c +++ /dev/null @@ -1,263 +0,0 @@ -// -// Created by lquenti on 22.11.21. -// - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include"io_benchmark.h" - -// Notes: -// Which clocks to use: -// - https://stackoverflow.com/a/12480485/9958281 - - - -/** Initializes the file used for reading/writing. - * - * Since write needs a buffer argument, we use our benchmark buffer to fill it, thus - * we possibly need multiple iterations to fill it. - * The size is defined by config->file_size_in_bytes. - */ -static void init_file(const benchmark_config_t *config, benchmark_state_t *state) -{ - /* is it externally managed? */ - if (!config->prepare_file_size) - return; - - state->fd = open_or_die(config->filepath, O_CREAT | O_RDWR, 0644); - - /* Does it already have the correct size? */ - struct stat st; - fstat_or_die(state->fd, &st); - close_or_die(state->fd); - if ((size_t)st.st_size == config->file_size_in_bytes) - return; - - /* If not, we just truncate it to zero and fill it up */ - state->fd = open_or_die(config->filepath, O_RDWR | O_TRUNC, 0644); - size_t count = (MAX_IO_SIZE <= config->memory_buffer_in_bytes) ? MAX_IO_SIZE : config->memory_buffer_in_bytes; - size_t iterations = config->file_size_in_bytes / count; - for (; iterations; --iterations) - { - write_or_die(state->fd, state->buffer, count); - } - /* Now allocate the rest which is less than 1 buffer size. */ - size_t rest = config->file_size_in_bytes % count; - write_or_die(state->fd, state->buffer, rest); - - /* Did it work? */ - fsync_or_die(state->fd); - fstat_or_die(state->fd, &st); - if ((size_t)st.st_size != config->file_size_in_bytes) - { - fprintf(stderr, "ERROR: File size does not match. Expected: %zu Actual: %zu\n", config->file_size_in_bytes, - (size_t)st.st_size); - exit(1); - } -} - -/** Initializes the benchmark_state_t struct with proper values from the config. */ -static void init_state(const benchmark_config_t *config, benchmark_state_t *state) -{ - state->buffer = malloc_or_die(config->memory_buffer_in_bytes); - memset(state->buffer, '1', config->memory_buffer_in_bytes); - state->last_mem_offset = 0; - state->last_file_offset = 0; - state->io_op = config->is_read_operation ? read : (io_op_t)write; -} - -/** Sane initialization of the benchmark_results_t struct based on config values. */ -static void init_results(const benchmark_config_t *config, benchmark_results_t *results) -{ - results->length = config->number_of_io_op_tests; - results->durations = malloc_or_die(sizeof(double) * config->number_of_io_op_tests); -} - -/** Initialzes the memory pointer for the working memory buffer. - * - * Depending on the access patterns. - */ -static inline void init_memory_position(const benchmark_config_t *config, size_t *res) -{ - switch (config->access_pattern_in_memory) - { - case ACCESS_PATTERN_CONST: - case ACCESS_PATTERN_SEQUENTIAL: - { - *res = 0; - return; - } - case ACCESS_PATTERN_RANDOM: - { - /* TODO: Possible Alignment and Rewrite */ - *res = ((size_t)rand() * 128) % (config->memory_buffer_in_bytes - config->access_size_in_bytes); - return; - } - } -} - -/** Initializes the file pointer location. - * - * Depending on the access patterns. - * In our program state we also track the current state - */ -static inline void init_file_position(const benchmark_config_t *config, benchmark_state_t *state) -{ - switch (config->access_pattern_in_file) - { - case ACCESS_PATTERN_CONST: - case ACCESS_PATTERN_SEQUENTIAL: - { - // We start at the beginning - state->last_file_offset = 0; - return; - } - case ACCESS_PATTERN_RANDOM: - { - // TODO: Possible alignment and rewrite - size_t random_offset = ((off_t)rand() * 128) % (config->file_size_in_bytes - config->access_size_in_bytes); - lseek_or_die(state->fd, random_offset, SEEK_CUR); - state->last_file_offset = random_offset; - return; - } - } -} - -/** Reset all state values before the run. */ -static void prepare_run(const benchmark_config_t *config, benchmark_state_t *state) -{ - if (config->use_o_direct) - state->fd = open_or_die(config->filepath, O_RDWR | O_DIRECT, 0644); - else - state->fd = open_or_die(config->filepath, O_RDWR, 0644); - if (config->restrict_free_ram_to != 0) - allocate_memory_until(config->restrict_free_ram_to/1024); - init_memory_position(config, &state->last_mem_offset); - init_file_position(config, state); -} - -/** Choose the next memory position after each io-op according to the access pattern. */ -static inline void pick_next_mem_position(const benchmark_config_t *config, benchmark_state_t *state) -{ - switch (config->access_pattern_in_memory) - { - case ACCESS_PATTERN_CONST: - /* After one io-op the pointer does not get moved like the fd-state for the file */ - return; - case ACCESS_PATTERN_SEQUENTIAL: - { - state->last_mem_offset += config->access_size_in_bytes; - return; - } - case ACCESS_PATTERN_RANDOM: - state->last_mem_offset = ((size_t)rand() * 128) % (config->memory_buffer_in_bytes - config->access_size_in_bytes); - return; - } -} - -/** Choose the next file position after each io-op according to the access pattern */ -static inline void pick_next_file_position(const benchmark_config_t *config, benchmark_state_t *state) -{ - switch (config->access_pattern_in_file) - { - case ACCESS_PATTERN_CONST: - lseek_or_die(state->fd, 0, SEEK_SET); - return; - case ACCESS_PATTERN_SEQUENTIAL: - { - state->last_file_offset = state->last_file_offset + config->access_size_in_bytes; - /* we don't have to lseek since the pointer moves naturally */ - return; - } - case ACCESS_PATTERN_RANDOM: - { - // TODO: Refactor align.... - size_t new_file_pos = ((size_t)rand() * 128) % (config->file_size_in_bytes - config->access_size_in_bytes); - lseek_or_die(state->fd, new_file_pos, SEEK_SET); - state->last_file_offset = new_file_pos; - return; - } - } -} - -/** Extracts the number of seconds from a struct timespec defined by time.h */ -static inline double timespec_to_double(const timespec_t *time) -{ - return time->tv_sec + 0.001 * 0.001 * 0.001 * time->tv_nsec; -} - -/** Update the tracked values after each io-operation */ -static double get_duration(const timespec_t *start, const timespec_t *end) -{ - return timespec_to_double(end) - timespec_to_double(start); -} - -static void do_reread_if_needed(const benchmark_config_t *config, benchmark_state_t *state) { - if (!config->do_reread) - return; - state->io_op(state->fd, state->buffer, config->access_size_in_bytes); - /* Seek back so that we read it twice */ - lseek_or_die(state->fd, state->last_file_offset, SEEK_SET); -} - -/** The actual benchmark function. - * - * After preparing, it gets the time before, does the io-op and then gets the time afterwards and updates it. - */ -static void do_benchmark(const benchmark_config_t *config, benchmark_state_t *state, benchmark_results_t *results) -{ - timespec_t start, end; - int res; - prepare_run(config, state); - for (size_t i = 0; i < config->number_of_io_op_tests; ++i) - { - do_reread_if_needed(config, state); - clock_gettime(CLOCK_MONOTONIC, &start); - res = state->io_op(state->fd, state->buffer, config->access_size_in_bytes); - clock_gettime(CLOCK_MONOTONIC, &end); - io_op_worked_or_die(res, config->is_read_operation); - pick_next_mem_position(config, state); - pick_next_file_position(config, state); - results->durations[i] = get_duration(&start, &end); - } -} - -static void do_cleanup(const benchmark_config_t *config, benchmark_state_t *state) { - close_or_die(state->fd); - if (config->delete_afterwards) { - remove_or_die(config->filepath); - } - free(state->buffer); -} - -/** Wrapper-function. - * - * The only non-static function. Creates the state and wraps the benchmark. - */ -benchmark_results_t *benchmark_file(const benchmark_config_t *config) -{ - benchmark_state_t state; - benchmark_results_t *results = malloc_or_die(sizeof(benchmark_results_t)); - - if (config->drop_cache_first) - drop_page_cache(); - init_state(config, &state); - init_file(config, &state); - init_results(config, results); - - do_benchmark(config, &state, results); - - do_cleanup(config, &state); - return results; -} - diff --git a/blackheap-benchmark/src/io_benchmark.h b/blackheap-benchmark/src/io_benchmark.h deleted file mode 100644 index 06a76e4..0000000 --- a/blackheap-benchmark/src/io_benchmark.h +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef IO_BENCHMARK_IO_BENCHMARK_H -#define IO_BENCHMARK_IO_BENCHMARK_H - -#include -#include -#include - -#include "helper.h" - -/** All possible access patterns. */ -typedef enum access_pattern_t -{ - ACCESS_PATTERN_CONST = 0, /**< always read at the same place */ - ACCESS_PATTERN_SEQUENTIAL = 1, /**< read sequentially through the file (like an intuitive read) */ - ACCESS_PATTERN_RANDOM = 2, /**< go to a random position after every read */ -} access_pattern_t; - -/** All possible configuration options for a benchmark run - * This will get parsed from the command line options. - */ -typedef struct benchmark_config_t -{ - /**< The path to the file from which will be read from or written to */ - const char *filepath; - - /**< The size of the memory buffer to/from which the information will be written/read to*/ - const size_t memory_buffer_in_bytes; - - /**< The size of the file specified by filepath. Ignored is prepare_file_size is set to false. */ - const size_t file_size_in_bytes; - - /**< The size of each I/O request. */ - const size_t access_size_in_bytes; - - /**< The number of tests done for this execution. */ - const size_t number_of_io_op_tests; - - /**< Which access pattern should be used aka how the memory pointer should be moved.*/ - const access_pattern_t access_pattern_in_memory; - - /**< Which access pattern should be used aka how the file pointer should be seeked.*/ - const access_pattern_t access_pattern_in_file; - - /**< Whether the benchmaked I/O-operation is read or write. */ - const bool is_read_operation; - - /** Whether the file should be bloated up to file_size_in_bytes. - * - * In most cases, this should be true. - * The only expections are special "files" that can't be made bigger like - * special devices. - */ - const bool prepare_file_size; - - const bool use_o_direct; - - const bool drop_cache_first; - - const bool do_reread; - - const bool delete_afterwards; - - const size_t restrict_free_ram_to; -} benchmark_config_t; - -typedef ssize_t (*io_op_t)(int fd, void *buf, size_t count); - -/** The current state of the program, wrapped into a struct to declutter global state. - * - * In order to not pollute global state we keep our state local to the functions. - * We wrap it all into a struct to not have functions with 10+ parameters. - * - * The state is basically a singleton for each thread, created by the benchmark itself. - */ -typedef struct benchmark_state_t -{ - /**< The memory buffer on which the io-ops read/write from/to */ - void *buffer; - - /**< The file descriptor of the benchmarked file specified by the config struct. */ - int fd; - - /**< The memory offset after the last io-operation, needed to specify the next one according to access pattern. */ - size_t last_mem_offset; - - /**< The file offset after the last io-operation, needed to specify the next one according to access pattern. */ - size_t last_file_offset; - io_op_t io_op; -} benchmark_state_t; - -/** The results returned after the benchmark. - * - * Each test, defined by its starting and end time, is for a single io-operation (i.e. a single read or write). - * No statistical accumulation or means. - */ -typedef struct benchmark_results_t -{ - /**< The number of io-ops measured in this benchmark */ - size_t length; - - /**< An array of durations. The i-th double corresponds to the starting time of the i-th io-operation */ - double *durations; - -} benchmark_results_t; - -typedef struct timespec timespec_t; -benchmark_results_t *benchmark_file(const benchmark_config_t *config); - -#endif //IO_BENCHMARK_IO_BENCHMARK_H diff --git a/blackheap-benchmark/src/main.c b/blackheap-benchmark/src/main.c deleted file mode 100644 index d2b56ad..0000000 --- a/blackheap-benchmark/src/main.c +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include -#include -#include - -#include "io_benchmark.h" -#include "arg_parser.h" -#include "output.h" - -int main(int argc, char **argv) -{ - benchmark_config_t config; - benchmark_results_t *results; - parse_benchmark_arguments(argc, argv, &config); - - results = benchmark_file(&config); - - print_output(&config, results); - - free(results); - return 0; -} diff --git a/blackheap-benchmark/src/output.c b/blackheap-benchmark/src/output.c deleted file mode 100644 index 4301478..0000000 --- a/blackheap-benchmark/src/output.c +++ /dev/null @@ -1,61 +0,0 @@ -// -// Created by lquenti on 02.12.21 -// - - -#include -#include - -#include "output.h" - -static inline char *access_pattern_to_str(access_pattern_t pat) -{ - switch (pat) - { - case ACCESS_PATTERN_CONST: - return "const"; - case ACCESS_PATTERN_SEQUENTIAL: - return "seq"; - case ACCESS_PATTERN_RANDOM: - return "rnd"; - } - /* This should never happen */ - __builtin_unreachable(); -} - -static void print_config(const benchmark_config_t *config) -{ - fprintf(stdout, "\"%s\": \"%s\",\n", "filepath", config->filepath); - fprintf(stdout, "\"%s\": %zu,\n", "repeats", config->number_of_io_op_tests); - fprintf(stdout, "\"%s\": %zu,\n", "memory_buffer_in_bytes", config->memory_buffer_in_bytes); - fprintf(stdout, "\"%s\": %zu,\n", "file_size_in_bytes", config->file_size_in_bytes); - fprintf(stdout, "\"%s\": %zu,\n", "access_size_in_bytes", config->access_size_in_bytes); - fprintf(stdout, "\"%s\": \"%s\",\n", "access_pattern_in_memory", access_pattern_to_str(config->access_pattern_in_memory)); - fprintf(stdout, "\"%s\": \"%s\",\n", "access_pattern_in_file", access_pattern_to_str(config->access_pattern_in_file)); - fprintf(stdout, "\"%s\": \"%s\",\n", "io_operation", config->is_read_operation ? "read" : "write"); - fprintf(stdout, "\"%s\": %s,\n", "prepare_file_size", config->prepare_file_size ? "true" : "false"); - fprintf(stdout, "\"%s\": %zu,\n", "restricted_ram_in_bytes", config->restrict_free_ram_to); - fprintf(stdout, "\"%s\": %s,\n", "use_o_direct", config->use_o_direct ? "true" : "false"); - fprintf(stdout, "\"%s\": %s,\n", "drop_cache_first", config->drop_cache_first ? "true" : "false"); - fprintf(stdout, "\"%s\": %s,\n", "reread_every_block", config->do_reread ? "true" : "false"); - fprintf(stdout, "\"%s\": %s,\n", "delete_afterwards", config->delete_afterwards ? "true" : "false"); -} - -static void print_results(const benchmark_results_t *results) -{ - fprintf(stdout, "\"durations\": [\n"); - for (size_t i = 0; i < results->length; ++i) - { - fprintf(stdout, "\t%.17g%s\n", results->durations[i], (i == results->length - 1) ? "" : ","); - } - fprintf(stdout, "]\n"); -} - -void print_output(const benchmark_config_t *config, const benchmark_results_t *results) -{ - fprintf(stdout, "{\n"); - print_config(config); - print_results(results); - fprintf(stdout, "}\n"); -} - diff --git a/blackheap-benchmark/src/output.h b/blackheap-benchmark/src/output.h deleted file mode 100644 index 32a1ac8..0000000 --- a/blackheap-benchmark/src/output.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef IO_BENCHMARK_OUTPUT_H -#define IO_BENCHMARK_OUTPUT_H -#include "io_benchmark.h" - -void print_output(const benchmark_config_t *config, const benchmark_results_t *results); - -#endif //IO_BENCHMARK_OUTPUT_H diff --git a/blackheap-benchmarker/Cargo.toml b/blackheap-benchmarker/Cargo.toml new file mode 100644 index 0000000..b7d625d --- /dev/null +++ b/blackheap-benchmarker/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "blackheap-benchmarker" +version = "0.1.0" +edition = "2021" + +[dependencies] +libc = "0.2" + +[build-dependencies] +cc = "1.0" diff --git a/blackheap-benchmarker/build.rs b/blackheap-benchmarker/build.rs new file mode 100644 index 0000000..ee982ea --- /dev/null +++ b/blackheap-benchmarker/build.rs @@ -0,0 +1,5 @@ +fn main() { + cc::Build::new() + .file("src/c_code/benchmarker_internal.c") + .compile("c_benchmarker"); +} diff --git a/blackheap-benchmarker/regenerate_types.sh b/blackheap-benchmarker/regenerate_types.sh new file mode 100755 index 0000000..3926253 --- /dev/null +++ b/blackheap-benchmarker/regenerate_types.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# This script generates Rust types for a benchmarker from a C header file using Bindgen. + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +if ! command -v bindgen &> /dev/null; then + echo "Error: Bindgen is not found in your PATH." + echo "Please install bindgen by running: \$ cargo install bindgen-cli" + exit 1 +fi + +bindgen ${SCRIPT_DIR}/src/c_code/benchmarker.h -o ${SCRIPT_DIR}/src/c_code/benchmarker.rs + +if [ $? -eq 0 ]; then + echo "Bindgen completed successfully. Rust types generated in src/c_code/benchmarker.rs." +else + echo "Error: Bindgen encountered an issue while generating Rust types." + exit 1 +fi diff --git a/blackheap-benchmarker/src/c_code/benchmarker.h b/blackheap-benchmarker/src/c_code/benchmarker.h new file mode 100644 index 0000000..6257a60 --- /dev/null +++ b/blackheap-benchmarker/src/c_code/benchmarker.h @@ -0,0 +1,73 @@ +#ifndef BLACKHEAP_BENCHMARKER_BENCHMARER_H +#define BLACKHEAP_BENCHMARKER_BENCHMARER_H + + +#define MEMINFO "/proc/meminfo" + +/* https://www.kernel.org/doc/Documentation/sysctl/vm.txt */ +#define DROP_PAGE_CACHE "/proc/sys/vm/drop_caches" + +#include +#include + +/* All possible access patterns */ +enum access_pattern { + ACCESS_PATTERN_CONST = 0, + ACCESS_PATTERN_SEQUENTIAL = 1, + ACCESS_PATTERN_RANDOM = 2, +}; + +enum error_codes { + ERROR_CODES_SUCCESS = 0, + + /* Linux operations that failed */ + ERROR_CODES_MALLOC_FAILED = 1, + ERROR_CODES_OPEN_FAILED = 2, + ERROR_CODES_READ_FAILED = 3, + ERROR_CODES_WRITE_FAILED = 4, + ERROR_CODES_LSEEK_FAILED = 5, + ERROR_CODES_FSYNC_FAILED = 6, + ERROR_CODES_FSTAT_FAILED = 7, + ERROR_CODES_IO_OP_FAILED = 8, + ERROR_CODES_REMOVE_FAILED = 9, + + /* Higher level operations */ + ERROR_CODES_DROP_PAGE_CACHE_FAILED_NO_PERMISSIONS = 10, + ERROR_CODES_DROP_PAGE_CACHE_FAILED_OTHER = 11, + + ERROR_CODES_INCORRECT_FILE_BUFFER_SIZE = 12, +}; + + +struct benchmark_config { + const char *filepath; + const size_t memory_buffer_in_bytes; + const size_t file_size_in_bytes; + const size_t access_size_in_bytes; + const size_t number_of_io_op_tests; + const enum access_pattern access_pattern_in_memory; + const enum access_pattern access_pattern_in_file; + const bool is_read_operation; + /* Whether the file should be bloated up to file_size_in_bytes. + * + * In most cases, this should be true. + * The only expections are special "files" that can't be made bigger like + * special devices. + */ + const bool prepare_file_size; + + /* Note that this requires root */ + const bool drop_cache_first; + const bool do_reread; + const size_t restrict_free_ram_to; +}; + +struct benchmark_results { + enum error_codes res; + size_t length; + double *durations; +}; + +struct benchmark_results benchmark_file(const struct benchmark_config *config); + +#endif diff --git a/blackheap-benchmarker/src/c_code/benchmarker.rs b/blackheap-benchmarker/src/c_code/benchmarker.rs new file mode 100644 index 0000000..4596efa --- /dev/null +++ b/blackheap-benchmarker/src/c_code/benchmarker.rs @@ -0,0 +1,275 @@ +/* automatically generated by rust-bindgen 0.69.4 */ + +pub const MEMINFO: &[u8; 14] = b"/proc/meminfo\0"; +pub const DROP_PAGE_CACHE: &[u8; 25] = b"/proc/sys/vm/drop_caches\0"; +pub const true_: u32 = 1; +pub const false_: u32 = 0; +pub const __bool_true_false_are_defined: u32 = 1; +pub type wchar_t = ::std::os::raw::c_int; +#[repr(C)] +#[repr(align(16))] +#[derive(Debug, Copy, Clone)] +pub struct max_align_t { + pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, + pub __bindgen_padding_0: u64, + pub __clang_max_align_nonce2: u128, +} +#[test] +fn bindgen_test_layout_max_align_t() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(max_align_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 16usize, + concat!("Alignment of ", stringify!(max_align_t)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).__clang_max_align_nonce1) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(max_align_t), + "::", + stringify!(__clang_max_align_nonce1) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).__clang_max_align_nonce2) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(max_align_t), + "::", + stringify!(__clang_max_align_nonce2) + ) + ); +} +pub const access_pattern_ACCESS_PATTERN_CONST: access_pattern = 0; +pub const access_pattern_ACCESS_PATTERN_SEQUENTIAL: access_pattern = 1; +pub const access_pattern_ACCESS_PATTERN_RANDOM: access_pattern = 2; +pub type access_pattern = ::std::os::raw::c_uint; +pub const error_codes_ERROR_CODES_SUCCESS: error_codes = 0; +pub const error_codes_ERROR_CODES_MALLOC_FAILED: error_codes = 1; +pub const error_codes_ERROR_CODES_OPEN_FAILED: error_codes = 2; +pub const error_codes_ERROR_CODES_READ_FAILED: error_codes = 3; +pub const error_codes_ERROR_CODES_WRITE_FAILED: error_codes = 4; +pub const error_codes_ERROR_CODES_LSEEK_FAILED: error_codes = 5; +pub const error_codes_ERROR_CODES_FSYNC_FAILED: error_codes = 6; +pub const error_codes_ERROR_CODES_FSTAT_FAILED: error_codes = 7; +pub const error_codes_ERROR_CODES_IO_OP_FAILED: error_codes = 8; +pub const error_codes_ERROR_CODES_REMOVE_FAILED: error_codes = 9; +pub const error_codes_ERROR_CODES_DROP_PAGE_CACHE_FAILED_NO_PERMISSIONS: error_codes = 10; +pub const error_codes_ERROR_CODES_DROP_PAGE_CACHE_FAILED_OTHER: error_codes = 11; +pub const error_codes_ERROR_CODES_INCORRECT_FILE_BUFFER_SIZE: error_codes = 12; +pub type error_codes = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct benchmark_config { + pub filepath: *const ::std::os::raw::c_char, + pub memory_buffer_in_bytes: usize, + pub file_size_in_bytes: usize, + pub access_size_in_bytes: usize, + pub number_of_io_op_tests: usize, + pub access_pattern_in_memory: access_pattern, + pub access_pattern_in_file: access_pattern, + pub is_read_operation: bool, + pub prepare_file_size: bool, + pub drop_cache_first: bool, + pub do_reread: bool, + pub restrict_free_ram_to: usize, +} +#[test] +fn bindgen_test_layout_benchmark_config() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 64usize, + concat!("Size of: ", stringify!(benchmark_config)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(benchmark_config)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).filepath) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(filepath) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).memory_buffer_in_bytes) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(memory_buffer_in_bytes) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).file_size_in_bytes) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(file_size_in_bytes) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).access_size_in_bytes) as usize - ptr as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(access_size_in_bytes) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).number_of_io_op_tests) as usize - ptr as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(number_of_io_op_tests) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).access_pattern_in_memory) as usize - ptr as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(access_pattern_in_memory) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).access_pattern_in_file) as usize - ptr as usize }, + 44usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(access_pattern_in_file) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).is_read_operation) as usize - ptr as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(is_read_operation) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).prepare_file_size) as usize - ptr as usize }, + 49usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(prepare_file_size) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).drop_cache_first) as usize - ptr as usize }, + 50usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(drop_cache_first) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).do_reread) as usize - ptr as usize }, + 51usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(do_reread) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).restrict_free_ram_to) as usize - ptr as usize }, + 56usize, + concat!( + "Offset of field: ", + stringify!(benchmark_config), + "::", + stringify!(restrict_free_ram_to) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct benchmark_results { + pub res: error_codes, + pub length: usize, + pub durations: *mut f64, +} +#[test] +fn bindgen_test_layout_benchmark_results() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(benchmark_results)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(benchmark_results)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).res) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(benchmark_results), + "::", + stringify!(res) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).length) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(benchmark_results), + "::", + stringify!(length) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).durations) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(benchmark_results), + "::", + stringify!(durations) + ) + ); +} +extern "C" { + pub fn benchmark_file(config: *const benchmark_config) -> benchmark_results; +} diff --git a/blackheap-benchmarker/src/c_code/benchmarker_internal.c b/blackheap-benchmarker/src/c_code/benchmarker_internal.c new file mode 100644 index 0000000..4c7618e --- /dev/null +++ b/blackheap-benchmarker/src/c_code/benchmarker_internal.c @@ -0,0 +1,459 @@ +#include"./benchmarker_internal.h" +#include "benchmarker.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum error_codes drop_page_cache() { + /* sync first */ + sync(); + + int fd = open(DROP_PAGE_CACHE, O_WRONLY); + if (fd == -1) { + if (errno == EACCES) { + fprintf(stderr, "In order to drop the page cache, we need permissions to open" DROP_PAGE_CACHE "\n"); + return ERROR_CODES_DROP_PAGE_CACHE_FAILED_NO_PERMISSIONS; + } else { + fprintf(stderr, "Unknown Error while opening" DROP_PAGE_CACHE ".\nError: %s\n", strerror(errno)); + return ERROR_CODES_DROP_PAGE_CACHE_FAILED_OTHER; + } + } + + char magic_value = '3'; + ssize_t res = write(fd, &magic_value, sizeof(char)); + if (res == -1) { + fprintf(stderr, "Dropping the page cache failed. The write was not successful.\nError: %s\n", strerror(errno)); + return ERROR_CODES_DROP_PAGE_CACHE_FAILED_OTHER; + } + + /* in case the OS does it non-blockingly */ + sleep(5); + + close(fd); + return ERROR_CODES_SUCCESS; +} + +enum error_codes init_state(const struct benchmark_config *config, struct benchmark_state *state) { + void *ptr; + + ptr = malloc(config->memory_buffer_in_bytes); + if (ptr == NULL) { + fprintf(stderr, "Mallocing the big memory buffer of size %zu failed\n", config->memory_buffer_in_bytes); + return ERROR_CODES_MALLOC_FAILED; + } + /* enforce that the buffer actually exists */ + memset(ptr, '1', (unsigned long)config->memory_buffer_in_bytes); + state->buffer = ptr; + + state->last_mem_offset = 0; + state->last_file_offset = 0; + + if (config->is_read_operation) { + state->io_op = read; + } else { + /* just casting away the const for the void pointer */ + state->io_op = (ssize_t (*)(int, void *, size_t))write; + } + + return ERROR_CODES_SUCCESS; +} + +enum error_codes init_file(const struct benchmark_config *config, struct benchmark_state *state) { + /* is it externally managed? */ + if (!config->prepare_file_size) { + return ERROR_CODES_SUCCESS; + } + + /* try to open it */ + state->fd = open(config->filepath, O_CREAT | O_RDWR, 0644); + if (state->fd == -1) { + fprintf(stderr, "Error opening \"%s\".\nError: %s\n", config->filepath, strerror(errno)); + return ERROR_CODES_OPEN_FAILED; + } + + /* Does it already have the correct size */ + struct stat st; + int res = fstat(state->fd, &st); + close(state->fd); + if (res == -1) { + fprintf(stderr, "Error checking file size of %s\nError: %s\n", config->filepath, strerror(errno)); + return ERROR_CODES_FSTAT_FAILED; + } + if ((size_t)st.st_size == config->file_size_in_bytes) { + return ERROR_CODES_SUCCESS; + } + + /* If not, we first truncate it to zero */ + state->fd = open(config->filepath, O_RDWR | O_TRUNC, 0644); + if (state->fd == -1) { + fprintf(stderr, "Error opening \"%s\".\nError: %s\n", config->filepath, strerror(errno)); + return ERROR_CODES_OPEN_FAILED; + } + + /* 64k is a good write size (if our buffer is big enough) */ + size_t block_size = 64*1024; + if (block_size > config->memory_buffer_in_bytes) { + block_size = config->memory_buffer_in_bytes; + } + + size_t bytes_written = 0; + ssize_t write_result; + + /* Fill bytes with 1s */ + while (bytes_written < config->file_size_in_bytes) { + size_t bytes_to_write = config->file_size_in_bytes - bytes_written; + if (bytes_to_write > block_size) { + bytes_to_write = block_size; + } + + write_result = write(state->fd, state->buffer, bytes_to_write); + if (write_result == -1) { + fprintf(stderr, "Failed to write to \"%s\"\nError: %s\n", config->filepath, strerror(errno)); + close(state->fd); + return ERROR_CODES_WRITE_FAILED; + } + bytes_written += write_result; + } + + /* Check whether it worked */ + if (fsync(state->fd) == -1) { + fprintf(stderr, "Failed to flush \"%s\" to disk.\nError: %s\n", config->filepath, strerror(errno)); + close(state->fd); + return ERROR_CODES_FSYNC_FAILED; + } + + if (fstat(state->fd, &st) == -1) { + fprintf(stderr, "Error checking file size of %s\nError: %s\n", config->filepath, strerror(errno)); + close(state->fd); + return ERROR_CODES_FSTAT_FAILED; + } + + close(state->fd); + + if ((long long)st.st_size != (long long)config->file_size_in_bytes) { + fprintf( + stderr, + "Incorrect file size after filling \"%s\". Expected: %zu Actual: %lld\n", + config->filepath, + config->file_size_in_bytes, + (long long)st.st_size + ); + return ERROR_CODES_INCORRECT_FILE_BUFFER_SIZE; + } + + return ERROR_CODES_SUCCESS; +} + + +enum error_codes init_results(const struct benchmark_config *config, struct benchmark_results *results) { + results->res = ERROR_CODES_SUCCESS; + results->length = config->number_of_io_op_tests; + + results->durations = malloc(sizeof(double) * config->number_of_io_op_tests); + return (results->durations == NULL) ? ERROR_CODES_MALLOC_FAILED : ERROR_CODES_SUCCESS; +} + +long parse_from_meminfo(char *key) { + long res = -1; + size_t keylen = strlen(key); + + FILE *fp = fopen(MEMINFO, "r"); + if (!fp) { + perror("Failed to open MEMINFO"); + return res; + } + + char buf[100]; + while (fgets(buf, sizeof(buf), fp)) { + + /* is it not out match? */ + if (strncmp(buf, key, keylen) != 0) { + continue; + } + printf("%s\n", buf); + + /* It is out match. */ + char *colon = strchr(buf, ':'); + if (colon) { + res = atol(colon+1); + break; + } + } + + fclose(fp); + return res; +} + +size_t get_available_mem_kib() { + long free = parse_from_meminfo("MemFree"); + long cached = parse_from_meminfo("Cached"); + long buffers = parse_from_meminfo("Buffers"); + + /* Log if any of them failed... */ + if (free == -1) { + fprintf(stderr, "Reading \"MemFree\" from /proc/meminfo failed..."); + return -1; + } + if (cached == -1) { + fprintf(stderr, "Reading \"Cached\" from /proc/meminfo failed..."); + return -1; + } + if (buffers == -1) { + fprintf(stderr, "Reading \"Buffers\" from /proc/meminfo failed..."); + return -1; + } + + return free+cached+buffers; +} + +/* Note that the callee has to free if it succeeded */ +struct allocation_result allocate_memory_until(size_t space_left_in_kib) { + struct allocation_result result; + result.pointers = NULL; + result.length = 0; + + bool was_successful = true; + + size_t current_available = get_available_mem_kib(); + while (current_available > space_left_in_kib) { + size_t delta = current_available - space_left_in_kib; + size_t n = (delta < 128 ? delta : 128) * 1024; + + void *p = malloc(n); + if (!p) { + fprintf(stderr, "Mallocing %zu bytes to restrict the memory failed. Currently still available: %zu KiB\n", n, current_available); + was_successful = false; + break; + } + + /* Ensure the memory is allocated */ + memset(p, '1', n); + + /* add to ptrs */ + void **new_pointers = realloc(result.pointers, (result.length + 1) * sizeof(void *)); + if (!new_pointers) { + fprintf(stderr, "Reallocating pointers array failed. Current length: %zu\n", result.length); + /* free the last allocation */ + free(p); + break; + } + + result.pointers = new_pointers; + result.pointers[result.length] = p; + result.length++; + + current_available = get_available_mem_kib(); + } + + /* If it failed, we will clean up... */ + if (!was_successful) { + for (ssize_t i=0; iio_op(state->fd, state->buffer, config->access_size_in_bytes); + if (res == -1) { + fprintf(stderr, "Failed to write to \"%s\"\nError: %s\n", config->filepath, strerror(errno)); + return ERROR_CODES_WRITE_FAILED; + } + + /* Seek back so that we read it twice */ + off_t seek_res = lseek(state->fd, state->last_file_offset, SEEK_SET); + if (seek_res == -1) { + fprintf(stderr, "Failed to seek \"%s\" to %zu \nError: %s\n", config->filepath, state->last_file_offset, strerror(errno)); + return ERROR_CODES_LSEEK_FAILED; + } + + return ERROR_CODES_SUCCESS; +} + +double timespec_to_double(const struct timespec *time) { + return time->tv_sec + 0.001 * 0.001 * 0.001 * time->tv_nsec; +} + +void pick_next_mem_position(const struct benchmark_config *config, struct benchmark_state *state) { + switch (config->access_pattern_in_memory) { + case ACCESS_PATTERN_CONST: + /* After one io-op the pointer does not get moved like the fd-state for the file */ + return; + case ACCESS_PATTERN_SEQUENTIAL: + state->last_mem_offset += config->access_size_in_bytes; + + /* Check if we have to wrap */ + if (state->last_mem_offset + config->access_size_in_bytes > config->memory_buffer_in_bytes) { + state->last_mem_offset = 0; + } + return; + case ACCESS_PATTERN_RANDOM: + state->last_mem_offset = ((size_t)rand() * 128) % (config->memory_buffer_in_bytes - config->access_size_in_bytes); + return; + } +} + +enum error_codes pick_next_file_position(const struct benchmark_config *config, struct benchmark_state *state) { + switch (config->access_pattern_in_file) { + case ACCESS_PATTERN_CONST: { + /* Update file descriptor */ + off_t new_offset = lseek(state->fd, 0, SEEK_SET); + if (new_offset == -1) { + fprintf(stderr, "Failed to seek \"%s\" to 0. \nError: %s\n", config->filepath, strerror(errno)); + return ERROR_CODES_LSEEK_FAILED; + } + } + break; + case ACCESS_PATTERN_SEQUENTIAL: { + /* update state */ + state->last_file_offset += config->access_size_in_bytes; + + /* Check if we have to wrap */ + if (state->last_file_offset + config->access_size_in_bytes > config->file_size_in_bytes) { + /* Lets start at zero again */ + state->last_file_offset = 0; + + off_t new_offset = lseek(state->fd, 0, SEEK_SET); + if (new_offset == -1) { + fprintf(stderr, "Failed to seek \"%s\" to 0. \nError: %s\n", config->filepath, strerror(errno)); + return ERROR_CODES_LSEEK_FAILED; + } + } + } + break; + case ACCESS_PATTERN_RANDOM: { + size_t new_file_pos = ((size_t)rand() * 128) % (config->file_size_in_bytes - config->access_size_in_bytes); + + /* Update state */ + state->last_file_offset = new_file_pos; + + /* Update file descriptor */ + off_t new_offset = lseek(state->fd, new_file_pos, SEEK_SET); + if (new_offset == -1) { + fprintf(stderr, "Failed to seek \"%s\" to %zu. \nError: %s\n", config->filepath, (size_t)new_offset, strerror(errno)); + return ERROR_CODES_LSEEK_FAILED; + } + } + break; + } + return ERROR_CODES_SUCCESS; +} + +enum error_codes do_benchmark(const struct benchmark_config *config, struct benchmark_state *state, struct benchmark_results *results) { + struct timespec start, end; + int res; + enum error_codes ret = ERROR_CODES_SUCCESS; + struct allocation_result mallocs; + + /* Open fd (closed by cleanup) */ + state->fd = open(config->filepath, O_RDWR, 0644); + if (state->fd == -1) { + fprintf(stderr, "Error opening \"%s\".\nError: %s\n", config->filepath, strerror(errno)); + return ERROR_CODES_OPEN_FAILED; + } + + /* restrict memory if configured */ + if (config->restrict_free_ram_to != 0) { + mallocs = allocate_memory_until(config->restrict_free_ram_to/1024); + if (mallocs.length == -1) { + return ERROR_CODES_MALLOC_FAILED; + } + } + + for (size_t i=0; inumber_of_io_op_tests; ++i) { + if (config->do_reread) { + ret = reread(config, state); + if (ret != ERROR_CODES_SUCCESS) { + goto cleanup_do_benchmark; + } + } + + /* Do the operation */ + clock_gettime(CLOCK_MONOTONIC, &start); + res = state->io_op(state->fd, state->buffer, config->access_size_in_bytes); + clock_gettime(CLOCK_MONOTONIC, &end); + + + /* did it work? */ + if (res != -1) { + results->durations[i] = timespec_to_double(&end) - timespec_to_double(&start); + } else { + results->durations[i] = -1.0; + } + + /* update offsets */ + pick_next_mem_position(config, state); + ret = pick_next_file_position(config, state); + if (ret != ERROR_CODES_SUCCESS) { + goto cleanup_do_benchmark; + } + } + +cleanup_do_benchmark: + if (config->restrict_free_ram_to != 0) { + for (ssize_t i=0; ifd); + free(state->buffer); +} + +struct benchmark_results benchmark_file(const struct benchmark_config *config) { + struct benchmark_state state; + struct benchmark_results results; + results.res = ERROR_CODES_SUCCESS; + + /* init randomness */ + srand((unsigned int)time(NULL)); + + /* Drop page cache if set (note that this requires root) */ + if (config->drop_cache_first) { + results.res = drop_page_cache(); + } + + /* Init memory buffer and other state */ + if (results.res == ERROR_CODES_SUCCESS) { + results.res = init_state(config, &state); + } + + /* init file buffer */ + if (results.res == ERROR_CODES_SUCCESS) { + results.res = init_file(config, &state); + } + + /* Init results array */ + if (results.res == ERROR_CODES_SUCCESS) { + results.res = init_results(config, &results); + } + + /* Do the benchmark! */ + if (results.res == ERROR_CODES_SUCCESS) { + do_benchmark(config, &state, &results); + } + + /* cleanup */ + do_cleanup(&state); + + return results; +} diff --git a/blackheap-benchmarker/src/c_code/benchmarker_internal.h b/blackheap-benchmarker/src/c_code/benchmarker_internal.h new file mode 100644 index 0000000..ac8ac5d --- /dev/null +++ b/blackheap-benchmarker/src/c_code/benchmarker_internal.h @@ -0,0 +1,43 @@ +#ifndef BLACKHEAP_BENCHMARKER_BENCHMARER_INTERNAL_H +#define BLACKHEAP_BENCHMARKER_BENCHMARER_INTERNAL_H + +#include +#include"./benchmarker.h" + +struct benchmark_state { + void *buffer; + int fd; + size_t last_mem_offset; + size_t last_file_offset; + ssize_t (*io_op)(int fd, void *buf, size_t count); +}; + +struct allocation_result { + void **pointers; + ssize_t length; +}; + +/* init */ +enum error_codes drop_page_cache(); +enum error_codes init_state(const struct benchmark_config *config, struct benchmark_state *state); +enum error_codes init_file(const struct benchmark_config *config, struct benchmark_state *state); +enum error_codes init_results(const struct benchmark_config *config, struct benchmark_results *results); + +/* Benchmarking helpers */ +long parse_from_meminfo(char *key); +size_t get_available_mem_kib(); +struct allocation_result allocate_memory_until(size_t space_left_in_kib); +enum error_codes reread(const struct benchmark_config *config, const struct benchmark_state *state); +double timespec_to_double(const struct timespec *time); +void pick_next_mem_position(const struct benchmark_config *config, struct benchmark_state *state); +enum error_codes pick_next_file_position(const struct benchmark_config *config, struct benchmark_state *state); + +/* Benchmarking function */ +enum error_codes do_benchmark(const struct benchmark_config *config, struct benchmark_state *state, struct benchmark_results *results); + + + +/* do_cleanup is best effort */ +void do_cleanup(struct benchmark_state *state); + +#endif diff --git a/blackheap-benchmarker/src/c_code/mod.rs b/blackheap-benchmarker/src/c_code/mod.rs new file mode 100644 index 0000000..51431c8 --- /dev/null +++ b/blackheap-benchmarker/src/c_code/mod.rs @@ -0,0 +1,7 @@ +// needed for autogenerated code by bindgen +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +pub mod benchmarker; diff --git a/blackheap-benchmarker/src/c_code/sanitizer_tests/.gitignore b/blackheap-benchmarker/src/c_code/sanitizer_tests/.gitignore new file mode 100644 index 0000000..b883f1f --- /dev/null +++ b/blackheap-benchmarker/src/c_code/sanitizer_tests/.gitignore @@ -0,0 +1 @@ +*.exe diff --git a/blackheap-benchmarker/src/c_code/sanitizer_tests/Makefile b/blackheap-benchmarker/src/c_code/sanitizer_tests/Makefile new file mode 100644 index 0000000..36e1d2c --- /dev/null +++ b/blackheap-benchmarker/src/c_code/sanitizer_tests/Makefile @@ -0,0 +1,22 @@ +all: clean build_asan build_ubsan + +clean: + rm *.exe + +build_asan: + clang -Wall -Wextra -fsanitize=address -g san_test.c ../benchmarker_internal.c -o benchmark_test_asan.exe + +build_ubsan: + clang -Wall -Wextra -fsanitize=undefined -g san_test.c ../benchmarker_internal.c -o benchmark_test_ubsan.exe + +run: run_asan run_ubsan + +run_asan: build_asan + echo "Running with AddressSanitizer (ASan)" + ./benchmark_test_asan.exe + +run_ubsan: build_ubsan + echo "Running with UndefinedBehaviorSanitizer (UBSan)" + ./benchmark_test_ubsan.exe + +.PHONY: all clean build_asan build_ubsan run run_asan run_ubsan diff --git a/blackheap-benchmarker/src/c_code/sanitizer_tests/san_test.c b/blackheap-benchmarker/src/c_code/sanitizer_tests/san_test.c new file mode 100644 index 0000000..76ae5a3 --- /dev/null +++ b/blackheap-benchmarker/src/c_code/sanitizer_tests/san_test.c @@ -0,0 +1,121 @@ +#include "../benchmarker.h" +#include +#include + +void run_benchmark(struct benchmark_config config, const char *description) { + printf("Running benchmark: %s\n", description); + struct benchmark_results results = benchmark_file(&config); + + if (results.res == ERROR_CODES_SUCCESS) { + printf("Benchmark completed successfully.\n"); + printf("Results length: %zu\n", results.length); + /* Print a few result durations */ + for (size_t i = 0; i < results.length && i < 3; ++i) { + printf("Duration for operation %zu: %f seconds\n", i, results.durations[i]); + } + } else { + printf("Benchmark failed with error code: %d\n", results.res); + } + + if (results.durations != NULL) { + free(results.durations); + } + printf("\n"); +} + +int main() { + run_benchmark((struct benchmark_config){ + .filepath = "/tmp/test_file.bin", + .memory_buffer_in_bytes = 1024, + .file_size_in_bytes = 1024 * 10, + .access_size_in_bytes = 128, + .number_of_io_op_tests = 10, + .access_pattern_in_memory = ACCESS_PATTERN_SEQUENTIAL, + .access_pattern_in_file = ACCESS_PATTERN_SEQUENTIAL, + .is_read_operation = true, + .prepare_file_size = true, + .drop_cache_first = false, + .do_reread = false, + .restrict_free_ram_to = 0 + }, "Simple Test"); + + run_benchmark((struct benchmark_config){ + .filepath = "/tmp/test_file.bin", + .memory_buffer_in_bytes = 1024 * 1024 * 512, // 512MB + .file_size_in_bytes = 1024 * 1024 * 1024, // 1GB + .access_size_in_bytes = 1024 * 1024 * 10, // 10MB + .number_of_io_op_tests = 10, + .access_pattern_in_memory = ACCESS_PATTERN_SEQUENTIAL, + .access_pattern_in_file = ACCESS_PATTERN_SEQUENTIAL, + .is_read_operation = true, + .prepare_file_size = true, + .drop_cache_first = false, + .do_reread = false, + .restrict_free_ram_to = 0 + }, "Handle Large Files"); + + run_benchmark((struct benchmark_config){ + .filepath = "/tmp/test_file.bin", + .memory_buffer_in_bytes = 1024 * 512, // 512KB + .file_size_in_bytes = 1024 * 512, // 512KB + .access_size_in_bytes = 1024 * 300, // 300KB + .number_of_io_op_tests = 10, + .access_pattern_in_memory = ACCESS_PATTERN_SEQUENTIAL, + .access_pattern_in_file = ACCESS_PATTERN_SEQUENTIAL, + .is_read_operation = true, + .prepare_file_size = true, + .drop_cache_first = false, + .do_reread = false, + .restrict_free_ram_to = 0 + }, "Can it handle wrapping (seq)"); + + run_benchmark((struct benchmark_config){ + .filepath = "/tmp/test_file.bin", + .memory_buffer_in_bytes = 1024 * 512, // 512KB + .file_size_in_bytes = 1024 * 512, // 512KB + .access_size_in_bytes = 1024 * 300, // 300KB + .number_of_io_op_tests = 10, + .access_pattern_in_memory = ACCESS_PATTERN_RANDOM, + .access_pattern_in_file = ACCESS_PATTERN_RANDOM, + .is_read_operation = true, + .prepare_file_size = true, + .drop_cache_first = false, + .do_reread = false, + .restrict_free_ram_to = 0 + }, "Can it handle wrapping (rnd)"); + + run_benchmark((struct benchmark_config){ + .filepath = "/tmp/test_file.bin", + .memory_buffer_in_bytes = 1024, // 1KB + .file_size_in_bytes = 1024 * 10, // 10KB + .access_size_in_bytes = 1, // 1 byte + .number_of_io_op_tests = 100000, // A lot of accesses + .access_pattern_in_memory = ACCESS_PATTERN_SEQUENTIAL, + .access_pattern_in_file = ACCESS_PATTERN_SEQUENTIAL, + .is_read_operation = true, + .prepare_file_size = true, + .drop_cache_first = false, + .do_reread = false, + .restrict_free_ram_to = 0 + }, "Many access sizes (test asan for leaks)"); + + run_benchmark((struct benchmark_config){ + .filepath = "/dev/shm/test_file.bin", + .memory_buffer_in_bytes = 1024 * 1024, // 1MB + .file_size_in_bytes = 1024 * 1024 * 10, // 10MB + .access_size_in_bytes = 1024 * 10, // 10KB + .number_of_io_op_tests = 100, // Moderate number of accesses + .access_pattern_in_memory = ACCESS_PATTERN_SEQUENTIAL, + .access_pattern_in_file = ACCESS_PATTERN_SEQUENTIAL, + .is_read_operation = true, + .prepare_file_size = true, + .drop_cache_first = false, + .do_reread = false, + .restrict_free_ram_to = 0 + }, "Memory as filesystem with /dev/shm"); + + remove("/tmp/test_file.bin"); + remove("/dev/shm/test_file.bin"); + return 0; +} + diff --git a/blackheap-benchmarker/src/lib.rs b/blackheap-benchmarker/src/lib.rs new file mode 100644 index 0000000..601dea0 --- /dev/null +++ b/blackheap-benchmarker/src/lib.rs @@ -0,0 +1,169 @@ +mod c_code; + +use c_code::benchmarker as b; + +use libc::c_char; +use std::ffi::CString; + +#[derive(Debug, Clone)] +pub enum AccessPattern { + Const, + Sequential, + Random, +} + +impl AccessPattern { + pub fn to_c_code(&self) -> b::access_pattern { + match self { + Self::Const => b::access_pattern_ACCESS_PATTERN_CONST, + Self::Sequential => b::access_pattern_ACCESS_PATTERN_SEQUENTIAL, + Self::Random => b::access_pattern_ACCESS_PATTERN_RANDOM, + } + } + + pub fn from_c_code(n: b::access_pattern) -> Self { + match n { + b::access_pattern_ACCESS_PATTERN_CONST => Self::Const, + b::access_pattern_ACCESS_PATTERN_SEQUENTIAL => Self::Sequential, + b::access_pattern_ACCESS_PATTERN_RANDOM => Self::Random, + _ => { + panic!("Unknown Access Pattern! Probably forgot to update Rust to C logic"); + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ErrorCodes { + Success, + + /* Linux operations that failed */ + MallocFailed, + OpenFailed, + ReadFailed, + WriteFailed, + LseekFailed, + FsyncFailed, + FstatFailed, + IOOpFailed, + RemoveFailed, + + /* High Level Operations */ + DropPageCacheFailedNoPermissions, + DropPageCacheFailedOther, + IncorrectFileBufferSize, +} + +impl ErrorCodes { + pub fn to_c_code(&self) -> b::error_codes { + match self { + Self::Success => b::error_codes_ERROR_CODES_SUCCESS, + Self::MallocFailed => b::error_codes_ERROR_CODES_MALLOC_FAILED, + Self::OpenFailed => b::error_codes_ERROR_CODES_OPEN_FAILED, + Self::ReadFailed => b::error_codes_ERROR_CODES_READ_FAILED, + Self::WriteFailed => b::error_codes_ERROR_CODES_WRITE_FAILED, + Self::LseekFailed => b::error_codes_ERROR_CODES_LSEEK_FAILED, + Self::FsyncFailed => b::error_codes_ERROR_CODES_FSYNC_FAILED, + Self::FstatFailed => b::error_codes_ERROR_CODES_FSTAT_FAILED, + Self::IOOpFailed => b::error_codes_ERROR_CODES_IO_OP_FAILED, + Self::RemoveFailed => b::error_codes_ERROR_CODES_REMOVE_FAILED, + Self::DropPageCacheFailedNoPermissions => { + b::error_codes_ERROR_CODES_DROP_PAGE_CACHE_FAILED_NO_PERMISSIONS + } + Self::DropPageCacheFailedOther => { + b::error_codes_ERROR_CODES_DROP_PAGE_CACHE_FAILED_OTHER + } + Self::IncorrectFileBufferSize => b::error_codes_ERROR_CODES_INCORRECT_FILE_BUFFER_SIZE, + } + } + + pub fn from_c_code(n: b::error_codes) -> Self { + match n { + b::error_codes_ERROR_CODES_SUCCESS => Self::Success, + b::error_codes_ERROR_CODES_MALLOC_FAILED => Self::MallocFailed, + b::error_codes_ERROR_CODES_OPEN_FAILED => Self::OpenFailed, + b::error_codes_ERROR_CODES_READ_FAILED => Self::ReadFailed, + b::error_codes_ERROR_CODES_WRITE_FAILED => Self::WriteFailed, + b::error_codes_ERROR_CODES_LSEEK_FAILED => Self::LseekFailed, + b::error_codes_ERROR_CODES_FSYNC_FAILED => Self::FsyncFailed, + b::error_codes_ERROR_CODES_FSTAT_FAILED => Self::FstatFailed, + b::error_codes_ERROR_CODES_IO_OP_FAILED => Self::IOOpFailed, + b::error_codes_ERROR_CODES_REMOVE_FAILED => Self::RemoveFailed, + b::error_codes_ERROR_CODES_DROP_PAGE_CACHE_FAILED_NO_PERMISSIONS => { + Self::DropPageCacheFailedNoPermissions + } + b::error_codes_ERROR_CODES_DROP_PAGE_CACHE_FAILED_OTHER => { + Self::DropPageCacheFailedOther + } + b::error_codes_ERROR_CODES_INCORRECT_FILE_BUFFER_SIZE => Self::IncorrectFileBufferSize, + _ => panic!("Unknown Error Code! Probably forgot to update Rust to C logic"), + } + } +} + +#[derive(Debug, Clone)] +pub struct BenchmarkConfig { + pub filepath: String, + pub memory_buffer_in_bytes: usize, + pub file_size_in_bytes: usize, + pub access_size_in_bytes: usize, + pub number_of_io_op_tests: usize, + pub access_pattern_in_memory: AccessPattern, + pub access_pattern_in_file: AccessPattern, + pub is_read_operation: bool, + pub prepare_file_size: bool, + pub drop_cache_first: bool, + pub do_reread: bool, + pub restrict_free_ram_to: Option, +} + +impl BenchmarkConfig { + pub fn to_c_code(&self) -> b::benchmark_config { + let filepath_cstr = CString::new(self.filepath.clone()).expect("CString::new failed"); + b::benchmark_config { + filepath: filepath_cstr.into_raw() as *const c_char, + memory_buffer_in_bytes: self.memory_buffer_in_bytes, + file_size_in_bytes: self.file_size_in_bytes, + access_size_in_bytes: self.access_size_in_bytes, + number_of_io_op_tests: self.number_of_io_op_tests, + access_pattern_in_memory: self.access_pattern_in_memory.to_c_code(), + access_pattern_in_file: self.access_pattern_in_file.to_c_code(), + is_read_operation: self.is_read_operation, + prepare_file_size: self.prepare_file_size, + drop_cache_first: self.drop_cache_first, + do_reread: self.do_reread, + restrict_free_ram_to: self.restrict_free_ram_to.unwrap_or(0), + } + } +} + +#[derive(Debug, Clone)] +pub struct BenchmarkResults { + pub res: ErrorCodes, + pub durations: Vec, +} + +impl BenchmarkResults { + unsafe fn from_c_code(c_results: b::benchmark_results) -> Self { + let res = ErrorCodes::from_c_code(c_results.res); + + let durations = if c_results.length > 0 && !c_results.durations.is_null() { + std::slice::from_raw_parts(c_results.durations, c_results.length).to_vec() + } else { + Vec::new() + }; + + libc::free(c_results.durations as *mut libc::c_void); + + BenchmarkResults { res, durations } + } +} + +pub fn benchmark_file(config: &BenchmarkConfig) -> BenchmarkResults { + let c_config = config.to_c_code(); + + unsafe { + let c_results = b::benchmark_file(&c_config); + BenchmarkResults::from_c_code(c_results) + } +} diff --git a/blackheap-frontend/.gitignore b/blackheap-frontend/.gitignore deleted file mode 100644 index 4d29575..0000000 --- a/blackheap-frontend/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/blackheap-frontend/.nvmrc b/blackheap-frontend/.nvmrc deleted file mode 100644 index dac255d..0000000 --- a/blackheap-frontend/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v16.15.1 diff --git a/blackheap-frontend/.prettierignore b/blackheap-frontend/.prettierignore deleted file mode 100644 index 34a1337..0000000 --- a/blackheap-frontend/.prettierignore +++ /dev/null @@ -1,7 +0,0 @@ -# build stuff -build -node_modules - -# cra eject stuff -config -scripts diff --git a/blackheap-frontend/.prettierrc.json b/blackheap-frontend/.prettierrc.json deleted file mode 100644 index 0967ef4..0000000 --- a/blackheap-frontend/.prettierrc.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/blackheap-frontend/README.md b/blackheap-frontend/README.md deleted file mode 100644 index 5815176..0000000 --- a/blackheap-frontend/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Blackheap Frontend for Analysis - -To build a single HTML file, use `yarn build`. - -Based on [Create React App](https://create-react-app.dev/) - -## Current libs - -- Typescript -- React + react-router-dom + react-icons (CRA based) -- Tailwind + DaisyUI -- Plotly -- lodash diff --git a/blackheap-frontend/config/env.js b/blackheap-frontend/config/env.js deleted file mode 100644 index ffa7e49..0000000 --- a/blackheap-frontend/config/env.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const paths = require('./paths'); - -// Make sure that including paths.js after env.js will read .env variables. -delete require.cache[require.resolve('./paths')]; - -const NODE_ENV = process.env.NODE_ENV; -if (!NODE_ENV) { - throw new Error( - 'The NODE_ENV environment variable is required but was not specified.' - ); -} - -// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use -const dotenvFiles = [ - `${paths.dotenv}.${NODE_ENV}.local`, - // Don't include `.env.local` for `test` environment - // since normally you expect tests to produce the same - // results for everyone - NODE_ENV !== 'test' && `${paths.dotenv}.local`, - `${paths.dotenv}.${NODE_ENV}`, - paths.dotenv, -].filter(Boolean); - -// Load environment variables from .env* files. Suppress warnings using silent -// if this file is missing. dotenv will never modify any environment variables -// that have already been set. Variable expansion is supported in .env files. -// https://github.com/motdotla/dotenv -// https://github.com/motdotla/dotenv-expand -dotenvFiles.forEach(dotenvFile => { - if (fs.existsSync(dotenvFile)) { - require('dotenv-expand')( - require('dotenv').config({ - path: dotenvFile, - }) - ); - } -}); - -// We support resolving modules according to `NODE_PATH`. -// This lets you use absolute paths in imports inside large monorepos: -// https://github.com/facebook/create-react-app/issues/253. -// It works similar to `NODE_PATH` in Node itself: -// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders -// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. -// Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. -// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 -// We also resolve them to make sure all tools using them work consistently. -const appDirectory = fs.realpathSync(process.cwd()); -process.env.NODE_PATH = (process.env.NODE_PATH || '') - .split(path.delimiter) - .filter(folder => folder && !path.isAbsolute(folder)) - .map(folder => path.resolve(appDirectory, folder)) - .join(path.delimiter); - -// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be -// injected into the application via DefinePlugin in webpack configuration. -const REACT_APP = /^REACT_APP_/i; - -function getClientEnvironment(publicUrl) { - const raw = Object.keys(process.env) - .filter(key => REACT_APP.test(key)) - .reduce( - (env, key) => { - env[key] = process.env[key]; - return env; - }, - { - // Useful for determining whether we’re running in production mode. - // Most importantly, it switches React into the correct mode. - NODE_ENV: process.env.NODE_ENV || 'development', - // Useful for resolving the correct path to static assets in `public`. - // For example, . - // This should only be used as an escape hatch. Normally you would put - // images into the `src` and `import` them in code to get their paths. - PUBLIC_URL: publicUrl, - // We support configuring the sockjs pathname during development. - // These settings let a developer run multiple simultaneous projects. - // They are used as the connection `hostname`, `pathname` and `port` - // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` - // and `sockPort` options in webpack-dev-server. - WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, - WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, - WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, - // Whether or not react-refresh is enabled. - // It is defined here so it is available in the webpackHotDevClient. - FAST_REFRESH: process.env.FAST_REFRESH !== 'false', - } - ); - // Stringify all values so we can feed into webpack DefinePlugin - const stringified = { - 'process.env': Object.keys(raw).reduce((env, key) => { - env[key] = JSON.stringify(raw[key]); - return env; - }, {}), - }; - - return { raw, stringified }; -} - -module.exports = getClientEnvironment; diff --git a/blackheap-frontend/config/getHttpsConfig.js b/blackheap-frontend/config/getHttpsConfig.js deleted file mode 100644 index 013d493..0000000 --- a/blackheap-frontend/config/getHttpsConfig.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); -const chalk = require('react-dev-utils/chalk'); -const paths = require('./paths'); - -// Ensure the certificate and key provided are valid and if not -// throw an easy to debug error -function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { - let encrypted; - try { - // publicEncrypt will throw an error with an invalid cert - encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); - } catch (err) { - throw new Error( - `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` - ); - } - - try { - // privateDecrypt will throw an error with an invalid key - crypto.privateDecrypt(key, encrypted); - } catch (err) { - throw new Error( - `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ - err.message - }` - ); - } -} - -// Read file and throw an error if it doesn't exist -function readEnvFile(file, type) { - if (!fs.existsSync(file)) { - throw new Error( - `You specified ${chalk.cyan( - type - )} in your env, but the file "${chalk.yellow(file)}" can't be found.` - ); - } - return fs.readFileSync(file); -} - -// Get the https config -// Return cert files if provided in env, otherwise just true or false -function getHttpsConfig() { - const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; - const isHttps = HTTPS === 'true'; - - if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { - const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); - const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); - const config = { - cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), - key: readEnvFile(keyFile, 'SSL_KEY_FILE'), - }; - - validateKeyAndCerts({ ...config, keyFile, crtFile }); - return config; - } - return isHttps; -} - -module.exports = getHttpsConfig; diff --git a/blackheap-frontend/config/jest/babelTransform.js b/blackheap-frontend/config/jest/babelTransform.js deleted file mode 100644 index 5b391e4..0000000 --- a/blackheap-frontend/config/jest/babelTransform.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -const babelJest = require('babel-jest').default; - -const hasJsxRuntime = (() => { - if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { - return false; - } - - try { - require.resolve('react/jsx-runtime'); - return true; - } catch (e) { - return false; - } -})(); - -module.exports = babelJest.createTransformer({ - presets: [ - [ - require.resolve('babel-preset-react-app'), - { - runtime: hasJsxRuntime ? 'automatic' : 'classic', - }, - ], - ], - babelrc: false, - configFile: false, -}); diff --git a/blackheap-frontend/config/jest/cssTransform.js b/blackheap-frontend/config/jest/cssTransform.js deleted file mode 100644 index 8f65114..0000000 --- a/blackheap-frontend/config/jest/cssTransform.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -// This is a custom Jest transformer turning style imports into empty objects. -// http://facebook.github.io/jest/docs/en/webpack.html - -module.exports = { - process() { - return 'module.exports = {};'; - }, - getCacheKey() { - // The output is always the same. - return 'cssTransform'; - }, -}; diff --git a/blackheap-frontend/config/jest/fileTransform.js b/blackheap-frontend/config/jest/fileTransform.js deleted file mode 100644 index aab6761..0000000 --- a/blackheap-frontend/config/jest/fileTransform.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const path = require('path'); -const camelcase = require('camelcase'); - -// This is a custom Jest transformer turning file imports into filenames. -// http://facebook.github.io/jest/docs/en/webpack.html - -module.exports = { - process(src, filename) { - const assetFilename = JSON.stringify(path.basename(filename)); - - if (filename.match(/\.svg$/)) { - // Based on how SVGR generates a component name: - // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 - const pascalCaseFilename = camelcase(path.parse(filename).name, { - pascalCase: true, - }); - const componentName = `Svg${pascalCaseFilename}`; - return `const React = require('react'); - module.exports = { - __esModule: true, - default: ${assetFilename}, - ReactComponent: React.forwardRef(function ${componentName}(props, ref) { - return { - $$typeof: Symbol.for('react.element'), - type: 'svg', - ref: ref, - key: null, - props: Object.assign({}, props, { - children: ${assetFilename} - }) - }; - }), - };`; - } - - return `module.exports = ${assetFilename};`; - }, -}; diff --git a/blackheap-frontend/config/modules.js b/blackheap-frontend/config/modules.js deleted file mode 100644 index d63e41d..0000000 --- a/blackheap-frontend/config/modules.js +++ /dev/null @@ -1,134 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const paths = require('./paths'); -const chalk = require('react-dev-utils/chalk'); -const resolve = require('resolve'); - -/** - * Get additional module paths based on the baseUrl of a compilerOptions object. - * - * @param {Object} options - */ -function getAdditionalModulePaths(options = {}) { - const baseUrl = options.baseUrl; - - if (!baseUrl) { - return ''; - } - - const baseUrlResolved = path.resolve(paths.appPath, baseUrl); - - // We don't need to do anything if `baseUrl` is set to `node_modules`. This is - // the default behavior. - if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { - return null; - } - - // Allow the user set the `baseUrl` to `appSrc`. - if (path.relative(paths.appSrc, baseUrlResolved) === '') { - return [paths.appSrc]; - } - - // If the path is equal to the root directory we ignore it here. - // We don't want to allow importing from the root directly as source files are - // not transpiled outside of `src`. We do allow importing them with the - // absolute path (e.g. `src/Components/Button.js`) but we set that up with - // an alias. - if (path.relative(paths.appPath, baseUrlResolved) === '') { - return null; - } - - // Otherwise, throw an error. - throw new Error( - chalk.red.bold( - "Your project's `baseUrl` can only be set to `src` or `node_modules`." + - ' Create React App does not support other values at this time.' - ) - ); -} - -/** - * Get webpack aliases based on the baseUrl of a compilerOptions object. - * - * @param {*} options - */ -function getWebpackAliases(options = {}) { - const baseUrl = options.baseUrl; - - if (!baseUrl) { - return {}; - } - - const baseUrlResolved = path.resolve(paths.appPath, baseUrl); - - if (path.relative(paths.appPath, baseUrlResolved) === '') { - return { - src: paths.appSrc, - }; - } -} - -/** - * Get jest aliases based on the baseUrl of a compilerOptions object. - * - * @param {*} options - */ -function getJestAliases(options = {}) { - const baseUrl = options.baseUrl; - - if (!baseUrl) { - return {}; - } - - const baseUrlResolved = path.resolve(paths.appPath, baseUrl); - - if (path.relative(paths.appPath, baseUrlResolved) === '') { - return { - '^src/(.*)$': '/src/$1', - }; - } -} - -function getModules() { - // Check if TypeScript is setup - const hasTsConfig = fs.existsSync(paths.appTsConfig); - const hasJsConfig = fs.existsSync(paths.appJsConfig); - - if (hasTsConfig && hasJsConfig) { - throw new Error( - 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' - ); - } - - let config; - - // If there's a tsconfig.json we assume it's a - // TypeScript project and set up the config - // based on tsconfig.json - if (hasTsConfig) { - const ts = require(resolve.sync('typescript', { - basedir: paths.appNodeModules, - })); - config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; - // Otherwise we'll check if there is jsconfig.json - // for non TS projects. - } else if (hasJsConfig) { - config = require(paths.appJsConfig); - } - - config = config || {}; - const options = config.compilerOptions || {}; - - const additionalModulePaths = getAdditionalModulePaths(options); - - return { - additionalModulePaths: additionalModulePaths, - webpackAliases: getWebpackAliases(options), - jestAliases: getJestAliases(options), - hasTsConfig, - }; -} - -module.exports = getModules(); diff --git a/blackheap-frontend/config/paths.js b/blackheap-frontend/config/paths.js deleted file mode 100644 index f0a6cd9..0000000 --- a/blackheap-frontend/config/paths.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; - -const path = require('path'); -const fs = require('fs'); -const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); - -// Make sure any symlinks in the project folder are resolved: -// https://github.com/facebook/create-react-app/issues/637 -const appDirectory = fs.realpathSync(process.cwd()); -const resolveApp = relativePath => path.resolve(appDirectory, relativePath); - -// We use `PUBLIC_URL` environment variable or "homepage" field to infer -// "public path" at which the app is served. -// webpack needs to know it to put the right - - - - - - - - - - - - - - - - - 1. Introduction2. Workflow2.1. Benchmark2.2. Analysis2.3. Model Creation3. Architecture4. Building5. Local Single Node Setup6. Streamed Multi Node Classifications with iofs7. FAQ7.1. How does the Build Process work?7.2. Why do we depend on glibc?7.3. Why the name Blackheap? - - - - - - - - - - - - - - - - - - Light (default) - Rust - Coal - Navy - Ayu - - - - - - - Blackheap documentation - - - - - - - - - - - - - - - - - - - - - - - - - - Document not found (404) -This URL is invalid, sorry. Please use the navigation bar or search to continue. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This URL is invalid, sorry. Please use the navigation bar or search to continue.