trip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2d170e305895c49de5afa27d65e21481a42b0647
4
+ data.tar.gz: d2245ff1028cbe489f101e53ac352c460db424d2
5
+ SHA512:
6
+ metadata.gz: 28639d5f905547c812c2696d6451984f2624b7327180e597b15f9891f972693204a179914fd6731caf346a97549a9f9088c0d76f79e4fad11e3722377406fe0c
7
+ data.tar.gz: c4ae55b130f2bf502a7c3626178065ab97128e44da94a9d16685b410451af9501938236a0500dc1dc3e352d1173e1b2ef0acf07255671c0bbdaed8cfa4a45acf
data/.bundle/config ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ BUNDLE_PATH: ".gems"
3
+ BUNDLE_DISABLE_SHARED_GEMS: '1'
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .gems/
2
+ *.md~
3
+ *.rb~
4
+ *.rb#
5
+ pkg/
6
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ install: bundle install
2
+ script: ruby -S rake
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.1.3
7
+ - ruby-head
8
+
9
+ notifications:
10
+ email: true
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ group :test do
4
+ gem 'minitest', require: ['minitest', 'minitest/spec']
5
+ end
6
+
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 rpag
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ __trip__
2
+
3
+ [![Build Status](https://travis-ci.org/rpag/trip.svg?branch=master)](https://travis-ci.org/rpag/trip)
4
+ [![Code Climate](https://codeclimate.com/github/rpag/trip/badges/gpa.svg)](https://codeclimate.com/github/rpag/trip)
5
+
6
+ trip is a concurrent tracer for the ruby programming language. It yields control
7
+ between two threads, most likely the main thread and a thread that trip creates.
8
+ It is implemented in pure ruby(no C) and with the help of "Thread#set_trace_func".
9
+ It can be useful when building developer tools like a debugger or if you want to
10
+ step through code progmatically at runtime.
11
+
12
+
13
+ __Limitations__
14
+
15
+ trip is implemented with "Thread#set_trace_func". It can be a bottleneck,
16
+ especially when there are a large number of events being created. I have seen
17
+ ruby grind to a halt when the codepath being traced is busy. It wouldn't be a good
18
+ library to use with something like a production rails application, but it can be
19
+ useful when building developer tools.
20
+
21
+ "Thread#set_trace_func" isn't well supported on any ruby implementation but the
22
+ official MRI/CRuby implementations, so jruby+rubinius aren't supported.
23
+
24
+ __Examples__
25
+
26
+ __1.__
27
+
28
+ The code being traced is paused on method call and method return events
29
+ by default.
30
+
31
+ ```ruby
32
+ def add(x,y)
33
+ x + y
34
+ end
35
+
36
+ trip = Trip.new { add(20,50) }
37
+ e1 = trip.start # Trip::Event (for the method call of "#add")
38
+ e2 = trip.resume # Trip::Event (for the method return of "#add")
39
+ e3 = trip.resume # returns nil (thread exits)
40
+ ```
41
+
42
+ __2.__
43
+
44
+ A predicate Proc can be implemented as a custom value. It receives
45
+ an instance of `Trip::Event` that can support the Proc when it is
46
+ making a decision on whether or not it should pause the tracer by
47
+ returning false, or to continue by returning true.
48
+
49
+ ```ruby
50
+ class Planet
51
+ def initialize(name)
52
+ @name = name
53
+ end
54
+
55
+ def echo
56
+ 'ping'
57
+ end
58
+ end
59
+
60
+ trip = Trip.new { Planet.new('earth').echo }
61
+ trip.pause? { |e| # pause on a method call
62
+ e.name == 'call'
63
+ }
64
+ e1 = trip.start # Trip::Event (for the method call of `Planet#initialize`)
65
+ e2 = trip.resume # Trip::Event (for the method call of `Planet#echo`)
66
+ e3 = trip.resume # returns nil (thread exits)
67
+ ```
68
+
69
+ __3.__
70
+
71
+ Trip::Event#binding can be used to evaluate ruby while the trip
72
+ thread is sleeping. The example returns the value of the local
73
+ variable "x" after the add method has been called.
74
+
75
+ ```ruby
76
+ def add(x,y)
77
+ x + y
78
+ end
79
+
80
+ trip = Trip.new { add(2,3) }
81
+ e1 = trip.start # Trip::Event (for the method call of add)
82
+ x = e1.binding.eval('x') # returns 2 (x is equal to 2)
83
+ trip.stop # thread exits
84
+
85
+ puts "x is equal to #{x}"
86
+ ```
87
+
88
+ __Install__
89
+
90
+ rubygems:
91
+
92
+ gem install trip
93
+
94
+ git:
95
+
96
+ git clone https://github.com/rpag/trip.git
97
+ cd trip
98
+ gem build trip.gemspec
99
+ gem install *.gem
100
+
101
+ __License__
102
+
103
+ See MIT-LICENSE.txt file.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:spec) do |t|
5
+ t.test_files = FileList['spec/*_spec.rb']
6
+ t.verbose = false
7
+ end
8
+ task :default => :spec
@@ -0,0 +1,41 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'trip'
3
+
4
+ #
5
+ # an example method.
6
+ # it assigns a local variable and returns control to the caller.
7
+ #
8
+ def example
9
+ x = 1
10
+ end
11
+
12
+ #
13
+ # initialize Trip with a block that calls the example() method.
14
+ # the trace doesn't start yet.
15
+ #
16
+ trip = Trip.new do
17
+ example()
18
+ end
19
+
20
+ #
21
+ # the tracer can be paused on certain events.
22
+ #
23
+ trip.pause? do |event|
24
+ event.name == 'call' or event.name == 'return'
25
+ end
26
+
27
+ #
28
+ # start trip.
29
+ # e1 and e2 reference Trip::Event objects. e3 ends up referencing nil.
30
+ # nil is returned when trip has finished.
31
+ #
32
+ e1 = trip.start # before assignment of 'x'
33
+ e2 = trip.resume # after assignment of 'x'
34
+ e3 = trip.resume # finished
35
+
36
+ #
37
+ # print the value of 'x'.
38
+ # the local variable 'x' belongs to the binding closed over by e2(method return).
39
+ #
40
+ print 'x is equal to "%s".' % e2.binding.eval('x')
41
+
data/examples/time.rb ADDED
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'trip'
3
+
4
+ class Phone
5
+ def self.call
6
+ sleep(2)
7
+ 'ring ring'
8
+ end
9
+ end
10
+
11
+ trip = Trip.new { Phone.call }
12
+ trip.pause? { |e| e.name == 'call' or e.name == 'return' }
13
+ t1 = trip.start.created_at
14
+ t2 = trip.resume.created_at
15
+ puts "it took around #{t2 - t1} seconds"
data/lib/trip.rb ADDED
@@ -0,0 +1,167 @@
1
+ class Trip
2
+ require 'thread'
3
+ require_relative 'trip/event'
4
+ require_relative 'trip/version'
5
+
6
+ NotStartedError = Class.new(RuntimeError)
7
+ NotFinishedError = Class.new(RuntimeError)
8
+
9
+ RUN_STATE = 'run'
10
+ SLEEP_STATE = 'sleep'
11
+ END_STATE = [nil, false]
12
+ CALL_E = ['call', 'c-call']
13
+ RETURN_E = ['return', 'c-return']
14
+ PAUSE_P = Proc.new { |event|
15
+ CALL_E.include?(event.name) or RETURN_E.include?(event.name)
16
+ }
17
+
18
+ #
19
+ # @param [Proc] &block
20
+ # a block of code
21
+ #
22
+ # @return [Trip]
23
+ # returns an instance of Trip
24
+ #
25
+ def initialize(&block)
26
+ if block.equal?(nil)
27
+ raise ArgumentError, 'expected to receive a block but none given'
28
+ end
29
+ @thread = nil
30
+ @block = block
31
+ @queue = nil
32
+ @pause = PAUSE_P
33
+ end
34
+
35
+ #
36
+ # @raise [ArgumentError]
37
+ # when no arguments are received
38
+ #
39
+ # @param [Proc] callable
40
+ # accepts a Proc or a block
41
+ #
42
+ # @return [Proc]
43
+ # returns a Proc
44
+ #
45
+ def pause?(callable = nil, &block)
46
+ pause = callable || block
47
+ if pause.equal?(nil)
48
+ raise ArgumentError, 'expected to receive a block but none given'
49
+ end
50
+ @pause = pause
51
+ end
52
+
53
+ #
54
+ # @return [Boolean]
55
+ # returns true when a trace has started
56
+ #
57
+ def started?
58
+ @thread != nil
59
+ end
60
+
61
+ #
62
+ # @return [Boolean]
63
+ # returns true when a tracer thread is running
64
+ #
65
+ def running?
66
+ @thread and @thread.status == RUN_STATE
67
+ end
68
+
69
+ #
70
+ # @return [Boolean]
71
+ # returns true when a tracer thread has finished
72
+ #
73
+ def finished?
74
+ @thread and END_STATE.include?(@thread.status)
75
+ end
76
+
77
+ #
78
+ # @return [Boolean]
79
+ # returns true when a tracer thread is sleeping
80
+ #
81
+ def sleeping?
82
+ @thread and @thread.status == SLEEP_STATE
83
+ end
84
+
85
+ #
86
+ # resume the tracer
87
+ #
88
+ # @raise [Trip::NotStartedError]
89
+ # when the start method has not been called yet
90
+ #
91
+ # @return [Trip::Event, nil]
92
+ # returns an event or nil
93
+ #
94
+ def resume
95
+ unless started?
96
+ raise NotStartedError, 'the tracer has not been started'
97
+ end
98
+ if sleeping?
99
+ @thread.wakeup
100
+ @queue.deq
101
+ end
102
+ end
103
+
104
+ #
105
+ # start the tracer
106
+ #
107
+ # @raise [Trip::NotFinishedError]
108
+ # when a trace is already in progress
109
+ #
110
+ # @return [Trip::Event, nil]
111
+ # returns an event, or nil
112
+ #
113
+ def start
114
+ if started? and !finished?
115
+ raise NotFinishedError, 'a trace is still in progress'
116
+ end
117
+ @queue = Queue.new
118
+ @thread = Thread.new do
119
+ Thread.current.set_trace_func method(:on_event).to_proc
120
+ @block.call
121
+ Thread.current.set_trace_func(nil)
122
+ @queue.enq(nil)
123
+ end
124
+ @queue.deq
125
+ end
126
+
127
+ #
128
+ # stop the tracer
129
+ #
130
+ # @return [nil]
131
+ # returns nil
132
+ #
133
+ def stop
134
+ if @thread
135
+ @thread.set_trace_func(nil)
136
+ @thread.exit
137
+ @thread.join
138
+ nil
139
+ end
140
+ end
141
+
142
+ private
143
+ def on_event(name, file, lineno, method, binding, classname)
144
+ event = Event.new name, {
145
+ file: file,
146
+ lineno: lineno,
147
+ module: classname,
148
+ method: method,
149
+ binding: binding
150
+ }
151
+ if event.file != __FILE__ and @pause.call(event)
152
+ @queue.enq(event)
153
+ Thread.stop
154
+ end
155
+ rescue Exception => e
156
+ warn <<-CRASH.each_line.map(&:lstrip)
157
+ (trip) the tracer has crashed! :(
158
+
159
+ #{e.class}:
160
+ #{e.message}
161
+
162
+ BACKTRACE
163
+ #{e.backtrace.join("\n")}
164
+ CRASH
165
+ Thread.current.set_trace_func(nil)
166
+ end
167
+ end
data/lib/trip/event.rb ADDED
@@ -0,0 +1,31 @@
1
+ class Trip::Event < BasicObject
2
+ Kernel = ::Kernel
3
+ Time = ::Time
4
+ CALLS = %w(call c-call)
5
+ RETURNS = %w(return c-return)
6
+
7
+ attr_reader :name, :created_at
8
+
9
+ def initialize(name, event)
10
+ @name = name
11
+ @event = event
12
+ @created_at = Time.now
13
+ end
14
+
15
+ [:file, :lineno, :module, :method, :binding].each do |name|
16
+ define_method(name) { @event[name] }
17
+ end
18
+
19
+ #
20
+ # @return [Binding]
21
+ # returns a binding for an instance of {Trip::Event}
22
+ #
23
+ def __binding__
24
+ Kernel.binding
25
+ end
26
+
27
+ def inspect
28
+ inspect_m = Kernel.instance_method(:inspect).bind(self)
29
+ inspect_m.call
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ class Trip
2
+ VERSION = '0.1.0'
3
+ end
data/spec/setup.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler/setup'
2
+ Bundler.require :default, :test
3
+ Minitest.autorun
data/spec/trip_spec.rb ADDED
@@ -0,0 +1,157 @@
1
+ require_relative 'setup'
2
+ describe Trip do
3
+ class Planet
4
+ def echo(message)
5
+ return message
6
+ end
7
+ end
8
+
9
+ before do
10
+ @planet = Planet.new
11
+ @trip = Trip.new { @planet.echo('ping') }
12
+ end
13
+
14
+ after do
15
+ unless @trip.finished?
16
+ @trip.stop
17
+ end
18
+ end
19
+
20
+ describe '#initialize' do
21
+ it 'raises an ArgumentError without a block' do
22
+ assert_raises(ArgumentError) { Trip.new }
23
+ end
24
+ end
25
+
26
+ describe '#start' do
27
+ it 'returns an instance of Trip::Event' do
28
+ assert Trip::Event === @trip.start
29
+ end
30
+
31
+ it 'returns nil with a false pause predicate' do
32
+ @trip.pause? { false }
33
+ assert_equal nil, @trip.start
34
+ end
35
+
36
+ it 'raises Trip::NotFinishedError' do
37
+ @trip.start
38
+ assert_raises(Trip::NotFinishedError) { @trip.start }
39
+ end
40
+ end
41
+
42
+ describe '#sleeping?' do
43
+ it 'returns true' do
44
+ @trip.start
45
+ assert_equal true, @trip.sleeping?
46
+ end
47
+
48
+ it 'returns false' do
49
+ @trip.start
50
+ @trip.resume while @trip.resume
51
+ assert_equal false, @trip.sleeping?
52
+ end
53
+ end
54
+
55
+ describe '#started?' do
56
+ it 'returns true' do
57
+ @trip.start
58
+ assert_equal true, @trip.started?
59
+ end
60
+
61
+ it 'returns false' do
62
+ assert_equal false, @trip.started?
63
+ end
64
+ end
65
+
66
+ describe '#resume' do
67
+ it 'raises Trip::NotStartedError' do
68
+ assert_raises(Trip::NotStartedError) { @trip.resume }
69
+ end
70
+ end
71
+
72
+ describe '#pause?' do
73
+ it 'raises an ArgumentError' do
74
+ assert_raises(ArgumentError) { @trip.pause? }
75
+ end
76
+
77
+ it 'accepts a Proc' do
78
+ obj = Proc.new {}
79
+ assert_equal obj, @trip.pause?(obj)
80
+ end
81
+ end
82
+
83
+ describe '#finished?' do
84
+ it 'returns true' do
85
+ @trip.start
86
+ @trip.resume while @trip.resume
87
+ assert_equal true, @trip.finished?
88
+ end
89
+
90
+ it 'returns false' do
91
+ @trip.start
92
+ assert_equal false, @trip.finished?
93
+ end
94
+
95
+ it 'returns nil' do
96
+ assert_equal nil, @trip.finished?
97
+ end
98
+ end
99
+
100
+ describe '#running?' do
101
+ it 'returns false' do
102
+ @trip.start
103
+ @trip.resume while @trip.resume
104
+ assert_equal false, @trip.running?
105
+ end
106
+
107
+ it 'returns nil' do
108
+ assert_equal nil, @trip.running?
109
+ end
110
+ end
111
+
112
+ describe Trip::Event do
113
+ describe '#module' do
114
+ it 'returns a Module' do
115
+ e1 = @trip.start
116
+ assert_equal Planet, e1.module
117
+ end
118
+ end
119
+
120
+ describe '#method' do
121
+ it 'returns a method name' do
122
+ e1 = @trip.start
123
+ assert_equal :echo, e1.method
124
+ end
125
+ end
126
+
127
+ describe '#file' do
128
+ it 'returns a path' do
129
+ e1 = @trip.start
130
+ assert_equal __FILE__, e1.file
131
+ end
132
+ end
133
+
134
+ describe '#lineno' do
135
+ it 'returns an Integer' do
136
+ e1 = @trip.start
137
+ assert_kind_of Integer, e1.lineno
138
+ end
139
+ end
140
+
141
+ describe '#binding' do
142
+ it 'returns a Binding with access to the local variable "message"' do
143
+ @trip.pause? { |e| e.name == 'call' or e.name == 'return' }
144
+ e1 = @trip.start
145
+ e2 = @trip.resume
146
+ assert_equal 'ping', e2.binding.eval('message')
147
+ end
148
+ end
149
+
150
+ describe '#__binding__' do
151
+ it 'returns a Binding for an instance of Trip::Event' do
152
+ e1 = @trip.start
153
+ assert Trip::Event === e1.__binding__.eval('self')
154
+ end
155
+ end
156
+ end
157
+ end
data/trip.gemspec ADDED
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH << 'lib'
2
+ require 'trip'
3
+ Gem::Specification.new do |g|
4
+ g.name = 'trip'
5
+ g.author = 'rpag'
6
+ g.email = 'rpag@singletonclass.com'
7
+ g.version = Trip::VERSION
8
+ g.summary = 'concurrent ruby tracer'
9
+ g.description = 'concurrent ruby tracer built with Thread#set_trace_func'
10
+ g.files = `git ls-files`.split($/)
11
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - rpag
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: concurrent ruby tracer built with Thread#set_trace_func
14
+ email: rpag@singletonclass.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".bundle/config"
20
+ - ".gitignore"
21
+ - ".travis.yml"
22
+ - Gemfile
23
+ - MIT-LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - examples/concurrent.rb
27
+ - examples/time.rb
28
+ - lib/trip.rb
29
+ - lib/trip/event.rb
30
+ - lib/trip/version.rb
31
+ - spec/setup.rb
32
+ - spec/trip_spec.rb
33
+ - trip.gemspec
34
+ homepage:
35
+ licenses: []
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 2.2.2
54
+ signing_key:
55
+ specification_version: 4
56
+ summary: concurrent ruby tracer
57
+ test_files: []
58
+ has_rdoc: