influxdb 0.6.2 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests.yml +92 -0
  3. data/.rubocop.yml +4 -0
  4. data/CHANGELOG.md +34 -0
  5. data/README.md +80 -34
  6. data/Rakefile +1 -1
  7. data/bin/provision.sh +3 -3
  8. data/influxdb.gemspec +2 -2
  9. data/lib/influxdb/client.rb +15 -9
  10. data/lib/influxdb/client/http.rb +11 -1
  11. data/lib/influxdb/config.rb +7 -5
  12. data/lib/influxdb/errors.rb +1 -0
  13. data/lib/influxdb/point_value.rb +4 -1
  14. data/lib/influxdb/query/batch.rb +9 -3
  15. data/lib/influxdb/query/cluster.rb +2 -2
  16. data/lib/influxdb/query/continuous_query.rb +1 -1
  17. data/lib/influxdb/query/core.rb +11 -7
  18. data/lib/influxdb/query/database.rb +2 -2
  19. data/lib/influxdb/query/series.rb +6 -2
  20. data/lib/influxdb/query/user.rb +8 -8
  21. data/lib/influxdb/timestamp_conversion.rb +52 -12
  22. data/lib/influxdb/version.rb +1 -1
  23. data/lib/influxdb/writer/async.rb +38 -11
  24. data/lib/influxdb/writer/udp.rb +3 -0
  25. data/spec/influxdb/cases/async_client_spec.rb +59 -35
  26. data/spec/influxdb/cases/query_cluster_spec.rb +3 -3
  27. data/spec/influxdb/cases/query_continuous_query_spec.rb +1 -1
  28. data/spec/influxdb/cases/query_database_spec.rb +4 -4
  29. data/spec/influxdb/cases/query_series_spec.rb +24 -8
  30. data/spec/influxdb/cases/query_user_spec.rb +8 -8
  31. data/spec/influxdb/cases/querying_issue_7000_spec.rb +1 -1
  32. data/spec/influxdb/cases/querying_spec.rb +1 -1
  33. data/spec/influxdb/cases/retry_requests_spec.rb +1 -1
  34. data/spec/influxdb/cases/udp_client_spec.rb +8 -14
  35. data/spec/influxdb/client_spec.rb +2 -2
  36. data/spec/influxdb/config_spec.rb +44 -14
  37. data/spec/influxdb/logging_spec.rb +3 -0
  38. data/spec/influxdb/point_value_spec.rb +11 -1
  39. data/spec/influxdb/time_conversion_spec.rb +19 -0
  40. data/spec/smoke/smoke_spec.rb +2 -2
  41. data/spec/spec_helper.rb +4 -4
  42. metadata +13 -14
  43. data/.travis.yml +0 -55
@@ -16,6 +16,8 @@ module InfluxDB
16
16
  open_timeout: 5,
17
17
  read_timeout: 300,
18
18
  auth_method: nil,
19
+ proxy_addr: nil,
20
+ proxy_port: nil,
19
21
 
20
22
  # SSL options
21
23
  use_ssl: false,
@@ -144,11 +146,11 @@ module InfluxDB
144
146
 
145
147
  def opts_from_non_params(url)
146
148
  {}.tap do |o|
147
- o[:host] = url.host if url.host
148
- o[:port] = url.port if url.port
149
- o[:username] = url.user if url.user
150
- o[:password] = url.password if url.password
151
- o[:database] = url.path[1..-1] if url.path.length > 1
149
+ o[:host] = url.host if url.host
150
+ o[:port] = url.port if url.port
151
+ o[:username] = URI.decode_www_form_component(url.user) if url.user
152
+ o[:password] = URI.decode_www_form_component(url.password) if url.password
153
+ o[:database] = url.path[1..-1] if url.path.length > 1
152
154
  o[:use_ssl] = url.scheme == "https".freeze
153
155
 
154
156
  o[:udp] = { host: o[:host], port: o[:port] } if url.scheme == "udp"
@@ -5,6 +5,7 @@ module InfluxDB # :nodoc:
5
5
  Error = Class.new StandardError
6
6
  AuthenticationError = Class.new Error
7
7
  ConnectionError = Class.new Error
8
+ LineProtocolError = Class.new Error
8
9
  SeriesNotFound = Class.new Error
9
10
  JSONParserError = Class.new Error
10
11
  QueryError = Class.new Error
