influxdb-client 1.3.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 (49) 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 +37 -0
  9. data/Gemfile +24 -0
  10. data/LICENSE +21 -0
  11. data/README.md +268 -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/examples/influxdb_18_example.rb +35 -0
  19. data/influxdb-client.gemspec +53 -0
  20. data/lib/influxdb2/client.rb +30 -0
  21. data/lib/influxdb2/client/client.rb +96 -0
  22. data/lib/influxdb2/client/default_api.rb +95 -0
  23. data/lib/influxdb2/client/delete_api.rb +80 -0
  24. data/lib/influxdb2/client/flux_csv_parser.rb +251 -0
  25. data/lib/influxdb2/client/flux_table.rb +99 -0
  26. data/lib/influxdb2/client/health_api.rb +49 -0
  27. data/lib/influxdb2/client/influx_error.rb +31 -0
  28. data/lib/influxdb2/client/models/delete_predicate_request.rb +215 -0
  29. data/lib/influxdb2/client/models/dialect.rb +317 -0
  30. data/lib/influxdb2/client/models/health_check.rb +256 -0
  31. data/lib/influxdb2/client/models/query.rb +284 -0
  32. data/lib/influxdb2/client/point.rb +215 -0
  33. data/lib/influxdb2/client/query_api.rb +93 -0
  34. data/lib/influxdb2/client/version.rb +23 -0
  35. data/lib/influxdb2/client/worker.rb +115 -0
  36. data/lib/influxdb2/client/write_api.rb +243 -0
  37. data/test/influxdb/client_test.rb +92 -0
  38. data/test/influxdb/delete_api_integration_test.rb +100 -0
  39. data/test/influxdb/delete_api_test.rb +121 -0
  40. data/test/influxdb/flux_csv_parser_test.rb +401 -0
  41. data/test/influxdb/point_test.rb +221 -0
  42. data/test/influxdb/query_api_integration_test.rb +58 -0
  43. data/test/influxdb/query_api_stream_test.rb +98 -0
  44. data/test/influxdb/query_api_test.rb +96 -0
  45. data/test/influxdb/write_api_batching_test.rb +292 -0
  46. data/test/influxdb/write_api_integration_test.rb +76 -0
  47. data/test/influxdb/write_api_test.rb +275 -0
  48. data/test/test_helper.rb +39 -0
  49. metadata +217 -0
