Klaus Demo bjoern / 9cf49e6
Add statsd metrics on server events (#168) Mohammad Gufran authored a month ago Jonas Haag committed a month ago
17 changed file(s) with 426 addition(s) and 30 deletion(s). Raw diff Collapse all Expand all
00 [submodule "http-parser"]
11 path = http-parser
22 url = git://github.com/joyent/http-parser
3 [submodule "statsd-c-client"]
4 path = statsd-c-client
5 url = https://github.com/romanbsd/statsd-c-client
88 HTTP_PARSER_OBJ = $(HTTP_PARSER_DIR)/http_parser.o
99 HTTP_PARSER_SRC = $(HTTP_PARSER_DIR)/http_parser.c
1010
11 objects = $(HTTP_PARSER_OBJ) \
11 STATSD_CLIENT_DIR = statsd-c-client
12 STATSD_CLIENT_OBJ = $(STATSD_CLIENT_DIR)/statsd-client.o
13 STATSD_CLIENT_SRC = $(STATSD_CLIENT_DIR)/statsd-client.c
14
15 objects = $(HTTP_PARSER_OBJ) $(STATSD_CLIENT_OBJ) \
1216 $(patsubst $(SOURCE_DIR)/%.c, $(BUILD_DIR)/%.o, \
1317 $(wildcard $(SOURCE_DIR)/*.c))
1418
15 CPPFLAGS += $(PYTHON_INCLUDE) -I . -I $(SOURCE_DIR) -I $(HTTP_PARSER_DIR)
19 CPPFLAGS += $(PYTHON_INCLUDE) -I . -I $(SOURCE_DIR) -I $(HTTP_PARSER_DIR) -I $(STATSD_CLIENT_DIR)
1620 CFLAGS += $(FEATURES) -std=c99 -fno-strict-aliasing -fcommon -fPIC -Wall
1721 LDFLAGS += $(PYTHON_LDFLAGS) -l ev -shared -fcommon
18
19 ifneq ($(WANT_SENDFILE), no)
20 FEATURES += -D WANT_SENDFILE
21 endif
2222
2323 ifneq ($(WANT_SIGINT_HANDLING), no)
2424 FEATURES += -D WANT_SIGINT_HANDLING
3030
3131 ifndef SIGNAL_CHECK_INTERVAL
3232 FEATURES += -D SIGNAL_CHECK_INTERVAL=0.1
33 endif
34
35 ifeq ($(WANT_STATSD), yes)
36 FEATURES += -D WANT_STATSD
37 endif
38
39 ifeq ($(WANT_STATSD_TAGS), yes)
40 FEATURES += -D WANT_STATSD_TAGS
3341 endif
3442
3543 all: prepare-build $(objects) _bjoernmodule
5462 again: clean all
5563
5664 debug:
57 CFLAGS='-D DEBUG -g' make again
65 CFLAGS='${CFLAGS} -D DEBUG -g' make again
5866
5967 $(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c
6068 @echo ' -> ' $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
106114
107115 $(HTTP_PARSER_OBJ):
108116 $(MAKE) -C $(HTTP_PARSER_DIR) http_parser.o CFLAGS_DEBUG_EXTRA=-fPIC CFLAGS_FAST_EXTRA=-fPIC
117
118 $(STATSD_CLIENT_OBJ):
119 $(MAKE) -C $(STATSD_CLIENT_DIR) statsd-client.o CFLAGS_DEBUG_EXTRA=-fPIC CFLAGS_FAST_EXTRA=-fPIC
4141 # Bind to abstract Unix socket: (Linux only)
4242 bjoern.run(wsgi_application, 'unix:@socket_name')
4343
44 # enable statsd metrics
45 # This needs manual compilation with `WANT_STATSD=yes`
46 bjoern.run(wsgi_application, host, port, reuse_port=True, enable_statsd=True)
47
4448 Alternatively, the mainloop can be run separately::
4549
4650 bjoern.listen(wsgi_application, host, port)
4751 bjoern.run()
48
52
53 # with metrics
54 # This needs manual compilation with `WANT_STATSD=yes`
55 bjoern.listen(wsgi_application, host, port)
56 bjoern.run(enable_statsd=True)
57
4958 You can also simply pass a Python socket(-like) object. Note that you are responsible
5059 for initializing and cleaning up the socket in that case. ::
5160
5261 bjoern.server_run(socket_object, wsgi_application)
5362 bjoern.server_run(filedescriptor_as_integer, wsgi_application)
5463
64 # This needs manual compilation with `WANT_STATSD=yes`
65 bjoern.server_run(socket_object, wsgi_application, enable_statsd=True)
66
5567 .. _WSGI: http://www.python.org/dev/peps/pep-0333/
5668 .. _libev: http://software.schmorp.de/pkg/libev.html
5769 .. _http-parser: https://github.com/joyent/http-parser
11 #include "server.h"
22 #include "wsgi.h"
33 #include "filewrapper.h"
4
5 #ifdef WANT_STATSD
6 #include "statsd-client.h"
7 #endif
48
59 static PyObject*
610 run(PyObject* self, PyObject* args)
913
1014 PyObject* socket;
1115
12 if(!PyArg_ParseTuple(args, "OO:server_run", &socket, &info.wsgi_app)) {
16 #ifdef WANT_STATSD
17 info.statsd = NULL;
18 int statsd_enabled;
19 char* statsd_host;
20 int statsd_port;
21 char* statsd_ns;
22 char* statsd_tags = NULL;
23
24 if(!PyArg_ParseTuple(args, "OOiziz|z:server_run", &socket, &info.wsgi_app,
25 &statsd_enabled, &statsd_host, &statsd_port, &statsd_ns, &statsd_tags)) {
1326 return NULL;
1427 }
28 #else
29 char* ignored_str = NULL;
30 int ignored_int = 0;
31
32 if(!PyArg_ParseTuple(args, "OO|izizz:server_run", &socket, &info.wsgi_app, &ignored_int,
33 &ignored_str, &ignored_int, &ignored_str, &ignored_str)) {
34 return NULL;
35 }
36 if (ignored_str != NULL || ignored_int != 0) {
37 PyErr_Format(PyExc_TypeError, "Unexpected statsd_* arguments (forgot to compile with statsd support?)");
38 return NULL;
39 }
40 #endif
1541
1642 info.sockfd = PyObject_AsFileDescriptor(socket);
1743 if (info.sockfd < 0) {
3157 }
3258 }
3359
60 #ifdef WANT_STATSD
61 if (statsd_enabled) {
62 if (statsd_host == NULL || *statsd_host == '\0') {
63 statsd_host = "127.0.0.1";
64 }
65
66 if (statsd_ns == NULL || *statsd_ns == '\0') {
67 info.statsd = statsd_init(statsd_host, statsd_port);
68 } else {
69 info.statsd = statsd_init_with_namespace(statsd_host, statsd_port, statsd_ns);
70 }
71 #ifdef WANT_STATSD_TAGS
72 info.statsd_tags = statsd_tags;
73 DBG("Statsd: host=%s, port=%d, ns=%s, tags=%s", statsd_host, statsd_port, statsd_ns, statsd_tags);
74 #else
75 DBG("Statsd: host=%s, port=%d, ns=%s", statsd_host, statsd_port, statsd_ns);
76 #endif
77 } else {
78 DBG("Statsd disabled");
79 }
80
81
82 #endif
83
3484 _initialize_request_module(&info);
85
3586 server_run(&info);
87
88 #ifdef WANT_STATSD
89 statsd_finalize(info.statsd);
90 #endif
3691
3792 Py_RETURN_NONE;
3893 }
72127 Py_INCREF(&FileWrapper_Type);
73128 Py_INCREF(&StartResponse_Type);
74129
130 PyObject* features = PyDict_New();
131
132 #ifdef WANT_SIGNAL_HANDLING
133 PyDict_SetItemString(features, "has_signal_handling", Py_True);
134 #else
135 PyDict_SetItemString(features, "has_signal_handling", Py_False);
136 #endif
137
138 #ifdef WANT_SIGINT_HANDLING
139 PyDict_SetItemString(features, "has_sigint_handling", Py_True);
140 #else
141 PyDict_SetItemString(features, "has_sigint_handling", Py_False);
142 #endif
143
144 #ifdef WANT_STATSD
145 PyDict_SetItemString(features, "has_statsd", Py_True);
146 #else
147 PyDict_SetItemString(features, "has_statsd", Py_False);
148 #endif
149
150 #ifdef WANT_STATSD_TAGS
151 PyDict_SetItemString(features, "has_statsd_tags", Py_True);
152 #else
153 PyDict_SetItemString(features, "has_statsd_tags", Py_False);
154 #endif
155
75156 #if PY_MAJOR_VERSION >= 3
76157 PyObject* bjoern_module = PyModule_Create(&module);
77158 if (bjoern_module == NULL) {
78159 return NULL;
79160 }
80
81 PyModule_AddObject(bjoern_module, "version", Py_BuildValue("(iii)", 3, 0, 1));
82 return bjoern_module;
83161 #else
84162 PyObject* bjoern_module = Py_InitModule("_bjoern", Bjoern_FunctionTable);
85 PyModule_AddObject(bjoern_module, "version", Py_BuildValue("(iii)", 3, 0, 1));
86163 #endif
87164
165 PyModule_AddObject(bjoern_module, "features", features);
166 PyModule_AddObject(bjoern_module, "version", Py_BuildValue("(iii)", 3, 0, 1));
167
168 #if PY_MAJOR_VERSION >= 3
169 return bjoern_module;
170 #endif
88171 }
5656 #define assert(...) do{}while(0)
5757 #endif
5858
59 #ifdef WANT_STATSD
60 #include "statsd-client.h"
61
62 #ifdef WANT_STATSD_TAGS
63 #include "statsd_tags.h"
64 #define STATSD_INCREMENT(name) \
65 do { \
66 DBG("statisd.inc: %s", name); \
67 statsd_inc_with_tags(((ThreadInfo*) ev_userdata(mainloop))->server_info->statsd, \
68 name, \
69 ((ThreadInfo*) ev_userdata(mainloop))->server_info->statsd_tags); \
70 } while (0)
71 #else
72 #define STATSD_INCREMENT(name) \
73 do { \
74 DBG("statisd.inc: %s", name); \
75 statsd_inc(((ThreadInfo*) ev_userdata(mainloop))->server_info->statsd, name, 1.0); \
76 } while (0)
77 #endif
78 #else
79 #define STATSD_INCREMENT(name) DBG("statsd.inc: %s", name)
5980 #endif
81
82 #endif
1616 #include "common.h"
1717 #include "wsgi.h"
1818 #include "server.h"
19
2019 #include "py2py3.h"
20
21 #ifdef WANT_STATSD
22 #include "statsd-client.h"
23 #endif
2124
2225 #define READ_BUFFER_SIZE 64*1024
2326 #define Py_XCLEAR(obj) do { if(obj) { Py_DECREF(obj); obj = NULL; } } while(0)
5053
5154 typedef void ev_io_callback(struct ev_loop*, ev_io*, const int);
5255
53 #if WANT_SIGINT_HANDLING
56 #ifdef WANT_SIGINT_HANDLING
5457 typedef void ev_signal_callback(struct ev_loop*, ev_signal*, const int);
5558 static ev_signal_callback ev_signal_on_sigint;
5659 #endif
5760
58 #if WANT_SIGINT_HANDLING
61 #ifdef WANT_SIGNAL_HANDLING
5962 typedef void ev_timer_callback(struct ev_loop*, ev_timer*, const int);
6063 static ev_timer_callback ev_timer_ontick;
6164 ev_timer timeout_watcher;
7881
7982 ThreadInfo thread_info;
8083 thread_info.server_info = server_info;
84
8185 ev_set_userdata(mainloop, &thread_info);
8286
8387 ev_io_init(&thread_info.accept_watcher, ev_io_on_request, server_info->sockfd, EV_READ);
8488 ev_io_start(mainloop, &thread_info.accept_watcher);
8589
86 #if WANT_SIGINT_HANDLING
90 #ifdef WANT_SIGINT_HANDLING
8791 ev_signal sigint_watcher;
8892 ev_signal_init(&sigint_watcher, ev_signal_on_sigint, SIGINT);
8993 ev_signal_start(mainloop, &sigint_watcher);
102106 Py_END_ALLOW_THREADS
103107 }
104108
105 #if WANT_SIGINT_HANDLING
109 #ifdef WANT_SIGINT_HANDLING
106110 static void
107111 pyerr_set_interrupt(struct ev_loop* mainloop, struct ev_cleanup* watcher, const int events)
108112 {
127131 }
128132 #endif
129133
130 #if WANT_SIGNAL_HANDLING
134 #ifdef WANT_SIGNAL_HANDLING
131135 static void
132136 ev_timer_ontick(struct ev_loop* mainloop, ev_timer* watcher, const int events)
133137 {
148152 client_fd = accept(watcher->fd, (struct sockaddr*)&sockaddr, &addrlen);
149153 if(client_fd < 0) {
150154 DBG("Could not accept() client: errno %d", errno);
155 STATSD_INCREMENT("conn.accept.error");
151156 return;
152157 }
153158
154159 int flags = fcntl(client_fd, F_GETFL, 0);
155160 if(fcntl(client_fd, F_SETFL, (flags < 0 ? 0 : flags) | O_NONBLOCK) == -1) {
161 STATSD_INCREMENT("conn.accept.error");
156162 DBG("Could not set_nonblocking() client %d: errno %d", client_fd, errno);
157163 return;
158164 }
167173
168174 GIL_UNLOCK(0);
169175
176 STATSD_INCREMENT("conn.accept.success");
177
170178 DBG_REQ(request, "Accepted client %s:%d on fd %d",
171179 inet_ntoa(sockaddr.sin_addr), ntohs(sockaddr.sin_port), client_fd);
172180
212220 /* Client disconnected */
213221 read_state = aborted;
214222 DBG_REQ(request, "Client disconnected");
223 STATSD_INCREMENT("req.error.client_disconnected");
215224 } else if (read_bytes < 0) {
216225 /* Would block or error */
217226 if(errno == EAGAIN || errno == EWOULDBLOCK) {
219228 } else {
220229 read_state = aborted;
221230 DBG_REQ(request, "Hit errno %d while read()ing", errno);
231 STATSD_INCREMENT("req.error.read");
222232 }
223233 } else {
224234 /* OK, either expect more data or done reading */
227237 /* HTTP parse error */
228238 read_state = done;
229239 DBG_REQ(request, "Parse error");
240 STATSD_INCREMENT("req.error.parse");
230241 request->current_chunk = _PEP3333_Bytes_FromString(
231242 http_error_messages[request->state.error_code]);
232243 assert(request->iterator == NULL);
234245 /* HTTP parse successful, meaning we have the entire
235246 * request (the header _and_ the body). */
236247 read_state = done;
248
249 STATSD_INCREMENT("req.success.read");
237250
238251 if (!wsgi_call_application(request)) {
239252 /* Response is "HTTP 500 Internal Server Error" */
244257 Py_XCLEAR(request->iterator);
245258 request->current_chunk = _PEP3333_Bytes_FromString(
246259 http_error_messages[HTTP_SERVER_ERROR]);
260 STATSD_INCREMENT("req.error.internal");
247261 }
248262 } else if (request->state.expect_continue) {
249263 /*
260274
261275 switch (read_state) {
262276 case not_yet_done:
277 STATSD_INCREMENT("req.active");
263278 break;
264279 case expect_continue:
265280 DBG_REQ(request, "pause read, write 100-continue");
271286 DBG_REQ(request, "Stop read watcher, start write watcher");
272287 ev_io_stop(mainloop, &request->ev_watcher);
273288 start_writing(mainloop, request);
289 STATSD_INCREMENT("req.done");
274290 break;
275291 case aborted:
276292 close_connection(mainloop, request);
293 STATSD_INCREMENT("req.aborted");
277294 break;
278295 }
279296
316333
317334 switch(write_state) {
318335 case not_yet_done:
336 STATSD_INCREMENT("resp.active");
319337 break;
320338 case done:
339 STATSD_INCREMENT("resp.done");
321340 if(request->state.keep_alive) {
322341 DBG_REQ(request, "done, keep-alive");
342 STATSD_INCREMENT("resp.done.keepalive");
323343 ev_io_stop(mainloop, &request->ev_watcher);
324344
325345 Request_clean(request);
328348 start_reading(mainloop, request);
329349 } else {
330350 DBG_REQ(request, "done, close");
351 STATSD_INCREMENT("resp.conn.close");
331352 close_connection(mainloop, request);
332353 }
333354 break;
342363 /* Response was aborted due to an error. We can't do anything graceful here
343364 * because at least one chunk is already sent... just close the connection. */
344365 close_connection(mainloop, request);
366 STATSD_INCREMENT("resp.aborted");
345367 break;
346368 }
347369
00 #ifndef __server_h__
11 #define __server_h__
2
3 #ifdef WANT_STATSD
4 #include "statsd-client.h"
5 #endif
26
37 typedef struct {
48 int sockfd;
59 PyObject* wsgi_app;
610 PyObject* host;
711 PyObject* port;
12 #ifdef WANT_STATSD
13 statsd_link* statsd;
14 # ifdef WANT_STATSD_TAGS
15 char* statsd_tags;
16 # endif
17 #endif
818 } ServerInfo;
919
1020 void server_run(ServerInfo*);
0 #include <stdio.h>
1 #include "statsd-client.h"
2
3 #define MAX_MSG_LEN_WITH_TAGS 1000
4
5 static int send_stats_with_tags(statsd_link* link, char *stat, size_t value, const char* type, const char* tags)
6 {
7 if (link == NULL) {
8 // Statsd disabled
9 return 0;
10 }
11
12 char message[MAX_MSG_LEN_WITH_TAGS];
13 snprintf(message, MAX_MSG_LEN_WITH_TAGS, "%s%s:%zd|%s|#%s", link->ns ? link->ns : "", stat, value, type, tags ? tags : "");
14 return statsd_send(link, message);
15 }
16
17 int statsd_inc_with_tags(statsd_link* link, char* stat, const char* tags)
18 {
19 return send_stats_with_tags(link, stat, 1, "c", tags);
20 }
0 #ifndef __statsd_tags__
1 #define __statsd_tags__
2
3 #include "statsd-client.h"
4
5 int send_stats_with_tags(statsd_link* link, char *stat, size_t value, const char *type, const char* tags);
6 int statsd_inc_with_tags(statsd_link* link, char *stat, const char* tags);
7
8 #endif
44
55 _default_instance = None
66 DEFAULT_LISTEN_BACKLOG = 1024
7
87
98 def bind_and_listen(host, port=None, reuse_port=False,
109 listen_backlog=DEFAULT_LISTEN_BACKLOG):
3433 return sock
3534
3635
37 def server_run(sock, wsgi_app):
38 _bjoern.server_run(sock, wsgi_app)
36 def server_run(sock, wsgi_app, statsd=None):
37 args = [sock, wsgi_app]
38
39 if _bjoern.features.get('has_statsd'):
40 if statsd:
41 args.extend([int(statsd['enable']), statsd['host'], statsd['port'], statsd['ns'], statsd.get('tags')])
42 else:
43 args.extend([0, None, 0, None, None])
44
45 _bjoern.server_run(*args)
3946
4047
41 # Backwards compatibility API
4248 def listen(wsgi_app, host, port=None, reuse_port=False,
4349 listen_backlog=DEFAULT_LISTEN_BACKLOG):
4450 """
6975 """
7076 global _default_instance
7177
78 statsd = kwargs.pop('statsd', None)
79
7280 if args or kwargs:
7381 # Called as `bjoern.run(wsgi_app, host, ...)`
7482 listen(*args, **kwargs)
8189
8290 sock, wsgi_app = _default_instance
8391 try:
84 server_run(sock, wsgi_app)
92 server_run(sock, wsgi_app, statsd)
8593 finally:
8694 if sock.family == socket.AF_UNIX:
8795 filename = sock.getsockname()
0 ## Instrumentation
1
2 Bjoern can export connection and request metrics to statsd. If you want support for instrumentation you must compile
3 bjoern manually with
4
5 ```bash
6 BJOERN_WANT_STATSD=yes python setup.py install
7 # or
8 WANT_STATSD=yes make all
9 ```
10
11 you can also enable Dogstatsd tags support by compiling with
12
13 ```bash
14 BJOERN_WANT_STATSD=yes BJOERN_WANT_STATSD_TAGS=yes python setup.py install
15 # or
16 WANT_STATSD=yes WANT_STATSD_TAGS=yes make all
17 ```
18
19 After building with statsd support you can enable statsd metrics on server boot by setting `enable_statsd=True`, e.g.
20
21 ```python
22 bjoern.listen(wsgi_application, host, port)
23 bjoern.run(statsd={'enable': True, 'host': '...', port: ..., ns: 'bjoern'})
24
25 # With tags
26 # Note that the tags parameter takes a comma separated string of tags, not a list of strings.
27 bjoern.run(statsd={'enable': True, 'host': '...', port: ..., ns: 'bjoern', 'tags': 'app:my-app-name,zone:central-europe'})
28 ```
29
30 Following metrics are exposed:
31
32 | Name | Type | Description |
33 |:------:|:------:|:-------------|
34 | `conn.accept.error` | increment | Incremented if bjoern fails to accept the socket connection from client |
35 | `conn.accept.success` | increment | Incremented when a socket connection is accepted successfully |
36 | `req.error.client_disconnected` | increment | Incremented if client disconnects before bjoern could read request |
37 | `req.error.read` | increment | Incremented if bjoern fails to read request data from a valid connection |
38 | `req.error.parse` | increment | Incremented if bjoern fails to parse the content as valid HTTP request |
39 | `req.success.read` | increment | Incremented every time a request is read and parsed successfully |
40 | `req.error.internal` | increment | Incremented if bjoern fails to successfully call the WSGI application |
41 | `req.active` | increment | Incremented when a request is accepted as a valid one and bjoern is expecting more data from client |
42 | `req.done` | increment | Incremented when a request is read and parsed completely and no more data is expected from client, server is expected to send response now |
43 | `req.aborted` | increment | Incremented if the request is aborted before it was read or parsed completely. At the moment this is same as `req.error.read` but logically these are two different things and will diverge in future |
44 | `resp.active` | increment | Incremented when bjoern is sending response and |
45 | `resp.done` | increment | Incremented when bjoern is done sending the response |
46 | `resp.done.keepalive` | increment | Incremented when the response is sent but the connection is kept alive |
47 | `resp.conn.close` | increment | Incremented when the response is sent and connection is closed |
48 | `resp.aborted` | increment | Incremented when there is an error in sending response. At this point the connection is also closed |
49
11 import glob
22 from setuptools import setup, Extension
33
4 WANT_SIGINT_HANDLING = os.environ.get('BJOERN_WANT_SIGINT_HANDLING', True)
5 WANT_SIGNAL_HANDLING = os.environ.get('BJOERN_WANT_SIGNAL_HANDLING', True)
6 SIGNAL_CHECK_INTERVAL = os.environ.get('BJOERN_SIGNAL_CHECK_INTERVAL', '0.1')
7 WANT_STATSD = os.environ.get('BJOERN_WANT_STATSD', False)
8 WANT_STATSD_TAGS = os.environ.get('BJOERN_WANT_STATSD_TAGS', False)
9
10 compile_flags = [('SIGNAL_CHECK_INTERVAL', SIGNAL_CHECK_INTERVAL)]
11 if WANT_SIGNAL_HANDLING:
12 compile_flags.append(('WANT_SIGNAL_HANDLING', 'yes'))
13 if WANT_SIGINT_HANDLING:
14 compile_flags.append(('WANT_SIGINT_HANDLING', 'yes'))
15 if WANT_STATSD:
16 compile_flags.append(('WANT_STATSD', 'yes'))
17 if WANT_STATSD_TAGS:
18 compile_flags.append(('WANT_STATSD_TAGS', 'yes'))
19
420 SOURCE_FILES = [os.path.join('http-parser', 'http_parser.c')] + \
21 [os.path.join('statsd-c-client', 'statsd-client.c')] + \
522 sorted(glob.glob(os.path.join('bjoern', '*.c')))
23
24 if not WANT_STATSD:
25 SOURCE_FILES.remove('statsd-c-client/statsd-client.c')
26 SOURCE_FILES.remove('bjoern/statsd_tags.c')
627
728 bjoern_extension = Extension(
829 '_bjoern',
930 sources = SOURCE_FILES,
1031 libraries = ['ev'],
11 include_dirs = ['http-parser', '/usr/include/libev', '/opt/local/include'],
12 define_macros = [('WANT_SENDFILE', '1'),
13 ('WANT_SIGINT_HANDLING', '1'),
14 ('WANT_SIGNAL_HANDLING', '1'),
15 ('SIGNAL_CHECK_INTERVAL', '0.1')],
32 include_dirs = ['http-parser', 'statsd-c-client', '/usr/include/libev', '/opt/local/include'],
33 define_macros = compile_flags,
1634 extra_compile_args = ['-std=c99', '-fno-strict-aliasing', '-fcommon',
1735 '-fPIC', '-Wall', '-Wextra', '-Wno-unused-parameter',
1836 '-Wno-missing-field-initializers', '-g']
0 import os
1 import glob
2 from setuptools import setup, Extension
3
4 WANT_SIGINT_HANDLING = os.environ.get('BJOERN_WANT_SIGINT_HANDLING', True)
5 WANT_SIGNAL_HANDLING = os.environ.get('BJOERN_WANT_SIGNAL_HANDLING', True)
6 SIGNAL_CHECK_INTERVAL = os.environ.get('BJOERN_SIGNAL_CHECK_INTERVAL', '0.1')
7 WANT_STATSD = os.environ.get('BJOERN_WANT_STATSD', False)
8 WANT_STATSD_TAGS = os.environ.get('BJOERN_WANT_STATSD_TAGS', False)
9
10 compile_flags = [('SIGNAL_CHECK_INTERVAL', SIGNAL_CHECK_INTERVAL)]
11 if WANT_SIGNAL_HANDLING:
12 compile_flags.append(('WANT_SIGNAL_HANDLING', 'yes'))
13 if WANT_SIGINT_HANDLING:
14 compile_flags.append(('WANT_SIGINT_HANDLING', 'yes'))
15 if WANT_STATSD:
16 compile_flags.append(('WANT_STATSD', 'yes'))
17 if WANT_STATSD_TAGS:
18 compile_flags.append(('WANT_STATSD_TAGS', 'yes'))
19
20 SOURCE_FILES = [os.path.join('http-parser', 'http_parser.c')] + \
21 [os.path.join('statsd-c-client', 'statsd-client.c')] + \
22 sorted(glob.glob(os.path.join('bjoern', '*.c')))
23
24 if not WANT_STATSD:
25 SOURCE_FILES.remove('statsd-c-client/statsd-client.c')
26 SOURCE_FILES.remove('bjoern/statsd_tags.c')
27
28 bjoern_extension = Extension(
29 '_bjoern',
30 sources = SOURCE_FILES,
31 libraries = ['ev'],
32 include_dirs = ['http-parser', 'statsd-c-client', '/usr/include/libev', '/opt/local/include'],
33 define_macros = compile_flags,
34 extra_compile_args = ['-std=c99', '-fno-strict-aliasing', '-fcommon',
35 '-fPIC', '-Wall', '-Wextra', '-Wno-unused-parameter',
36 '-Wno-missing-field-initializers', '-g']
37 )
38
39 setup(
40 name = 'bjoern',
41 author = 'Jonas Haag',
42 author_email = 'jonas@lophus.org',
43 license = '2-clause BSD',
44 url = 'https://github.com/jonashaag/bjoern',
45 description = 'A screamingly fast Python 2 + 3 WSGI server written in C.',
46 version = '3.0.1',
47 classifiers = ['Development Status :: 4 - Beta',
48 'License :: OSI Approved :: BSD License',
49 'Programming Language :: C',
50 'Programming Language :: Python :: 2',
51 'Programming Language :: Python :: 3',
52 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server'],
53 py_modules = ['bjoern'],
54 ext_modules = [bjoern_extension]
55 )
0 import bjoern
1
2 if __name__ == '__main__':
3 for k, v in bjoern.features.iteritems():
4 print('{}: {}'.format(k, v))
5
2828 return choice(apps)(env, start_response)
2929
3030 if __name__ == '__main__':
31 bjoern.run(wsgi_app, '0.0.0.0', 8080)
31 bjoern.run(wsgi_app, '127.0.0.1', 8080)
0 import bjoern
1 from random import choice
2
3 def app1(env, sr):
4 #print(env)
5 sr('200 ok', [('Foo', 'Bar'), ('Blah', 'Blubb'), ('Spam', 'Eggs'), ('Blurg', 'asdasjdaskdasdjj asdk jaks / /a jaksdjkas jkasd jkasdj '),
6 ('asd2easdasdjaksdjdkskjkasdjka', 'oasdjkadk kasdk k k k k k ')])
7 return [b'hello', b'world']
8
9 def app2(env, sr):
10 #print(env)
11 sr('200 ok', [])
12 return b'hello'
13
14 def app3(env, sr):
15 #print(env)
16 sr('200 abc', [('Content-Length', '12')])
17 yield b'Hello'
18 yield b' World'
19 yield b'\n'
20
21 def app4(e, sr):
22 sr('200 ok', [])
23 return [b'hello there ... \n']
24
25 apps = (app1, app2, app3, app4)
26
27 def wsgi_app(env, start_response):
28 return choice(apps)(env, start_response)
29
30 if __name__ == '__main__':
31 bjoern.run(wsgi_app, '127.0.0.1', 8080, statsd={'enable': True, 'host': '127.0.0.1', 'port': 8888, 'ns': 'bjoern'})
0 import bjoern
1 from random import choice
2
3 def app1(env, sr):
4 #print(env)
5 sr('200 ok', [('Foo', 'Bar'), ('Blah', 'Blubb'), ('Spam', 'Eggs'), ('Blurg', 'asdasjdaskdasdjj asdk jaks / /a jaksdjkas jkasd jkasdj '),
6 ('asd2easdasdjaksdjdkskjkasdjka', 'oasdjkadk kasdk k k k k k ')])
7 return [b'hello', b'world']
8
9 def app2(env, sr):
10 #print(env)
11 sr('200 ok', [])
12 return b'hello'
13
14 def app3(env, sr):
15 #print(env)
16 sr('200 abc', [('Content-Length', '12')])
17 yield b'Hello'
18 yield b' World'
19 yield b'\n'
20
21 def app4(e, sr):
22 sr('200 ok', [])
23 return [b'hello there ... \n']
24
25 apps = (app1, app2, app3, app4)
26
27 def wsgi_app(env, start_response):
28 return choice(apps)(env, start_response)
29
30 if __name__ == '__main__':
31 bjoern.run(wsgi_app, '0.0.0.0', 8080, statsd={'enable': True, 'host': '127.0.0.1', 'port': 8888, 'ns': 'bjoern', 'tags': 'tag1:val1,tag2:val2'})