flipper 0.6.0 → 0.6.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/Changelog.md +4 -0
- data/Gemfile +1 -0
- data/README.md +22 -5
- data/lib/flipper/instrumentation/metriks_subscriber.rb +6 -78
- data/lib/flipper/instrumentation/statsd.rb +6 -0
- data/lib/flipper/instrumentation/statsd_subscriber.rb +28 -0
- data/lib/flipper/instrumentation/subscriber.rb +102 -0
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +0 -7
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +76 -0
- data/spec/helper.rb +2 -0
- data/spec/support/fake_udp_socket.rb +27 -0
- metadata +11 -4
data/Changelog.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,21 @@
|
|
1
|
-

|
2
|
+
|
3
|
+
<pre>
|
4
|
+
__
|
5
|
+
_.-~ )
|
6
|
+
_..--~~~~,' ,-/ _
|
7
|
+
.-'. . . .' ,-',' ,' )
|
8
|
+
,'. . . _ ,--~,-'__..-' ,'
|
9
|
+
,'. . . (@)' ---~~~~ ,'
|
10
|
+
/. . . . '~~ ,-'
|
11
|
+
/. . . . . ,-'
|
12
|
+
; . . . . - . ,'
|
13
|
+
: . . . . _ /
|
14
|
+
. . . . . `-.:
|
15
|
+
. . . ./ - . )
|
16
|
+
. . . | _____..---.._/ _____
|
17
|
+
~---~~~~----~~~~ ~~
|
18
|
+
</pre>
|
2
19
|
|
3
20
|
Feature flipping is the act of enabling or disabling features or parts of your application, ideally without re-deploying or changing anything in your code base.
|
4
21
|
|
@@ -18,10 +35,6 @@ Or install it yourself with:
|
|
18
35
|
|
19
36
|
$ gem install flipper
|
20
37
|
|
21
|
-
## Coming Soon™
|
22
|
-
|
23
|
-
* [Web UI](https://github.com/jnunemaker/flipper-ui) (think resque UI for features toggling/status)
|
24
|
-
|
25
38
|
## Usage
|
26
39
|
|
27
40
|
The goal of the API for flipper was to have everything revolve around features and what ways they can be enabled. Start with top level and dig into a feature, then dig in further and enable that feature for a given type of access, as opposed to thinking about how the feature will be accessed first (ie: stats.enable vs activate_group(:stats, ...)).
|
@@ -245,3 +258,7 @@ config.middleware.use Flipper::Middleware::Memoizer, lambda { $flipper }
|
|
245
258
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
246
259
|
4. Push to the branch (`git push origin my-new-feature`)
|
247
260
|
5. Create new Pull Request
|
261
|
+
|
262
|
+
## Coming Soon™
|
263
|
+
|
264
|
+
* [Web UI](https://github.com/jnunemaker/flipper-ui) (think resque UI for features toggling/status)
|
@@ -2,90 +2,18 @@
|
|
2
2
|
# ActiveSupport::Notifications. Instead, you should require the metriks file
|
3
3
|
# that lives in the same directory as this file. The benefit is that it
|
4
4
|
# subscribes to the correct events and does everything for your.
|
5
|
+
require 'flipper/instrumentation/subscriber'
|
5
6
|
require 'metriks'
|
6
7
|
|
7
8
|
module Flipper
|
8
9
|
module Instrumentation
|
9
|
-
class MetriksSubscriber
|
10
|
-
|
11
|
-
|
12
|
-
new(name, start, ending, transaction_id, payload).update
|
10
|
+
class MetriksSubscriber < Subscriber
|
11
|
+
def update_timer(metric)
|
12
|
+
Metriks.timer(metric).update(@duration)
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
@name = name
|
18
|
-
@start = start
|
19
|
-
@ending = ending
|
20
|
-
@payload = payload
|
21
|
-
@duration = ending - start
|
22
|
-
@transaction_id = transaction_id
|
23
|
-
end
|
24
|
-
|
25
|
-
def update
|
26
|
-
operation_type = @name.split('.').first
|
27
|
-
method_name = "update_#{operation_type}_metrics"
|
28
|
-
|
29
|
-
if respond_to?(method_name)
|
30
|
-
send(method_name)
|
31
|
-
else
|
32
|
-
puts "Could not update #{operation_type} metrics as MetriksSubscriber did not respond to `#{method_name}`"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def update_feature_operation_metrics
|
37
|
-
feature_name = @payload[:feature_name]
|
38
|
-
gate_name = @payload[:gate_name]
|
39
|
-
operation = strip_trailing_question_mark(@payload[:operation])
|
40
|
-
result = @payload[:result]
|
41
|
-
thing = @payload[:thing]
|
42
|
-
|
43
|
-
Metriks.timer("flipper.feature_operation.#{operation}").update(@duration)
|
44
|
-
|
45
|
-
if @payload[:operation] == :enabled?
|
46
|
-
metric_name = if result
|
47
|
-
"flipper.feature.#{feature_name}.enabled"
|
48
|
-
else
|
49
|
-
"flipper.feature.#{feature_name}.disabled"
|
50
|
-
end
|
51
|
-
|
52
|
-
Metriks.meter(metric_name).mark
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def update_adapter_operation_metrics
|
57
|
-
adapter_name = @payload[:adapter_name]
|
58
|
-
operation = @payload[:operation]
|
59
|
-
result = @payload[:result]
|
60
|
-
value = @payload[:value]
|
61
|
-
key = @payload[:key]
|
62
|
-
|
63
|
-
Metriks.timer("flipper.adapter.#{adapter_name}.#{operation}").update(@duration)
|
64
|
-
end
|
65
|
-
|
66
|
-
def update_gate_operation_metrics
|
67
|
-
feature_name = @payload[:feature_name]
|
68
|
-
gate_name = @payload[:gate_name]
|
69
|
-
operation = strip_trailing_question_mark(@payload[:operation])
|
70
|
-
result = @payload[:result]
|
71
|
-
thing = @payload[:thing]
|
72
|
-
|
73
|
-
Metriks.timer("flipper.gate_operation.#{gate_name}.#{operation}").update(@duration)
|
74
|
-
Metriks.timer("flipper.feature.#{feature_name}.gate_operation.#{gate_name}.#{operation}").update(@duration)
|
75
|
-
|
76
|
-
if @payload[:operation] == :open?
|
77
|
-
metric_name = if result
|
78
|
-
"flipper.feature.#{feature_name}.gate.#{gate_name}.open"
|
79
|
-
else
|
80
|
-
"flipper.feature.#{feature_name}.gate.#{gate_name}.closed"
|
81
|
-
end
|
82
|
-
|
83
|
-
Metriks.meter(metric_name).mark
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def strip_trailing_question_mark(operation)
|
88
|
-
operation.to_s.gsub(/\?$/, '')
|
15
|
+
def update_counter(metric)
|
16
|
+
Metriks.meter(metric).mark
|
89
17
|
end
|
90
18
|
end
|
91
19
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Note: You should never need to require this file directly if you are using
|
2
|
+
# ActiveSupport::Notifications. Instead, you should require the metriks file
|
3
|
+
# that lives in the same directory as this file. The benefit is that it
|
4
|
+
# subscribes to the correct events and does everything for your.
|
5
|
+
require 'flipper/instrumentation/subscriber'
|
6
|
+
require 'statsd'
|
7
|
+
|
8
|
+
module Flipper
|
9
|
+
module Instrumentation
|
10
|
+
class StatsdSubscriber < Subscriber
|
11
|
+
class << self
|
12
|
+
attr_accessor :client
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_timer(metric)
|
16
|
+
if self.class.client
|
17
|
+
self.class.client.timing metric, (@duration * 1_000).round
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_counter(metric)
|
22
|
+
if self.class.client
|
23
|
+
self.class.client.increment metric
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Instrumentation
|
3
|
+
class Subscriber
|
4
|
+
# Public: Use this as the subscribed block.
|
5
|
+
def self.call(name, start, ending, transaction_id, payload)
|
6
|
+
new(name, start, ending, transaction_id, payload).update
|
7
|
+
end
|
8
|
+
|
9
|
+
# Private: Initializes a new event processing instance.
|
10
|
+
def initialize(name, start, ending, transaction_id, payload)
|
11
|
+
@name = name
|
12
|
+
@start = start
|
13
|
+
@ending = ending
|
14
|
+
@payload = payload
|
15
|
+
@duration = ending - start
|
16
|
+
@transaction_id = transaction_id
|
17
|
+
end
|
18
|
+
|
19
|
+
# Internal: Override in subclass.
|
20
|
+
def update_timer(metric)
|
21
|
+
raise 'not implemented'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Internal: Override in subclass.
|
25
|
+
def update_counter(metric)
|
26
|
+
raise 'not implemented'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Private
|
30
|
+
def update
|
31
|
+
operation_type = @name.split('.').first
|
32
|
+
method_name = "update_#{operation_type}_metrics"
|
33
|
+
|
34
|
+
if respond_to?(method_name)
|
35
|
+
send(method_name)
|
36
|
+
else
|
37
|
+
puts "Could not update #{operation_type} metrics as #{self.class} did not respond to `#{method_name}`"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Private
|
42
|
+
def update_feature_operation_metrics
|
43
|
+
feature_name = @payload[:feature_name]
|
44
|
+
gate_name = @payload[:gate_name]
|
45
|
+
operation = strip_trailing_question_mark(@payload[:operation])
|
46
|
+
result = @payload[:result]
|
47
|
+
thing = @payload[:thing]
|
48
|
+
|
49
|
+
update_timer "flipper.feature_operation.#{operation}"
|
50
|
+
|
51
|
+
if @payload[:operation] == :enabled?
|
52
|
+
metric_name = if result
|
53
|
+
"flipper.feature.#{feature_name}.enabled"
|
54
|
+
else
|
55
|
+
"flipper.feature.#{feature_name}.disabled"
|
56
|
+
end
|
57
|
+
|
58
|
+
update_counter metric_name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Private
|
63
|
+
def update_adapter_operation_metrics
|
64
|
+
adapter_name = @payload[:adapter_name]
|
65
|
+
operation = @payload[:operation]
|
66
|
+
result = @payload[:result]
|
67
|
+
value = @payload[:value]
|
68
|
+
key = @payload[:key]
|
69
|
+
|
70
|
+
|
71
|
+
update_timer "flipper.adapter.#{adapter_name}.#{operation}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Private
|
75
|
+
def update_gate_operation_metrics
|
76
|
+
feature_name = @payload[:feature_name]
|
77
|
+
gate_name = @payload[:gate_name]
|
78
|
+
operation = strip_trailing_question_mark(@payload[:operation])
|
79
|
+
result = @payload[:result]
|
80
|
+
thing = @payload[:thing]
|
81
|
+
|
82
|
+
update_timer "flipper.gate_operation.#{gate_name}.#{operation}"
|
83
|
+
update_timer "flipper.feature.#{feature_name}.gate_operation.#{gate_name}.#{operation}"
|
84
|
+
|
85
|
+
if @payload[:operation] == :open?
|
86
|
+
metric_name = if result
|
87
|
+
"flipper.feature.#{feature_name}.gate.#{gate_name}.open"
|
88
|
+
else
|
89
|
+
"flipper.feature.#{feature_name}.gate.#{gate_name}.closed"
|
90
|
+
end
|
91
|
+
|
92
|
+
update_counter metric_name
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Private
|
97
|
+
def strip_trailing_question_mark(operation)
|
98
|
+
operation.to_s.gsub(/\?$/, '')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/flipper/version.rb
CHANGED
@@ -56,11 +56,4 @@ describe Flipper::Instrumentation::MetriksSubscriber do
|
|
56
56
|
Metriks.meter("flipper.feature.stats.gate.actor.open").count.should be(1)
|
57
57
|
Metriks.meter("flipper.feature.stats.gate.boolean.closed").count.should be(1)
|
58
58
|
end
|
59
|
-
|
60
|
-
# Helper for seeing what is in the metriks registry
|
61
|
-
def print_registry_names
|
62
|
-
Metriks::Registry.default.each do |name, metric|
|
63
|
-
puts name
|
64
|
-
end
|
65
|
-
end
|
66
59
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'flipper/adapters/memory'
|
3
|
+
require 'flipper/instrumentation/statsd'
|
4
|
+
|
5
|
+
describe Flipper::Instrumentation::StatsdSubscriber do
|
6
|
+
let(:statsd_client) { Statsd.new }
|
7
|
+
let(:socket) { FakeUDPSocket.new }
|
8
|
+
let(:adapter) { Flipper::Adapters::Memory.new }
|
9
|
+
let(:flipper) {
|
10
|
+
Flipper.new(adapter, :instrumenter => ActiveSupport::Notifications)
|
11
|
+
}
|
12
|
+
|
13
|
+
let(:user) { user = Struct.new(:flipper_id).new('1') }
|
14
|
+
|
15
|
+
before do
|
16
|
+
described_class.client = statsd_client
|
17
|
+
Thread.current[:statsd_socket] = socket
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
described_class.client = nil
|
22
|
+
Thread.current[:statsd_socket] = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_timer(metric)
|
26
|
+
regex = /#{Regexp.escape metric}\:\d+\|ms/
|
27
|
+
socket.buffer.detect { |op| op.first =~ regex }.should_not be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_counter(metric)
|
31
|
+
socket.buffer.detect { |op| op.first == "#{metric}:1|c" }.should_not be_nil
|
32
|
+
end
|
33
|
+
|
34
|
+
context "for enabled feature" do
|
35
|
+
it "updates feature metrics when calls happen" do
|
36
|
+
flipper[:stats].enable(user)
|
37
|
+
assert_timer 'flipper.feature_operation.enable'
|
38
|
+
|
39
|
+
flipper[:stats].enabled?(user)
|
40
|
+
assert_timer 'flipper.feature_operation.enabled'
|
41
|
+
assert_counter 'flipper.feature.stats.enabled'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "for disabled feature" do
|
46
|
+
it "updates feature metrics when calls happen" do
|
47
|
+
flipper[:stats].disable(user)
|
48
|
+
assert_timer 'flipper.feature_operation.disable'
|
49
|
+
|
50
|
+
flipper[:stats].enabled?(user)
|
51
|
+
assert_timer 'flipper.feature_operation.enabled'
|
52
|
+
assert_counter 'flipper.feature.stats.disabled'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "updates adapter metrics when calls happen" do
|
57
|
+
flipper[:stats].enable(user)
|
58
|
+
assert_timer 'flipper.adapter.memory.enable'
|
59
|
+
|
60
|
+
flipper[:stats].enabled?(user)
|
61
|
+
assert_timer 'flipper.adapter.memory.get'
|
62
|
+
|
63
|
+
flipper[:stats].disable(user)
|
64
|
+
assert_timer 'flipper.adapter.memory.disable'
|
65
|
+
end
|
66
|
+
|
67
|
+
it "updates gate metrics when calls happen" do
|
68
|
+
flipper[:stats].enable(user)
|
69
|
+
flipper[:stats].enabled?(user)
|
70
|
+
|
71
|
+
assert_timer 'flipper.gate_operation.boolean.open'
|
72
|
+
assert_timer 'flipper.feature.stats.gate_operation.boolean.open'
|
73
|
+
assert_counter 'flipper.feature.stats.gate.actor.open'
|
74
|
+
assert_counter 'flipper.feature.stats.gate.boolean.closed'
|
75
|
+
end
|
76
|
+
end
|
data/spec/helper.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
class FakeUDPSocket
|
2
|
+
attr_reader :buffer
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@buffer = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def send(message, *rest)
|
9
|
+
@buffer.push [message]
|
10
|
+
end
|
11
|
+
|
12
|
+
def recv
|
13
|
+
@buffer.shift
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear
|
17
|
+
@buffer = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"<FakeUDPSocket: #{@buffer.inspect}>"
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-04-27 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Feature flipper for any adapter
|
15
15
|
email:
|
@@ -56,6 +56,9 @@ files:
|
|
56
56
|
- lib/flipper/instrumentation/log_subscriber.rb
|
57
57
|
- lib/flipper/instrumentation/metriks.rb
|
58
58
|
- lib/flipper/instrumentation/metriks_subscriber.rb
|
59
|
+
- lib/flipper/instrumentation/statsd.rb
|
60
|
+
- lib/flipper/instrumentation/statsd_subscriber.rb
|
61
|
+
- lib/flipper/instrumentation/subscriber.rb
|
59
62
|
- lib/flipper/instrumenters/memory.rb
|
60
63
|
- lib/flipper/instrumenters/noop.rb
|
61
64
|
- lib/flipper/middleware/memoizer.rb
|
@@ -83,6 +86,7 @@ files:
|
|
83
86
|
- spec/flipper/gates/percentage_of_random_spec.rb
|
84
87
|
- spec/flipper/instrumentation/log_subscriber_spec.rb
|
85
88
|
- spec/flipper/instrumentation/metriks_subscriber_spec.rb
|
89
|
+
- spec/flipper/instrumentation/statsd_subscriber_spec.rb
|
86
90
|
- spec/flipper/instrumenters/memory_spec.rb
|
87
91
|
- spec/flipper/instrumenters/noop_spec.rb
|
88
92
|
- spec/flipper/middleware/memoizer_spec.rb
|
@@ -96,6 +100,7 @@ files:
|
|
96
100
|
- spec/flipper_spec.rb
|
97
101
|
- spec/helper.rb
|
98
102
|
- spec/integration_spec.rb
|
103
|
+
- spec/support/fake_udp_socket.rb
|
99
104
|
homepage: http://jnunemaker.github.com/flipper
|
100
105
|
licenses: []
|
101
106
|
post_install_message:
|
@@ -110,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
110
115
|
version: '0'
|
111
116
|
segments:
|
112
117
|
- 0
|
113
|
-
hash:
|
118
|
+
hash: -2625440176401499604
|
114
119
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
120
|
none: false
|
116
121
|
requirements:
|
@@ -119,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
124
|
version: '0'
|
120
125
|
segments:
|
121
126
|
- 0
|
122
|
-
hash:
|
127
|
+
hash: -2625440176401499604
|
123
128
|
requirements: []
|
124
129
|
rubyforge_project:
|
125
130
|
rubygems_version: 1.8.23
|
@@ -141,6 +146,7 @@ test_files:
|
|
141
146
|
- spec/flipper/gates/percentage_of_random_spec.rb
|
142
147
|
- spec/flipper/instrumentation/log_subscriber_spec.rb
|
143
148
|
- spec/flipper/instrumentation/metriks_subscriber_spec.rb
|
149
|
+
- spec/flipper/instrumentation/statsd_subscriber_spec.rb
|
144
150
|
- spec/flipper/instrumenters/memory_spec.rb
|
145
151
|
- spec/flipper/instrumenters/noop_spec.rb
|
146
152
|
- spec/flipper/middleware/memoizer_spec.rb
|
@@ -154,3 +160,4 @@ test_files:
|
|
154
160
|
- spec/flipper_spec.rb
|
155
161
|
- spec/helper.rb
|
156
162
|
- spec/integration_spec.rb
|
163
|
+
- spec/support/fake_udp_socket.rb
|