@@ -0,0 +1,215 @@
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
+ DEFAULT_WRITE_PRECISION = WritePrecision::NANOSECOND
23
+ ESCAPE_KEY_LIST = ['\\'.freeze, ','.freeze, ' '.freeze, '='.freeze].freeze
24
+ ESCAPE_VALUE_LIST = ['\\'.freeze, '"'.freeze].freeze
25
+
26
+ # Point defines the values that will be written to the database.
27
+ # Ref: http://bit.ly/influxdata-point
28
+ class Point
29
+ # Create DataPoint instance for specified measurement name.
30
+ #
31
+ # @example InfluxDB::Point.new(name: "h2o",
32
+ # tags: {host: 'aws', region: 'us'},
33
+ # fields: {level: 5, saturation: "99%"},
34
+ # time: 123)
35
+ #
36
+ # @param [String] name the measurement name for the point.
37
+ # @param [Hash] tags the tag set for the point
38
+ # @param [Hash] fields the fields for the point
39
+ # @param [Integer] time the timestamp for the point
40
+ # @param [WritePrecision] precision the precision for the unix timestamps within the body line-protocol
41
+ def initialize(name:, tags: nil, fields: nil, time: nil, precision: DEFAULT_WRITE_PRECISION)
42
+ @name = name
43
+ @tags = tags || {}
44
+ @fields = fields || {}
45
+ @time = time
46
+ @precision = precision
47
+ end
48
+ attr_reader :precision
49
+
50
+ # Create DataPoint instance from specified data.
51
+ #
52
+ # @example Point.fromHash({
53
+ # name: 'cpu',
54
+ # tags: { host: 'server_nl', regios: 'us' },
55
+ # fields: {internal: 5, external: 6},
56
+ # time: 1422568543702900257
57
+ # })
58
+ #
59
+ # @param [Hash] data
60
+ def self.from_hash(data)
61
+ obj = new(name: data[:name], tags: data[:tags], fields: data[:fields], time: data[:time])
62
+ obj
63
+ end
64
+
65
+ # Adds or replaces a tag value for a point.
66
+ #
67
+ # @example InfluxDB::Point.new(name: "h2o")
68
+ # .add_tag("location", "europe")
69
+ # .add_field("level", 2)
70
+ #
71
+ # @param [Object] key the tag name
72
+ # @param [Object] value the tag value
73
+ def add_tag(key, value)
74
+ @tags[key] = value
75
+ self
76
+ end
77
+
78
+ # Adds or replaces a field value for a point.
79
+ #
80
+ # @example InfluxDB::Point.new(name: "h2o")
81
+ # .add_tag("location", "europe")
82
+ # .add_field("level", 2)
83
+ #
84
+ # @param [Object] key the field name
85
+ # @param [Object] value the field value
86
+ def add_field(key, value)
87
+ @fields[key] = value
88
+ self
89
+ end
90
+
91
+ # Updates the timestamp for the point.
92
+ #
93
+ # @example InfluxDB::Point.new(name: "h2o")
94
+ # .add_tag("location", "europe")
95
+ # .add_field("level", 2)
96
+ # .time(Time.new(2015, 10, 15, 8, 20, 15), InfluxDB::WritePrecision::MILLISECOND)
97
+ #
98
+ # @example InfluxDB::Point.new(name: "h2o")
99
+ # .add_tag("location", "europe")
100
+ # .add_field("level", 2)
101
+ # .time(123, InfluxDB::WritePrecision::NANOSECOND)
102
+ #
103
+ # @param [Object] time the timestamp
104
+ # @param [WritePrecision] precision the timestamp precision
105
+ def time(time, precision)
106
+ @time = time
107
+ @precision = precision
108
+ self
109
+ end
110
+
111
+ # If there is no field then return nil.
112
+ #
113
+ # @return a string representation of the point
114
+ def to_line_protocol
115
+ line_protocol = ''
116
+ measurement = _escape_key(@name || '')
117
+
118
+ line_protocol << measurement
119
+
120
+ tags = _escape_tags
121
+ line_protocol << ",#{tags}" unless tags.empty?
122
+ line_protocol << ' '.freeze if line_protocol[-1] == '\\'
123
+
124
+ fields = _escape_fields
125
+ return nil if fields.empty?
126
+
127
+ line_protocol << " #{fields}" if fields
128
+ timestamp = _escape_time
129
+ line_protocol << " #{timestamp}" if timestamp
130
+
131
+ line_protocol
132
+ end
133
+
134
+ private
135
+
136
+ def _escape_tags
137
+ return if @tags.nil?
138
+
139
+ @tags.sort.to_h.map do |k, v|
140
+ key = _escape_key(k.to_s)
141
+ value = _escape_key(v.to_s)
142
+ if key.empty? || value.empty?
143
+ nil
144
+ else
145
+ "#{key}=#{value}"
146
+ end
147
+ end.reject(&:nil?).join(','.freeze)
148
+ end
149
+
150
+ def _escape_fields
151
+ return if @fields.nil?
152
+
153
+ @fields.sort.to_h.map do |k, v|
154
+ key = _escape_key(k.to_s)
155
+ value = _escape_value(v)
156
+ if key.empty? || value.empty?
157
+ nil
158
+ else
159
+ "#{key}=#{value}"
160
+ end
161
+ end.reject(&:nil?).join(','.freeze)
162
+ end
163
+
164
+ def _escape_key(value)
165
+ result = value.dup
166
+ ESCAPE_KEY_LIST.each do |ch|
167
+ result = result.gsub(ch) { "\\#{ch}" }
168
+ end
169
+ result
170
+ end
171
+
172
+ def _escape_value(value)
173
+ if value.nil?
174
+ ''
175
+ elsif value.is_a?(String)
176
+ result = value.dup
177
+ ESCAPE_VALUE_LIST.each do |ch|
178
+ result = result.gsub(ch) { "\\#{ch}" }
179
+ end
180
+ '"'.freeze + result + '"'.freeze
181
+ elsif value.is_a?(Integer)
182
+ "#{value}i"
183
+ elsif [Float::INFINITY, -Float::INFINITY].include?(value)
184
+ ''
185
+ else
186
+ value.to_s
187
+ end
188
+ end
189
+
190
+ def _escape_time
191
+ if @time.nil?
192
+ nil
193
+ elsif @time.is_a?(Integer)
194
+ @time.to_s
195
+ elsif @time.is_a?(Float)
196
+ @time.round.to_s
197
+ elsif @time.is_a?(Time)
198
+ nano_seconds = @time.to_i * 1e9
199
+ nano_seconds += @time.tv_nsec
200
+ case @precision || DEFAULT_WRITE_PRECISION
201
+ when InfluxDB2::WritePrecision::MILLISECOND then
202
+ (nano_seconds / 1e6).round
203
+ when InfluxDB2::WritePrecision::SECOND then
204
+ (nano_seconds / 1e9).round
205
+ when InfluxDB2::WritePrecision::MICROSECOND then
206
+ (nano_seconds / 1e3).round
207
+ when InfluxDB2::WritePrecision::NANOSECOND then
208
+ nano_seconds.round
209
+ end
210
+ else
211
+ @time.to_s
212
+ end
213
+ end
214
+ end
215
+ end
@@ -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.3.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