skylight 0.0.16 → 0.1.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -19
  3. data/bin/skylight +0 -2
  4. data/lib/skylight.rb +52 -42
  5. data/lib/skylight/api.rb +34 -0
  6. data/lib/skylight/cli.rb +68 -57
  7. data/lib/skylight/compat.rb +19 -5
  8. data/lib/skylight/config.rb +219 -98
  9. data/lib/skylight/gc.rb +109 -0
  10. data/lib/skylight/instrumenter.rb +53 -70
  11. data/lib/skylight/messages.rb +19 -0
  12. data/lib/skylight/messages/annotation.rb +13 -0
  13. data/lib/skylight/messages/base.rb +24 -0
  14. data/lib/skylight/messages/batch.rb +11 -0
  15. data/lib/skylight/messages/endpoint.rb +12 -0
  16. data/lib/skylight/messages/event.rb +12 -0
  17. data/lib/skylight/messages/hello.rb +53 -0
  18. data/lib/skylight/messages/span.rb +21 -0
  19. data/lib/skylight/messages/trace.rb +162 -0
  20. data/lib/skylight/middleware.rb +2 -4
  21. data/lib/skylight/normalizers.rb +89 -0
  22. data/lib/skylight/normalizers/default.rb +22 -0
  23. data/lib/skylight/normalizers/process_action.rb +19 -0
  24. data/lib/skylight/normalizers/render_collection.rb +14 -0
  25. data/lib/skylight/normalizers/render_partial.rb +14 -0
  26. data/lib/skylight/normalizers/render_template.rb +14 -0
  27. data/lib/skylight/{normalize → normalizers}/send_file.rb +15 -15
  28. data/lib/skylight/normalizers/sql.rb +25 -0
  29. data/lib/skylight/railtie.rb +21 -41
  30. data/lib/skylight/subscriber.rb +29 -19
  31. data/lib/skylight/util/clock.rb +8 -21
  32. data/lib/skylight/util/http.rb +93 -46
  33. data/lib/skylight/util/logging.rb +66 -0
  34. data/lib/skylight/util/queue.rb +7 -3
  35. data/lib/skylight/util/task.rb +154 -0
  36. data/lib/skylight/{compat → vendor/active_support}/notifications.rb +56 -24
  37. data/lib/skylight/{compat → vendor/active_support}/notifications/fanout.rb +19 -26
  38. data/lib/skylight/{compat → vendor/active_support}/notifications/instrumenter.rb +25 -18
  39. data/lib/skylight/vendor/active_support/per_thread_registry.rb +52 -0
  40. data/lib/skylight/vendor/beefcake.rb +256 -0
  41. data/lib/skylight/vendor/beefcake/buffer.rb +112 -0
  42. data/lib/skylight/vendor/beefcake/decode.rb +107 -0
  43. data/lib/skylight/vendor/beefcake/encode.rb +115 -0
  44. data/lib/skylight/vendor/{highline.rb → cli/highline.rb} +0 -0
  45. data/lib/skylight/vendor/{highline → cli/highline}/color_scheme.rb +0 -0
  46. data/lib/skylight/vendor/{highline → cli/highline}/compatibility.rb +0 -0
  47. data/lib/skylight/vendor/{highline → cli/highline}/import.rb +0 -0
  48. data/lib/skylight/vendor/{highline → cli/highline}/menu.rb +0 -0
  49. data/lib/skylight/vendor/{highline → cli/highline}/question.rb +0 -0
  50. data/lib/skylight/vendor/{highline → cli/highline}/simulate.rb +0 -0
  51. data/lib/skylight/vendor/{highline → cli/highline}/string_extensions.rb +0 -0
  52. data/lib/skylight/vendor/{highline → cli/highline}/style.rb +0 -0
  53. data/lib/skylight/vendor/{highline → cli/highline}/system_extensions.rb +0 -0
  54. data/lib/skylight/vendor/{thor.rb → cli/thor.rb} +0 -0
  55. data/lib/skylight/vendor/{thor → cli/thor}/actions.rb +0 -0
  56. data/lib/skylight/vendor/{thor → cli/thor}/actions/create_file.rb +0 -0
  57. data/lib/skylight/vendor/{thor → cli/thor}/actions/create_link.rb +0 -0
  58. data/lib/skylight/vendor/{thor → cli/thor}/actions/directory.rb +0 -0
  59. data/lib/skylight/vendor/{thor → cli/thor}/actions/empty_directory.rb +0 -0
  60. data/lib/skylight/vendor/{thor → cli/thor}/actions/file_manipulation.rb +0 -0
  61. data/lib/skylight/vendor/{thor → cli/thor}/actions/inject_into_file.rb +0 -0
  62. data/lib/skylight/vendor/{thor → cli/thor}/base.rb +0 -0
  63. data/lib/skylight/vendor/{thor → cli/thor}/command.rb +0 -0
  64. data/lib/skylight/vendor/{thor → cli/thor}/core_ext/hash_with_indifferent_access.rb +0 -0
  65. data/lib/skylight/vendor/{thor → cli/thor}/core_ext/io_binary_read.rb +0 -0
  66. data/lib/skylight/vendor/{thor → cli/thor}/core_ext/ordered_hash.rb +0 -0
  67. data/lib/skylight/vendor/{thor → cli/thor}/error.rb +0 -0
  68. data/lib/skylight/vendor/{thor → cli/thor}/group.rb +0 -0
  69. data/lib/skylight/vendor/{thor → cli/thor}/invocation.rb +0 -0
  70. data/lib/skylight/vendor/{thor → cli/thor}/parser.rb +0 -0
  71. data/lib/skylight/vendor/{thor → cli/thor}/parser/argument.rb +0 -0
  72. data/lib/skylight/vendor/{thor → cli/thor}/parser/arguments.rb +0 -0
  73. data/lib/skylight/vendor/{thor → cli/thor}/parser/option.rb +0 -0
  74. data/lib/skylight/vendor/{thor → cli/thor}/parser/options.rb +0 -0
  75. data/lib/skylight/vendor/{thor → cli/thor}/rake_compat.rb +0 -0
  76. data/lib/skylight/vendor/{thor → cli/thor}/runner.rb +0 -0
  77. data/lib/skylight/vendor/{thor → cli/thor}/shell.rb +0 -0
  78. data/lib/skylight/vendor/{thor → cli/thor}/shell/basic.rb +0 -0
  79. data/lib/skylight/vendor/{thor → cli/thor}/shell/color.rb +0 -0
  80. data/lib/skylight/vendor/{thor → cli/thor}/shell/html.rb +0 -0
  81. data/lib/skylight/vendor/{thor → cli/thor}/util.rb +0 -0
  82. data/lib/skylight/vendor/{thor → cli/thor}/version.rb +0 -0
  83. data/lib/skylight/vendor/thread_safe.rb +126 -0
  84. data/lib/skylight/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  85. data/lib/skylight/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  86. data/lib/skylight/version.rb +2 -1
  87. data/lib/skylight/worker.rb +12 -154
  88. data/lib/skylight/worker/builder.rb +72 -0
  89. data/lib/skylight/worker/collector.rb +124 -0
  90. data/lib/skylight/worker/connection.rb +77 -0
  91. data/lib/skylight/worker/embedded.rb +6 -0
  92. data/lib/skylight/worker/server.rb +307 -0
  93. data/lib/skylight/worker/standalone.rb +356 -0
  94. metadata +89 -77
  95. data/lib/skylight/connection.rb +0 -25
  96. data/lib/skylight/json_proto.rb +0 -88
  97. data/lib/skylight/normalize.rb +0 -63
  98. data/lib/skylight/normalize/default.rb +0 -17
  99. data/lib/skylight/normalize/process_action.rb +0 -17
  100. data/lib/skylight/normalize/render_collection.rb +0 -11
  101. data/lib/skylight/normalize/render_partial.rb +0 -14
  102. data/lib/skylight/normalize/render_template.rb +0 -13
  103. data/lib/skylight/normalize/sql.rb +0 -26
  104. data/lib/skylight/normalize/start_processing.rb +0 -12
  105. data/lib/skylight/sanity_checker.rb +0 -73
  106. data/lib/skylight/trace.rb +0 -160
  107. data/lib/skylight/util/atomic.rb +0 -73
  108. data/lib/skylight/util/bytes.rb +0 -40
  109. data/lib/skylight/util/ewma.rb +0 -32
  110. data/lib/skylight/util/uuid.rb +0 -33
