stack_tracy 0.1.0

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/bin/tracy ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "stack_tracy"
5
+
6
+ begin
7
+ StackTracy.open ARGV.first
8
+ rescue StackTracy::Error => e
9
+ puts e.message.red
10
+ end
@@ -0,0 +1,4 @@
1
+ # See http://guides.rubygems.org/c-extensions and http://www.rubyinside.com/how-to-create-a-ruby-extension-in-c-in-under-5-minutes-100.html
2
+ require "mkmf"
3
+ dir_config "stack_tracy"
4
+ create_makefile "stack_tracy"
@@ -0,0 +1,216 @@
1
+ #include "stack_tracy.h"
2
+
3
+ static double nsec() {
4
+ #if defined(__linux__)
5
+ struct timespec clock;
6
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &clock);
7
+ return (clock.tv_sec * 1000000000 + clock.tv_nsec) / 1000000000.0;
8
+ #elif defined(_win32)
9
+ FILETIME createTime;
10
+ FILETIME exitTime;
11
+ FILETIME sysTime;
12
+ FILETIME cpuTime;
13
+
14
+ ULARGE_INTEGER sysTimeInt;
15
+ ULARGE_INTEGER cpuTimeInt;
16
+ ULONGLONG totalTime;
17
+
18
+ GetProcessTimes(GetCurrentProcess(), &createTime, &exitTime, &sysTime, &cpuTime);
19
+
20
+ /* Doing this based on MSFT's recommendation in the FILETIME structure documentation at
21
+ http://msdn.microsoft.com/en-us/library/ms724284%28VS.85%29.aspx*/
22
+
23
+ sysTimeInt.LowPart = sysTime.dwLowDateTime;
24
+ sysTimeInt.HighPart = sysTime.dwHighDateTime;
25
+ cpuTimeInt.LowPart = cpuTime.dwLowDateTime;
26
+ cpuTimeInt.HighPart = cpuTime.dwHighDateTime;
27
+
28
+ totalTime = sysTimeInt.QuadPart + cpuTimeInt.QuadPart;
29
+
30
+ // Times are in 100-nanosecond time units. So instead of 10-9 use 10-7
31
+ return totalTime / 10000000.0;
32
+ #else
33
+ return ((double) clock()) / CLOCKS_PER_SEC;
34
+ #endif
35
+ }
36
+
37
+ static const char *event_name(rb_event_flag_t event) {
38
+ switch (event) {
39
+ case RUBY_EVENT_LINE:
40
+ return "line";
41
+ case RUBY_EVENT_CLASS:
42
+ return "class";
43
+ case RUBY_EVENT_END:
44
+ return "end";
45
+ case RUBY_EVENT_CALL:
46
+ return "call";
47
+ case RUBY_EVENT_RETURN:
48
+ return "return";
49
+ case RUBY_EVENT_C_CALL:
50
+ return "c-call";
51
+ case RUBY_EVENT_C_RETURN:
52
+ return "c-return";
53
+ case RUBY_EVENT_RAISE:
54
+ return "raise";
55
+ #ifdef RUBY_VM
56
+ case RUBY_EVENT_SWITCH:
57
+ return "thread-interrupt";
58
+ #endif
59
+ default:
60
+ return "unknown";
61
+ }
62
+ }
63
+
64
+ #if defined(RB_EVENT_HOOKS_HAVE_CALLBACK_DATA) || defined(RUBY_EVENT_VM)
65
+ static void stack_tracy_trap(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
66
+ #else
67
+ static void stack_tracy_trap(rb_event_flag_t event, NODE *node, VALUE self, ID id, VALUE klass)
68
+ #endif
69
+ {
70
+ int i;
71
+ bool singleton = false, match = false;
72
+ EventInfo info;
73
+
74
+ if (event == RUBY_EVENT_CALL || event == RUBY_EVENT_C_CALL) {
75
+ trace = true;
76
+ }
77
+
78
+ if (trace == false) {
79
+ return;
80
+ }
81
+
82
+ #ifdef RUBY_VM
83
+ if (id == 0) {
84
+ rb_frame_method_id_and_class(&id, &klass);
85
+ }
86
+ #endif
87
+
88
+ if (klass) {
89
+ if (TYPE(klass) == T_ICLASS) {
90
+ klass = RBASIC(klass)->klass;
91
+ }
92
+
93
+ singleton = FL_TEST(klass, FL_SINGLETON);
94
+
95
+ #ifdef RUBY_VM
96
+ if (singleton && !(TYPE(self) == T_CLASS || TYPE(self) == T_MODULE))
97
+ singleton = false;
98
+ #endif
99
+ }
100
+
101
+ info.method = (ID *) id;
102
+
103
+ if (info.method != NULL) {
104
+ info.object = (VALUE *)(singleton ? self : klass);
105
+
106
+ if (info.object) {
107
+ for (i = 0; i < exclude_size; i++) {
108
+ if (((VALUE) exclude[i].klass) == (VALUE) info.object) {
109
+ return;
110
+ }
111
+ }
112
+
113
+ if (only_size > 0) {
114
+ match = false;
115
+ for (i = 0; i < only_size; i++) {
116
+ match = match || (((VALUE) only[i].klass) == (VALUE) info.object);
117
+ }
118
+ if (!match) {
119
+ return;
120
+ }
121
+ }
122
+
123
+ info.event = event;
124
+ info.file = (char *) rb_sourcefile();
125
+ info.line = rb_sourceline();
126
+ info.singleton = singleton;
127
+ info.nsec = nsec();
128
+
129
+ stack_size = stack_size + 1;
130
+ stack = (EventInfo *) realloc (stack, stack_size * sizeof(EventInfo));
131
+ stack[stack_size - 1] = info;
132
+ }
133
+ }
134
+ }
135
+
136
+ VALUE stack_tracy_start(VALUE self, VALUE only_names, VALUE exclude_names) {
137
+ int i;
138
+ char *token;
139
+
140
+ token = strtok((char *) RSTRING_PTR(only_names), " ");
141
+ only_size = 0;
142
+
143
+ while (token != NULL) {
144
+ only_size++;
145
+ only = (RubyClass *) realloc (only, only_size * sizeof(RubyClass));
146
+
147
+ RubyClass klass;
148
+ klass.name = (char *) token;
149
+ klass.klass = (VALUE *) rb_path2class((char *) token);
150
+ only[only_size - 1] = klass;
151
+
152
+ token = strtok(NULL, " ");
153
+ }
154
+
155
+ token = strtok((char *) RSTRING_PTR(exclude_names), " ");
156
+ exclude_size = 0;
157
+
158
+ while (token != NULL) {
159
+ exclude_size++;
160
+ exclude = (RubyClass *) realloc (exclude, exclude_size * sizeof(RubyClass));
161
+
162
+ RubyClass klass;
163
+ klass.name = (char *) token;
164
+ klass.klass = (VALUE *) rb_path2class((char *) token);
165
+ exclude[exclude_size - 1] = klass;
166
+
167
+ token = strtok(NULL, " ");
168
+ }
169
+
170
+ #if defined(RB_EVENT_HOOKS_HAVE_CALLBACK_DATA) || defined(RUBY_EVENT_VM)
171
+ rb_add_event_hook(stack_tracy_trap, RUBY_EVENT_CALL | RUBY_EVENT_C_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN, 0);
172
+ #else
173
+ rb_add_event_hook(stack_tracy_trap, RUBY_EVENT_CALL | RUBY_EVENT_C_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN);
174
+ #endif
175
+
176
+ stack_size = 0, trace = false;
177
+
178
+ return Qnil;
179
+ }
180
+
181
+ VALUE stack_tracy_stop(VALUE self) {
182
+ VALUE events, event;
183
+ ID id;
184
+ const char *method;
185
+ int i;
186
+
187
+ rb_remove_event_hook(stack_tracy_trap);
188
+
189
+ events = rb_ary_new();
190
+
191
+ for (i = 0; i < stack_size - 2; i++) {
192
+ method = rb_id2name((ID) stack[i].method);
193
+ if (method != NULL) {
194
+ event = rb_funcall(cEventInfo, rb_intern("new"), 0);
195
+ rb_iv_set(event, "@event", rb_str_new2(event_name(stack[i].event)));
196
+ rb_iv_set(event, "@file", rb_str_new2(stack[i].file));
197
+ rb_iv_set(event, "@line", rb_int_new(stack[i].line));
198
+ rb_iv_set(event, "@singleton", stack[i].singleton);
199
+ rb_iv_set(event, "@object", (VALUE) stack[i].object);
200
+ rb_iv_set(event, "@method", rb_str_new2(method));
201
+ rb_iv_set(event, "@nsec", rb_float_new(stack[i].nsec));
202
+ rb_ary_push(events, event);
203
+ }
204
+ }
205
+
206
+ rb_iv_set(mStackTracy, "@stack_trace", events);
207
+
208
+ return Qnil;
209
+ }
210
+
211
+ void Init_stack_tracy() {
212
+ mStackTracy = rb_const_get(rb_cObject, rb_intern("StackTracy"));
213
+ cEventInfo = rb_const_get(mStackTracy, rb_intern("EventInfo"));
214
+ rb_define_singleton_method(mStackTracy, "_start", stack_tracy_start, 2);
215
+ rb_define_singleton_method(mStackTracy, "_stop", stack_tracy_stop, 0);
216
+ }
@@ -0,0 +1,48 @@
1
+ #ifndef STACK_TRACY_H
2
+ #define STACK_TRACY_H
3
+
4
+ #include <ruby.h>
5
+ #include <stdbool.h>
6
+ #include <time.h>
7
+ #include <sys/time.h>
8
+ #include <string.h>
9
+
10
+ typedef struct event_info_t {
11
+ rb_event_flag_t event;
12
+ const char *file;
13
+ int line;
14
+ bool singleton;
15
+ const VALUE *object;
16
+ const ID *method;
17
+ double nsec;
18
+ } EventInfo;
19
+
20
+ typedef struct ruby_class_t {
21
+ const char *name;
22
+ const VALUE *klass;
23
+ } RubyClass;
24
+
25
+ static VALUE mStackTracy;
26
+ static VALUE cEventInfo;
27
+
28
+ static EventInfo *stack;
29
+ static RubyClass *only;
30
+ static RubyClass *exclude;
31
+
32
+ static int stack_size, only_size, exclude_size;
33
+ static bool trace;
34
+
35
+ static double nsec();
36
+ static const char *event_name(rb_event_flag_t event);
37
+
38
+ #if defined(RB_EVENT_HOOKS_HAVE_CALLBACK_DATA) || defined(RUBY_EVENT_VM)
39
+ static void stack_tracy_trap(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass);
40
+ #else
41
+ static void stack_tracy_trap(rb_event_flag_t event, NODE *node, VALUE self, ID id, VALUE klass);
42
+ #endif
43
+
44
+ VALUE stack_tracy_start(VALUE self, VALUE only_names, VALUE exclude_names);
45
+ VALUE stack_tracy_stop(VALUE self);
46
+ void Init_stack_tracy();
47
+
48
+ #endif
@@ -0,0 +1,162 @@
1
+ require "erb"
2
+ require "csv"
3
+ require "tmpdir"
4
+ require "securerandom"
5
+ require "rich/support/core/string/colorize"
6
+ require "launchy"
7
+
8
+ require "stack_tracy/core_ext"
9
+ require "stack_tracy/event_info"
10
+ require "stack_tracy/sinatra"
11
+ require "stack_tracy/version"
12
+
13
+ module StackTracy
14
+ extend self
15
+
16
+ PRESETS = {
17
+ :core => "Array BasicObject Enumerable Fixnum Float Hash IO Integer Kernel Module Mutex Numeric Object Rational String Symbol Thread Time",
18
+ :active_record => "ActiveRecord::Base",
19
+ :data_mapper => "DataMapper::Resource"
20
+ }
21
+ @options = Struct.new(:dump_dir, :only, :exclude).new(Dir::tmpdir)
22
+
23
+ class Error < StandardError; end
24
+
25
+ def config
26
+ yield @options
27
+ end
28
+
29
+ def start(options = {})
30
+ opts = merge_options(options)
31
+ _start mod_names(opts[:only]), mod_names(opts[:exclude])
32
+ nil
33
+ end
34
+
35
+ def stop
36
+ _stop
37
+ nil
38
+ end
39
+
40
+ def stack_trace
41
+ @stack_trace || []
42
+ end
43
+
44
+ def select(*only)
45
+ [].tap do |lines|
46
+ first, call_stack, only = nil, [], only.flatten.collect{|x| x.split(" ")}.flatten
47
+ stack_trace.each do |event_info|
48
+ next unless process?(event_info, only)
49
+ first ||= event_info
50
+ if event_info.call?
51
+ lines << event_info.to_hash(first).merge!(:depth => call_stack.size)
52
+ call_stack << [lines.size - 1, event_info]
53
+ elsif event_info.return? && call_stack.last && event_info.matches?(call_stack.last.last)
54
+ call_stack.pop.tap do |(line, match)|
55
+ lines[line][:duration] = event_info - match
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def print(*only)
63
+ puts select(only).collect{ |event|
64
+ line = " " * event[:depth]
65
+ line << event[:call]
66
+ line << " <#{"%.6f" % event[:duration]}>" if event[:duration]
67
+ }
68
+ end
69
+
70
+ def dump(path = nil, *only)
71
+ unless path && path.match(/\.csv$/)
72
+ path = File.join [path || @options.dump_dir, "stack_events-#{SecureRandom.hex(3)}.csv"].compact
73
+ end
74
+ File.expand_path(path).tap do |path|
75
+ keys = [:event, :file, :line, :singleton, :object, :method, :nsec, :time, :call, :depth, :duration]
76
+ File.open(path, "w") do |file|
77
+ file << keys.join(";") + "\n"
78
+ select(only).each do |event|
79
+ file << event.values_at(*keys).join(";") + "\n"
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def open(path = nil, use_current_stack_trace = false)
86
+ unless use_current_stack_trace
87
+ unless path && path.match(/\.csv$/)
88
+ path = Dir[File.join(path || @options.dump_dir, "stack_events-*.csv")].sort_by{|f| File.mtime(f)}.last
89
+ path ||= Dir[File.join(path || Dir::tmpdir, "stack_events-*.csv")].sort_by{|f| File.mtime(f)}.last
90
+ end
91
+ if path
92
+ file = File.expand_path(path)
93
+ else
94
+ raise Error, "Could not locate StackTracy file"
95
+ end
96
+ end
97
+
98
+ index = ui("index.html")
99
+
100
+ if use_current_stack_trace || (file && File.exists?(file))
101
+ events = use_current_stack_trace ? select : StackTracy::EventInfo.to_hashes(File.read(file))
102
+ erb = ERB.new File.new(ui("index.html.erb")).read
103
+ File.open(index, "w"){|f| f.write erb.result(binding)}
104
+ elsif path && path.match(/\.csv$/)
105
+ raise Error, "Could not locate StackTracy file at #{file}"
106
+ end
107
+
108
+ if File.exists?(index)
109
+ Launchy.open("file://#{index}")
110
+ nil
111
+ else
112
+ raise Error, "Could not locate StackTracy file"
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def merge_options(hash = {})
119
+ {:only => @options.only, :exclude => @options.exclude}.merge(hash.inject({}){|h, (k, v)| h.merge!(k.to_sym => v)})
120
+ end
121
+
122
+ def mod_names(arg)
123
+ names = PRESETS.inject([arg || []].flatten.collect(&:to_s).join(" ")){|s, (k, v)| s.gsub k.to_s, v}
124
+ if names.include?("*")
125
+ names.split(/\s/).collect do |name|
126
+ name.include?("*") ? mods_within([constantize(name.gsub("*", ""))]).collect(&:name) : spec
127
+ end.flatten
128
+ else
129
+ names.split(/\s/)
130
+ end.sort.join(" ")
131
+ end
132
+
133
+ def constantize(name)
134
+ name.split("::").inject(Kernel){|m, x| m.const_get x}
135
+ end
136
+
137
+ def mods_within(mods, initial_array = nil)
138
+ (initial_array || mods).tap do |array|
139
+ mods.each do |mod|
140
+ mod.constants.each do |c|
141
+ const = mod.const_get c
142
+ if !array.include?(const) && (const.is_a?(Class) || const.is_a?(Module)) && const.name.match(/^#{mod.name}/)
143
+ array << const
144
+ mods_within([const], array)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ def process?(event_info, only)
152
+ return true if only.empty?
153
+ only.any?{|x| event_info.matches?(x)}
154
+ end
155
+
156
+ def ui(file)
157
+ File.expand_path("../../ui/#{file}", __FILE__)
158
+ end
159
+
160
+ end
161
+
162
+ require File.expand_path("../../ext/stack_tracy/stack_tracy", __FILE__)
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
2
+ require path
3
+ end