influxdb-client 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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