influxer 0.2.4 → 0.3.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.
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
+ }