celluloid-essentials 0.20.0.pre12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.env-ci +4 -0
  3. data/.env-dev +4 -0
  4. data/.gitignore +10 -0
  5. data/.gitmodules +3 -0
  6. data/.rspec +5 -0
  7. data/.rubocop.yml +2 -0
  8. data/.travis.yml +29 -0
  9. data/CHANGES.md +0 -0
  10. data/Gemfile +29 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +3 -0
  13. data/Rakefile +9 -0
  14. data/celluloid-essentials.gemspec +24 -0
  15. data/lib/celluloid/essentials.rb +33 -0
  16. data/lib/celluloid/internals/call_chain.rb +15 -0
  17. data/lib/celluloid/internals/cpu_counter.rb +36 -0
  18. data/lib/celluloid/internals/handlers.rb +42 -0
  19. data/lib/celluloid/internals/links.rb +38 -0
  20. data/lib/celluloid/internals/logger.rb +98 -0
  21. data/lib/celluloid/internals/method.rb +33 -0
  22. data/lib/celluloid/internals/properties.rb +28 -0
  23. data/lib/celluloid/internals/receivers.rb +64 -0
  24. data/lib/celluloid/internals/registry.rb +104 -0
  25. data/lib/celluloid/internals/responses.rb +45 -0
  26. data/lib/celluloid/internals/signals.rb +24 -0
  27. data/lib/celluloid/internals/stack.rb +76 -0
  28. data/lib/celluloid/internals/stack/dump.rb +14 -0
  29. data/lib/celluloid/internals/stack/states.rb +74 -0
  30. data/lib/celluloid/internals/stack/summary.rb +14 -0
  31. data/lib/celluloid/internals/task_set.rb +51 -0
  32. data/lib/celluloid/internals/thread_handle.rb +52 -0
  33. data/lib/celluloid/internals/uuid.rb +40 -0
  34. data/lib/celluloid/logging/incident.rb +21 -0
  35. data/lib/celluloid/logging/incident_logger.rb +128 -0
  36. data/lib/celluloid/logging/incident_reporter.rb +48 -0
  37. data/lib/celluloid/logging/log_event.rb +20 -0
  38. data/lib/celluloid/logging/ring_buffer.rb +65 -0
  39. data/lib/celluloid/notifications.rb +95 -0
  40. data/lib/celluloid/probe.rb +75 -0
  41. data/tasks/benchmarks.rake +16 -0
  42. data/tasks/rspec.rake +7 -0
  43. data/tasks/rubocop.rake +4 -0
  44. metadata +117 -0
