influxdb-client 1.2.0

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +157 -0
  3. data/.circleci/setup-rubygems.sh +3 -0
  4. data/.codecov.yml +3 -0
  5. data/.github/PULL_REQUEST_TEMPLATE +8 -0
  6. data/.gitignore +16 -0
  7. data/.rubocop.yml +38 -0
  8. data/CHANGELOG.md +27 -0
  9. data/Gemfile +24 -0
  10. data/LICENSE +21 -0
  11. data/README.md +248 -0
  12. data/Rakefile +37 -0
  13. data/bin/generate-sources.sh +23 -0
  14. data/bin/influxdb-onboarding.sh +39 -0
  15. data/bin/influxdb-restart.sh +60 -0
  16. data/bin/pom.xml +34 -0
  17. data/bin/swagger.yml +9867 -0
  18. data/influxdb-client.gemspec +53 -0
  19. data/lib/influxdb2/client.rb +29 -0
  20. data/lib/influxdb2/client/client.rb +89 -0
  21. data/lib/influxdb2/client/default_api.rb +87 -0
  22. data/lib/influxdb2/client/delete_api.rb +80 -0
  23. data/lib/influxdb2/client/flux_csv_parser.rb +251 -0
  24. data/lib/influxdb2/client/flux_table.rb +99 -0
  25. data/lib/influxdb2/client/influx_error.rb +31 -0
  26. data/lib/influxdb2/client/models/delete_predicate_request.rb +215 -0
  27. data/lib/influxdb2/client/models/dialect.rb +317 -0
  28. data/lib/influxdb2/client/models/query.rb +284 -0
  29. data/lib/influxdb2/client/point.rb +215 -0
  30. data/lib/influxdb2/client/query_api.rb +93 -0
  31. data/lib/influxdb2/client/version.rb +23 -0
  32. data/lib/influxdb2/client/worker.rb +115 -0
  33. data/lib/influxdb2/client/write_api.rb +243 -0
  34. data/test/influxdb/client_test.rb +70 -0
  35. data/test/influxdb/delete_api_integration_test.rb +100 -0
  36. data/test/influxdb/delete_api_test.rb +121 -0
  37. data/test/influxdb/flux_csv_parser_test.rb +401 -0
  38. data/test/influxdb/point_test.rb +221 -0
  39. data/test/influxdb/query_api_integration_test.rb +58 -0
  40. data/test/influxdb/query_api_stream_test.rb +98 -0
  41. data/test/influxdb/query_api_test.rb +96 -0
  42. data/test/influxdb/write_api_batching_test.rb +292 -0
  43. data/test/influxdb/write_api_integration_test.rb +76 -0
  44. data/test/influxdb/write_api_test.rb +275 -0
  45. data/test/test_helper.rb +39 -0
  46. metadata +214 -0
