influxer 1.1.6 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +34 -58
- data/.travis.yml +6 -1
- data/Changelog.md +6 -2
- data/Gemfile +5 -2
- data/MIT-LICENSE +1 -1
- data/README.md +21 -21
- data/Rakefile +4 -2
- data/bin/console +8 -0
- data/gemfiles/rails32.gemfile +1 -1
- data/gemfiles/rails42.gemfile +1 -1
- data/gemfiles/rails5.gemfile +2 -2
- data/gemfiles/rails6.gemfile +7 -0
- data/influxer.gemspec +16 -9
- data/lib/influxer.rb +10 -9
- data/lib/influxer/client.rb +1 -1
- data/lib/influxer/config.rb +3 -3
- data/lib/influxer/engine.rb +1 -1
- data/lib/influxer/metrics/active_model3/model.rb +2 -4
- data/lib/influxer/metrics/metrics.rb +8 -8
- data/lib/influxer/metrics/quoting/timestamp.rb +13 -13
- data/lib/influxer/metrics/relation.rb +17 -13
- data/lib/influxer/metrics/relation/calculations.rb +1 -1
- data/lib/influxer/metrics/relation/time_query.rb +15 -14
- data/lib/influxer/metrics/relation/where_clause.rb +6 -4
- data/lib/influxer/metrics/scoping.rb +4 -4
- data/lib/influxer/metrics/scoping/current_scope.rb +2 -1
- data/lib/influxer/metrics/scoping/default.rb +1 -1
- data/lib/influxer/metrics/scoping/named.rb +2 -1
- data/lib/influxer/model.rb +4 -9
- data/lib/influxer/rails/client.rb +1 -1
- data/lib/influxer/version.rb +1 -1
- data/spec/cases/points_spec.rb +4 -4
- data/spec/cases/write_points_spec.rb +9 -9
- data/spec/client_spec.rb +4 -4
- data/spec/metrics/metrics_spec.rb +16 -16
- data/spec/metrics/relation_spec.rb +11 -11
- data/spec/metrics/scoping_spec.rb +2 -2
- data/spec/model/user_spec.rb +2 -2
- data/spec/spec_helper.rb +13 -14
- data/spec/support/shared_contexts/shared_precision.rb +1 -1
- metadata +27 -63
@@ -1,16 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "influxer/metrics/relation"
|
4
|
+
require "influxer/metrics/scoping"
|
5
|
+
require "influxer/metrics/quoting/timestamp"
|
6
|
+
require "influxer/metrics/active_model3/model"
|
7
7
|
|
8
8
|
module Influxer
|
9
9
|
class MetricsError < StandardError; end
|
10
10
|
class MetricsInvalid < MetricsError; end
|
11
11
|
|
12
12
|
# Base class for InfluxDB querying and writing
|
13
|
-
# rubocop:disable Metrics/ClassLength
|
14
13
|
class Metrics
|
15
14
|
TIME_FACTOR = 1_000_000_000
|
16
15
|
if Influxer.active_model3?
|
@@ -124,7 +123,7 @@ module Influxer
|
|
124
123
|
quoted_series(val.call(instance), write: write)
|
125
124
|
when Array
|
126
125
|
if val.length > 1
|
127
|
-
"merge(#{val.map { |s| quoted_series(s, write: write) }.join(
|
126
|
+
"merge(#{val.map { |s| quoted_series(s, write: write) }.join(",")})"
|
128
127
|
else
|
129
128
|
quoted_series(val.first, write: write)
|
130
129
|
end
|
@@ -132,7 +131,7 @@ module Influxer
|
|
132
131
|
# Only add retention policy when selecting points
|
133
132
|
# and not writing
|
134
133
|
if !write && retention_policy.present?
|
135
|
-
[quote(@retention_policy), quote(val)].join(
|
134
|
+
[quote(@retention_policy), quote(val)].join(".")
|
136
135
|
else
|
137
136
|
quote(val)
|
138
137
|
end
|
@@ -168,6 +167,7 @@ module Influxer
|
|
168
167
|
|
169
168
|
def write!
|
170
169
|
raise MetricsInvalid if invalid?
|
170
|
+
|
171
171
|
write
|
172
172
|
end
|
173
173
|
|
@@ -228,7 +228,7 @@ module Influxer
|
|
228
228
|
end
|
229
229
|
|
230
230
|
def unquote(name)
|
231
|
-
name.gsub(/(\A['"]|['"]\z)/,
|
231
|
+
name.gsub(/(\A['"]|['"]\z)/, "")
|
232
232
|
end
|
233
233
|
end
|
234
234
|
end
|
@@ -3,27 +3,27 @@
|
|
3
3
|
module Influxer
|
4
4
|
module TimestampQuoting #:nodoc:
|
5
5
|
TIME_FACTORS = {
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
"ns" => 1_000_000_000,
|
7
|
+
"ms" => 1_000,
|
8
|
+
"s" => 1
|
9
9
|
}.freeze
|
10
10
|
|
11
|
-
DEFAULT_PRECISION =
|
11
|
+
DEFAULT_PRECISION = "ns"
|
12
12
|
|
13
13
|
# Quote timestamp
|
14
14
|
# rubocop: disable Metrics/MethodLength, Metrics/AbcSize
|
15
15
|
def quote_timestamp(val, client)
|
16
16
|
if Influxer.config.time_duration_suffix_enabled
|
17
|
-
precision = if TIME_FACTORS.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
precision = if TIME_FACTORS.key?(client.time_precision)
|
18
|
+
client.time_precision
|
19
|
+
else
|
20
|
+
DEFAULT_PRECISION
|
21
|
+
end
|
22
22
|
return quote_timestamp_with_suffix(val, precision)
|
23
23
|
end
|
24
24
|
|
25
|
-
if !TIME_FACTORS.
|
26
|
-
|
25
|
+
if !TIME_FACTORS.key?(client.time_precision) &&
|
26
|
+
!val.is_a?(Numeric)
|
27
27
|
warn(
|
28
28
|
"Influxer doesn't automatically cast Time and String values " \
|
29
29
|
"to '#{client.time_precision}' precision. " \
|
@@ -41,8 +41,8 @@ module Influxer
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def quote_timestamp_for_write(val, client)
|
44
|
-
if !TIME_FACTORS.
|
45
|
-
|
44
|
+
if !TIME_FACTORS.key?(client.time_precision) &&
|
45
|
+
!val.is_a?(Numeric)
|
46
46
|
raise ArgumentError,
|
47
47
|
"Influxer doesn't support quoting #{val} " \
|
48
48
|
" with '#{client.time_precision}' precision. " \
|
@@ -1,14 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
require "influxer/metrics/relation/time_query"
|
5
|
+
require "influxer/metrics/relation/calculations"
|
6
|
+
require "influxer/metrics/relation/where_clause"
|
7
|
+
require "influxer/metrics/quoting/timestamp"
|
8
8
|
|
9
9
|
module Influxer
|
10
10
|
# Relation is used to build queries
|
11
|
-
# rubocop:disable Metrics/ClassLength
|
12
11
|
class Relation
|
13
12
|
include Influxer::TimeQuery
|
14
13
|
include Influxer::Calculations
|
@@ -144,17 +143,17 @@ module Influxer
|
|
144
143
|
|
145
144
|
sql << "from #{build_series_name}"
|
146
145
|
|
147
|
-
sql << "where #{where_values.join(
|
146
|
+
sql << "where #{where_values.join(" and ")}" unless where_values.empty?
|
148
147
|
|
149
148
|
unless group_values.empty? && time_value.nil?
|
150
|
-
group_fields = (time_value.nil? ? [] : [
|
149
|
+
group_fields = (time_value.nil? ? [] : ["time(" + @values[:time] + ")"]) + group_values
|
151
150
|
group_fields.uniq!
|
152
|
-
sql << "group by #{group_fields.join(
|
151
|
+
sql << "group by #{group_fields.join(", ")}"
|
153
152
|
end
|
154
153
|
|
155
154
|
sql << "fill(#{fill_value})" unless fill_value.nil?
|
156
155
|
|
157
|
-
sql << "order by #{order_values.uniq.join(
|
156
|
+
sql << "order by #{order_values.uniq.join(",")}" unless order_values.empty?
|
158
157
|
|
159
158
|
sql << "limit #{limit_value}" unless limit_value.nil?
|
160
159
|
sql << "offset #{offset_value}" unless offset_value.nil?
|
@@ -169,14 +168,15 @@ module Influxer
|
|
169
168
|
|
170
169
|
def to_a
|
171
170
|
return @records if loaded?
|
171
|
+
|
172
172
|
load
|
173
173
|
end
|
174
174
|
|
175
175
|
def inspect
|
176
176
|
entries = to_a.take(11).map!(&:inspect)
|
177
|
-
entries[10] =
|
177
|
+
entries[10] = "..." if entries.size == 11
|
178
178
|
|
179
|
-
"#<#{self.class.name} [#{entries.join(
|
179
|
+
"#<#{self.class.name} [#{entries.join(", ")}]>"
|
180
180
|
end
|
181
181
|
|
182
182
|
def empty?
|
@@ -209,7 +209,7 @@ module Influxer
|
|
209
209
|
|
210
210
|
sql << "from #{@instance.series}"
|
211
211
|
|
212
|
-
sql << "where #{where_values.join(
|
212
|
+
sql << "where #{where_values.join(" and ")}" unless where_values.empty?
|
213
213
|
|
214
214
|
sql = sql.join " "
|
215
215
|
|
@@ -228,6 +228,7 @@ module Influxer
|
|
228
228
|
# rubocop:disable Metrics/MethodLength
|
229
229
|
def merge!(rel)
|
230
230
|
return self if rel.nil?
|
231
|
+
|
231
232
|
MULTI_VALUE_METHODS.each do |method|
|
232
233
|
(@values[method] ||= []).concat(rel.values[method]).uniq! unless rel.values[method].nil?
|
233
234
|
end
|
@@ -280,6 +281,7 @@ module Influxer
|
|
280
281
|
|
281
282
|
def get_points(list)
|
282
283
|
return list if normalized?
|
284
|
+
|
283
285
|
list.reduce([]) do |a, e|
|
284
286
|
a + e.fetch("values", []).map { |v| inject_tags(v, e["tags"] || {}) }
|
285
287
|
end
|
@@ -291,11 +293,13 @@ module Influxer
|
|
291
293
|
|
292
294
|
def method_missing(method, *args, &block)
|
293
295
|
return super unless @klass.respond_to?(method)
|
296
|
+
|
294
297
|
merge!(scoping { @klass.public_send(method, *args, &block) })
|
295
298
|
end
|
296
299
|
|
297
300
|
def respond_to_missing?(method, *args)
|
298
301
|
return true if @klass.respond_to?(method)
|
302
|
+
|
299
303
|
super
|
300
304
|
end
|
301
305
|
end
|
@@ -21,7 +21,7 @@ module Influxer
|
|
21
21
|
|
22
22
|
def percentile(name, val, alias_name = nil)
|
23
23
|
@values[:has_calculations] = true
|
24
|
-
select_values << "percentile(#{name}, #{val})#{alias_name ?
|
24
|
+
select_values << "percentile(#{name}, #{val})#{alias_name ? " as " + alias_name.to_s : ""}"
|
25
25
|
self
|
26
26
|
end
|
27
27
|
end
|
@@ -3,15 +3,15 @@
|
|
3
3
|
module Influxer
|
4
4
|
module TimeQuery #:nodoc:
|
5
5
|
TIME_ALIASES = {
|
6
|
-
hour:
|
7
|
-
minute:
|
8
|
-
second:
|
9
|
-
ms:
|
10
|
-
u:
|
11
|
-
week:
|
12
|
-
day:
|
13
|
-
month:
|
14
|
-
year:
|
6
|
+
hour: "1h",
|
7
|
+
minute: "1m",
|
8
|
+
second: "1s",
|
9
|
+
ms: "1ms",
|
10
|
+
u: "1u",
|
11
|
+
week: "1w",
|
12
|
+
day: "1d",
|
13
|
+
month: "30d",
|
14
|
+
year: "365d"
|
15
15
|
}.freeze
|
16
16
|
|
17
17
|
FILL_RESERVED = %i[null previous none].freeze
|
@@ -28,10 +28,10 @@ module Influxer
|
|
28
28
|
# # select * from metrics group by time(4d) fill(0)
|
29
29
|
def time(val, options = {})
|
30
30
|
@values[:time] = if val.is_a?(Symbol)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
TIME_ALIASES[val] || "1" + val.to_s
|
32
|
+
else
|
33
|
+
val
|
34
|
+
end
|
35
35
|
|
36
36
|
build_fill(options[:fill])
|
37
37
|
self
|
@@ -51,7 +51,7 @@ module Influxer
|
|
51
51
|
def past(val)
|
52
52
|
case val
|
53
53
|
when Symbol
|
54
|
-
where("time > now() - #{TIME_ALIASES[val] || (
|
54
|
+
where("time > now() - #{TIME_ALIASES[val] || ("1" + val.to_s)}")
|
55
55
|
when String
|
56
56
|
where("time > now() - #{val}")
|
57
57
|
else
|
@@ -76,6 +76,7 @@ module Influxer
|
|
76
76
|
|
77
77
|
def build_fill(val)
|
78
78
|
return if val.nil?
|
79
|
+
|
79
80
|
fill(FILL_RESERVED.include?(val) ? val.to_s : val.to_i)
|
80
81
|
end
|
81
82
|
end
|
@@ -29,6 +29,7 @@ module Influxer
|
|
29
29
|
|
30
30
|
def load
|
31
31
|
return if @null_relation
|
32
|
+
|
32
33
|
super
|
33
34
|
end
|
34
35
|
|
@@ -57,14 +58,15 @@ module Influxer
|
|
57
58
|
when NilClass
|
58
59
|
build_eql(key, /.*/, !negate)
|
59
60
|
when Regexp
|
60
|
-
"#{key}#{negate ?
|
61
|
+
"#{key}#{negate ? " !~ " : " =~ "}#{val.inspect}"
|
61
62
|
when Array
|
62
63
|
return build_none(negate) if val.empty?
|
64
|
+
|
63
65
|
build_in(key, val, negate)
|
64
66
|
when Range
|
65
67
|
build_range(key, val, negate)
|
66
68
|
else
|
67
|
-
"#{key}#{negate ?
|
69
|
+
"#{key}#{negate ? " <> " : " = "}#{quoted(val, key)}"
|
68
70
|
end
|
69
71
|
end
|
70
72
|
# rubocop:enable Metrics/CyclomaticComplexity
|
@@ -75,7 +77,7 @@ module Influxer
|
|
75
77
|
arr.each do |val|
|
76
78
|
buf << build_eql(key, val, negate)
|
77
79
|
end
|
78
|
-
buf.join(negate ?
|
80
|
+
buf.join(negate ? " and " : " or ").to_s
|
79
81
|
end
|
80
82
|
|
81
83
|
# rubocop: disable Metrics/AbcSize, Metrics/MethodLength, Style/IfInsideElse
|
@@ -100,7 +102,7 @@ module Influxer
|
|
100
102
|
|
101
103
|
def build_none(negate = false)
|
102
104
|
@null_relation = !negate
|
103
|
-
negate ?
|
105
|
+
negate ? "time >= 0" : "time < 0"
|
104
106
|
end
|
105
107
|
end
|
106
108
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "influxer/metrics/scoping/default"
|
4
|
+
require "influxer/metrics/scoping/named"
|
5
5
|
|
6
6
|
if Influxer.active_model3?
|
7
|
-
require
|
7
|
+
require "influxer/metrics/scoping/old_current_scope"
|
8
8
|
else
|
9
|
-
require
|
9
|
+
require "influxer/metrics/scoping/current_scope"
|
10
10
|
end
|
11
11
|
|
12
12
|
module Influxer
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_support/per_thread_registry"
|
4
4
|
|
5
5
|
module Influxer
|
6
6
|
module Scoping
|
@@ -39,6 +39,7 @@ module Influxer
|
|
39
39
|
|
40
40
|
def raise_invalid_scope_type!(scope_type)
|
41
41
|
return if VALID_SCOPE_TYPES.include?(scope_type)
|
42
|
+
|
42
43
|
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. \
|
43
44
|
Scope types must be included in VALID_SCOPE_TYPES"
|
44
45
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_support/concern"
|
4
4
|
|
5
5
|
module Influxer
|
6
6
|
module Scoping
|
@@ -10,6 +10,7 @@ module Influxer
|
|
10
10
|
module ClassMethods
|
11
11
|
def scope(name, scope)
|
12
12
|
raise "Scope not defined: #{name}" if scope.nil? || !scope.respond_to?(:call)
|
13
|
+
|
13
14
|
singleton_class.send(:define_method, name) do |*args|
|
14
15
|
rel = all
|
15
16
|
rel.merge!(rel.scoping { scope.call(*args) })
|
data/lib/influxer/model.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_support"
|
4
4
|
|
5
5
|
module Influxer
|
6
6
|
# Add `has_metrics` method to AR::Base
|
@@ -8,9 +8,6 @@ module Influxer
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
module ClassMethods # :nodoc:
|
11
|
-
# rubocop:disable Naming/PredicateName
|
12
|
-
# rubocop:disable Metrics/MethodLength
|
13
|
-
# rubocop:disable Metrics/AbcSize
|
14
11
|
def has_metrics(*args, **params)
|
15
12
|
metrics_name = args.empty? ? "metrics" : args.first.to_s
|
16
13
|
|
@@ -22,12 +19,10 @@ module Influxer
|
|
22
19
|
foreign_key = params.fetch(:foreign_key, to_s.foreign_key)
|
23
20
|
|
24
21
|
define_method(metrics_name) do
|
25
|
-
rel_attrs = foreign_key ? {
|
22
|
+
rel_attrs = foreign_key ? {foreign_key => id} : {}
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
rel_attrs[key] = send(key)
|
30
|
-
end
|
24
|
+
attrs&.each do |key|
|
25
|
+
rel_attrs[key] = send(key)
|
31
26
|
end
|
32
27
|
Relation.new klass, attributes: rel_attrs
|
33
28
|
end
|
data/lib/influxer/version.rb
CHANGED
data/spec/cases/points_spec.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe DummyMetrics do
|
6
6
|
before do
|
7
7
|
stub_request(:get, "http://localhost:8086/query")
|
8
8
|
.with(
|
9
|
-
query: {
|
9
|
+
query: {q: 'select * from "dummy"', u: "root", p: "root", precision: "ns", db: "db"}
|
10
10
|
)
|
11
11
|
.to_return(body: fixture_file)
|
12
12
|
end
|
13
13
|
|
14
14
|
context "single_series" do
|
15
|
-
let(:fixture_file) { File.read(
|
15
|
+
let(:fixture_file) { File.read("./spec/fixtures/single_series.json") }
|
16
16
|
|
17
17
|
context "default format (values merged with tags)" do
|
18
18
|
subject { described_class.all.to_a }
|
@@ -25,7 +25,7 @@ describe DummyMetrics do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
context "empty result" do
|
28
|
-
let(:fixture_file) { File.read(
|
28
|
+
let(:fixture_file) { File.read("./spec/fixtures/empty_result.json") }
|
29
29
|
|
30
30
|
subject { described_class.all.to_a }
|
31
31
|
|