influxer 0.2.4 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +12 -0
  4. data/.travis.yml +1 -17
  5. data/Changelog.md +7 -0
  6. data/Gemfile +7 -0
  7. data/README.md +6 -20
  8. data/Rakefile +4 -0
  9. data/gemfiles/rails42.gemfile +4 -1
  10. data/influxer.gemspec +6 -6
  11. data/lib/influxer/client.rb +4 -44
  12. data/lib/influxer/config.rb +23 -14
  13. data/lib/influxer/engine.rb +0 -1
  14. data/lib/influxer/metrics/metrics.rb +40 -7
  15. data/lib/influxer/metrics/relation/calculations.rb +13 -7
  16. data/lib/influxer/metrics/relation/time_query.rb +14 -8
  17. data/lib/influxer/metrics/relation/where_clause.rb +62 -0
  18. data/lib/influxer/metrics/relation.rb +67 -76
  19. data/lib/influxer/metrics/scoping.rb +4 -4
  20. data/lib/influxer/model.rb +4 -0
  21. data/lib/influxer/rails/client.rb +47 -0
  22. data/lib/influxer/version.rb +1 -1
  23. data/lib/influxer.rb +4 -2
  24. data/spec/cases/points_spec.rb +34 -0
  25. data/spec/client_spec.rb +21 -24
  26. data/spec/fixtures/empty_result.json +21 -0
  27. data/spec/fixtures/single_series.json +29 -0
  28. data/spec/metrics/metrics_spec.rb +139 -113
  29. data/spec/metrics/relation_spec.rb +160 -105
  30. data/spec/metrics/scoping_spec.rb +11 -17
  31. data/spec/model/user_spec.rb +44 -0
  32. data/spec/spec_helper.rb +38 -8
  33. data/spec/support/metrics/action_metrics.rb +3 -0
  34. data/spec/support/metrics/custom_metrics.rb +4 -0
  35. data/spec/support/{dummy_metrics.rb → metrics/dummy_metrics.rb} +2 -1
  36. data/spec/support/metrics/user_metrics.rb +4 -0
  37. data/spec/support/metrics/visits_metrics.rb +4 -0
  38. data/spec/support/shared_contexts/shared_query.rb +16 -0
  39. data/spec/support/user.rb +14 -0
  40. metadata +42 -71
  41. data/gemfiles/rails40.gemfile +0 -7
  42. data/gemfiles/rails41.gemfile +0 -7
  43. data/lib/influxer/metrics/fanout.rb +0 -65
  44. data/lib/influxer/metrics/relation/fanout_query.rb +0 -49
  45. data/lib/tasks/influxer_tasks.rake +0 -4
  46. data/spec/config_spec.rb +0 -17
  47. data/spec/dummy/README.rdoc +0 -28
  48. data/spec/dummy/Rakefile +0 -6
  49. data/spec/dummy/app/assets/images/.keep +0 -0
  50. data/spec/dummy/app/assets/javascripts/application.js +0 -13
  51. data/spec/dummy/app/assets/stylesheets/application.css +0 -15
  52. data/spec/dummy/app/controllers/application_controller.rb +0 -5
  53. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  55. data/spec/dummy/app/mailers/.keep +0 -0
  56. data/spec/dummy/app/metrics/testo_metrics.rb +0 -3
  57. data/spec/dummy/app/models/.keep +0 -0
  58. data/spec/dummy/app/models/concerns/.keep +0 -0
  59. data/spec/dummy/app/models/testo.rb +0 -6
  60. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  61. data/spec/dummy/bin/bundle +0 -3
  62. data/spec/dummy/bin/rails +0 -4
  63. data/spec/dummy/bin/rake +0 -4
  64. data/spec/dummy/config/application.rb +0 -23
  65. data/spec/dummy/config/boot.rb +0 -5
  66. data/spec/dummy/config/database.yml +0 -25
  67. data/spec/dummy/config/environment.rb +0 -5
  68. data/spec/dummy/config/environments/development.rb +0 -37
  69. data/spec/dummy/config/environments/production.rb +0 -83
  70. data/spec/dummy/config/environments/test.rb +0 -38
  71. data/spec/dummy/config/influxdb.yml +0 -5
  72. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  73. data/spec/dummy/config/initializers/cookies_serializer.rb +0 -3
  74. data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  75. data/spec/dummy/config/initializers/inflections.rb +0 -16
  76. data/spec/dummy/config/initializers/mime_types.rb +0 -4
  77. data/spec/dummy/config/initializers/session_store.rb +0 -3
  78. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  79. data/spec/dummy/config/locales/en.yml +0 -23
  80. data/spec/dummy/config/routes.rb +0 -56
  81. data/spec/dummy/config/secrets.yml +0 -25
  82. data/spec/dummy/config.ru +0 -4
  83. data/spec/dummy/db/migrate/20140730133818_add_testos.rb +0 -11
  84. data/spec/dummy/db/migrate/20140731162044_add_column_to_testos.rb +0 -5
  85. data/spec/dummy/db/schema.rb +0 -22
  86. data/spec/dummy/db/test.sqlite3 +0 -0
  87. data/spec/dummy/lib/assets/.keep +0 -0
  88. data/spec/dummy/log/.keep +0 -0
  89. data/spec/dummy/public/404.html +0 -67
  90. data/spec/dummy/public/422.html +0 -67
  91. data/spec/dummy/public/500.html +0 -66
  92. data/spec/dummy/public/favicon.ico +0 -0
  93. data/spec/fixtures/fanout_series.json +0 -23
  94. data/spec/metrics/fanout_spec.rb +0 -61
  95. data/spec/model/testo_spec.rb +0 -27