@@ -13,6 +13,7 @@ module InfluxDB
13
13
  def dump
14
14
  dump = @series.dup
15
15
  dump << ",#{@tags}" if @tags
16
+ dump << " ".freeze if dump[-1] == "\\"
16
17
  dump << " #{@values}"
17
18
  dump << " #{@timestamp}" if @timestamp
18
19
  dump
@@ -45,7 +46,9 @@ module InfluxDB
45
46
  end
46
47
 
47
48
  def escape_values(values)
48
- return if values.nil?
49
+ if values.nil? || values.empty?
50
+ raise InfluxDB::LineProtocolError, "Cannot create point with empty values".freeze
51
+ end
49
52
 
50
53
  values.map do |k, v|
51
54
  key = escape(k.to_s, :field_key)
@@ -27,7 +27,7 @@ module InfluxDB
27
27
  )
28
28
  return [] if statements.empty?
29
29
 
30
- url = full_url "/query".freeze, query_params(statements.join(";"), opts)
30
+ url = full_url "/query".freeze, **query_params(statements.join(";"), **opts)
31
31
  series = fetch_series get(url, parse: true, json_streaming: !chunk_size.nil?)
32
32
 
33
33
  if denormalize
@@ -82,8 +82,14 @@ module InfluxDB
82
82
  denormalize_series
83
83
  denormalized_series_list
84
84
  ].each do |method_name|
85
- define_method(method_name) do |*args|
86
- client.send method_name, *args
85
+ if RUBY_VERSION < "2.7"
86
+ define_method(method_name) do |*args|
87
+ client.send method_name, *args
88
+ end
89
+ else
90
+ define_method(method_name) do |*args, **kwargs|
91
+ client.send method_name, *args, **kwargs
92
+ end
87
93
  end
88
94
  end
89
95
  end
@@ -2,7 +2,7 @@ module InfluxDB
2
2
  module Query
3
3
  module Cluster # :nodoc:
4
4
  def create_cluster_admin(username, password)
5
- execute("CREATE USER #{username} WITH PASSWORD '#{password}' WITH ALL PRIVILEGES")
5
+ execute("CREATE USER \"#{username}\" WITH PASSWORD '#{password}' WITH ALL PRIVILEGES")
6
6
  end
7
7
 
8
8
  def list_cluster_admins
@@ -10,7 +10,7 @@ module InfluxDB
10
10
  end
11
11
 
12
12
  def revoke_cluster_admin_privileges(username)
13
- execute("REVOKE ALL PRIVILEGES FROM #{username}")
13
+ execute("REVOKE ALL PRIVILEGES FROM \"#{username}\"")
14
14
  end
15
15
  end
16
16
  end
@@ -24,7 +24,7 @@ module InfluxDB
24
24
  end
25
25
 
26
26
  def delete_continuous_query(name, database)
27
- execute("DROP CONTINUOUS QUERY #{name} ON #{database}")
27
+ execute("DROP CONTINUOUS QUERY \"#{name}\" ON \"#{database}\"")
28
28
  end
29
29
  end
30
30
  end
@@ -26,7 +26,7 @@ module InfluxDB
26
26
  )
27
27
  query = builder.build(query, params)
28
28
 
29
- url = full_url("/query".freeze, query_params(query, opts))
29
+ url = full_url("/query".freeze, **query_params(query, **opts))
30
30
  series = fetch_series(get(url, parse: true, json_streaming: !chunk_size.nil?))
31
31
 
32
32
  if block_given?
@@ -79,7 +79,7 @@ module InfluxDB
79
79
  }
80
80
 
81
81
  params[:rp] = retention_policy if retention_policy
82
- url = full_url("/write", params)
82
+ url = full_url("/write", **params)
83
83
  post(url, data)
84
84
  end
85
85
 
@@ -121,15 +121,19 @@ module InfluxDB
121
121
  end
122
122
 
123
123
  def generate_payload(data)
124
- data.map do |point|
125
- InfluxDB::PointValue.new(point).dump
126
- end.join("\n".freeze)
124
+ data.map { |point| generate_point(point) }.join("\n".freeze)
125
+ end
126
+
127
+ def generate_point(point)
128
+ InfluxDB::PointValue.new(point).dump
129
+ rescue InfluxDB::LineProtocolError => e
130
+ (log :error, "Cannot write data: #{e.inspect}") && nil
127
131
  end
