rbkit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +3 -0
- data/.gitignore +16 -0
- data/Gemfile +7 -0
- data/README.md +91 -0
- data/Rakefile +7 -0
- data/docs/debugging.md +27 -0
- data/docs/design.md +3 -0
- data/experiments/benchmark.rb +37 -0
- data/experiments/mspack_to_file.rb +10 -0
- data/experiments/no_growth.rb +20 -0
- data/experiments/object_dump.rb +21 -0
- data/experiments/rbkit_client.rb +25 -0
- data/experiments/rbkit_command_test.rb +54 -0
- data/experiments/using_new_client.rb +22 -0
- data/experiments/using_object_tracer.rb +11 -0
- data/experiments/using_rbkit.rb +19 -0
- data/experiments/zmq_client.rb +46 -0
- data/experiments/zmq_server.rb +52 -0
- data/ext/extconf.rb +18 -0
- data/ext/rbkit_message_aggregator.c +66 -0
- data/ext/rbkit_message_aggregator.h +13 -0
- data/ext/rbkit_object_graph.c +154 -0
- data/ext/rbkit_object_graph.h +39 -0
- data/ext/rbkit_tracer.c +583 -0
- data/ext/rbkit_tracer.h +35 -0
- data/lib/rbkit.rb +101 -0
- data/lib/rbkit/timer.rb +18 -0
- data/lib/rbkit/version.rb +3 -0
- data/rbkit.gemspec +33 -0
- data/schema/tracing_info.md +8 -0
- data/spec/spec_helper.rb +15 -0
- metadata +120 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require "zmq"
|
2
|
+
require "pp"
|
3
|
+
require "thread"
|
4
|
+
|
5
|
+
$ctx = ZMQ::Context.new
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
ENDPOINT = "tcp://127.0.0.1:5555"
|
9
|
+
|
10
|
+
class Server
|
11
|
+
@@publish_topic = "foo"
|
12
|
+
|
13
|
+
def start_publisher
|
14
|
+
server_socket = $ctx.socket(:PUB)
|
15
|
+
# server_socket.verbose = true
|
16
|
+
server_socket.bind(ENDPOINT)
|
17
|
+
server_socket.linger = 0
|
18
|
+
loop do
|
19
|
+
server_socket.sendm(@@publish_topic)
|
20
|
+
server_socket.send("hello #{Time.now}")
|
21
|
+
|
22
|
+
server_socket.sendm("lol")
|
23
|
+
server_socket.send("for lol #{Time.now}")
|
24
|
+
sleep(1)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def start_listener
|
29
|
+
listener_socket = $ctx.socket(:SUB)
|
30
|
+
listener_socket.subscribe("bar")
|
31
|
+
listener_socket.verbose = true
|
32
|
+
listener_socket.connect(ENDPOINT)
|
33
|
+
|
34
|
+
loop do
|
35
|
+
data = listener_socket.recv
|
36
|
+
puts "********** Received from client #{data}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
server = Server.new()
|
42
|
+
threads = []
|
43
|
+
|
44
|
+
threads << Thread.new do
|
45
|
+
server.start_publisher
|
46
|
+
end
|
47
|
+
|
48
|
+
threads << Thread.new do
|
49
|
+
server.start_listener
|
50
|
+
end
|
51
|
+
|
52
|
+
threads.each { |thr| thr.join }
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
if ENV['RBKIT_DEV']
|
3
|
+
$stderr.puts "Dev environment enabled."
|
4
|
+
$CFLAGS << ' -g'
|
5
|
+
$defs << '-DRBKIT_DEV'
|
6
|
+
end
|
7
|
+
if(have_func('rb_postponed_job_register_one') &&
|
8
|
+
have_func('rb_profile_frames') &&
|
9
|
+
have_func('rb_tracepoint_new') &&
|
10
|
+
have_const('RUBY_INTERNAL_EVENT_NEWOBJ') &&
|
11
|
+
have_library("zmq") &&
|
12
|
+
have_header("zmq.h") &&
|
13
|
+
have_library("msgpack") &&
|
14
|
+
have_header("msgpack.h"))
|
15
|
+
create_makefile('rbkit_tracer')
|
16
|
+
else
|
17
|
+
fail 'missing API: are you using ruby 2.1+?'
|
18
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#include <assert.h>
|
2
|
+
#include "zmq.h"
|
3
|
+
#include "rbkit_message_aggregator.h"
|
4
|
+
|
5
|
+
static msgpack_sbuffer * sbuf;
|
6
|
+
static void* message_array;
|
7
|
+
static size_t used_memsize;
|
8
|
+
static size_t total_capacity;
|
9
|
+
static size_t no_of_messages;
|
10
|
+
|
11
|
+
static int has_enough_space_for(size_t size) {
|
12
|
+
return ((total_capacity - used_memsize) >= size);
|
13
|
+
}
|
14
|
+
|
15
|
+
static void double_the_capacity() {
|
16
|
+
total_capacity *= 2;
|
17
|
+
message_array = realloc(message_array, total_capacity);
|
18
|
+
assert(message_array);
|
19
|
+
}
|
20
|
+
|
21
|
+
void message_list_new() {
|
22
|
+
size_t initial_size = 1024; // Reserve 1 KB of memory
|
23
|
+
message_array = malloc(initial_size);
|
24
|
+
total_capacity = initial_size;
|
25
|
+
used_memsize = 0;
|
26
|
+
no_of_messages = 0;
|
27
|
+
}
|
28
|
+
|
29
|
+
void message_list_destroy() {
|
30
|
+
free(message_array);
|
31
|
+
used_memsize = 0;
|
32
|
+
total_capacity = 0;
|
33
|
+
no_of_messages = 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
void message_list_clear() {
|
37
|
+
used_memsize = 0;
|
38
|
+
no_of_messages = 0;
|
39
|
+
}
|
40
|
+
|
41
|
+
// Copies the msgpack sbuffer to the end of
|
42
|
+
// a dynamically growing array
|
43
|
+
void add_message(msgpack_sbuffer *buffer) {
|
44
|
+
while(!has_enough_space_for(buffer->size))
|
45
|
+
double_the_capacity();
|
46
|
+
memcpy(message_array + used_memsize, buffer->data, buffer->size);
|
47
|
+
used_memsize += buffer->size;
|
48
|
+
no_of_messages += 1;
|
49
|
+
}
|
50
|
+
|
51
|
+
// Creates a message containing all the available
|
52
|
+
// msgpack sbuffers in the array
|
53
|
+
void get_event_collection_message(msgpack_sbuffer *sbuf) {
|
54
|
+
if(no_of_messages > 0) {
|
55
|
+
msgpack_packer *pk = msgpack_packer_new(sbuf, msgpack_sbuffer_write);
|
56
|
+
pack_event_header(pk, "event_collection", 3);
|
57
|
+
pack_string(pk, "payload");
|
58
|
+
msgpack_pack_array(pk, no_of_messages);
|
59
|
+
sbuf->data = realloc(sbuf->data, used_memsize + sbuf->size);
|
60
|
+
assert(sbuf->data);
|
61
|
+
memcpy(sbuf->data + sbuf->size, message_array, used_memsize);
|
62
|
+
sbuf->size += used_memsize;
|
63
|
+
|
64
|
+
msgpack_packer_free(pk);
|
65
|
+
}
|
66
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#ifndef RBKIT_MESSAGE_AGGREGATOR_H
|
2
|
+
#define RBKIT_MESSAGE_AGGREGATOR_H
|
3
|
+
|
4
|
+
#include "msgpack.h"
|
5
|
+
#include "zmq.h"
|
6
|
+
|
7
|
+
void message_list_new();
|
8
|
+
void add_message(msgpack_sbuffer *);
|
9
|
+
void get_event_collection_message(msgpack_sbuffer *);
|
10
|
+
void message_list_destroy();
|
11
|
+
void message_list_clear();
|
12
|
+
|
13
|
+
#endif
|
@@ -0,0 +1,154 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include "rbkit_object_graph.h"
|
3
|
+
|
4
|
+
rbkit_object_dump_page * rbkit_object_dump_page_new() {
|
5
|
+
rbkit_object_dump_page *page = malloc(sizeof(rbkit_object_dump_page));
|
6
|
+
page->count = 0;
|
7
|
+
page->next = NULL;
|
8
|
+
return page;
|
9
|
+
}
|
10
|
+
|
11
|
+
rbkit_object_data * initialize_object_data(rbkit_object_dump *dump) {
|
12
|
+
if(dump->first == NULL) {
|
13
|
+
rbkit_object_dump_page *page = rbkit_object_dump_page_new();
|
14
|
+
dump->first = page;
|
15
|
+
dump->last = page;
|
16
|
+
dump->page_count = 1;
|
17
|
+
dump->object_count = 0;
|
18
|
+
} else if (dump->last->count == RBKIT_OBJECT_DUMP_PAGE_SIZE) {
|
19
|
+
rbkit_object_dump_page *page = rbkit_object_dump_page_new();
|
20
|
+
dump->last->next = page;
|
21
|
+
dump->last = page;
|
22
|
+
dump->page_count++;
|
23
|
+
}
|
24
|
+
rbkit_object_data *data = &(dump->last->data[dump->last->count]);
|
25
|
+
data->references = NULL;
|
26
|
+
data->class_name = NULL;
|
27
|
+
data->reference_count = 0;
|
28
|
+
data->file = NULL;
|
29
|
+
data->line = 0;
|
30
|
+
data->size = 0;
|
31
|
+
return(data);
|
32
|
+
}
|
33
|
+
|
34
|
+
static void set_size(VALUE obj, rbkit_object_data * data) {
|
35
|
+
size_t size;
|
36
|
+
if ((size = rb_obj_memsize_of(obj)) > 0)
|
37
|
+
data->size = size;
|
38
|
+
}
|
39
|
+
|
40
|
+
static void dump_root_object(VALUE obj, const char* category, rbkit_object_dump *dump) {
|
41
|
+
rbkit_object_data *data = initialize_object_data(dump);
|
42
|
+
|
43
|
+
//Set object id
|
44
|
+
data->object_id = (void *)obj;
|
45
|
+
//Set classname
|
46
|
+
data->class_name = NULL;
|
47
|
+
//Set classname of "symbols" category as "Symbol" to match <symbol>.class output
|
48
|
+
if(strcmp(category, "symbols") == 0){
|
49
|
+
data->class_name = "Symbol" ;
|
50
|
+
} else {
|
51
|
+
data->class_name = category ;
|
52
|
+
}
|
53
|
+
|
54
|
+
//Set file path and line no where object is defined
|
55
|
+
struct allocation_info *info;
|
56
|
+
if (st_lookup(dump->object_table, obj, (st_data_t *)&info)) {
|
57
|
+
if(info) {
|
58
|
+
data->file = info->path;
|
59
|
+
data->line = info->line;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
set_size(obj, data);
|
64
|
+
// Update total number of objects
|
65
|
+
dump->object_count++;
|
66
|
+
// Update number of objects on last page
|
67
|
+
dump->last->count++;
|
68
|
+
}
|
69
|
+
|
70
|
+
static void reachable_object_i(VALUE ref, rbkit_object_data *data)
|
71
|
+
{
|
72
|
+
if(RBASIC_CLASS(ref) == ref)
|
73
|
+
return;
|
74
|
+
|
75
|
+
data->reference_count ++;
|
76
|
+
if (data->reference_count == 1) {
|
77
|
+
data->references = malloc(sizeof(void *));
|
78
|
+
} else {
|
79
|
+
data->references = realloc(data->references, data->reference_count * sizeof(void *) );
|
80
|
+
}
|
81
|
+
data->references[data->reference_count - 1] = (void *)ref;
|
82
|
+
}
|
83
|
+
|
84
|
+
static void dump_heap_object(VALUE obj, rbkit_object_dump *dump) {
|
85
|
+
|
86
|
+
// Get next available slot from page
|
87
|
+
rbkit_object_data *data = initialize_object_data(dump);
|
88
|
+
//Set object id
|
89
|
+
data->object_id = (void *)obj;
|
90
|
+
|
91
|
+
//Set classname
|
92
|
+
VALUE klass = RBASIC_CLASS(obj);
|
93
|
+
data->class_name = rb_class2name(klass);
|
94
|
+
|
95
|
+
//Set references
|
96
|
+
rb_objspace_reachable_objects_from(obj, reachable_object_i, data);
|
97
|
+
|
98
|
+
//Set file path and line no where object is defined
|
99
|
+
struct allocation_info *info;
|
100
|
+
if (st_lookup(dump->object_table, obj, (st_data_t *)&info)) {
|
101
|
+
if(info) {
|
102
|
+
data->file = info->path;
|
103
|
+
data->line = info->line;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
set_size(obj, data);
|
108
|
+
dump->object_count++;
|
109
|
+
dump->last->count++;
|
110
|
+
}
|
111
|
+
|
112
|
+
/*
|
113
|
+
* The following categories of objects are directly accessible from the root:
|
114
|
+
* ["vm", "machine_context", "symbols", "global_list", "end_proc", "global_tbl"]
|
115
|
+
*/
|
116
|
+
static void root_object_i(const char *category, VALUE obj, void *dump_data)
|
117
|
+
{
|
118
|
+
dump_root_object(obj, category, (rbkit_object_dump *)dump_data);
|
119
|
+
}
|
120
|
+
|
121
|
+
/*
|
122
|
+
* Iterator that walks over heap pages
|
123
|
+
*/
|
124
|
+
static int heap_obj_i(void *vstart, void *vend, size_t stride, void *dump_data)
|
125
|
+
{
|
126
|
+
VALUE obj = (VALUE)vstart;
|
127
|
+
VALUE klass ;
|
128
|
+
|
129
|
+
for (; obj != (VALUE)vend; obj += stride) {
|
130
|
+
klass = RBASIC_CLASS(obj);
|
131
|
+
if (!NIL_P(klass) && BUILTIN_TYPE(obj) != T_NONE && BUILTIN_TYPE(obj) != T_ZOMBIE && BUILTIN_TYPE(obj) != T_ICLASS) {
|
132
|
+
dump_heap_object(obj, (rbkit_object_dump *)dump_data);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
return 0;
|
136
|
+
}
|
137
|
+
|
138
|
+
static void collect_root_objects(rbkit_object_dump * dump) {
|
139
|
+
rb_objspace_reachable_objects_from_root(root_object_i, (void *)dump);
|
140
|
+
}
|
141
|
+
|
142
|
+
static void collect_heap_objects(rbkit_object_dump * dump) {
|
143
|
+
rb_objspace_each_objects(heap_obj_i, (void *)dump);
|
144
|
+
}
|
145
|
+
|
146
|
+
rbkit_object_dump * get_object_dump(st_table * object_table) {
|
147
|
+
rbkit_object_dump * dump = (rbkit_object_dump *) malloc(sizeof(rbkit_object_dump));
|
148
|
+
dump->object_table = object_table;
|
149
|
+
dump->first = NULL;
|
150
|
+
dump->last = NULL;
|
151
|
+
collect_root_objects(dump);
|
152
|
+
collect_heap_objects(dump);
|
153
|
+
return dump;
|
154
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#ifndef RBKIT_OBJECT_GRAPH
|
2
|
+
#define RBKIT_OBJECT_GRAPH
|
3
|
+
|
4
|
+
#define RBKIT_OBJECT_DUMP_PAGE_SIZE 1000
|
5
|
+
|
6
|
+
typedef struct _rbkit_object_data {
|
7
|
+
const void * object_id;
|
8
|
+
const char * class_name;
|
9
|
+
void ** references;
|
10
|
+
size_t reference_count;
|
11
|
+
char * file;
|
12
|
+
unsigned long line;
|
13
|
+
size_t size;
|
14
|
+
} rbkit_object_data;
|
15
|
+
|
16
|
+
typedef struct _rbkit_object_dump_page {
|
17
|
+
rbkit_object_data data[RBKIT_OBJECT_DUMP_PAGE_SIZE];
|
18
|
+
size_t count;
|
19
|
+
struct _rbkit_object_dump_page *next;
|
20
|
+
} rbkit_object_dump_page;
|
21
|
+
|
22
|
+
typedef struct _rbkit_object_dump {
|
23
|
+
size_t page_count;
|
24
|
+
size_t object_count;
|
25
|
+
rbkit_object_dump_page *first;
|
26
|
+
rbkit_object_dump_page *last;
|
27
|
+
st_table * object_table;
|
28
|
+
} rbkit_object_dump;
|
29
|
+
|
30
|
+
rbkit_object_dump * get_object_dump();
|
31
|
+
|
32
|
+
struct allocation_info {
|
33
|
+
const char *path;
|
34
|
+
unsigned long line;
|
35
|
+
const char *class_path;
|
36
|
+
VALUE method_id;
|
37
|
+
size_t generation;
|
38
|
+
};
|
39
|
+
#endif
|
data/ext/rbkit_tracer.c
ADDED
@@ -0,0 +1,583 @@
|
|
1
|
+
//
|
2
|
+
// rbkit.c
|
3
|
+
// gather_stats
|
4
|
+
//
|
5
|
+
// Created by Hemant Kumar on 26/04/14.
|
6
|
+
// Copyright (c) 2014 Codemancers. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#include "rbkit_tracer.h"
|
10
|
+
#include "rbkit_object_graph.h"
|
11
|
+
#include "rbkit_message_aggregator.h"
|
12
|
+
#include <sys/time.h>
|
13
|
+
|
14
|
+
static const char *event_names[] = {
|
15
|
+
"gc_start",
|
16
|
+
"gc_end_m",
|
17
|
+
"gc_end_s",
|
18
|
+
"obj_created",
|
19
|
+
"obj_destroyed"
|
20
|
+
};
|
21
|
+
|
22
|
+
static struct gc_hooks *logger;
|
23
|
+
static int tmp_keep_remains;
|
24
|
+
static void *zmq_publisher;
|
25
|
+
static void *zmq_context;
|
26
|
+
static void *zmq_response_socket;
|
27
|
+
static zmq_pollitem_t items[1];
|
28
|
+
|
29
|
+
void pack_event_header(msgpack_packer* packer, const char *event_type, int map_size)
|
30
|
+
{
|
31
|
+
msgpack_pack_map(packer, map_size);
|
32
|
+
pack_string(packer, "event_type");
|
33
|
+
pack_string(packer, event_type);
|
34
|
+
|
35
|
+
pack_string(packer, "timestamp");
|
36
|
+
pack_timestamp(packer);
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
static void trace_gc_invocation(void *data, int event_index) {
|
41
|
+
if (event_index == 0) {
|
42
|
+
msgpack_sbuffer_clear(logger->sbuf);
|
43
|
+
pack_event_header(logger->msgpacker, event_names[event_index], 2);
|
44
|
+
add_message(logger->sbuf);
|
45
|
+
} else if (event_index == 2) {
|
46
|
+
msgpack_sbuffer_clear(logger->sbuf);
|
47
|
+
pack_event_header(logger->msgpacker, event_names[event_index], 2);
|
48
|
+
add_message(logger->sbuf);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
static struct gc_hooks * get_trace_logger() {
|
53
|
+
int i = 0;
|
54
|
+
if (logger == 0) {
|
55
|
+
logger = ALLOC_N(struct gc_hooks, 1);
|
56
|
+
logger->enabled = Qfalse;
|
57
|
+
logger->newobj_trace = 0;
|
58
|
+
logger->freeobj_trace = 0;
|
59
|
+
logger->keep_remains = tmp_keep_remains;
|
60
|
+
logger->object_table = st_init_numtable();
|
61
|
+
logger->str_table = st_init_strtable();
|
62
|
+
|
63
|
+
for (i = 0; i < 3; i++) {
|
64
|
+
logger->funcs[i] = trace_gc_invocation;
|
65
|
+
logger->args[i] = (void *)event_names[i];
|
66
|
+
}
|
67
|
+
logger->sbuf = msgpack_sbuffer_new();
|
68
|
+
logger->msgpacker = msgpack_packer_new(logger->sbuf, msgpack_sbuffer_write);
|
69
|
+
logger->data = 0;
|
70
|
+
}
|
71
|
+
return logger;
|
72
|
+
}
|
73
|
+
|
74
|
+
|
75
|
+
static void
|
76
|
+
gc_start_i(VALUE tpval, void *data)
|
77
|
+
{
|
78
|
+
struct gc_hooks *hooks = (struct gc_hooks *)data;
|
79
|
+
(*hooks->funcs[0])(hooks->args[0], 0);
|
80
|
+
}
|
81
|
+
|
82
|
+
static void
|
83
|
+
gc_end_mark_i(VALUE tpval, void *data)
|
84
|
+
{
|
85
|
+
struct gc_hooks *hooks = (struct gc_hooks *)data;
|
86
|
+
(*hooks->funcs[1])(hooks->args[1], 1);
|
87
|
+
}
|
88
|
+
|
89
|
+
static void
|
90
|
+
gc_end_sweep_i(VALUE tpval, void *data)
|
91
|
+
{
|
92
|
+
struct gc_hooks *hooks = (struct gc_hooks *)data;
|
93
|
+
(*hooks->funcs[2])(hooks->args[2], 2);
|
94
|
+
}
|
95
|
+
|
96
|
+
static void
|
97
|
+
create_gc_hooks(void)
|
98
|
+
{
|
99
|
+
int i;
|
100
|
+
logger->hooks[0] =
|
101
|
+
rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_GC_START, gc_start_i, logger);
|
102
|
+
logger->hooks[1] =
|
103
|
+
rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_GC_END_MARK, gc_end_mark_i, logger);
|
104
|
+
logger->hooks[2] =
|
105
|
+
rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_GC_END_SWEEP, gc_end_sweep_i, logger);
|
106
|
+
/* mark for GC */
|
107
|
+
for (i=0; i<3; i++) rb_gc_register_mark_object(logger->hooks[i]);
|
108
|
+
}
|
109
|
+
|
110
|
+
void pack_pointer(msgpack_packer *packer, VALUE object_id) {
|
111
|
+
char *object_string;
|
112
|
+
asprintf(&object_string, "%p", object_id);
|
113
|
+
pack_string(packer, object_string);
|
114
|
+
free(object_string);
|
115
|
+
}
|
116
|
+
/*
|
117
|
+
* make_unique_str helps to reuse memory by allocating memory for a string
|
118
|
+
* only once and keeping track of how many times that string is referenced.
|
119
|
+
* It does so by creating a map of strings to their no of references.
|
120
|
+
* A new map is created for a string on its first use, and for further usages
|
121
|
+
* the reference count is incremented.
|
122
|
+
*/
|
123
|
+
static const char * make_unique_str(st_table *tbl, const char *str, long len) {
|
124
|
+
if (!str) {
|
125
|
+
return NULL;
|
126
|
+
}
|
127
|
+
else {
|
128
|
+
st_data_t n;
|
129
|
+
char *result;
|
130
|
+
|
131
|
+
if (st_lookup(tbl, (st_data_t)str, &n)) {
|
132
|
+
st_insert(tbl, (st_data_t)str, n+1);
|
133
|
+
st_get_key(tbl, (st_data_t)str, (st_data_t *)&result);
|
134
|
+
}
|
135
|
+
else {
|
136
|
+
result = (char *)ruby_xmalloc(len+1);
|
137
|
+
strncpy(result, str, len);
|
138
|
+
result[len] = 0;
|
139
|
+
st_add_direct(tbl, (st_data_t)result, 1);
|
140
|
+
}
|
141
|
+
return result;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
/*
|
146
|
+
* Used to free allocation of string when it's not referenced anymore.
|
147
|
+
* Decrements the reference count of a string if it's still used, else
|
148
|
+
* the map is removed completely.
|
149
|
+
*/
|
150
|
+
static void delete_unique_str(st_table *tbl, const char *str) {
|
151
|
+
if (str) {
|
152
|
+
st_data_t n;
|
153
|
+
|
154
|
+
st_lookup(tbl, (st_data_t)str, &n);
|
155
|
+
if (n == 1) {
|
156
|
+
st_delete(tbl, (st_data_t *)&str, 0);
|
157
|
+
ruby_xfree((char *)str);
|
158
|
+
}
|
159
|
+
else {
|
160
|
+
st_insert(tbl, (st_data_t)str, n-1);
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
// Refer Ruby source ext/objspace/object_tracing.c::newobj_i
|
166
|
+
static void newobj_i(VALUE tpval, void *data) {
|
167
|
+
struct gc_hooks * arg = (struct gc_hooks *)data;
|
168
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
169
|
+
VALUE obj = rb_tracearg_object(tparg);
|
170
|
+
VALUE klass = RBASIC_CLASS(obj);
|
171
|
+
VALUE path = rb_tracearg_path(tparg);
|
172
|
+
VALUE line = rb_tracearg_lineno(tparg);
|
173
|
+
VALUE method_id = rb_tracearg_method_id(tparg);
|
174
|
+
VALUE defined_klass = rb_tracearg_defined_class(tparg);
|
175
|
+
|
176
|
+
struct allocation_info *info;
|
177
|
+
const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : 0;
|
178
|
+
VALUE class_path = (RTEST(defined_klass) && !OBJ_FROZEN(defined_klass)) ? rb_class_path_cached(defined_klass) : Qnil;
|
179
|
+
const char *class_path_cstr = RTEST(class_path) ? make_unique_str(arg->str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0;
|
180
|
+
|
181
|
+
if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {
|
182
|
+
/* reuse info */
|
183
|
+
delete_unique_str(arg->str_table, info->path);
|
184
|
+
delete_unique_str(arg->str_table, info->class_path);
|
185
|
+
}
|
186
|
+
else {
|
187
|
+
info = (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info));
|
188
|
+
}
|
189
|
+
|
190
|
+
info->path = path_cstr;
|
191
|
+
info->line = NUM2INT(line);
|
192
|
+
info->method_id = method_id;
|
193
|
+
info->class_path = class_path_cstr;
|
194
|
+
info->generation = rb_gc_count();
|
195
|
+
st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info);
|
196
|
+
|
197
|
+
msgpack_sbuffer_clear(arg->sbuf);
|
198
|
+
pack_event_header(arg->msgpacker, event_names[3], 3);
|
199
|
+
pack_string(arg->msgpacker, "payload");
|
200
|
+
msgpack_pack_map(arg->msgpacker, 2);
|
201
|
+
pack_string(arg->msgpacker, "object_id");
|
202
|
+
pack_pointer(arg->msgpacker, obj);
|
203
|
+
pack_string(arg->msgpacker, "class");
|
204
|
+
if (!NIL_P(klass) && BUILTIN_TYPE(obj) != T_NONE && BUILTIN_TYPE(obj) != T_ZOMBIE && BUILTIN_TYPE(obj) != T_ICLASS) {
|
205
|
+
pack_string(arg->msgpacker, rb_class2name(klass));
|
206
|
+
|
207
|
+
} else {
|
208
|
+
msgpack_pack_nil(arg->msgpacker);
|
209
|
+
}
|
210
|
+
add_message(arg->sbuf);
|
211
|
+
}
|
212
|
+
|
213
|
+
// Refer Ruby source ext/objspace/object_tracing.c::freeobj_i
|
214
|
+
static void freeobj_i(VALUE tpval, void *data) {
|
215
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
216
|
+
VALUE obj = rb_tracearg_object(tparg);
|
217
|
+
|
218
|
+
struct gc_hooks *arg = (struct gc_hooks *)data;
|
219
|
+
struct allocation_info *info;
|
220
|
+
|
221
|
+
if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {
|
222
|
+
st_delete(arg->object_table, (st_data_t *)&obj, (st_data_t *)&info);
|
223
|
+
delete_unique_str(arg->str_table, info->path);
|
224
|
+
delete_unique_str(arg->str_table, info->class_path);
|
225
|
+
ruby_xfree(info);
|
226
|
+
}
|
227
|
+
|
228
|
+
msgpack_sbuffer_clear(logger->sbuf);
|
229
|
+
pack_event_header(logger->msgpacker, event_names[4], 3);
|
230
|
+
pack_string(logger->msgpacker, "payload");
|
231
|
+
msgpack_pack_map(logger->msgpacker, 1);
|
232
|
+
pack_string(logger->msgpacker, "object_id");
|
233
|
+
pack_pointer(logger->msgpacker, obj);
|
234
|
+
add_message(logger->sbuf);
|
235
|
+
}
|
236
|
+
|
237
|
+
static VALUE start_stat_server(int argc, VALUE *argv, VALUE self) {
|
238
|
+
int default_pub_port = 5555;
|
239
|
+
int default_request_port = 5556;
|
240
|
+
VALUE pub_port;
|
241
|
+
VALUE request_port;
|
242
|
+
int bind_result;
|
243
|
+
|
244
|
+
rb_scan_args(argc, argv, "02", &pub_port, &request_port);
|
245
|
+
if (!NIL_P(pub_port)) {
|
246
|
+
default_pub_port = FIX2INT(pub_port);
|
247
|
+
if (default_pub_port < 1024 || default_pub_port > 65000)
|
248
|
+
rb_raise(rb_eArgError, "invalid port value");
|
249
|
+
}
|
250
|
+
|
251
|
+
if (!NIL_P(request_port)) {
|
252
|
+
default_request_port = FIX2INT(request_port);
|
253
|
+
if(default_request_port < 1024 || default_request_port > 65000)
|
254
|
+
rb_raise(rb_eArgError, "invalid port value");
|
255
|
+
}
|
256
|
+
|
257
|
+
// Creates a list which aggregates messages
|
258
|
+
message_list_new();
|
259
|
+
logger = get_trace_logger();
|
260
|
+
logger->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, logger);
|
261
|
+
logger->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, logger);
|
262
|
+
rb_gc_register_mark_object(logger->newobj_trace);
|
263
|
+
rb_gc_register_mark_object(logger->freeobj_trace);
|
264
|
+
create_gc_hooks();
|
265
|
+
|
266
|
+
char zmq_endpoint[14];
|
267
|
+
sprintf(zmq_endpoint, "tcp://*:%d", default_pub_port);
|
268
|
+
|
269
|
+
zmq_context = zmq_ctx_new();
|
270
|
+
zmq_publisher = zmq_socket(zmq_context, ZMQ_PUB);
|
271
|
+
bind_result = zmq_bind(zmq_publisher, zmq_endpoint);
|
272
|
+
assert(bind_result == 0);
|
273
|
+
|
274
|
+
char zmq_request_endpoint[14];
|
275
|
+
sprintf(zmq_request_endpoint, "tcp://*:%d", default_request_port);
|
276
|
+
|
277
|
+
zmq_response_socket = zmq_socket(zmq_context, ZMQ_REP);
|
278
|
+
bind_result = zmq_bind(zmq_response_socket, zmq_request_endpoint);
|
279
|
+
assert(bind_result == 0);
|
280
|
+
|
281
|
+
items[0].socket = zmq_response_socket;
|
282
|
+
items[0].events = ZMQ_POLLIN;
|
283
|
+
return Qnil;
|
284
|
+
}
|
285
|
+
|
286
|
+
char * tracer_string_recv(void *socket) {
|
287
|
+
zmq_msg_t msg;
|
288
|
+
int rc = zmq_msg_init(&msg);
|
289
|
+
assert(rc == 0);
|
290
|
+
|
291
|
+
rc = zmq_msg_recv(&msg, socket, 0);
|
292
|
+
assert(rc != -1);
|
293
|
+
size_t message_size = zmq_msg_size(&msg);
|
294
|
+
char *message = (char *)malloc(message_size +1);
|
295
|
+
memcpy(message, zmq_msg_data(&msg), message_size);
|
296
|
+
message[message_size] = 0;
|
297
|
+
zmq_msg_close(&msg);
|
298
|
+
return message;
|
299
|
+
}
|
300
|
+
|
301
|
+
|
302
|
+
int tracer_string_send(void *socket, const char *message) {
|
303
|
+
int size = zmq_send (socket, message, strlen (message), 0);
|
304
|
+
return size;
|
305
|
+
}
|
306
|
+
|
307
|
+
static VALUE poll_for_request() {
|
308
|
+
// Wait for 100 millisecond and check if there is a message
|
309
|
+
// we can't wait here indefenitely because ruby is not aware this is a
|
310
|
+
// blocking operation. Remember ruby releases GVL in a thread
|
311
|
+
// whenever it encounters a known blocking operation.
|
312
|
+
zmq_poll(items, 1, 100);
|
313
|
+
if (items[0].revents && ZMQ_POLLIN) {
|
314
|
+
char *message = tracer_string_recv(zmq_response_socket);
|
315
|
+
tracer_string_send(zmq_response_socket, "ok");
|
316
|
+
VALUE command_ruby_string = rb_str_new_cstr(message);
|
317
|
+
free(message);
|
318
|
+
return command_ruby_string;
|
319
|
+
} else {
|
320
|
+
return Qnil;
|
321
|
+
}
|
322
|
+
}
|
323
|
+
|
324
|
+
static VALUE stop_stat_tracing() {
|
325
|
+
if (logger->hooks[0] != 0) {
|
326
|
+
rb_tracepoint_disable(logger->hooks[0]);
|
327
|
+
rb_tracepoint_disable(logger->hooks[1]);
|
328
|
+
rb_tracepoint_disable(logger->hooks[2]);
|
329
|
+
}
|
330
|
+
|
331
|
+
if (logger->newobj_trace) {
|
332
|
+
rb_tracepoint_disable(logger->newobj_trace);
|
333
|
+
rb_tracepoint_disable(logger->freeobj_trace);
|
334
|
+
}
|
335
|
+
logger->enabled = Qfalse;
|
336
|
+
return Qnil;
|
337
|
+
}
|
338
|
+
|
339
|
+
|
340
|
+
static int free_keys_i(st_data_t key, st_data_t value, void *data) {
|
341
|
+
ruby_xfree((void *)key);
|
342
|
+
return ST_CONTINUE;
|
343
|
+
}
|
344
|
+
|
345
|
+
static int free_values_i(st_data_t key, st_data_t value, void *data) {
|
346
|
+
ruby_xfree((void *)value);
|
347
|
+
return ST_CONTINUE;
|
348
|
+
}
|
349
|
+
|
350
|
+
static VALUE stop_stat_server() {
|
351
|
+
if (logger->enabled == Qtrue)
|
352
|
+
stop_stat_tracing();
|
353
|
+
|
354
|
+
// Destroy the list which aggregates messages
|
355
|
+
message_list_destroy();
|
356
|
+
// Clear object_table which holds object allocation info
|
357
|
+
st_foreach(logger->object_table, free_values_i, 0);
|
358
|
+
st_clear(logger->object_table);
|
359
|
+
st_foreach(logger->str_table, free_keys_i, 0);
|
360
|
+
st_clear(logger->str_table);
|
361
|
+
|
362
|
+
msgpack_sbuffer_free(logger->sbuf);
|
363
|
+
msgpack_packer_free(logger->msgpacker);
|
364
|
+
zmq_close(zmq_publisher);
|
365
|
+
zmq_close(zmq_response_socket);
|
366
|
+
zmq_ctx_destroy(zmq_context);
|
367
|
+
free(logger);
|
368
|
+
return Qnil;
|
369
|
+
}
|
370
|
+
|
371
|
+
void pack_value_object(msgpack_packer *packer, VALUE value) {
|
372
|
+
switch (TYPE(value)) {
|
373
|
+
case T_FIXNUM:
|
374
|
+
msgpack_pack_long(packer, FIX2LONG(value));
|
375
|
+
break;
|
376
|
+
case T_FLOAT:
|
377
|
+
msgpack_pack_double(packer, rb_num2dbl(value));
|
378
|
+
break;
|
379
|
+
default:
|
380
|
+
;
|
381
|
+
VALUE rubyString = rb_funcall(value, rb_intern("to_s"), 0, 0);
|
382
|
+
char *keyString = StringValueCStr(rubyString);
|
383
|
+
pack_string(packer, keyString);
|
384
|
+
break;
|
385
|
+
}
|
386
|
+
}
|
387
|
+
|
388
|
+
static int hash_iterator(VALUE key, VALUE value, VALUE hash_arg) {
|
389
|
+
msgpack_packer *packer = (msgpack_packer *)hash_arg;
|
390
|
+
|
391
|
+
// pack the key
|
392
|
+
pack_value_object(packer,key);
|
393
|
+
// pack the value
|
394
|
+
pack_value_object(packer, value);
|
395
|
+
return ST_CONTINUE;
|
396
|
+
}
|
397
|
+
|
398
|
+
|
399
|
+
void pack_string(msgpack_packer *packer, char *string) {
|
400
|
+
if(string == NULL) {
|
401
|
+
msgpack_pack_nil(packer);
|
402
|
+
} else {
|
403
|
+
int length = strlen(string);
|
404
|
+
msgpack_pack_raw(packer, length);
|
405
|
+
msgpack_pack_raw_body(packer, string, length);
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
409
|
+
void pack_timestamp(msgpack_packer *packer) {
|
410
|
+
struct timeval tv;
|
411
|
+
gettimeofday(&tv, NULL);
|
412
|
+
|
413
|
+
double time_in_milliseconds = (tv.tv_sec)*1000 + (tv.tv_usec)/1000;
|
414
|
+
msgpack_pack_double(packer, time_in_milliseconds);
|
415
|
+
}
|
416
|
+
|
417
|
+
static VALUE send_hash_as_event(int argc, VALUE *argv, VALUE self) {
|
418
|
+
VALUE hash_object;
|
419
|
+
VALUE event_name;
|
420
|
+
|
421
|
+
rb_scan_args(argc, argv, "20", &hash_object, &event_name);
|
422
|
+
|
423
|
+
int size = RHASH_SIZE(hash_object);
|
424
|
+
msgpack_sbuffer *buffer = msgpack_sbuffer_new();
|
425
|
+
msgpack_packer *packer = msgpack_packer_new(buffer, msgpack_sbuffer_write);
|
426
|
+
pack_event_header(packer, StringValueCStr(event_name), 3);
|
427
|
+
|
428
|
+
pack_string(packer, "payload");
|
429
|
+
msgpack_pack_map(packer, size);
|
430
|
+
|
431
|
+
rb_hash_foreach(hash_object, hash_iterator, (VALUE)packer);
|
432
|
+
add_message(buffer);
|
433
|
+
msgpack_sbuffer_free(buffer);
|
434
|
+
msgpack_packer_free(packer);
|
435
|
+
return Qnil;
|
436
|
+
}
|
437
|
+
|
438
|
+
static VALUE start_stat_tracing() {
|
439
|
+
if (logger->enabled == Qtrue)
|
440
|
+
return Qnil;
|
441
|
+
rb_tracepoint_enable(logger->newobj_trace);
|
442
|
+
rb_tracepoint_enable(logger->freeobj_trace);
|
443
|
+
int i = 0;
|
444
|
+
for (i=0; i<3; i++) {
|
445
|
+
rb_tracepoint_enable(logger->hooks[i]);
|
446
|
+
}
|
447
|
+
logger->enabled = Qtrue;
|
448
|
+
return Qnil;
|
449
|
+
}
|
450
|
+
|
451
|
+
static VALUE send_objectspace_dump() {
|
452
|
+
msgpack_sbuffer* buffer = msgpack_sbuffer_new();
|
453
|
+
msgpack_packer* pk = msgpack_packer_new(buffer, msgpack_sbuffer_write);
|
454
|
+
|
455
|
+
rbkit_object_dump * dump = get_object_dump(logger->object_table);
|
456
|
+
pack_event_header(pk, "object_space_dump", 3);
|
457
|
+
pack_string(pk, "payload");
|
458
|
+
// Set size of array to hold all objects
|
459
|
+
msgpack_pack_array(pk, dump->object_count);
|
460
|
+
|
461
|
+
// Iterate through all object data
|
462
|
+
rbkit_object_dump_page * page = dump->first ;
|
463
|
+
while(page != NULL) {
|
464
|
+
rbkit_object_data *data;
|
465
|
+
size_t i = 0;
|
466
|
+
for(;i < page->count; i++) {
|
467
|
+
data = &(page->data[i]);
|
468
|
+
/* Object dump is a map that looks like this :
|
469
|
+
* {
|
470
|
+
* object_id: <OBJECT_ID_IN_HEX>,
|
471
|
+
* class: <CLASS_NAME>,
|
472
|
+
* references: [<OBJECT_ID_IN_HEX>, <OBJECT_ID_IN_HEX>, ...],
|
473
|
+
* file: <FILE_PATH>,
|
474
|
+
* line: <LINE_NO>,
|
475
|
+
* size: <SIZE>
|
476
|
+
* }
|
477
|
+
*/
|
478
|
+
|
479
|
+
msgpack_pack_map(pk, 6);
|
480
|
+
|
481
|
+
// Key1 : "object_id"
|
482
|
+
pack_string(pk, "object_id");
|
483
|
+
|
484
|
+
// Value1 : pointer address of object
|
485
|
+
char * object_id;
|
486
|
+
asprintf(&object_id, "%p", data->object_id);
|
487
|
+
pack_string(pk, object_id);
|
488
|
+
free(object_id);
|
489
|
+
|
490
|
+
// Key2 : "class_name"
|
491
|
+
pack_string(pk, "class_name");
|
492
|
+
|
493
|
+
// Value2 : Class name of object
|
494
|
+
if(data->class_name == NULL) {
|
495
|
+
msgpack_pack_nil(pk);
|
496
|
+
} else {
|
497
|
+
pack_string(pk, data->class_name);
|
498
|
+
}
|
499
|
+
|
500
|
+
// Key3 : "references"
|
501
|
+
pack_string(pk, "references");
|
502
|
+
|
503
|
+
// Value3 : References held by the object
|
504
|
+
msgpack_pack_array(pk, data->reference_count);
|
505
|
+
if(data->reference_count != 0) {
|
506
|
+
size_t count = 0;
|
507
|
+
for(; count < data->reference_count; count++ ) {
|
508
|
+
char * object_id;
|
509
|
+
asprintf(&object_id, "%p", data->references[count]);
|
510
|
+
pack_string(pk, object_id);
|
511
|
+
free(object_id);
|
512
|
+
}
|
513
|
+
free(data->references);
|
514
|
+
}
|
515
|
+
|
516
|
+
// Key4 : "file"
|
517
|
+
pack_string(pk, "file");
|
518
|
+
|
519
|
+
// Value4 : File path where object is defined
|
520
|
+
pack_string(pk, data->file);
|
521
|
+
|
522
|
+
// Key5 : "line"
|
523
|
+
pack_string(pk, "line");
|
524
|
+
|
525
|
+
// Value5 : Line no where object is defined
|
526
|
+
if(data->line == 0)
|
527
|
+
msgpack_pack_nil(pk);
|
528
|
+
else
|
529
|
+
msgpack_pack_unsigned_long(pk, data->line);
|
530
|
+
|
531
|
+
// Key6 : "size"
|
532
|
+
pack_string(pk, "size");
|
533
|
+
|
534
|
+
// Value6 : Size of the object in memory
|
535
|
+
if(data->size == 0)
|
536
|
+
msgpack_pack_nil(pk);
|
537
|
+
else
|
538
|
+
msgpack_pack_uint32(pk, data->size);
|
539
|
+
}
|
540
|
+
rbkit_object_dump_page * prev = page;
|
541
|
+
page = page->next;
|
542
|
+
free(prev);
|
543
|
+
}
|
544
|
+
|
545
|
+
// Send packed message over zmq
|
546
|
+
add_message(buffer);
|
547
|
+
|
548
|
+
//Cleanup
|
549
|
+
free(dump);
|
550
|
+
msgpack_sbuffer_free(buffer);
|
551
|
+
msgpack_packer_free(pk);
|
552
|
+
|
553
|
+
return Qnil;
|
554
|
+
}
|
555
|
+
|
556
|
+
/*
|
557
|
+
* Creates a msgpack array which contains all the messages packed after
|
558
|
+
* the last time send_messages() was called, and sends it over the PUB socket.
|
559
|
+
*/
|
560
|
+
static VALUE send_messages() {
|
561
|
+
//Get all aggregated messages as payload of a single event.
|
562
|
+
msgpack_sbuffer * sbuf = msgpack_sbuffer_new();
|
563
|
+
get_event_collection_message(sbuf);
|
564
|
+
//Send the msgpack array over zmq PUB socket
|
565
|
+
if(sbuf && sbuf->size > 0)
|
566
|
+
zmq_send(zmq_publisher, sbuf->data, sbuf->size, 0);
|
567
|
+
// Clear the aggregated messages
|
568
|
+
message_list_clear();
|
569
|
+
msgpack_sbuffer_free(sbuf);
|
570
|
+
return Qnil;
|
571
|
+
}
|
572
|
+
|
573
|
+
void Init_rbkit_tracer(void) {
|
574
|
+
VALUE objectStatsModule = rb_define_module("Rbkit");
|
575
|
+
rb_define_module_function(objectStatsModule, "start_stat_server", start_stat_server, -1);
|
576
|
+
rb_define_module_function(objectStatsModule, "stop_stat_server", stop_stat_server, 0);
|
577
|
+
rb_define_module_function(objectStatsModule, "start_stat_tracing", start_stat_tracing, 0);
|
578
|
+
rb_define_module_function(objectStatsModule, "stop_stat_tracing", stop_stat_tracing, 0);
|
579
|
+
rb_define_module_function(objectStatsModule, "poll_for_request", poll_for_request, 0);
|
580
|
+
rb_define_module_function(objectStatsModule, "send_objectspace_dump", send_objectspace_dump, 0);
|
581
|
+
rb_define_module_function(objectStatsModule, "send_hash_as_event", send_hash_as_event, -1);
|
582
|
+
rb_define_module_function(objectStatsModule, "send_messages", send_messages, 0);
|
583
|
+
}
|