wtf-tools 1.0.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: 39cd4bdb18179b654c8c89627596eba83ef1bb93
4
+ data.tar.gz: b55d4b4ddb008660477c7c54a3124299b50f8380
5
+ SHA512:
6
+ metadata.gz: 3fe5a3c452e36a089d9f54f94f2e082e3453bf9f2164ea91d28a58c3b725d24b7c18c8935e914890ab830374305cca3cb6d73add38cdbad12bc86191b6485b13
7
+ data.tar.gz: b3301c939c79f6dcf23ad3060908e0555255bd517e3f71a129bc26f57a26ec4c3cdde88643f2828bf607ff49c18038420667dcc8782937cfb61af39bbe5bdce2
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Remigijus Jodelis
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 all
13
+ 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 THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ wtf-tools
2
+ =========
3
+
4
+ Some tools for debugging and profiling Ruby on Rails projects. Included:
5
+
6
+ * data dumper
7
+ * code timing tool
8
+ * method tracker - a kind of middle ground between simple timing and full profiling
9
+ * SQL tracker - detect where exactly given SQL query originated from
10
+
11
+ User is responsible for requiring gems necessary for rendering output.
12
+ For example, when :yaml option is used with `WTF?`, we expect that `YAML` is already loaded.
13
+ Library requirements for specific options are documented below.
14
+
15
+ Usage examples
16
+ --------------
17
+
18
+ ### Data dumping
19
+
20
+ ```ruby
21
+ WTF? my_var # basic
22
+ WTF? my_var, other_var, { some: data }, :pp # more data, option: pretty-print
23
+ WTF? :label, records, :bare, :time # multiple options given
24
+ ```
25
+
26
+ Supported options
27
+
28
+ ```
29
+ Prefix
30
+ (default) WTF (my_file_name/method_name:227):
31
+ :time with timestamp: [2014-10-28 12:33:11 +0200]
32
+ :nl new line before the record
33
+ :no no prefix at all
34
+
35
+ Formatting
36
+ (default) simple Ruby inspect
37
+ :pp pretty-print, require 'pp'
38
+ :yaml YAML format, require 'yaml'
39
+ :json JSON format, require 'json'
40
+ :csv CSV format, require 'csv'
41
+ :text simple Ruby to_s
42
+ :line modifier, each object in separate line
43
+ :bare modifier, ActiveRecord with just id attributs: #<MyClass id: 1234>
44
+
45
+ Output control
46
+ (default) to the chosen logger (see configuration)
47
+ :log to Rails default logger
48
+ :file to a separate file in configured location
49
+ :page to a thread variable Thread.current[:wtf]
50
+ :redis to a Redis list on key 'wtf', expiring in 30min
51
+ :raise raise the string containing data as exception
52
+ ```
53
+
54
+ ---
55
+
56
+ ### Code timing
57
+
58
+ ```ruby
59
+ class MyClass
60
+ def run_something
61
+ WTF.time {
62
+ # your code
63
+ }
64
+ end
65
+ end
66
+ ```
67
+
68
+ ---
69
+
70
+ ### Method tracking
71
+
72
+ ```ruby
73
+ class MyClass
74
+ def run_something
75
+ WTF.track(self, OtherClass)
76
+ # lots of code
77
+ WTF.track_finish
78
+ end
79
+ end
80
+ ```
81
+
82
+ This will create a CSV file in configured location, containing profiling info.
83
+ Profiling happens only in the methods of the calling class, and other given class.
84
+
85
+ How it works: every method in `MyClass` and `OtherClass` is overridden, adding resource timing code.
86
+ All calls to those methods from the code between `track` and `track_finish` are measured (time and memory),
87
+ and sum amounts are output at the end, sorted by total time used.
88
+
89
+ Example output:
90
+
91
+ ```csv
92
+ class,method,count,time,heap_mb
93
+ ,top,0,0.948,0.0
94
+ Overview,some_helper,16408,0.351,0.0
95
+ Overview,my_records,28,0.172,0.0
96
+ Overview,load_data,1,0.166,0.0
97
+ ...
98
+ ```
99
+
100
+ Requirements: Ruby 2.0, because the technique used involves module `prepend`-ing, which is not available in Ruby 1.9.
101
+ Warning: tracking method calls adds time overhead (somewhere from 10% to 3x, depending on number of times the methods were called).
102
+
103
+ ---
104
+
105
+ ### SQL tracking
106
+
107
+ ```ruby
108
+ class MyClass
109
+ def run_something
110
+ WTF.sql %q{SELECT * FROM `my_records` WHERE `attribute`='value' AND `etc`}
111
+ WTF.sql %q{UPDATE `my_records` SET `some_values`=NULL}
112
+ # lots of code
113
+ end
114
+ end
115
+ ```
116
+
117
+ This will add a `WTF?`-style dump in the default location, containing stacktrace where given SQL statement was generated. SQL must match exactly as strings.
118
+
119
+ ---
120
+
121
+
122
+ Configuration
123
+ -------------
124
+
125
+ Configure WTF before using the above-mentioned facilities. Rails initializer dir is a good place for it.
126
+
127
+ ```ruby
128
+ WTF.options = {
129
+ default: Rails.logger,
130
+ files: "#{Rails.root}/log/wtf",
131
+ redis: Redis.new,
132
+ }
133
+
134
+ require 'yaml' # to use :yaml option, etc
135
+ ```
136
+
137
+ ---
138
+
139
+ License
140
+ -------
141
+
142
+ This is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
data/example/dump.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'wtf-tools'
3
+ require 'pp'
4
+ require 'json'
5
+ require 'yaml'
6
+ require 'active_support/logger'
7
+ require 'fileutils'
8
+
9
+ WTF.options = {
10
+ :default => ActiveSupport::Logger.new('./example.log'),
11
+ :files => './wtf',
12
+ }
13
+
14
+ data = { 'some' => { 'nested' => { 'data' => 17 } }, :question => %w(life universe and everything), :answer => 42 }
15
+
16
+ WTF? Time.now
17
+
18
+ WTF? :label, "string", 17, 2.0001, :time, :nl
19
+
20
+ WTF? 'label', "string", 17, 2.0001, :time, :line, :nl
21
+
22
+ WTF? data, :pp, :nl
23
+
24
+ WTF? data, :json, :nl
25
+
26
+ WTF? data, :yaml, :no, :file
data/example/timing.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'wtf-tools'
3
+ require 'active_support/logger'
4
+
5
+ # default: puts
6
+ WTF.time {
7
+ sleep 1.01
8
+ }
9
+
10
+ # logging time with specified precision and options
11
+ WTF.options = {
12
+ :default => ActiveSupport::Logger.new('./example.log'),
13
+ }
14
+ result = WTF.time(4, :nl) {
15
+ sleep 3.12346
16
+ 17
17
+ }
18
+ p result # => 17
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'wtf-tools'
3
+ require 'fileutils'
4
+ require 'csv'
5
+
6
+ WTF.options = {
7
+ :files => './wtf',
8
+ }
9
+
10
+ class Sample
11
+ def run
12
+ WTF.track(self)
13
+ action1
14
+ action2
15
+ action3
16
+ WTF.track_finish
17
+ end
18
+
19
+ def action1
20
+ sleep 1
21
+ end
22
+
23
+ def action2
24
+ sleep 2
25
+ end
26
+
27
+ def action3
28
+ sleep 3
29
+ end
30
+ end
31
+
32
+ Sample.new.run
data/lib/wtf-tools.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'wtf/dumper'
2
+ require 'wtf/method_tracker'
3
+ require 'wtf/query_tracker'
4
+
5
+ module WTF
6
+ class << self
7
+ def options=(value)
8
+ @options = value
9
+ end
10
+
11
+ def options
12
+ @options ||= {}
13
+ end
14
+
15
+ def files_path
16
+ dirs = FileUtils.mkdir_p(options[:files])
17
+ dirs.first
18
+ end
19
+
20
+ # TODO: separately track ActiveRecord finders usage in the related methods
21
+ def track(*objects)
22
+ WTF::MethodTracker.start_tracking(*objects)
23
+ if block_given?
24
+ yield
25
+ track_finish
26
+ end
27
+ end
28
+
29
+ def track_finish
30
+ WTF::MethodTracker.finish
31
+ end
32
+
33
+ def sql(sql)
34
+ WTF::QueryTracker.start_tracking(sql)
35
+ end
36
+
37
+ def time(*args)
38
+ require 'absolute_time'
39
+
40
+ precision = args.shift if args.first.is_a?(Fixnum)
41
+ precision ||= 3
42
+ before = AbsoluteTime.now
43
+ result = yield
44
+ duration = AbsoluteTime.now - before
45
+ WTF::Dumper.new(duration.round(precision), *args)
46
+ result
47
+ end
48
+ end
49
+ end
50
+
51
+ Object.class_eval do
52
+ def WTF?(*args)
53
+ WTF::Dumper.new(*args)
54
+ nil
55
+ end
56
+ end
data/lib/wtf/dumper.rb ADDED
@@ -0,0 +1,87 @@
1
+ module WTF
2
+ class Dumper
3
+ OPTIONS = [
4
+ :time, :nl, :no, # prefix
5
+ :pp, :yaml, :json, :text, :line, :csv, # format
6
+ :bare, # modify
7
+ :page, :file, :raise, :redis, :log, # output
8
+ ].freeze
9
+
10
+ attr_reader :options
11
+
12
+ def initialize(*args)
13
+ @options = {}
14
+ while OPTIONS.include?(args.last)
15
+ @options[args.pop] = true
16
+ end
17
+
18
+ data = prefix(args) << format(args)
19
+ output(data)
20
+ end
21
+
22
+ private
23
+
24
+ def prefix(args)
25
+ data = ''
26
+ return data if options[:no]
27
+ data << "\n" if options[:nl]
28
+ data << "[%s] " % Time.now if options[:time]
29
+ data << if args[0].is_a?(Symbol)
30
+ args.shift.to_s.upcase
31
+ else
32
+ pattern = %r{([^/]+?)(?:\.rb)?:(\d+):in `(.*)'$} # '
33
+ "WTF (%s/%s:%s)" % caller[3].match(pattern).values_at(1,3,2)
34
+ end
35
+ data << ': '
36
+ end
37
+
38
+ def format(args)
39
+ case
40
+ when options[:pp]
41
+ cleanup(args.pretty_inspect, options[:bare])
42
+ when options[:yaml]
43
+ YAML.dump(args)
44
+ when options[:json]
45
+ JSON::pretty_generate(args)
46
+ when options[:text]
47
+ args.map(&:to_s).join("\n")
48
+ when options[:line]
49
+ args.map(&:inspect).join("\n ")
50
+ when options[:csv]
51
+ args[0].map(&:to_csv).join
52
+ else
53
+ cleanup(args.inspect, options[:bare])
54
+ end
55
+ end
56
+
57
+ def output(data)
58
+ case
59
+ when options[:page]
60
+ (Thread.current[:wtf] ||= []) << data
61
+ when options[:file]
62
+ time = Time.now.strftime('%m%d_%H%M%S')
63
+ file = "#{WTF.files_path}/wtf_#{time}_#{rand(10000)}.txt"
64
+ File.write(file, data)
65
+ when options[:raise]
66
+ raise StandardError, data
67
+ when options[:redis]
68
+ WTF.options[:redis].rpush('wtf', data)
69
+ WTF.options[:redis].expire('wtf', 30*60)
70
+ when options[:log]
71
+ Rails.logger.info(data)
72
+ when WTF.options[:default]
73
+ WTF.options[:default].info(data)
74
+ else
75
+ puts data
76
+ end
77
+ end
78
+
79
+ def cleanup(str, bare = false)
80
+ # remove array parentheses
81
+ str.gsub!(/^\[|\]$/,'')
82
+ # ActiveRecord no attributes
83
+ str.gsub!(/#<[A-Z]\w+ id: \d+\K.*?>/, '>') if bare
84
+ str
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,89 @@
1
+ module WTF
2
+ module MethodTracker
3
+ class << self
4
+ def start_tracking(*objects)
5
+ require 'absolute_time'
6
+
7
+ objects.each do |object|
8
+ klass = object.is_a?(Module) ? object : object.class
9
+ prepare(klass)
10
+ end
11
+ reset_state
12
+ end
13
+
14
+ def prepare(base)
15
+ methods = base.instance_methods(false) + base.private_instance_methods(false)
16
+ compiled = methods.map do |name|
17
+ override_method(base, name)
18
+ end
19
+ base.module_eval %{
20
+ module Tracking
21
+ #{compiled.join}
22
+ end
23
+ prepend Tracking
24
+ }
25
+ end
26
+
27
+ def override_method(base, name)
28
+ %{
29
+ def #{name}(*args)
30
+ WTF::MethodTracker.on_start(#{base}, :#{name})
31
+ return_value = super
32
+ WTF::MethodTracker.on_end
33
+ return_value
34
+ end
35
+ }
36
+ end
37
+
38
+ attr_accessor :stats, :stack, :last_time, :last_heap
39
+
40
+ def reset_state
41
+ self.stats = Hash.new { |h,k| h[k] = { :freq => 0, :time => 0.0, :heap => 0 } }
42
+ self.stack = [[nil, :top]]
43
+ self.last_time = AbsoluteTime.now
44
+ self.last_heap = GC.stat[:heap_length]
45
+ end
46
+
47
+ def on_start(*full_name)
48
+ add_stats(full_name)
49
+ stack.push(full_name)
50
+ end
51
+
52
+ def on_end
53
+ add_stats
54
+ stack.pop
55
+ end
56
+
57
+ def finish
58
+ add_stats
59
+ dump_stats
60
+ reset_state
61
+ end
62
+
63
+ def add_stats(at_start = nil)
64
+ stat = stats[stack.last]
65
+
66
+ this_time = AbsoluteTime.now
67
+ stat[:time] += this_time - last_time
68
+ self.last_time = this_time
69
+
70
+ this_heap = GC.stat[:heap_length]
71
+ stat[:heap] += this_heap - last_heap
72
+ self.last_heap = this_heap
73
+
74
+ stats[at_start][:freq] += 1 if at_start
75
+ end
76
+
77
+ def dump_stats
78
+ data = stats.map do |key, val|
79
+ [*key, val[:freq].to_i, val[:time].to_f.round(3), (val[:heap].to_f / 64).round(3)]
80
+ end
81
+ data = data.sort_by {|it| it[3] }.reverse
82
+ data.unshift(%w(class method count time heap_mb))
83
+
84
+ time = Time.now.strftime('%m%d_%H%M%S')
85
+ File.write("#{WTF.files_path}/track_#{time}_#{rand(10000)}.csv", data.map(&:to_csv).join)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,33 @@
1
+ module WTF
2
+ module QueryTracker
3
+ class << self
4
+ attr_reader :trackable
5
+
6
+ def start_tracking(sql)
7
+ if @trackable.nil?
8
+ prepare_hook
9
+ @trackable = {}
10
+ end
11
+ @trackable[sql] = true
12
+ end
13
+
14
+ def prepare_hook
15
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval %{
16
+ module TrackingSQL
17
+ def log(sql, *args, &block)
18
+ WTF::QueryTracker.on_sql(sql)
19
+ super(sql, *args, &block)
20
+ end
21
+ end
22
+ prepend TrackingSQL
23
+ }
24
+ end
25
+
26
+ def on_sql(sql)
27
+ if trackable[sql]
28
+ WTF::Dumper.new(:sql, sql, *caller.take(30), :line)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ require 'minitest/autorun'
2
+ require 'wtf-tools'
3
+
4
+ class TestWtfDumper < Minitest::Unit::TestCase
5
+ def test_dump
6
+ # TODO
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'minitest/autorun'
2
+ require 'wtf-tools'
3
+
4
+ class TestWtfMethodTracker < Minitest::Unit::TestCase
5
+ def test_track
6
+ # TODO
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'minitest/autorun'
2
+ require 'wtf-tools'
3
+
4
+ class TestWtfTiming < Minitest::Unit::TestCase
5
+ def test_timing
6
+ # TODO
7
+ end
8
+ end
data/wtf-tools.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'wtf-tools'
3
+ s.version = '1.0.0'
4
+ s.date = '2014-10-14'
5
+ s.platform = Gem::Platform::RUBY
6
+ s.required_ruby_version = Gem::Requirement.new(">= 2.0.0")
7
+ s.summary = 'tools for debugging and profiling Ruby on Rails projects'
8
+ s.license = 'MIT'
9
+
10
+ s.description = <<-EOF
11
+ WTF-tools offers some flexible options for your puts-style Ruby debugging needs,
12
+ and method-level profiling for Ruby on Rails projects.
13
+ EOF
14
+
15
+ s.files = Dir['{lib/**/*,example/*,test/*}'] + %w(LICENSE Rakefile README.md wtf-tools.gemspec)
16
+ s.require_path = 'lib'
17
+ s.test_files = Dir['test/*.rb']
18
+
19
+ s.author = 'Remigijus Jodelis'
20
+ s.email = 'remigijus.jodelis@gmail.com'
21
+ s.homepage = 'http://github.com/remigijusj/wtf-tools'
22
+
23
+ s.add_runtime_dependency 'absolute_time', '~> 1.0'
24
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wtf-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Remigijus Jodelis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: absolute_time
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ description: |
28
+ WTF-tools offers some flexible options for your puts-style Ruby debugging needs,
29
+ and method-level profiling for Ruby on Rails projects.
30
+ email: remigijus.jodelis@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - LICENSE
36
+ - README.md
37
+ - Rakefile
38
+ - example/dump.rb
39
+ - example/timing.rb
40
+ - example/tracking.rb
41
+ - lib/wtf-tools.rb
42
+ - lib/wtf/dumper.rb
43
+ - lib/wtf/method_tracker.rb
44
+ - lib/wtf/query_tracker.rb
45
+ - test/test_dumper.rb
46
+ - test/test_method_tracker.rb
47
+ - test/test_timing.rb
48
+ - wtf-tools.gemspec
49
+ homepage: http://github.com/remigijusj/wtf-tools
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.0
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.2.2
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: tools for debugging and profiling Ruby on Rails projects
73
+ test_files:
74
+ - test/test_dumper.rb
75
+ - test/test_method_tracker.rb
76
+ - test/test_timing.rb