flipper 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.6.1
2
+
3
+ * Added statsd support for instrumentation.
4
+
1
5
  ## 0.4.0
2
6
 
3
7
  * No longer use #id for detecting actors. You must now define #flipper_id on
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ gemspec
3
3
 
4
4
  gem 'rake'
5
5
  gem 'metriks', :require => false
6
+ gem 'statsd-ruby', :require => false
6
7
  gem 'rspec'
7
8
  gem 'rack-test'
8
9
  gem 'activesupport', :require => false
data/README.md CHANGED
@@ -1,4 +1,21 @@
1
- ![flipper logo](https://raw.github.com/jnunemaker/flipper-ui/master/lib/flipper/ui/public/flipper/images/logo.png)
1
+ ![flipper logo](https://raw.github.com/jnunemaker/flipper-ui/master/lib/flipper/ui/public/images/logo.png)
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
- # Public: Use this as the subscribed block.
11
- def self.call(name, start, ending, transaction_id, payload)
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
- # Private: Initializes a new event processing instance.
16
- def initialize(name, start, ending, transaction_id, payload)
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,6 @@
1
+ require 'securerandom'
2
+ require 'active_support/notifications'
3
+ require 'flipper/instrumentation/statsd_subscriber'
4
+
5
+ ActiveSupport::Notifications.subscribe /\.flipper$/,
6
+ Flipper::Instrumentation::StatsdSubscriber
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = "0.6.0"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -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
@@ -15,6 +15,8 @@ Bundler.setup(:default)
15
15
 
16
16
  require 'flipper'
17
17
 
18
+ Dir[root_path.join("spec/support/**/*.rb")].each { |f| require f }
19
+
18
20
  RSpec.configure do |config|
19
21
  config.fail_fast = true
20
22
 
@@ -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.0
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-02-20 00:00:00.000000000 Z
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: 592688489057647185
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: 592688489057647185
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