tick_tock 0.2.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
+ SHA256:
3
+ metadata.gz: 78fb0e8694ff38d1b76653ed5d713cd0334368f265e041cb43f4895979effb19
4
+ data.tar.gz: 6e2de7ccb4128857a521773c9ddf3ea1b67acaacc753be369414a08c7a9d750b
5
+ SHA512:
6
+ metadata.gz: 87a256c0e37232f5aa37e2605aeca0fc1bd8b62c3aa67a24f42235fb11ac30618cb253991bf5896507554bf70fdd805ee50c10400b3215babe70b79964974317
7
+ data.tar.gz: b15e21a6b9222f418ef40df680b57dfb176e7e59b349faf609216ba1d68ea3d817fba004dacd0f05e94b191090967c3bf9e5789fbb0c6346646dcf534fb48720
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ # RVM local config
15
+ .ruby-version
16
+ .ruby-gemset
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ sudo: false
2
+
3
+ language: ruby
4
+
5
+ rvm:
6
+ - 2.4
7
+ - 2.3
8
+
9
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in tick_tock.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # TickTock
2
+
3
+ [![Build Status](https://img.shields.io/travis/ms-ati/tick_tock/master.svg)](https://travis-ci.org/ms-ati/tick_tock)
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/tick_tock`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ TODO: Delete this and the text above, and describe your gem
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'tick_tock'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install tick_tock
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Development
30
+
31
+ 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.
32
+
33
+ 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).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tick_tock.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tick_tock"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,21 @@
1
+ require "pp"
2
+ require "tick_tock"
3
+
4
+ lazy_enum = [1, 2, 3].lazy
5
+
6
+ slow_proc = ->(n) { sleep(1); n * 2 }
7
+
8
+ # See how these can be defined elsewhere, but end up logging hierarchically
9
+ wrapped_slow_proc =
10
+ TickTock.tick_tock_proc(slow_proc, subject: ->(n) { "Num #{n}" })
11
+
12
+ wrapped_lazy_enum =
13
+ TickTock.tick_tock_lazy(lazy_enum, subject: "Lazy Enum")
14
+
15
+ # Here we both log the block and also hierarchically log what happens inside it:
16
+ result =
17
+ TickTock.tick_tock(subject: "Top Level") do
18
+ wrapped_lazy_enum.map(&wrapped_slow_proc).to_a
19
+ end
20
+
21
+ pp result
@@ -0,0 +1,27 @@
1
+ require "pp"
2
+ require "tick_tock"
3
+
4
+ inputs = [1, 2, 3]
5
+
6
+ slow_proc = ->(n) { sleep(1); n * Thread.current.name.to_i }
7
+
8
+ wrapped_slow_proc = TickTock.tick_tock_proc(
9
+ slow_proc,
10
+ subject: ->(n) { "Thr #{Thread.current.name}, Num #{n}" }
11
+ )
12
+
13
+ result =
14
+ TickTock.tick_tock(subject: "Top Level") do
15
+ thread_proc = TickTock.tick_tock_proc(
16
+ subject: ->(n) { "Thr #{n}" },
17
+ save_context: true
18
+ ) do |n|
19
+ Thread.current.name = n.to_s
20
+ inputs.map(&wrapped_slow_proc)
21
+ end
22
+
23
+ # In each thread, we pick up logging within the saved context
24
+ Array.new(3) { |n| Thread.new(n + 1, &thread_proc) }.map(&:value)
25
+ end
26
+
27
+ pp result
data/lib/tick_tock.rb ADDED
@@ -0,0 +1,174 @@
1
+ require "tick_tock/clock"
2
+ require "tick_tock/version"
3
+
4
+ # {TickTock} makes it easy to wrap your Ruby code to measure nested timings and
5
+ # to log them -- even when the code is asynchronous or lazy, so straight-forward
6
+ # blocks do not work.
7
+ #
8
+ # The TickTock module uses Ruby's
9
+ # {http://ruby-doc.org/core-2.3.4/Module.html#method-i-module_function
10
+ # module_function} directive to enable two different usage patterns:
11
+ # 1. Calling methods directly on the module
12
+ # 2. Including the module into classes as a mix-in
13
+ #
14
+ # == Configuration
15
+ #
16
+ # When calling methods directly on the module, the instance of {TickTock::Clock}
17
+ # configured via {TickTock.clock=} will be used for all calls.
18
+ #
19
+ # On the other hand, when including the TickTock module into a class, the class
20
+ # may choose to override {#clock} to return a specific instance.
21
+ #
22
+ # @example 1. Calling TickTock methods directly on the module
23
+ # TickTock.tick_tock(subject: "a block") do
24
+ # times_2 = ->(n) { n * 2 }
25
+ # [1, 2].map(&TickTock.tick_tock_proc(times_2, subject: :to_s.to_proc))
26
+ # end
27
+ # # => [2, 4]
28
+ #
29
+ # # Logs the following
30
+ # # I, [2018-02-04T14:22:01.1261 #26] INFO -- : > Started a block
31
+ # # I, [2018-02-04T14:22:01.1264 #26] INFO -- : >> Started 1
32
+ # # I, [2018-02-04T14:22:01.1265 #26] INFO -- : << Completed 1 [0.000s]
33
+ # # I, [2018-02-04T14:22:01.1267 #26] INFO -- : >> Started 2
34
+ # # I, [2018-02-04T14:22:01.1267 #26] INFO -- : << Completed 2 [0.000s]
35
+ # # I, [2018-02-04T14:22:01.1268 #26] INFO -- : < Completed a block [0.001s]
36
+ #
37
+ # @example 2. Including the module into classes as a mix-in
38
+ # class Times2
39
+ # include TickTock
40
+ #
41
+ # def call
42
+ # tick_tock(subject: "a block") do
43
+ # [1, 2].map(&tick_tock_proc(method(:times_2), subject: :to_s.to_proc))
44
+ # end
45
+ # end
46
+ #
47
+ # def times_2(n)
48
+ # n * 2
49
+ # end
50
+ # end
51
+ #
52
+ # Times2.new.call
53
+ # # => [2, 4]
54
+ #
55
+ # # Logs the same as above
56
+ #
57
+ module TickTock
58
+ class << self
59
+ # @return [Clock]
60
+ # the configured global instance of Clock, assigned by calling {.clock=}.
61
+ attr_accessor :clock
62
+ end
63
+
64
+ # @return [Clock]
65
+ # when the TickTock module is included in a class, by default returns the
66
+ # configured global instance of Clock. Override {#clock} in classes to
67
+ # return a class-specific instance instead.
68
+ def clock
69
+ self.class.clock
70
+ end
71
+
72
+ module_function
73
+
74
+ # @!group Basics: Start and stop a timing context
75
+
76
+ # Starts a new timing context, returns a "card" representing it.
77
+ #
78
+ # @param subject [Object] Description of the subject we are timing
79
+ # @return [Object] A new card representing the new timing context
80
+ def tick(subject: nil)
81
+ clock.tick(subject: subject)
82
+ end
83
+
84
+ # Completes a timing context represented by the given "card".
85
+ #
86
+ # @param card [Object] A card representing the timing context to complete
87
+ # @return [Object] The card after being marked completed
88
+ def tock(card:)
89
+ clock.tock(card: card)
90
+ end
91
+
92
+ # @!endgroup
93
+
94
+ # @!group Helpers: Wrap an asynchronous construct in a timing context
95
+
96
+ # Executes the given block in a timing context using {.tick} and {.tock}.
97
+ #
98
+ # @param subject [Object] Description of the subject we are timing
99
+ # @return [Object] Return value of the given block
100
+ def tick_tock(subject: nil)
101
+ card = tick(subject: subject)
102
+ yield
103
+ ensure
104
+ tock(card: card) unless card.nil?
105
+ end
106
+
107
+ # Wraps a
108
+ # {http://ruby-doc.org/core-2.3.4/Enumerator/Lazy.html lazy enumerator} with
109
+ # a timing context using *lazy* calls to {.tick} and {.tock}, which will be
110
+ # called when the enumerator starts and completes enumeration respectively.
111
+ #
112
+ # @param lazy_enum_to_wrap [Enumerator::Lazy] Lazy enumerator to wrap
113
+ # @param subject [Object] Description of the subject
114
+ # @return [Enumerator::Lazy] Wrapped lazy enumerator
115
+ def tick_tock_lazy(lazy_enum_to_wrap, subject: nil)
116
+ shared_state = [nil]
117
+
118
+ lazy_tick = proc { shared_state[0] = tick(subject: subject); [] }
119
+ lazy_tock = proc { shared_state[0] = tock(card: shared_state[0]); [] }
120
+
121
+ arr_with_callbacks = [
122
+ [:dummy].lazy.flat_map(&lazy_tick),
123
+ lazy_enum_to_wrap.lazy,
124
+ [:dummy].lazy.flat_map(&lazy_tock)
125
+ ]
126
+
127
+ arr_with_callbacks.lazy.flat_map(&:itself)
128
+ end
129
+
130
+ # Wraps a Proc with a timing context using a call to {.tick_tock}. Can
131
+ # optionally wrap the current nested timing contexts into the Proc, so that
132
+ # when executed asynchronously it will retain the same context.
133
+ #
134
+ # @param callable_to_wrap [Proc, #call]
135
+ # Callable to wrap, given as a normal parameter.
136
+ #
137
+ # @param subject [Object, Proc, #call]
138
+ # Description of the subject, or optionally a function which, when called on
139
+ # the args that are actually eventually passed to the final proc, will
140
+ # generate the actual subject.
141
+ #
142
+ # @param save_context [Boolean]
143
+ # Optionally wrap the currently active timing contexts into the Proc, so
144
+ # that when executed asynchronously it will retain the same context.
145
+ #
146
+ # @param proc_to_wrap [Proc]
147
+ # Alternatively you can pass the callable as a "block" parameter -- only
148
+ # used if callable_to_wrap is nil.
149
+ #
150
+ # @return [Proc]
151
+ # A Proc wrapping the given callable in a timing context.
152
+ def tick_tock_proc(
153
+ callable_to_wrap = nil,
154
+ subject: nil,
155
+ save_context: false,
156
+ &proc_to_wrap
157
+ )
158
+ tt_proc = proc do |*proc_args|
159
+ # if original subject was a Proc, apply it to args to create the subject
160
+ subject = subject&.respond_to?(:call) ? subject.call(*proc_args) : subject
161
+
162
+ tick_tock(subject: subject) do
163
+ (callable_to_wrap || proc_to_wrap).call(*proc_args)
164
+ end
165
+ end
166
+
167
+ save_context ? Locals.wrap_proc(&tt_proc) : tt_proc
168
+ end
169
+
170
+ # @!endgroup
171
+ end
172
+
173
+ # Assigns a global default clock instance
174
+ TickTock.clock = TickTock::Clock.default
@@ -0,0 +1,43 @@
1
+ require "values"
2
+
3
+ module TickTock
4
+ # @api private
5
+ #
6
+ # The representation of a "punch card" used by the default {Punch}
7
+ # implementation
8
+ #
9
+ # @!parse
10
+ # class Card
11
+ # # Constructor accepting keyword args.
12
+ # #
13
+ # # @param subject [Object] Description of subject of this card
14
+ # # @param parent_card [Card, nil]
15
+ # # @param time_in [Time, nil]
16
+ # # @param time_out [Time, nil]
17
+ # # @return [Card]
18
+ # def self.with(subject:, parent_card:, time_in:, time_out:); end
19
+ #
20
+ # # @param subject [Object] Description of subject of this card
21
+ # # @param parent_card [Card, nil]
22
+ # # @param time_in [Time, nil]
23
+ # # @param time_out [Time, nil]
24
+ # def initialize(subject, parent_card, time_in, time_out); end
25
+ #
26
+ # # @return [Card]
27
+ # # a copy of this instance with any given values replaced.
28
+ # #
29
+ # # @param subject [Object] Description of subject of this card
30
+ # # @param parent_card [Card, nil]
31
+ # # @param time_in [Time, nil]
32
+ # # @param time_out [Time, nil]
33
+ # def with(subject: nil, parent_card: nil, time_in: nil, time_out: nil)
34
+ # end
35
+ # end
36
+ #
37
+ # @attr_reader subject [Object] Description of subject of this card
38
+ # @attr_reader parent_card [Card, nil]
39
+ # @attr_reader time_in [Time, nil]
40
+ # @attr_reader time_out [Time, nil]
41
+ class Card < Value.new(:subject, :parent_card, :time_in, :time_out)
42
+ end
43
+ end
@@ -0,0 +1,58 @@
1
+ require "logger"
2
+ require "values"
3
+
4
+ module TickTock
5
+ CardLogger = Value.new(
6
+ :logger,
7
+ :severity,
8
+ :secs_decimals,
9
+ :show_zero_mins,
10
+ :show_zero_hrs
11
+ )
12
+
13
+ class CardLogger
14
+ def self.default
15
+ with(
16
+ logger: Logger.new($stdout),
17
+ severity: Logger::INFO,
18
+ secs_decimals: 3,
19
+ show_zero_mins: false,
20
+ show_zero_hrs: false
21
+ )
22
+ end
23
+
24
+ def call(card)
25
+ logger.add(severity, nil, nil) { format(card) }
26
+ end
27
+
28
+ private
29
+
30
+ def format(card)
31
+ prefix, verb = card.time_out.nil? ? [">", "Started"] : ["<", "Completed"]
32
+ suffix = card.time_out && "[#{elapsed(card.time_out - card.time_in)}]"
33
+ [prefix * depth(card), verb, card.subject, suffix].compact.join(" ")
34
+ end
35
+
36
+ def depth(card, count = 1)
37
+ card.parent_card.nil? ? count : depth(card.parent_card, count + 1)
38
+ end
39
+
40
+ def elapsed(secs)
41
+ if secs < 60 && !show_zero_hrs && !show_zero_mins
42
+ "%.#{secs_decimals}fs" % secs
43
+ else
44
+ secs_width = 3 + secs_decimals # because 3 is 2 digits plus a "."
45
+
46
+ str = "%02dh:%02dm:%0#{secs_width}.#{secs_decimals}fs" % [
47
+ secs / 3600, # hours
48
+ secs / 60 % 60, # minutes
49
+ secs % 60 # seconds
50
+ ]
51
+
52
+ str.sub!("00h:", "") unless show_zero_hrs
53
+ str.sub!("00m:", "") unless show_zero_hrs || show_zero_mins
54
+ str
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,107 @@
1
+ require "tick_tock/card"
2
+ require "tick_tock/card_logger"
3
+ require "tick_tock/locals"
4
+ require "tick_tock/punch"
5
+
6
+ module TickTock
7
+ # A {TickTock::Clock} can {#tick} and then {#tock}. Inspired by the
8
+ # {https://en.wikipedia.org/wiki/Time_clock "punch-card" or "bundy" clock}
9
+ # (where workers carry their own cards to punch in and out): the instances of
10
+ # Clock are immutable, and the states of timing contexts are kept on "cards".
11
+ #
12
+ # == Punch cards
13
+ #
14
+ # This class delegates to a {TickTock::Punch} implementation (via {#punch}) in
15
+ # order to obtain cards, to punch them in, and to punch them out. It does not
16
+ # depend on any details of those classes. It is therefore possible to replace
17
+ # the implementation of {Punch} - for example, to provide an alternate time
18
+ # source, or to use a memory-optimized representation of the punch cards.
19
+ #
20
+ # == Hierarchical state
21
+ #
22
+ # To represent the hierarchy of currently active punch cards, we use {Locals},
23
+ # which allows us to get and set state in a manner analogous to thread-local
24
+ # variables, but which can be captured and accessed across asynchronous
25
+ # contexts.
26
+ #
27
+ # == Logging and any other side-effects
28
+ #
29
+ # Callbacks {#on_tick} and {#on_tock} define logging (and any other
30
+ # {https://en.wikipedia.org/wiki/Side_effect_(computer_science) side-effects})
31
+ # which should take place on *ticks* and *tocks*.
32
+ #
33
+ # @!parse
34
+ # class Clock
35
+ # # Constructor accepting keyword args.
36
+ # #
37
+ # # @param punch [Punch] Implementation of {Punch} to use
38
+ # # @param on_tick [Proc, nil] Callback on {#tick}
39
+ # # @param on_tock [Proc, nil] Callback on {#tock}
40
+ # # @return [Clock]
41
+ # def self.with(punch:, on_tick:, on_tock:); end
42
+ #
43
+ # # @param punch [Punch] Implementation of {Punch} to use
44
+ # # @param on_tick [Proc, nil] Callback on {#tick}
45
+ # # @param on_tock [Proc, nil] Callback on {#tock}
46
+ # def initialize(punch, on_tick, on_tock); end
47
+ #
48
+ # # @return [Clock]
49
+ # # a copy of this instance with any given values replaced.
50
+ # #
51
+ # # @param punch [Punch] Implementation of {Punch} to use
52
+ # # @param on_tick [Proc, nil] Callback on {#tick}
53
+ # # @param on_tock [Proc, nil] Callback on {#tock}
54
+ # def with(punch: nil, on_tick: nil, on_tock: nil); end
55
+ # end
56
+ #
57
+ # @attr_reader punch [Punch] Implementation of {Punch} to use
58
+ # @attr_reader on_tick [Proc, nil] Callback on {#tick}
59
+ # @attr_reader on_tock [Proc, nil] Callback on {#tock}
60
+ class Clock < Value.new(:punch, :on_tick, :on_tock)
61
+ # @return [Clock] an instance with the default configuration
62
+ def self.default
63
+ log_card = CardLogger.default.method(:call)
64
+ with(punch: Punch.default, on_tick: log_card, on_tock: log_card)
65
+ end
66
+
67
+ # Starts timing a new card, calls any given {#on_tick}, and returns it.
68
+ #
69
+ # @param subject [Object] Description of the subject we are timing
70
+ # @return [Object] A new card representing the new timing context
71
+ def tick(subject: nil)
72
+ card = punch.card(subject: subject, parent_card: self.class.current_card)
73
+ card_in = punch.in(card)
74
+ self.class.current_card = card_in
75
+ on_tick&.call(card_in)
76
+ card_in
77
+ end
78
+
79
+ # Completes timing the given card, calls any given {#on_tock}, and returns
80
+ # it.
81
+ #
82
+ # @param card [Object] A card representing the timing context to complete
83
+ # @return [Object] The card after being marked completed
84
+ def tock(card:)
85
+ card_out = punch.out(card)
86
+ self.class.current_card = punch.parent_card_of(card_out)
87
+ on_tock&.call(card_out)
88
+ card_out
89
+ end
90
+
91
+ private
92
+
93
+ # @return [Symbol] Key to store and retrieve current punch card in {Locals}
94
+ CURRENT_CARD = :"__tick_tock/current_card__"
95
+ private_constant :CURRENT_CARD
96
+
97
+ # @return [Punch::Card] The currently active punch card
98
+ def self.current_card
99
+ Locals.key?(CURRENT_CARD) ? TickTock::Locals[CURRENT_CARD] : nil
100
+ end
101
+
102
+ # Sets the currently active punch card
103
+ def self.current_card=(card)
104
+ Locals[CURRENT_CARD] = card
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,115 @@
1
+ module TickTock
2
+ # {Locals} provides the convenience of Thread-local (or Fiber-local) variables
3
+ # to asynchronous code. By "asynchronous", we simply mean that a given Proc
4
+ # may end up executing on a different Thread or Fiber from the one in which
5
+ # it was created -- or equally -- on the same thread, but at a later time!
6
+ #
7
+ # See {.wrap_proc} for an example of how this can be used to capture local
8
+ # state and wrap it with a Proc for later use, independent of how that state
9
+ # may change during the intervening period.
10
+ #
11
+ # {https://github.com/monix/monix/blob/v3.0.0-M3/monix-execution/shared/src/main/scala/monix/execution/misc/Local.scala
12
+ # Here's how similar concepts are implemented} in the Monix library in Scala.
13
+ #
14
+ module Locals
15
+ module_function
16
+
17
+ # @return [Hash] Default context is an empty hash
18
+ DEFAULT_CONTEXT = {}.freeze
19
+ private_constant :DEFAULT_CONTEXT
20
+
21
+ # @return [Symbol] Key under which context is stored as a fiber-local var
22
+ FIBER_LOCAL_KEY = :"__tick_tock/local_context__"
23
+ private_constant :FIBER_LOCAL_KEY
24
+
25
+ # Wraps the current local context into the given proc, so that when it is
26
+ # run it has access to the same local context as when it was wrapped, even
27
+ # if it is run in a different Thread, Fiber, or simply at a later time after
28
+ # the local context was changed.
29
+ #
30
+ # @example Wrap current local context into a Proc object
31
+ # # proc which depends on some local variable
32
+ # a_proc = proc { "proc sees: " + TickTock::Locals[:foo].to_s }
33
+ #
34
+ # # wraps the current state of the locals into the proc
35
+ # TickTock::Locals[:foo] = :bar
36
+ # wrapped_proc = TickTock::Locals.wrap_proc(&a_proc)
37
+ #
38
+ # # later, the state of the locals change, but the wrapped state does not
39
+ # TickTock::Locals[:foo] = 42
40
+ # wrapped_proc.call
41
+ # #=> "proc sees: bar"
42
+ #
43
+ # TickTock::Locals[:foo]
44
+ # #=> 42
45
+ #
46
+ # @param proc_to_wrap [Proc] A proc to wrap with the current local context
47
+ # @return [Proc] Wrapped version of the given `proc_to_wrap`
48
+ def wrap_proc(&proc_to_wrap)
49
+ wrapped_context = context
50
+
51
+ proc do |*args|
52
+ begin
53
+ saved_context = context
54
+ self.context = wrapped_context
55
+ proc_to_wrap.call(*args)
56
+ ensure
57
+ self.context = saved_context
58
+ end
59
+ end
60
+ end
61
+
62
+ # Fetches current value of given `key` in the local context.
63
+ #
64
+ # @param key [String, Symbol] Key to fetch
65
+ # @return [Object] Current value of `key` in local context
66
+ # @raise [KeyError] Raise if key not found
67
+ def [](key)
68
+ context.fetch(key)
69
+ end
70
+
71
+ # Updates the current local context to map *key* to *value*. Works in a
72
+ # non-mutative way, so that any other references to the old context will be
73
+ # left unchanged.
74
+ #
75
+ # @param key [String, Symbol] Key to set a value for
76
+ # @param value [Object] Value to set for `key` in local context
77
+ # @return [Object] The value that was set
78
+ def []=(key, value)
79
+ self.context = context.merge(key => value).freeze
80
+ end
81
+
82
+ # Check if the given key is currently set in the local context.
83
+ #
84
+ # @param key [String, Symbol] Key to check for
85
+ # @return [Boolean] Is the key currently set?
86
+ def key?(key)
87
+ context.key?(key)
88
+ end
89
+
90
+ # Clears the current local context
91
+ def clear!
92
+ self.context = DEFAULT_CONTEXT
93
+ end
94
+
95
+ # @return [Hash{String, Symbol => Object}]
96
+ # The current local context represented as a frozen hash.
97
+ def context
98
+ if Thread.current.key?(FIBER_LOCAL_KEY)
99
+ Thread.current[FIBER_LOCAL_KEY]
100
+ else
101
+ DEFAULT_CONTEXT
102
+ end
103
+ end
104
+
105
+ # @api private
106
+ #
107
+ # Sets the local context to the given frozen hash.
108
+ #
109
+ # @param hsh [Hash] Frozen hash to set as current local context
110
+ def context=(hsh)
111
+ Thread.current[FIBER_LOCAL_KEY] = hsh
112
+ end
113
+ private_class_method :context=
114
+ end
115
+ end
@@ -0,0 +1,30 @@
1
+ require "tick_tock/card"
2
+
3
+ module TickTock
4
+ class Punch < Value.new(:time_now)
5
+ def self.default
6
+ with(time_now: Time.method(:now))
7
+ end
8
+
9
+ def card(subject: nil, parent_card: nil)
10
+ Card.with(
11
+ subject: subject,
12
+ parent_card: parent_card,
13
+ time_in: nil,
14
+ time_out: nil
15
+ )
16
+ end
17
+
18
+ def in(card)
19
+ card.with(time_in: time_now.call)
20
+ end
21
+
22
+ def out(card)
23
+ card.with(time_out: time_now.call)
24
+ end
25
+
26
+ def parent_card_of(card)
27
+ card.parent_card
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module TickTock
2
+ VERSION = "0.2.0"
3
+ end
data/tick_tock.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "tick_tock/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "tick_tock"
7
+ spec.version = TickTock::VERSION
8
+ spec.authors = ["Marc Siegel"]
9
+ spec.email = ["marc@usainnov.com"]
10
+
11
+ spec.summary = "TickTock wraps Ruby code in timing and logging easily!"
12
+ spec.description = "TickTock makes it easy to wrap your Ruby code to "\
13
+ "measure nested timings and to log them -- even when "\
14
+ "the code is asynchronous or lazy, so straight-forward "\
15
+ "blocks do not work. Versions follow SemVer."
16
+ spec.homepage = "https://github.com/ms-ati/tick_tock"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.required_ruby_version = ">= 2.3.0"
26
+
27
+ spec.add_runtime_dependency "values", ">= 1.8.0"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.16"
30
+ spec.add_development_dependency "rake", "~> 12.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+
33
+ spec.add_development_dependency "byebug"
34
+
35
+ spec.add_development_dependency "benchmark-ips"
36
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tick_tock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Marc Siegel
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: values
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.8.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.8.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: benchmark-ips
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: TickTock makes it easy to wrap your Ruby code to measure nested timings
98
+ and to log them -- even when the code is asynchronous or lazy, so straight-forward
99
+ blocks do not work. Versions follow SemVer.
100
+ email:
101
+ - marc@usainnov.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".rspec"
108
+ - ".travis.yml"
109
+ - Gemfile
110
+ - README.md
111
+ - Rakefile
112
+ - bin/console
113
+ - bin/setup
114
+ - examples/lazy_example.rb
115
+ - examples/threaded_example.rb
116
+ - lib/tick_tock.rb
117
+ - lib/tick_tock/card.rb
118
+ - lib/tick_tock/card_logger.rb
119
+ - lib/tick_tock/clock.rb
120
+ - lib/tick_tock/locals.rb
121
+ - lib/tick_tock/punch.rb
122
+ - lib/tick_tock/version.rb
123
+ - tick_tock.gemspec
124
+ homepage: https://github.com/ms-ati/tick_tock
125
+ licenses: []
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 2.3.0
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.7.4
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: TickTock wraps Ruby code in timing and logging easily!
147
+ test_files: []