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