stack_tracy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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