@@ -0,0 +1,14 @@
1
+ module Celluloid
2
+ module Internals
3
+ class Stack
4
+ class Summary < Stack
5
+
6
+ def initialize(threads)
7
+ super(threads)
8
+ snapshot
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,51 @@
1
+ require 'set'
2
+ require 'forwardable'
3
+
4
+ module Celluloid
5
+ module Internals
6
+ if RUBY_PLATFORM == 'java'
7
+ require 'jruby/synchronized'
8
+
9
+ class TaskSet
10
+ extend Forwardable
11
+ include JRuby::Synchronized
12
+
13
+ def_delegators :@tasks, :<<, :delete, :first, :empty?, :to_a
14
+
15
+ def initialize
16
+ @tasks = Set.new
17
+ end
18
+ end
19
+ elsif RUBY_ENGINE == 'rbx'
20
+ class TaskSet
21
+ def initialize
22
+ @tasks = Set.new
23
+ end
24
+
25
+ def <<(task)
26
+ Rubinius.synchronize(self) { @tasks << task }
27
+ end
28
+
29
+ def delete(task)
30
+ Rubinius.synchronize(self) { @tasks.delete task }
31
+ end
32
+
33
+ def first
34
+ Rubinius.synchronize(self) { @tasks.first }
35
+ end
36
+
37
+ def empty?
38
+ Rubinius.synchronize(self) { @tasks.empty? }
39
+ end
40
+
41
+ def to_a
42
+ Rubinius.synchronize(self) { @tasks.to_a }
43
+ end
44
+ end
45
+ else
46
+ # Assume we're on MRI, where we have the GIL. But what about IronRuby?
47
+ # Or MacRuby. Do people care? This will break Celluloid::Internals::StackDumps
48
+ TaskSet = Set
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ module Celluloid
2
+ module Internals
3
+ # An abstraction around threads from the InternalPool which ensures we don't
4
+ # accidentally do things to threads which have been returned to the pool,
5
+ # such as, say, killing them
6
+ class ThreadHandle
7
+ def initialize(actor_system, role = nil)
8
+ @mutex = Mutex.new
9
+ @join = ConditionVariable.new
10
+
11
+ @thread = actor_system.get_thread do
12
+ Thread.current.role = role
13
+ begin
14
+ yield
15
+ ensure
16
+ @mutex.synchronize do
17
+ @thread = nil
18
+ @join.broadcast
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # Is the thread running?
25
+ def alive?
26
+ @mutex.synchronize { @thread.alive? if @thread }
27
+ end
28
+
29
+ # Forcibly kill the thread
30
+ def kill
31
+ !!@mutex.synchronize { @thread.kill if @thread }
32
+ self
33
+ end
34
+
35
+ # Join to a running thread, blocking until it terminates
36
+ def join(limit = nil)
37
+ raise ThreadError, "Target thread must not be current thread" if @thread == Thread.current
38
+ @mutex.synchronize { @join.wait(@mutex, limit) if @thread }
39
+ self
40
+ end
41
+
42
+ # Obtain the backtrace for this thread
43
+ def backtrace
44
+ @thread.backtrace
45
+ rescue NoMethodError
46
+ # undefined method `backtrace' for nil:NilClass
47
+ # Swallow this in case this ThreadHandle was terminated and @thread was
48
+ # set to nil
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,40 @@
1
+ require 'securerandom'
2
+
3
+ module Celluloid
4
+ module Internals
5
+ # Clearly Ruby doesn't have enough UUID libraries
6
+ # This one aims to be fast and simple with good support for multiple threads
7
+ # If there's a better UUID library I can use with similar multithreaded
8
+ # performance, I certainly wouldn't mind using a gem for this!
9
+ module UUID
10
+ values = SecureRandom.hex(9).match(/(.{8})(.{4})(.{3})(.{3})/)
11
+ PREFIX = "#{values[1]}-#{values[2]}-4#{values[3]}-8#{values[4]}".freeze
12
+ BLOCK_SIZE = 0x10000
13
+
14
+ @counter = 0
15
+ @counter_mutex = Mutex.new
16
+
17
+ def self.generate
18
+ thread = Thread.current
19
+
20
+ unless thread.uuid_limit
21
+ @counter_mutex.synchronize do
22
+ block_base = @counter
23
+ @counter += BLOCK_SIZE
24
+ thread.uuid_counter = block_base
25
+ thread.uuid_limit = @counter - 1
26
+ end
27
+ end
28
+
29
+ counter = thread.uuid_counter
30
+ if thread.uuid_counter >= thread.uuid_limit
31
+ thread.uuid_counter = thread.uuid_limit = nil
32
+ else
33
+ thread.uuid_counter += 1
34
+ end
35
+
36
+ "#{PREFIX}-#{sprintf('%012x', counter)}".freeze
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module Celluloid
2
+ # Wraps all events and context for a single incident.
3
+ class Incident
4
+ attr_accessor :pid
5
+ attr_accessor :events, :triggering_event
6
+
7
+ def initialize(events, triggering_event=nil)
8
+ @events = events
9
+ @triggering_event = triggering_event
10
+ @pid = $$
11
+ end
12
+
13
+ # Merge two incidents together. This may be useful if two incidents occur at the same time.
14
+ def merge(*other_incidents)
15
+ merged_events = other_incidents.flatten.inject(events) do |events, incident|
16
+ events += incident.events
17
+ end
18
+ Incident.new(merged_events.sort, triggering_event)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,128 @@
1
+ require 'logger'
2
+ module Celluloid
3
+ # A logger that holds all messages in circular buffers, then flushes the buffers
4
+ # when an event occurs at a configurable severity threshold.
5
+ #
6
+ # Unlike ruby's Logger, this class only supports a single progname.
7
+ class IncidentLogger
8
+ module Severity
9
+ include ::Logger::Severity
10
+
11
+ TRACE = -1
12
+
13
+ def severity_to_string(severity)
14
+ case severity
15
+ when TRACE then 'TRACE'
16
+ when DEBUG then 'DEBUG'
17
+ when INFO then 'INFO'
18
+ when WARN then 'WARN'
19
+ when ERROR then 'ERROR'
20
+ when FATAL then 'FATAL'
21
+ when UNKNOWN then 'UNKNOWN'
22
+ end
23
+ end
24
+ end
25
+ include Severity
26
+
27
+ # The progname (facility) for this instance.
28
+ attr_accessor :progname
29
+
30
+ # The logging level. Messages below this severity will not be logged at all.
31
+ attr_accessor :level
32
+
33
+ # The incident threshold. Messages at or above this severity will generate an
34
+ # incident and be published to incident reporters.
35
+ attr_accessor :threshold
36
+
37
+ # The buffer size limit. Each log level will retain this number of messages
38
+ # at maximum.
39
+ attr_accessor :sizelimit
40
+
41
+ attr_accessor :buffers
42
+
43
+ # Create a new IncidentLogger.
44
+ def initialize(progname=nil, options={})
45
+ @progname = progname || "default"
46
+ @level = options[:level] || DEBUG
47
+ @threshold = options[:threshold] || ERROR
48
+ @sizelimit = options[:sizelimit] || 100
49
+
50
+ @buffer_mutex = Mutex.new
51
+ @buffers = Hash.new do |progname_hash, _progname|
52
+ @buffer_mutex.synchronize do
53
+ progname_hash[_progname] = Hash.new do |severity_hash, severity|
54
+ severity_hash[severity] = RingBuffer.new(@sizelimit)
55
+ end
56
+ end
57
+ end
58
+
59
+ # When the IncidentLogger itself encounters an error, it falls back to logging to stderr
60
+ @fallback_logger = ::Logger.new(STDERR)
61
+ @fallback_logger.progname = "FALLBACK"
62
+ end
63
+
64
+ # add an event.
65
+ def add(severity, message=nil, progname=nil, &block)
66
+ progname ||= @progname
67
+ severity ||= UNKNOWN
68
+
69
+ if severity < @level
70
+ return event.id
71
+ end
72
+
73
+ if message.nil? && !block_given?
74
+ message = progname
75
+ progname = @progname
76
+ end
77
+
78
+ event = LogEvent.new(severity, message, progname, &block)
79
+
80
+ @buffers[progname][severity] << event
81
+
82
+ if severity >= @threshold
83
+ begin
84
+ Celluloid::Notifications.notifier.async.publish(incident_topic, create_incident(event))
85
+ rescue => ex
86
+ @fallback_logger.error(ex)
87
+ end
88
+ end
89
+ event.id
90
+ end
91
+ alias :log :add
92
+
93
+ # See docs for Logger#info
94
+ def trace (progname=nil, &block); add(TRACE, nil, progname, &block); end
95
+ def debug (progname=nil, &block); add(DEBUG, nil, progname, &block); end
96
+ def info (progname=nil, &block); add(INFO, nil, progname, &block); end
97
+ def warn (progname=nil, &block); add(WARN, nil, progname, &block); end
98
+ def error (progname=nil, &block); add(ERROR, nil, progname, &block); end
99
+ def fatal (progname=nil, &block); add(FATAL, nil, progname, &block); end
100
+ def unknown (progname=nil, &block); add(UNKNOWN, nil, progname, &block); end
101
+
102
+ def flush
103
+ messages = []
104
+ @buffer_mutex.synchronize do
105
+ @buffers.each do |progname, severities|
106
+ severities.each do |severity, buffer|
107
+ messages += buffer.flush
108
+ end
109
+ end
110
+ end
111
+ messages.sort
112
+ end
113
+
114
+ def clear
115
+ @buffer_mutex.synchronize do
116
+ @buffers.each(&:clear)
117
+ end
118
+ end
119
+
120
+ def create_incident(event=nil)
121
+ Incident.new(flush, event)
122
+ end
123
+
124
+ def incident_topic
125
+ "log.incident.#{@progname}"
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,48 @@
1
+ require 'logger'
2
+ module Celluloid
3
+ # Subscribes to log incident topics to report on them.
4
+ class IncidentReporter
5
+ include Celluloid
6
+ include Celluloid::Notifications
7
+
8
+ # get the time from the event
9
+ class Formatter < ::Logger::Formatter
10
+ def call(severity, time, progname, msg)
11
+ super(severity, msg.time, progname, msg.message)
12
+ end
13
+ end
14
+
15
+ def initialize(*args)
16
+ subscribe(/log\.incident/, :report)
17
+ @logger = ::Logger.new(*args)
18
+ @logger.formatter = Formatter.new
19
+ @silenced = false
20
+ end
21
+
22
+ def report(topic, incident)
23
+ return if @silenced
24
+
25
+ header = "INCIDENT"
26
+ header << " AT #{incident.triggering_event.time}" if incident.triggering_event
27
+ @logger << header
28
+ @logger << "\n"
29
+ @logger << "====================\n"
30
+ incident.events.each do |event|
31
+ @logger.add(event.severity, event, event.progname)
32
+ end
33
+ @logger << "====================\n"
34
+ end
35
+
36
+ def silence
37
+ @silenced = true
38
+ end
39
+
40
+ def unsilence
41
+ @silenced = false
42
+ end
43
+
44
+ def silenced?
45
+ @silenced
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ module Celluloid
2
+ # Wraps a single log event.
3
+ class LogEvent
4
+ attr_accessor :id, :severity, :message, :progname, :time
5
+
6
+ def initialize(severity, message, progname, time=Time.now, &block)
7
+ # This id should be ordered. For now relies on Celluloid::UUID to be ordered.
8
+ # May want to use a generation/counter strategy for independence of uuid.
9
+ @id = Internals::UUID.generate
10
+ @severity = severity
11
+ @message = block_given? ? yield : message
12
+ @progname = progname
13
+ @time = time
14
+ end
15
+
16
+ def <=>(other)
17
+ @id <=> other.id
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,65 @@
1
+ module Celluloid
2
+ class RingBuffer
3
+ def initialize(size)
4
+ @size = size
5
+ @start = 0
6
+ @count = 0
7
+ @buffer = Array.new(size)
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def full?
12
+ @count == @size
13
+ end
14
+
15
+ def empty?
16
+ @count == 0
17
+ end
18
+
19
+ def push(value)
20
+ @mutex.synchronize do
21
+ stop = (@start + @count) % @size
22
+ @buffer[stop] = value
23
+ if full?
24
+ @start = (@start + 1) % @size
25
+ else
26
+ @count += 1
27
+ end
28
+ value
29
+ end
30
+ end
31
+ alias :<< :push
32
+
33
+ def shift
34
+ @mutex.synchronize do
35
+ remove_element
36
+ end
37
+ end
38
+
39
+ def flush
40
+ values = []
41
+ @mutex.synchronize do
42
+ while !empty?
43
+ values << remove_element
44
+ end
45
+ end
46
+ values
47
+ end
48
+
49
+ def clear
50
+ @buffer = Array.new(@size)
51
+ @start = 0
52
+ @count = 0
53
+ end
54
+
55
+ private
56
+
57
+ def remove_element
58
+ return nil if empty?
59
+ value, @buffer[@start] = @buffer[@start], nil
60
+ @start = (@start + 1) % @size
61
+ @count -= 1
62
+ value
63
+ end
64
+ end
65
+ end