128
132
 
129
133
  def execute(query, db: nil, **options)
130
134
  params = { q: query }
131
135
  params[:db] = db if db
132
- url = full_url("/query".freeze, params)
136
+ url = full_url("/query".freeze, **params)
133
137
  get(url, options)
134
138
  end
135
139
 
@@ -143,7 +147,7 @@ module InfluxDB
143
147
  series.select { |k, _| %w[columns values].include?(k) }
144
148
  end
145
149
 
146
- def full_url(path, params = {})
150
+ def full_url(path, **params)
147
151
  if config.auth_method == "params".freeze
148
152
  params[:u] = config.username
149
153
  params[:p] = config.password
@@ -2,11 +2,11 @@ module InfluxDB
2
2
  module Query
3
3
  module Database # :nodoc:
4
4
  def create_database(name = nil)
5
- execute("CREATE DATABASE #{name || config.database}")
5
+ execute("CREATE DATABASE \"#{name || config.database}\"")
6
6
  end
7
7
 
8
8
  def delete_database(name = nil)
9
- execute("DROP DATABASE #{name || config.database}")
9
+ execute("DROP DATABASE \"#{name || config.database}\"")
10
10
  end
11
11
 
12
12
  def list_databases
@@ -1,8 +1,12 @@
1
1
  module InfluxDB
2
2
  module Query
3
3
  module Series # :nodoc:
4
- def delete_series(name)
5
- execute("DROP SERIES FROM #{name}", db: config.database)
4
+ def delete_series(name, where: nil, db: config.database)
5
+ if where
6
+ execute("DROP SERIES FROM \"#{name}\" WHERE #{where}", db: db)
7
+ else
8
+ execute("DROP SERIES FROM \"#{name}\"", db: db)
9
+ end
6
10
  end
7
11
 
8
12
  def list_series
@@ -6,36 +6,36 @@ module InfluxDB
6
6
  def create_database_user(database, username, password, options = {})
7
7
  permissions = options.fetch(:permissions, :all)
8
8
  execute(
9
- "CREATE user #{username} WITH PASSWORD '#{password}'; "\
10
- "GRANT #{permissions.to_s.upcase} ON #{database} TO #{username}"
9
+ "CREATE user \"#{username}\" WITH PASSWORD '#{password}'; "\
10
+ "GRANT #{permissions.to_s.upcase} ON \"#{database}\" TO \"#{username}\""
11
11
  )
12
12
  end
13
13
 
14
14
  def update_user_password(username, password)
15
- execute("SET PASSWORD FOR #{username} = '#{password}'")
15
+ execute("SET PASSWORD FOR \"#{username}\" = '#{password}'")
16
16
  end
17
17
 
18
18
  # permission => [:all]
19
19
  def grant_user_admin_privileges(username)
20
- execute("GRANT ALL PRIVILEGES TO #{username}")
20
+ execute("GRANT ALL PRIVILEGES TO \"#{username}\"")
21
21
  end
22
22
 
23
23
  # permission => [:read|:write|:all]
24
24
  def grant_user_privileges(username, database, permission)
25
- execute("GRANT #{permission.to_s.upcase} ON #{database} TO #{username}")
25
+ execute("GRANT #{permission.to_s.upcase} ON \"#{database}\" TO \"#{username}\"")
26
26
  end
27
27
 
28
28
  def list_user_grants(username)
29
- execute("SHOW GRANTS FOR #{username}")
29
+ execute("SHOW GRANTS FOR \"#{username}\"")
30
30
  end
31
31
 
32
32
  # permission => [:read|:write|:all]
33
33
  def revoke_user_privileges(username, database, permission)
34
- execute("REVOKE #{permission.to_s.upcase} ON #{database} FROM #{username}")
34
+ execute("REVOKE #{permission.to_s.upcase} ON \"#{database}\" FROM \"#{username}\"")
35
35
  end
36
36
 
37
37
  def delete_user(username)
38
- execute("DROP USER #{username}")
38
+ execute("DROP USER \"#{username}\"")
39
39
  end
40
40
 
41
41
  # => [{"username"=>"usr", "admin"=>true}, {"username"=>"justauser", "admin"=>false}]
@@ -1,5 +1,45 @@
1
1
  module InfluxDB #:nodoc:
