celluloid-essentials 0.20.0.pre12

Sign up to get free protection for your applications and to get access to all the features.
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