@@ -27,13 +27,17 @@ module Skylight
27
27
  @mutex.synchronize { __length }
28
28
  end
29
29
 
30
- # Returns true if the item was queued, false otherwise
30
+ # Returns the number of items in the queue or nil if the queue is full
31
31
  def push(obj)
32
+ ret = nil
33
+
32
34
  @mutex.synchronize do
33
- return false if __length == @max
35
+ return if __length == @max
34
36
  @values[@produce] = obj
35
37
  @produce = (@produce + 1) % @max
36
38
 
39
+ ret = __length
40
+
37
41
  # Wakeup a blocked thread
38
42
  begin
39
43
  t = @waiting.shift
@@ -43,7 +47,7 @@ module Skylight
43
47
  end
44
48
  end
45
49
 
46
- true
50
+ ret
47
51
  end
48
52
 
49
53
  def pop(timeout = nil)
@@ -0,0 +1,154 @@
1
+ require 'thread'
2
+
3
+ module Skylight
4
+ module Util
5
+ class Task
6
+ SHUTDOWN = :__SK_TASK_SHUTDOWN
7
+
8
+ include Util::Logging
9
+
10
+ def initialize(size, timeout = 0.1, &blk)
11
+ @pid = Process.pid
12
+ @thread = nil
13
+ @size = size
14
+ @lock = Mutex.new
15
+ @timeout = timeout
16
+ @blk = blk
17
+ end
18
+
19
+ def submit(msg, pid = Process.pid)
20
+ return unless @pid
21
+
22
+ spawn(pid)
23
+
24
+ return unless q = @queue
25
+
26
+ !!q.push(msg)
27
+ end
28
+
29
+ def spawn(pid = Process.pid)
30
+ unless spawned?
31
+ __spawn(pid)
32
+ end
33
+
34
+ true
35
+ end
36
+
37
+ def spawned?
38
+ !!@thread
39
+ end
40
+
41
+ def running?
42
+ spawned? && @pid
43
+ end
44
+
45
+ def shutdown(timeout = 5)
46
+ t = nil
47
+ m = false
48
+
49
+ @lock.synchronize do
50
+ t = @thread
51
+
52
+ if q = @queue
53
+ m = true
54
+ q.push(SHUTDOWN)
55
+ @pid = nil
56
+ end
57
+ end
58
+
59
+ return true if timeout && timeout < 0
60
+ return true unless t
61
+
62
+ ret = nil
63
+
64
+ begin
65
+ ret = !!t.join(timeout)
66
+ ensure
67
+ if !ret && m
68
+ begin
69
+ t.kill # FORCE KILL!!!
70
+ rescue ThreadError
71
+ end
72
+ end
73
+ end
74
+
75
+ ret
76
+ end
77
+
78
+ private
79
+
80
+ def __spawn(pid)
81
+ @lock.synchronize do
82
+ return if spawned? && @pid == pid
83
+ @pid = Process.pid
84
+ @queue = Util::Queue.new(@size)
85
+ @thread = Thread.new do
86
+ begin
87
+ unless work
88
+ @queue = nil
89
+ end
90
+
91
+ finish
92
+ rescue Exception => e
93
+ error "failed to execute task; msg=%s", e.message
94
+ t { e.backtrace.join("\n") }
95
+ end
96
+ end
97
+ end
98
+
99
+ true
100
+ end
101
+
102
+ def work
103
+ return unless q = @queue
104
+
105
+ while @pid
106
+ if msg = q.pop(@timeout)
107
+ return true if SHUTDOWN == msg
108
+
109
+ unless __handle(msg)
110
+ return false
111
+ end
112
+ else
113
+ return unless @queue
114
+ # just a tick
115
+ unless __handle(msg)
116
+ return false
117
+ end
118
+ end
119
+ end
120
+
121
+ # Drain the queue
122
+ while msg = q.pop(0)
123
+ return true if SHUTDOWN == msg
124
+
125
+ unless __handle(msg)
126
+ return false
127
+ end
128
+ end
129
+
130
+ true
131
+ end
132
+
133
+ def __handle(msg)
134
+ begin
135
+ handle(msg)
136
+ rescue Exception => e
137
+ error "error handling event; msg=%s; event=%p", e.message, msg
138
+ t { e.backtrace.join("\n") }
139
+ sleep 1
140
+ true
141
+ end
142
+ end
143
+
144
+ def handle(msg)
145
+ return true unless @blk
146
+ @blk.call(msg)
147
+ end
148
+
149
+ def finish
150
+ end
151
+
152
+ end
153
+ end
154
+ end
@@ -1,22 +1,24 @@
1
- require 'skylight/compat/notifications/instrumenter'
2
- require 'skylight/compat/notifications/fanout'
1
+ require 'skylight/vendor/active_support/notifications/instrumenter'
2
+ require 'skylight/vendor/active_support/notifications/fanout'
3
+ require 'skylight/vendor/active_support/per_thread_registry'
3
4
 