2
- TIMESTAMP_CONVERSIONS = {
2
+ # Converts a Time to a timestamp with the given precision.
3
+ #
4
+ # === Example
5
+ #
6
+ # InfluxDB.convert_timestamp(Time.now, "ms")
7
+ # #=> 1543533308243
8
+ def self.convert_timestamp(time, precision = "s")
9
+ factor = TIME_PRECISION_FACTORS.fetch(precision) do
10
+ raise ArgumentError, "invalid time precision: #{precision}"
11
+ end
12
+
13
+ (time.to_r * factor).to_i
14
+ end
15
+
16
+ # Returns the current timestamp with the given precision.
17
+ #
18
+ # Implementation detail: This does not create an intermediate Time
19
+ # object with `Time.now`, but directly requests the CLOCK_REALTIME,
20
+ # which in general is a bit faster.
21
+ #
22
+ # This is useful, if you want or need to shave off a few microseconds
23
+ # from your measurement.
24
+ #
25
+ # === Examples
26
+ #
27
+ # InfluxDB.now("ns") #=> 1543612126401392625
28
+ # InfluxDB.now("u") #=> 1543612126401392
29
+ # InfluxDB.now("ms") #=> 1543612126401
30
+ # InfluxDB.now("s") #=> 1543612126
31
+ # InfluxDB.now("m") #=> 25726868
32
+ # InfluxDB.now("h") #=> 428781
33
+ def self.now(precision = "s")
34
+ name, divisor = CLOCK_NAMES.fetch(precision) do
35
+ raise ArgumentError, "invalid time precision: #{precision}"
36
+ end
37
+
38
+ time = Process.clock_gettime Process::CLOCK_REALTIME, name
39
+ (time / divisor).to_i
40
+ end
41
+
42
+ TIME_PRECISION_FACTORS = {
3
43
  "ns" => 1e9.to_r,
4
44
  nil => 1e9.to_r,
5
45
  "u" => 1e6.to_r,
@@ -8,16 +48,16 @@ module InfluxDB #:nodoc:
8
48
  "m" => 1.to_r / 60,
9
49
  "h" => 1.to_r / 60 / 60,
10
50
  }.freeze
11
- private_constant :TIMESTAMP_CONVERSIONS
51
+ private_constant :TIME_PRECISION_FACTORS
12
52
 
13
- # Converts a Time to a timestamp with the given precision.
14
- #
15
- # `convert_timestamp(Time.now, "ms")` might return `1543533308243`.
16
- def self.convert_timestamp(time, time_precision)
17
- factor = TIMESTAMP_CONVERSIONS.fetch(time_precision) do
18
- raise "Invalid time precision: #{time_precision}"
19
- end
20
-
21
- (time.to_r * factor).to_i
22
- end
53
+ CLOCK_NAMES = {
54
+ "ns" => [:nanosecond, 1],
55
+ nil => [:nanosecond, 1],
56
+ "u" => [:microsecond, 1],
57
+ "ms" => [:millisecond, 1],
58
+ "s" => [:second, 1],
59
+ "m" => [:second, 60.to_r],
60
+ "h" => [:second, (60 * 60).to_r],
61
+ }.freeze
62
+ private_constant :CLOCK_NAMES
23
63
  end
@@ -1,3 +1,3 @@
1
1
  module InfluxDB # :nodoc:
2
- VERSION = "0.6.2".freeze
2
+ VERSION = "0.8.1".freeze
3
3
  end
@@ -9,6 +9,16 @@ module InfluxDB
9
9
  def initialize(client, config)
10
10
  @client = client
11
11
  @config = config
12
+ @stopped = false
13
+ end
14
+
15
+ def stopped?
16
+ @stopped
17
+ end
18
+
19
+ def stop!
20
+ worker.stop!
21
+ @stopped = true
12
22
  end
13
23
 
14
24
  def write(data, precision = nil, retention_policy = nil, database = nil)
@@ -29,7 +39,7 @@ module InfluxDB
29
39
  end
30
40
  end
31
41
 
32
- class Worker
42
+ class Worker # rubocop:disable Metrics/ClassLength
33
43
  attr_reader :client,
34
44
  :queue,
35
45
  :threads,
@@ -37,7 +47,8 @@ module InfluxDB
37
47
  :max_queue_size,
38
48
  :num_worker_threads,
39
49
  :sleep_interval,
40
- :block_on_full_queue
50
+ :block_on_full_queue,
51
+ :shutdown_timeout
41
52
 
42
53
  include InfluxDB::Logging
43
54
 
@@ -47,7 +58,7 @@ module InfluxDB
47
58
  SLEEP_INTERVAL = 5
48
59
  BLOCK_ON_FULL_QUEUE = false
49
60
 
50
- def initialize(client, config)
61
+ def initialize(client, config) # rubocop:disable Metrics/MethodLength
51
62
  @client = client
52
63
  config = config.is_a?(Hash) ? config : {}
53
64
 
@@ -56,10 +67,11 @@ module InfluxDB
56
67
  @num_worker_threads = config.fetch(:num_worker_threads, NUM_WORKER_THREADS)
57
68
  @sleep_interval = config.fetch(:sleep_interval, SLEEP_INTERVAL)
58
69
  @block_on_full_queue = config.fetch(:block_on_full_queue, BLOCK_ON_FULL_QUEUE)
70
+ @shutdown_timeout = config.fetch(:shutdown_timeout, 2 * @sleep_interval)
59
71
 
60
72
  queue_class = @block_on_full_queue ? SizedQueue : InfluxDB::MaxQueue
61
73
  @queue = queue_class.new max_queue_size
62
-
74
+ @should_stop = false
63
75
  spawn_threads!
64
76
  end
65
77
 
@@ -68,11 +80,11 @@ module InfluxDB
68
80
  end
69
81
 
70
82
  def current_threads
71
- Thread.list.select { |t| t[:influxdb] == object_id }
83
+ @threads
72
84
  end
73
85
 
74
86
  def current_thread_count
75
- Thread.list.count { |t| t[:influxdb] == object_id }
87
+ @threads.count
76
88
  end
77
89
 
78
90
  # rubocop:disable Metrics/CyclomaticComplexity
@@ -87,7 +99,7 @@ module InfluxDB
87
99
  @threads << Thread.new do
88
100
  Thread.current[:influxdb] = object_id
89
101
 
90
- until client.stopped?
102
+ until @should_stop
91
103
  check_background_queue(thread_num)
92
104
  sleep rand(sleep_interval)
93
105
  end
@@ -97,7 +109,7 @@ module InfluxDB
97
109
  end
98
110
  end
99
111
 
100
- def check_background_queue(thread_num = 0)
112
+ def check_background_queue(thread_num = -1)
101
113
  log(:debug) do
102
114
  "Checking background queue on thread #{thread_num} (#{current_thread_count} active)"
103
115
  end
@@ -123,10 +135,10 @@ module InfluxDB
123
135
  return if data.values.flatten.empty?
124
136
 
125
137
  begin
126
- log(:debug) { "Found data in the queue! (#{sizes(data)})" }
138
+ log(:debug) { "Found data in the queue! (#{sizes(data)}) on thread #{thread_num}" }
127
139
  write(data)
128
140
  rescue StandardError => e
129
- log :error, "Cannot write data: #{e.inspect}"
141
+ log :error, "Cannot write data: #{e.inspect} on thread #{thread_num}"
130
142
  end
131
143
 
132
144
  break if queue.length > max_post_points
@@ -138,7 +150,22 @@ module InfluxDB
138
150
  # rubocop:enable Metrics/AbcSize
139
151
 
140
152
  def stop!
141
- log(:debug) { "Thread exiting, flushing queue." }
153
+ log(:debug) { "Worker is being stopped, flushing queue." }
154
+
155
+ # If retry was infinite (-1), set it to zero to give the threads one
156
+ # last chance to write their data
157
+ client.config.retry = 0 if client.config.retry < 0
158
+
159
+ # Signal the background threads that they should exit.
160
+ @should_stop = true
161
+
162
+ # Wait for the threads to exit and then kill them
163
+ @threads.each do |t|
164
+ r = t.join(shutdown_timeout)
165
+ t.kill if r.nil?
166
+ end
167
+
168
+ # Flush any remaining items in the queue on the main thread
142
169
  check_background_queue until queue.empty?
143
170
  end
144
171
 
@@ -11,6 +11,9 @@ module InfluxDB
11
11
  @port = port
12
12
  end
13
13
 
14
+ # No-op for UDP writers
15
+ def stop!; end
16
+
14
17
  def write(payload, _precision = nil, _retention_policy = nil, _database = nil)
15
18
  with_socket { |sock| sock.send(payload, 0) }
16
19
  end