@@ -1,25 +1,26 @@
1
1
  require 'influxer/metrics/relation/time_query'
2
- require 'influxer/metrics/relation/fanout_query'
3
2
  require 'influxer/metrics/relation/calculations'
3
+ require 'influxer/metrics/relation/where_clause'
4
4
 
5
5
  module Influxer
6
6
  # Relation is used to build queries
7
+ # rubocop:disable Metrics/ClassLength
7
8
  class Relation
8
9
  include Influxer::TimeQuery
9
- include Influxer::FanoutQuery
10
10
  include Influxer::Calculations
11
+ include Influxer::WhereClause
11
12
 
12
13
  attr_reader :values
13
14
 
14
- MULTI_VALUE_METHODS = [:select, :where, :group]
15
+ MULTI_VALUE_METHODS = [:select, :where, :group, :order]
15
16
 
16
17
  MULTI_KEY_METHODS = [:fanout]
17
18
 
18
- SINGLE_VALUE_METHODS = [:fill, :limit, :merge, :time]
19
+ SINGLE_VALUE_METHODS = [:fill, :time, :limit, :offset, :slimit, :soffset, :normalized]
19
20
 
20
21
  MULTI_VALUE_SIMPLE_METHODS = [:select, :group]
21
22
 
22
- SINGLE_VALUE_SIMPLE_METHODS = [:fill, :limit, :merge]
23
+ SINGLE_VALUE_SIMPLE_METHODS = [:fill, :limit, :offset, :slimit, :soffset]
23
24
 
24
25
  MULTI_VALUE_METHODS.each do |name|
25
26
  class_eval <<-CODE, __FILE__, __LINE__ + 1
@@ -82,50 +83,76 @@ module Influxer
82
83
  end
83
84
 
84
85
  def write(params = {})
85
- build params
86
- @instance.write
86
+ build(params).write
87
+ end
88
+
89
+ def write!(params = {})
90
+ build(params).write!
87
91
  end
88
92
 
89
93
  def build(params = {})
94
+ point = @instance.dup
90
95
  params.each do |key, val|
91
- @instance.send("#{key}=", val) if @instance.respond_to?(key)
96
+ point.send("#{key}=", val) if point.respond_to?(key)
92
97
  end