4
5
  module ActiveSupport
5
6
  # = Notifications
6
7
  #
7
- # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for Ruby.
8
+ # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
9
+ # Ruby.
8
10
  #
9
11
  # == Instrumenters
10
12
  #
11
13
  # To instrument an event you just need to do:
12
14
  #
13
- # ActiveSupport::Notifications.instrument("render", :extra => :information) do
14
- # render :text => "Foo"
15
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
16
+ # render text: 'Foo'
15
17
  # end
16
18
  #
17
19
  # That executes the block first and notifies all subscribers once done.
18
20
  #
19
- # In the example above "render" is the name of the event, and the rest is called
21
+ # In the example above +render+ is the name of the event, and the rest is called
20
22
  # the _payload_. The payload is a mechanism that allows instrumenters to pass
21
23
  # extra information to subscribers. Payloads consist of a hash whose contents
22
24
  # are arbitrary and generally depend on the event.
@@ -24,25 +26,35 @@ module ActiveSupport
24
26
  # == Subscribers
25
27
  #
26
28
  # You can consume those events and the information they provide by registering
27
- # a subscriber. For instance, let's store all "render" events in an array:
29
+ # a subscriber.
30
+ #
31
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
32
+ # name # => String, name of the event (such as 'render' from above)
33
+ # start # => Time, when the instrumented block started execution
34
+ # finish # => Time, when the instrumented block ended execution
35
+ # id # => String, unique ID for this notification
36
+ # payload # => Hash, the payload
37
+ # end
38
+ #
39
+ # For instance, let's store all "render" events in an array:
28
40
  #
