├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── FindRAGEL.cmake ├── LICENSE ├── README.md ├── scripts ├── fmt ├── lsirq.py ├── memaslap.py └── mutilate.py ├── sphinxd ├── CMakeLists.txt ├── include │ ├── MurmurHash3.h │ └── sphinx │ │ ├── buffer.h │ │ ├── hardware.h │ │ ├── index.h │ │ ├── logmem.h │ │ ├── memory.h │ │ ├── reactor-epoll.h │ │ ├── reactor.h │ │ ├── spsc_queue.h │ │ └── string.h ├── perf │ ├── buffer_perf.cpp │ ├── logmem_perf.cpp │ ├── main.cpp │ └── protocol_perf.cpp ├── src │ ├── MurmurHash3.cpp │ ├── buffer.cpp │ ├── logmem.cpp │ ├── memory.cpp │ ├── protocol.rl │ ├── reactor-epoll.cpp │ ├── reactor.cpp │ └── sphinxd.cpp └── test │ ├── buffer_test.cpp │ ├── logmem_test.cpp │ ├── main.cpp │ ├── protocol_test.cpp │ ├── spsc_queue_test.cpp │ └── string_test.cpp └── version.h.in /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Mozilla 2 | Standard: Cpp11 3 | Cpp11BracedListStyle: true 4 | ColumnLimit: 100 5 | AllowShortFunctionsOnASingleLine: false 6 | BreakBeforeBraces: Custom 7 | BraceWrapping: 8 | AfterClass: true 9 | AfterControlStatement: false 10 | AfterEnum: true 11 | AfterFunction: true 12 | AfterNamespace: false 13 | AfterStruct: true 14 | AfterUnion: true 15 | BeforeCatch: false 16 | BeforeElse: false 17 | SplitEmptyFunction: true 18 | SplitEmptyNamespace: true 19 | SplitEmptyRecord: true 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | project(Sphinx LANGUAGES CXX) 4 | 5 | enable_testing() 6 | 7 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}) 8 | 9 | find_package(RAGEL 6.6 REQUIRED) 10 | find_package(GTest) 11 | 12 | set(SPHINX_VERSION 0.1.0) 13 | 14 | set(CMAKE_CXX_STANDARD 17) 15 | 16 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer") 17 | 18 | set(CMAKE_CXX_FLAGS "-O3 -g -Wall -Wextra") 19 | 20 | execute_process( 21 | COMMAND git log -1 --format=%h 22 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 23 | OUTPUT_VARIABLE GIT_COMMIT_HASH 24 | OUTPUT_STRIP_TRAILING_WHITESPACE 25 | ) 26 | 27 | configure_file( 28 | ${CMAKE_SOURCE_DIR}/version.h.in 29 | ${CMAKE_BINARY_DIR}/generated/version.h 30 | ) 31 | 32 | include_directories(${CMAKE_BINARY_DIR}/generated) 33 | 34 | add_subdirectory(sphinxd) 35 | -------------------------------------------------------------------------------- /FindRAGEL.cmake: -------------------------------------------------------------------------------- 1 | # - Find Ragel executable and provides macros to generate custom build rules 2 | # The module defines the following variables: 3 | # 4 | # RAGEL_EXECUTABLE - path to the bison program 5 | # RAGEL_VERSION - version of bison 6 | # RAGEL_FOUND - true if the program was found 7 | # 8 | # If ragel is found, the module defines the macros: 9 | # 10 | # RAGEL_TARGET( 11 | # [COMPILE_FLAGS ]) 12 | # 13 | # which will create a custom rule to generate a state machine. is 14 | # the path to a Ragel file. is the name of the source file 15 | # generated by ragel. If COMPILE_FLAGS option is specified, the next 16 | # parameter is added in the bison command line. 17 | # 18 | # The macro defines a set of variables: 19 | # RAGEL_${Name}_DEFINED - true is the macro ran successfully 20 | # RAGEL_${Name}_INPUT - The input source file, an alias for 21 | # RAGEL_${Name}_OUTPUT_SOURCE - The source file generated by ragel 22 | # RAGEL_${Name}_OUTPUT_HEADER - The header file generated by ragel 23 | # RAGEL_${Name}_OUTPUTS - The sources files generated by ragel 24 | # RAGEL_${Name}_COMPILE_FLAGS - Options used in the ragel command line 25 | # 26 | # ==================================================================== 27 | # Example: 28 | # 29 | # find_package(RAGEL) # or e.g.: find_package(RAGEL 6.6 REQUIRED) 30 | # RAGEL_TARGET(MyMachine machine.rl ${CMAKE_CURRENT_BINARY_DIR}/machine.cc) 31 | # add_executable(Foo main.cc ${RAGEL_MyMachine_OUTPUTS}) 32 | # ==================================================================== 33 | 34 | # 2014-02-09, Georg Sauthoff 35 | # 36 | # I don't think that these few lines are even copyrightable material, 37 | # but I am fine with using the BSD/MIT/GPL license on it ... 38 | # 39 | # I've used following references: 40 | # http://www.cmake.org/cmake/help/v2.8.12/cmake.html 41 | # /usr/share/cmake/Modules/FindFLEX.cmake 42 | # /usr/share/cmake/Modules/FindBISON.cmake 43 | 44 | # uses some features which are not available in 2.6 45 | cmake_minimum_required(VERSION 2.8) 46 | 47 | find_program(RAGEL_EXECUTABLE NAMES ragel DOC "path to the ragel executable") 48 | mark_as_advanced(RAGEL_EXECUTABLE) 49 | 50 | if(RAGEL_EXECUTABLE) 51 | 52 | execute_process(COMMAND ${RAGEL_EXECUTABLE} --version 53 | OUTPUT_VARIABLE RAGEL_version_output 54 | ERROR_VARIABLE RAGEL_version_error 55 | RESULT_VARIABLE RAGEL_version_result 56 | OUTPUT_STRIP_TRAILING_WHITESPACE) 57 | 58 | if(${RAGEL_version_result} EQUAL 0) 59 | string(REGEX REPLACE "^Ragel State Machine Compiler version ([^ ]+) .*$" 60 | "\\1" 61 | RAGEL_VERSION "${RAGEL_version_output}") 62 | else() 63 | message(SEND_ERROR 64 | "Command \"${RAGEL_EXECUTABLE} --version\" failed with output: 65 | ${RAGEL_version_error}") 66 | endif() 67 | 68 | #============================================================ 69 | # RAGEL_TARGET (public macro) 70 | #============================================================ 71 | # 72 | macro(RAGEL_TARGET Name Input Output) 73 | set(RAGEL_TARGET_usage 74 | "RAGEL_TARGET( [COMPILE_FLAGS ]") 75 | if(${ARGC} GREATER 3) 76 | if(${ARGC} EQUAL 5) 77 | if("${ARGV3}" STREQUAL "COMPILE_FLAGS") 78 | set(RAGEL_EXECUTABLE_opts "${ARGV4}") 79 | separate_arguments(RAGEL_EXECUTABLE_opts) 80 | else() 81 | message(SEND_ERROR ${RAGEL_TARGET_usage}) 82 | endif() 83 | else() 84 | message(SEND_ERROR ${RAGEL_TARGET_usage}) 85 | endif() 86 | endif() 87 | 88 | add_custom_command(OUTPUT ${Output} 89 | COMMAND ${RAGEL_EXECUTABLE} 90 | ARGS ${RAGEL_EXECUTABLE_opts} -o${Output} ${Input} 91 | DEPENDS ${Input} 92 | COMMENT 93 | "[RAGEL][${Name}] Compiling state machine with Ragel ${RAGEL_VERSION}" 94 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 95 | 96 | set(RAGEL_${Name}_DEFINED TRUE) 97 | set(RAGEL_${Name}_OUTPUTS ${Output}) 98 | set(RAGEL_${Name}_INPUT ${Input}) 99 | set(RAGEL_${Name}_COMPILE_FLAGS ${RAGEL_EXECUTABLE_opts}) 100 | endmacro() 101 | 102 | endif() 103 | 104 | # use this include when module file is located under /usr/share/cmake/Modules 105 | #include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) 106 | # use this include when module file is located in build tree 107 | include(FindPackageHandleStandardArgs) 108 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(RAGEL REQUIRED_VARS RAGEL_EXECUTABLE 109 | VERSION_VAR RAGEL_VERSION) 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sphinx 2 | ====== 3 | 4 | What is Sphinx? 5 | --------------- 6 | 7 | Sphinx is a fast in-memory key-value store that is compatible with the [Memcached](http://memcached.org/) wire protocol. 8 | 9 | Sphinx partitions data between logical cores, similar to MICA (Lim _et al._, 2014), so that a specific core manages each key. Sphinx also partitions connection sockets between cores. If a remote core manages a request key, Sphinx uses message passing to execute the request on that core. To manage key-value pairs, Sphinx uses an in-memory, log-structured memory allocator, similar to RAMCloud (Rumble _et al._, 2014). 10 | 11 | Requirements 12 | ------------ 13 | 14 | To build Sphinx, you need: 15 | 16 | * C++17 compatible compiler, such as [GCC 7](https://gcc.gnu.org/) or [Clang 5](https://clang.llvm.org/) 17 | * [CMake](https://cmake.org/) 18 | * [Ragel](http://www.colm.net/open-source/ragel/) 19 | * [Google Test](https://github.com/google/googletest) 20 | * [Google Benchmark library](https://github.com/google/benchmark) 21 | 22 | Build 23 | ----- 24 | 25 | To build Sphinx, run the following commands: 26 | 27 | mkdir build 28 | cd build 29 | cmake .. 30 | make 31 | 32 | Usage 33 | ----- 34 | 35 | To start Sphinx, run the following command in the build directory: 36 | 37 | sphinxd/sphinxd 38 | 39 | References 40 | ---------- 41 | 42 | Hyeontaek Lim, Dongsu Han, David G. Andersen, and Michael Kaminsky. 2014. MICA: a holistic approach to fast in-memory key-value storage. In _Proceedings of the 11th USENIX Conference on Networked Systems Design and Implementation (NSDI'14)_. USENIX Association, Berkeley, CA, USA, 429-444. 43 | 44 | Stephen M. Rumble, Ankita Kejriwal, and John Ousterhout. 2014. Log-structured memory for DRAM-based storage. In _Proceedings of the 12th USENIX conference on File and Storage Technologies (FAST'14)_. USENIX Association, Berkeley, CA, USA, 1-16. 45 | -------------------------------------------------------------------------------- /scripts/fmt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find sphinxd/ -name "*.h" -o -name "*.cpp" | grep -v MurmurHash3 | xargs -n 1 clang-format -i 4 | -------------------------------------------------------------------------------- /scripts/lsirq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | def get_irq_smp_affinity(irq): 5 | with open("/proc/irq/%s/smp_affinity" % (irq)) as f: 6 | return f.read().strip() 7 | 8 | 9 | def list_irqs(): 10 | with open('/proc/interrupts') as f: 11 | lines = f.readlines() 12 | lines.pop(0) 13 | for raw_line in lines: 14 | line = raw_line.split() 15 | device = line[-1].strip() 16 | irq = line[0].strip()[0:-1] 17 | if not irq.isdigit(): 18 | continue 19 | smp_affinity = get_irq_smp_affinity(irq) 20 | print("%s = %s %s" % (device, irq, smp_affinity)) 21 | 22 | if __name__ == '__main__': 23 | list_irqs() 24 | -------------------------------------------------------------------------------- /scripts/memaslap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # Copyright 2018 The Sphinxd Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import subprocess 18 | import argparse 19 | import time 20 | import sys 21 | import re 22 | import os 23 | 24 | 25 | class Runner: 26 | 27 | def __init__(self, args): 28 | self.args = args 29 | self.ssh_cmd = ["ssh", args.server_host] 30 | 31 | self.server_cmd = list(self.ssh_cmd) 32 | if args.server_cpu_affinity: 33 | self.server_cmd += ["taskset", 34 | "--cpu-list", args.server_cpu_affinity] 35 | self.server_cmd += [args.server_cmd] 36 | self.server_cmd += ['-l', str(args.server_host)] 37 | self.server_cmd += ['-p', str(args.server_tcp_port)] 38 | self.server_cmd += ['-t', str(args.server_threads)] 39 | self.server_cmd += ['-m', str(args.server_memory)] 40 | if args.server_cpu_isolate: 41 | self.server_cmd += ['-i', args.server_cpu_isolate] 42 | 43 | self.server_pattern = os.path.basename(args.server_cmd) 44 | self.pkill_cmd = self.ssh_cmd + ["pkill", self.server_pattern] 45 | 46 | self.sar_start_cmd = self.ssh_cmd + ["sar", "5"] 47 | self.sar_kill_cmd = self.ssh_cmd + ["pkill", "--signal", "INT", "sar"] 48 | 49 | def start_server(self): 50 | print("# server command = %s" % ' '.join(self.server_cmd)) 51 | if not self.args.dry_run: 52 | server_proc = subprocess.Popen(self.server_cmd, stdout=open( 53 | '/dev/null', 'r'), stderr=open('/dev/null', 'r')) 54 | 55 | time.sleep(int(self.args.server_startup_wait)) 56 | 57 | def kill_server(self): 58 | print("# pkill command = %s" % ' '.join(self.pkill_cmd)) 59 | if not self.args.dry_run: 60 | subprocess.call(self.pkill_cmd, stdout=open( 61 | '/dev/null', 'r'), stderr=open('/dev/null', 'r')) 62 | 63 | def start_sar(self): 64 | print("# sar start command = %s" % ' '.join(self.sar_start_cmd)) 65 | if not self.args.dry_run: 66 | self.sar_proc = subprocess.Popen( 67 | self.sar_start_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 68 | 69 | def kill_sar(self): 70 | print("# sar kill command = %s" % ' '.join(self.sar_kill_cmd)) 71 | if not self.args.dry_run: 72 | subprocess.call(self.sar_kill_cmd, stdout=open( 73 | '/dev/null', 'r'), stderr=open('/dev/null', 'r')) 74 | 75 | def sar_output(self): 76 | return self.sar_proc.communicate()[0] 77 | 78 | 79 | def measure(args): 80 | raw_client_conn_range = args.client_connections.split(',') 81 | if len(raw_client_conn_range) == 1: 82 | client_conn_range = range(int(raw_client_conn_range[0])) 83 | elif len(raw_client_conn_range) == 2: 84 | client_conn_range = range( 85 | int(raw_client_conn_range[0]), int(raw_client_conn_range[1])) 86 | elif len(raw_client_conn_range) == 3: 87 | client_conn_range = range(int(raw_client_conn_range[0]), int( 88 | raw_client_conn_range[1]), int(raw_client_conn_range[2])) 89 | else: 90 | raise ValueError( 91 | "unable to parse client connection range: '%s' % args.client_connections") 92 | 93 | output_file = open(args.output, "w") 94 | 95 | outputs = [sys.stdout, output_file] 96 | 97 | for out in outputs: 98 | out.write( 99 | "Sample\tConcurrency\tCPU_user\tCPU_nice\tCPU_system\tCPU_iowait\tCPU_steal\tCPU_idle") 100 | i = 0 101 | for client_host in args.client_host: 102 | out.write("\tTPS_%d\tNet_rate_%d" % (i, i)) 103 | i += 1 104 | out.write("\n") 105 | out.flush() 106 | 107 | runner = Runner(args) 108 | 109 | for client_conn in client_conn_range: 110 | for sample in range(0, args.samples): 111 | runner.kill_server() 112 | 113 | runner.start_server() 114 | 115 | total_concurrency = 0 116 | client_cmds = [] 117 | for client_host in args.client_host: 118 | raw_client_threads = subprocess.check_output( 119 | ['ssh', client_host, 'nproc']) 120 | client_threads = int(raw_client_threads.strip()) 121 | client_concurrency = client_threads * client_conn 122 | 123 | client_cmd = ['ssh', client_host, args.client_cmd] 124 | client_cmd += ['-s', "%s:%d" % 125 | (args.server_host, args.server_tcp_port)] 126 | client_cmd += ['--threads', str(client_threads)] 127 | client_cmd += ['--time', "%ds" % (args.duration)] 128 | client_cmd += ['--concurrency', str(client_concurrency)] 129 | client_cmd += ['--fixed_size', "200"] 130 | client_cmds += [client_cmd] 131 | 132 | total_concurrency += client_concurrency 133 | 134 | if not args.dry_run: 135 | client_procs = [] 136 | for client_cmd in client_cmds: 137 | print("# client command = %s" % ' '.join(client_cmd)) 138 | client_proc = subprocess.Popen( 139 | client_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 140 | client_procs += [client_proc] 141 | runner.start_sar() 142 | for client_proc in client_procs: 143 | client_proc.wait() 144 | runner.kill_sar() 145 | sar_output = str(runner.sar_output()) 146 | 147 | sar_regex = r"Average:.*all\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)" 148 | cpu_user = re.search(sar_regex, sar_output).group(1) 149 | cpu_nice = re.search(sar_regex, sar_output).group(2) 150 | cpu_system = re.search(sar_regex, sar_output).group(3) 151 | cpu_iowait = re.search(sar_regex, sar_output).group(4) 152 | cpu_steal = re.search(sar_regex, sar_output).group(5) 153 | cpu_idle = re.search(sar_regex, sar_output).group(6) 154 | 155 | result = "%d\t%d\t%s\t%s\t%s\t%s\t%s\t%s" % ( 156 | sample + 1, total_concurrency, cpu_user, cpu_nice, cpu_system, cpu_iowait, cpu_steal, cpu_idle) 157 | 158 | for client_proc in client_procs: 159 | raw_client_output = client_proc.communicate()[0] 160 | client_output = str(raw_client_output) 161 | regex = r" TPS: (\d+) Net_rate: (\d+\.\d+)M/s" 162 | tps = re.search(regex, client_output).group(1) 163 | net_rate = re.search(regex, client_output).group(2) 164 | result += "\t%s\t%s" % (tps, net_rate) 165 | 166 | result += "\n" 167 | 168 | for out in outputs: 169 | out.write(result) 170 | out.flush() 171 | 172 | runner.kill_server() 173 | 174 | 175 | def parse_args(): 176 | parser = argparse.ArgumentParser( 177 | description="Benchmark Memcache server using Memaslap tool") 178 | parser.add_argument("--server-host", metavar="HOST", type=str, 179 | required=True, help="host name of the server to benchmark") 180 | parser.add_argument("--server-startup-wait", metavar="TIME", type=int, required=False, 181 | default=5, help="time to wait for server to start up before starting benchmark") 182 | parser.add_argument("--server-cmd", metavar='CMD', type=str, 183 | required=True, help="command to start the server with") 184 | parser.add_argument("--server-tcp-port", metavar='PORT', type=int, required=False, 185 | default=11211, help="TCP port to listen on (default: 11211)") 186 | parser.add_argument("--server-threads", metavar='N', type=int, 187 | required=True, help="number of server threads to use") 188 | parser.add_argument("--server-memory", metavar='SIZE', type=int, 189 | required=True, help="amount of server memory to use in megabytes") 190 | parser.add_argument("--server-cpu-affinity", metavar='LIST', type=str, required=False, default=None, 191 | help="list of processor to run server threads on (default: disabled). For example, use '--server-cpu-affinity 0,2-3', to run server threads on CPUs 0, 2, and 3.") 192 | parser.add_argument("--server-cpu-isolate", metavar='LIST', type=str, required=False, default=None, 193 | help="list of processor to isolate server threads from (default: disabled). For example, use '--server-cpu-isolate 0,2-3', to not run server threads on CPUs 0, 2, and 3.") 194 | parser.add_argument("--client-host", metavar="HOST", type=str, action='append', 195 | required=True, help="host name of the client") 196 | parser.add_argument("--client-cmd", metavar='CMD', type=str, required=True, 197 | help="command to start the client with. For example, use '--client-cmd ./memaslap' to start 'memaslap' executable from local diretory") 198 | parser.add_argument("--client-connections", metavar='RANGE', type=str, required=True, 199 | help="range of number of client connections to use. For example, use '--client-connections 10,21,5', to run with 10, 15, and 20 client connections.") 200 | parser.add_argument("--samples", metavar='N', type=int, required=True, 201 | help="number of samples to measure per client connection count") 202 | parser.add_argument("--duration", metavar='TIME', type=int, required=True, 203 | help="duration to run the measurements for in seconds") 204 | parser.add_argument("--dry-run", action="store_true", 205 | help="print commands to be executed but don't run them") 206 | parser.add_argument("--output", type=str, 207 | required=True, help="Output file") 208 | return parser.parse_args() 209 | 210 | 211 | def main(): 212 | args = parse_args() 213 | 214 | measure(args) 215 | 216 | 217 | if __name__ == '__main__': 218 | main() 219 | -------------------------------------------------------------------------------- /scripts/mutilate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # Copyright 2018 The Sphinxd Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import subprocess 18 | import argparse 19 | import time 20 | import sys 21 | import re 22 | import os 23 | 24 | 25 | class Runner: 26 | 27 | def __init__(self, args): 28 | self.args = args 29 | self.ssh_cmd = ["ssh", args.server_host] 30 | 31 | self.server_cmd = list(self.ssh_cmd) 32 | if args.server_cpu_affinity: 33 | self.server_cmd += ["taskset", 34 | "--cpu-list", args.server_cpu_affinity] 35 | self.server_cmd += [args.server_cmd] 36 | self.server_cmd += ['-p', str(args.server_tcp_port)] 37 | self.server_cmd += ['-t', str(args.server_threads)] 38 | self.server_cmd += ['-m', str(args.server_memory)] 39 | if args.server_cpu_isolate: 40 | self.server_cmd += ['-i', args.server_cpu_isolate] 41 | 42 | self.server_pattern = os.path.basename(args.server_cmd) 43 | self.pkill_cmd = self.ssh_cmd + ["pkill", self.server_pattern] 44 | 45 | def start_server(self): 46 | print("# server command = %s" % ' '.join(self.server_cmd)) 47 | if not self.args.dry_run: 48 | server_proc = subprocess.Popen(self.server_cmd, stdout=open( 49 | '/dev/null', 'r'), stderr=open('/dev/null', 'r')) 50 | time.sleep(int(self.args.server_startup_wait)) 51 | 52 | def kill_server(self): 53 | print("# pkill command = %s" % ' '.join(self.pkill_cmd)) 54 | if not self.args.dry_run: 55 | subprocess.call(self.pkill_cmd, stdout=open( 56 | '/dev/null', 'r'), stderr=open('/dev/null', 'r')) 57 | 58 | 59 | def measure(args): 60 | if args.scenario == 'read': 61 | update_ratio = 0.0 62 | elif args.scenario == 'update': 63 | update_ratio = 1.0 64 | else: 65 | raise ValueError("unable to parse scenario: '%s'" % args.scenario) 66 | 67 | raw_client_conn_range = args.client_connections.split(',') 68 | if len(raw_client_conn_range) == 1: 69 | client_conn_range = range(int(raw_client_conn_range[0])) 70 | elif len(raw_client_conn_range) == 2: 71 | client_conn_range = range( 72 | int(raw_client_conn_range[0]), int(raw_client_conn_range[1])) 73 | elif len(raw_client_conn_range) == 3: 74 | client_conn_range = range(int(raw_client_conn_range[0]), int( 75 | raw_client_conn_range[1]), int(raw_client_conn_range[2])) 76 | else: 77 | raise ValueError( 78 | "unable to parse client connection range: '%s' % args.client_connections") 79 | 80 | output_file = open(args.output, "w") 81 | 82 | outputs = [sys.stdout, output_file] 83 | 84 | for out in outputs: 85 | out.write( 86 | "Sample\tConcurrency\tavg\tstd\tmin\tp1\tp5\tp10\tp20\tp50\tp80\tp90\tp95\tp99\tQPS\tRX\tTX\n") 87 | out.flush() 88 | 89 | runner = Runner(args) 90 | 91 | for client_conn in client_conn_range: 92 | for sample in range(0, args.samples): 93 | runner.start_server() 94 | 95 | client_cmd = [args.client_cmd] 96 | client_cmd += ['-s', "%s:%d" % 97 | (args.server_host, args.server_tcp_port)] 98 | client_cmd += ['-T', str(args.client_threads)] 99 | client_cmd += ['-u', str(update_ratio)] 100 | client_cmd += ['-t', str(args.duration)] 101 | client_cmd += ['-c', str(client_conn)] 102 | print("# client command = %s" % ' '.join(client_cmd)) 103 | 104 | if not args.dry_run: 105 | raw_client_output = subprocess.check_output(client_cmd) 106 | client_output = str(raw_client_output) 107 | 108 | latency_regex = r"%s\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)\s+(\d+.\d+)" % args.scenario 109 | avg = re.search(latency_regex, client_output).group(1) 110 | std = re.search(latency_regex, client_output).group(2) 111 | min_ = re.search(latency_regex, client_output).group(3) 112 | p1 = re.search(latency_regex, client_output).group(4) 113 | p5 = re.search(latency_regex, client_output).group(5) 114 | p10 = re.search(latency_regex, client_output).group(6) 115 | p20 = re.search(latency_regex, client_output).group(7) 116 | p50 = re.search(latency_regex, client_output).group(8) 117 | p80 = re.search(latency_regex, client_output).group(9) 118 | p90 = re.search(latency_regex, client_output).group(10) 119 | p95 = re.search(latency_regex, client_output).group(11) 120 | p99 = re.search(latency_regex, client_output).group(12) 121 | 122 | qps_regex = r"Total QPS = (\d+.\d+)" 123 | qps = re.search(qps_regex, client_output).group(1) 124 | 125 | rx_regex = r"RX\s+\d+ bytes :\s+(\d+.\d+) MB/s" 126 | rx = re.search(rx_regex, client_output).group(1) 127 | 128 | tx_regex = r"TX\s+\d+ bytes :\s+(\d+.\d+) MB/s" 129 | tx = re.search(tx_regex, client_output).group(1) 130 | 131 | result = "%d\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % ( 132 | sample + 1, client_conn, avg, std, min_, p1, p5, p10, p20, p50, p80, p90, p95, p99, qps, rx, tx) 133 | 134 | for out in outputs: 135 | out.write(result) 136 | out.flush() 137 | 138 | runner.kill_server() 139 | 140 | 141 | def parse_args(): 142 | parser = argparse.ArgumentParser( 143 | description="Benchmark Memcache server using Mutilate tool") 144 | parser.add_argument("--server-host", metavar="HOST", type=str, 145 | required=True, help="host name of the server to benchmark") 146 | parser.add_argument("--server-startup-wait", metavar="TIME", type=int, required=False, 147 | default=5, help="time to wait for server to start up before starting benchmark") 148 | parser.add_argument("--server-cmd", metavar='CMD', type=str, 149 | required=True, help="command to start the server with") 150 | parser.add_argument("--server-tcp-port", metavar='PORT', type=int, required=False, 151 | default=11211, help="TCP port to listen on (default: 11211)") 152 | parser.add_argument("--server-threads", metavar='N', type=int, 153 | required=True, help="number of server threads to use") 154 | parser.add_argument("--server-memory", metavar='SIZE', type=int, 155 | required=True, help="amount of server memory to use in megabytes") 156 | parser.add_argument("--server-cpu-affinity", metavar='LIST', type=str, required=False, default=None, 157 | help="list of processor to run server threads on (default: disabled). For example, use '--server-cpu-affinity 0,2-3', to run server threads on CPUs 0, 2, and 3.") 158 | parser.add_argument("--server-cpu-isolate", metavar='LIST', type=str, required=False, default=None, 159 | help="list of processor to isolate server threads from (default: disabled). For example, use '--server-cpu-isolate 0,2-3', to not run server threads on CPUs 0, 2, and 3.") 160 | parser.add_argument("--client-cmd", metavar='CMD', type=str, required=True, 161 | help="command to start the client with. For example, use '--client-cmd ./mutilate' to start 'mutilate' executable from local diretory") 162 | parser.add_argument("--client-threads", metavar='N', type=int, 163 | required=True, help="number of client threads to use") 164 | parser.add_argument("--client-connections", metavar='RANGE', type=str, required=True, 165 | help="range of number of client connections to use. For example, use '--client-connections 10,21,5', to run with 10, 15, and 20 client connections.") 166 | parser.add_argument("--samples", metavar='N', type=int, required=True, 167 | help="number of samples to measure per client connection count") 168 | parser.add_argument("--scenario", metavar='NAME', type=str, required=True, 169 | help="name of benchmark scenario to run (options: 'read' and 'update'") 170 | parser.add_argument("--duration", metavar='TIME', type=int, required=True, 171 | help="duration to run the measurements for in seconds") 172 | parser.add_argument("--dry-run", action="store_true", 173 | help="print commands to be executed but don't run them") 174 | parser.add_argument("--output", type=str, 175 | required=True, help="Output file") 176 | return parser.parse_args() 177 | 178 | 179 | def main(): 180 | args = parse_args() 181 | 182 | measure(args) 183 | 184 | if __name__ == '__main__': 185 | main() 186 | -------------------------------------------------------------------------------- /sphinxd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(BUILD_BENCHMARKS "Build Sphinx benchmarks" OFF) 2 | 3 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/sphinx) 4 | 5 | RAGEL_TARGET(protocol src/protocol.rl ${CMAKE_CURRENT_BINARY_DIR}/include/sphinx/protocol.h) 6 | 7 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 8 | set(SYSTEM_SOURCES src/reactor-epoll.cpp) 9 | else() 10 | message(FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}") 11 | endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 12 | 13 | include_directories( 14 | include 15 | ${CMAKE_CURRENT_BINARY_DIR}/include 16 | ) 17 | 18 | add_executable(sphinxd 19 | src/MurmurHash3.cpp 20 | src/buffer.cpp 21 | src/logmem.cpp 22 | src/memory.cpp 23 | src/reactor.cpp 24 | src/sphinxd.cpp 25 | ${SYSTEM_SOURCES} 26 | ${RAGEL_protocol_OUTPUTS} 27 | ) 28 | target_link_libraries(sphinxd pthread) 29 | 30 | if(GTEST_FOUND) 31 | add_executable(sphinxd_tests 32 | src/MurmurHash3.cpp 33 | src/buffer.cpp 34 | src/logmem.cpp 35 | test/buffer_test.cpp 36 | test/logmem_test.cpp 37 | test/main.cpp 38 | test/protocol_test.cpp 39 | test/spsc_queue_test.cpp 40 | test/string_test.cpp 41 | ${RAGEL_protocol_OUTPUTS} 42 | ) 43 | target_link_libraries(sphinxd_tests ${GTEST_BOTH_LIBRARIES} pthread) 44 | add_test(sphinxd_tests sphinxd_tests) 45 | endif() 46 | 47 | if(BUILD_BENCHMARKS) 48 | add_executable(sphinxd_perf 49 | perf/buffer_perf.cpp 50 | perf/logmem_perf.cpp 51 | perf/main.cpp 52 | perf/protocol_perf.cpp 53 | src/MurmurHash3.cpp 54 | src/buffer.cpp 55 | src/logmem.cpp 56 | src/memory.cpp 57 | ${RAGEL_protocol_OUTPUTS} 58 | ) 59 | target_link_libraries(sphinxd_perf benchmark pthread) 60 | endif() 61 | -------------------------------------------------------------------------------- /sphinxd/include/MurmurHash3.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // MurmurHash3 was written by Austin Appleby, and is placed in the public 3 | // domain. The author hereby disclaims copyright to this source code. 4 | 5 | #ifndef _MURMURHASH3_H_ 6 | #define _MURMURHASH3_H_ 7 | 8 | //----------------------------------------------------------------------------- 9 | // Platform-specific functions and macros 10 | 11 | // Microsoft Visual Studio 12 | 13 | #if defined(_MSC_VER) && (_MSC_VER < 1600) 14 | 15 | typedef unsigned char uint8_t; 16 | typedef unsigned int uint32_t; 17 | typedef unsigned __int64 uint64_t; 18 | 19 | // Other compilers 20 | 21 | #else // defined(_MSC_VER) 22 | 23 | #include 24 | 25 | #endif // !defined(_MSC_VER) 26 | 27 | //----------------------------------------------------------------------------- 28 | 29 | void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out ); 30 | 31 | void MurmurHash3_x86_128 ( const void * key, int len, uint32_t seed, void * out ); 32 | 33 | void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out ); 34 | 35 | //----------------------------------------------------------------------------- 36 | 37 | #endif // _MURMURHASH3_H_ 38 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace sphinx::buffer { 23 | 24 | class Buffer 25 | { 26 | std::vector _data; 27 | 28 | public: 29 | bool is_empty() const; 30 | void append(std::string_view data); 31 | void remove_prefix(size_t n); 32 | const char* data() const; 33 | size_t size() const; 34 | std::string_view string_view() const; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/hardware.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #pragma once 20 | 21 | namespace sphinx { 22 | 23 | namespace hardware { 24 | 25 | static constexpr int cache_line_size = 26 | #if defined(__x86_64__) 27 | 64; 28 | #else 29 | #error "L1 cache line size is not defined for this architecture." 30 | #endif 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/index.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace sphinx::index { 23 | 24 | template 25 | class Index 26 | { 27 | std::unordered_map _index; 28 | 29 | public: 30 | std::optional find(Key key) const 31 | { 32 | auto it = _index.find(key); 33 | if (it != _index.end()) { 34 | return it->second; 35 | } 36 | return std::nullopt; 37 | } 38 | std::optional insert_or_assign(Key key, Value value) 39 | { 40 | auto [it, inserted] = _index.insert_or_assign(key, value); 41 | if (!inserted) { 42 | return it->second; 43 | } 44 | return std::nullopt; 45 | } 46 | void erase(Key key) 47 | { 48 | _index.erase(key); 49 | } 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/logmem.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | /// \defgroup logmem-module Log-structured memory allocator. 32 | /// 33 | /// Log-structured memory allocator manages main memory as a log to improve 34 | /// memory utilization. The allocator manages memory in fixed-size segments, 35 | /// which are arranged as vector of lists, sorted by amount of memory remaining 36 | /// for allocation in the segment. Segments can hold objects of different sizes, 37 | /// which eliminates internal fragmentation (object size being smaller than 38 | /// allocation size) and also reduces external fragmentation (available memory 39 | /// is in such small blocks that objects cannot be allocated from them) because 40 | /// segments are expired in full. 41 | /// 42 | /// The main entry point to the log-structured memory allocator is the \ref 43 | /// Log::append() function, which attempts to append a key-value pair to the 44 | /// log. The function allocates memory one segment at a time. That is, all 45 | /// allocations are satisfied by the same segment until it runs out of memory. 46 | /// Furthermore, the allocator first exhausts all memory it manages before 47 | /// attempting to reclaim space by expiring segments. 48 | 49 | namespace sphinx::logmem { 50 | 51 | /// \addtogroup logmem-module 52 | /// @{ 53 | 54 | /// Object hash type. 55 | using Hash = uint64_t; 56 | 57 | /// Object key type. 58 | using Key = std::string_view; 59 | 60 | /// Object blob type. 61 | using Blob = std::string_view; 62 | 63 | /// An object in a segment of a log. 64 | class Object final 65 | { 66 | uint32_t _key_size; 67 | uint32_t _blob_size; 68 | uint32_t _expiration; 69 | 70 | public: 71 | /// \brief Return the size of an object of \ref key and \ref blob. 72 | static size_t size_of(const Key& key, const Blob& blob); 73 | /// \brief Return the size of an object of \ref key_size and \ref blob_size. 74 | static size_t size_of(size_t key_size, size_t blob_size); 75 | /// \brief Return the hash of \ref key. 76 | static Hash hash_of(const Key& key); 77 | /// \brief Construct a \ref Object instance. 78 | Object(const Key& key, const Blob& blob); 79 | /// \brief Expire object. 80 | void expire(); 81 | /// \brief Return true if object is expired; otherwise return false. 82 | bool is_expired() const; 83 | /// \brief Returns the size of the object in memory. 84 | size_t size() const; 85 | /// \brief Return object key. 86 | Key key() const; 87 | /// \brief Return object blob. 88 | Blob blob() const; 89 | 90 | private: 91 | const char* key_start() const; 92 | const char* blob_start() const; 93 | }; 94 | 95 | /// A segment in a log. 96 | class Segment 97 | { 98 | char* _pos; 99 | char* _end; 100 | 101 | public: 102 | /// \brief Construct a \ref Segment instance. 103 | Segment(size_t size); 104 | /// \brief Return true if segment has no objects; otherwise return false; 105 | bool is_empty() const; 106 | /// \brief Return true if segment is full of objects; otherwise return false; 107 | bool is_full() const; 108 | /// \brief Returns the number of bytes allocated for objects in this segment. 109 | size_t size() const; 110 | /// \brief Returns the number of bytes occupying the segment. 111 | size_t occupancy() const; 112 | /// \brief Returns the number of bytes available in the segment. 113 | size_t remaining() const; 114 | /// \brief Reset the segment into a clean segment. 115 | void reset(); 116 | /// \brief Append an object represented by \ref key and \ref blob to the log. 117 | Object* append(const Key& key, const Blob& blob); 118 | /// \brief Return a pointer to the first object in the segment. 119 | Object* first_object(); 120 | /// \brief Return a pointer to the next object immediatelly following \ref object. 121 | Object* next_object(Object* object); 122 | 123 | private: 124 | char* start(); 125 | const char* start() const; 126 | }; 127 | 128 | struct LogConfig 129 | { 130 | char* memory_ptr; 131 | size_t memory_size; 132 | size_t segment_size; 133 | }; 134 | 135 | /// A log of objects. 136 | class Log 137 | { 138 | sphinx::index::Index _index; 139 | std::vector _segment_ring; 140 | size_t _segment_ring_head = 0; 141 | size_t _segment_ring_tail = 0; 142 | LogConfig _config; 143 | 144 | public: 145 | /// \brief Construct a \ref Log instance. 146 | Log(const LogConfig& config); 147 | /// \brief Find for a blob for a given \ref key from the log. 148 | std::optional find(const Key& key) const; 149 | /// \brief Append a key-blob pair to the log. 150 | bool append(const Key& key, const Blob& blob); 151 | /// \brief Remove the given \ref key from the log. 152 | bool remove(const Key& key); 153 | 154 | private: 155 | bool try_to_append(const Key& key, const Blob& blob); 156 | bool try_to_append(Segment* segment, const Key& key, const Blob& blob); 157 | size_t expire(size_t reclaim_target); 158 | size_t expire(Segment* segment); 159 | size_t segment_index(size_t size); 160 | }; 161 | 162 | /// @} 163 | } 164 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/memory.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | namespace sphinx::memory { 22 | 23 | class Memory 24 | { 25 | void* _addr; 26 | size_t _size; 27 | 28 | public: 29 | static Memory mmap(size_t size); 30 | explicit Memory(void* ptr, size_t size); 31 | ~Memory(); 32 | void* addr() const; 33 | size_t size() const; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/reactor-epoll.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | namespace sphinx::reactor { 22 | 23 | class EpollReactor : public Reactor 24 | { 25 | std::unordered_map> _pollables; 26 | std::unordered_map _epoll_events; 27 | int _epollfd; 28 | 29 | public: 30 | EpollReactor(size_t thread_id, size_t nr_threads, OnMessageFn&& on_message_fn); 31 | ~EpollReactor(); 32 | virtual void accept(std::shared_ptr&& listener) override; 33 | virtual void recv(std::shared_ptr&& socket) override; 34 | virtual void send(std::shared_ptr socket) override; 35 | virtual void close(std::shared_ptr socket) override; 36 | virtual void run() override; 37 | 38 | private: 39 | void update_epoll(Pollable* pollable, uint32_t events); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/reactor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | namespace sphinx::reactor { 32 | 33 | struct SockAddr 34 | { 35 | ::sockaddr_storage addr; 36 | ::socklen_t len; 37 | 38 | SockAddr(::sockaddr_storage addr, ::socklen_t len); 39 | SockAddr(const SockAddr&) = default; 40 | SockAddr(SockAddr&&) = default; 41 | SockAddr& operator=(const SockAddr&) = default; 42 | }; 43 | 44 | using TcpAcceptFn = std::function; 45 | 46 | struct Pollable 47 | { 48 | virtual ~Pollable() 49 | { 50 | } 51 | virtual int fd() const = 0; 52 | virtual void on_pollin() = 0; 53 | virtual bool on_pollout() = 0; 54 | }; 55 | 56 | class Socket : public Pollable 57 | { 58 | protected: 59 | int _sockfd; 60 | 61 | public: 62 | explicit Socket(int sockfd); 63 | virtual ~Socket(); 64 | 65 | int fd() const; 66 | virtual bool send(const char* msg, size_t len, std::optional dst = std::nullopt) = 0; 67 | }; 68 | 69 | class TcpListener : public Pollable 70 | { 71 | int _sockfd; 72 | TcpAcceptFn _accept_fn; 73 | 74 | public: 75 | explicit TcpListener(int sockfd, TcpAcceptFn&& accept_fn); 76 | ~TcpListener(); 77 | 78 | int fd() const override; 79 | 80 | void on_pollin() override; 81 | bool on_pollout() override; 82 | 83 | private: 84 | void accept(); 85 | }; 86 | 87 | std::shared_ptr 88 | make_tcp_listener(const std::string& iface, int port, int backlog, TcpAcceptFn&& recv_fn); 89 | 90 | class TcpSocket; 91 | 92 | using TcpRecvFn = std::function, std::string_view)>; 93 | 94 | class TcpSocket 95 | : public Socket 96 | , public std::enable_shared_from_this 97 | { 98 | TcpRecvFn _recv_fn; 99 | std::vector _tx_buf; 100 | 101 | public: 102 | explicit TcpSocket(int sockfd, TcpRecvFn&& recv_fn); 103 | ~TcpSocket(); 104 | void set_tcp_nodelay(bool nodelay); 105 | bool send(const char* msg, size_t len, std::optional dst = std::nullopt) override; 106 | void on_pollin() override; 107 | bool on_pollout() override; 108 | 109 | private: 110 | void recv(); 111 | }; 112 | 113 | class UdpSocket; 114 | 115 | using UdpRecvFn = 116 | std::function, std::string_view, std::optional)>; 117 | 118 | class UdpSocket 119 | : public Socket 120 | , public std::enable_shared_from_this 121 | { 122 | UdpRecvFn _recv_fn; 123 | 124 | public: 125 | explicit UdpSocket(int sockfd, UdpRecvFn&& recv_fn); 126 | ~UdpSocket(); 127 | bool send(const char* msg, size_t len, std::optional dst) override; 128 | void on_pollin() override; 129 | bool on_pollout() override; 130 | }; 131 | 132 | std::shared_ptr 133 | make_udp_socket(const std::string& iface, int port, UdpRecvFn&& recv_fn); 134 | 135 | using OnMessageFn = std::function; 136 | 137 | constexpr int max_nr_threads = 64; 138 | 139 | class Reactor 140 | { 141 | protected: 142 | static int _efds[max_nr_threads]; 143 | static pthread_t _pthread_ids[max_nr_threads]; 144 | static std::atomic _thread_is_sleeping[max_nr_threads]; 145 | static constexpr int _msg_queue_size = 10000; 146 | static sphinx::spsc::Queue _msg_queues[max_nr_threads][max_nr_threads]; 147 | 148 | int _efd; 149 | size_t _thread_id; 150 | size_t _nr_threads; 151 | std::bitset _pending_wakeups; 152 | OnMessageFn _on_message_fn; 153 | 154 | public: 155 | static std::string default_backend(); 156 | 157 | Reactor(size_t thread_id, size_t nr_threads, OnMessageFn&& on_message_fn); 158 | virtual ~Reactor(); 159 | size_t thread_id() const; 160 | size_t nr_threads() const; 161 | bool send_msg(size_t thread, void* data); 162 | virtual void accept(std::shared_ptr&& listener) = 0; 163 | virtual void recv(std::shared_ptr&& socket) = 0; 164 | virtual void send(std::shared_ptr socket) = 0; 165 | virtual void close(std::shared_ptr socket) = 0; 166 | virtual void run() = 0; 167 | 168 | protected: 169 | void wake_up_pending(); 170 | void wake_up(size_t thread_id); 171 | bool has_messages() const; 172 | bool poll_messages(); 173 | }; 174 | 175 | std::unique_ptr 176 | make_reactor(const std::string& backend, 177 | size_t thread_id, 178 | size_t nr_threads, 179 | OnMessageFn&& on_message_fn); 180 | } 181 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/spsc_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | /// \defgroup spsc-queue-module A bounded, single-producer/single-consumer (SPSC) wait-free and 25 | /// lock-free queue. 26 | /// 27 | /// A SPSC queue is a ring buffer with two indexes to the ring: head and tail. A producer writes new 28 | /// entries in the queue after the current tail and a consumer reads entries from the head. 29 | /// 30 | /// While the algorithm was implemented independently, the following implementations were used as 31 | /// reference: 32 | /// 33 | /// https://www.scylladb.com/2018/02/15/memory-barriers-seastar-linux/ 34 | /// https://github.com/rigtorp/SPSCQueue 35 | /// https://github.com/fsaintjacques/disruptor-- 36 | /// 37 | /// Furthermore, the same algorithm is described in the following paper (including proof of 38 | /// correctness): 39 | /// 40 | /// Lê, N.M., Guatto, A., Cohen, A. and Pop, A., 2013, October. Correct and 41 | /// efficient bounded FIFO queues. In Computer Architecture and High Performance 42 | /// Computing (SBAC-PAD), 2013 25th International Symposium on (pp. 144-151). 43 | /// IEEE. 44 | 45 | namespace sphinx::spsc { 46 | 47 | /// \addtogroup spsc-queue-module 48 | /// @{ 49 | 50 | template 51 | class Queue 52 | { 53 | alignas(hardware::cache_line_size) std::atomic _head = 0; 54 | alignas(hardware::cache_line_size) std::atomic _tail = 0; 55 | std::array _data; 56 | 57 | public: 58 | bool empty() noexcept 59 | { 60 | return _head.load(std::memory_order_acquire) == _tail.load(std::memory_order_acquire); 61 | } 62 | template 63 | bool try_to_emplace(Args&&... args) noexcept 64 | { 65 | auto tail = _tail.load(std::memory_order_relaxed); 66 | auto next_tail = tail + 1; 67 | if (next_tail == N) { 68 | next_tail = 0; 69 | } 70 | if (next_tail == _head.load(std::memory_order_acquire)) { 71 | return false; 72 | } 73 | _data[tail] = T{std::forward(args)...}; 74 | // The release fence here ensures that message is constructed before we update the tail. This 75 | // prevents the consumer from reading stale messages. 76 | _tail.store(next_tail, std::memory_order_release); 77 | return true; 78 | } 79 | T* front() noexcept 80 | { 81 | auto head = _head.load(std::memory_order_relaxed); 82 | if (_tail.load(std::memory_order_acquire) == head) { 83 | return nullptr; 84 | } 85 | return &_data[head]; 86 | } 87 | void pop() noexcept 88 | { 89 | auto head = _head.load(std::memory_order_relaxed); 90 | auto next_head = head + 1; 91 | if (next_head == N) { 92 | next_head = 0; 93 | } 94 | _data[head].~T(); 95 | // The release fence here ensures that message is destructed before we update the head index. 96 | // This prevents the producer from reusing memory for a message, which will be destructed later 97 | // thus corrupting the new message. 98 | _head.store(next_head, std::memory_order_release); 99 | } 100 | }; 101 | 102 | /// @} 103 | } 104 | -------------------------------------------------------------------------------- /sphinxd/include/sphinx/string.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | namespace sphinx { 22 | 23 | inline std::string 24 | to_string(unsigned long n) 25 | { 26 | if (n != 0) { 27 | constexpr auto size = 20; 28 | char ret[size]; 29 | size_t offset = size; 30 | while (n > 0) { 31 | auto digit = n % 10; 32 | n = (n - digit) / 10; 33 | ret[--offset] = '0' + digit; 34 | } 35 | return std::string{ret + offset, size - offset}; 36 | } else { 37 | return "0"; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sphinxd/perf/buffer_perf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | static std::string 22 | make_random(size_t len) 23 | { 24 | auto make_random_char = []() { 25 | static const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 26 | const size_t nr_chars = sizeof(chars) - 1; 27 | return chars[rand() % nr_chars]; 28 | }; 29 | std::string str(len, 0); 30 | std::generate_n(str.begin(), len, make_random_char); 31 | return str; 32 | } 33 | 34 | static void 35 | Buffer_append(benchmark::State& state) 36 | { 37 | using namespace sphinx::buffer; 38 | Buffer buf; 39 | std::string value = make_random(state.range(0)); 40 | for (auto _ : state) { 41 | buf.append(value); 42 | } 43 | } 44 | BENCHMARK(Buffer_append)->RangeMultiplier(2)->Range(8, 8 << 10); 45 | -------------------------------------------------------------------------------- /sphinxd/perf/logmem_perf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | static std::string 26 | make_random(size_t len) 27 | { 28 | auto make_random_char = []() { 29 | static const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 30 | const size_t nr_chars = sizeof(chars) - 1; 31 | return chars[rand() % nr_chars]; 32 | }; 33 | std::string str(len, 0); 34 | std::generate_n(str.begin(), len, make_random_char); 35 | return str; 36 | } 37 | 38 | static void 39 | Log_append_expiring(benchmark::State& state) 40 | { 41 | using namespace sphinx::memory; 42 | using namespace sphinx::logmem; 43 | size_t mem_size = 2 * 1024 * 1024; 44 | size_t segment_size = 1 * 1024 * 1024; 45 | Memory memory = Memory::mmap(mem_size); 46 | LogConfig cfg; 47 | cfg.segment_size = segment_size; 48 | cfg.memory_ptr = reinterpret_cast(memory.addr()); 49 | cfg.memory_size = memory.size(); 50 | Log log{cfg}; 51 | std::string key = make_random(8); 52 | std::string blob = make_random(state.range(0)); 53 | for (auto _ : state) { 54 | log.append(key, blob); 55 | } 56 | } 57 | BENCHMARK(Log_append_expiring)->RangeMultiplier(2)->Range(8, 8 << 10); 58 | -------------------------------------------------------------------------------- /sphinxd/perf/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | BENCHMARK_MAIN(); 20 | -------------------------------------------------------------------------------- /sphinxd/perf/protocol_perf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | static void 22 | Protocol_parse(benchmark::State& state) 23 | { 24 | using namespace sphinx::memcache; 25 | std::string msg = "get QeYm4XMK\r\n"; 26 | for (auto _ : state) { 27 | Parser parser; 28 | parser.parse(msg); 29 | } 30 | } 31 | BENCHMARK(Protocol_parse); 32 | -------------------------------------------------------------------------------- /sphinxd/src/MurmurHash3.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // MurmurHash3 was written by Austin Appleby, and is placed in the public 3 | // domain. The author hereby disclaims copyright to this source code. 4 | 5 | // Note - The x86 and x64 versions do _not_ produce the same results, as the 6 | // algorithms are optimized for their respective platforms. You can still 7 | // compile and run any of them on any platform, but your performance with the 8 | // non-native version will be less than optimal. 9 | 10 | #include "MurmurHash3.h" 11 | 12 | //----------------------------------------------------------------------------- 13 | // Platform-specific functions and macros 14 | 15 | // Microsoft Visual Studio 16 | 17 | #if defined(_MSC_VER) 18 | 19 | #define FORCE_INLINE __forceinline 20 | 21 | #include 22 | 23 | #define ROTL32(x,y) _rotl(x,y) 24 | #define ROTL64(x,y) _rotl64(x,y) 25 | 26 | #define BIG_CONSTANT(x) (x) 27 | 28 | // Other compilers 29 | 30 | #else // defined(_MSC_VER) 31 | 32 | #define FORCE_INLINE inline __attribute__((always_inline)) 33 | 34 | inline uint32_t rotl32 ( uint32_t x, int8_t r ) 35 | { 36 | return (x << r) | (x >> (32 - r)); 37 | } 38 | 39 | inline uint64_t rotl64 ( uint64_t x, int8_t r ) 40 | { 41 | return (x << r) | (x >> (64 - r)); 42 | } 43 | 44 | #define ROTL32(x,y) rotl32(x,y) 45 | #define ROTL64(x,y) rotl64(x,y) 46 | 47 | #define BIG_CONSTANT(x) (x##LLU) 48 | 49 | #endif // !defined(_MSC_VER) 50 | 51 | //----------------------------------------------------------------------------- 52 | // Block read - if your platform needs to do endian-swapping or can only 53 | // handle aligned reads, do the conversion here 54 | 55 | FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i ) 56 | { 57 | return p[i]; 58 | } 59 | 60 | FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i ) 61 | { 62 | return p[i]; 63 | } 64 | 65 | //----------------------------------------------------------------------------- 66 | // Finalization mix - force all bits of a hash block to avalanche 67 | 68 | FORCE_INLINE uint32_t fmix32 ( uint32_t h ) 69 | { 70 | h ^= h >> 16; 71 | h *= 0x85ebca6b; 72 | h ^= h >> 13; 73 | h *= 0xc2b2ae35; 74 | h ^= h >> 16; 75 | 76 | return h; 77 | } 78 | 79 | //---------- 80 | 81 | FORCE_INLINE uint64_t fmix64 ( uint64_t k ) 82 | { 83 | k ^= k >> 33; 84 | k *= BIG_CONSTANT(0xff51afd7ed558ccd); 85 | k ^= k >> 33; 86 | k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); 87 | k ^= k >> 33; 88 | 89 | return k; 90 | } 91 | 92 | //----------------------------------------------------------------------------- 93 | 94 | void MurmurHash3_x86_32 ( const void * key, int len, 95 | uint32_t seed, void * out ) 96 | { 97 | const uint8_t * data = (const uint8_t*)key; 98 | const int nblocks = len / 4; 99 | 100 | uint32_t h1 = seed; 101 | 102 | const uint32_t c1 = 0xcc9e2d51; 103 | const uint32_t c2 = 0x1b873593; 104 | 105 | //---------- 106 | // body 107 | 108 | const uint32_t * blocks = (const uint32_t *)(data + nblocks*4); 109 | 110 | for(int i = -nblocks; i; i++) 111 | { 112 | uint32_t k1 = getblock32(blocks,i); 113 | 114 | k1 *= c1; 115 | k1 = ROTL32(k1,15); 116 | k1 *= c2; 117 | 118 | h1 ^= k1; 119 | h1 = ROTL32(h1,13); 120 | h1 = h1*5+0xe6546b64; 121 | } 122 | 123 | //---------- 124 | // tail 125 | 126 | const uint8_t * tail = (const uint8_t*)(data + nblocks*4); 127 | 128 | uint32_t k1 = 0; 129 | 130 | switch(len & 3) 131 | { 132 | case 3: k1 ^= tail[2] << 16; 133 | case 2: k1 ^= tail[1] << 8; 134 | case 1: k1 ^= tail[0]; 135 | k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; 136 | }; 137 | 138 | //---------- 139 | // finalization 140 | 141 | h1 ^= len; 142 | 143 | h1 = fmix32(h1); 144 | 145 | *(uint32_t*)out = h1; 146 | } 147 | 148 | //----------------------------------------------------------------------------- 149 | 150 | void MurmurHash3_x86_128 ( const void * key, const int len, 151 | uint32_t seed, void * out ) 152 | { 153 | const uint8_t * data = (const uint8_t*)key; 154 | const int nblocks = len / 16; 155 | 156 | uint32_t h1 = seed; 157 | uint32_t h2 = seed; 158 | uint32_t h3 = seed; 159 | uint32_t h4 = seed; 160 | 161 | const uint32_t c1 = 0x239b961b; 162 | const uint32_t c2 = 0xab0e9789; 163 | const uint32_t c3 = 0x38b34ae5; 164 | const uint32_t c4 = 0xa1e38b93; 165 | 166 | //---------- 167 | // body 168 | 169 | const uint32_t * blocks = (const uint32_t *)(data + nblocks*16); 170 | 171 | for(int i = -nblocks; i; i++) 172 | { 173 | uint32_t k1 = getblock32(blocks,i*4+0); 174 | uint32_t k2 = getblock32(blocks,i*4+1); 175 | uint32_t k3 = getblock32(blocks,i*4+2); 176 | uint32_t k4 = getblock32(blocks,i*4+3); 177 | 178 | k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; 179 | 180 | h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; 181 | 182 | k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; 183 | 184 | h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; 185 | 186 | k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; 187 | 188 | h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; 189 | 190 | k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; 191 | 192 | h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; 193 | } 194 | 195 | //---------- 196 | // tail 197 | 198 | const uint8_t * tail = (const uint8_t*)(data + nblocks*16); 199 | 200 | uint32_t k1 = 0; 201 | uint32_t k2 = 0; 202 | uint32_t k3 = 0; 203 | uint32_t k4 = 0; 204 | 205 | switch(len & 15) 206 | { 207 | case 15: k4 ^= tail[14] << 16; 208 | case 14: k4 ^= tail[13] << 8; 209 | case 13: k4 ^= tail[12] << 0; 210 | k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; 211 | 212 | case 12: k3 ^= tail[11] << 24; 213 | case 11: k3 ^= tail[10] << 16; 214 | case 10: k3 ^= tail[ 9] << 8; 215 | case 9: k3 ^= tail[ 8] << 0; 216 | k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; 217 | 218 | case 8: k2 ^= tail[ 7] << 24; 219 | case 7: k2 ^= tail[ 6] << 16; 220 | case 6: k2 ^= tail[ 5] << 8; 221 | case 5: k2 ^= tail[ 4] << 0; 222 | k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; 223 | 224 | case 4: k1 ^= tail[ 3] << 24; 225 | case 3: k1 ^= tail[ 2] << 16; 226 | case 2: k1 ^= tail[ 1] << 8; 227 | case 1: k1 ^= tail[ 0] << 0; 228 | k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; 229 | }; 230 | 231 | //---------- 232 | // finalization 233 | 234 | h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; 235 | 236 | h1 += h2; h1 += h3; h1 += h4; 237 | h2 += h1; h3 += h1; h4 += h1; 238 | 239 | h1 = fmix32(h1); 240 | h2 = fmix32(h2); 241 | h3 = fmix32(h3); 242 | h4 = fmix32(h4); 243 | 244 | h1 += h2; h1 += h3; h1 += h4; 245 | h2 += h1; h3 += h1; h4 += h1; 246 | 247 | ((uint32_t*)out)[0] = h1; 248 | ((uint32_t*)out)[1] = h2; 249 | ((uint32_t*)out)[2] = h3; 250 | ((uint32_t*)out)[3] = h4; 251 | } 252 | 253 | //----------------------------------------------------------------------------- 254 | 255 | void MurmurHash3_x64_128 ( const void * key, const int len, 256 | const uint32_t seed, void * out ) 257 | { 258 | const uint8_t * data = (const uint8_t*)key; 259 | const int nblocks = len / 16; 260 | 261 | uint64_t h1 = seed; 262 | uint64_t h2 = seed; 263 | 264 | const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); 265 | const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); 266 | 267 | //---------- 268 | // body 269 | 270 | const uint64_t * blocks = (const uint64_t *)(data); 271 | 272 | for(int i = 0; i < nblocks; i++) 273 | { 274 | uint64_t k1 = getblock64(blocks,i*2+0); 275 | uint64_t k2 = getblock64(blocks,i*2+1); 276 | 277 | k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; 278 | 279 | h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; 280 | 281 | k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; 282 | 283 | h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; 284 | } 285 | 286 | //---------- 287 | // tail 288 | 289 | const uint8_t * tail = (const uint8_t*)(data + nblocks*16); 290 | 291 | uint64_t k1 = 0; 292 | uint64_t k2 = 0; 293 | 294 | switch(len & 15) 295 | { 296 | case 15: k2 ^= ((uint64_t)tail[14]) << 48; 297 | case 14: k2 ^= ((uint64_t)tail[13]) << 40; 298 | case 13: k2 ^= ((uint64_t)tail[12]) << 32; 299 | case 12: k2 ^= ((uint64_t)tail[11]) << 24; 300 | case 11: k2 ^= ((uint64_t)tail[10]) << 16; 301 | case 10: k2 ^= ((uint64_t)tail[ 9]) << 8; 302 | case 9: k2 ^= ((uint64_t)tail[ 8]) << 0; 303 | k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; 304 | 305 | case 8: k1 ^= ((uint64_t)tail[ 7]) << 56; 306 | case 7: k1 ^= ((uint64_t)tail[ 6]) << 48; 307 | case 6: k1 ^= ((uint64_t)tail[ 5]) << 40; 308 | case 5: k1 ^= ((uint64_t)tail[ 4]) << 32; 309 | case 4: k1 ^= ((uint64_t)tail[ 3]) << 24; 310 | case 3: k1 ^= ((uint64_t)tail[ 2]) << 16; 311 | case 2: k1 ^= ((uint64_t)tail[ 1]) << 8; 312 | case 1: k1 ^= ((uint64_t)tail[ 0]) << 0; 313 | k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; 314 | }; 315 | 316 | //---------- 317 | // finalization 318 | 319 | h1 ^= len; h2 ^= len; 320 | 321 | h1 += h2; 322 | h2 += h1; 323 | 324 | h1 = fmix64(h1); 325 | h2 = fmix64(h2); 326 | 327 | h1 += h2; 328 | h2 += h1; 329 | 330 | ((uint64_t*)out)[0] = h1; 331 | ((uint64_t*)out)[1] = h2; 332 | } 333 | 334 | //----------------------------------------------------------------------------- 335 | 336 | -------------------------------------------------------------------------------- /sphinxd/src/buffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | namespace sphinx::buffer { 20 | 21 | bool 22 | Buffer::is_empty() const 23 | { 24 | return _data.empty(); 25 | } 26 | 27 | void 28 | Buffer::append(std::string_view data) 29 | { 30 | _data.insert(_data.end(), data.data(), data.data() + data.size()); 31 | } 32 | 33 | void 34 | Buffer::remove_prefix(size_t n) 35 | { 36 | _data.erase(_data.begin(), _data.begin() + n); 37 | } 38 | 39 | const char* 40 | Buffer::data() const 41 | { 42 | return _data.data(); 43 | } 44 | 45 | size_t 46 | Buffer::size() const 47 | { 48 | return _data.size(); 49 | } 50 | 51 | std::string_view 52 | Buffer::string_view() const 53 | { 54 | return std::string_view{_data.data(), _data.size()}; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sphinxd/src/logmem.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | namespace sphinx::logmem { 25 | 26 | Object::Object(const Key& key, const Blob& blob) 27 | : _key_size{uint32_t(key.size())} 28 | , _blob_size{uint32_t(blob.size())} 29 | , _expiration{0} 30 | { 31 | std::memcpy(const_cast(key_start()), key.data(), _key_size); 32 | std::memcpy(const_cast(blob_start()), blob.data(), _blob_size); 33 | } 34 | 35 | size_t 36 | Object::size_of(const Key& key, const Blob& blob) 37 | { 38 | return Object::size_of(key.size(), blob.size()); 39 | } 40 | 41 | size_t 42 | Object::size_of(size_t key_size, size_t blob_size) 43 | { 44 | return sizeof(Object) + key_size + blob_size; 45 | } 46 | 47 | Hash 48 | Object::hash_of(const Key& key) 49 | { 50 | uint32_t hash = 0; 51 | MurmurHash3_x86_32(key.data(), key.size(), 1, &hash); 52 | return hash; 53 | } 54 | 55 | size_t 56 | Object::size() const 57 | { 58 | return Object::size_of(_key_size, _blob_size); 59 | } 60 | 61 | void 62 | Object::expire() 63 | { 64 | _expiration = 1; 65 | } 66 | 67 | bool 68 | Object::is_expired() const 69 | { 70 | return _expiration; 71 | } 72 | 73 | Key 74 | Object::key() const 75 | { 76 | return Key{key_start(), _key_size}; 77 | } 78 | 79 | Blob 80 | Object::blob() const 81 | { 82 | return Blob{blob_start(), _blob_size}; 83 | } 84 | 85 | const char* 86 | Object::key_start() const 87 | { 88 | const char* obj_start = reinterpret_cast(this); 89 | return obj_start + sizeof(Object); 90 | } 91 | 92 | const char* 93 | Object::blob_start() const 94 | { 95 | return key_start() + _key_size; 96 | } 97 | 98 | Segment::Segment(size_t size) 99 | : _pos{start()} 100 | , _end{start() + (size - sizeof(Segment))} 101 | { 102 | } 103 | 104 | bool 105 | Segment::is_empty() const 106 | { 107 | return _pos == start(); 108 | } 109 | 110 | bool 111 | Segment::is_full() const 112 | { 113 | return _pos == _end; 114 | } 115 | 116 | size_t 117 | Segment::size() const 118 | { 119 | return _end - start(); 120 | } 121 | 122 | size_t 123 | Segment::occupancy() const 124 | { 125 | return _pos - start(); 126 | } 127 | 128 | size_t 129 | Segment::remaining() const 130 | { 131 | return _end - _pos; 132 | } 133 | 134 | void 135 | Segment::reset() 136 | { 137 | _pos = start(); 138 | } 139 | 140 | Object* 141 | Segment::append(const Key& key, const Blob& blob) 142 | { 143 | size_t object_size = Object::size_of(key, blob); 144 | size_t remaining = _end - _pos; 145 | if (remaining >= object_size) { 146 | Object* object = new (_pos) Object(key, blob); 147 | _pos += object_size; 148 | return object; 149 | } 150 | return nullptr; 151 | } 152 | 153 | Object* 154 | Segment::first_object() 155 | { 156 | if (is_empty()) { 157 | return nullptr; 158 | } 159 | return reinterpret_cast(start()); 160 | } 161 | 162 | Object* 163 | Segment::next_object(Object* object) 164 | { 165 | char* next = reinterpret_cast(object) + object->size(); 166 | if (next >= _pos) { 167 | return nullptr; 168 | } 169 | return reinterpret_cast(next); 170 | } 171 | 172 | char* 173 | Segment::start() 174 | { 175 | return reinterpret_cast(this) + sizeof(Segment); 176 | } 177 | 178 | const char* 179 | Segment::start() const 180 | { 181 | return reinterpret_cast(this) + sizeof(Segment); 182 | } 183 | 184 | Log::Log(const LogConfig& config) 185 | : _config{config} 186 | { 187 | auto seg_size = _config.segment_size; 188 | auto mem_ptr = _config.memory_ptr; 189 | auto mem_size = _config.memory_size; 190 | for (size_t seg_off = 0; seg_off < mem_size; seg_off += seg_size) { 191 | char* seg_ptr = mem_ptr + seg_off; 192 | Segment* seg = new (seg_ptr) Segment(seg_size); 193 | _segment_ring.emplace_back(seg); 194 | } 195 | } 196 | 197 | std::optional 198 | Log::find(const Key& key) const 199 | { 200 | const auto& search = _index.find(key); 201 | if (search) { 202 | return search.value()->blob(); 203 | } 204 | return std::nullopt; 205 | } 206 | 207 | bool 208 | Log::append(const Key& key, const Blob& blob) 209 | { 210 | size_t object_size = Object::size_of(key, blob); 211 | if (object_size > _config.segment_size) { 212 | return false; 213 | } 214 | restart: 215 | if (try_to_append(key, blob)) { 216 | return true; 217 | } 218 | if (expire(object_size) >= object_size) { 219 | goto restart; 220 | } 221 | return false; 222 | } 223 | 224 | bool 225 | Log::try_to_append(const Key& key, const Blob& blob) 226 | { 227 | if (try_to_append(_segment_ring[_segment_ring_tail], key, blob)) { 228 | return true; 229 | } 230 | auto next_tail = _segment_ring_tail + 1; 231 | if (next_tail == _segment_ring.size()) { 232 | next_tail = 0; 233 | } 234 | if (next_tail == _segment_ring_head) { 235 | /* Out of clean segments */ 236 | return false; 237 | } 238 | _segment_ring_tail = next_tail; 239 | return try_to_append(_segment_ring[_segment_ring_tail], key, blob); 240 | } 241 | 242 | bool 243 | Log::try_to_append(Segment* segment, const Key& key, const Blob& blob) 244 | { 245 | Object* object = segment->append(key, blob); 246 | if (!object) { 247 | return false; 248 | } 249 | auto old = _index.insert_or_assign(object->key(), object); 250 | if (old) { 251 | old.value()->expire(); 252 | } 253 | return true; 254 | } 255 | 256 | bool 257 | Log::remove(const Key& key) 258 | { 259 | auto value_opt = _index.find(key); 260 | if (value_opt) { 261 | value_opt.value()->expire(); 262 | _index.erase(key); 263 | return true; 264 | } 265 | return false; 266 | } 267 | 268 | size_t 269 | Log::expire(size_t reclaim_target) 270 | { 271 | size_t nr_reclaimed = 0; 272 | for (;;) { 273 | if (_segment_ring_head == _segment_ring_tail) { 274 | /* No more segments to expire */ 275 | break; 276 | } 277 | nr_reclaimed += expire(_segment_ring[_segment_ring_head]); 278 | _segment_ring_head++; 279 | if (_segment_ring_head == _segment_ring.size()) { 280 | _segment_ring_head = 0; 281 | } 282 | if (nr_reclaimed >= reclaim_target) { 283 | break; 284 | } 285 | } 286 | return nr_reclaimed; 287 | } 288 | 289 | size_t 290 | Log::expire(Segment* seg) 291 | { 292 | Object* obj = seg->first_object(); 293 | while (obj) { 294 | if (!obj->is_expired()) { 295 | _index.erase(obj->key()); 296 | } 297 | obj = seg->next_object(obj); 298 | } 299 | size_t nr_reclaimed = seg->size(); 300 | seg->reset(); 301 | return nr_reclaimed; 302 | } 303 | 304 | template 305 | static inline int 306 | fls(T x) 307 | { 308 | return std::numeric_limits::digits - __builtin_clz(x); 309 | } 310 | 311 | size_t 312 | Log::segment_index(size_t size) 313 | { 314 | return fls(size); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /sphinxd/src/memory.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | 23 | namespace sphinx::memory { 24 | 25 | Memory 26 | Memory::mmap(size_t size) 27 | { 28 | void* addr = 29 | ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_POPULATE, -1, 0); 30 | if (addr == MAP_FAILED) { 31 | throw std::system_error(errno, std::system_category(), "mmap"); 32 | } 33 | return Memory{addr, size}; 34 | } 35 | 36 | Memory::Memory(void* addr, size_t size) 37 | : _addr{addr} 38 | , _size{size} 39 | { 40 | } 41 | 42 | void* 43 | Memory::addr() const 44 | { 45 | return _addr; 46 | } 47 | 48 | size_t 49 | Memory::size() const 50 | { 51 | return _size; 52 | } 53 | 54 | Memory::~Memory() 55 | { 56 | ::munmap(_addr, _size); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sphinxd/src/protocol.rl: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | %%{ 20 | 21 | machine memcache_protocol; 22 | 23 | access _fsm_; 24 | 25 | action key_start { 26 | _key_start = p; 27 | } 28 | 29 | action key_end { 30 | _key_end = p; 31 | } 32 | 33 | action blob_start { 34 | _blob_start = p + 1; 35 | } 36 | 37 | crlf = "\r\n"; 38 | 39 | key = [^ ]+ >key_start %key_end; 40 | 41 | number = digit+ >{ _number = 0; } ${ _number *= 10; _number += fc - '0'; }; 42 | 43 | flags = number %{ _flags = _number; }; 44 | 45 | exptime = number %{ _expiration = _number; }; 46 | 47 | bytes = number %{ _blob_size = _number; }; 48 | 49 | storage_cmd = space key space flags space exptime space bytes space? crlf @blob_start; 50 | 51 | set = "set" space key space flags space exptime space bytes space? crlf @blob_start @{ _op = Opcode::Set; }; 52 | 53 | add = "add" space key space flags space exptime space bytes space? crlf @blob_start @{ _op = Opcode::Add; }; 54 | 55 | replace = "replace" space key space flags space exptime space bytes space? crlf @blob_start @{ _op = Opcode::Replace; }; 56 | 57 | get = "get" space key crlf @{ _op = Opcode::Get; }; 58 | 59 | version = "version" crlf @{ _op = Opcode::Version; }; 60 | 61 | main := (set | add | replace | get | version); 62 | 63 | }%% 64 | 65 | namespace sphinx::memcache { 66 | 67 | %% write data nofinal noprefix; 68 | 69 | enum class Opcode 70 | { 71 | Set, 72 | Add, 73 | Replace, 74 | Get, 75 | Version, 76 | }; 77 | 78 | class Parser 79 | { 80 | int _fsm_cs; 81 | 82 | public: 83 | std::optional _op; 84 | const char* _key_start = nullptr; 85 | const char* _key_end = nullptr; 86 | uint64_t _number = 0; 87 | uint64_t _flags = 0; 88 | uint64_t _expiration = 0; 89 | const char* _blob_start = nullptr; 90 | uint64_t _blob_size = 0; 91 | 92 | Parser() 93 | { 94 | %% write init; 95 | } 96 | 97 | std::string_view key() const 98 | { 99 | std::string_view::size_type key_size = _key_end - _key_start; 100 | return std::string_view{_key_start, key_size}; 101 | } 102 | 103 | size_t parse(std::string_view msg) 104 | { 105 | auto* start = msg.data(); 106 | auto* end = start + msg.size(); 107 | auto* next = parse(start, end); 108 | if (start != next) { 109 | return next - start; 110 | } 111 | return end - start; 112 | } 113 | 114 | private: 115 | const char* parse(const char *p, const char *pe) 116 | { 117 | %% write exec; 118 | return p; 119 | } 120 | }; 121 | 122 | } 123 | -------------------------------------------------------------------------------- /sphinxd/src/reactor-epoll.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | namespace sphinx::reactor { 27 | 28 | class Eventfd : public Pollable 29 | { 30 | int _efd; 31 | 32 | public: 33 | explicit Eventfd(int efd) 34 | : _efd{efd} 35 | { 36 | } 37 | 38 | int fd() const override 39 | { 40 | return _efd; 41 | } 42 | 43 | void on_pollin() override 44 | { 45 | eventfd_t unused; 46 | if (::eventfd_read(_efd, &unused) < 0) { 47 | throw std::system_error(errno, std::system_category(), "eventfd_read"); 48 | } 49 | } 50 | bool on_pollout() override 51 | { 52 | return false; 53 | } 54 | }; 55 | 56 | EpollReactor::EpollReactor(size_t thread_id, size_t nr_threads, OnMessageFn&& on_message_fn) 57 | : Reactor{thread_id, nr_threads, std::move(on_message_fn)} 58 | , _epollfd{::epoll_create1(0)} 59 | { 60 | auto eventfd = std::make_shared(_efd); 61 | update_epoll(eventfd.get(), EPOLLIN); 62 | _pollables.emplace(eventfd->fd(), eventfd); 63 | } 64 | 65 | EpollReactor::~EpollReactor() 66 | { 67 | ::close(_epollfd); 68 | } 69 | 70 | void 71 | EpollReactor::accept(std::shared_ptr&& listener) 72 | { 73 | update_epoll(listener.get(), EPOLLIN); 74 | _pollables.emplace(listener->fd(), std::move(listener)); 75 | } 76 | 77 | void 78 | EpollReactor::recv(std::shared_ptr&& socket) 79 | { 80 | update_epoll(socket.get(), EPOLLIN); 81 | _pollables.emplace(socket->fd(), std::move(socket)); 82 | } 83 | 84 | void 85 | EpollReactor::send(std::shared_ptr socket) 86 | { 87 | update_epoll(socket.get(), EPOLLIN | EPOLLOUT); 88 | _pollables.emplace(socket->fd(), socket); 89 | } 90 | 91 | void 92 | EpollReactor::close(std::shared_ptr socket) 93 | { 94 | _epoll_events.erase(socket->fd()); 95 | if (::epoll_ctl(_epollfd, EPOLL_CTL_DEL, socket->fd(), nullptr) < 0) { 96 | throw std::system_error(errno, std::system_category(), "epoll_ctl"); 97 | } 98 | if (::shutdown(socket->fd(), SHUT_RDWR) < 0) { 99 | if (errno != ENOTCONN) { 100 | throw std::system_error(errno, std::system_category(), "close"); 101 | } 102 | } 103 | _pollables.erase(socket->fd()); 104 | } 105 | 106 | void 107 | EpollReactor::run() 108 | { 109 | std::array events; 110 | for (;;) { 111 | wake_up_pending(); 112 | int nr_events = 0; 113 | if (poll_messages()) { 114 | // We had messages, speculate that there will be more message, and 115 | // therefore do not sleep: 116 | nr_events = ::epoll_wait(_epollfd, events.data(), events.size(), 0); 117 | } else { 118 | // No messages, attempt to sleep: 119 | _thread_is_sleeping[_thread_id].store(true, std::memory_order_seq_cst); 120 | if (has_messages()) { 121 | // Raced with producers, restart: 122 | _thread_is_sleeping[_thread_id].store(false, std::memory_order_seq_cst); 123 | continue; 124 | } 125 | nr_events = ::epoll_wait(_epollfd, events.data(), events.size(), -1); 126 | _thread_is_sleeping[_thread_id].store(false, std::memory_order_seq_cst); 127 | } 128 | if (nr_events == -1 && errno == EINTR) { 129 | continue; 130 | } 131 | if (nr_events < 0) { 132 | throw std::system_error(errno, std::system_category(), "epoll_wait"); 133 | } 134 | for (int i = 0; i < nr_events; i++) { 135 | epoll_event* event = &events[i]; 136 | auto fd = event->data.fd; 137 | auto it = _pollables.find(fd); 138 | if (it == _pollables.end()) { 139 | ::epoll_ctl(_epollfd, EPOLL_CTL_DEL, fd, nullptr); 140 | continue; 141 | } 142 | auto pollable = it->second; 143 | if (event->events & EPOLLIN) { 144 | pollable->on_pollin(); 145 | } 146 | if (event->events & EPOLLOUT) { 147 | if (pollable->on_pollout()) { 148 | update_epoll(pollable.get(), EPOLLIN); 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | void 156 | EpollReactor::update_epoll(Pollable* pollable, uint32_t events) 157 | { 158 | int op = EPOLL_CTL_ADD; 159 | auto it = _epoll_events.find(pollable->fd()); 160 | if (it != _epoll_events.end()) { 161 | if (events == it->second) { 162 | return; 163 | } 164 | op = EPOLL_CTL_MOD; 165 | } 166 | ::epoll_event ev = {}; 167 | ev.data.fd = pollable->fd(); 168 | ev.events = events; 169 | if (::epoll_ctl(_epollfd, op, pollable->fd(), &ev) < 0) { 170 | throw std::system_error(errno, std::system_category(), "epoll_ctl"); 171 | } 172 | _epoll_events.insert_or_assign(pollable->fd(), events); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /sphinxd/src/reactor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | namespace sphinx::reactor { 36 | 37 | SockAddr::SockAddr(::sockaddr_storage addr, ::socklen_t len) 38 | : addr{addr} 39 | , len{len} 40 | { 41 | } 42 | 43 | Socket::Socket(int sockfd) 44 | : _sockfd{sockfd} 45 | { 46 | } 47 | 48 | Socket::~Socket() 49 | { 50 | ::close(_sockfd); 51 | } 52 | 53 | int 54 | Socket::fd() const 55 | { 56 | return _sockfd; 57 | } 58 | 59 | TcpListener::TcpListener(int sockfd, TcpAcceptFn&& accept_fn) 60 | : _sockfd{sockfd} 61 | , _accept_fn{accept_fn} 62 | { 63 | } 64 | 65 | TcpListener::~TcpListener() 66 | { 67 | ::close(_sockfd); 68 | } 69 | 70 | void 71 | TcpListener::on_pollin() 72 | { 73 | accept(); 74 | } 75 | 76 | bool 77 | TcpListener::on_pollout() 78 | { 79 | return true; 80 | } 81 | 82 | void 83 | TcpListener::accept() 84 | { 85 | int connfd = ::accept4(_sockfd, nullptr, nullptr, SOCK_NONBLOCK); 86 | if (connfd < 0) { 87 | throw std::system_error(errno, std::system_category(), "accept4"); 88 | } 89 | _accept_fn(connfd); 90 | } 91 | 92 | int 93 | TcpListener::fd() const 94 | { 95 | return _sockfd; 96 | } 97 | 98 | static addrinfo* 99 | lookup_addresses(const std::string& iface, int port, int sock_type) 100 | { 101 | addrinfo hints; 102 | memset(&hints, 0, sizeof(hints)); 103 | hints.ai_family = AF_INET; 104 | hints.ai_socktype = sock_type; 105 | hints.ai_protocol = 0; 106 | hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; 107 | addrinfo* ret = nullptr; 108 | int err = getaddrinfo(iface.c_str(), std::to_string(port).c_str(), &hints, &ret); 109 | if (err != 0) { 110 | throw std::runtime_error("'" + iface + "': " + gai_strerror(err)); 111 | } 112 | return ret; 113 | } 114 | 115 | std::shared_ptr 116 | make_tcp_listener(const std::string& iface, int port, int backlog, TcpAcceptFn&& accept_fn) 117 | { 118 | auto* addresses = lookup_addresses(iface, port, SOCK_STREAM); 119 | for (addrinfo* rp = addresses; rp != NULL; rp = rp->ai_next) { 120 | int sockfd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 121 | if (sockfd < 0) { 122 | continue; 123 | } 124 | int one = 1; 125 | ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &one, sizeof(one)); 126 | if (::bind(sockfd, rp->ai_addr, rp->ai_addrlen) < 0) { 127 | ::close(sockfd); 128 | continue; 129 | } 130 | if (::listen(sockfd, backlog) < 0) { 131 | ::close(sockfd); 132 | continue; 133 | } 134 | freeaddrinfo(addresses); 135 | return std::make_shared(sockfd, std::move(accept_fn)); 136 | } 137 | freeaddrinfo(addresses); 138 | throw std::runtime_error("Failed to listen to interface: '" + iface + "'"); 139 | } 140 | 141 | TcpSocket::TcpSocket(int sockfd, TcpRecvFn&& recv_fn) 142 | : Socket{sockfd} 143 | , _recv_fn{recv_fn} 144 | { 145 | } 146 | 147 | TcpSocket::~TcpSocket() 148 | { 149 | } 150 | 151 | void 152 | TcpSocket::set_tcp_nodelay(bool nodelay) 153 | { 154 | int value = nodelay; 155 | if (setsockopt(_sockfd, SOL_TCP, TCP_NODELAY, &value, sizeof(value)) < 0) { 156 | throw std::system_error(errno, std::system_category(), "setsockopt"); 157 | } 158 | } 159 | 160 | bool 161 | TcpSocket::send(const char* msg, size_t len, [[gnu::unused]] std::optional dst) 162 | { 163 | if (!_tx_buf.empty()) { 164 | _tx_buf.insert(_tx_buf.end(), msg, msg + len); 165 | return false; 166 | } 167 | ssize_t nr = ::send(_sockfd, msg, len, MSG_NOSIGNAL | MSG_DONTWAIT); 168 | if ((nr < 0) && (errno == ECONNRESET || errno == EPIPE)) { 169 | return true; 170 | } 171 | if ((nr < 0) && (errno == EAGAIN || errno == EWOULDBLOCK)) { 172 | _tx_buf.insert(_tx_buf.end(), msg, msg + len); 173 | return false; 174 | } 175 | if (nr < 0) { 176 | throw std::system_error(errno, std::system_category(), "send"); 177 | } 178 | if (size_t(nr) < len) { 179 | _tx_buf.insert(_tx_buf.end(), msg + nr, msg + (len - nr)); 180 | return false; 181 | } 182 | return true; 183 | } 184 | 185 | void 186 | TcpSocket::on_pollin() 187 | { 188 | constexpr size_t rx_buf_size = 256 * 1024; 189 | std::array rx_buf; 190 | ssize_t nr = ::recv(_sockfd, rx_buf.data(), rx_buf.size(), MSG_DONTWAIT); 191 | if ((nr < 0 && errno == ECONNRESET)) { 192 | _recv_fn(this->shared_from_this(), std::string_view{}); 193 | return; 194 | } 195 | if (nr < 0) { 196 | throw std::system_error(errno, std::system_category(), "recv"); 197 | } 198 | _recv_fn(this->shared_from_this(), 199 | std::string_view{rx_buf.data(), std::string_view::size_type(nr)}); 200 | } 201 | 202 | bool 203 | TcpSocket::on_pollout() 204 | { 205 | if (_tx_buf.empty()) { 206 | return true; 207 | } 208 | ssize_t nr = ::send(_sockfd, _tx_buf.data(), _tx_buf.size(), MSG_NOSIGNAL | MSG_DONTWAIT); 209 | if ((nr < 0) && (errno == ECONNRESET || errno == EPIPE)) { 210 | return true; 211 | } 212 | if ((nr < 0) && (errno == EAGAIN || errno == EWOULDBLOCK)) { 213 | return false; 214 | } 215 | if (nr < 0) { 216 | throw std::system_error(errno, std::system_category(), "send"); 217 | } 218 | _tx_buf.erase(_tx_buf.begin(), _tx_buf.begin() + nr); 219 | return _tx_buf.empty(); 220 | } 221 | 222 | UdpSocket::UdpSocket(int sockfd, UdpRecvFn&& recv_fn) 223 | : Socket{sockfd} 224 | , _recv_fn{recv_fn} 225 | { 226 | } 227 | 228 | UdpSocket::~UdpSocket() 229 | { 230 | } 231 | 232 | bool 233 | UdpSocket::send(const char* msg, size_t len, std::optional dst) 234 | { 235 | ssize_t nr = ::sendto(_sockfd, 236 | msg, 237 | len, 238 | MSG_NOSIGNAL | MSG_DONTWAIT, 239 | reinterpret_cast<::sockaddr*>(&dst->addr), 240 | dst->len); 241 | if ((nr < 0) && (errno == ECONNRESET || errno == EPIPE)) { 242 | return true; 243 | } 244 | if (nr < 0) { 245 | throw std::system_error(errno, std::system_category(), "send"); 246 | } 247 | if (size_t(nr) != len) { 248 | throw std::runtime_error("partial send"); 249 | } 250 | return true; 251 | } 252 | 253 | void 254 | UdpSocket::on_pollin() 255 | { 256 | constexpr size_t rx_buf_size = 256 * 1024; 257 | std::array rx_buf; 258 | ::sockaddr_storage src_addr; 259 | ::socklen_t src_addr_len = sizeof(src_addr); 260 | ssize_t nr = ::recvfrom(_sockfd, 261 | rx_buf.data(), 262 | rx_buf.size(), 263 | MSG_DONTWAIT, 264 | reinterpret_cast<::sockaddr*>(&src_addr), 265 | &src_addr_len); 266 | if ((nr < 0 && errno == ECONNRESET)) { 267 | _recv_fn(this->shared_from_this(), std::string_view{}, std::nullopt); 268 | return; 269 | } 270 | if (nr < 0) { 271 | throw std::system_error(errno, std::system_category(), "recvfrom"); 272 | } 273 | SockAddr src{src_addr, src_addr_len}; 274 | _recv_fn(this->shared_from_this(), 275 | std::string_view{rx_buf.data(), std::string_view::size_type(nr)}, 276 | src); 277 | } 278 | 279 | bool 280 | UdpSocket::on_pollout() 281 | { 282 | return true; 283 | } 284 | 285 | std::shared_ptr 286 | make_udp_socket(const std::string& iface, int port, UdpRecvFn&& recv_fn) 287 | { 288 | auto* addresses = lookup_addresses(iface, port, SOCK_DGRAM); 289 | for (addrinfo* rp = addresses; rp != NULL; rp = rp->ai_next) { 290 | int sockfd = ::socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol); 291 | if (sockfd < 0) { 292 | continue; 293 | } 294 | int one = 1; 295 | ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &one, sizeof(one)); 296 | if (::bind(sockfd, rp->ai_addr, rp->ai_addrlen) < 0) { 297 | ::close(sockfd); 298 | continue; 299 | } 300 | freeaddrinfo(addresses); 301 | return std::make_shared(sockfd, std::move(recv_fn)); 302 | } 303 | freeaddrinfo(addresses); 304 | throw std::runtime_error("Failed to listen to interface: '" + iface + "'"); 305 | } 306 | 307 | int Reactor::_efds[max_nr_threads]; 308 | pthread_t Reactor::_pthread_ids[max_nr_threads]; 309 | std::atomic Reactor::_thread_is_sleeping[max_nr_threads]; 310 | sphinx::spsc::Queue Reactor::_msg_queues[max_nr_threads] 311 | [max_nr_threads]; 312 | 313 | std::string 314 | Reactor::default_backend() 315 | { 316 | return "epoll"; 317 | } 318 | 319 | Reactor::Reactor(size_t thread_id, size_t nr_threads, OnMessageFn&& on_message_fn) 320 | : _efd{::eventfd(0, EFD_NONBLOCK)} 321 | , _thread_id{thread_id} 322 | , _nr_threads{nr_threads} 323 | , _on_message_fn{on_message_fn} 324 | { 325 | _efds[_thread_id] = _efd; 326 | _thread_is_sleeping[_thread_id].store(false, std::memory_order_seq_cst); 327 | _pthread_ids[_thread_id] = pthread_self(); 328 | } 329 | 330 | Reactor::~Reactor() 331 | { 332 | } 333 | 334 | size_t 335 | Reactor::thread_id() const 336 | { 337 | return _thread_id; 338 | } 339 | 340 | size_t 341 | Reactor::nr_threads() const 342 | { 343 | return _nr_threads; 344 | } 345 | 346 | bool 347 | Reactor::send_msg(size_t remote_id, void* msg) 348 | { 349 | if (remote_id == _thread_id) { 350 | throw std::invalid_argument("Attempting to send message to self"); 351 | } 352 | auto& queue = _msg_queues[remote_id][_thread_id]; 353 | if (!queue.try_to_emplace(msg)) { 354 | return false; 355 | } 356 | _pending_wakeups.set(remote_id); 357 | return true; 358 | } 359 | 360 | void 361 | Reactor::wake_up_pending() 362 | { 363 | for (size_t id = 0; id < _pending_wakeups.size(); id++) { 364 | if (_pending_wakeups.test(id)) { 365 | if (_thread_is_sleeping[id].load(std::memory_order_seq_cst)) { 366 | _thread_is_sleeping[id].store(false, std::memory_order_seq_cst); 367 | wake_up(id); 368 | } 369 | } 370 | } 371 | _pending_wakeups.reset(); 372 | } 373 | 374 | void 375 | Reactor::wake_up(size_t thread_id) 376 | { 377 | if (::eventfd_write(_efds[thread_id], 1) < 0) { 378 | throw std::system_error(errno, std::system_category(), "eventfd_write"); 379 | } 380 | } 381 | 382 | bool 383 | Reactor::has_messages() const 384 | { 385 | for (size_t other = 0; other < _nr_threads; other++) { 386 | if (other == _thread_id) { 387 | continue; 388 | } 389 | auto& queue = _msg_queues[_thread_id][other]; 390 | for (;;) { 391 | auto* msg = queue.front(); 392 | if (!msg) { 393 | break; 394 | } 395 | return true; 396 | } 397 | } 398 | return false; 399 | } 400 | 401 | bool 402 | Reactor::poll_messages() 403 | { 404 | bool has_messages = false; 405 | for (size_t other = 0; other < _nr_threads; other++) { 406 | if (other == _thread_id) { 407 | continue; 408 | } 409 | auto& queue = _msg_queues[_thread_id][other]; 410 | for (;;) { 411 | auto* msg = queue.front(); 412 | if (!msg) { 413 | break; 414 | } 415 | has_messages |= true; 416 | _on_message_fn(*msg); 417 | queue.pop(); 418 | } 419 | } 420 | return has_messages; 421 | } 422 | 423 | std::unique_ptr 424 | make_reactor(const std::string& backend, 425 | size_t thread_id, 426 | size_t nr_threads, 427 | OnMessageFn&& on_message_fn) 428 | { 429 | if (backend == "epoll") { 430 | return std::make_unique(thread_id, nr_threads, std::move(on_message_fn)); 431 | } else { 432 | throw std::invalid_argument("unrecognized '" + backend + "' backend"); 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /sphinxd/src/sphinxd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include // FIXME 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "version.h" 37 | 38 | static std::string program; 39 | 40 | static constexpr int DEFAULT_TCP_PORT = 11211; 41 | static constexpr int DEFAULT_UDP_PORT = 0; /* disabled */ 42 | static constexpr const char* DEFAULT_LISTEN_ADDR = "0.0.0.0"; 43 | static constexpr int DEFAULT_MEMORY_LIMIT = 64; 44 | static constexpr int DEFAULT_SEGMENT_SIZE = 2; 45 | static constexpr int DEFAULT_LISTEN_BACKLOG = 1024; 46 | static constexpr int DEFAULT_NR_THREADS = 4; 47 | 48 | struct Args 49 | { 50 | std::string listen_addr = DEFAULT_LISTEN_ADDR; 51 | int tcp_port = DEFAULT_TCP_PORT; 52 | int memory_limit = DEFAULT_MEMORY_LIMIT; /* in MB */ 53 | int segment_size = DEFAULT_SEGMENT_SIZE; /* in MB */ 54 | int listen_backlog = DEFAULT_LISTEN_BACKLOG; 55 | int nr_threads = DEFAULT_NR_THREADS; 56 | std::string backend = sphinx::reactor::Reactor::default_backend(); 57 | std::set isolate_cpus; 58 | bool sched_fifo = false; 59 | }; 60 | 61 | struct Command 62 | { 63 | std::shared_ptr sock; 64 | sphinx::buffer::Buffer buffer; 65 | sphinx::memcache::Opcode op; 66 | uint8_t thread_id; 67 | uint8_t key_size; 68 | 69 | std::string_view key() const 70 | { 71 | return buffer.string_view().substr(0, key_size); 72 | } 73 | std::string_view blob() const 74 | { 75 | return buffer.string_view().substr(key_size); 76 | } 77 | }; 78 | 79 | struct Connection 80 | { 81 | sphinx::buffer::Buffer _rx_buffer; 82 | }; 83 | 84 | class Server 85 | { 86 | std::unique_ptr _reactor; 87 | sphinx::logmem::Log _log; 88 | 89 | public: 90 | Server(const sphinx::logmem::LogConfig& log_cfg, 91 | const std::string& backend, 92 | size_t thread_id, 93 | size_t nr_threads); 94 | void serve(const Args& args); 95 | 96 | private: 97 | void on_message(void* data); 98 | void accept(int sockfd); 99 | void recv(Connection& conn, 100 | std::shared_ptr sock, 101 | std::string_view msg); 102 | size_t process_one(std::shared_ptr sock, std::string_view msg); 103 | void cmd_set(std::shared_ptr sock, std::string_view key, std::string_view blob); 104 | void cmd_add(std::shared_ptr sock, std::string_view key, std::string_view blob); 105 | void cmd_replace(std::shared_ptr sock, std::string_view key, std::string_view blob); 106 | void cmd_get(std::shared_ptr sock, std::string_view key); 107 | void respond(std::shared_ptr sock, std::string_view msg); 108 | size_t find_target(const sphinx::logmem::Hash& hash) const; 109 | }; 110 | 111 | Server::Server(const sphinx::logmem::LogConfig& log_cfg, 112 | const std::string& backend, 113 | size_t thread_id, 114 | size_t nr_threads) 115 | : _reactor{sphinx::reactor::make_reactor(backend, 116 | thread_id, 117 | nr_threads, 118 | [this](void* data) { this->on_message(data); })} 119 | , _log{log_cfg} 120 | { 121 | } 122 | 123 | void 124 | Server::serve(const Args& args) 125 | { 126 | auto accept_fn = [this](int sockfd) { this->accept(sockfd); }; 127 | auto listener = sphinx::reactor::make_tcp_listener( 128 | args.listen_addr, args.tcp_port, args.listen_backlog, std::move(accept_fn)); 129 | _reactor->accept(std::move(listener)); 130 | _reactor->run(); 131 | } 132 | 133 | void 134 | Server::on_message(void* data) 135 | { 136 | using namespace sphinx::memcache; 137 | auto* cmd = reinterpret_cast(data); 138 | switch (cmd->op) { 139 | case Opcode::Version: 140 | assert(0); 141 | case Opcode::Set: { 142 | cmd_set(cmd->sock, cmd->key(), cmd->blob()); 143 | delete cmd; 144 | break; 145 | } 146 | case Opcode::Add: { 147 | cmd_add(cmd->sock, cmd->key(), cmd->blob()); 148 | delete cmd; 149 | break; 150 | } 151 | case Opcode::Replace: { 152 | cmd_replace(cmd->sock, cmd->key(), cmd->blob()); 153 | delete cmd; 154 | break; 155 | } 156 | case Opcode::Get: { 157 | cmd_get(cmd->sock, cmd->key()); 158 | delete cmd; 159 | break; 160 | } 161 | } 162 | } 163 | 164 | void 165 | Server::accept(int sockfd) 166 | { 167 | Connection conn; 168 | auto recv_fn = 169 | [this, conn = std::move(conn)](const std::shared_ptr& sock, 170 | std::string_view msg) mutable { this->recv(conn, sock, msg); }; 171 | auto sock = std::make_shared(sockfd, std::move(recv_fn)); 172 | sock->set_tcp_nodelay(true); 173 | this->_reactor->recv(std::move(sock)); 174 | } 175 | 176 | void 177 | Server::recv(Connection& conn, 178 | std::shared_ptr sock, 179 | std::string_view msg) 180 | { 181 | if (msg.size() == 0) { 182 | _reactor->close(sock); 183 | return; 184 | } 185 | if (conn._rx_buffer.is_empty()) { 186 | for (;;) { 187 | if (msg.find('\n') == std::string_view::npos) { 188 | conn._rx_buffer.append(msg); 189 | break; 190 | } 191 | size_t nr_consumed = process_one(sock, msg); 192 | if (!nr_consumed) { 193 | conn._rx_buffer.append(msg); 194 | break; 195 | } 196 | msg.remove_prefix(nr_consumed); 197 | } 198 | } else { 199 | conn._rx_buffer.append(msg); 200 | for (;;) { 201 | msg = conn._rx_buffer.string_view(); 202 | if (msg.find('\n') == std::string_view::npos) { 203 | break; 204 | } 205 | size_t nr_consumed = process_one(sock, msg); 206 | if (!nr_consumed) { 207 | break; 208 | } 209 | conn._rx_buffer.remove_prefix(nr_consumed); 210 | } 211 | } 212 | } 213 | 214 | size_t 215 | Server::process_one(std::shared_ptr sock, std::string_view msg) 216 | { 217 | using namespace sphinx::memcache; 218 | Parser parser; 219 | size_t nr_consumed = parser.parse(msg); 220 | if (!parser._op) { 221 | static std::string error{"ERROR\r\n"}; 222 | respond(sock, error); 223 | return nr_consumed; 224 | } 225 | switch (*parser._op) { 226 | case Opcode::Version: { 227 | static std::string error{"VERSION 1.5.16\r\n"}; 228 | respond(sock, error); 229 | return nr_consumed; 230 | break; 231 | } 232 | case Opcode::Set: 233 | case Opcode::Add: 234 | case Opcode::Replace: { 235 | size_t data_block_size = parser._blob_size + 2; 236 | if (msg.size() < (nr_consumed + data_block_size)) { 237 | nr_consumed = 0; 238 | break; 239 | } 240 | nr_consumed += data_block_size; 241 | const auto& key = parser.key(); 242 | auto hash = sphinx::logmem::Object::hash_of(key); 243 | auto target_id = find_target(hash); 244 | std::string_view blob{parser._blob_start, parser._blob_size}; 245 | if (target_id == _reactor->thread_id()) { 246 | switch (*parser._op) { 247 | case Opcode::Set: cmd_set(sock, key, blob); break; 248 | case Opcode::Add: cmd_add(sock, key, blob); break; 249 | case Opcode::Replace: cmd_replace(sock, key, blob); break; 250 | default: assert(0); 251 | } 252 | } else { 253 | Command* cmd = new Command(); 254 | cmd->sock = sock; 255 | cmd->op = *parser._op; 256 | cmd->key_size = key.size(); 257 | cmd->buffer.append(key); 258 | cmd->buffer.append(blob); 259 | cmd->thread_id = _reactor->thread_id(); 260 | assert(_reactor->send_msg(target_id, cmd)); // FIXME 261 | } 262 | break; 263 | } 264 | case Opcode::Get: { 265 | const auto& key = parser.key(); 266 | auto hash = sphinx::logmem::Object::hash_of(key); 267 | auto target_id = find_target(hash); 268 | if (target_id == _reactor->thread_id()) { 269 | cmd_get(sock, key); 270 | } else { 271 | Command* cmd = new Command(); 272 | cmd->sock = sock; 273 | cmd->op = *parser._op; 274 | cmd->key_size = key.size(); 275 | cmd->buffer.append(key); 276 | cmd->thread_id = _reactor->thread_id(); 277 | assert(_reactor->send_msg(target_id, cmd)); // FIXME 278 | } 279 | break; 280 | } 281 | } 282 | return nr_consumed; 283 | } 284 | 285 | void 286 | Server::cmd_set(std::shared_ptr sock, std::string_view key, std::string_view blob) 287 | { 288 | if (this->_log.append(key, blob)) { 289 | static std::string stored{"STORED\r\n"}; 290 | respond(sock, stored); 291 | } else { 292 | static std::string out_of_memory{"SERVER_ERROR out of memory storing object\r\n"}; 293 | respond(sock, out_of_memory); 294 | } 295 | } 296 | 297 | void 298 | Server::cmd_add(std::shared_ptr sock, std::string_view key, std::string_view blob) 299 | { 300 | if (bool(this->_log.find(key))) { 301 | static std::string not_stored{"NOT_STORED\r\n"}; 302 | respond(sock, not_stored); 303 | return; 304 | } 305 | if (this->_log.append(key, blob)) { 306 | static std::string stored{"STORED\r\n"}; 307 | respond(sock, stored); 308 | } else { 309 | static std::string out_of_memory{"SERVER_ERROR out of memory storing object\r\n"}; 310 | respond(sock, out_of_memory); 311 | } 312 | } 313 | 314 | void 315 | Server::cmd_replace(std::shared_ptr sock, std::string_view key, std::string_view blob) 316 | { 317 | if (!bool(this->_log.find(key))) { 318 | static std::string not_stored{"NOT_STORED\r\n"}; 319 | respond(sock, not_stored); 320 | return; 321 | } 322 | if (this->_log.append(key, blob)) { 323 | static std::string stored{"STORED\r\n"}; 324 | respond(sock, stored); 325 | } else { 326 | static std::string out_of_memory{"SERVER_ERROR out of memory storing object\r\n"}; 327 | respond(sock, out_of_memory); 328 | } 329 | } 330 | 331 | void 332 | Server::cmd_get(std::shared_ptr sock, std::string_view key) 333 | { 334 | std::string response; 335 | auto search = this->_log.find(key); 336 | if (search) { 337 | const auto& value = search.value(); 338 | response += "VALUE "; 339 | response += key; 340 | response += " 0 "; 341 | response += sphinx::to_string(value.size()); 342 | response += "\r\n"; 343 | response += value; 344 | response += "\r\n"; 345 | } 346 | response += "END\r\n"; 347 | respond(sock, response); 348 | } 349 | 350 | void 351 | Server::respond(std::shared_ptr sock, std::string_view msg) 352 | { 353 | if (!sock->send(msg.data(), msg.size(), std::nullopt)) { 354 | _reactor->send(sock); 355 | } 356 | } 357 | 358 | size_t 359 | Server::find_target(const sphinx::logmem::Hash& hash) const 360 | { 361 | size_t nr_threads = _reactor->nr_threads(); 362 | if (nr_threads == 1) { 363 | return _reactor->thread_id(); 364 | } 365 | return hash % nr_threads; 366 | } 367 | 368 | static void 369 | print_version() 370 | { 371 | std::cout << "Sphinx " << SPHINX_VERSION << std::endl; 372 | } 373 | 374 | static void 375 | print_usage() 376 | { 377 | std::cout << "Usage: " << program << " [OPTION]..." << std::endl; 378 | std::cout << "Start the Sphinx daemon." << std::endl; 379 | std::cout << std::endl; 380 | std::cout << "Options:" << std::endl; 381 | std::cout << " -p, --port number TCP port to listen to (default: " << DEFAULT_TCP_PORT 382 | << ")" << std::endl; 383 | std::cout << " -U, --port number UDP port to listen to (default: " << DEFAULT_UDP_PORT 384 | << ")" << std::endl; 385 | std::cout << " -l, --listen address interface to listen to (default: " 386 | << DEFAULT_LISTEN_ADDR << ")" << std::endl; 387 | std::cout << " -m, --memory-limit number Memory limit in MB (default: " << DEFAULT_MEMORY_LIMIT 388 | << ")" << std::endl; 389 | std::cout << " -s, --segment-size number Segment size in MB (default: " << DEFAULT_SEGMENT_SIZE 390 | << ")" << std::endl; 391 | std::cout << " -b, --listen-backlog number Listen backlog size (default: " 392 | << DEFAULT_LISTEN_BACKLOG << ")" << std::endl; 393 | std::cout << " -t, --threads number number of threads to use (default: " 394 | << DEFAULT_NR_THREADS << ")" << std::endl; 395 | std::cout << " -I, --io-backend name I/O backend (default: " 396 | << sphinx::reactor::Reactor::default_backend() << ")" << std::endl; 397 | std::cout << " -i, --isolate-cpus list list of CPUs to isolate application threads" 398 | << std::endl; 399 | std::cout << " -S, --sched-fifo use SCHED_FIFO scheduling policy" << std::endl; 400 | std::cout << " --help print this help text and exit" << std::endl; 401 | std::cout << " --version print Sphinx version and exit" << std::endl; 402 | std::cout << std::endl; 403 | } 404 | 405 | static void 406 | print_opt_error(const std::string& option, const std::string& reason) 407 | { 408 | std::cerr << program << ": " << reason << " '" << option << "' option" << std::endl; 409 | std::cerr << "Try '" << program << " --help' for more information" << std::endl; 410 | } 411 | 412 | static void 413 | print_unrecognized_opt(const std::string& option) 414 | { 415 | print_opt_error(option, "unregonized"); 416 | } 417 | 418 | static std::set 419 | parse_cpu_list(const std::string& raw_cpu_list) 420 | { 421 | std::set cpu_list; 422 | std::istringstream iss(raw_cpu_list); 423 | std::string token; 424 | while (std::getline(iss, token, ',')) { 425 | cpu_list.emplace(std::stoi(token)); 426 | } 427 | return cpu_list; 428 | } 429 | 430 | static Args 431 | parse_cmd_line(int argc, char* argv[]) 432 | { 433 | static struct option long_options[] = {{"port", required_argument, 0, 'p'}, 434 | {"listen", required_argument, 0, 'l'}, 435 | {"memory-limit", required_argument, 0, 'm'}, 436 | {"segment-size", required_argument, 0, 's'}, 437 | {"listen-backlog", required_argument, 0, 'b'}, 438 | {"threads", required_argument, 0, 't'}, 439 | {"io-backend", required_argument, 0, 'I'}, 440 | {"isolate-cpus", required_argument, 0, 'i'}, 441 | {"sched-fifo", no_argument, 0, 'S'}, 442 | {"help", no_argument, 0, 'h'}, 443 | {"version", no_argument, 0, 'v'}, 444 | {0, 0, 0, 0}}; 445 | Args args; 446 | int opt, long_index; 447 | while ((opt = ::getopt_long(argc, argv, "p:l:m:s:b:t:I:i:S", long_options, &long_index)) != -1) { 448 | switch (opt) { 449 | case 'p': 450 | args.tcp_port = std::stoi(optarg); 451 | break; 452 | case 'l': 453 | args.listen_addr = optarg; 454 | break; 455 | case 'm': 456 | args.memory_limit = std::stoi(optarg); 457 | break; 458 | case 's': 459 | args.segment_size = std::stoi(optarg); 460 | break; 461 | case 'b': 462 | args.listen_backlog = std::stoi(optarg); 463 | break; 464 | case 't': 465 | args.nr_threads = std::stoi(optarg); 466 | break; 467 | case 'I': 468 | args.backend = optarg; 469 | break; 470 | case 'i': 471 | args.isolate_cpus = parse_cpu_list(optarg); 472 | break; 473 | case 'S': 474 | args.sched_fifo = true; 475 | break; 476 | case 'h': 477 | print_usage(); 478 | std::exit(EXIT_SUCCESS); 479 | case 'v': 480 | print_version(); 481 | std::exit(EXIT_SUCCESS); 482 | case '?': 483 | print_unrecognized_opt(argv[optind - 1]); 484 | std::exit(EXIT_FAILURE); 485 | default: 486 | print_usage(); 487 | std::exit(EXIT_FAILURE); 488 | } 489 | } 490 | if (args.memory_limit % args.nr_threads != 0) { 491 | throw std::invalid_argument("memory limit (" + std::to_string(args.memory_limit) + 492 | ") is not divisible by number of threads (" + 493 | std::to_string(args.nr_threads) + 494 | "), which is required for partitioning"); 495 | } 496 | return args; 497 | } 498 | 499 | void 500 | server_thread(size_t thread_id, std::optional cpu_id, const Args& args) 501 | { 502 | try { 503 | if (cpu_id) { 504 | cpu_set_t cpuset; 505 | CPU_ZERO(&cpuset); 506 | CPU_SET(*cpu_id, &cpuset); 507 | auto err = ::pthread_setaffinity_np(::pthread_self(), sizeof(cpu_set_t), &cpuset); 508 | if (err != 0) { 509 | throw std::system_error(err, std::system_category(), "pthread_setaffinity_np"); 510 | } 511 | } 512 | if (args.sched_fifo) { 513 | ::sched_param param = {}; 514 | param.sched_priority = 1; 515 | auto err = ::pthread_setschedparam(::pthread_self(), SCHED_FIFO, ¶m); 516 | if (err != 0) { 517 | throw std::system_error(errno, std::system_category(), "pthread_setschedparam"); 518 | } 519 | } 520 | size_t mem_size = size_t(args.memory_limit) * 1024 * 1024; 521 | sphinx::memory::Memory memory = sphinx::memory::Memory::mmap(mem_size / args.nr_threads); 522 | sphinx::logmem::LogConfig log_cfg; 523 | log_cfg.segment_size = args.segment_size * 1024 * 1024; 524 | log_cfg.memory_ptr = reinterpret_cast(memory.addr()); 525 | log_cfg.memory_size = memory.size(); 526 | Server server{log_cfg, args.backend, thread_id, size_t(args.nr_threads)}; 527 | server.serve(args); 528 | } catch (const std::exception& e) { 529 | std::cerr << "error: " << e.what() << std::endl; 530 | std::exit(EXIT_FAILURE); 531 | } 532 | } 533 | 534 | struct CpuAffinity 535 | { 536 | std::set isolate_cpus; 537 | std::optional next_id; 538 | CpuAffinity(const std::set isolate_cpus) 539 | : isolate_cpus{isolate_cpus} 540 | { 541 | } 542 | int next_cpu_id() 543 | { 544 | int id = next_id.value_or(0); 545 | for (;;) { 546 | if (isolate_cpus.count(id) == 0) { 547 | break; 548 | } 549 | id++; 550 | } 551 | next_id = id + 1; 552 | return id; 553 | } 554 | }; 555 | 556 | int 557 | main(int argc, char* argv[]) 558 | { 559 | static_assert(sizeof(Command) <= sphinx::hardware::cache_line_size); 560 | try { 561 | program = ::basename(argv[0]); 562 | auto args = parse_cmd_line(argc, argv); 563 | CpuAffinity cpu_affinity{args.isolate_cpus}; 564 | std::vector threads; 565 | for (int i = 0; i < args.nr_threads; i++) { 566 | auto thread = std::thread{server_thread, i, cpu_affinity.next_cpu_id(), args}; 567 | threads.push_back(std::move(thread)); 568 | } 569 | for (auto& t : threads) { 570 | t.join(); 571 | } 572 | } catch (const std::exception& e) { 573 | std::cerr << "error: " << e.what() << std::endl; 574 | std::exit(EXIT_FAILURE); 575 | } 576 | std::exit(EXIT_SUCCESS); 577 | } 578 | -------------------------------------------------------------------------------- /sphinxd/test/buffer_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | 23 | TEST(BufferTest, append) 24 | { 25 | using namespace sphinx::buffer; 26 | Buffer buf; 27 | ASSERT_TRUE(buf.size() == 0); 28 | std::string value = "The quick brown fox jumps over the lazy dog"; 29 | buf.append(value); 30 | ASSERT_EQ(value.size(), buf.size()); 31 | ASSERT_EQ(value, buf.string_view()); 32 | } 33 | -------------------------------------------------------------------------------- /sphinxd/test/logmem_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | 23 | static std::string 24 | make_random(size_t len) 25 | { 26 | auto make_random_char = []() { 27 | static const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 28 | const size_t nr_chars = sizeof(chars) - 1; 29 | return chars[rand() % nr_chars]; 30 | }; 31 | std::string str(len, 0); 32 | std::generate_n(str.begin(), len, make_random_char); 33 | return str; 34 | } 35 | 36 | TEST(LogTest, append) 37 | { 38 | using namespace sphinx::logmem; 39 | std::array memory; 40 | LogConfig cfg; 41 | cfg.segment_size = 64; 42 | cfg.memory_ptr = memory.data(); 43 | cfg.memory_size = memory.size(); 44 | Log log{cfg}; 45 | auto key = make_random(8); 46 | auto blob = make_random(16); 47 | log.append(key, blob); 48 | auto blob_opt = log.find(key); 49 | ASSERT_TRUE(blob_opt.has_value()); 50 | ASSERT_EQ(blob_opt.value(), blob); 51 | } 52 | 53 | TEST(LogTest, append_expires) 54 | { 55 | using namespace sphinx::logmem; 56 | std::array memory; 57 | LogConfig cfg; 58 | cfg.segment_size = 64; 59 | cfg.memory_ptr = memory.data(); 60 | cfg.memory_size = memory.size(); 61 | Log log{cfg}; 62 | std::string key; 63 | std::string blob; 64 | for (int i = 0; i < 10; i++) { 65 | key = make_random(8); 66 | blob = make_random(16); 67 | ASSERT_TRUE(log.append(key, blob)); 68 | } 69 | } -------------------------------------------------------------------------------- /sphinxd/test/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | int 20 | main(int argc, char** argv) 21 | { 22 | ::testing::InitGoogleTest(&argc, argv); 23 | int ret = RUN_ALL_TESTS(); 24 | return ret; 25 | } 26 | -------------------------------------------------------------------------------- /sphinxd/test/protocol_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | TEST(ProtocolTest, parse_error) 22 | { 23 | using namespace sphinx::memcache; 24 | std::string msg = "foo"; 25 | Parser parser; 26 | parser.parse(msg); 27 | ASSERT_EQ(bool(parser._op), false); 28 | } 29 | 30 | TEST(ProtocolTest, parse_set) 31 | { 32 | using namespace sphinx::memcache; 33 | std::string msg = "set foo 0 0 3\r\nbar\r\n"; 34 | Parser parser; 35 | parser.parse(msg); 36 | ASSERT_EQ(*parser._op, Opcode::Set); 37 | } 38 | 39 | TEST(ProtocolTest, parse_get) 40 | { 41 | using namespace sphinx::memcache; 42 | std::string msg = "get foo\r\n"; 43 | Parser parser; 44 | parser.parse(msg); 45 | ASSERT_EQ(*parser._op, Opcode::Get); 46 | } 47 | 48 | TEST(ProtocolTest, parse_many) 49 | { 50 | using namespace sphinx::memcache; 51 | std::string raw_msg = "set foo 0 0 3\r\nbar\r\nget foo\r\n"; 52 | std::string_view msg = raw_msg; 53 | { 54 | Parser parser; 55 | auto nr_consumed = parser.parse(msg); 56 | ASSERT_EQ(15, nr_consumed); 57 | ASSERT_EQ(parser._op, Opcode::Set); 58 | ASSERT_EQ(3, parser._blob_size); 59 | msg.remove_prefix(nr_consumed + parser._blob_size + 2); 60 | } 61 | { 62 | Parser parser; 63 | auto nr_consumed = parser.parse(msg); 64 | ASSERT_EQ(9, nr_consumed); 65 | ASSERT_EQ(*parser._op, Opcode::Get); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sphinxd/test/spsc_queue_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | TEST(QueueTest, try_to_emplace) 23 | { 24 | using namespace sphinx::spsc; 25 | Queue queue; 26 | ASSERT_TRUE(queue.empty()); 27 | ASSERT_TRUE(queue.try_to_emplace(1)); 28 | ASSERT_FALSE(queue.empty()); 29 | } 30 | 31 | TEST(QueueTest, producer_consumer) 32 | { 33 | using namespace sphinx::spsc; 34 | constexpr int nr_iterations = 1000000; 35 | Queue queue; 36 | std::thread producer{[&queue]() { 37 | for (int i = 0; i < nr_iterations; i++) { 38 | for (;;) { 39 | if (queue.try_to_emplace(i)) { 40 | break; 41 | } 42 | } 43 | } 44 | }}; 45 | std::thread consumer{[&queue]() { 46 | for (int i = 0; i < nr_iterations; i++) { 47 | for (;;) { 48 | auto* item = queue.front(); 49 | if (item) { 50 | ASSERT_EQ(i, *item); 51 | queue.pop(); 52 | break; 53 | } 54 | } 55 | } 56 | }}; 57 | producer.join(); 58 | consumer.join(); 59 | } 60 | -------------------------------------------------------------------------------- /sphinxd/test/string_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Sphinxd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | TEST(StringTest, to_string) 25 | { 26 | for (int i = 0; i < 100; i++) { 27 | ASSERT_EQ(std::to_string(i), sphinx::to_string(i)); 28 | auto v = std::rand(); 29 | ASSERT_EQ(std::to_string(v), sphinx::to_string(v)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /version.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define SPHINX_VERSION "v@SPHINX_VERSION@-@GIT_COMMIT_HASH@" 4 | --------------------------------------------------------------------------------