@@ -0,0 +1,93 @@
1
+ # The MIT License
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+ require_relative 'models/dialect'
21
+ require_relative 'models/query'
22
+ require_relative 'flux_csv_parser'
23
+ require 'json'
24
+
25
+ module InfluxDB2
26
+ # The client of the InfluxDB 2.0 that implement Query HTTP API endpoint.
27
+ #
28
+ class QueryApi < DefaultApi
29
+ DEFAULT_DIALECT = InfluxDB2::Dialect.new(header: true, delimiter: ',', comment_prefix: '#',
30
+ annotations: %w[datatype group default])
31
+
32
+ # @param [Hash] options The options to be used by the client.
33
+ def initialize(options:)
34
+ super(options: options)
35
+ end
36
+
37
+ # @param [Object] query the flux query to execute. The data could be represent by [String], [Query]
38
+ # @param [String] org specifies the source organization
39
+ # @return [String] result of query
40
+ def query_raw(query: nil, org: nil, dialect: DEFAULT_DIALECT)
41
+ _post_query(query: query, org: org, dialect: dialect).read_body
42
+ end
43
+
44
+ # @param [Object] query the flux query to execute. The data could be represent by [String], [Query]
45
+ # @param [String] org specifies the source organization
46
+ # @return [Array] list of FluxTables which are matched the query
47
+ def query(query: nil, org: nil, dialect: DEFAULT_DIALECT)
48
+ response = query_raw(query: query, org: org, dialect: dialect)
49
+ parser = InfluxDB2::FluxCsvParser.new(response)
50
+
51
+ parser.parse
52
+ parser.tables
53
+ end
54
+
55
+ # @param [Object] query the flux query to execute. The data could be represent by [String], [Query]
56
+ # @param [String] org specifies the source organization
57
+ # @return stream of Flux Records
58
+ def query_stream(query: nil, org: nil, dialect: DEFAULT_DIALECT)
59
+ response = _post_query(query: query, org: org, dialect: dialect)
60
+
61
+ InfluxDB2::FluxCsvParser.new(response, stream: true)
62
+ end
63
+
64
+ private
65
+
66
+ def _post_query(query: nil, org: nil, dialect: DEFAULT_DIALECT)
67
+ org_param = org || @options[:org]
68
+ _check('org', org_param)
69
+
70
+ payload = _generate_payload(query, dialect)
71
+ return nil if payload.nil?
72
+
73
+ uri = URI.parse(File.join(@options[:url], '/api/v2/query'))
74
+ uri.query = URI.encode_www_form(org: org_param)
75
+
76
+ _post_json(payload.to_body.to_json, uri)
77
+ end
78
+
79
+ def _generate_payload(query, dialect)
80
+ if query.nil?
81
+ nil
82
+ elsif query.is_a?(Query)
83
+ query
84
+ elsif query.is_a?(String)
85
+ if query.empty?
86
+ nil
87
+ else
88
+ Query.new(query: query, dialect: dialect, type: nil)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,23 @@
1
+ # The MIT License
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module InfluxDB2
22
+ VERSION = '1.2.0'.freeze
23
+ end
@@ -0,0 +1,115 @@
1
+ # The MIT License
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module InfluxDB2
22
+ # Worker for handling write batching queue
23
+ #
24
+ class Worker
25
+ def initialize(api_client, write_options)
26
+ @api_client = api_client
27
+ @write_options = write_options
28
+
29
+ @queue = Queue.new
30
+ @queue_event = Queue.new
31
+
32
+ @queue_event.push(true)
33
+
34
+ @thread_flush = Thread.new do
35
+ until api_client.closed
36
+ sleep @write_options.flush_interval.to_f / 1_000
37
+ _check_background_queue
38
+ end
39
+ end
40
+
41
+ @thread_size = Thread.new do
42
+ until api_client.closed
43
+ _check_background_queue(size: true) if @queue.length >= @write_options.batch_size
44
+ sleep 0.01
45
+ end
46
+ end
47
+ end
48
+
49
+ def push(payload)
50
+ if payload.respond_to? :each
51
+ payload.each do |item|
52
+ push(item)
53
+ end
54
+ else
55
+ @queue.push(payload)
56
+ end
57
+ end
58
+
59
+ def flush_all
60
+ _check_background_queue until @queue.empty?
61
+ end
62
+
63
+ private
64
+
65
+ def _check_background_queue(size: false)
66
+ @queue_event.pop
67
+ data = {}
68
+ points = 0
69
+
70
+ if size && @queue.length < @write_options.batch_size
71
+ @queue_event.push(true)
72
+ return
73
+ end
74
+
75
+ while (points < @write_options.batch_size) && !@queue.empty?
76
+ begin
77
+ item = @queue.pop(true)
78
+ key = item.key
79
+ data[key] = [] unless data.key?(key)
80
+ data[key] << item.data
81
+ points += 1
82
+ rescue ThreadError
83
+ @queue_event.push(true)
84
+ return
85
+ end
86
+ end
87
+
88
+ begin
89
+ _write(data) unless data.values.flatten.empty?
90
+ ensure
91
+ @queue_event.push(true)
92
+ end
93
+ end
94
+
95
+ def _write(data)
96
+ data.each do |key, points|
97
+ _write_raw(key, points)
98
+ end
99
+ end
100
+
101
+ def _write_raw(key, points)
102
+ if @write_options.jitter_interval.positive?
103
+ jitter_delay = (@write_options.jitter_interval.to_f / 1_000) * rand
104
+ sleep jitter_delay
105
+ end
106
+ @api_client.write_raw(points.join("\n"), precision: key.precision, bucket: key.bucket, org: key.org)
107
+ rescue InfluxError => e
108
+ raise e if e.code.nil? || !(%w[429 503].include? e.code)
109
+
110
+ timeout = e.retry_after.empty? ? @write_options.retry_interval.to_f / 1_000 : e.retry_after.to_f
111
+ sleep timeout
112
+ _write_raw(key, points)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,243 @@
1
+ # The MIT License
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+ require_relative 'worker'
21
+
22
+ module InfluxDB2
23
+ module WriteType
24
+ SYNCHRONOUS = 1
25
+ BATCHING = 2
26
+ end
27
+
28
+ # Creates write api configuration.
29
+ #
30
+ class WriteOptions
31
+ # @param [WriteType] write_type: methods of write (batching, synchronous)
32
+ # @param [Integer] batch_size: the number of data point to collect in batch
33
+ # @param [Integer] flush_interval: flush data at least in this interval
34
+ # @param [Integer] retry_interval: number of milliseconds to retry unsuccessful write.
35
+ # The retry interval is used when the InfluxDB server does not specify "Retry-After" header.
36
+ # @param [Integer] jitter_interval: the number of milliseconds to increase the batch flush interval
37
+ # by a random amount
38
+ def initialize(write_type: WriteType::SYNCHRONOUS, batch_size: 1_000, flush_interval: 1_000, retry_interval: 1_000,
39
+ jitter_interval: 0)
40
+ _check_not_negative('batch_size', batch_size)
41
+ _check_not_negative('flush_interval', flush_interval)
42
+ _check_not_negative('retry_interval', retry_interval)
43
+ _check_positive('jitter_interval', jitter_interval)
44
+ @write_type = write_type
45
+ @batch_size = batch_size
46
+ @flush_interval = flush_interval
47
+ @retry_interval = retry_interval
48
+ @jitter_interval = jitter_interval
49
+ end
50
+
51
+ attr_reader :write_type, :batch_size, :flush_interval, :retry_interval, :jitter_interval
52
+
53
+ def _check_not_negative(key, value)
54
+ raise ArgumentError, "The '#{key}' should be positive or zero, but is: #{value}" if value <= 0
55
+ end
56
+
57
+ def _check_positive(key, value)
58
+ raise ArgumentError, "The '#{key}' should be positive number, but is: #{value}" if value < 0
59
+ end
60
+ end
61
+
62
+ SYNCHRONOUS = InfluxDB2::WriteOptions.new(write_type: WriteType::SYNCHRONOUS)
63
+
64
+ # Precision constants.
65
+ #
66
+ class WritePrecision
67
+ SECOND = 's'.freeze
68
+ MILLISECOND = 'ms'.freeze
69
+ MICROSECOND = 'us'.freeze
70
+ NANOSECOND = 'ns'.freeze
71
+
72
+ def get_from_value(value)
73
+ constants = WritePrecision.constants.select { |c| WritePrecision.const_get(c) == value }
74
+ raise "The time precision #{value} is not supported." if constants.empty?
75
+
76
+ value
77
+ end
78
+ end
79
+
80
+ # Write time series data into InfluxDB.
81
+ #
82
+ class WriteApi < DefaultApi
83
+ # @param [Hash] options The options to be used by the client.
84
+ # @param [WriteOptions] write_options Write api configuration.
85
+ def initialize(options:, write_options: SYNCHRONOUS)
86
+ super(options: options)
87
+ @write_options = write_options
88
+ @closed = false
89
+ end
90
+ attr_reader :closed
91
+
92
+ # Write data into specified Bucket.
93
+ #
94
+ # @example write(data:
95
+ # [
96
+ # {
97
+ # name: 'cpu',
98
+ # tags: { host: 'server_nl', region: 'us' },
99
+ # fields: {internal: 5, external: 6},
100
+ # time: 1422568543702900257
101
+ # },
102
+ # {name: 'gpu', fields: {value: 0.9999}}
103
+ # ],
104
+ # precision: InfluxDB::WritePrecision::NANOSECOND,
105
+ # bucket: 'my-bucket',
106
+ # org: 'my-org'
107
+ # )
108
+ #
109
+ # @example write(data: 'h2o,location=west value=33i 15')
110
+ #
111
+ # @example point = InfluxDB::Point.new(name: 'h2o')
112
+ # .add_tag('location', 'europe')
113
+ # .add_field('level', 2)
114
+ #
115
+ # hash = { name: 'h2o', tags: { host: 'aws', region: 'us' }, fields: { level: 5, saturation: '99%' }, time: 123 }
116
+ #
117
+ # write(data: ['h2o,location=west value=33i 15', point, hash])
118
+ #
119
+ # @param [Object] data DataPoints to write into InfluxDB. The data could be represent by [Hash], [Point], [String]
120
+ # or by collection of these types
121
+ # @param [WritePrecision] precision The precision for the unix timestamps within the body line-protocol
122
+ # @param [String] bucket specifies the destination bucket for writes
123
+ # @param [String] org specifies the destination organization for writes
124
+ def write(data:, precision: nil, bucket: nil, org: nil)
125
+ precision_param = precision || @options[:precision]
126
+ bucket_param = bucket || @options[:bucket]
127
+ org_param = org || @options[:org]
128
+ _check('precision', precision_param)
129
+ _check('bucket', bucket_param)
130
+ _check('org', org_param)
131
+
132
+ payload = _generate_payload(data, bucket: bucket_param, org: org_param, precision: precision_param)
133
+ return nil if payload.nil?
134
+
135
+ if WriteType::BATCHING == @write_options.write_type
136
+ _worker.push(payload)
137
+ else
138
+ write_raw(payload, precision: precision_param, bucket: bucket_param, org: org_param)
139
+ end
140
+ end
141
+
142
+ # @return [ true ] Always true.
143
+ def close!
144
+ _worker.flush_all unless _worker.nil?
145
+ @closed = true
146
+ true
147
+ end
148
+
149
+ # @param [String] payload data as String
150
+ # @param [WritePrecision] precision The precision for the unix timestamps within the body line-protocol
151
+ # @param [String] bucket specifies the destination bucket for writes
152
+ # @param [String] org specifies the destination organization for writes
153
+ def write_raw(payload, precision: nil, bucket: nil, org: nil)
154
+ precision_param = precision || @options[:precision]
155
+ bucket_param = bucket || @options[:bucket]
156
+ org_param = org || @options[:org]
157
+ _check('precision', precision_param)
158
+ _check('bucket', bucket_param)
159
+ _check('org', org_param)
160
+
161
+ return nil unless payload.instance_of?(String) || payload.empty?
162
+
163
+ uri = URI.parse(File.join(@options[:url], '/api/v2/write'))
164
+ uri.query = URI.encode_www_form(bucket: bucket_param, org: org_param, precision: precision_param.to_s)
165
+
166
+ _post_text(payload, uri)
167
+ end
168
+
169
+ # Item for batching queue
170
+ class BatchItem
171
+ def initialize(key, data)
172
+ @key = key
173
+ @data = data
174
+ end
175
+ attr_reader :key, :data
176
+ end
177
+
178
+ # Key for batch item
179
+ class BatchItemKey
180
+ def initialize(bucket, org, precision = DEFAULT_WRITE_PRECISION)
181
+ @bucket = bucket
182
+ @org = org
183
+ @precision = precision
184
+ end
185
+ attr_reader :bucket, :org, :precision
186
+
187
+ def ==(other)
188
+ @bucket == other.bucket && @org == other.org && @precision == other.precision
189
+ end
190
+
191
+ alias eql? ==
192
+
193
+ def hash
194
+ @bucket.hash ^ @org.hash ^ @precision.hash # XOR
195
+ end
196
+ end
197
+
198
+ private
199
+
200
+ WORKER_MUTEX = Mutex.new
201
+ def _worker
202
+ return nil unless @write_options.write_type == WriteType::BATCHING
203
+
204
+ return @worker if @worker
205
+
206
+ WORKER_MUTEX.synchronize do
207
+ # this return is necessary because the previous mutex holder
208
+ # might have already assigned the @worker
209
+ return @worker if @worker
210
+
211
+ @worker = Worker.new(self, @write_options)
212
+ end
213
+ end
214
+
215
+ def _generate_payload(data, precision: nil, bucket: nil, org: nil)
216
+ if data.nil?
217
+ nil
218
+ elsif data.is_a?(Point)
219
+ _generate_payload(data.to_line_protocol, bucket: bucket, org: org, precision: data.precision ||
220
+ DEFAULT_WRITE_PRECISION)
221
+ elsif data.is_a?(String)
222
+ if data.empty?
223
+ nil
224
+ elsif @write_options.write_type == WriteType::BATCHING
225
+ BatchItem.new(BatchItemKey.new(bucket, org, precision), data)
226
+ else
227
+ data
228
+ end
229
+ elsif data.is_a?(Hash)
230
+ _generate_payload(Point.from_hash(data), bucket: bucket, org: org, precision: precision)
231
+ elsif data.respond_to? :map
232
+ payloads = data.map do |item|
233
+ _generate_payload(item, bucket: bucket, org: org, precision: precision)
234
+ end.reject(&:nil?)
235
+ if @write_options.write_type == WriteType::BATCHING
236
+ payloads
237
+ else
238
+ payloads.join("\n".freeze)
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end