29
41
  # events = []
30
42
  #
31
- # ActiveSupport::Notifications.subscribe("render") do |*args|
43
+ # ActiveSupport::Notifications.subscribe('render') do |*args|
32
44
  # events << ActiveSupport::Notifications::Event.new(*args)
33
45
  # end
34
46
  #
35
47
  # That code returns right away, you are just subscribing to "render" events.
36
48
  # The block is saved and will be called whenever someone instruments "render":
37
49
  #
38
- # ActiveSupport::Notifications.instrument("render", :extra => :information) do
39
- # render :text => "Foo"
50
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
51
+ # render text: 'Foo'
40
52
  # end
41
53
  #
42
54
  # event = events.first
43
55
  # event.name # => "render"
44
56
  # event.duration # => 10 (in milliseconds)
45
- # event.payload # => { :extra => :information }
57
+ # event.payload # => { extra: :information }
46
58
  #
47
59
  # The block in the <tt>subscribe</tt> call gets the name of the event, start
48
60
  # timestamp, end timestamp, a string with a unique identifier for that event
@@ -63,7 +75,7 @@ module ActiveSupport
63
75
  # module ActionController
64
76
  # class PageRequest
65
77
  # def call(name, started, finished, unique_id, payload)
66
- # Rails.logger.debug ["notification:", name, started, finished, unique_id, payload].join(" ")
78
+ # Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ')
67
79
  # end
68
80
  # end
69
81
  # end
@@ -73,15 +85,15 @@ module ActiveSupport
73
85
  # resulting in the following output within the logs including a hash with the payload:
74
86
  #
75
87
  # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
76
- # :controller=>"Devise::SessionsController",
77
- # :action=>"new",
78
- # :params=>{"action"=>"new", "controller"=>"devise/sessions"},
79
- # :format=>:html,
80
- # :method=>"GET",
81
- # :path=>"/login/sign_in",
82
- # :status=>200,
83
- # :view_runtime=>279.3080806732178,
84
- # :db_runtime=>40.053
88
+ # controller: "Devise::SessionsController",
89
+ # action: "new",
90
+ # params: {"action"=>"new", "controller"=>"devise/sessions"},
91
+ # format: :html,
92
+ # method: "GET",
93
+ # path: "/login/sign_in",
94
+ # status: 200,
95
+ # view_runtime: 279.3080806732178,
96
+ # db_runtime: 40.053
85
97
  # }
86
98
  #
87
99
  # You can also subscribe to all events whose name matches a certain regexp:
@@ -142,9 +154,9 @@ module ActiveSupport
142
154
  notifier.publish(name, *args)
143
155
  end
144
156
 
145
- def instrument(name, payload = {}, &blk)
157
+ def instrument(name, payload = {})
146
158
  if notifier.listening?(name)
147
- instrumenter.instrument(name, payload, &blk)
159
+ instrumenter.instrument(name, payload) { yield payload if block_given? }
148
160
  else
149
161
  yield payload if block_given?
150
162
  end
@@ -166,7 +178,27 @@ module ActiveSupport
166
178
  end
167
179
 
168
180
  def instrumenter
169
- Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
181
+ InstrumentationRegistry.instrumenter_for(notifier)
182
+ end
183
+ end
184
+
185
+ # This class is a registry which holds all of the +Instrumenter+ objects
186
+ # in a particular thread local. To access the +Instrumenter+ object for a
187
+ # particular +notifier+, you can call the following method:
188
+ #
189
+ # InstrumentationRegistry.instrumenter_for(notifier)
190
+ #
191
+ # The instrumenters for multiple notifiers are held in a single instance of
192
+ # this class.
193
+ class InstrumentationRegistry # :nodoc:
194
+ extend ActiveSupport::PerThreadRegistry
195
+
196
+ def initialize
197
+ @registry = {}
198
+ end
199
+
200
+ def instrumenter_for(notifier)
201
+ @registry[notifier] ||= Instrumenter.new(notifier)
170
202
  end
171
203
  end
172
204
 
@@ -1,4 +1,9 @@
1
1
  require 'mutex_m'
2
+ begin
3
+ require 'thread_safe'
4
+ rescue LoadError
5
+ require 'skylight/vendor/thread_safe'
6
+ end
2
7
 
3
8
  module ActiveSupport
4
9
  module Notifications
@@ -11,7 +16,7 @@ module ActiveSupport
11
16
 
12
17
  def initialize
13
18
  @subscribers = []
14
- @listeners_for = {}
19
+ @listeners_for = ThreadSafe::Cache.new
15
20
  super
16
21
  end
17
22
 
@@ -39,16 +44,14 @@ module ActiveSupport
39
44
  listeners_for(name).each { |s| s.finish(name, id, payload) }
40
45
  end
41
46
 
42
- def measure(name, id, payload)
43
- listeners_for(name).each { |s| s.measure(name, id, payload) }
44
- end
45
-
46
47
  def publish(name, *args)
47
48
  listeners_for(name).each { |s| s.publish(name, *args) }
48
49
  end
49
50
 
50
51
  def listeners_for(name)
51
- synchronize do
52
+ # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics)
53
+ @listeners_for[name] || synchronize do
54
+ # use synchronisation when accessing @subscribers
52
55
  @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
53
56
  end
54
57
  end
@@ -63,12 +66,10 @@ module ActiveSupport
63
66
 