93
- @instance
98
+ point
94
99
  end
95
100
 
96
- # accepts hash or strings conditions
97
- def where(*args, **hargs)
98
- build_where(args, hargs, false)
101
+ alias_method :new, :build
102
+
103
+ def normalized
104
+ @values[:normalized] = true
99
105
  self
100
106
  end
101
107
 
102
- def not(*args, **hargs)
103
- build_where(args, hargs, true)
108
+ def normalized?
109
+ @values[:normalized] == true
110
+ end
111
+
112
+ def order(val)
113
+ case val
114
+ when Hash
115
+ val.each { |k, v| order_values << "#{k} #{v}" }
116
+ when String
117
+ order_values << val
118
+ end
104
119
  self
105
120
  end
106
121
 
122
+ # rubocop:disable Metrics/AbcSize
123
+ # rubocop:disable Metrics/CyclomaticComplexity
124
+ # rubocop:disable Metrics/MethodLength
125
+ # rubocop:disable Metrics/PerceivedComplexity
107
126
  def to_sql
108
127
  sql = ["select"]
109
128
  select_values << "*" if select_values.empty?
110
129
 
111
130
  sql << select_values.uniq.join(",")
112
131
 
113
- sql << "from #{ build_series_name }"
114
- sql << "merge #{ @klass.quoted_series(merge_value) }" unless merge_value.nil?
132
+ sql << "from #{build_series_name}"
133
+
134
+ sql << "where #{where_values.join(' and ')}" unless where_values.empty?
115
135
 
116
136
  unless group_values.empty? && time_value.nil?
117
137
  group_fields = (time_value.nil? ? [] : ['time(' + @values[:time] + ')']) + group_values
118
138
  group_fields.uniq!
119
- sql << "group by #{ group_fields.join(',') }"
139
+ sql << "group by #{group_fields.join(',')}"
120
140
  end
121
141
 
122
- sql << "fill(#{ fill_value })" unless fill_value.nil?
142
+ sql << "fill(#{fill_value})" unless fill_value.nil?
123
143
 
124
- sql << "where #{ where_values.join(' and ') }" unless where_values.empty?
144
+ sql << "order by #{order_values.uniq.join(',')}" unless order_values.empty?
125
145
 
126
- sql << "limit #{ limit_value }" unless limit_value.nil?
146
+ sql << "limit #{limit_value}" unless limit_value.nil?
147
+ sql << "offset #{offset_value}" unless offset_value.nil?
148
+ sql << "slimit #{slimit_value}" unless slimit_value.nil?
149
+ sql << "soffset #{soffset_value}" unless soffset_value.nil?
127
150
  sql.join " "
128
151
  end
152
+ # rubocop:enable Metrics/AbcSize
153
+ # rubocop:enable Metrics/CyclomaticComplexity
154
+ # rubocop:enable Metrics/MethodLength
155
+ # rubocop:enable Metrics/PerceivedComplexity
129
156
 
130
157
  def to_a
131
158
  return @records if loaded?
@@ -153,13 +180,13 @@ module Influxer
153
180
  end
154
181
 
155
182
  def load
156
- @records = get_points(@instance.client.cached_query(to_sql))
183
+ @records = get_points(@instance.client.query(to_sql, denormalize: !normalized?))
157
184
  @loaded = true
158
185
  @records
159
186
  end
160
187
 
161
188
  def delete_all
162
- sql = ["delete"]
189
+ sql = ["drop series"]
163
190
 
164
191
  sql << "from #{@instance.series}"
165
192
 
@@ -167,7 +194,7 @@ module Influxer
167
194
 
168
195
  sql = sql.join " "
169
196
 
170
- @instance.client.query sql
197
+ @instance.client.exec sql
171
198
  end
172
199
 
173
200
  def scoping
@@ -177,6 +204,8 @@ module Influxer
177
204
  @klass.current_scope = previous
178
205
  end
179
206
 
207
+ # rubocop:disable Metrics/AbcSize
208
+ # rubocop:disable Metrics/MethodLength
180
209
  def merge!(rel)
181
210
  return self if rel.nil?
182
211
  MULTI_VALUE_METHODS.each do |method|
@@ -193,57 +222,13 @@ module Influxer
193
222
 
194
223
  self
195
224
  end
225
+ # rubocop:enable Metrics/AbcSize
226
+ # rubocop:enable Metrics/MethodLength
196
227
 
197
228
  protected
198
229
 
199
- def build_where(args, hargs, negate)
200
- case
201
- when (args.present? && args[0].is_a?(String))
202
- where_values.concat args.map { |str| "(#{str})" }
203
- when hargs.present?
204
- build_hash_where(hargs, negate)
205
- else
206
- false
207
- end
208
- end
209
-
210
- def build_hash_where(hargs, negate = false)
211
- hargs.each do |key, val|
212
- if @klass.fanout?(key)
213
- build_fanout(key, val)
214
- else
215
- where_values << "(#{ build_eql(key, val, negate) })"
216
- end
217
- end
218
- end
219
-
220
- def build_eql(key, val, negate)
221
- case val
222
- when Regexp
223
- "#{key}#{ negate ? '!~' : '=~'}#{val.inspect}"
224
- when Array
225
- build_in(key, val, negate)
226
- when Range
227
- build_range(key, val, negate)
228
- else
229
- "#{key}#{ negate ? '<>' : '='}#{quoted(val)}"
230
- end
231
- end
232
-
233
- def build_in(key, arr, negate)
234
- buf = []
235
- arr.each do |val|
236
- buf << build_eql(key, val, negate)
237
- end
238
- "#{ buf.join(negate ? ' and ' : ' or ') }"
239
- end
240
-
241
- def build_range(key, val, negate)
242
- if negate
243
- "#{key}<#{quoted(val.begin)} and #{key}>#{quoted(val.end)}"
244
- else
245
- "#{key}>#{quoted(val.begin)} and #{key}<#{quoted(val.end)}"
246
- end
230
+ def build_series_name
231
+ @instance.series
247
232
  end
248
233
 
249
234
  def loaded?
@@ -263,8 +248,8 @@ module Influxer
263
248
  self
264
249
  end
265
250
 
266
- def quoted(val)
267
- if val.is_a?(String) || val.is_a?(Symbol)
251
+ def quoted(val, key = nil)
252
+ if val.is_a?(String) || val.is_a?(Symbol) || @klass.tag?(key)
268
253
  "'#{val}'"
269
254
  elsif val.is_a?(Time) || val.is_a?(DateTime)
270
255
  "#{val.to_i}s"
@@ -273,9 +258,15 @@ module Influxer
273
258
  end
274
259
  end
275
260
 
276
- def get_points(hash)
277
- prepare_fanout_points(hash) if @values[:has_fanout] == true
278
- hash.values.reduce([], :+)
261
+ def get_points(list)
262
+ return list if normalized?
263
+ list.reduce([]) do |a, e|
264
+ a + e.fetch("values", []).map { |v| inject_tags(v, e["tags"] || {}) }
265
+ end
266
+ end
267
+
268
+ def inject_tags(val, tags)
269
+ val.merge(tags)
279
270
  end
280
271
 
281
272
  def method_missing(method, *args, &block)
@@ -3,6 +3,7 @@ require 'influxer/metrics/scoping/default'
3
3
  require 'influxer/metrics/scoping/named'
4
4
 
5
5
  module Influxer
6
+ # Clone of ActiveRecord::Relation scoping
6
7
  module Scoping # :nodoc:
7
8
  extend ActiveSupport::Concern
8
9
 
@@ -47,10 +48,9 @@ module Influxer
47
48
  private
48
49
 
49
50
  def raise_invalid_scope_type!(scope_type)
