expri 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/expri.rb ADDED
@@ -0,0 +1,10 @@
1
+ require_relative "expri/dumper"
2
+ require_relative "expri/metric"
3
+ require_relative "expri/metric_set"
4
+
5
+ module Expri
6
+ def self.metrics(options={}, &block)
7
+ options[:verbose] ||= true
8
+ MetricSet.new(options).run(&block)
9
+ end
10
+ end
@@ -0,0 +1,52 @@
1
+ module Expri
2
+ class Dumper
3
+ def initialize(options={})
4
+ @options = options
5
+ end
6
+
7
+ def run
8
+ str = ""
9
+ str << header
10
+ metrics = Metric.list
11
+ metrics.each do |metric|
12
+ metric = Metric.new(metric, {}, @options)
13
+ metric.get
14
+ str << dump_metric(metric)
15
+ end
16
+ str << footer
17
+ end
18
+
19
+ private
20
+
21
+ NUMERICAL_ATTRIBUTES = %w{divisor frequency precentile window}.
22
+ map { |x| x.to_sym }.freeze
23
+
24
+ def dump_metric(metric)
25
+ attributes = metric.attributes_without_defaults.dup
26
+
27
+ type = attributes.delete(:type)
28
+ predicate = attributes.delete(:predicate)
29
+
30
+ # at bare minimum, window is required, so attributes size should always
31
+ # be > 0 even after type and predicate are removed
32
+ attributes_str = attributes.to_a.
33
+ sort_by { |a| a[0] }.
34
+ map { |a| "#{a[0]}: #{NUMERICAL_ATTRIBUTES.include?(a[0]) ? a[1] : %{"#{a[1]}"}}" }.
35
+ join(",\n ")
36
+
37
+ # predicate is important, but optional
38
+ str = %{ metric "#{metric.name}",\n}
39
+ str << %{ type: :#{type},\n}
40
+ str << %{ predicate: #{predicate},\n} if predicate
41
+ str << %{ #{attributes_str}\n\n}
42
+ end
43
+
44
+ def footer
45
+ "end"
46
+ end
47
+
48
+ def header
49
+ "Expri.metrics do\n\n"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,91 @@
1
+ module Expri
2
+ class Metric
3
+ ATTRIBUTE_DEFAULTS = { :frequency => 3 }.freeze
4
+ VALID_ATTRIBUTES = %w{divisor frequency key percentile predicate predicate2
5
+ type window value}.map { |x| x.to_sym }.freeze
6
+
7
+ attr_accessor :attributes, :name, :options
8
+
9
+ def self.list(options={})
10
+ metric = Metric.new(nil, {}, {})
11
+ MultiJson.decode(metric.api.get(path: "/metrics", expects: 200).body)
12
+ end
13
+
14
+ def initialize(name, attributes={}, options={})
15
+ @name = name
16
+ @options = options
17
+ self.attributes = attributes
18
+ end
19
+
20
+ def ==(other)
21
+ attributes_without_defaults == other.attributes_without_defaults
22
+ end
23
+
24
+ def api
25
+ @api ||= Excon.new(api_url)
26
+ end
27
+
28
+ def attributes=(attributes)
29
+ # symbolize keys
30
+ attributes.keys.each do |key|
31
+ attributes[(key.to_sym rescue key) || key] = attributes.delete(key)
32
+ end
33
+
34
+ attributes.delete_if { |k, v| !VALID_ATTRIBUTES.include?(k) }
35
+ attributes.delete_if { |k, v| v == nil }
36
+
37
+ # manual normalization
38
+ attributes[:type] = attributes[:type].to_sym if attributes[:type]
39
+
40
+ @attributes = attributes
41
+ end
42
+
43
+ # only the attributes that are not at their default value
44
+ def attributes_without_defaults
45
+ attributes = @attributes.dup
46
+ attributes.delete_if { |k, v| ATTRIBUTE_DEFAULTS[k] == v }
47
+ end
48
+
49
+ def destroy
50
+ puts "destroy name=#{name}" if @options[:verbose]
51
+ api.delete(path: "/metrics/#{name}", body: MultiJson.encode(@attributes),
52
+ expects: 200)
53
+ end
54
+
55
+ def get
56
+ puts "show name=#{name}" if @options[:verbose]
57
+ self.attributes =
58
+ MultiJson.decode(api.get(path: "/metrics/#{name}", expects: 200).body)
59
+ end
60
+
61
+ def post
62
+ puts "create name=#{name}" if @options[:verbose]
63
+ # exprd's put is like most api's post
64
+ api.put(path: "/metrics/#{name}", body: MultiJson.encode(@attributes),
65
+ expects: 200)
66
+ rescue Excon::Errors::BadRequest => e
67
+ message = MultiJson.decode(e.response.body)["message"]
68
+ raise(message)
69
+ end
70
+
71
+ def put
72
+ begin
73
+ existing = Metric.new(name, {}, @options)
74
+ existing.get
75
+ if self != existing
76
+ existing.destroy
77
+ post
78
+ end
79
+ rescue Excon::Errors::NotFound
80
+ post
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def api_url
87
+ @options[:exprd_api_url] || ENV["EXPRD_API_URL"] ||
88
+ raise("need EXPRD_API")
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,39 @@
1
+ module Expri
2
+ class MetricSet
3
+ def metric(name, attributes={}, options={})
4
+ @metrics << Metric.new(name, attributes, @options.merge(options))
5
+ end
6
+
7
+ def initialize(options={})
8
+ @options = { :destroy_extras => true }.merge(options)
9
+ end
10
+
11
+ def run(&block)
12
+ puts "run options=#{@options}" if @options[:verbose]
13
+ cache_current if @options[:destroy_extras]
14
+
15
+ @metrics = []
16
+ instance_eval(&block)
17
+ @metrics.each do |metric|
18
+ metric.put
19
+ end
20
+
21
+ destroy_extras if @options[:destroy_extras]
22
+ end
23
+
24
+ private
25
+
26
+ def cache_current
27
+ @current_metrics = Metric.list
28
+ end
29
+
30
+ def destroy_extras
31
+ puts "check_extras" if @options[:verbose]
32
+ extra_metrics = @current_metrics - @metrics.map(&:name)
33
+ extra_metrics.each do |metric|
34
+ metric = Metric.new(metric, {}, @options)
35
+ metric.destroy
36
+ end
37
+ end
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: expri
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brandur
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: excon
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.16.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.16.4
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.3.6
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.3.6
46
+ description:
47
+ email: brandur@mutelight.org
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - lib/expri/dumper.rb
53
+ - lib/expri/metric.rb
54
+ - lib/expri/metric_set.rb
55
+ - lib/expri.rb
56
+ homepage: https://github.com/heroku/expri
57
+ licenses:
58
+ - MIT
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 1.8.23
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Versionable metrics recipes for exprd.
81
+ test_files: []