cabin 0.1.8 → 0.2.1
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.
- data/lib/cabin/metrics.rb +85 -0
- data/lib/cabin/metrics/counter.rb +29 -0
- data/lib/cabin/metrics/gauge.rb +15 -0
- data/lib/cabin/metrics/meter.rb +27 -0
- data/lib/cabin/metrics/timer.rb +63 -0
- data/lib/cabin/mixins/CAPSLOCK.rb +24 -0
- data/lib/cabin/mixins/logger.rb +5 -1
- data/lib/cabin/namespace.rb +1 -0
- data/lib/cabin/outputs/stdlib-logger.rb +3 -1
- data/test/all.rb +13 -0
- data/test/test_metrics.rb +54 -0
- metadata +26 -15
- data/lib/cabin/mixins/metrics.rb +0 -16
@@ -0,0 +1,85 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
require "cabin/metrics/gauge"
|
3
|
+
require "cabin/metrics/meter"
|
4
|
+
require "cabin/metrics/counter"
|
5
|
+
require "cabin/metrics/timer"
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
# What type of metrics do we want?
|
10
|
+
#
|
11
|
+
# What metrics should come by default?
|
12
|
+
# Per-call/transaction/request metrics like:
|
13
|
+
# - hit (count++ type metrics)
|
14
|
+
# - latencies/timings
|
15
|
+
#
|
16
|
+
# Per app or generally long-lifetime metrics like:
|
17
|
+
# - "uptime"
|
18
|
+
# - cpu usage
|
19
|
+
# - memory usage
|
20
|
+
# - count of active/in-flight actions/requests/calls/transactions
|
21
|
+
# - peer metrics (number of cluster members, etc)
|
22
|
+
# ------------------------------------------------------------------
|
23
|
+
# https://github.com/codahale/metrics/tree/master/metrics-core/src/main/java/com/yammer/metrics/core
|
24
|
+
# Reading what Coda Hale's "Metrics" stuff has, here's my summary:
|
25
|
+
#
|
26
|
+
# gauges (callback to return a number)
|
27
|
+
# counters (.inc and .dec methods)
|
28
|
+
# meters (.mark to track each 'hit')
|
29
|
+
# Also exposes 1, 5, 15 minute moving averages
|
30
|
+
# histograms: (.update(value) to record a new value)
|
31
|
+
# like meter, but takes values more than simply '1'
|
32
|
+
# as a result, exposes percentiles, median, etc.
|
33
|
+
# timers
|
34
|
+
# combination of meter + histogram
|
35
|
+
# meter for invocations, histogram for duration
|
36
|
+
#
|
37
|
+
# With the exception of gauges, all the other metrics are all active/pushed.
|
38
|
+
# Gauges take callbacks, so their values are pulled, not pushed. The active
|
39
|
+
# metrics can be represented as events since they the update occurs at the
|
40
|
+
# time of the change.
|
41
|
+
#
|
42
|
+
# These active/push metrics can therefore be considered events.
|
43
|
+
#
|
44
|
+
# All metrics (active/passive) can be queried for 'current state', too,
|
45
|
+
# making this suitable for serving to interested parties like monitoring
|
46
|
+
# and management tools.
|
47
|
+
class Cabin::Metrics
|
48
|
+
include Enumerable
|
49
|
+
|
50
|
+
public
|
51
|
+
def initialize
|
52
|
+
@metrics = {}
|
53
|
+
end # def initialize
|
54
|
+
|
55
|
+
private
|
56
|
+
def create(instance, name, metric_object)
|
57
|
+
return @metrics[[instance, name]] = metric_object
|
58
|
+
end # def create
|
59
|
+
|
60
|
+
public
|
61
|
+
def counter(instance, name=nil)
|
62
|
+
return create(instance, name, Cabin::Metrics::Counter.new)
|
63
|
+
end # def counter
|
64
|
+
|
65
|
+
public
|
66
|
+
def meter(instance, name=nil)
|
67
|
+
return create(instance, name, Cabin::Metrics::Meter.new)
|
68
|
+
end # def meter
|
69
|
+
|
70
|
+
#public
|
71
|
+
#def histogram(instance, name)
|
72
|
+
#return create(instance, name, Cabin::Metrics::Histogram.new)
|
73
|
+
#end # def histogram
|
74
|
+
|
75
|
+
public
|
76
|
+
def timer(instance, name=nil)
|
77
|
+
return create(instance, name, Cabin::Metrics::Timer.new)
|
78
|
+
end # def timer
|
79
|
+
|
80
|
+
# iterate over each metric. yields identifer, metric
|
81
|
+
def each(&block)
|
82
|
+
# delegate to the @metrics hash until we need something fancier
|
83
|
+
@metrics.each(&block)
|
84
|
+
end # def each
|
85
|
+
end # class Cabin::Metrics
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
class Cabin::Metrics::Counter
|
5
|
+
# A new Counter.
|
6
|
+
#
|
7
|
+
# Counters can be incremented and decremented only by 1 at a time..
|
8
|
+
public
|
9
|
+
def initialize
|
10
|
+
@value = 0
|
11
|
+
@lock = Mutex.new
|
12
|
+
end # def initialize
|
13
|
+
|
14
|
+
# increment this counter
|
15
|
+
def incr
|
16
|
+
@lock.synchronize { @value += 1 }
|
17
|
+
end # def incr
|
18
|
+
|
19
|
+
# decrement this counter
|
20
|
+
def decr
|
21
|
+
@lock.synchronize { @value -= 1 }
|
22
|
+
end # def decr
|
23
|
+
|
24
|
+
# Get the value of this metric.
|
25
|
+
public
|
26
|
+
def value
|
27
|
+
return @lock.synchronize { @value }
|
28
|
+
end # def value
|
29
|
+
end # class Cabin::Metrics::Counter
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
|
3
|
+
class Cabin::Metrics::Gauge
|
4
|
+
# A new Gauge. The block given will be called every time the metric is read.
|
5
|
+
public
|
6
|
+
def initialize(&block)
|
7
|
+
@block = block
|
8
|
+
end # def initialize
|
9
|
+
|
10
|
+
# Get the value of this metric.
|
11
|
+
public
|
12
|
+
def value
|
13
|
+
return @block.call
|
14
|
+
end # def value
|
15
|
+
end # class Cabin::Metrics::Gauge
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
class Cabin::Metrics::Meter
|
5
|
+
# A new Meter
|
6
|
+
#
|
7
|
+
# Counters can be incremented and decremented only by 1 at a time..
|
8
|
+
public
|
9
|
+
def initialize
|
10
|
+
@value = 0
|
11
|
+
@lock = Mutex.new
|
12
|
+
end # def initialize
|
13
|
+
|
14
|
+
# Mark an event
|
15
|
+
def mark
|
16
|
+
@lock.synchronize do
|
17
|
+
@value += 1
|
18
|
+
# TODO(sissel): Keep some moving averages?
|
19
|
+
end
|
20
|
+
end # def mark
|
21
|
+
|
22
|
+
# Get the value of this metric.
|
23
|
+
public
|
24
|
+
def value
|
25
|
+
return @lock.synchronize { @value }
|
26
|
+
end # def value
|
27
|
+
end # class Cabin::Metrics::Counter
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
class Cabin::Metrics::Timer
|
5
|
+
# A new Timer metric
|
6
|
+
#
|
7
|
+
# Timers behave like a combination of Meter and Histogram. Every Timer
|
8
|
+
# invocation is metered and the duration of the timer is put into the
|
9
|
+
# Histogram.
|
10
|
+
public
|
11
|
+
def initialize
|
12
|
+
@invocations = 0
|
13
|
+
@lock = Mutex.new
|
14
|
+
end # def initialize
|
15
|
+
|
16
|
+
# Start timing something.
|
17
|
+
#
|
18
|
+
# If no block is given
|
19
|
+
# If a block is given, the execution of that block is timed.
|
20
|
+
#
|
21
|
+
public
|
22
|
+
def time(&block)
|
23
|
+
return time_block(&block) if block_given?
|
24
|
+
|
25
|
+
# Return an object we can .stop
|
26
|
+
return TimerContext.new(method(:record))
|
27
|
+
end # def time
|
28
|
+
|
29
|
+
private
|
30
|
+
def time_block(&block)
|
31
|
+
start = Time.now
|
32
|
+
block.call
|
33
|
+
record(Time.now - start)
|
34
|
+
end # def time_block
|
35
|
+
|
36
|
+
public
|
37
|
+
def record(duration)
|
38
|
+
@lock.synchronize do
|
39
|
+
@invocations += 1
|
40
|
+
# TODO(sissel): histogram the duration
|
41
|
+
end
|
42
|
+
end # def record
|
43
|
+
|
44
|
+
# Get the number of times this timer has been used
|
45
|
+
public
|
46
|
+
def count
|
47
|
+
return @lock.synchronize { @invocations }
|
48
|
+
end # def value
|
49
|
+
|
50
|
+
class TimerContext
|
51
|
+
public
|
52
|
+
def initialize(&stop_callback)
|
53
|
+
@start = Time.now
|
54
|
+
@callback = stop_callback
|
55
|
+
end
|
56
|
+
|
57
|
+
public
|
58
|
+
def stop
|
59
|
+
duration = Time.now - @start
|
60
|
+
@callback.call(duration)
|
61
|
+
end # def stop
|
62
|
+
end # class TimerContext
|
63
|
+
end # class Cabin::Metrics::Counter
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "cabin/namespace"
|
2
|
+
|
3
|
+
# ALL CAPS MEANS SERIOUS BUSINESS
|
4
|
+
module Cabin::Mixins::CAPSLOCK
|
5
|
+
def log(level, message, data={})
|
6
|
+
if message.is_a?(Hash)
|
7
|
+
data.merge!(message)
|
8
|
+
else
|
9
|
+
data[:message] = message
|
10
|
+
end
|
11
|
+
|
12
|
+
# CAPITALIZE ALL THE STRINGS
|
13
|
+
data.each do |key, value|
|
14
|
+
value.upcase! if value.respond_to?(:upcase!)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add extra debugging bits (file, line, method) if level is debug.
|
18
|
+
debugharder(caller.collect { |c| c.upcase }, data) if @level == :debug
|
19
|
+
|
20
|
+
data[:level] = level.upcase
|
21
|
+
|
22
|
+
publish(data)
|
23
|
+
end # def log
|
24
|
+
end # module Cabin::Mixins::CAPSLOCK
|
data/lib/cabin/mixins/logger.rb
CHANGED
@@ -13,7 +13,11 @@ module Cabin::Mixins::Logger
|
|
13
13
|
}
|
14
14
|
|
15
15
|
def level=(value)
|
16
|
-
|
16
|
+
if value.respond_to?(:downcase)
|
17
|
+
@level = value.downcase.to_sym
|
18
|
+
else
|
19
|
+
@level = value.to_sym
|
20
|
+
end
|
17
21
|
end # def level
|
18
22
|
|
19
23
|
# Define the usual log methods: info, fatal, etc.
|
data/lib/cabin/namespace.rb
CHANGED
@@ -8,15 +8,17 @@ class Cabin::Outputs::StdlibLogger
|
|
8
8
|
public
|
9
9
|
def initialize(logger)
|
10
10
|
@logger = logger
|
11
|
+
@logger.level = logger.class::DEBUG
|
11
12
|
end # def initialize
|
12
13
|
|
13
14
|
# Receive an event
|
14
15
|
public
|
15
16
|
def <<(data)
|
16
|
-
method = data[:level] ||
|
17
|
+
method = data[:level].downcase.to_sym || :info
|
17
18
|
|
18
19
|
message = "#{data[:message]} #{data.to_json}"
|
19
20
|
|
21
|
+
#p [@logger.level, logger.class::DEBUG]
|
20
22
|
# This will call @logger.info(data) or something similar.
|
21
23
|
@logger.send(method, message)
|
22
24
|
end # def <<
|
data/test/all.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "minitest/autorun"
|
5
|
+
require "simplecov"
|
6
|
+
|
7
|
+
SimpleCov.start
|
8
|
+
|
9
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
10
|
+
Dir.glob(File.join(dir, "**", "test_*.rb")).each do |path|
|
11
|
+
puts "Loading tests from #{path}"
|
12
|
+
require path
|
13
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
$: << File.dirname(__FILE__)
|
2
|
+
$: << File.join(File.dirname(__FILE__), "..", "lib")
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require "minitest-patch"
|
6
|
+
require "cabin"
|
7
|
+
require "cabin/metrics"
|
8
|
+
require "minitest/autorun" if __FILE__ == $0
|
9
|
+
|
10
|
+
describe Cabin::Metrics do
|
11
|
+
before do
|
12
|
+
@metrics = Cabin::Metrics.new
|
13
|
+
end
|
14
|
+
|
15
|
+
#test "gauge" do
|
16
|
+
#gauge = @metrics.gauge(self) { 3 }
|
17
|
+
#assert_equal(3, gauge.value)
|
18
|
+
## metrics.first == [identifier, Gauge]
|
19
|
+
#assert_equal(3, @metrics.first.last.value)
|
20
|
+
#end
|
21
|
+
|
22
|
+
test "counter" do
|
23
|
+
counter = @metrics.counter(self)
|
24
|
+
0.upto(30) do |i|
|
25
|
+
assert_equal(i, counter.value)
|
26
|
+
counter.incr
|
27
|
+
end
|
28
|
+
31.downto(0) do |i|
|
29
|
+
assert_equal(i, counter.value)
|
30
|
+
counter.decr
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
test "meter counter" do
|
35
|
+
meter = @metrics.meter(self)
|
36
|
+
30.times do |i|
|
37
|
+
assert_equal(i, meter.value)
|
38
|
+
meter.mark
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
test "meter time-based averages" # TODO(sissel): implement
|
43
|
+
|
44
|
+
test "timer counter" do
|
45
|
+
timer = @metrics.timer(self)
|
46
|
+
30.times do |i|
|
47
|
+
assert_equal(i, timer.count)
|
48
|
+
timer.time { true }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
test "timer histogram" # TODO(sissel): implement
|
53
|
+
test "histogram" # TODO(sissel): implement
|
54
|
+
end # describe Cabin::Channel do
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cabin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 21
|
5
|
+
prerelease:
|
5
6
|
segments:
|
6
7
|
- 0
|
8
|
+
- 2
|
7
9
|
- 1
|
8
|
-
|
9
|
-
version: 0.1.8
|
10
|
+
version: 0.2.1
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Jordan Sissel
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2012-
|
18
|
+
date: 2012-02-05 00:00:00 -08:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
@@ -25,12 +26,13 @@ dependencies:
|
|
25
26
|
requirements:
|
26
27
|
- - ">="
|
27
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
28
30
|
segments:
|
29
31
|
- 0
|
30
32
|
version: "0"
|
31
33
|
type: :runtime
|
32
34
|
version_requirements: *id001
|
33
|
-
description: This is an experiment to try and make logging more flexible and more consumable. Plain text logs are bullshit, let's emit structured and contextual logs.
|
35
|
+
description: This is an experiment to try and make logging more flexible and more consumable. Plain text logs are bullshit, let's emit structured and contextual logs. Metrics, too!
|
34
36
|
email:
|
35
37
|
- jls@semicomplete.com
|
36
38
|
executables:
|
@@ -40,21 +42,28 @@ extensions: []
|
|
40
42
|
extra_rdoc_files: []
|
41
43
|
|
42
44
|
files:
|
43
|
-
- lib/cabin.rb
|
44
|
-
- lib/cabin/outputs/em/stdlib-logger.rb
|
45
|
-
- lib/cabin/outputs/stdlib-logger.rb
|
46
|
-
- lib/cabin/channel.rb
|
47
|
-
- lib/cabin/namespace.rb
|
48
|
-
- lib/cabin/timer.rb
|
49
45
|
- lib/cabin/context.rb
|
50
|
-
- lib/cabin/
|
51
|
-
- lib/cabin/
|
46
|
+
- lib/cabin/timer.rb
|
47
|
+
- lib/cabin/outputs/stdlib-logger.rb
|
48
|
+
- lib/cabin/outputs/em/stdlib-logger.rb
|
52
49
|
- lib/cabin/mixins/logger.rb
|
50
|
+
- lib/cabin/mixins/dragons.rb
|
51
|
+
- lib/cabin/mixins/CAPSLOCK.rb
|
52
|
+
- lib/cabin/namespace.rb
|
53
|
+
- lib/cabin/metrics/gauge.rb
|
54
|
+
- lib/cabin/metrics/timer.rb
|
55
|
+
- lib/cabin/metrics/meter.rb
|
56
|
+
- lib/cabin/metrics/counter.rb
|
57
|
+
- lib/cabin/metrics.rb
|
58
|
+
- lib/cabin/channel.rb
|
59
|
+
- lib/cabin.rb
|
53
60
|
- examples/fibonacci-timing.rb
|
54
61
|
- examples/sinatra-logging.rb
|
55
62
|
- examples/sample.rb
|
56
|
-
- test/minitest-patch.rb
|
57
63
|
- test/test_logging.rb
|
64
|
+
- test/minitest-patch.rb
|
65
|
+
- test/test_metrics.rb
|
66
|
+
- test/all.rb
|
58
67
|
- LICENSE
|
59
68
|
- CHANGELIST
|
60
69
|
- bin/rubygems-cabin-test
|
@@ -73,6 +82,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
82
|
requirements:
|
74
83
|
- - ">="
|
75
84
|
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
76
86
|
segments:
|
77
87
|
- 0
|
78
88
|
version: "0"
|
@@ -81,13 +91,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
91
|
requirements:
|
82
92
|
- - ">="
|
83
93
|
- !ruby/object:Gem::Version
|
94
|
+
hash: 3
|
84
95
|
segments:
|
85
96
|
- 0
|
86
97
|
version: "0"
|
87
98
|
requirements: []
|
88
99
|
|
89
100
|
rubyforge_project:
|
90
|
-
rubygems_version: 1.
|
101
|
+
rubygems_version: 1.6.2
|
91
102
|
signing_key:
|
92
103
|
specification_version: 3
|
93
104
|
summary: Experiments in structured and contextual logging
|
data/lib/cabin/mixins/metrics.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
require "cabin/namespace"
|
2
|
-
|
3
|
-
# What kind of metrics do we want?
|
4
|
-
# Per-call/transaction/request metrics like:
|
5
|
-
# - hit (count++ type metrics)
|
6
|
-
# - latencies/timings
|
7
|
-
#
|
8
|
-
# Per app or generally long-lifetime metrics like:
|
9
|
-
# - "uptime"
|
10
|
-
# - cpu usage
|
11
|
-
# - memory usage
|
12
|
-
# - count of active/in-flight actions/requests/calls/transactions
|
13
|
-
# - peer metrics (number of cluster members, etc)
|
14
|
-
module Cabin::Mixins::Metrics
|
15
|
-
|
16
|
-
end # module Cabin::Mixins::Metrics
|