50
- if !VALID_SCOPE_TYPES.include?(scope_type)
51
- raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. \
52
- Scope types must be included in VALID_SCOPE_TYPES"
53
- end
51
+ return if VALID_SCOPE_TYPES.include?(scope_type)
52
+ fail ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. \
53
+ Scope types must be included in VALID_SCOPE_TYPES"
54
54
  end
55
55
  end
56
56
  end
@@ -6,6 +6,10 @@ module Influxer
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  module ClassMethods # :nodoc:
9
+ # rubocop:disable Style/PredicateName
10
+ # rubocop:disable Metrics/MethodLength
11
+ # rubocop:disable Metrics/AbcSize
12
+ # rubocop:disable Metrics/CyclomaticComplexity
9
13
  def has_metrics(*args, **params)
10
14
  metrics_name = args.empty? ? "metrics" : args.first.to_s
11
15
 
@@ -0,0 +1,47 @@
1
+ module Influxer
2
+ # - Overriding loggging (use instrumentation and Rails logger)
3
+ # - Add cache support for queries
4
+ class Client
5
+ def query(sql, options = {})
6
+ log(sql) do
7
+ if !options.fetch(:cache, true) || Influxer.config.cache == false
8
+ super(sql, options)
9
+ else
10
+ Rails.cache.fetch(normalized_cache_key(sql), cache_options(sql)) { super(sql, options) }
11
+ end
12
+ end
13
+ end
14
+
15
+ def log(sql)
16
+ return yield unless logger.debug?
17
+
18
+ start_ts = Time.now
19
+ res = yield
20
+ duration = (Time.now - start_ts) * 1000
21
+
22
+ name = "InfluxDB SQL (#{duration.round(1)}ms)"
23
+
24
+ # bold black name and blue query string
25
+ msg = "\e[1m\e[30m#{name}\e[0m \e[34m#{sql}\e[0m"
26
+ logger.debug msg
27
+ res
28
+ end
29
+
30
+ # if sql contains 'now()' set expires to 1 minute or :cache_now_for value
31
+ # of config.cache if defined
32
+ def cache_options(sql = nil)
33
+ options = Influxer.config.cache.dup
34
+ options[:expires_in] = (options[:cache_now_for] || 60) if sql =~ /\snow\(\)/
35
+ options
36
+ end
37
+
38
+ # add prefix; remove whitespaces
39
+ def normalized_cache_key(sql)
40
+ "influxer:#{sql.gsub(/\s*/, '')}"
41
+ end
42
+
43
+ def logger
44
+ Rails.logger
45
+ end
46
+ end
47
+ end
@@ -1,3 +1,3 @@
1
1
  module Influxer # :nodoc:
2
- VERSION = "0.2.4"
2
+ VERSION = "0.3.1"
3
3
  end
data/lib/influxer.rb CHANGED
@@ -10,7 +10,8 @@ module Influxer
10
10
  require 'influxer/model'
11
11
  end
12
12
 
13
- require 'influxer/engine'
13
+ require 'influxer/rails/client' if defined?(Rails)
14
+ require 'influxer/engine' if defined?(Rails)
14
15
 
15
16
  def self.config
16
17
  @config ||= Config.new
@@ -24,7 +25,8 @@ module Influxer
24
25
  @client ||= Client.new
25
26
  end
26
27
 
27
- def self.reset
28
+ def self.reset!
29
+ @client.stop! unless @client.nil?
28
30
  @config = nil
29
31
  @client = nil
