rbkit 0.0.1
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.
- 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
|
+
}
|