├── .clang-format ├── .gitignore ├── Dockerfile ├── Makefile.am ├── README.md ├── autogen.sh ├── configure.ac ├── example.cfg ├── lint.py ├── package.json └── src ├── buf.c ├── buf.h ├── cfg.c ├── cfg.h ├── config.c ├── config.h ├── ctx.c ├── ctx.h ├── event.c ├── event.h ├── event_epoll.c ├── event_timer.c ├── ketama.c ├── ketama.h ├── log.c ├── log.h ├── md5.c ├── md5.h ├── parser.c ├── parser.h ├── proxy.c ├── proxy.h └── statsd-proxy.c /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -1 4 | ConstructorInitializerIndentWidth: 4 5 | AlignEscapedNewlinesLeft: true 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: true 8 | AllowShortBlocksOnASingleLine: false 9 | AllowShortIfStatementsOnASingleLine: true 10 | AllowShortLoopsOnASingleLine: true 11 | AllowShortFunctionsOnASingleLine: All 12 | AlwaysBreakTemplateDeclarations: true 13 | AlwaysBreakBeforeMultilineStrings: true 14 | BreakBeforeBinaryOperators: false 15 | BreakBeforeTernaryOperators: true 16 | BreakConstructorInitializersBeforeComma: false 17 | BinPackParameters: true 18 | ColumnLimit: 80 19 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 20 | DerivePointerAlignment: true 21 | ExperimentalAutoDetectBinPacking: false 22 | IndentCaseLabels: true 23 | IndentWrappedFunctionNames: false 24 | IndentFunctionDeclarationAfterType: false 25 | MaxEmptyLinesToKeep: 1 26 | KeepEmptyLinesAtTheStartOfBlocks: false 27 | NamespaceIndentation: None 28 | ObjCSpaceAfterProperty: false 29 | ObjCSpaceBeforeProtocolList: false 30 | PenaltyBreakBeforeFirstCallParameter: 1 31 | PenaltyBreakComment: 300 32 | PenaltyBreakString: 1000 33 | PenaltyBreakFirstLessLess: 120 34 | PenaltyExcessCharacter: 1000000 35 | PenaltyReturnTypeOnItsOwnLine: 200 36 | PointerAlignment: Right 37 | SpacesBeforeTrailingComments: 2 38 | Cpp11BracedListStyle: true 39 | Standard: Auto 40 | IndentWidth: 4 41 | TabWidth: 8 42 | UseTab: Never 43 | BreakBeforeBraces: Attach 44 | SpacesInParentheses: false 45 | SpacesInAngles: false 46 | SpaceInEmptyParentheses: false 47 | SpacesInCStyleCastParentheses: false 48 | SpacesInContainerLiterals: true 49 | SpaceBeforeAssignmentOperators: true 50 | ContinuationIndentWidth: 4 51 | CommentPragmas: '^ IWYU pragma:' 52 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 53 | SpaceBeforeParens: ControlStatements 54 | DisableFormat: false 55 | ... 56 | 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/statsd-proxy 2 | *.o 3 | *.log 4 | *.sw[opn] 5 | test.* 6 | test_serv.py 7 | *.m4 8 | compile 9 | config.h 10 | *.in 11 | .deps 12 | *.in~ 13 | depcomp 14 | config.status 15 | install-sh 16 | Makefile 17 | missing 18 | stamp-* 19 | COPYING 20 | INSTALL 21 | *.cache/ 22 | .dirstamp 23 | statsd-proxy 24 | configure 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | RUN apt-get update && apt-get install -y \ 3 | build-essential \ 4 | autoconf 5 | COPY . /app 6 | WORKDIR /app 7 | RUN ./autogen.sh 8 | RUN ./configure 9 | RUN make 10 | EXPOSE 8125 11 | CMD ./statsd-proxy -f ./example.cfg 12 | 13 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS=statsd-proxy 2 | statsd_proxy_SOURCES=src/buf.c \ 3 | src/cfg.c \ 4 | src/config.c \ 5 | src/ctx.c \ 6 | src/event.c \ 7 | src/ketama.c \ 8 | src/log.c \ 9 | src/md5.c \ 10 | src/parser.c \ 11 | src/proxy.c \ 12 | src/statsd-proxy.c 13 | statsd_proxy_CFLAGS=-std=c99 -D_GNU_SOURCE 14 | statsd_proxy_LDFLAGS=-pthread 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Statsd-Proxy 2 | ============ 3 | 4 | Proxy for [etsy/statsd](https://github.com/etsy/statsd). 5 | 6 | Why 7 | --- 8 | 9 | [etsy/statsd](https://github.com/etsy/statsd) comes with a proxy in nodejs, 10 | and we are running it on a single server, proxing a statsd cluster via an 11 | udp port. But we found that this nodejs proxy is losing packets, up to 12 | 30~40% sometimes! 13 | 14 | Cpus are idle but packets are being lost. In our case, one api call makes one 15 | statsd request, maybe the single udp socket is too busy. 16 | 17 | We tried to use `SO_REUSEPORT` on the original nodejs proxy, this enables 18 | us to bind multiple udp sockets on a single port, but nodejs(or libuv) has 19 | disabled this option, and golang just dosen't have a method `setsockopt()`. 20 | 21 | Therefore, we made it in C. 22 | 23 | Features 24 | -------- 25 | 26 | * Zero dependencies. 27 | * Very very fast. 28 | * Multiple threading. 29 | * Reuseport support. 30 | * Packet aggregation. 31 | 32 | Limitations 33 | ----------- 34 | 35 | * Only available on linux 3.9+ (option `SO_REUSEPORT`) 36 | * Only support udp server and udp backends. 37 | 38 | Requirements 39 | ------------- 40 | 41 | Linux 3.9+. 42 | 43 | Build 44 | ------ 45 | 46 | $ ./autogen.sh 47 | $ ./configure 48 | $ make 49 | 50 | Usage 51 | ----- 52 | 53 | Usage: 54 | ./statsd-proxy -f ./path/to/config.cfg 55 | Options: 56 | -h, --help Show this message 57 | -v, --version Show version 58 | -d, --debug Enable debug logging 59 | Copyright (c) https://github.com/hit9/statsd-proxy 60 | 61 | License 62 | ------- 63 | 64 | MIT (c) Chao Wang 2015. 65 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | autoreconf -fvi 4 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ([2.69]) 5 | AC_INIT([statsd-proxy], [0.1.0], [hit9@icloud.com]) 6 | AM_INIT_AUTOMAKE([foreign subdir-objects]) 7 | AC_CONFIG_SRCDIR([src]) 8 | AC_CONFIG_HEADERS([config.h]) 9 | 10 | # Checks for programs. 11 | AC_PROG_CC 12 | AC_PROG_MAKE_SET 13 | 14 | # Checks for libraries. 15 | 16 | # Checks for header files. 17 | AC_CHECK_HEADERS([arpa/inet.h fcntl.h netinet/in.h stddef.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h unistd.h]) 18 | 19 | # Checks for typedefs, structures, and compiler characteristics. 20 | AC_TYPE_SIZE_T 21 | AC_TYPE_UINT32_T 22 | 23 | # Checks for library functions. 24 | AC_FUNC_MALLOC 25 | AC_FUNC_REALLOC 26 | AC_CHECK_FUNCS([bzero gettimeofday memmove memset socket strtol]) 27 | 28 | AC_CONFIG_FILES([Makefile]) 29 | AC_OUTPUT 30 | -------------------------------------------------------------------------------- /example.cfg: -------------------------------------------------------------------------------- 1 | port 8125 2 | num_threads 4 3 | flush_interval 10 # ms 4 | socket_receive_bufsize 106496 5 | #socket_send_packet_size 8192 6 | 7 | node 127.0.0.1:8126:1 8 | node 127.0.0.1:8127:1 9 | node 127.0.0.1:8128:1 10 | node 127.0.0.1:8129:1 11 | node 127.0.0.1:8130:1 12 | node 127.0.0.1:8131:1 13 | node 127.0.0.1:8132:1 14 | -------------------------------------------------------------------------------- /lint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Lint script via clang-format. 5 | 6 | Usage 7 | 8 | $ ./lint.py [fix] 9 | """ 10 | 11 | import sys 12 | import os 13 | import subprocess 14 | import xml.etree.ElementTree as ET 15 | 16 | FILE_EXTENSIONS = ('.h', '.c') 17 | CLANGFOMART_NAMES = ['clang-format', 'clang-format-3.5', 'clang-format-3.6'] 18 | CLANGFORMAT_LINT_OPTIONS = ['-output-replacements-xml', '-style=file'] 19 | CLANGFORMAT_FIX_OPTIONS = ['-i', '-style=file'] 20 | 21 | 22 | def print_usage(): 23 | print "Usage: ./lint.py [fix]" 24 | 25 | 26 | def traverse_files(): 27 | """Get file path list recursively. 28 | """ 29 | paths = [] 30 | for root, dirs, files in os.walk("./"): 31 | for filename in files: 32 | if filename.endswith(FILE_EXTENSIONS): 33 | paths.append(os.path.join(root, filename)) 34 | return paths 35 | 36 | 37 | def get_clang_format_bin(): 38 | for name in CLANGFOMART_NAMES: 39 | try: 40 | subprocess.check_output([name, "-version"]) 41 | except OSError: 42 | continue 43 | else: 44 | return name 45 | raise Exception("No clang-format command available.") 46 | 47 | 48 | def lint(fix=False): 49 | num_errors = 0 50 | num_error_files = 0 51 | num_fixed_files = 0 52 | clangformat_bin = get_clang_format_bin() 53 | for path in traverse_files(): 54 | cmd = [clangformat_bin, path] 55 | cmd.extend(CLANGFORMAT_LINT_OPTIONS) 56 | out = subprocess.check_output(cmd) 57 | root = ET.fromstring(out) 58 | has_error = False 59 | for tag in root.findall('replacement'): 60 | offset = tag.get('offset', None) 61 | length = tag.get("length", None) 62 | if offset is not None: 63 | has_error = True 64 | num_errors += 1 65 | print "{0}:{1},{2}".format(path, offset, length) 66 | if has_error: 67 | num_error_files += 1 68 | if fix: 69 | cmd = [clangformat_bin, path] 70 | cmd.extend(CLANGFORMAT_FIX_OPTIONS) 71 | if subprocess.call(cmd) == 0: 72 | num_fixed_files += 1 73 | if has_error: 74 | print "{} fixed".format(path) 75 | if num_errors > 0 and num_error_files != num_fixed_files: 76 | sys.exit(1) 77 | 78 | 79 | if __name__ == '__main__': 80 | if len(sys.argv) <= 1: # Case ./program 81 | lint(fix=False) 82 | elif sys.argv[1] == 'fix': # Case ./program fix 83 | lint(fix=True) 84 | else: 85 | print_usage() 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statsd-proxy", 3 | "description": "Fast proxy for etsy/statsd. ", 4 | "version": "0.0.9", 5 | "author": "Chao Wang ", 6 | "homepage": "https://github.com/hit9/statsd-proxy.git", 7 | "bugs": "https://github.com/hit9/statsd-proxy/issues", 8 | "keywords": ["statsd", "proxy"], 9 | "bin": {"statsd-proxy": "./src/statsd-proxy"}, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/hit9/statsd-proxy.git" 13 | }, 14 | "preferGlobal": true, 15 | "os": ["linux"], 16 | "scripts":{"preinstall": "make"}, 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /src/buf.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "buf.h" 13 | 14 | /* Create new buffer and init it with a C null-terminated 15 | * string if `s` is not NULL. 16 | */ 17 | struct buf *buf_new(const char *s) { 18 | struct buf *buf = malloc(sizeof(struct buf)); 19 | 20 | if (buf != NULL) { 21 | buf->len = 0; 22 | buf->cap = 0; 23 | buf->data = NULL; 24 | 25 | if (s != NULL) { 26 | if (buf_puts(buf, s) != BUF_OK) return NULL; 27 | } 28 | } 29 | return buf; 30 | } 31 | 32 | /* Create empty buffer */ 33 | struct buf *buf_empty(void) { 34 | return buf_new(NULL); 35 | } 36 | 37 | /* Free a buffer and its data, no operation is performed 38 | * if the buffer is NULL. */ 39 | void buf_free(struct buf *buf) { 40 | if (buf != NULL) { 41 | if (buf->data != NULL) free(buf->data); 42 | free(buf); 43 | } 44 | } 45 | 46 | /* Clear a buffer and the data memory will also be freed. */ 47 | void buf_clear(struct buf *buf) { 48 | assert(buf != NULL); 49 | 50 | if (buf->data != NULL) free(buf->data); 51 | buf->data = NULL; 52 | buf->len = 0; 53 | buf->cap = 0; 54 | } 55 | 56 | /* Grow a buffer's capacity to given size, the new capacity is 57 | * calculated like k*unit>=cap, by default, the unit is current cap, 58 | * if the unit is large enough, use BUF_UNIT_MAX instead. */ 59 | int buf_grow(struct buf *buf, size_t cap) { 60 | assert(buf != NULL); 61 | 62 | if (cap > BUF_CAP_MAX) return BUF_ENOMEM; 63 | 64 | if (cap <= buf->cap) return BUF_OK; 65 | 66 | size_t unit = buf->cap; 67 | 68 | if (unit > BUF_UNIT_MAX) unit = BUF_UNIT_MAX; 69 | 70 | if (unit < BUF_UNIT_MIN) unit = BUF_UNIT_MIN; 71 | 72 | size_t new_cap = buf->cap + unit; 73 | while (new_cap < cap) new_cap += unit; 74 | 75 | char *data = realloc(buf->data, new_cap * sizeof(char)); 76 | 77 | if (data == NULL) return BUF_ENOMEM; 78 | 79 | buf->data = data; 80 | buf->cap = new_cap; 81 | return BUF_OK; 82 | } 83 | 84 | /* Put chars on the end of a buffer */ 85 | int buf_put(struct buf *buf, char *data, size_t len) { 86 | int error = buf_grow(buf, buf->len + len); 87 | 88 | if (error == BUF_OK) { 89 | memcpy(buf->data + buf->len, data, len); 90 | buf->len += len; 91 | } 92 | return error; 93 | } 94 | 95 | /* Put null-terminated chars to the end of a buffer. */ 96 | int buf_puts(struct buf *buf, const char *s) { 97 | return buf_put(buf, (char *)s, strlen(s)); 98 | } 99 | 100 | /* Put a single char to the end of a buffer. */ 101 | int buf_putc(struct buf *buf, char ch) { 102 | int error = buf_grow(buf, buf->len + 1); 103 | 104 | if (error == BUF_OK) { 105 | buf->data[buf->len] = ch; 106 | buf->len += 1; 107 | } 108 | return error; 109 | } 110 | 111 | /* Get buffer data as null-terminated chars */ 112 | char *buf_str(struct buf *buf) { 113 | assert(buf != NULL); 114 | 115 | if (buf->len < buf->cap && buf->data[buf->len] == '\0') return buf->data; 116 | 117 | if (buf->len + 1 <= buf->cap || buf_grow(buf, buf->len + 1) == BUF_OK) { 118 | buf->data[buf->len] = '\0'; 119 | return buf->data; 120 | } 121 | 122 | return NULL; 123 | } 124 | 125 | /* Return 1 if a buffer is empty, else 0 */ 126 | int buf_isempty(struct buf *buf) { 127 | assert(buf != NULL); 128 | if (buf->len == 0) return 1; 129 | return 0; 130 | } 131 | 132 | /* Formatted printing to a buffer. */ 133 | int buf_sprintf(struct buf *buf, const char *fmt, ...) { 134 | assert(buf != NULL); 135 | 136 | if (buf->len >= buf->cap && buf_grow(buf, buf->len + 1) != BUF_OK) 137 | return BUF_ENOMEM; 138 | 139 | va_list ap; 140 | int num; 141 | 142 | va_start(ap, fmt); 143 | num = vsnprintf(buf->data + buf->len, buf->cap - buf->len, fmt, ap); 144 | va_end(ap); 145 | 146 | if (num < 0) return BUF_EFAILED; 147 | 148 | size_t size = (size_t)num; 149 | 150 | if (size >= buf->cap - buf->len) { 151 | if (buf_grow(buf, buf->len + size + 1) != BUF_OK) return BUF_ENOMEM; 152 | va_start(ap, fmt); 153 | num = vsnprintf(buf->data + buf->len, buf->cap - buf->len, fmt, ap); 154 | va_end(ap); 155 | } 156 | 157 | if (num < 0) return BUF_EFAILED; 158 | 159 | buf->len += num; 160 | return BUF_OK; 161 | } 162 | 163 | /* Romve part of buf on the left. */ 164 | void buf_lrm(struct buf *buf, size_t len) { 165 | assert(buf != NULL); 166 | 167 | if (len > buf->len) { 168 | buf->len = 0; 169 | return; 170 | } 171 | 172 | buf->len -= len; 173 | memmove(buf->data, buf->data + len, buf->len); 174 | } 175 | 176 | /* Get buf length. */ 177 | size_t buf_len(struct buf *buf) { 178 | assert(buf != NULL); 179 | return buf->len; 180 | } 181 | 182 | /* Get buf capacity. */ 183 | size_t buf_cap(struct buf *buf) { 184 | assert(buf != NULL); 185 | return buf->cap; 186 | } 187 | -------------------------------------------------------------------------------- /src/buf.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Dynamic buffer implementation. 5 | * deps: None. 6 | */ 7 | 8 | #ifndef _CW_BUF_H 9 | #define _CW_BUF_H 1 10 | 11 | #include 12 | #include 13 | 14 | #if defined(__cplusplus) 15 | extern "C" { 16 | #endif 17 | 18 | #define BUF_CAP_MAX 16 * 1024 * 1024 /* max buffer capacity: 16mb */ 19 | #define BUF_UNIT_MIN 1 /* min buffer realloc unit: 1 */ 20 | #define BUF_UNIT_MAX 1024 * 1024 /* max buffer realloc unit: 1mb */ 21 | 22 | #define buf(s) buf_new(s) 23 | #define str(b) buf_str(b) 24 | 25 | enum { 26 | BUF_OK = 0, /* operation is ok */ 27 | BUF_ENOMEM = 1, /* no memory error */ 28 | BUF_EFAILED = 2, /* operation is failed */ 29 | }; 30 | 31 | struct buf { 32 | size_t len; /* buffer length */ 33 | size_t cap; /* buffer capacity */ 34 | char *data; /* real buffer pointer */ 35 | }; 36 | 37 | struct buf *buf_new(const char *s); 38 | struct buf *buf_empty(void); 39 | void buf_free(struct buf *buf); 40 | void buf_clear(struct buf *buf); 41 | int buf_grow(struct buf *buf, size_t cap); 42 | int buf_put(struct buf *buf, char *data, size_t len); 43 | int buf_puts(struct buf *buf, const char *s); 44 | int buf_putc(struct buf *buf, char ch); 45 | char *buf_str(struct buf *buf); 46 | int buf_isempty(struct buf *buf); 47 | int buf_sprintf(struct buf *buf, const char *fmt, ...); 48 | void buf_lrm(struct buf *buf, size_t len); 49 | size_t buf_len(struct buf *buf); 50 | size_t buf_cap(struct buf *buf); 51 | 52 | #if defined(__cplusplus) 53 | } 54 | #endif 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/cfg.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include "cfg.h" 6 | #include 7 | #include 8 | 9 | /* Get key and val from cfg. */ 10 | int cfg_get(struct cfg *cfg) { 11 | assert(cfg != NULL); 12 | assert(cfg->lineno > 0); 13 | 14 | if (cfg->data == NULL || cfg->len == 0) return CFG_EOF; 15 | 16 | char *data = cfg->data; 17 | size_t len = cfg->len; 18 | size_t idx = 0; 19 | 20 | cfg->key = NULL; 21 | cfg->val = NULL; 22 | cfg->key_len = 0; 23 | cfg->val_len = 0; 24 | 25 | while (idx < len) { 26 | switch (data[idx]) { 27 | case '\t': 28 | case ' ': 29 | if (cfg->key != NULL && cfg->key_len == 0) /* key end */ 30 | cfg->key_len = data + idx - cfg->key; 31 | if (cfg->val != NULL && cfg->val_len == 0) /* val end */ 32 | cfg->val_len = data + idx - cfg->val; 33 | break; 34 | case '\n': 35 | if (cfg->val != NULL && cfg->val_len == 0) /* val end */ 36 | cfg->val_len = data + idx - cfg->val; 37 | if (cfg->key_len != 0 && cfg->val_len != 0) 38 | /* line end ok with key & val */ 39 | return CFG_OK; 40 | if (cfg->key != NULL && cfg->val == NULL) 41 | /* line contains only one word */ 42 | return CFG_EBADFMT; 43 | cfg->lineno++; 44 | break; 45 | case '#': 46 | if (cfg->val != NULL && cfg->val_len == 0) /* val end */ 47 | cfg->val_len = data + idx - cfg->val; 48 | 49 | while (idx + 1 < len && data[idx + 1] != '\n') { 50 | idx++; 51 | cfg->data++; 52 | cfg->len--; 53 | } 54 | break; 55 | default: 56 | if (cfg->key == NULL) /* key start */ 57 | cfg->key = data + idx; 58 | if (cfg->val == NULL && cfg->key_len != 0) /* val start */ 59 | cfg->val = data + idx; 60 | if (cfg->key_len != 0 && cfg->val_len != 0) 61 | /* bad char after key and val */ 62 | return CFG_EBADFMT; 63 | break; 64 | } 65 | 66 | idx++; 67 | cfg->data++; 68 | cfg->len--; 69 | }; 70 | 71 | return CFG_EOF; 72 | } 73 | -------------------------------------------------------------------------------- /src/cfg.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Simple configuration reader. 5 | * 6 | * example cfg string: 7 | * 8 | * # proxy port 9 | * port 8125 10 | * # proxy backend nodes 11 | * node 127.0.0.1:8126 12 | * node 127.0.0.1:8127 13 | * node 127.0.0.1:8128 14 | * 15 | * example usage: 16 | * 17 | * struct cfg cfg; 18 | * int err; 19 | * 20 | * cfg.data = buf->data; 21 | * cfg.len = buf->len; 22 | * cfg.lineno = 0; 23 | * 24 | * while ((err = cfg_get(&cfg)) == CFG_OK) 25 | * printf("%.*s => %.*s\n", cfg.key_len, cfg.key 26 | * cfg.val_len, cfg.val); 27 | * 28 | * if (err == CFG_EBADFMT) 29 | * printf("bad format at line %ld", cfg.lineno); 30 | */ 31 | 32 | #ifndef _CW_CFG_H 33 | #define _CW_CFG_H 1 34 | 35 | #include 36 | 37 | #if defined(__cplusplus) 38 | extern "C" { 39 | #endif 40 | 41 | enum { 42 | CFG_OK = 0, /* operation is ok */ 43 | CFG_EOF = 1, /* EOF reached */ 44 | CFG_EBADFMT = 2, /* invalid format */ 45 | }; 46 | 47 | struct cfg { 48 | char *data; /* currnt cfg data */ 49 | size_t len; /* currnt cfg data length */ 50 | char *key; /* current key */ 51 | size_t key_len; /* current key length */ 52 | char *val; /* current val */ 53 | size_t val_len; /* current val length */ 54 | size_t lineno; /* cfg lineno */ 55 | }; 56 | 57 | int cfg_get(struct cfg *cfg); 58 | 59 | #if defined(__cplusplus) 60 | } 61 | #endif 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Proxy config. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "buf.h" 13 | #include "cfg.h" 14 | #include "config.h" 15 | #include "log.h" 16 | #include "proxy.h" 17 | 18 | struct config *config_new(void) { 19 | struct config *c = malloc(sizeof(struct config)); 20 | 21 | if (c == NULL) return NULL; 22 | 23 | c->port = 8125; 24 | c->num_threads = 4; 25 | c->flush_interval = 10; 26 | c->socket_receive_bufsize = 0; 27 | c->socket_send_packet_size = BUF_SEND_UNIT; 28 | 29 | int i; 30 | 31 | for (i = 0; i < KETAMA_NUM_NODES_MAX; i++) { 32 | if ((c->nodes[i].key = 33 | malloc(KETAMA_NODE_KEY_LEN_MAX * sizeof(char))) == NULL) { 34 | for (i = 0; i < KETAMA_NUM_NODES_MAX; i++) 35 | if (c->nodes[i].key != NULL) free(c->nodes[i].key); 36 | return NULL; 37 | } 38 | } 39 | 40 | return c; 41 | } 42 | 43 | void config_free(struct config *c) { 44 | if (c != NULL) { 45 | int i; 46 | for (i = 0; i < KETAMA_NUM_NODES_MAX; i++) 47 | if (c->nodes[i].key != NULL) free(c->nodes[i].key); 48 | free(c); 49 | } 50 | } 51 | 52 | int config_init(struct config *c, const char *filename) { 53 | assert(c != NULL); 54 | 55 | struct buf *buf = buf_new(NULL); 56 | 57 | /* Read config file */ 58 | int nread; 59 | FILE *fp = fopen(filename, "r"); 60 | 61 | if (fp == NULL) { 62 | log_error("cannot open file %s", filename); 63 | return CONFIG_EFOPEN; 64 | } 65 | 66 | while (1) { 67 | if (buf_grow(buf, buf->len + CONFIG_READ_UNIT) != BUF_OK) { 68 | log_error("no memory error"); 69 | return CONFIG_ENOMEM; 70 | } 71 | 72 | if ((nread = fread(buf->data + buf->len, sizeof(char), 73 | buf->cap - buf->len, fp)) <= 0) 74 | break; 75 | 76 | buf->len += nread; 77 | } 78 | 79 | /* Parse and get values */ 80 | struct cfg cfg; 81 | cfg.data = buf->data; 82 | cfg.len = buf->len; 83 | cfg.lineno = 1; 84 | 85 | int cfg_err; 86 | c->num_nodes = 0; 87 | 88 | char s[CONFIG_VAL_LEN_MAX] = {0}; 89 | 90 | while ((cfg_err = cfg_get(&cfg)) == CFG_OK) { 91 | memset(s, 0, CONFIG_VAL_LEN_MAX); 92 | memcpy(s, cfg.val, cfg.val_len); 93 | 94 | if (cfg.val_len > CONFIG_VAL_LEN_MAX) { 95 | log_error("value is too large at line %d", cfg.lineno); 96 | return CONFIG_EVALUE; 97 | } 98 | 99 | if (strncmp("port", cfg.key, cfg.key_len) == 0) { 100 | long port = strtol(s, NULL, 10); 101 | 102 | if (port <= 0 || port > 65535) { 103 | log_error("invalid port at line %d", cfg.lineno); 104 | return CONFIG_EVALUE; 105 | } 106 | 107 | c->port = (unsigned short)port; 108 | log_debug("load config.port => %hu", c->port); 109 | } 110 | 111 | if (strncmp("num_threads", cfg.key, cfg.key_len) == 0) { 112 | long num_threads = strtol(s, NULL, 10); 113 | 114 | if (num_threads <= 0 || num_threads > 1024) { 115 | log_error("invalid num_threads at line %d", cfg.lineno); 116 | return CONFIG_EVALUE; 117 | } 118 | 119 | c->num_threads = (unsigned short)num_threads; 120 | log_debug("load config.num_threads => %hu", c->num_threads); 121 | } 122 | 123 | if (strncmp("flush_interval", cfg.key, cfg.key_len) == 0) { 124 | long flush_interval = strtol(s, NULL, 10); 125 | 126 | if (flush_interval <= 0 || flush_interval > 1000) { 127 | log_error("invalid flush_interval at line %d", cfg.lineno); 128 | return CONFIG_EVALUE; 129 | } 130 | 131 | c->flush_interval = (uint32_t)flush_interval; 132 | log_debug("load config.flush_interval => %ldms", c->flush_interval); 133 | } 134 | 135 | if (strncmp("socket_receive_bufsize", cfg.key, cfg.key_len) == 0) { 136 | long socket_receive_bufsize = strtol(s, NULL, 10); 137 | 138 | if (socket_receive_bufsize <= 0) { 139 | log_error("invalid socket_receive_bufsize at line %d", cfg.lineno); 140 | return CONFIG_EVALUE; 141 | } 142 | 143 | c->socket_receive_bufsize = socket_receive_bufsize; 144 | log_debug("load config.socket_receive_bufsize => %ld", c->socket_receive_bufsize); 145 | } 146 | 147 | if (strncmp("socket_send_packet_size", cfg.key, cfg.key_len) == 0) { 148 | uint32_t socket_send_packet_size = strtol(s, NULL, 10); 149 | 150 | if (socket_send_packet_size <= 0) { 151 | log_error("invalid socket_send_packet_size at line %d", cfg.lineno); 152 | return CONFIG_EVALUE; 153 | } 154 | 155 | c->socket_send_packet_size = socket_send_packet_size; 156 | log_debug("load config.socket_send_packet_size => %ld", c->socket_send_packet_size); 157 | } 158 | 159 | if (strncmp("node", cfg.key, cfg.key_len) == 0) { 160 | if (strlen(s) >= KETAMA_NODE_KEY_LEN_MAX) { 161 | log_error("node address too large at line %d", cfg.lineno); 162 | return CONFIG_EVALUE; 163 | } 164 | 165 | char host[cfg.val_len]; 166 | unsigned short port; 167 | unsigned int weight; 168 | 169 | if (sscanf(s, "%[^:]:%hu:%u", host, &port, &weight) != 3) { 170 | log_error("invalid node at line %d", cfg.lineno); 171 | return CONFIG_EVALUE; 172 | } 173 | 174 | sprintf((c->nodes[c->num_nodes]).key, "%s:%hu", host, port); 175 | (c->nodes[c->num_nodes]).weight = weight; 176 | (c->nodes[c->num_nodes]).idx = c->num_nodes; 177 | c->num_nodes++; 178 | log_debug("load config.node#%d udp://%s:%hu:%u", c->num_nodes, host, 179 | port, weight); 180 | } 181 | } 182 | 183 | buf_free(buf); 184 | 185 | if (cfg_err == CFG_EBADFMT) { 186 | log_error("invalid syntax in %s, at line %d", filename, cfg.lineno); 187 | return CONFIG_EBADFMT; 188 | } 189 | 190 | log_debug("config load done."); 191 | return CONFIG_OK; 192 | } 193 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Proxy config. 5 | */ 6 | 7 | #ifndef _CW_CONFIG_H 8 | #define _CW_CONFIG_H 1 9 | 10 | #include 11 | #include "ketama.h" 12 | 13 | #define KETAMA_NUM_NODES_MAX 1024 14 | #define KETAMA_NODE_KEY_LEN_MAX 32 15 | #define CONFIG_READ_UNIT 1024 16 | #define CONFIG_VAL_LEN_MAX 1024 17 | 18 | enum { 19 | CONFIG_OK = 0, 20 | CONFIG_ENOMEM = 1, 21 | CONFIG_EFOPEN = 2, 22 | CONFIG_EBADFMT = 3, 23 | CONFIG_EVALUE = 4, 24 | }; 25 | 26 | struct config { 27 | unsigned short port; 28 | unsigned short num_threads; 29 | size_t num_nodes; 30 | uint32_t flush_interval; 31 | long socket_receive_bufsize; 32 | uint32_t socket_send_packet_size; 33 | struct ketama_node nodes[KETAMA_NUM_NODES_MAX]; 34 | }; 35 | 36 | struct config *config_new(void); 37 | int config_init(struct config *c, const char *filename); 38 | void config_free(struct config *c); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/ctx.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include "ctx.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "buf.h" 18 | #include "ketama.h" 19 | #include "log.h" 20 | 21 | /* Create ctx, init client/server sockets and ketama ring. */ 22 | struct ctx *ctx_new(struct ketama_node *nodes, size_t num_nodes, 23 | unsigned short port, uint32_t flush_interval, long socket_receive_bufsize, uint32_t socket_send_packet_size) { 24 | assert(nodes != NULL); 25 | 26 | /* Create ctx */ 27 | struct ctx *ctx = malloc(sizeof(struct ctx)); 28 | 29 | if (ctx == NULL) return NULL; 30 | 31 | /* Create recv buf (for this thread) */ 32 | struct buf *buf = buf_new(NULL); 33 | 34 | if (buf == NULL) { 35 | free(ctx); 36 | return NULL; 37 | } 38 | 39 | /* Create net address */ 40 | struct sockaddr_in *addrs = malloc(sizeof(struct sockaddr_in) * num_nodes); 41 | 42 | if (addrs == NULL) { 43 | free(buf); 44 | free(ctx); 45 | return NULL; 46 | } 47 | 48 | /* Create ketama ring */ 49 | struct ketama_ring *ring = ketama_ring_new(nodes, num_nodes); 50 | 51 | if (ring == NULL) { 52 | free(addrs); 53 | free(buf); 54 | free(ctx); 55 | return NULL; 56 | } 57 | 58 | /* Create send bufs (for each node) 59 | * note: for threading safe reasons, we cannot assign `node.data` and 60 | * arg `nodes` */ 61 | struct buf **sbufs = malloc(sizeof(struct buf *) * num_nodes); 62 | 63 | if (sbufs == NULL) { 64 | ketama_ring_free(ring); 65 | free(addrs); 66 | free(buf); 67 | free(ctx); 68 | return NULL; 69 | } 70 | 71 | int i; 72 | 73 | for (i = 0; i < num_nodes; i++) { 74 | if ((sbufs[i] = buf_new(NULL)) == NULL) { 75 | free(sbufs); 76 | ketama_ring_free(ring); 77 | free(addrs); 78 | free(buf); 79 | free(ctx); 80 | return NULL; 81 | } 82 | } 83 | 84 | /* Assign all attrs */ 85 | ctx->buf = buf; 86 | ctx->cfd = -1; 87 | ctx->sfd = -1; 88 | ctx->ring = ring; 89 | ctx->port = port; 90 | ctx->addrs = addrs; 91 | ctx->nodes = nodes; 92 | ctx->sbufs = sbufs; 93 | ctx->num_nodes = num_nodes; 94 | ctx->flush_interval = flush_interval; 95 | ctx->socket_receive_bufsize = socket_receive_bufsize; 96 | ctx->socket_send_packet_size = socket_send_packet_size; 97 | return ctx; 98 | } 99 | 100 | /* Free ctx, close client/server sockets and free ketama ring. */ 101 | void ctx_free(struct ctx *ctx) { 102 | if (ctx != NULL) { 103 | if (ctx->cfd > 0) close(ctx->cfd); 104 | 105 | if (ctx->sfd > 0) close(ctx->sfd); 106 | 107 | if (ctx->ring != NULL) ketama_ring_free(ctx->ring); 108 | 109 | if (ctx->buf != NULL) buf_free(ctx->buf); 110 | 111 | if (ctx->addrs != NULL) free(ctx->addrs); 112 | 113 | if (ctx->sbufs != NULL) { 114 | int i; 115 | for (i = 0; i < ctx->num_nodes; i++) buf_free(ctx->sbufs[i]); 116 | free(ctx->sbufs); 117 | } 118 | 119 | free(ctx); 120 | } 121 | } 122 | 123 | /* Init ctx, init addrs and client/sockets */ 124 | int ctx_init(struct ctx *ctx) { 125 | assert(ctx != NULL); 126 | 127 | /* Fillin net address */ 128 | assert(ctx->nodes != NULL); 129 | assert(ctx->addrs != NULL); 130 | struct sockaddr_in *addrs = ctx->addrs; 131 | int i; 132 | unsigned short bport = 8125; 133 | char bhost[17] = {0}; /* 255.255.255.255 (15) */ 134 | struct ketama_node *nodes = ctx->nodes; 135 | 136 | for (i = 0; i < ctx->num_nodes; i++) { 137 | if (strlen(nodes[i].key) > 26) /* 15 + 10 + 1 = 26 */ 138 | return CTX_EBADFMT; 139 | 140 | if (sscanf(nodes[i].key, "%[^:]:%hu", bhost, &bport) != 2) 141 | return CTX_EBADFMT; 142 | 143 | bzero(&addrs[i], sizeof(struct sockaddr_in)); 144 | addrs[i].sin_family = AF_INET; 145 | addrs[i].sin_addr.s_addr = inet_addr(bhost); 146 | addrs[i].sin_port = htons(bport); 147 | } 148 | 149 | /* Init client socket */ 150 | assert(ctx->cfd == -1); 151 | 152 | ctx->cfd = socket(AF_INET, SOCK_DGRAM, 0); 153 | 154 | if (ctx->cfd < 0) { 155 | return CTX_ESOCKET; 156 | } 157 | 158 | /* Init server socket */ 159 | assert(ctx->sfd == -1); 160 | 161 | ctx->sfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); 162 | int optval = 1; 163 | setsockopt(ctx->sfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&optval, 164 | sizeof(int)); 165 | if (ctx->socket_receive_bufsize > 0) { 166 | log_debug("Got non-zero socket_receive_bufsize. Setting SO_RCVBUF to %ld...", ctx->socket_receive_bufsize); 167 | setsockopt(ctx->sfd, SOL_SOCKET, SO_RCVBUF, &ctx->socket_receive_bufsize, sizeof(ctx->socket_receive_bufsize)); 168 | long actual_bufsize; 169 | socklen_t buflen = sizeof(actual_bufsize); 170 | if (getsockopt(ctx->sfd, SOL_SOCKET, SO_RCVBUF, &actual_bufsize, &buflen) == 0) { 171 | if (actual_bufsize != ctx->socket_receive_bufsize * KERNEL_ADDED_OVERHEAD_FACTOR) { 172 | log_warn("unable to set socket_receive_bufsize to %ld. buffer was reset to %ld", ctx->socket_receive_bufsize, actual_bufsize); 173 | } 174 | log_debug("SO_RCVBUF ultimately set to %ld", actual_bufsize); 175 | } 176 | else { 177 | log_warn("unable to read socket buffer size: %s", strerror(errno)); 178 | } 179 | } 180 | 181 | if (ctx->sfd < 0) { 182 | close(ctx->cfd); 183 | return CTX_ESOCKET; 184 | } 185 | return CTX_OK; 186 | } 187 | -------------------------------------------------------------------------------- /src/ctx.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Proxy thread context. 5 | */ 6 | 7 | 8 | #ifndef KERNEL_ADDED_OVERHEAD_FACTOR 9 | #define KERNEL_ADDED_OVERHEAD_FACTOR 2 // socket(7) man page indicates that the kernel doubles the size of SO_RCVBUF when set by setsockopt() 10 | #endif 11 | 12 | #ifndef _CW_CTX_H 13 | #define _CW_CTX_H 1 14 | 15 | #include 16 | #include "buf.h" 17 | #include "ketama.h" 18 | 19 | #if defined(__cplusplus) 20 | extern "C" { 21 | #endif 22 | 23 | enum { 24 | CTX_OK = 0, /* operation is ok */ 25 | CTX_ENOMEM = 1, /* no memory error */ 26 | CTX_EBADFMT = 2, /* invalid format */ 27 | CTX_ESOCKET = 3, /* socket create error */ 28 | CTX_ETFD = 4, /* timer fd create error */ 29 | }; 30 | 31 | struct ctx { 32 | int cfd; /* client udp socket fd */ 33 | int sfd; /* server udp socket fd */ 34 | unsigned short port; /* server port to bind */ 35 | uint32_t flush_interval; /* buffer flush interval */ 36 | long socket_receive_bufsize; /* socket receive buffer size in bytes */ 37 | uint32_t socket_send_packet_size; /* socket send packet size in bytes */ 38 | size_t num_nodes; /* number of ketama nodes */ 39 | struct ketama_node * 40 | nodes; /* ketama nodes ref (shared by multiple threads, read only) */ 41 | struct ketama_ring *ring; /* ketama ring */ 42 | struct sockaddr_in *addrs; /* backend addresses */ 43 | struct buf *buf; /* buffer to read socket */ 44 | struct buf **sbufs; /* buffers to send to socket */ 45 | }; 46 | 47 | struct ctx *ctx_new(struct ketama_node *nodes, size_t num_nodes, 48 | unsigned short port, uint32_t flush_interval, long socket_receive_bufsize, uint32_t socket_send_packet_size); 49 | void ctx_free(struct ctx *ctx); 50 | int ctx_init(struct ctx *ctx); 51 | 52 | #if defined(__cplusplus) 53 | } 54 | #endif 55 | #endif 56 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "event.h" 9 | 10 | #include "event_timer.c" 11 | #ifdef HAVE_KQUEUE 12 | #include "event_kqueue.c" 13 | #else 14 | #ifdef HAVE_EPOLL 15 | #include "event_epoll.c" 16 | #else 17 | #error "no event lib avaliable" 18 | #endif 19 | #endif 20 | 21 | /* Create an event loop. */ 22 | struct event_loop *event_loop_new(int size) { 23 | assert(size >= 0); 24 | 25 | /* event numbers must be greater than RESERVED_FDS + FDSET_INCR */ 26 | size += EVENT_FDSET_INCR + EVENT_MIN_RESERVED_FDS; 27 | 28 | struct event_loop *loop = malloc(sizeof(struct event_loop)); 29 | 30 | if (loop == NULL) return NULL; 31 | 32 | loop->size = size; 33 | loop->events = NULL; 34 | loop->api = NULL; 35 | loop->num_timers = 0; 36 | 37 | /* events */ 38 | loop->events = malloc(sizeof(struct event) * size); 39 | if (loop->events == NULL) { 40 | free(loop); 41 | return NULL; 42 | } 43 | 44 | /* event api */ 45 | if (event_api_loop_new(loop) != EVENT_OK) { 46 | free(loop->events); 47 | free(loop); 48 | return NULL; 49 | } 50 | 51 | /* timer heap */ 52 | loop->timer_heap = event_timer_heap_new(); 53 | if (loop->timer_heap == NULL) { 54 | event_api_loop_free(loop); 55 | free(loop->events); 56 | free(loop); 57 | return NULL; 58 | } 59 | 60 | /* init all timers id to -1 */ 61 | int i; 62 | for (i = 0; i < EVENT_TIMER_ID_MAX; i++) loop->timers[i].id = -1; 63 | 64 | /* init all events mask to NONE */ 65 | for (i = 0; i < size; i++) loop->events[i].mask = EVENT_NONE; 66 | return loop; 67 | } 68 | 69 | /* Free an event loop. */ 70 | void event_loop_free(struct event_loop *loop) { 71 | if (loop != NULL) { 72 | event_timer_heap_free(loop->timer_heap); 73 | event_api_loop_free(loop); 74 | if (loop->events != NULL) free(loop->events); 75 | free(loop); 76 | } 77 | } 78 | 79 | /* Wait for events. */ 80 | int event_wait(struct event_loop *loop) { 81 | assert(loop != NULL); 82 | 83 | long time_now = event_time_now(); 84 | long timeout = -1; /* block forever */ 85 | struct event_timer *nearest_timer = event_nearest_timer(loop); 86 | 87 | if (nearest_timer != NULL) timeout = nearest_timer->fire_at - time_now; 88 | 89 | int result = event_api_wait(loop, timeout); 90 | event_process_timers(loop); 91 | return result; 92 | } 93 | 94 | /* Start event loop */ 95 | int event_loop_start(struct event_loop *loop) { 96 | assert(loop != NULL); 97 | 98 | loop->state = EVENT_LOOP_RUNNING; 99 | 100 | int err; 101 | 102 | while (loop->state != EVENT_LOOP_STOPPED) 103 | if ((err = event_wait(loop)) != EVENT_OK) return err; 104 | 105 | return EVENT_OK; 106 | } 107 | 108 | /* Stop event loop */ 109 | void event_loop_stop(struct event_loop *loop) { 110 | assert(loop != NULL); 111 | loop->state = EVENT_LOOP_STOPPED; 112 | } 113 | 114 | /* Add an event to event loop (mod if the fd already in set). */ 115 | int event_add(struct event_loop *loop, int fd, int mask, event_cb_t cb, 116 | void *data) { 117 | assert(loop != NULL); 118 | assert(loop->api != NULL); 119 | assert(cb != NULL); 120 | 121 | if (fd > loop->size) return EVENT_ERANGE; 122 | 123 | int err = event_api_add(loop, fd, mask); 124 | 125 | if (err != EVENT_OK) return err; 126 | 127 | struct event *ev = &loop->events[fd]; 128 | ev->mask |= mask; 129 | 130 | if (mask & EVENT_ERROR) ev->ecb = cb; 131 | if (mask & EVENT_READABLE) ev->rcb = cb; 132 | if (mask & EVENT_WRITABLE) ev->wcb = cb; 133 | 134 | ev->data = data; 135 | return EVENT_OK; 136 | } 137 | 138 | /* Delete an event from loop. */ 139 | int event_del(struct event_loop *loop, int fd, int mask) { 140 | assert(loop != NULL); 141 | 142 | if (fd > loop->size) return EVENT_ERANGE; 143 | 144 | struct event *ev = &loop->events[fd]; 145 | 146 | if (ev->mask == EVENT_NONE) return EVENT_OK; 147 | 148 | int err = event_api_del(loop, fd, mask); 149 | 150 | if (err != EVENT_OK) return err; 151 | 152 | ev->mask = ev->mask & (~mask); 153 | return EVENT_OK; 154 | } 155 | 156 | /* Add timer to event loop. (interval#ms) */ 157 | int event_add_timer(struct event_loop *loop, long interval, event_timer_cb_t cb, 158 | void *data) { 159 | assert(loop != NULL && loop->timers != NULL && loop->timer_heap != NULL); 160 | assert(interval > 0); 161 | 162 | int id; 163 | struct event_timer *timer; 164 | 165 | /* find available id */ 166 | for (id = 0; id < EVENT_TIMER_ID_MAX; id++) { 167 | timer = &loop->timers[id]; 168 | if (timer->id < 0) break; 169 | } 170 | 171 | if (id >= EVENT_TIMER_ID_MAX) return EVENT_ERANGE; 172 | 173 | timer->id = id; 174 | timer->cb = cb; 175 | timer->interval = interval; 176 | timer->fire_at = event_time_now() + interval; 177 | timer->data = data; 178 | loop->num_timers += 1; 179 | /* push to heap */ 180 | event_timer_heap_push(loop->timer_heap, timer); 181 | return EVENT_OK; 182 | } 183 | 184 | /* Delete timer from event loop. */ 185 | int event_del_timer(struct event_loop *loop, int id) { 186 | assert(loop != NULL && loop->timers != NULL && loop->timer_heap != NULL); 187 | 188 | if (id < 0 || id >= EVENT_TIMER_ID_MAX) return EVENT_ERANGE; 189 | 190 | struct event_timer *timer = &loop->timers[id]; 191 | 192 | if (timer->id < 0) return EVENT_ENOTFOUND; 193 | 194 | int error; 195 | if ((error = event_timer_heap_del(loop->timer_heap, id)) != EVENT_OK) 196 | return error; 197 | 198 | timer->id = -1; 199 | loop->num_timers -= 1; 200 | return EVENT_OK; 201 | } 202 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Event loop wrapper. 5 | * deps: event_epoll.c event_kqueue.c. 6 | */ 7 | 8 | #ifndef __EVENT_H__ 9 | #define __EVENT_H__ 10 | 11 | #if defined(__cplusplus) 12 | extern "C" { 13 | #endif 14 | 15 | #include 16 | 17 | #define EVENT_MIN_RESERVED_FDS 32 18 | #define EVENT_FDSET_INCR 96 19 | #define EVENT_TIMER_ID_MAX 1024 * 10 20 | 21 | #define EVENT_NONE 0b000 22 | #define EVENT_READABLE 0b001 23 | #define EVENT_WRITABLE 0b010 24 | #define EVENT_ERROR 0b100 25 | 26 | #define EVENT_LOOP_RUNNING 0 27 | #define EVENT_LOOP_STOPPED 1 28 | 29 | #ifdef __linux__ 30 | #define HAVE_EPOLL 1 31 | #endif 32 | 33 | #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ 34 | defined(__OpenBSD__) 35 | #define HAVE_KQUEUE 1 36 | #endif 37 | 38 | #ifdef __sun 39 | #include 40 | #ifdef _dtrace_version 41 | #define HAVE_EVPORT 1 42 | #endif 43 | #endif 44 | 45 | #define event_loop(size) event_loop_new(size) 46 | #define event_add_in(loop, fd, cb, data) \ 47 | event_add(loop, fd, EVENT_READABLE, cb, data); 48 | #define event_add_out(loop, fd, cb, data) \ 49 | event_add(loop, fd, EVENT_WRITABLE, cb, data); 50 | #define event_del_in(loop, fd) event_del(loop, fd, EVENT_READABLE); 51 | #define event_del_out(loop, fd) event_del(loop, fd, EVENT_WRITABLE); 52 | 53 | enum { 54 | EVENT_OK = 0, /* operation is ok */ 55 | EVENT_ENOMEM = 1, /* no memory error */ 56 | EVENT_EFAILED = 2, /* operation is failed */ 57 | EVENT_ERANGE = 3, /* range is invalid */ 58 | EVENT_ENOTFOUND = 4, /* not found error */ 59 | }; 60 | 61 | struct event_loop; 62 | struct event_api; 63 | 64 | typedef void (*event_cb_t)(struct event_loop *loop, int fd, int mask, 65 | void *data); 66 | typedef void (*event_timer_cb_t)(struct event_loop *loop, int id, void *data); 67 | 68 | struct event { 69 | int mask; /* EVENT_(NONE|READABLE|WRITABLE..) */ 70 | event_cb_t rcb; /* callback function on EVENT_READABLE */ 71 | event_cb_t wcb; /* callback function on EVENT_WRITABLE */ 72 | event_cb_t ecb; /* callback function on EVENT_ERROR */ 73 | void *data; /* user defined data */ 74 | }; 75 | 76 | struct event_timer { 77 | int id; /* timer identifier [0, EVENT_TIMER_ID_MAX) */ 78 | event_timer_cb_t cb; /* callback function on timer fired */ 79 | long interval; /* periodicity interval to fire (ms) */ 80 | long fire_at; /* the time for the next fire (ms) */ 81 | void *data; /* user defined data */ 82 | }; 83 | 84 | struct event_timer_heap { 85 | struct event_timer *timers[EVENT_TIMER_ID_MAX]; 86 | size_t len; 87 | }; 88 | 89 | struct event_loop { 90 | int size; /* the max number of fds to track */ 91 | int state; /* one of EVENT_LOOP_(STOPPED|RUNNING) */ 92 | int num_timers; /* the number of timers */ 93 | struct event *events; /* struct event[] */ 94 | struct event_api *api; /* to be implemented */ 95 | struct event_timer 96 | timers[EVENT_TIMER_ID_MAX]; /* struct event_timers[MAX] */ 97 | struct event_timer_heap *timer_heap; 98 | }; 99 | 100 | struct event_loop *event_loop_new(int size); 101 | void event_loop_free(struct event_loop *loop); 102 | int event_loop_start(struct event_loop *loop); 103 | void event_loop_stop(struct event_loop *loop); 104 | int event_add(struct event_loop *loop, int fd, int mask, event_cb_t cb, 105 | void *data); /* O(1) */ 106 | int event_del(struct event_loop *loop, int fd, int mask); 107 | int event_wait(struct event_loop *loop); 108 | int event_add_timer(struct event_loop *loop, long interval, event_timer_cb_t cb, 109 | void *data); /* O(EVENT_TIMER_ID_MAX) */ 110 | int event_del_timer(struct event_loop *loop, int id); /* O(1) */ 111 | 112 | #if defined(__cplusplus) 113 | } 114 | #endif 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/event_epoll.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "event.h" 11 | 12 | #define EVENT_EPOLL_ALWAYS_ET 1 13 | 14 | struct event_api { 15 | int ep; /* epoll descriptor */ 16 | struct epoll_event * 17 | events; /* struct epoll_events[], with size `loop->size` */ 18 | }; 19 | 20 | static int event_api_loop_new(struct event_loop *loop) { 21 | assert(loop != NULL); 22 | assert(loop->size > 0); 23 | assert(loop->api == NULL); 24 | 25 | struct event_api *api = malloc(sizeof(struct event_api)); 26 | 27 | if (api == NULL) return EVENT_ENOMEM; 28 | 29 | api->ep = epoll_create(loop->size); 30 | 31 | if (api->ep < 0) { 32 | free(api); 33 | return EVENT_EFAILED; 34 | } 35 | 36 | api->events = malloc(sizeof(struct epoll_event) * loop->size); 37 | 38 | if (api->events == NULL) { 39 | close(api->ep); 40 | free(api); 41 | return EVENT_ENOMEM; 42 | } 43 | 44 | loop->api = api; 45 | return EVENT_OK; 46 | } 47 | 48 | void event_api_loop_free(struct event_loop *loop) { 49 | assert(loop != NULL); 50 | 51 | if (loop->api != NULL) { 52 | if (loop->api->ep > 0) close(loop->api->ep); 53 | if (loop->api->events != NULL) free(loop->api->events); 54 | free(loop->api); 55 | } 56 | } 57 | 58 | int event_api_add(struct event_loop *loop, int fd, int mask) { 59 | assert(loop != NULL); 60 | assert(loop->events != NULL); 61 | assert(loop->api != NULL); 62 | 63 | struct epoll_event ev; 64 | 65 | int op = 66 | loop->events[fd].mask == EVENT_NONE ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; 67 | 68 | ev.events = 0; 69 | ev.data.fd = fd; 70 | 71 | mask |= loop->events[fd].mask; /* merge old events */ 72 | 73 | if (EVENT_EPOLL_ALWAYS_ET) ev.events |= EPOLLET; 74 | if (mask & EVENT_READABLE) ev.events |= EPOLLIN; 75 | if (mask & EVENT_WRITABLE) ev.events |= EPOLLOUT; 76 | if (mask & EVENT_ERROR) ev.events |= EPOLLERR; 77 | 78 | if (epoll_ctl(loop->api->ep, op, fd, &ev) < 0) return EVENT_EFAILED; 79 | 80 | return EVENT_OK; 81 | } 82 | 83 | int event_api_del(struct event_loop *loop, int fd, int delmask) { 84 | assert(loop != NULL); 85 | assert(loop->events != NULL); 86 | assert(loop->api != NULL); 87 | 88 | struct epoll_event ev; 89 | int mask = loop->events[fd].mask & (~delmask); 90 | 91 | ev.events = 0; 92 | 93 | if (EVENT_EPOLL_ALWAYS_ET) ev.events |= EPOLLET; 94 | if (mask & EVENT_READABLE) ev.events |= EPOLLIN; 95 | if (mask & EVENT_WRITABLE) ev.events |= EPOLLOUT; 96 | if (mask & EVENT_ERROR) ev.events |= EPOLLERR; 97 | 98 | ev.data.fd = fd; 99 | 100 | if (mask != EVENT_NONE) { 101 | epoll_ctl(loop->api->ep, EPOLL_CTL_MOD, fd, &ev); 102 | } else { 103 | /* Note: kernel < 2.6.9 requires a non null event pointer even for 104 | * EPOLL_CTL_DEL. */ 105 | epoll_ctl(loop->api->ep, EPOLL_CTL_DEL, fd, &ev); 106 | } 107 | return EVENT_OK; 108 | } 109 | 110 | int event_api_wait(struct event_loop *loop, int timeout) { 111 | assert(loop != NULL); 112 | assert(loop->events != NULL); 113 | 114 | struct event_api *api = loop->api; 115 | 116 | assert(api != NULL); 117 | assert(api->ep >= 0); 118 | assert(api->events != NULL); 119 | 120 | int i; 121 | int nfds = epoll_wait(api->ep, api->events, loop->size, timeout); 122 | 123 | if (nfds > 0) { 124 | for (i = 0; i < nfds; i++) { 125 | struct epoll_event ee = api->events[i]; 126 | int fd = ee.data.fd; 127 | struct event ev = loop->events[fd]; 128 | 129 | int mask = 0; 130 | 131 | if (ee.events & EPOLLERR) mask |= EVENT_ERROR; 132 | if (ee.events & EPOLLIN) mask |= EVENT_READABLE; 133 | if (ee.events & EPOLLOUT) mask |= EVENT_WRITABLE; 134 | 135 | if (mask & EVENT_ERROR && ev.ecb != NULL) 136 | (ev.ecb)(loop, fd, mask, ev.data); 137 | if (mask & EVENT_READABLE && ev.rcb != NULL) 138 | (ev.rcb)(loop, fd, mask, ev.data); 139 | if (mask & EVENT_WRITABLE && ev.wcb != NULL) 140 | (ev.wcb)(loop, fd, mask, ev.data); 141 | } 142 | 143 | return EVENT_OK; 144 | } 145 | 146 | if (nfds == 0) { 147 | if (timeout >= 0) return EVENT_OK; 148 | } 149 | 150 | return EVENT_EFAILED; 151 | } 152 | -------------------------------------------------------------------------------- /src/event_timer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "event.h" 11 | 12 | /* Get time now in milliseconds. */ 13 | static long event_time_now(void) { 14 | struct timeval tv; 15 | gettimeofday(&tv, NULL); 16 | return (1000000 * tv.tv_sec + tv.tv_usec) / 1000; 17 | } 18 | 19 | /** 20 | * Event timer heap 21 | */ 22 | 23 | /* Create a timer heap. */ 24 | struct event_timer_heap *event_timer_heap_new(void) { 25 | struct event_timer_heap *heap = malloc(sizeof(struct event_timer_heap)); 26 | if (heap != NULL) heap->len = 0; 27 | return heap; 28 | } 29 | 30 | /* Free a timer heap. */ 31 | void event_timer_heap_free(struct event_timer_heap *heap) { 32 | if (heap != NULL) free(heap); 33 | } 34 | 35 | /* Sift down the timer heap. */ 36 | void event_timer_heap_siftdown(struct event_timer_heap *heap, size_t start_idx, 37 | size_t idx) { 38 | assert(heap != NULL && heap->timers != NULL); 39 | 40 | size_t parent_idx; 41 | struct event_timer *parent; 42 | struct event_timer *timer = heap->timers[idx]; 43 | 44 | while (idx > start_idx) { 45 | parent_idx = (idx - 1) >> 1; 46 | parent = heap->timers[parent_idx]; 47 | if (timer->fire_at < parent->fire_at) { 48 | heap->timers[idx] = parent; 49 | idx = parent_idx; 50 | continue; 51 | } 52 | break; 53 | } 54 | heap->timers[idx] = timer; 55 | } 56 | 57 | /* Sift up the timer heap. */ 58 | void event_timer_heap_siftup(struct event_timer_heap *heap, size_t idx) { 59 | assert(heap != NULL && heap->timers != NULL); 60 | 61 | size_t len = heap->len; 62 | size_t start_idx = idx; 63 | size_t child_idx = idx * 2 + 1; 64 | size_t right_idx; 65 | struct event_timer *timer = heap->timers[idx]; 66 | 67 | while (child_idx < len) { 68 | right_idx = child_idx + 1; 69 | if (right_idx < len && 70 | heap->timers[child_idx]->fire_at >= 71 | heap->timers[right_idx]->fire_at) 72 | child_idx = right_idx; 73 | heap->timers[idx] = heap->timers[child_idx]; 74 | idx = child_idx; 75 | child_idx = idx * 2 + 1; 76 | } 77 | 78 | heap->timers[idx] = timer; 79 | event_timer_heap_siftdown(heap, start_idx, idx); 80 | } 81 | 82 | /* Push a timer into timer heap. */ 83 | int event_timer_heap_push(struct event_timer_heap *heap, 84 | struct event_timer *timer) { 85 | assert(heap != NULL && heap->timers != NULL); 86 | 87 | if (heap->len >= EVENT_TIMER_ID_MAX) return EVENT_ERANGE; 88 | 89 | heap->timers[heap->len++] = timer; 90 | event_timer_heap_siftdown(heap, 0, heap->len - 1); 91 | return EVENT_OK; 92 | } 93 | 94 | /* Pop a timer from heap, NULL on empty. */ 95 | struct event_timer *event_timer_heap_pop(struct event_timer_heap *heap) { 96 | assert(heap != NULL && heap->timers != NULL); 97 | 98 | if (heap->len == 0) return NULL; 99 | 100 | struct event_timer *tail = heap->timers[--heap->len]; 101 | if (heap->len == 0) return tail; 102 | struct event_timer *head = heap->timers[0]; 103 | heap->timers[0] = tail; 104 | event_timer_heap_siftup(heap, 0); 105 | return head; 106 | } 107 | 108 | /* Get the smallest timer from heap, NULL on empty. */ 109 | struct event_timer *event_timer_heap_top(struct event_timer_heap *heap) { 110 | assert(heap != NULL && heap->timers != NULL); 111 | 112 | if (heap->len == 0) return NULL; 113 | return heap->timers[0]; 114 | } 115 | 116 | /* Delete timer by id from heap. */ 117 | int event_timer_heap_del(struct event_timer_heap *heap, int id) { 118 | assert(heap != NULL && heap->timers != NULL); 119 | 120 | if (id < 0 || id >= EVENT_TIMER_ID_MAX) return EVENT_ERANGE; 121 | 122 | if (heap->len == 0) return EVENT_ENOTFOUND; 123 | 124 | int i; 125 | for (i = 0; i < heap->len; i++) { 126 | if (heap->timers[i]->id == id) { 127 | heap->len -= 1; 128 | if (heap->len > 0) { 129 | heap->timers[i] = heap->timers[heap->len]; 130 | event_timer_heap_siftup(heap, i); 131 | } 132 | return EVENT_OK; 133 | } 134 | } 135 | return EVENT_ENOTFOUND; 136 | } 137 | 138 | /* Replace the top item with another. */ 139 | int event_timer_heap_replace(struct event_timer_heap *heap, 140 | struct event_timer *timer) { 141 | assert(heap != NULL && heap->timers != NULL); 142 | 143 | if (heap->len == 0) return EVENT_ERANGE; 144 | heap->timers[0] = timer; 145 | event_timer_heap_siftup(heap, 0); 146 | return EVENT_OK; 147 | } 148 | 149 | /** 150 | * Event loop. 151 | */ 152 | 153 | /** 154 | * Get the nearest timer to fire from timer heap. O(1) 155 | */ 156 | struct event_timer *event_nearest_timer(struct event_loop *loop) { 157 | assert(loop != NULL && loop->timers != NULL && loop->timer_heap != NULL); 158 | return event_timer_heap_top(loop->timer_heap); 159 | } 160 | 161 | /** 162 | * Fire timed out timers from heap and update their fire_at. O(log(N)). 163 | */ 164 | void event_process_timers(struct event_loop *loop) { 165 | assert(loop != NULL && loop->timers != NULL && loop->timer_heap); 166 | 167 | struct event_timer *timer; 168 | 169 | while (1) { 170 | timer = event_timer_heap_top(loop->timer_heap); 171 | if (timer == NULL) /* no waiting timers to be processed */ 172 | break; 173 | if (timer->fire_at <= event_time_now()) { 174 | /* fire this timeouted timer */ 175 | if (timer->cb != NULL) (timer->cb)(loop, timer->id, timer->data); 176 | if (timer->id < 0) { 177 | continue; // won't push back if the timer is invalid now. 178 | } 179 | /* push back the timer */ 180 | timer->fire_at += timer->interval; 181 | event_timer_heap_replace(loop->timer_heap, timer); 182 | continue; 183 | } 184 | /* the other timers are not ready now */ 185 | break; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/ketama.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include "ketama.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "md5.h" 11 | 12 | static uint32_t ketama_hash(char *key, size_t len, size_t align) { 13 | assert(align < 4); 14 | unsigned char results[16]; 15 | md5_signature((unsigned char *)key, (unsigned long)len, results); 16 | return ((uint32_t)(results[3 + align * 4] & 0xff) << 24) | 17 | ((uint32_t)(results[2 + align * 4] & 0xff) << 16) | 18 | ((uint32_t)(results[1 + align * 4] & 0xff) << 8) | 19 | (results[0 + align * 4] & 0xff); 20 | } 21 | 22 | static int ketama_node_cmp(const void *node_a, const void *node_b) { 23 | uint32_t hash_a = ((struct ketama_node *)node_a)->hash; 24 | uint32_t hash_b = ((struct ketama_node *)node_b)->hash; 25 | 26 | if (hash_a > hash_b) 27 | return 1; 28 | else if (hash_a < hash_b) 29 | return -1; 30 | else 31 | return 0; 32 | } 33 | 34 | /* Create ketama hash ring from nodes array. */ 35 | struct ketama_ring *ketama_ring_new(struct ketama_node *nodes, size_t len) { 36 | if (len > 0) assert(nodes != NULL); 37 | 38 | struct ketama_ring *ring = malloc(sizeof(struct ketama_ring)); 39 | 40 | if (ring == NULL) return NULL; 41 | 42 | int i; 43 | 44 | for (i = 0, ring->len = 0; i < len; i++) ring->len += nodes[i].weight * 160; 45 | 46 | ring->nodes = malloc(sizeof(struct ketama_node) * ring->len); 47 | 48 | if (ring->nodes == NULL) { 49 | free(ring); 50 | return NULL; 51 | } 52 | 53 | int j, k, n, digits; 54 | struct ketama_node *node; 55 | /* memset(&node, 0, sizeof(node)); */ 56 | unsigned int num; 57 | size_t key_len_max; 58 | for (i = 0, k = 0; i < len; i++) { 59 | node = &nodes[i]; 60 | 61 | for (digits = 0, num = node->weight; num > 0; num /= 10, ++digits) 62 | ; 63 | 64 | assert(node->key != NULL); 65 | assert(node->hash == 0); 66 | 67 | key_len_max = strlen(node->key) + digits + 1; 68 | char key[key_len_max]; 69 | 70 | for (j = 0; j < node->weight * 40; j++) { 71 | memset(key, 0, key_len_max); 72 | sprintf(key, "%s-%d", node->key, j); 73 | for (n = 0; n < 4; n++, k++) { 74 | ring->nodes[k].key = node->key; 75 | ring->nodes[k].weight = node->weight; 76 | ring->nodes[k].data = node->data; 77 | ring->nodes[k].idata = node->idata; 78 | ring->nodes[k].idx = i; 79 | ring->nodes[k].hash = ketama_hash(key, strlen(key), n); 80 | } 81 | } 82 | } 83 | 84 | qsort(ring->nodes, ring->len, sizeof(struct ketama_node), ketama_node_cmp); 85 | return ring; 86 | } 87 | 88 | /* Free ketama ring. */ 89 | void ketama_ring_free(struct ketama_ring *ring) { 90 | if (ring != NULL) { 91 | if (ring->nodes != NULL) free(ring->nodes); 92 | free(ring); 93 | } 94 | } 95 | 96 | /* Get node by key from ring. */ 97 | struct ketama_node *ketama_node_iget(struct ketama_ring *ring, char *key, 98 | size_t key_len) { 99 | assert(ring != NULL); 100 | assert(key != NULL); 101 | assert(ring->nodes != NULL); 102 | 103 | struct ketama_node *nodes = ring->nodes; 104 | size_t len = ring->len; 105 | 106 | if (len == 0) return NULL; 107 | 108 | if (len == 1) return &nodes[0]; 109 | 110 | int left = 0, right = len, mid; 111 | uint32_t hash = ketama_hash(key, key_len, 0); 112 | uint32_t mval, pval; 113 | 114 | while (1) { 115 | mid = (left + right) / 2; 116 | 117 | if (mid == len) return &nodes[0]; 118 | 119 | mval = nodes[mid].hash; 120 | pval = mid == 0 ? 0 : nodes[mid - 1].hash; 121 | 122 | if (hash <= mval && hash > pval) return &nodes[mid]; 123 | 124 | if (mval < hash) { 125 | left = mid + 1; 126 | } else { 127 | right = mid - 1; 128 | } 129 | 130 | if (left > right) return &nodes[0]; 131 | } 132 | } 133 | 134 | struct ketama_node *ketama_node_get(struct ketama_ring *ring, char *key) { 135 | return ketama_node_iget(ring, key, strlen(key)); 136 | } 137 | -------------------------------------------------------------------------------- /src/ketama.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Ketama consistent hashing implementation. 5 | * deps: None. 6 | */ 7 | 8 | #ifndef _CW_KETAMA_H 9 | #define _CW_KETAMA_H 1 10 | 11 | #include 12 | #include 13 | 14 | #if defined(__cplusplus) 15 | extern "C" { 16 | #endif 17 | 18 | #define ketama_ring(nodes, size) ketama_ring_new(nodes, size) 19 | 20 | /* Note ketama ring's `nodes` and its `length` is not the orignal parameter 21 | * passed in. */ 22 | struct ketama_ring { 23 | size_t len; /* hash ring nodes array length */ 24 | struct ketama_node *nodes; /* hash ring nodes array */ 25 | }; 26 | 27 | struct ketama_node { 28 | char *key; /* node key */ 29 | unsigned int weight; /* node weight */ 30 | void *data; /* user data */ 31 | long idata; /* user long typed data */ 32 | size_t idx; /* node idx in origin array */ 33 | uint32_t hash; /* hash value in the ring */ 34 | }; 35 | 36 | struct ketama_ring *ketama_ring_new(struct ketama_node *nodes, size_t len); 37 | void ketama_ring_free(struct ketama_ring *ring); 38 | struct ketama_node *ketama_node_iget(struct ketama_ring *ring, char *key, 39 | size_t key_len); /* O(logN) */ 40 | struct ketama_node *ketama_node_get(struct ketama_ring *ring, 41 | char *key); /* O(logN) */ 42 | 43 | #if defined(__cplusplus) 44 | } 45 | #endif 46 | #endif 47 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "log.h" 19 | 20 | static struct logger logger; 21 | 22 | /* Open global logger, if `filename` is NULL, use stderr. 23 | * The `rotate_size` only works when logging to a file, 24 | * and `rotate_size==0` means no rotation. */ 25 | int log_open(char *name, char *filename, size_t rotate_size) { 26 | assert(name != NULL); 27 | 28 | struct logger *l = &logger; 29 | 30 | l->name = name; 31 | l->level = LOG_INFO; 32 | l->filename = NULL; 33 | 34 | if (filename == NULL) { 35 | l->fd = STDERR_FILENO; 36 | l->rotate_size = 0; 37 | l->fsize = 0; 38 | } else { 39 | assert(strlen(filename) <= LOG_FILENAME_LEN_MAX); 40 | l->filename = filename; 41 | l->rotate_size = rotate_size; 42 | l->fd = open(filename, LOG_FILE_PERM, LOG_FILE_MODE); 43 | 44 | if (l->fd < 0) { 45 | return LOG_EOPEN; 46 | } 47 | 48 | struct stat st; 49 | if (fstat(l->fd, &st) != 0) return LOG_ESTAT; 50 | l->fsize = st.st_size; 51 | } 52 | 53 | if (LOG_THREAD_SAFE) pthread_mutex_init(&(l->lock), NULL); 54 | return LOG_OK; 55 | } 56 | 57 | /* Close global logger. */ 58 | void log_close(void) { 59 | struct logger *l = &logger; 60 | 61 | if (LOG_THREAD_SAFE) pthread_mutex_destroy(&(l->lock)); 62 | 63 | if (l->fd < 0 || l->fd == STDERR_FILENO) return; 64 | close(l->fd); 65 | } 66 | 67 | /* Reopen logging file. */ 68 | int log_reopen(void) { 69 | struct logger *l = &logger; 70 | 71 | if (l->fd < 0 || l->fd == STDERR_FILENO) return LOG_OK; 72 | 73 | close(l->fd); 74 | 75 | assert(l->filename != NULL); 76 | 77 | l->fd = open(l->filename, LOG_FILE_PERM, LOG_FILE_MODE); 78 | 79 | if (l->fd < 0) return LOG_EOPEN; 80 | return LOG_OK; 81 | } 82 | 83 | /* Set logger's level. */ 84 | void log_setlevel(int level) { 85 | struct logger *l = &logger; 86 | 87 | if (level > LOG_CRITICAL) { 88 | l->level = LOG_CRITICAL; 89 | } else if (level < LOG_DEBUG) { 90 | l->level = LOG_DEBUG; 91 | } else { 92 | l->level = level; 93 | } 94 | } 95 | 96 | /* Rotate log file. */ 97 | int log_rotate(void) { 98 | struct logger *l = &logger; 99 | 100 | assert(l->name != NULL); 101 | assert(l->fd > 0 && l->fd != STDERR_FILENO); 102 | assert(l->filename != NULL); 103 | 104 | char buf[LOG_FILENAME_LEN_MAX]; 105 | time_t sec; 106 | struct timeval tv; 107 | struct tm *tm; 108 | gettimeofday(&tv, NULL); 109 | sec = tv.tv_sec; 110 | tm = localtime(&sec); 111 | sprintf(buf, "%s.%04d%02d%02d-%02d%02d%03d", l->filename, 112 | tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, 113 | tm->tm_min, tm->tm_sec); 114 | 115 | if (rename(l->filename, buf)) return LOG_ERENAME; 116 | l->fsize = 0; 117 | return log_reopen(); 118 | } 119 | 120 | /* Format logging message to file/stderr. */ 121 | int log_log(int level, char *levelname, const char *fmt, ...) { 122 | struct logger *l = &logger; 123 | 124 | assert(levelname != NULL); 125 | assert(l->name != NULL); 126 | assert(l->fd == STDERR_FILENO || l->fd > 0); 127 | 128 | if (level < l->level) return LOG_OK; 129 | 130 | int len = 0, size = LOG_LINE_LEN_MAX; 131 | 132 | char buf[size + 1]; 133 | 134 | // readable time with ms 135 | struct timeval tv; 136 | gettimeofday(&tv, NULL); 137 | 138 | len += strftime(buf + len, size - len, "%Y-%m-%d %H:%M:%S.", 139 | localtime(&tv.tv_sec)); 140 | len += snprintf(buf + len, size - len, "%03ld", (long)tv.tv_usec / 1000); 141 | // level 142 | len += snprintf(buf + len, size - len, " %s", levelname); 143 | // name and pid and tid 144 | #ifdef __linux__ 145 | /* using syacall to get tid, or `pid` on system view */ 146 | long pid = (long)syscall(SYS_gettid); 147 | #else 148 | /* I can't find a way to get tid from system view, only print the pid */ 149 | long pid = getpid(); 150 | #endif 151 | len += snprintf(buf + len, size - len, " %s[%ld] ", l->name, pid); 152 | 153 | va_list args; 154 | va_start(args, fmt); 155 | len += vsnprintf(buf + len, size - len, fmt, args); 156 | va_end(args); 157 | 158 | buf[len++] = '\n'; 159 | 160 | if (LOG_THREAD_SAFE) pthread_mutex_lock(&(l->lock)); 161 | 162 | if (write(l->fd, buf, len) < 0) { 163 | return LOG_EWRITE; 164 | } else if (l->filename != NULL) { 165 | l->fsize += len; 166 | if (l->rotate_size != 0) { 167 | if (l->fsize > l->rotate_size) log_rotate(); 168 | } 169 | } 170 | 171 | if (LOG_THREAD_SAFE) pthread_mutex_unlock(&(l->lock)); 172 | return LOG_OK; 173 | } 174 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Logging implementation. 5 | * deps: None. 6 | */ 7 | 8 | #ifndef _CW_LOG_H 9 | #define _CW_LOG_H 1 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #if defined(__cplusplus) 16 | extern "C" { 17 | #endif 18 | 19 | #define LOG_LINE_LEN_MAX 256 20 | #define LOG_FILENAME_LEN_MAX 1024 21 | #define LOG_FILE_MODE 0644 22 | #define LOG_FILE_PERM O_WRONLY | O_APPEND | O_CREAT 23 | #define LOG_THREAD_SAFE 1 24 | 25 | #define LOG_DEBUG_S "debug" 26 | #define LOG_INFO_S "info" 27 | #define LOG_WARN_S "warn" 28 | #define LOG_ERROR_S "error" 29 | #define LOG_CRITICAL_S "critical" 30 | 31 | enum { 32 | LOG_DEBUG = 10, 33 | LOG_INFO = 20, 34 | LOG_WARN = 30, 35 | LOG_ERROR = 40, 36 | LOG_CRITICAL = 50, 37 | }; 38 | 39 | enum { 40 | LOG_OK = 0, /* operation is ok */ 41 | LOG_EOPEN = 1, /* failed to open file */ 42 | LOG_EWRITE = 2, /* failed to write to file */ 43 | LOG_ESTAT = 3, /* failed to stat file */ 44 | LOG_ERENAME = 4, /* failed to rename file */ 45 | }; 46 | 47 | struct logger { 48 | char *name; /* logger name */ 49 | char *filename; /* filename to log */ 50 | size_t rotate_size; /* rotate size, in bytes (0 for no rotation) */ 51 | int level; /* logging level */ 52 | int fd; /* fd to write */ 53 | size_t fsize; /* original file size + number of bytes written) */ 54 | pthread_mutex_t lock; /* lock on logging */ 55 | }; 56 | 57 | #define log_debug(...) log_log(LOG_DEBUG, LOG_DEBUG_S, __VA_ARGS__) 58 | #define log_info(...) log_log(LOG_INFO, LOG_INFO_S, __VA_ARGS__) 59 | #define log_warn(...) log_log(LOG_WARN, LOG_WARN_S, __VA_ARGS__) 60 | #define log_error(...) log_log(LOG_ERROR, LOG_ERROR_S, __VA_ARGS__) 61 | #define log_critical(...) log_log(LOG_CRITICAL, LOG_CRITICAL_S, __VA_ARGS__) 62 | 63 | int log_open(char *name, char *filename, size_t rotate_size); 64 | void log_close(void); 65 | int log_reopen(void); 66 | void log_setlevel(int level); 67 | int log_rotate(void); 68 | int log_log(int level, char *levelname, const char *fmt, ...); 69 | 70 | #if defined(__cplusplus) 71 | } 72 | #endif 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/md5.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. 3 | * MD5 Message-Digest Algorithm (RFC 1321). 4 | * 5 | * Homepage: 6 | * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 7 | * 8 | * Author: Alexander Peslyak, better known as Solar Designer 10 | */ 11 | 12 | #include "md5.h" 13 | #include 14 | #include 15 | 16 | typedef unsigned int MD5_u32plus; 17 | 18 | typedef struct { 19 | MD5_u32plus lo, hi; 20 | MD5_u32plus a, b, c, d; 21 | unsigned char buffer[64]; 22 | MD5_u32plus block[16]; 23 | } MD5_CTX; 24 | 25 | /* 26 | * The basic MD5 functions. 27 | * 28 | * F and G are optimized compared to their RFC 1321 definitions for 29 | * architectures that lack an AND-NOT instruction, just like in Colin Plumb's 30 | * implementation. 31 | */ 32 | #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) 33 | #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) 34 | #define H(x, y, z) ((x) ^ (y) ^ (z)) 35 | #define I(x, y, z) ((y) ^ ((x) | ~(z))) 36 | 37 | /* 38 | * The MD5 transformation for all four rounds. 39 | */ 40 | #define STEP(f, a, b, c, d, x, t, s) \ 41 | (a) += f((b), (c), (d)) + (x) + (t); \ 42 | (a) = (((a) << (s)) | (((a)&0xffffffff) >> (32 - (s)))); \ 43 | (a) += (b); 44 | 45 | /* 46 | * SET reads 4 input bytes in little-endian byte order and stores them 47 | * in a properly aligned word in host byte order. 48 | * 49 | * The check for little-endian architectures that tolerate unaligned 50 | * memory accesses is just an optimization. Nothing will break if it 51 | * doesn't work. 52 | */ 53 | #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) 54 | #define SET(n) (*(MD5_u32plus *)&ptr[(n)*4]) 55 | #define GET(n) SET(n) 56 | #else 57 | #define SET(n) \ 58 | (ctx->block[(n)] = (MD5_u32plus)ptr[(n)*4] | \ 59 | ((MD5_u32plus)ptr[(n)*4 + 1] << 8) | \ 60 | ((MD5_u32plus)ptr[(n)*4 + 2] << 16) | \ 61 | ((MD5_u32plus)ptr[(n)*4 + 3] << 24)) 62 | #define GET(n) (ctx->block[(n)]) 63 | #endif 64 | 65 | /* 66 | * This processes one or more 64-byte data blocks, but does NOT update 67 | * the bit counters. There are no alignment requirements. 68 | */ 69 | static void *body(MD5_CTX *ctx, void *data, unsigned long size) { 70 | unsigned char *ptr; 71 | MD5_u32plus a, b, c, d; 72 | MD5_u32plus saved_a, saved_b, saved_c, saved_d; 73 | 74 | ptr = data; 75 | 76 | a = ctx->a; 77 | b = ctx->b; 78 | c = ctx->c; 79 | d = ctx->d; 80 | 81 | do { 82 | saved_a = a; 83 | saved_b = b; 84 | saved_c = c; 85 | saved_d = d; 86 | 87 | /* Round 1 */ 88 | STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) 89 | STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) 90 | STEP(F, c, d, a, b, SET(2), 0x242070db, 17) 91 | STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) 92 | STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) 93 | STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) 94 | STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) 95 | STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) 96 | STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) 97 | STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) 98 | STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) 99 | STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) 100 | STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) 101 | STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) 102 | STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) 103 | STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) 104 | 105 | /* Round 2 */ 106 | STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) 107 | STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) 108 | STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) 109 | STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) 110 | STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) 111 | STEP(G, d, a, b, c, GET(10), 0x02441453, 9) 112 | STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) 113 | STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) 114 | STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) 115 | STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) 116 | STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) 117 | STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) 118 | STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) 119 | STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) 120 | STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) 121 | STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) 122 | 123 | /* Round 3 */ 124 | STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) 125 | STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) 126 | STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) 127 | STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) 128 | STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) 129 | STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) 130 | STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) 131 | STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) 132 | STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) 133 | STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) 134 | STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) 135 | STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) 136 | STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) 137 | STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) 138 | STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) 139 | STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) 140 | 141 | /* Round 4 */ 142 | STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) 143 | STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) 144 | STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) 145 | STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) 146 | STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) 147 | STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) 148 | STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) 149 | STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) 150 | STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) 151 | STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) 152 | STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) 153 | STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) 154 | STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) 155 | STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) 156 | STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) 157 | STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) 158 | 159 | a += saved_a; 160 | b += saved_b; 161 | c += saved_c; 162 | d += saved_d; 163 | 164 | ptr += 64; 165 | } while (size -= 64); 166 | 167 | ctx->a = a; 168 | ctx->b = b; 169 | ctx->c = c; 170 | ctx->d = d; 171 | 172 | return ptr; 173 | } 174 | 175 | void MD5_Init(MD5_CTX *ctx) { 176 | ctx->a = 0x67452301; 177 | ctx->b = 0xefcdab89; 178 | ctx->c = 0x98badcfe; 179 | ctx->d = 0x10325476; 180 | 181 | ctx->lo = 0; 182 | ctx->hi = 0; 183 | } 184 | 185 | void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size) { 186 | MD5_u32plus saved_lo; 187 | unsigned long used, free; 188 | 189 | saved_lo = ctx->lo; 190 | if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) { 191 | ctx->hi++; 192 | } 193 | ctx->hi += size >> 29; 194 | 195 | used = saved_lo & 0x3f; 196 | 197 | if (used) { 198 | free = 64 - used; 199 | 200 | if (size < free) { 201 | memcpy(&ctx->buffer[used], data, size); 202 | return; 203 | } 204 | 205 | memcpy(&ctx->buffer[used], data, free); 206 | data = (unsigned char *)data + free; 207 | size -= free; 208 | body(ctx, ctx->buffer, 64); 209 | } 210 | 211 | if (size >= 64) { 212 | data = body(ctx, data, size & ~(unsigned long)0x3f); 213 | size &= 0x3f; 214 | } 215 | 216 | memcpy(ctx->buffer, data, size); 217 | } 218 | 219 | void MD5_Final(unsigned char *result, MD5_CTX *ctx) { 220 | unsigned long used, free; 221 | 222 | used = ctx->lo & 0x3f; 223 | 224 | ctx->buffer[used++] = 0x80; 225 | 226 | free = 64 - used; 227 | 228 | if (free < 8) { 229 | memset(&ctx->buffer[used], 0, free); 230 | body(ctx, ctx->buffer, 64); 231 | used = 0; 232 | free = 64; 233 | } 234 | 235 | memset(&ctx->buffer[used], 0, free - 8); 236 | 237 | ctx->lo <<= 3; 238 | ctx->buffer[56] = ctx->lo; 239 | ctx->buffer[57] = ctx->lo >> 8; 240 | ctx->buffer[58] = ctx->lo >> 16; 241 | ctx->buffer[59] = ctx->lo >> 24; 242 | ctx->buffer[60] = ctx->hi; 243 | ctx->buffer[61] = ctx->hi >> 8; 244 | ctx->buffer[62] = ctx->hi >> 16; 245 | ctx->buffer[63] = ctx->hi >> 24; 246 | 247 | body(ctx, ctx->buffer, 64); 248 | 249 | result[0] = ctx->a; 250 | result[1] = ctx->a >> 8; 251 | result[2] = ctx->a >> 16; 252 | result[3] = ctx->a >> 24; 253 | result[4] = ctx->b; 254 | result[5] = ctx->b >> 8; 255 | result[6] = ctx->b >> 16; 256 | result[7] = ctx->b >> 24; 257 | result[8] = ctx->c; 258 | result[9] = ctx->c >> 8; 259 | result[10] = ctx->c >> 16; 260 | result[11] = ctx->c >> 24; 261 | result[12] = ctx->d; 262 | result[13] = ctx->d >> 8; 263 | result[14] = ctx->d >> 16; 264 | result[15] = ctx->d >> 24; 265 | 266 | memset(ctx, 0, sizeof(*ctx)); 267 | } 268 | 269 | /* 270 | * Just a simple method for getting the signature 271 | * result must be == 16 272 | */ 273 | void md5_signature(unsigned char *key, unsigned long length, 274 | unsigned char *result) { 275 | MD5_CTX my_md5; 276 | 277 | MD5_Init(&my_md5); 278 | (void)MD5_Update(&my_md5, key, length); 279 | MD5_Final(result, &my_md5); 280 | } 281 | 282 | uint32_t hash_md5(const char *key, size_t key_length) { 283 | unsigned char results[16]; 284 | 285 | md5_signature((unsigned char *)key, (unsigned long)key_length, results); 286 | 287 | return ((uint32_t)(results[3] & 0xFF) << 24) | 288 | ((uint32_t)(results[2] & 0xFF) << 16) | 289 | ((uint32_t)(results[1] & 0xFF) << 8) | (results[0] & 0xFF); 290 | } 291 | -------------------------------------------------------------------------------- /src/md5.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * md5 hash function. 5 | */ 6 | 7 | /* 8 | * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. 9 | * MD5 Message-Digest Algorithm (RFC 1321). 10 | * 11 | * Homepage: 12 | * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 13 | * 14 | * Author: Alexander Peslyak, better known as Solar Designer 16 | */ 17 | 18 | #ifndef _CW_MD5_H 19 | #define _CW_MD5_H 1 20 | 21 | #include 22 | #include 23 | 24 | #if defined(__cplusplus) 25 | extern "C" { 26 | #endif 27 | 28 | void md5_signature(unsigned char *key, unsigned long length, 29 | unsigned char *result); 30 | uint32_t hash_md5(const char *key, size_t key_length); 31 | 32 | #if defined(__cplusplus) 33 | } 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include "parser.h" 6 | 7 | /* Parse statsd netblock string, and return once a key was 8 | * found. 9 | * 10 | * Return number chars parsed on success, -1 on failure. 11 | */ 12 | int parse(struct parser_result *result, char *data, size_t len) { 13 | int i; /* to find the first block */ 14 | int j; /* to find key in one block */ 15 | 16 | for (i = 0; i < len; i++) { 17 | if (data[i] == '\n') { 18 | for (j = 0; j < i; j++) { 19 | if (data[j] == ':') { 20 | result->key = data; 21 | result->len = j; /* exclude ':'*/ 22 | result->block = data; 23 | result->blen = i; /* exclude '\n' */ 24 | return i + 1; 25 | } 26 | } 27 | } 28 | } 29 | 30 | /* no '\n' was found, single block */ 31 | for (i = 0; i < len; i++) { 32 | if (data[i] == ':') { 33 | result->key = data; 34 | result->len = i; 35 | result->block = data; 36 | result->blen = len; 37 | return len; 38 | } 39 | } 40 | 41 | result->key = NULL; 42 | result->len = 0; 43 | result->block = NULL; 44 | result->blen = 0; 45 | return -1; 46 | } 47 | -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Proxy parser (statsd protocol). 5 | */ 6 | 7 | #ifndef _CW_PARSER_H 8 | #define _CW_PARSER_H 1 9 | 10 | #include 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | struct parser_result { 17 | char *key; /* parsed key */ 18 | size_t len; /* parsed key len */ 19 | char *block; /* parsed block */ 20 | size_t blen; /* parsed block len */ 21 | }; 22 | 23 | /** 24 | * example usage: 25 | * 26 | * struct parser_result res; 27 | * int n, n_parsed = 0; 28 | * while ((n = parse(&res, data, len)) > 0) { 29 | * // do operations... 30 | * data += n; 31 | * len -= n; 32 | * n_parsed += n; 33 | * } 34 | * buf_lrm(buf, n_parsed); 35 | */ 36 | int parse(struct parser_result *result, char *data, size_t len); 37 | 38 | #if defined(__cplusplus) 39 | } 40 | #endif 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/proxy.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | */ 4 | 5 | #include "proxy.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "ctx.h" 14 | #include "event.h" 15 | #include "ketama.h" 16 | #include "log.h" 17 | #include "parser.h" 18 | 19 | /* Start proxy in a thread. */ 20 | void *thread_start(void *arg) { 21 | struct ctx *ctx = arg; 22 | int err = -1; 23 | 24 | if ((err = ctx_init(ctx)) == CTX_OK) err = server_start(ctx); 25 | 26 | switch (err) { 27 | case PROXY_ENOMEM: 28 | log_error("no memory"); 29 | break; 30 | case PROXY_EBIND: 31 | log_error("failed to bind server on port %d", ctx->port); 32 | break; 33 | default: 34 | log_error("fatal error occurred: %d", err); 35 | } 36 | exit(1); 37 | } 38 | 39 | /* Recv into buffer on data */ 40 | void recv_buf(struct event_loop *loop, int fd, int mask, void *data) { 41 | struct ctx *ctx = data; 42 | int n; 43 | struct buf *buf = ctx->buf; 44 | 45 | while (1) { 46 | if (buf_grow(buf, buf->len + BUF_RECV_UNIT) != BUF_OK) { 47 | log_error("no memory"); 48 | exit(1); 49 | } 50 | 51 | if ((n = recv(ctx->sfd, buf->data + buf->len, 52 | (buf->cap - buf->len) * sizeof(char), 0)) < 0) 53 | break; 54 | 55 | buf->len += n; 56 | 57 | if (relay_buf(ctx) != PROXY_OK) { 58 | log_error("no memory"); 59 | exit(1); 60 | } 61 | } 62 | 63 | if (buf->cap >= BUF_RECV_CAP_MAX) { 64 | buf_clear(buf); 65 | } else { 66 | buf->len = 0; 67 | } 68 | } 69 | 70 | /* Start server. */ 71 | int server_start(struct ctx *ctx) { 72 | assert(ctx != NULL); 73 | assert(ctx->sfd > 0); 74 | 75 | struct sockaddr_in addr; 76 | addr.sin_family = AF_INET; 77 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 78 | addr.sin_port = htons(ctx->port); 79 | 80 | if (bind(ctx->sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 81 | return PROXY_EBIND; 82 | 83 | log_info("serving on udp://127.0.0.1:%d..", ctx->port); 84 | 85 | struct event_loop *loop = event_loop_new(2); 86 | 87 | if (loop == NULL) return PROXY_ENOMEM; 88 | event_add_in(loop, ctx->sfd, &recv_buf, (void *)ctx); 89 | event_add_timer(loop, (long)(ctx->flush_interval), &flush_buf, (void *)ctx); 90 | if (event_loop_start(loop) != EVENT_OK) /* block forerver */ 91 | return PROXY_ELOOP; 92 | event_loop_free(loop); 93 | return PROXY_OK; 94 | } 95 | 96 | /* Util to send buf to addr */ 97 | void send_buf(struct ctx *ctx, struct sockaddr_in addr, struct buf *sbuf, 98 | char *addr_s) { 99 | assert(ctx != NULL); 100 | assert(sbuf != NULL); 101 | 102 | // trim the last \n 103 | if (sbuf->len > 0 && sbuf->data[sbuf->len - 1] == '\n') sbuf->len -= 1; 104 | 105 | int n = sendto(ctx->cfd, sbuf->data, sbuf->len, 0, (struct sockaddr *)&addr, 106 | sizeof(struct sockaddr_in)); 107 | 108 | if (n < 0) log_warn("send => an error occurred, skipping.."); 109 | 110 | log_debug("flush %d bytes => %s", n, addr_s); 111 | 112 | if (sbuf->cap >= BUF_SEND_CAP_MAX) { 113 | buf_clear(sbuf); 114 | } else { 115 | sbuf->len = 0; 116 | } 117 | } 118 | 119 | /* Relay buffer to backends. Note: all network etc. errors on single 120 | * relay will be ignored. */ 121 | int relay_buf(struct ctx *ctx) { 122 | assert(ctx != NULL); 123 | assert(ctx->buf != NULL); 124 | assert(ctx->sbufs != NULL); 125 | 126 | if (ctx->buf->len == 0) return PROXY_OK; 127 | 128 | struct parser_result result; 129 | int n, n_parsed = 0; 130 | char *data = ctx->buf->data; 131 | size_t len = ctx->buf->len; 132 | struct ketama_node *node; 133 | struct sockaddr_in addr; 134 | struct buf *sbuf = NULL; 135 | 136 | while ((n = parse(&result, data, len)) > 0) { 137 | node = ketama_node_iget(ctx->ring, result.key, result.len); 138 | 139 | sbuf = ctx->sbufs[node->idx]; 140 | addr = ctx->addrs[node->idx]; 141 | 142 | if (sbuf->len > 0 && buf_putc(sbuf, '\n') != BUF_OK) 143 | return PROXY_ENOMEM; 144 | 145 | if (buf_put(sbuf, result.block, result.blen) != BUF_OK) 146 | return PROXY_ENOMEM; 147 | 148 | /* flush buffer if this buf is large enough */ 149 | if (sbuf->len >= ctx->socket_send_packet_size) send_buf(ctx, addr, sbuf, node->key); 150 | 151 | data += n; 152 | len -= n; 153 | n_parsed += n; 154 | } 155 | 156 | buf_lrm(ctx->buf, n_parsed); 157 | return PROXY_OK; 158 | } 159 | 160 | /* Flush buffers on interval. */ 161 | void flush_buf(struct event_loop *loop, int id, void *data) { 162 | struct ctx *ctx = data; 163 | struct buf *sbuf; 164 | struct sockaddr_in addr; 165 | struct ketama_node *node; 166 | int i; 167 | 168 | for (i = 0; i < ctx->num_nodes; i++) { 169 | sbuf = ctx->sbufs[i]; 170 | addr = ctx->addrs[i]; 171 | node = &(ctx->nodes[i]); 172 | if (sbuf->len > 0) send_buf(ctx, addr, sbuf, node->key); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/proxy.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Proxy threading entry. 5 | */ 6 | 7 | #ifndef _CW_PROXY_H 8 | #define _CW_PROXY_H 1 9 | 10 | #include "ctx.h" 11 | #include "event.h" 12 | 13 | #if defined(__cplusplus) 14 | extern "C" { 15 | #endif 16 | 17 | #define BUF_RECV_UNIT 64 * 1024 /* recv packet size limit: 64kb */ 18 | #define BUF_RECV_CAP_MAX 1024 * 1024 /* recv buffer memory limit: 1mb */ 19 | #define BUF_SEND_UNIT 32 * 1024 /* send packet size limit: 32kb */ 20 | #define BUF_SEND_CAP_MAX 1024 * 1024 /* send buffer memory limit: 1mb */ 21 | 22 | enum { 23 | PROXY_OK = 0, /* operation is ok */ 24 | PROXY_ENOMEM = 1, /* no memory error */ 25 | PROXY_EBIND = 2, /* socket bind error */ 26 | PROXY_ELOOP = 3, /* event loop error */ 27 | }; 28 | 29 | void *thread_start(void *arg); 30 | int server_start(struct ctx *ctx); 31 | void recv_buf(struct event_loop *loop, int fd, int mask, void *data); 32 | int relay_buf(struct ctx *ctx); 33 | void send_buf(struct ctx *ctx, struct sockaddr_in addr, struct buf *buf, 34 | char *addr_s); 35 | void flush_buf(struct event_loop *loop, int id, void *data); 36 | 37 | #if defined(__cplusplus) 38 | } 39 | #endif 40 | #endif 41 | -------------------------------------------------------------------------------- /src/statsd-proxy.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Chao Wang 3 | * 4 | * Proxy for etsy/statsd. 5 | */ 6 | 7 | #ifndef __linux__ 8 | #error "statsd-proxy requires linux3.9+" 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "log.h" 18 | #include "proxy.h" 19 | 20 | #include "config.h" 21 | 22 | #define STATSD_PROXY_VERSION "0.1.0" 23 | 24 | void version(void); 25 | void usage(void); 26 | void start(struct config *config); 27 | 28 | int main(int argc, char *argv[]) { 29 | log_open("statsd-proxy", NULL, 0); 30 | 31 | char *filename; 32 | 33 | const char *short_opt = "hvdf:"; 34 | struct option long_opt[] = { 35 | {"help", no_argument, NULL, 'h'}, 36 | {"version", no_argument, NULL, 'v'}, 37 | {"debug", no_argument, NULL, 'd'}, 38 | {"file", required_argument, NULL, 'f'}, 39 | {NULL, 0, NULL, 0}, 40 | }; 41 | 42 | int c; 43 | while ((c = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { 44 | switch (c) { 45 | case 'h': 46 | case ':': 47 | case '?': 48 | usage(); 49 | break; 50 | case 'v': 51 | version(); 52 | break; 53 | case 'f': 54 | filename = optarg; 55 | break; 56 | case 'd': 57 | log_setlevel(LOG_DEBUG); 58 | break; 59 | default: 60 | usage(); 61 | }; 62 | } 63 | 64 | if (argc == 1 || optind < argc) usage(); 65 | 66 | struct config *config = config_new(); 67 | 68 | if (config == NULL) exit(1); 69 | 70 | if (config_init(config, filename) != CONFIG_OK) exit(1); 71 | 72 | start(config); 73 | 74 | config_free(config); 75 | log_close(); 76 | return 0; 77 | } 78 | 79 | void version(void) { 80 | fprintf(stderr, "statsd-proxy@%s\n", STATSD_PROXY_VERSION); 81 | exit(1); 82 | } 83 | 84 | void usage(void) { 85 | fprintf(stderr, "Usage:\n"); 86 | fprintf(stderr, " ./statsd-proxy -f ./path/to/config.cfg\n"); 87 | fprintf(stderr, "Options:\n"); 88 | fprintf(stderr, " -h, --help Show this message\n"); 89 | fprintf(stderr, " -v, --version Show version\n"); 90 | fprintf(stderr, " -d, --debug Enable debug logging\n"); 91 | fprintf(stderr, "Copyright (c) https://github.com/hit9/statsd-proxy\n"); 92 | exit(1); 93 | } 94 | 95 | void start(struct config *config) { 96 | assert(config != NULL); 97 | assert(config->num_threads > 0); 98 | assert(config->port > 0); 99 | assert(config->nodes != NULL); 100 | assert(config->num_nodes > 0); 101 | 102 | pthread_t threads[config->num_threads]; 103 | struct ctx *ctxs[config->num_threads]; 104 | 105 | int i; 106 | 107 | for (i = 0; i < config->num_threads; i++) { 108 | if ((ctxs[i] = ctx_new(config->nodes, config->num_nodes, config->port, 109 | config->flush_interval, config->socket_receive_bufsize, config->socket_send_packet_size)) == NULL) 110 | exit(1); 111 | pthread_create(&threads[i], NULL, &thread_start, ctxs[i]); 112 | } 113 | 114 | for (i = 0; i < config->num_threads; i++) { 115 | pthread_join(threads[i], NULL); 116 | ctx_free(ctxs[i]); 117 | } 118 | } 119 | --------------------------------------------------------------------------------