diff --git a/README.md b/README.md index fb03d36a..8aa329c9 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,8 @@ cachesim supports the following algorithms: ### One-line install We provide some scripts for quick installation of libCacheSim. ```bash -cd scripts && bash install_dependency.sh && bash install_libcachesim.sh +cd scripts && bash ./install_dependency.sh && source ~/.bashrc && bash ./install_libcachesim.sh +# Replace ~/.bashrc with ~/.zshrc if you are using zsh on macos ``` If this does not work, please 1. let us know what system you are using and what error you get diff --git a/doc/quickstart_plugin.md b/doc/quickstart_plugin.md index 88f81d50..10bf4beb 100644 --- a/doc/quickstart_plugin.md +++ b/doc/quickstart_plugin.md @@ -6,7 +6,7 @@ --- -## 1 . How the Plugin System Works +## 1. How the Plugin System Works A series of hook functions defines the behavior of the custom cache during cache hits and misses. In essence, `libCacheSim` maintains a basic cache that tracks whether an object is a hit or miss, whether the cache is full, and provides hooks accordingly. The actual cache management logic—such as deciding which object(s) to evict on a miss—is entirely delegated to the plugin via these hooks. @@ -49,7 +49,10 @@ Because plugins are completely decoupled from core code you can: --- -## 2 . C/C++ Plugin Development +## 2. C/C++ Plugin Development + +> [!IMPORTANT] +> Before we start, make sure you have followed [Build and Install libCacheSim](../README.md#build-and-install-libcachesim) to build the core *libCacheSim* library. ### 2.1 Required Hook Functions @@ -62,243 +65,281 @@ Your library **must** export the following C-symbols: | `cache_miss_hook` | `void cache_miss_hook(void *data, const request_t *req);` | A requested object is **not** in the cache *after* insertion. | | `cache_eviction_hook` | `obj_id_t cache_eviction_hook(void *data, const request_t *req);` | Cache is full – must return the object-ID to evict. | | `cache_remove_hook` | `void cache_remove_hook(void *data, obj_id_t obj_id);` | An object is explicitly removed (not necessarily due to eviction). | +| `cache_free_hook` | `void cache_free_hook(void *data);` | Plugin resources can be freed. | -The opaque pointer returned by `cache_init_hook` is passed back to every other hook via the `data` parameter, letting your plugin maintain arbitrary state (linked lists, hash maps, statistics, …). For memory safety, your library can export `cache_free_hook` (`void cache_free_hook(void *data);`) to free the resources used by your cache struct according to your demands. +**Notes** +1. The opaque pointer returned by `cache_init_hook` is passed back to every other hook via the `data` parameter, letting your plugin maintain arbitrary state (linked lists, hash maps, statistics, etc). +2. The `cache_free_hook` is optional but recommended for memory safety. ### 2.2 Minimal Plugin Skeleton (C++) -Below is an **abridged** version of the LRU example in `example/plugin_v2/plugin_lru.cpp`. You can copy this as a starting point for your own policy: +Below is a minimal FIFO plugin implementation in C++. You can follow this guide as a starting point for your own policy. Create a new file at `plugins/plugin_fifo.cpp` (you need to create the parent directory as well) and paste the following code: ```cpp -#include // public headers installed by libCacheSim -#include - -class MyPolicy { - /* your data structures */ -public: - MyPolicy() {/*init*/} - void on_hit(obj_id_t id) {/*...*/} - void on_miss(obj_id_t id, uint64_t size) {/*...*/} - obj_id_t evict() {/* decide victim */} - void on_remove(obj_id_t id) {/*...*/} +#include + +#include + +class FifoCache { + private: + std::deque queue_; + uint64_t cache_size_; + + public: + FifoCache(uint64_t cache_size) : cache_size_(cache_size) {} + + void on_hit(obj_id_t id) {} + + void on_miss(obj_id_t id, uint64_t size) { + if (size <= cache_size_) { + queue_.push_back(id); + } + } + + obj_id_t evict() { + if (queue_.empty()) { + return 0; + } + obj_id_t victim = queue_.front(); + queue_.pop_front(); + return victim; + } + + void on_remove(obj_id_t id) { + for (auto it = queue_.begin(); it != queue_.end(); ++it) { + if (*it == id) { + queue_.erase(it); + break; + } + } + } }; extern "C" { -void *cache_init_hook(const common_cache_params_t /*params*/) { - return new MyPolicy(); +void *cache_init_hook(const common_cache_params_t params) { + return new FifoCache(params.cache_size); } void cache_hit_hook(void *data, const request_t *req) { - static_cast(data)->on_hit(req->obj_id); + static_cast(data)->on_hit(req->obj_id); } void cache_miss_hook(void *data, const request_t *req) { - static_cast(data)->on_miss(req->obj_id, req->obj_size); + static_cast(data)->on_miss(req->obj_id, req->obj_size); } obj_id_t cache_eviction_hook(void *data, const request_t * /*req*/) { - return static_cast(data)->evict(); + return static_cast(data)->evict(); } void cache_remove_hook(void *data, obj_id_t obj_id) { - static_cast(data)->on_remove(obj_id); + static_cast(data)->on_remove(obj_id); +} + +void cache_free_hook(void *data) { + FifoCache *fifo_cache = (FifoCache *)data; + delete fifo_cache; } -} // extern "C" +} // extern "C" ``` -*Notes* +**Notes** 1. The plugin can allocate dynamic memory; it will live until the cache is destroyed. -2. Thread safety is up to you – core *libCacheSim* is single-threaded today. +2. Thread safety is up to you - core *libCacheSim* is single-threaded today. +3. Remember to check if objects can fit in the cache before adding them to your data structure in `cache_miss_hook`. Objects larger than the cache size are never inserted into the internal cache, but the hook is still called. ### 2.3 Building the Plugin -#### 2.3.1 Dependencies - -* **CMake ≥ 3.12** (recommended) -* A C/C++ compiler (``gcc``, ``clang``) - -#### 2.3.2 Sample `CMakeLists.txt` +We will use CMake to build the plugin (though any build system that can produce a shared library with the required symbols will work). Create a `CMakeLists.txt` in the `plugins/` directory with the following content: ```cmake cmake_minimum_required(VERSION 3.12) -project(my_cache_plugin CXX C) +project(plugins CXX C) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(GLIB REQUIRED glib-2.0) -# Tell CMake to create a shared library -add_library(plugin_my_policy SHARED plugin_my_policy.cpp) +set(PLUGINS fifo) # Add more plugins here when you create them -# Location of libCacheSim headers – adjust if you installed elsewhere -target_include_directories(plugin_my_policy PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/../../include) +foreach(plugin IN LISTS PLUGINS) + add_library(plugin_${plugin} SHARED plugin_${plugin}.cpp) -# Position-independent code is implicit for shared libs but keep for clarity -set_property(TARGET plugin_my_policy PROPERTY POSITION_INDEPENDENT_CODE ON) + target_include_directories(plugin_${plugin} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../libCacheSim/include + ${GLIB_INCLUDE_DIRS}) -# Optional: strip symbols & set output name -set_target_properties(plugin_my_policy PROPERTIES - OUTPUT_NAME "plugin_my_policy_hooks") + set_target_properties(plugin_${plugin} PROPERTIES + OUTPUT_NAME "plugin_${plugin}_hooks") +endforeach() ``` -#### 2.3.3 Build Commands +Make sure you are currently in the `plugins/` directory. To build your plugin(s), run the following commands: ```bash -mkdir build && cd build -cmake -G Ninja .. # or "cmake .. && make" -ninja # produces libplugin_my_policy_hooks.so +mkdir -p build && cd build/ +cmake -G Ninja .. && ninja ``` -> On macOS the file extension will be `.dylib` instead of `.so`. +This will produce `libplugin_fifo_hooks.so` in the `plugins/build/` directory (or `libplugin_fifo_hooks.dylib` on macOS). ### 2.4 Using the C/C++ Plugin with `cachesim` -1. **Compile** the plugin (`libplugin_my_policy_hooks.so`). -2. **Run** `cachesim` with `pluginCache` **and** supply `plugin_path=`: +If you are in the `plugins/build/` directory, you can run `cachesim` with your plugin like this: ```bash -./bin/cachesim data/cloudPhysicsIO.vscsi vscsi pluginCache 0.01 \ - -e "plugin_path=/absolute/path/libplugin_my_policy_hooks.so,cache_name=myPolicy" +../../_build/bin/cachesim ../../data/cloudPhysicsIO.vscsi vscsi pluginCache 1MB \ + -e "plugin_path=libplugin_fifo_hooks.so" ``` -* Keys after `-e` are comma-separated. Supported keys today: - * `plugin_path` (required) – absolute or relative path to the `.so` / `.dylib`. - * `cache_name` (optional) – override the cache's display name. - * `print` – debug helper: print current parameters and exit. +If you are in other directories, adjust the paths accordingly. -If you omit `cache_name`, the runtime will default to `pluginCache-` for easier identification in logs. +Keys after `-e` are comma-separated. The supported keys today are: +* `plugin_path` (required) – absolute or relative path to the `.so` / `.dylib`. +* `cache_name` (optional) – override the cache's display name. If not provided, the runtime will default to `pluginCache-` for easier identification in logs. +* `print` – debug helper: print current parameters and exit. + +For more information, check `-?/--help` of `libcachesim`: + +```bash +../../_build/bin/cachesim --help +``` --- -## 3 . Python Plugin Development +## 3. Python Plugin Development + +> [!IMPORTANT] +> Before we start, make sure you have installed the Python binding: +> ```bash +> pip3 install libcachesim +> ``` ### 3.1 Required Hook Functions -You need to implement these Python callback functions: +Your Python plugin **must** implement the following callback functions: | Hook | Prototype | Called When | |------|-----------|-------------| -| `init_hook` | `init_hook(cache_size: int) -> Any` | Once at cache creation. Return your data structure. | -| `hit_hook` | `hit_hook(data: Any, obj_id: int, obj_size: int) -> None` | A requested object is found in the cache. | -| `miss_hook` | `miss_hook(data: Any, obj_id: int, obj_size: int) -> None` | A requested object is **not** in the cache *after* insertion. | -| `eviction_hook` | `eviction_hook(data: Any, obj_id: int, obj_size: int) -> int` | Cache is full – must return the object-ID to evict. | -| `remove_hook` | `remove_hook(data: Any, obj_id: int) -> None` | An object is explicitly removed (not necessarily due to eviction). | -| `free_hook` | `free_hook(data: Any) -> None` | [Optional] Final cleanup when cache is destroyed. | +| `cache_init_hook` | `cache_init_hook(common_cache_params: CommonCacheParams) -> Any` | Once at cache creation. Return an opaque object to maintain plugin state. | +| `cache_hit_hook` | `cache_hit_hook(data: Any, req: Request) -> None` | A requested object is found in the cache. | +| `cache_miss_hook` | `cache_miss_hook(data: Any, req: Request) -> None` | A requested object is **not** in the cache *after* insertion. | +| `cache_eviction_hook` | `cache_eviction_hook(data: Any, req: Request) -> int` | Cache is full – must return the object-ID to evict. | +| `cache_remove_hook` | `cache_remove_hook(data: Any, obj_id: int) -> None` | An object is explicitly removed (not necessarily due to eviction). | +| `cache_free_hook` | `cache_free_hook(data: Any) -> None` | Plugin resources can be freed. | + +**Notes** +1. The opaque object returned by `cache_init_hook` is passed back to every other hook via the `data` parameter, letting your plugin maintain arbitrary state (lists, dicts, custom classes, etc). You can replace `Any` with the actual type of your state to get better type hints. +2. The `cache_free_hook` is optionally but recommended. -### 3.2 Example: Custom LRU Implementation +### 3.2 Minimal Plugin Skeleton (Python) + +Below is a minimal FIFO plugin implementation in Python. You can follow this guide as a starting point for your own policy. Create a new file `plugins/plugin_fifo.py` and paste the following code: ```python -import libcachesim as lcs -from collections import OrderedDict +from collections import deque +from libcachesim import CommonCacheParams, Request -# Create a Python hook-based cache -cache = lcs.PythonHookCachePolicy(cache_size=1024*1024, cache_name="MyLRU") -# Define LRU policy hooks -def init_hook(cache_size): - return OrderedDict() # Track access order +class FifoCache: + def __init__(self, cache_size: int): + self.queue = deque() + self.cache_size = cache_size -def hit_hook(lru_dict, obj_id, obj_size): - lru_dict.move_to_end(obj_id) # Move to most recent + def on_hit(self, req: Request): + pass # FIFO doesn't reorder on hit -def miss_hook(lru_dict, obj_id, obj_size): - lru_dict[obj_id] = True # Add to end + def on_miss(self, req: Request): + if req.obj_size <= self.cache_size: + self.queue.append(req.obj_id) -def eviction_hook(lru_dict, obj_id, obj_size): - return next(iter(lru_dict)) # Return least recent + def evict(self, req: Request): + if not self.queue: + return 0 + return self.queue.popleft() -def remove_hook(lru_dict, obj_id): - lru_dict.pop(obj_id, None) + def on_remove(self, obj_id: int): + try: + self.queue.remove(obj_id) + except ValueError: + pass # Object not in queue -# Set the hooks -cache.set_hooks(init_hook, hit_hook, miss_hook, eviction_hook, remove_hook) -# Use it like any other cache -req = lcs.Request(obj_id=1, obj_size=100) -hit = cache.get(req) -print(f"Cache hit: {hit}") # Should be False (miss) -``` +def cache_init_hook(common_cache_params: CommonCacheParams): + return FifoCache(common_cache_params.cache_size) -### 3.3 Example: Custom FIFO Implementation -```python -import libcachesim as lcs -from collections import deque -from contextlib import suppress +def cache_hit_hook(data: FifoCache, req: Request): + data.on_hit(req) -cache = lcs.PythonHookCachePolicy(cache_size=1024, cache_name="CustomFIFO") -def init_hook(cache_size): - return deque() # Use deque for FIFO order +def cache_miss_hook(data: FifoCache, req: Request): + data.on_miss(req) -def hit_hook(fifo_queue, obj_id, obj_size): - pass # FIFO doesn't reorder on hit -def miss_hook(fifo_queue, obj_id, obj_size): - fifo_queue.append(obj_id) # Add to end of queue +def cache_eviction_hook(data: FifoCache, req: Request): + return data.evict(req) -def eviction_hook(fifo_queue, obj_id, obj_size): - return fifo_queue[0] # Return first item (oldest) -def remove_hook(fifo_queue, obj_id): - with suppress(ValueError): - fifo_queue.remove(obj_id) +def cache_remove_hook(data: FifoCache, obj_id: int): + data.on_remove(obj_id) -# Set the hooks and test -cache.set_hooks(init_hook, hit_hook, miss_hook, eviction_hook, remove_hook) -req = lcs.Request(obj_id=1, obj_size=100) -hit = cache.get(req) -print(f"Cache hit: {hit}") # Should be False (miss) +def cache_free_hook(data: FifoCache): + data.queue.clear() ``` -### 3.4 Using Python Plugins +### 3.3 Using the Python Plugin -Python plugins work directly with the Python binding: +There is no build step required for Python plugins. You can use your plugin directly in the same Python script. For instance, you can add the following content after the plugin we just created: ```python -import libcachesim as lcs - -# Create your custom cache policy -cache = lcs.PythonHookCachePolicy(cache_size=1024*1024, cache_name="MyCustomCache") - -# Set your hook functions -cache.set_hooks(init_hook, hit_hook, miss_hook, eviction_hook, remove_hook) - -# Process traces efficiently -reader = lcs.open_trace("./data/cloudPhysicsIO.vscsi", lcs.TraceType.VSCSI_TRACE) -obj_miss_ratio, byte_miss_ratio = cache.process_trace(reader) -print(f"Obj miss ratio: {obj_miss_ratio:.4f}, byte miss ratio: {byte_miss_ratio:.4f}") - -# Or process individual requests -req = lcs.Request(obj_id=1, obj_size=100) -hit = cache.get(req) +if __name__ == "__main__": + from pathlib import Path + from libcachesim import PluginCache, TraceReader, TraceType + + plugin_fifo_cache = PluginCache( + cache_size=1024 * 1024, # 1 MB + cache_init_hook=cache_init_hook, + cache_hit_hook=cache_hit_hook, + cache_miss_hook=cache_miss_hook, + cache_eviction_hook=cache_eviction_hook, + cache_remove_hook=cache_remove_hook, + cache_free_hook=cache_free_hook, + cache_name="fifo", + ) + + trace = Path(__file__).parent.parent / "data" / "cloudPhysicsIO.vscsi" + reader = TraceReader(trace=str(trace), trace_type=TraceType.VSCSI_TRACE) + + req_miss_ratio, byte_miss_ratio = plugin_fifo_cache.process_trace(reader) + print(f"Request miss ratio: {req_miss_ratio:.4f}") + print(f"Byte miss ratio: {byte_miss_ratio:.4f}") ``` ---- +Then simply run this script with `python3`. -## 4 . A full example +--- -A comprehensive C/C++ example lives in `example/plugin_v2`. After building the example plugin: +## 4. More Examples -For Python examples, see the `libCacheSim-python/README.md` file which contains additional examples and benchmarking code. +- C/C++: See [example/plugin_v2/](../example/plugin_v2/) for a more comprehensive example. +- Python: See the [libCacheSim-python](https://github.com/cacheMon/libCacheSim-python/tree/main/examples) repository for more examples. --- -## 5 . Troubleshooting Checklist +## 5. Troubleshooting ### C/C++ Plugin Issues * **Plugin not found?** Verify the path passed via `plugin_path=` is correct, you may want to use absolute path. * **Missing symbols?** Make sure the function names exactly match the prototypes above and are declared `extern "C"` when compiling as C++. * **Link-time errors?** Pass the same architecture flags (`-m64`, etc.) that *libCacheSim* was built with. -* **Runtime crash inside plugin?** Use `gdb -ex r --args cachesim …` and place breakpoints in your hook functions. +* **Runtime crash inside plugin?** Use `gdb -ex r --args cachesim ...` and place breakpoints in your hook functions. ### Python Plugin Issues -* **Import Error**: Make sure libCacheSim C++ library is built first: - ```bash - cmake -G Ninja -B build && ninja -C build - ``` * **Performance Issues**: Use `process_trace()` for large workloads instead of individual `get()` calls for better performance. -* **Memory Usage**: Monitor cache statistics (`cache.occupied_byte`) and ensure proper cache size limits for your system. +* **Memory Usage**: Monitor cache statistics (`cache.get_occupied_byte()`) and ensure proper cache size limits for your system. * **Custom Cache Issues**: Validate your custom implementation against built-in algorithms using test functions. * **Implementation Issues**: When re-implementing an eviction algorithm in libCacheSim using the plugin system, note that the core hook functions are simplified. This may introduce some challenges. The central function for cache simulation is `get` and its common internal logic is: @@ -316,7 +357,7 @@ For Python examples, see the `libCacheSim-python/README.md` file which contains style H fill:#bbf,stroke:#333,stroke-width:2px ``` - Because find is not exposed to plugins, any state-update logic that normally happens inside find must instead be implemented inside the relevant hook functions (cache_hit_hook, cache_eviction_hook, or cache_miss_hook) according to your algorithm’s needs. + Because find is not exposed to plugins, any state-update logic that normally happens inside find must instead be implemented inside the relevant hook functions (`cache_hit_hook`, `cache_eviction_hook`, or `cache_miss_hook`) according to your algorithm's needs. --- diff --git a/example/plugin_v2/CMakeLists.txt b/example/plugin_v2/CMakeLists.txt index 1db97037..79a64536 100644 --- a/example/plugin_v2/CMakeLists.txt +++ b/example/plugin_v2/CMakeLists.txt @@ -21,13 +21,13 @@ message(STATUS "GLIB include: ${GLIB_INCLUDE_DIRS}") # Find libCacheSim find_library(LIBCACHESIM_LIBRARY NAMES CacheSim libCacheSim - PATHS /usr/local/lib /usr/lib + PATHS ../../_build /usr/local/lib /usr/lib DOC "libCacheSim unified library" ) find_path(LIBCACHESIM_INCLUDE_DIR NAMES libCacheSim.h - PATHS /usr/local/include /usr/include + PATHS ../../libCacheSim/include /usr/local/include /usr/include DOC "libCacheSim include directory" ) @@ -43,7 +43,7 @@ add_library(plugin_lru_hooks SHARED plugin_lru.cpp) # Set compiler flags target_compile_options(plugin_lru_hooks PRIVATE - -Wall -Wextra + -Wall -Wextra -Wno-unused-variable -Wno-unused-function -Wno-unused-parameter ) target_include_directories(plugin_lru_hooks PRIVATE diff --git a/example/plugin_v2/README.md b/example/plugin_v2/README.md index 5c856332..77c67b3e 100644 --- a/example/plugin_v2/README.md +++ b/example/plugin_v2/README.md @@ -8,19 +8,17 @@ This example demonstrates how to create a plugin for libCacheSim using the v2 ho - `test_hooks_plugin.c` - Comprehensive test program for the plugin - `CMakeLists.txt` - Build configuration for creating the shared library +## Dependencies + +Make sure you have followed the root README instructions to set up development tools and install libCacheSim. + ## Building To compile the plugin into a shared library: ```bash -# Prerequisites: Install Ninja build system if not already available -# Ubuntu/Debian: sudo apt install ninja-build -# macOS: brew install ninja - -mkdir build -cd build -cmake -G Ninja .. -ninja +mkdir -p build && cd build/ +cmake -G Ninja .. && ninja ``` This will create: @@ -50,9 +48,8 @@ The plugin implements these required hook functions: ### With cachesim Binary ```bash -# Run cachesim with the plugin -./bin/cachesim ../data/cloudPhysicsIO.vscsi vscsi lru,pluginCache 0.01,0.1 \ - -e "plugin_path=/path/to/libCacheSim/example/plugin_v2/build/libplugin_lru_hooks.so" +../../../_build/bin/cachesim ../../../data/cloudPhysicsIO.vscsi vscsi lru,pluginCache 0.01,0.1 \ + -e "plugin_path=libplugin_lru_hooks.so" ``` ### Testing the Plugin @@ -60,24 +57,11 @@ The plugin implements these required hook functions: Run the included test to verify plugin functionality: ```bash -cd build ./test_hooks_plugin ``` The test compares the plugin LRU implementation against libCacheSim's built-in LRU to ensure identical behavior. -## Dependencies - -- **libCacheSim**: Headers and libraries from the main project -- **CMake 3.12+**: Build system - -## Plugin Parameter Format - -When using the plugin with cachesim or other libCacheSim tools, use the format: -``` -plugin_path=/full/path/to/libplugin_lru_hooks.so -``` - ## Implementation Notes - The plugin uses C++ internally but exports C functions for compatibility @@ -89,4 +73,4 @@ plugin_path=/full/path/to/libplugin_lru_hooks.so - Ensure the plugin path uses the `plugin_path=` prefix format - Verify all dependencies are available (check with `ldd libplugin_lru_hooks.so`) -- Use absolute paths when specifying the plugin location +- If relative paths don't work, use absolute paths when specifying the plugin location