influxdb 0.6.2 → 0.8.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 (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