hermann 0.11-x86-darwin-12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+
2
+ require 'rake/clean'
3
+
4
+ EXT_CONF = "ext/extconf.rb"
5
+ MAKEFILE = 'ext/Makefile'
6
+ MODULE = 'ext/hermann_lib.so'
7
+ SRC = Dir.glob('ext/*.c')
8
+ SRC << MAKEFILE
9
+
10
+ CLEAN.include [ 'ext/*.o', 'ext/depend', 'ext/hermann_lib.bundle', MODULE ]
11
+ CLOBBER.include [ 'config.save', 'ext/mkmf.log', 'ext/hermann_lib.bundle', MAKEFILE ]
12
+
13
+ file MAKEFILE => EXT_CONF do |t|
14
+ Dir::chdir(File::dirname(EXT_CONF)) do
15
+ unless sh "ruby #{File::basename(EXT_CONF)}"
16
+ $stderr.puts "Failed to run extconf"
17
+ break
18
+ end
19
+ end
20
+ end
21
+
22
+ file MODULE => SRC do |t|
23
+ Dir::chdir(File::dirname(EXT_CONF)) do
24
+ unless sh "make"
25
+ $stderr.puts "make failed"
26
+ end
27
+ end
28
+ end
29
+
30
+ desc "Build the native library"
31
+ task :build => MODULE
data/bin/hermann ADDED
@@ -0,0 +1,4 @@
1
+ #!ruby
2
+
3
+ require 'hermann'
4
+ puts "Placeholder..."
data/ext/extconf.rb ADDED
@@ -0,0 +1,32 @@
1
+ # External configuration for Hermann Gem
2
+
3
+ require 'mkmf'
4
+
5
+ RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
6
+
7
+ LIBDIR = RbConfig::CONFIG['libdir']
8
+ INCLUDEDIR = RbConfig::CONFIG['includedir']
9
+
10
+ puts "Library Dir: #{LIBDIR}\n"
11
+ puts "Include Dir: #{INCLUDEDIR}"
12
+
13
+ HEADER_DIRS = [INCLUDEDIR]
14
+
15
+ LIB_DIRS = [LIBDIR]
16
+
17
+ dir_config('rdkafka', HEADER_DIRS, LIB_DIRS)
18
+
19
+ unless find_header('librdkafka/rdkafka.h')
20
+ abort "librdkafka not installed"
21
+ end
22
+
23
+ unless find_library('rdkafka', 'rd_kafka_conf_new')
24
+ abort "librdkafka not installed"
25
+ end
26
+
27
+ #unless have_func "rb_thread_blocking_region"
28
+ # abort "rb_thread_blocking_region function missing"
29
+ #end
30
+
31
+ # create_header('hermann_lib.h')
32
+ create_makefile('hermann/hermann_lib')
data/ext/hermann_lib.c ADDED
@@ -0,0 +1,727 @@
1
+ /*
2
+ * hermann_lib.c - Ruby wrapper for the librdkafka library
3
+ *
4
+ * Copyright (c) 2014 Stan Campbell
5
+ * All rights reserved.
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without
8
+ * modification, are permitted provided that the following conditions are met:
9
+ *
10
+ * 1. Redistributions of source code must retain the above copyright notice,
11
+ * this list of conditions and the following disclaimer.
12
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ * this list of conditions and the following disclaimer in the documentation
14
+ * and/or other materials provided with the distribution.
15
+ *
16
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ * POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
29
+ /* Much of the librdkafka library calls were lifted from rdkafka_example.c */
30
+
31
+ #include "hermann_lib.h"
32
+
33
+ /**
34
+ * Utility functions
35
+ */
36
+
37
+
38
+ /**
39
+ * Convenience function
40
+ *
41
+ * @param msg char* the string to be logged under debugging.
42
+ */
43
+ void log_debug(char* msg) {
44
+ if(DEBUG) {
45
+ fprintf(stderr, "%s\n", msg);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Message delivery report callback.
51
+ * Called once for each message.
52
+ *
53
+ * @param rk rd_kafka_t* instance of producer or consumer
54
+ * @param payload void* the payload of the message
55
+ * @param len size_t the length of the payload in bytes
56
+ * @param error_code int
57
+ * @param opaque void* optional context
58
+ * @param msg_opaque void* it's opaque
59
+ */
60
+ static void msg_delivered (rd_kafka_t *rk,
61
+ void *payload, size_t len,
62
+ int error_code,
63
+ void *opaque, void *msg_opaque) {
64
+
65
+ if (error_code)
66
+ fprintf(stderr, "%% Message delivery failed: %s\n",
67
+ rd_kafka_err2str(error_code));
68
+ }
69
+
70
+ /**
71
+ * Producer partitioner callback.
72
+ * Used to determine the target partition within a topic for production.
73
+ *
74
+ * Returns an integer partition number or RD_KAFKA_PARTITION_UA if no
75
+ * available partition could be determined.
76
+ *
77
+ * @param rkt rd_kafka_topic_t* the topic
78
+ * @param keydata void* key information for calculating the partition
79
+ * @param keylen size_t key size
80
+ * @param partition_cnt int32_t the count of the number of partitions
81
+ * @param rkt_opaque void* opaque topic info
82
+ * @param msg_opaque void* opaque message info
83
+ */
84
+ static int32_t producer_paritioner_callback( const rd_kafka_topic_t *rkt,
85
+ const void *keydata,
86
+ size_t keylen,
87
+ int32_t partition_cnt,
88
+ void *rkt_opaque,
89
+ void *msg_opaque) {
90
+ /* Pick a random partition */
91
+ int retry;
92
+ for(retry=0;retry<partition_cnt;retry++) {
93
+ int32_t partition = rand() % partition_cnt;
94
+ if(rd_kafka_topic_partition_available(rkt, partition)) {
95
+ break; /* this one will do */
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * hexdump
102
+ *
103
+ * Write the given payload to file in hex notation.
104
+ *
105
+ * @param fp FILE* the file into which to write
106
+ * @param name char* name
107
+ * @param ptr void* payload
108
+ * @param len size_t payload length
109
+ */
110
+ static void hexdump (FILE *fp, const char *name, const void *ptr, size_t len) {
111
+ const char *p = (const char *)ptr;
112
+ int of = 0;
113
+
114
+
115
+ if (name)
116
+ fprintf(fp, "%s hexdump (%zd bytes):\n", name, len);
117
+
118
+ for (of = 0 ; of < len ; of += 16) {
119
+ char hexen[16*3+1];
120
+ char charen[16+1];
121
+ int hof = 0;
122
+
123
+ int cof = 0;
124
+ int i;
125
+
126
+ for (i = of ; i < of + 16 && i < len ; i++) {
127
+ hof += sprintf(hexen+hof, "%02x ", p[i] & 0xff);
128
+ cof += sprintf(charen+cof, "%c",
129
+ isprint((int)p[i]) ? p[i] : '.');
130
+ }
131
+ fprintf(fp, "%08x: %-48s %-16s\n",
132
+ of, hexen, charen);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * msg_consume
138
+ *
139
+ * Callback on message receipt.
140
+ *
141
+ * @param rkmessage rd_kafka_message_t* the message
142
+ * @param opaque void* opaque context
143
+ */
144
+ static void msg_consume (rd_kafka_message_t *rkmessage,
145
+ void *opaque) {
146
+
147
+ HermannInstanceConfig* cfg;
148
+
149
+ cfg = (HermannInstanceConfig*)opaque;
150
+
151
+ if (rkmessage->err) {
152
+ if (rkmessage->err == RD_KAFKA_RESP_ERR__PARTITION_EOF) {
153
+ fprintf(stderr,
154
+ "%% Consumer reached end of %s [%"PRId32"] "
155
+ "message queue at offset %"PRId64"\n",
156
+ rd_kafka_topic_name(rkmessage->rkt),
157
+ rkmessage->partition, rkmessage->offset);
158
+
159
+ if (cfg->exit_eof)
160
+ cfg->run = 0;
161
+
162
+ return;
163
+ }
164
+
165
+ fprintf(stderr, "%% Consume error for topic \"%s\" [%"PRId32"] "
166
+ "offset %"PRId64": %s\n",
167
+ rd_kafka_topic_name(rkmessage->rkt),
168
+ rkmessage->partition,
169
+ rkmessage->offset,
170
+ rd_kafka_message_errstr(rkmessage));
171
+ return;
172
+ }
173
+
174
+ if (DEBUG && rkmessage->key_len) {
175
+ if (output == OUTPUT_HEXDUMP)
176
+ hexdump(stdout, "Message Key",
177
+ rkmessage->key, rkmessage->key_len);
178
+ else
179
+ printf("Key: %.*s\n",
180
+ (int)rkmessage->key_len, (char *)rkmessage->key);
181
+ }
182
+
183
+ if (output == OUTPUT_HEXDUMP) {
184
+ if(DEBUG)
185
+ hexdump(stdout, "Message Payload", rkmessage->payload, rkmessage->len);
186
+ } else {
187
+ if(DEBUG)
188
+ printf("%.*s\n", (int)rkmessage->len, (char *)rkmessage->payload);
189
+ }
190
+
191
+ // Yield the data to the Consumer's block
192
+ if(rb_block_given_p()) {
193
+ VALUE value = rb_str_new((char *)rkmessage->payload, rkmessage->len);
194
+ rb_yield(value);
195
+ } else {
196
+ if(DEBUG)
197
+ fprintf(stderr, "No block given\n"); // todo: should this be an error?
198
+ }
199
+ }
200
+
201
+ /**
202
+ * logger
203
+ *
204
+ * Kafka logger callback (optional)
205
+ *
206
+ * todo: introduce better logging
207
+ *
208
+ * @param rk rd_kafka_t the producer or consumer
209
+ * @param level int the log level
210
+ * @param fac char* something of which I am unaware
211
+ * @param buf char* the log message
212
+ */
213
+ static void logger (const rd_kafka_t *rk, int level,
214
+ const char *fac, const char *buf) {
215
+ struct timeval tv;
216
+ gettimeofday(&tv, NULL);
217
+ fprintf(stderr, "%u.%03u RDKAFKA-%i-%s: %s: %s\n",
218
+ (int)tv.tv_sec, (int)(tv.tv_usec / 1000),
219
+ level, fac, rd_kafka_name(rk), buf);
220
+ }
221
+
222
+ /**
223
+ * consumer_init_kafka
224
+ *
225
+ * Initialize the Kafka context and instantiate a consumer.
226
+ *
227
+ * @param config HermannInstanceConfig* pointer to the instance configuration for this producer or consumer
228
+ */
229
+ void consumer_init_kafka(HermannInstanceConfig* config) {
230
+
231
+ config->quiet = !isatty(STDIN_FILENO);
232
+
233
+ /* Kafka configuration */
234
+ config->conf = rd_kafka_conf_new();
235
+
236
+ /* Topic configuration */
237
+ config->topic_conf = rd_kafka_topic_conf_new();
238
+
239
+ /* Create Kafka handle */
240
+ if (!(config->rk = rd_kafka_new(RD_KAFKA_CONSUMER, config->conf,
241
+ config->errstr, sizeof(config->errstr)))) {
242
+ fprintf(stderr, "%% Failed to create new consumer: %s\n", config->errstr);
243
+ exit(1);
244
+ }
245
+
246
+ /* Set logger */
247
+ rd_kafka_set_logger(config->rk, logger);
248
+ rd_kafka_set_log_level(config->rk, LOG_DEBUG);
249
+
250
+ /* TODO: offset calculation */
251
+ config->start_offset = RD_KAFKA_OFFSET_END;
252
+
253
+ /* Add brokers */
254
+ if(rd_kafka_brokers_add(config->rk, config->brokers) == 0) {
255
+ fprintf(stderr, "%% No valid brokers specified\n");
256
+ exit(1);
257
+ }
258
+
259
+ /* Create topic */
260
+ config->rkt = rd_kafka_topic_new(config->rk, config->topic, config->topic_conf);
261
+
262
+ /* We're now initialized */
263
+ config->isInitialized = 1;
264
+ }
265
+
266
+ // Ruby gem extensions
267
+
268
+ /**
269
+ * Callback invoked if Ruby needs to stop our Consumer's IO loop for any reason (system exit, etc.)
270
+ */
271
+ static void consumer_consume_stop_callback(void *ptr) {
272
+ HermannInstanceConfig* config = (HermannInstanceConfig*)ptr;
273
+
274
+ config->run = 0;
275
+ }
276
+
277
+ /**
278
+ * Loop on a timeout to receive messages from Kafka. When the consumer_consume_stop_callback is invoked by Ruby,
279
+ * we'll break out of our loop and return.
280
+ */
281
+ void consumer_consume_loop(HermannInstanceConfig* consumerConfig) {
282
+
283
+ while (consumerConfig->run) {
284
+ rd_kafka_message_t *rkmessage;
285
+
286
+ if(rd_kafka_consume_callback(consumerConfig->rkt, consumerConfig->partition,
287
+ 1000/*timeout*/,
288
+ msg_consume,
289
+ consumerConfig) < 0) {
290
+ fprintf(stderr, "%% Error: %s\n", rd_kafka_err2str( rd_kafka_errno2err(errno)));
291
+ }
292
+
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Hermann::Consumer.consume
298
+ *
299
+ * Begin listening on the configured topic for messages. msg_consume will be called on each message received.
300
+ *
301
+ * @param VALUE self the Ruby object for this consumer
302
+ */
303
+ static VALUE consumer_consume(VALUE self) {
304
+
305
+ HermannInstanceConfig* consumerConfig;
306
+
307
+ Data_Get_Struct(self, HermannInstanceConfig, consumerConfig);
308
+
309
+ if(consumerConfig->topic==NULL) {
310
+ fprintf(stderr, "Topic is null!");
311
+ return;
312
+ }
313
+
314
+ if(!consumerConfig->isInitialized) {
315
+ consumer_init_kafka(consumerConfig);
316
+ }
317
+
318
+ /* Start consuming */
319
+ if (rd_kafka_consume_start(consumerConfig->rkt, consumerConfig->partition, consumerConfig->start_offset) == -1){
320
+ fprintf(stderr, "%% Failed to start consuming: %s\n",
321
+ rd_kafka_err2str(rd_kafka_errno2err(errno)));
322
+ exit(1);
323
+ }
324
+
325
+ #ifdef RB_THREAD_BLOCKING_REGION
326
+ /** The consumer will listen for incoming messages in a loop, timing out and checking the consumerConfig->run
327
+ * flag every second.
328
+ *
329
+ * Call rb_thread_blocking_region to release the GVM lock and allow Ruby to amuse itself while we wait on
330
+ * IO from Kafka.
331
+ *
332
+ * If Ruby needs to interrupt the consumer loop, the stop callback will be invoked and the loop should exit.
333
+ */
334
+ rb_thread_blocking_region(consumer_consume_loop, consumerConfig, consumer_consume_stop_callback, consumerConfig);
335
+ #else
336
+ consumer_consume_loop(consumerConfig);
337
+ #endif
338
+
339
+
340
+ /* Stop consuming */
341
+ rd_kafka_consume_stop(consumerConfig->rkt, consumerConfig->partition);
342
+
343
+ return Qnil;
344
+ }
345
+
346
+ /**
347
+ * producer_init_kafka
348
+ *
349
+ * Initialize the producer instance, setting up the Kafka topic and context.
350
+ *
351
+ * @param config HermannInstanceConfig* the instance configuration associated with this producer.
352
+ */
353
+ void producer_init_kafka(HermannInstanceConfig* config) {
354
+
355
+ config->quiet = !isatty(STDIN_FILENO);
356
+
357
+ /* Kafka configuration */
358
+ config->conf = rd_kafka_conf_new();
359
+
360
+ /* Topic configuration */
361
+ config->topic_conf = rd_kafka_topic_conf_new();
362
+
363
+ /* Set up a message delivery report callback.
364
+ * It will be called once for each message, either on successful
365
+ * delivery to broker, or upon failure to deliver to broker. */
366
+ rd_kafka_conf_set_dr_cb(config->conf, msg_delivered);
367
+
368
+ /* Create Kafka handle */
369
+ if (!(config->rk = rd_kafka_new(RD_KAFKA_PRODUCER, config->conf, config->errstr, sizeof(config->errstr)))) {
370
+ fprintf(stderr,
371
+ "%% Failed to create new producer: %s\n", config->errstr);
372
+ exit(1);
373
+ }
374
+
375
+ /* Set logger */
376
+ rd_kafka_set_logger(config->rk, logger);
377
+ rd_kafka_set_log_level(config->rk, LOG_DEBUG);
378
+
379
+ if(rd_kafka_brokers_add(config->rk, config->brokers) == 0) {
380
+ fprintf(stderr, "%% No valid brokers specified\n");
381
+ exit(1);
382
+ }
383
+
384
+ /* Create topic */
385
+ config->rkt = rd_kafka_topic_new(config->rk, config->topic, config->topic_conf);
386
+
387
+ /* Set the partitioner callback */
388
+ rd_kafka_topic_conf_set_partitioner_cb( config->topic_conf, producer_paritioner_callback );
389
+
390
+ /* We're now initialized */
391
+ config->isInitialized = 1;
392
+ }
393
+
394
+ /**
395
+ * producer_push_single
396
+ *
397
+ * @param self VALUE the Ruby producer instance
398
+ * @param message VALUE the ruby String containing the outgoing message.
399
+ */
400
+ static VALUE producer_push_single(VALUE self, VALUE message) {
401
+
402
+ HermannInstanceConfig* producerConfig;
403
+ char buf[2048];
404
+
405
+ Data_Get_Struct(self, HermannInstanceConfig, producerConfig);
406
+
407
+ if(producerConfig->topic==NULL) {
408
+ fprintf(stderr, "Topic is null!");
409
+ return self;
410
+ }
411
+
412
+ if(!producerConfig->isInitialized) {
413
+ producer_init_kafka(producerConfig);
414
+ }
415
+
416
+ char *msg = StringValueCStr(message);
417
+ strcpy(buf, msg);
418
+
419
+ size_t len = strlen(buf);
420
+ if (buf[len-1] == '\n')
421
+ buf[--len] = '\0';
422
+
423
+ /* Send/Produce message. */
424
+ if (rd_kafka_produce(producerConfig->rkt, producerConfig->partition, RD_KAFKA_MSG_F_COPY,
425
+ /* Payload and length */
426
+ buf, len,
427
+ /* Optional key and its length */
428
+ NULL, 0,
429
+ /* Message opaque, provided in
430
+ * delivery report callback as
431
+ * msg_opaque. */
432
+ NULL) == -1) {
433
+
434
+ fprintf(stderr, "%% Failed to produce to topic %s partition %i: %s\n",
435
+ rd_kafka_topic_name(producerConfig->rkt), producerConfig->partition,
436
+ rd_kafka_err2str(rd_kafka_errno2err(errno)));
437
+
438
+ /* Poll to handle delivery reports */
439
+ rd_kafka_poll(producerConfig->rk, 10);
440
+ }
441
+
442
+ /* Must poll to handle delivery reports */
443
+ rd_kafka_poll(producerConfig->rk, 0);
444
+
445
+ return self;
446
+ }
447
+
448
+ /**
449
+ * producer_push_array
450
+ *
451
+ * Publish each of the messages in array on the configured topic.
452
+ *
453
+ * @param self VALUE the instance of the Ruby Producer object
454
+ * @param length int the length of the outgoing messages array
455
+ * @param array VALUE the Ruby array of messages
456
+ */
457
+ static VALUE producer_push_array(VALUE self, int length, VALUE array) {
458
+
459
+ int i;
460
+ VALUE message;
461
+
462
+ for(i=0;i<length;i++) {
463
+ message = RARRAY_PTR(array)[i];
464
+ producer_push_single(self, message);
465
+ }
466
+
467
+ return self;
468
+ }
469
+
470
+ /**
471
+ * Hermann::Producer.push(msg)
472
+ *
473
+ * Publish the given message on the configured topic.
474
+ *
475
+ * @param self VALUE the Ruby instance of the Producer.
476
+ * @param message VALUE the Ruby string containing the message.
477
+ */
478
+ static VALUE producer_push(VALUE self, VALUE message) {
479
+
480
+ VALUE arrayP = rb_check_array_type(message);
481
+
482
+ if(!NIL_P(arrayP)) {
483
+ return producer_push_array(self, RARRAY_LEN(arrayP), message);
484
+ } else {
485
+ return producer_push_single(self, message);
486
+ }
487
+ }
488
+
489
+ /**
490
+ * consumer_free
491
+ *
492
+ * Callback called when Ruby needs to GC the configuration associated with an Hermann instance.
493
+ *
494
+ * @param p void* the instance of an HermannInstanceConfig to be freed from allocated memory.
495
+ */
496
+ static void consumer_free(void * p) {
497
+
498
+ HermannInstanceConfig* config = (HermannInstanceConfig *)p;
499
+
500
+ // the p *should* contain a pointer to the consumerConfig which also must be freed
501
+ rd_kafka_topic_destroy(config->rkt);
502
+
503
+ rd_kafka_destroy(config->rk);
504
+
505
+ // clean up the struct
506
+ free(config);
507
+ }
508
+
509
+ /**
510
+ * consumer_allocate
511
+ *
512
+ * Allocate and wrap an HermannInstanceConfig for this Consumer object.
513
+ *
514
+ * @param klass VALUE the class of the enclosing Ruby object.
515
+ */
516
+ static VALUE consumer_allocate(VALUE klass) {
517
+
518
+ VALUE obj;
519
+
520
+ HermannInstanceConfig* consumerConfig = ALLOC(HermannInstanceConfig);
521
+ obj = Data_Wrap_Struct(klass, 0, consumer_free, consumerConfig);
522
+
523
+ return obj;
524
+ }
525
+
526
+ /**
527
+ * consumer_initialize
528
+ *
529
+ * todo: configure the brokers through passed parameter, later through zk
530
+ *
531
+ * Set up the Consumer's HermannInstanceConfig context.
532
+ *
533
+ * @param self VALUE the Ruby instance of the Consumer
534
+ * @param topic VALUE a Ruby string
535
+ * @param brokers VALUE a Ruby string containing list of host:port
536
+ * @param partition VALUE a Ruby number
537
+ */
538
+ static VALUE consumer_initialize(VALUE self, VALUE topic, VALUE brokers, VALUE partition) {
539
+
540
+ HermannInstanceConfig* consumerConfig;
541
+ char* topicPtr;
542
+ char* brokersPtr;
543
+ int partitionNo;
544
+
545
+ topicPtr = StringValuePtr(topic);
546
+ brokersPtr = StringValuePtr(brokers);
547
+ partitionNo = FIX2INT(partition);
548
+ Data_Get_Struct(self, HermannInstanceConfig, consumerConfig);
549
+
550
+ consumerConfig->topic = topicPtr;
551
+ consumerConfig->brokers = brokersPtr;
552
+ consumerConfig->partition = partitionNo;
553
+ consumerConfig->run = 1;
554
+ consumerConfig->exit_eof = 0;
555
+ consumerConfig->quiet = 0;
556
+
557
+ return self;
558
+ }
559
+
560
+ /**
561
+ * consumer_init_copy
562
+ *
563
+ * When copying into a new instance of a Consumer, reproduce the configuration info.
564
+ *
565
+ * @param copy VALUE the Ruby Consumer instance (with configuration) as destination
566
+ * @param orig VALUE the Ruby Consumer instance (with configuration) as source
567
+ *
568
+ */
569
+ static VALUE consumer_init_copy(VALUE copy, VALUE orig) {
570
+ HermannInstanceConfig* orig_config;
571
+ HermannInstanceConfig* copy_config;
572
+
573
+ if(copy == orig) {
574
+ return copy;
575
+ }
576
+
577
+ if (TYPE(orig) != T_DATA || RDATA(orig)->dfree != (RUBY_DATA_FUNC)consumer_free) {
578
+ rb_raise(rb_eTypeError, "wrong argument type");
579
+ }
580
+
581
+ Data_Get_Struct(orig, HermannInstanceConfig, orig_config);
582
+ Data_Get_Struct(copy, HermannInstanceConfig, copy_config);
583
+
584
+ // Copy over the data from one struct to the other
585
+ MEMCPY(copy_config, orig_config, HermannInstanceConfig, 1);
586
+
587
+ return copy;
588
+ }
589
+
590
+ /**
591
+ * producer_free
592
+ *
593
+ * Reclaim memory allocated to the Producer's configuration
594
+ *
595
+ * @param p void* the instance's configuration struct
596
+ */
597
+ static void producer_free(void * p) {
598
+
599
+ HermannInstanceConfig* config = (HermannInstanceConfig *)p;
600
+
601
+ // Clean up the topic
602
+ rd_kafka_topic_destroy(config->rkt);
603
+
604
+ // Take care of the producer instance
605
+ rd_kafka_destroy(config->rk);
606
+
607
+ // Free the struct
608
+ free(config);
609
+ }
610
+
611
+ /**
612
+ * producer_allocate
613
+ *
614
+ * Allocate the memory for a Producer's configuration
615
+ *
616
+ * @param klass VALUE the class of the Producer
617
+ */
618
+ static VALUE producer_allocate(VALUE klass) {
619
+
620
+ VALUE obj;
621
+
622
+ HermannInstanceConfig* producerConfig = ALLOC(HermannInstanceConfig);
623
+ obj = Data_Wrap_Struct(klass, 0, producer_free, producerConfig);
624
+
625
+ return obj;
626
+ }
627
+
628
+ /**
629
+ * producer_initialize
630
+ *
631
+ * Set up the configuration context for the Producer instance
632
+ *
633
+ * @param self VALUE the Producer instance
634
+ * @param topic VALUE the Ruby string naming the topic
635
+ * @param brokers VALUE a Ruby string containing host:port pairs separated by commas
636
+ */
637
+ static VALUE producer_initialize(VALUE self, VALUE topic, VALUE brokers) {
638
+
639
+ HermannInstanceConfig* producerConfig;
640
+ char* topicPtr;
641
+ char* brokersPtr;
642
+
643
+ topicPtr = StringValuePtr(topic);
644
+ brokersPtr = StringValuePtr(brokers);
645
+ Data_Get_Struct(self, HermannInstanceConfig, producerConfig);
646
+
647
+ producerConfig->topic = topicPtr;
648
+ producerConfig->brokers = brokersPtr;
649
+ /** Using RD_KAFKA_PARTITION_UA specifies we want the partitioner callback to be called to determine the target
650
+ * partition
651
+ */
652
+ producerConfig->partition = RD_KAFKA_PARTITION_UA;
653
+ producerConfig->run = 1;
654
+ producerConfig->exit_eof = 0;
655
+ producerConfig->quiet = 0;
656
+
657
+ return self;
658
+ }
659
+
660
+ /**
661
+ * producer_init_copy
662
+ *
663
+ * Copy the configuration information from orig into copy for the given Producer instances.
664
+ *
665
+ * @param copy VALUE destination Producer
666
+ * @param orign VALUE source Producer
667
+ */
668
+ static VALUE producer_init_copy(VALUE copy, VALUE orig) {
669
+ HermannInstanceConfig* orig_config;
670
+ HermannInstanceConfig* copy_config;
671
+
672
+ if(copy == orig) {
673
+ return copy;
674
+ }
675
+
676
+ if (TYPE(orig) != T_DATA || RDATA(orig)->dfree != (RUBY_DATA_FUNC)producer_free) {
677
+ rb_raise(rb_eTypeError, "wrong argument type");
678
+ }
679
+
680
+ Data_Get_Struct(orig, HermannInstanceConfig, orig_config);
681
+ Data_Get_Struct(copy, HermannInstanceConfig, copy_config);
682
+
683
+ // Copy over the data from one struct to the other
684
+ MEMCPY(copy_config, orig_config, HermannInstanceConfig, 1);
685
+
686
+ return copy;
687
+ }
688
+
689
+ /**
690
+ * Init_hermann_lib
691
+ *
692
+ * Called by Ruby when the Hermann gem is loaded.
693
+ * Defines the Hermann module.
694
+ * Defines the Producer and Consumer classes.
695
+ */
696
+ void Init_hermann_lib() {
697
+
698
+ /* Define the module */
699
+ m_hermann = rb_define_module("Hermann");
700
+
701
+ /* ---- Define the consumer class ---- */
702
+ VALUE c_consumer = rb_define_class_under(m_hermann, "Consumer", rb_cObject);
703
+
704
+ /* Allocate */
705
+ rb_define_alloc_func(c_consumer, consumer_allocate);
706
+
707
+ /* Initialize */
708
+ rb_define_method(c_consumer, "initialize", consumer_initialize, 3);
709
+ rb_define_method(c_consumer, "initialize_copy", consumer_init_copy, 1);
710
+
711
+ /* Consumer has method 'consume' */
712
+ rb_define_method( c_consumer, "consume", consumer_consume, 0 );
713
+
714
+ /* ---- Define the producer class ---- */
715
+ VALUE c_producer = rb_define_class_under(m_hermann, "Producer", rb_cObject);
716
+
717
+ /* Allocate */
718
+ rb_define_alloc_func(c_producer, producer_allocate);
719
+
720
+ /* Initialize */
721
+ rb_define_method(c_producer, "initialize", producer_initialize, 2);
722
+ rb_define_method(c_producer, "initialize_copy", producer_init_copy, 1);
723
+
724
+ /* Producer.push(msg) */
725
+ rb_define_method( c_producer, "push", producer_push, 1 );
726
+
727
+ }
data/ext/hermann_lib.h ADDED
@@ -0,0 +1,83 @@
1
+ /*
2
+ * hermann_lib.h - Ruby wrapper for the librdkafka library
3
+ *
4
+ * Copyright (c) 2014 Stan Campbell
5
+ * All rights reserved.
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without
8
+ * modification, are permitted provided that the following conditions are met:
9
+ *
10
+ * 1. Redistributions of source code must retain the above copyright notice,
11
+ * this list of conditions and the following disclaimer.
12
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ * this list of conditions and the following disclaimer in the documentation
14
+ * and/or other materials provided with the distribution.
15
+ *
16
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ * POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
29
+ #ifndef HERMANN_H
30
+ #define HERMANN_H
31
+
32
+ #include <ruby.h>
33
+
34
+ #include <ctype.h>
35
+ #include <signal.h>
36
+ #include <string.h>
37
+ #include <unistd.h>
38
+ #include <stdlib.h>
39
+ #include <syslog.h>
40
+ #include <sys/time.h>
41
+ #include <errno.h>
42
+
43
+ #include <librdkafka/rdkafka.h>
44
+
45
+ // Holds the defined Ruby module for Hermann
46
+ static VALUE m_hermann;
47
+
48
+ static int DEBUG = 0;
49
+
50
+ // Should we expect rb_thread_blocking_region to be present?
51
+ // #define RB_THREAD_BLOCKING_REGION
52
+ #undef RB_THREAD_BLOCKING_REGION
53
+
54
+ static enum {
55
+ OUTPUT_HEXDUMP,
56
+ OUTPUT_RAW,
57
+ } output = OUTPUT_HEXDUMP;
58
+
59
+ typedef struct HermannInstanceConfig {
60
+
61
+ char* topic;
62
+
63
+ /* Kafka configuration */
64
+ rd_kafka_t *rk;
65
+ rd_kafka_topic_t *rkt;
66
+ char *brokers;
67
+ int partition;
68
+ rd_kafka_topic_conf_t *topic_conf;
69
+ char errstr[512];
70
+ rd_kafka_conf_t *conf;
71
+ const char *debug;
72
+ int64_t start_offset;
73
+ int do_conf_dump;
74
+
75
+ int run;
76
+ int exit_eof;
77
+ int quiet;
78
+
79
+ int isInitialized;
80
+
81
+ } HermannInstanceConfig;
82
+
83
+ #endif
data/ext/hermann_lib.o ADDED
Binary file
data/lib/hermann.rb ADDED
@@ -0,0 +1 @@
1
+ require 'hermann_lib'
@@ -0,0 +1,9 @@
1
+ require 'test/unit'
2
+ require 'hermann'
3
+
4
+ class HermannTest < Test::Unit::TestCase
5
+
6
+ def test_stub
7
+ assert_equal "Apples", "Apples"
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hermann
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 11
9
+ version: "0.11"
10
+ platform: x86-darwin-12
11
+ authors:
12
+ - Stan Campbell
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2014-05-29 00:00:00 Z
18
+ dependencies: []
19
+
20
+ description: Ruby gem wrapper for the RdKafka C library
21
+ email: stan.campbell3@gmail.com
22
+ executables: []
23
+
24
+ extensions:
25
+ - ext/extconf.rb
26
+ extra_rdoc_files: []
27
+
28
+ files:
29
+ - Rakefile
30
+ - ext/hermann_lib.h
31
+ - ext/hermann_lib.c
32
+ - ext/extconf.rb
33
+ - bin/hermann
34
+ - lib/hermann.rb
35
+ - ext/hermann_lib.o
36
+ - test/test_hermann.rb
37
+ homepage: http://rubygems.org/gems/hermann
38
+ licenses:
39
+ - MIT
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ - ext
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ hash: 3
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.8.25
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: The Kafka consumer is based on the librdkafka C library.
71
+ test_files:
72
+ - test/test_hermann.rb