chronometer 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2dc6f536c735b6262063d5a3091e7675208e093ec96951006bec8dca45be910e
4
- data.tar.gz: d636d8a2d12a4b63d7edc2b1e685dd072e87b728d5984b75a0f9767f140009a2
3
+ metadata.gz: 6c6623661c6007a3257437cac6a4e7d132283fac8cfe20735578398074a3d426
4
+ data.tar.gz: 5fd920b984dcbfb9a205e8b2dbfa9edbb631d66bea949b25eb3ff94e355bc2e8
5
5
  SHA512:
6
- metadata.gz: 5d7f49faecbf4ebed41c43d4640a22ff9f9db2ece0cf6907f38a039f7fd05b10187464b260679228dd3c3a56b7db041231d9b44dfcd02b692d1b0e385aa5a42f
7
- data.tar.gz: 513375c1f1b6a04070deb65253d17fd4598ce3cde492e3436f9797dc7953db126bb8d594ed597081097e31629e2dec54ad1fa0c0e09084b1f88b754ff84411f0
6
+ metadata.gz: ed6ebd6637e45305e2bd89c79d7a37941a092f3c0a03a13463af2bbab138d01586a6dbae1a694c42096eee7412bf9c5086157b047ce80d632a0ad8d3ba7e2023
7
+ data.tar.gz: 0ac9cbd6bf0c0159ee75c348a65a572ad64872ed39dc929e787f629ad465bd54112b626c3257020306539eea7a513778b0b9316bf87a14a00d869f534f108531
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at segiddins@squareup.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Samuel Giddins
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.
@@ -0,0 +1,43 @@
1
+ # Chronometer
2
+
3
+ A small gem that generates Chrome trace files for Ruby programs.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'chronometer'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install chronometer
20
+
21
+ ## Usage
22
+
23
+ ```
24
+ $ chronometer CocoaPods.chronofile -- pod --version
25
+ ```
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/chronometer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the Chronometer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/chronometer/blob/master/CODE_OF_CONDUCT.md).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.3
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'chronometer/dsl'
4
+ require_relative 'chronometer/event'
5
+ require_relative 'chronometer/trace_event'
6
+ require_relative 'chronometer/version'
7
+
8
+ class Chronometer
9
+ attr_reader :trace_events
10
+
11
+ def self.from_file(path, contents: File.read(path))
12
+ new do
13
+ instance_eval(contents, path)
14
+ end
15
+ end
16
+
17
+ def initialize(&blk)
18
+ dsl = DSL.new
19
+ dsl.instance_exec(&blk)
20
+ @events = dsl.events
21
+ @tracepoints = dsl.tracepoints
22
+ @trace_event_queue = Queue.new
23
+ @trace_events = []
24
+ end
25
+
26
+ def install!
27
+ @events.each { |e| install_method_hook(e) }
28
+ @tracepoints.each { |tp| install_tracepoint(tp) }
29
+ end
30
+
31
+ def drain!
32
+ loop { @trace_events << @trace_event_queue.pop(true) }
33
+ rescue ThreadError
34
+ nil
35
+ end
36
+
37
+ def print_trace_event_report(dest, metadata: {})
38
+ raise ArgumentError, 'cannot manually specify :traceEvents' if metadata.key?(:traceEvents)
39
+ require 'json'
40
+ File.open(dest, 'w') do |f|
41
+ f << JSON.generate(metadata)
42
+ f.seek(-1, :CUR) # remove closing }
43
+ f << ',' unless metadata.empty?
44
+ f << '"traceEvents":['
45
+ @trace_events.each_with_index do |te, i|
46
+ f << ',' unless i == 0
47
+ f << JSON.generate(te.to_h)
48
+ end
49
+ f << ']}'
50
+ end
51
+ end
52
+
53
+ def self.timestamp_us
54
+ Time.now.utc.to_f.*(1_000_000).round
55
+ end
56
+
57
+ def register_trace_event(event)
58
+ @trace_event_queue << event
59
+ end
60
+
61
+ private
62
+
63
+ def install_method_hook(event)
64
+ cls = event.cls
65
+ method = event.method
66
+
67
+ unbound_method = cls.instance_method(method)
68
+ arg_labels = unbound_method.parameters.map(&:last)
69
+
70
+ timer = self
71
+
72
+ cls.send(:define_method, method) do |*args, &blk|
73
+ context = event.context&.call(self)
74
+ args_dict = arg_labels.zip(args).to_h
75
+ args_dict[:context] = context if context
76
+
77
+ start_time = ::Chronometer.timestamp_us
78
+
79
+ event_type = event.event_type
80
+ if event_type == :X
81
+ timer.register_trace_event TraceEvent.new(
82
+ process_id: Process.pid,
83
+ thread_id: Thread.current.object_id,
84
+ start_time_usec: ::Chronometer.timestamp_us,
85
+ event_type: :B,
86
+ name: event.name
87
+ )
88
+ event_type = :E
89
+ end
90
+
91
+ r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
92
+ begin
93
+ unbound_method.bind(self).call(*args, &blk)
94
+ ensure
95
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC).-(r0).*(1_000_000).round
96
+
97
+ timer.register_trace_event TraceEvent.new(
98
+ process_id: Process.pid,
99
+ thread_id: Thread.current.object_id,
100
+ start_time_usec: event_type == :E ? ::Chronometer.timestamp_us : start_time,
101
+ event_type: event_type,
102
+ name: event.name,
103
+ args: args_dict,
104
+ category: event.category,
105
+ duration: duration,
106
+ cls: cls,
107
+ method: method
108
+ )
109
+ end
110
+ end
111
+ end
112
+
113
+ def install_tracepoint(tracepoint)
114
+ event_name, blk = tracepoint
115
+ TracePoint.trace(event_name) do |tp|
116
+ next if tp.path == __FILE__
117
+ args = {
118
+ process_id: Process.pid,
119
+ thread_id: Thread.current.object_id,
120
+ start_time_usec: ::Chronometer.timestamp_us,
121
+ event_type: :I,
122
+ name: event_name
123
+ }
124
+ args.update blk&.call(tp)
125
+
126
+ te = TraceEvent.new(**args)
127
+ register_trace_event(te)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'claide'
4
+ require 'chronometer'
5
+
6
+ class Chronometer
7
+ module Command; end
8
+ class Command::Chronometer < CLAide::Command
9
+ self.version = VERSION
10
+
11
+ self.summary = "Trace a ruby program's execution using a chronofile"
12
+
13
+ self.arguments = [
14
+ CLAide::Argument.new('CHRONOFILE', true),
15
+ CLAide::Argument.new('RUBY_FILE', true),
16
+ CLAide::Argument.new('ARGUMENTS', false, true)
17
+ ]
18
+
19
+ def self.options
20
+ [
21
+ ['--output=TRACE', 'The path to the tracefile chronometer will write']
22
+ ].concat(super)
23
+ end
24
+
25
+ def initialize(argv)
26
+ @chronofile = argv.shift_argument
27
+ @output = argv.option('output', "#{@chronofile}.trace")
28
+ @file_to_load = argv.shift_argument
29
+ @arguments = argv.remainder! if @chronofile && @file_to_load
30
+ super
31
+ end
32
+
33
+ def validate!
34
+ super
35
+ help! 'Must supply a chronofile' unless @chronofile
36
+ @chronofile_contents = begin
37
+ File.read(@chronofile)
38
+ rescue StandardError
39
+ help!("No such chronofile `#{@chronofile}`")
40
+ end
41
+ help! 'Must supply a ruby file to load' unless @file_to_load
42
+ @file_to_load = ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).push('.').reduce do |a, e|
43
+ next a if a
44
+ a ||= File.join(e, @file_to_load)
45
+ a &&= nil unless File.file?(a)
46
+ a
47
+ end
48
+ help! "Could not find `#{@file_to_load}`" unless @file_to_load
49
+ end
50
+
51
+ def run
52
+ argv = ::ARGV.dup
53
+ ::ARGV.replace(@arguments)
54
+ time { load(@file_to_load) }
55
+ ensure
56
+ ::ARGV.replace(argv)
57
+ end
58
+
59
+ private
60
+
61
+ def time
62
+ timer = ::Chronometer.from_file(@chronofile, contents: @chronofile_contents)
63
+ timer.install!
64
+
65
+ begin
66
+ yield
67
+ ensure
68
+ timer.drain!
69
+ timer.print_trace_event_report(@output, metadata: { meta_success: $ERROR_INFO.nil? })
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Chronometer
4
+ class DSL
5
+ attr_reader :events, :tracepoints
6
+
7
+ def initialize
8
+ @events = []
9
+ @tracepoints = []
10
+ end
11
+
12
+ def for_class(cls)
13
+ @cls && raise('already in for_class')
14
+ @cls = cls
15
+
16
+ yield
17
+ ensure
18
+ @cls = nil
19
+ end
20
+
21
+ def for_singleton_class(cls, &blk)
22
+ for_class(cls.singleton_class, &blk)
23
+ end
24
+
25
+ def method(method_name, **opts)
26
+ opts[:name] ||= @cls.singleton_class? ? "#{ObjectSpace.each_object(@cls).to_a.last}.#{method_name}" : "#{@cls}##{method_name}"
27
+ opts.delete(:method) && raise('Cannot specify :method')
28
+ opts[:method] = method_name
29
+ opts[:event_type] ||= :X
30
+ opts[:cls] = @cls || raise('must be in for_class block')
31
+ events << Event.new(opts)
32
+ end
33
+
34
+ def methods(*method_names, **opts)
35
+ method_names.flatten.each do |method_name|
36
+ method method_name, **opts
37
+ end
38
+ end
39
+
40
+ def tracepoint(event_name, &blk)
41
+ @cls && raise('in for_class block')
42
+
43
+ tracepoints << [event_name, blk]
44
+ end
45
+ end
46
+ private_constant :DSL
47
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Chronometer
4
+ class Event
5
+ attr_reader :cls, :method, :name, :category, :event_type, :context
6
+
7
+ def initialize(cls: nil, method: nil, name: nil, category: nil, event_type: nil, context: nil)
8
+ @cls = cls
9
+ @method = method
10
+ @name = name
11
+ @category = category
12
+ @event_type = event_type
13
+ @context = context
14
+ end
15
+ end
16
+ private_constant :Event
17
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Chronometer
4
+ class TraceEvent
5
+ attr_reader :process_id, :thread_id, :start_time_usec, :event_type, :name, :args, :category, :duration, :cls, :method
6
+
7
+ def initialize(process_id: nil, thread_id: nil, start_time_usec: nil, event_type: nil, name: nil, args: nil, category: nil, duration: nil, cls: nil, method: nil)
8
+ @process_id = process_id
9
+ @thread_id = thread_id
10
+ @start_time_usec = start_time_usec
11
+ @event_type = event_type
12
+ @name = name
13
+ @args = args
14
+ @category = category
15
+ @duration = duration
16
+ @cls = cls
17
+ @method = method
18
+ end
19
+
20
+ def to_h
21
+ compact_hash(
22
+ pid: process_id,
23
+ tid: thread_id,
24
+ ts: start_time_usec,
25
+ ph: event_type,
26
+ name: name,
27
+ args: args,
28
+ cat: category,
29
+ dur: duration
30
+ )
31
+ end
32
+
33
+ if {}.respond_to?(:compact)
34
+ def compact_hash(hash)
35
+ hash.compact
36
+ end
37
+ else
38
+ def compact_hash(hash)
39
+ hash.reject { |_k, v| v.nil? }
40
+ end
41
+ end
42
+ end
43
+ private_constant :TraceEvent
44
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Chronometer
4
+ VERSION = File.read(File.expand_path('../../VERSION', __dir__)).strip.freeze
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chronometer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Giddins
@@ -88,7 +88,17 @@ executables:
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
+ - CODE_OF_CONDUCT.md
92
+ - LICENSE.txt
93
+ - README.md
94
+ - VERSION
91
95
  - exe/chronometer
96
+ - lib/chronometer.rb
97
+ - lib/chronometer/command.rb
98
+ - lib/chronometer/dsl.rb
99
+ - lib/chronometer/event.rb
100
+ - lib/chronometer/trace_event.rb
101
+ - lib/chronometer/version.rb
92
102
  homepage: https://github.com/segiddins
93
103
  licenses:
94
104
  - MIT