64
67
  module Subscribers # :nodoc:
65
68
  def self.new(pattern, listener)
66
- if listener.respond_to?(:call)
67
- subscriber = Timed.new pattern, listener
68
- elsif listener.respond_to?(:measure)
69
+ if listener.respond_to?(:start) and listener.respond_to?(:finish)
69
70
  subscriber = Evented.new pattern, listener
70
71
  else
71
- subscriber = LegacyEvented.new pattern, listener
72
+ subscriber = Timed.new pattern, listener
72
73
  end
73
74
 
74
75
  unless pattern
@@ -82,6 +83,13 @@ module ActiveSupport
82
83
  def initialize(pattern, delegate)
83
84
  @pattern = pattern
84
85
  @delegate = delegate
86
+ @can_publish = delegate.respond_to?(:publish)
87
+ end
88
+
89
+ def publish(name, *args)
90
+ if @can_publish
91
+ @delegate.publish name, *args
92
+ end
85
93
  end
86
94
 
87
95
  def start(name, id, payload)
@@ -92,10 +100,6 @@ module ActiveSupport
92
100
  @delegate.finish name, id, payload
93
101
  end
94
102
 
95
- def measure(name, id, payload)
96
- @delegate.measure(name, id, payload)
97
- end
98
-
99
103
  def subscribed_to?(name)
100
104
  @pattern === name.to_s
101
105
  end
@@ -106,14 +110,7 @@ module ActiveSupport
106
110
  end
107
111
  end
108
112
 
109
- class LegacyEvented < Evented
110
- def measure(name, id, payload)
111
- start(name, id, payload)
112
- finish(name, id, payload)
113
- end
114
- end
115
-
116
- class Timed < LegacyEvented
113
+ class Timed < Evented
117
114
  def initialize(pattern, delegate)
118
115
  @timestack = []
119
116
  super
@@ -146,10 +143,6 @@ module ActiveSupport
146
143
  @delegate.finish name, id, payload
147
144
  end
148
145
 
149
- def measure(name, id, payload)
150
- @delegate.measure name, id, payload
151
- end
152
-
153
146
  def publish(name, *args)
154
147
  @delegate.publish name, *args
155
148
  end
@@ -2,38 +2,45 @@ require 'securerandom'
2
2
 
3
3
  module ActiveSupport
4
4
  module Notifications
5
- # Instrumentors are stored in a thread local.
5
+ # Instrumenters are stored in a thread local.
6
6
  class Instrumenter
7
7
  attr_reader :id
8
8
 
9
9
  def initialize(notifier)
10
- @id = unique_id
10
+ @id = unique_id
11
11
  @notifier = notifier
12
12
  end
13
13
 
14
14
  # Instrument the given block by measuring the time taken to execute it
15
15
  # and publish it. Notice that events get sent even if an error occurs
16
- # in the passed-in block
16
+ # in the passed-in block.
17
17
  def instrument(name, payload={})
18
- if block_given?
19
- @notifier.start(name, @id, payload)
20
- begin
21
- yield payload
22
- rescue Exception => e
23
- payload[:exception] = [e.class.name, e.message]
24
- raise e
25
- ensure
26
- @notifier.finish(name, @id, payload)
27
- end
28
- else
29
- @notifier.measure(name, @id, payload)
18
+ start name, payload
19
+ begin
20
+ yield payload
21
+ rescue Exception => e
22
+ payload[:exception] = [e.class.name, e.message]
23
+ raise e
24
+ ensure
25
+ finish name, payload
30
26
  end
31
27
  end
32
28
 
29
+ # Send a start notification with +name+ and +payload+.
30
+ def start(name, payload)
31
+ @notifier.start name, @id, payload
32
+ end
33
+
34
+ # Send a finish notification with +name+ and +payload+.
35
+ def finish(name, payload)
36
+ @notifier.finish name, @id, payload
37
+ end
38
+
33
39
  private
34
- def unique_id
35
- SecureRandom.hex(10)
36
- end
40
+
41
+ def unique_id
42
+ SecureRandom.hex(10)
43
+ end
37
44
  end
38
45
 
39
46
  class Event