30
32
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe DummyMetrics do
4
+ before do
5
+ stub_request(:get, "http://localhost:8086/query")
6
+ .with(
7
+ query: { q: 'select * from "dummy"', u: "root", p: "root", precision: 's', db: 'db' }
8
+ )
9
+ .to_return(body: fixture_file)
10
+ end
11
+
12
+ context "single_series" do
13
+ let(:fixture_file) { File.read('./spec/fixtures/single_series.json') }
14
+
15
+ context "default format (values merged with tags)" do
16
+ subject { described_class.all.to_a }
17
+
18
+ it "returns array of hashes" do
19
+ expect(subject.first).to include("host" => "server01", "region" => "us-west", "value" => 0.64)
20
+ expect(subject.second).to include("host" => "server01", "region" => "us-west", "value" => 0.93)
21
+ end
22
+ end
23
+ end
24
+
25
+ context "empty result" do
26
+ let(:fixture_file) { File.read('./spec/fixtures/empty_result.json') }
27
+
28
+ subject { described_class.all.to_a }
29
+
30
+ it "returns empty array" do
31
+ expect(subject).to eq []
32
+ end
33
+ end
34
+ end
data/spec/client_spec.rb CHANGED
@@ -1,46 +1,43 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Influxer::Client do
4
- after(:each) do
5
- Rails.cache.clear
6
- end
7
-
8
4
  let(:conf) { Influxer.config }
9
- let(:client) { Influxer.client }
5
+ subject { Influxer.client }
10
6
 
11
- it "should have config params" do
12
- expect(client.username).to eq conf.username
13
- expect(client.port).to eq conf.port
14
- expect(client.database).to eq conf.database
15
- end
7
+ describe "#initialize" do
8
+ it "sets config database value" do
9
+ expect(subject.config.database).to eq conf.database
10
+ end
16
11
 
17
- describe "cache" do
18
- before do
19
- allow_any_instance_of(Influxer::Client).to receive(:query) do |_, sql|
20
- sql
21
- end
12
+ it "passes config params" do
13
+ conf.username = 'admin'
14
+ conf.port = 2222
15
+ expect(subject.config.username).to eq 'admin'
16
+ expect(subject.config.port).to eq 2222
22
17
  end
18
+ end
23
19
 
20
+ describe "cache", :query do
24
21
  let(:q) { "list series" }
22
+ after { Rails.cache.clear }
25
23
 
26
- after(:each) do
27
- conf.cache = false
28
- end
29
-
30
- it "should write data to cache" do
24
+ it "writes data to cache" do
31
25
  conf.cache = {}
32
26
 
33
- client.cached_query(q)
27
+ subject.query(q)
34
28
  expect(Rails.cache.exist?("influxer:listseries")).to be_truthy
35
29
  end
36
30
 
37
31
  it "should write data to cache with expiration" do
38
- conf.cache = { expires_in: 1 }
32
+ conf.cache = { expires_in: 90 }
33
+
34
+ subject.query(q)
35
+ expect(Rails.cache.exist?("influxer:listseries")).to be_truthy
39
36
 
40
- client.cached_query(q)
37
+ Timecop.travel(1.minute.from_now)
41
38
  expect(Rails.cache.exist?("influxer:listseries")).to be_truthy
42
39
 
43
- sleep 2
40
+ Timecop.travel(2.minutes.from_now)
44
41
  expect(Rails.cache.exist?("influxer:listseries")).to be_falsey
45
42
  end
46
43
  end
@@ -0,0 +1,21 @@
1
+ {
2
+ "results": [
3
+ {
4
+ "series": [
5
+ {
6
+ "name": "cpu_load_short",
7
+ "tags": {
8
+ "host": "server01",
9
+ "region": "us-west"
10
+ },
11
+ "columns": [
12
+ "time",
13
+ "value"
14
+ ],
15
+ "values": [
16
+ ]
17
+ }
18
+ ]
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "results": [
3
+ {
4
+ "series": [
5
+ {
6
+ "name": "cpu_load_short",
7
+ "tags": {
8
+ "host": "server01",
9
+ "region": "us-west"
10
+ },
11
+ "columns": [
12
+ "time",
13
+ "value"
14
+ ],
15
+ "values": [
16
+ [
17
+ "2015-01-29T21:51:28.968422294Z",
18
+ 0.64
19
+ ],
20
+ [
21
+ "2015-01-29T21:51:38.968422294Z",
22
+ 0.93
23
+ ]
24
+ ]
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }