influxdb-client 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) 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 +12 -0
  9. data/Gemfile +24 -0
  10. data/LICENSE +21 -0
  11. data/README.md +226 -0
  12. data/Rakefile +37 -0
  13. data/bin/generate-sources.sh +30 -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 +28 -0
  20. data/lib/influxdb2/client/client.rb +82 -0
  21. data/lib/influxdb2/client/default_api.rb +68 -0
  22. data/lib/influxdb2/client/flux_csv_parser.rb +246 -0
  23. data/lib/influxdb2/client/flux_table.rb +99 -0
  24. data/lib/influxdb2/client/influx_error.rb +27 -0
  25. data/lib/influxdb2/client/models/dialect.rb +317 -0
  26. data/lib/influxdb2/client/models/query.rb +284 -0
  27. data/lib/influxdb2/client/point.rb +215 -0
  28. data/lib/influxdb2/client/query_api.rb +93 -0
  29. data/lib/influxdb2/client/version.rb +23 -0
  30. data/lib/influxdb2/client/worker.rb +89 -0
  31. data/lib/influxdb2/client/write_api.rb +219 -0
  32. data/test/influxdb/client_test.rb +70 -0
  33. data/test/influxdb/flux_csv_parser_test.rb +326 -0
  34. data/test/influxdb/point_test.rb +221 -0
  35. data/test/influxdb/query_api_integration_test.rb +58 -0
  36. data/test/influxdb/query_api_stream_test.rb +98 -0
  37. data/test/influxdb/query_api_test.rb +75 -0
  38. data/test/influxdb/write_api_batching_test.rb +153 -0
  39. data/test/influxdb/write_api_integration_test.rb +75 -0
  40. data/test/influxdb/write_api_test.rb +235 -0
  41. data/test/test_helper.rb +39 -0
  42. metadata +208 -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(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.1.0'.freeze
23
+ end
@@ -0,0 +1,89 @@
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 / 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
+ @queue.push(payload)
51
+ end
52
+
53
+ def check_background_queue(size: false, flush_all: false)
54
+ @queue_event.pop
55
+ data = {}
56
+ points = 0
57
+
58
+ if size && @queue.length < @write_options.batch_size
59
+ @queue_event.push(true)
60
+ return
61
+ end
62
+
63
+ while (flush_all || points < @write_options.batch_size) && !@queue.empty?
64
+ begin
65
+ item = @queue.pop(true)
66
+ key = item.key
67
+ data[key] = [] unless data.key?(key)
68
+ data[key] << item.data
69
+ points += 1
70
+ rescue ThreadError
71
+ return
72
+ end
73
+ end
74
+
75
+ write(data) unless data.values.flatten.empty?
76
+ @queue_event.push(true)
77
+ end
78
+
79
+ def flush_all
80
+ check_background_queue(flush_all: true) unless @queue.empty?
81
+ end
82
+
83
+ def write(data)
84
+ data.each do |key, points|
85
+ @api_client.write_raw(points.join("\n"), precision: key.precision, bucket: key.bucket, org: key.org)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,219 @@
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
+ # @param write_type: methods of write (batching, asynchronous, synchronous)
31
+ # @param batch_size: the number of data point to collect in batch
32
+ # @param flush_interval: flush data at least in this interval
33
+ class WriteOptions
34
+ def initialize(write_type: WriteType::SYNCHRONOUS, batch_size: 1_000, flush_interval: 1_000)
35
+ @write_type = write_type
36
+ @batch_size = batch_size
37
+ @flush_interval = flush_interval
38
+ end
39
+
40
+ attr_reader :write_type, :batch_size, :flush_interval
41
+ end
42
+
43
+ SYNCHRONOUS = InfluxDB2::WriteOptions.new(write_type: WriteType::SYNCHRONOUS)
44
+
45
+ # Precision constants.
46
+ #
47
+ class WritePrecision
48
+ SECOND = 's'.freeze
49
+ MILLISECOND = 'ms'.freeze
50
+ MICROSECOND = 'us'.freeze
51
+ NANOSECOND = 'ns'.freeze
52
+
53
+ def get_from_value(value)
54
+ constants = WritePrecision.constants.select { |c| WritePrecision.const_get(c) == value }
55
+ raise "The time precision #{value} is not supported." if constants.empty?
56
+
57
+ value
58
+ end
59
+ end
60
+
61
+ # Write time series data into InfluxDB.
62
+ #
63
+ class WriteApi < DefaultApi
64
+ # @param [Hash] options The options to be used by the client.
65
+ # @param [WriteOptions] write_options Write api configuration.
66
+ def initialize(options:, write_options: SYNCHRONOUS)
67
+ super(options: options)
68
+ @write_options = write_options
69
+ @closed = false
70
+ end
71
+ attr_reader :closed
72
+
73
+ # Write data into specified Bucket.
74
+ #
75
+ # @example write(data:
76
+ # [
77
+ # {
78
+ # name: 'cpu',
79
+ # tags: { host: 'server_nl', region: 'us' },
80
+ # fields: {internal: 5, external: 6},
81
+ # time: 1422568543702900257
82
+ # },
83
+ # {name: 'gpu', fields: {value: 0.9999}}
84
+ # ],
85
+ # precision: InfluxDB::WritePrecision::NANOSECOND,
86
+ # bucket: 'my-bucket',
87
+ # org: 'my-org'
88
+ # )
89
+ #
90
+ # @example write(data: 'h2o,location=west value=33i 15')
91
+ #
92
+ # @example point = InfluxDB::Point.new(name: 'h2o')
93
+ # .add_tag('location', 'europe')
94
+ # .add_field('level', 2)
95
+ #
96
+ # hash = { name: 'h2o', tags: { host: 'aws', region: 'us' }, fields: { level: 5, saturation: '99%' }, time: 123 }
97
+ #
98
+ # write(data: ['h2o,location=west value=33i 15', point, hash])
99
+ #
100
+ # @param [Object] data DataPoints to write into InfluxDB. The data could be represent by [Hash], [Point], [String]
101
+ # or by collection of these types
102
+ # @param [WritePrecision] precision The precision for the unix timestamps within the body line-protocol
103
+ # @param [String] bucket specifies the destination bucket for writes
104
+ # @param [String] org specifies the destination organization for writes
105
+ def write(data:, precision: nil, bucket: nil, org: nil)
106
+ precision_param = precision || @options[:precision]
107
+ bucket_param = bucket || @options[:bucket]
108
+ org_param = org || @options[:org]
109
+ _check('precision', precision_param)
110
+ _check('bucket', bucket_param)
111
+ _check('org', org_param)
112
+
113
+ payload = _generate_payload(data, bucket: bucket_param, org: org_param, precision: precision_param)
114
+ return nil if payload.nil?
115
+
116
+ if WriteType::BATCHING == @write_options.write_type
117
+ _worker.push(payload)
118
+ else
119
+ write_raw(payload, precision: precision_param, bucket: bucket_param, org: org_param)
120
+ end
121
+ end
122
+
123
+ # @return [ true ] Always true.
124
+ def close!
125
+ _worker.flush_all unless _worker.nil?
126
+ @closed = true
127
+ true
128
+ end
129
+
130
+ # @param [String] payload data as String
131
+ # @param [WritePrecision] precision The precision for the unix timestamps within the body line-protocol
132
+ # @param [String] bucket specifies the destination bucket for writes
133
+ # @param [String] org specifies the destination organization for writes
134
+ def write_raw(payload, precision: nil, bucket: nil, org: nil)
135
+ precision_param = precision || @options[:precision]
136
+ bucket_param = bucket || @options[:bucket]
137
+ org_param = org || @options[:org]
138
+ _check('precision', precision_param)
139
+ _check('bucket', bucket_param)
140
+ _check('org', org_param)
141
+
142
+ return nil unless payload.instance_of?(String) || payload.empty?
143
+
144
+ uri = URI.parse(File.join(@options[:url], '/api/v2/write'))
145
+ uri.query = URI.encode_www_form(bucket: bucket_param, org: org_param, precision: precision_param.to_s)
146
+
147
+ _post(payload, uri)
148
+ end
149
+
150
+ # Item for batching queue
151
+ class BatchItem
152
+ def initialize(key, data)
153
+ @key = key
154
+ @data = data
155
+ end
156
+ attr_reader :key, :data
157
+ end
158
+
159
+ # Key for batch item
160
+ class BatchItemKey
161
+ def initialize(bucket, org, precision = DEFAULT_WRITE_PRECISION)
162
+ @bucket = bucket
163
+ @org = org
164
+ @precision = precision
165
+ end
166
+ attr_reader :bucket, :org, :precision
167
+
168
+ def ==(other)
169
+ @bucket == other.bucket && @org == other.org && @precision == other.precision
170
+ end
171
+
172
+ alias eql? ==
173
+
174
+ def hash
175
+ @bucket.hash ^ @org.hash ^ @precision.hash # XOR
176
+ end
177
+ end
178
+
179
+ private
180
+
181
+ WORKER_MUTEX = Mutex.new
182
+ def _worker
183
+ return nil unless @write_options.write_type == WriteType::BATCHING
184
+
185
+ return @worker if @worker
186
+
187
+ WORKER_MUTEX.synchronize do
188
+ # this return is necessary because the previous mutex holder
189
+ # might have already assigned the @worker
190
+ return @worker if @worker
191
+
192
+ @worker = Worker.new(self, @write_options)
193
+ end
194
+ end
195
+
196
+ def _generate_payload(data, precision: nil, bucket: nil, org: nil)
197
+ if data.nil?
198
+ nil
199
+ elsif data.is_a?(Point)
200
+ _generate_payload(data.to_line_protocol, bucket: bucket, org: org, precision: data.precision ||
201
+ DEFAULT_WRITE_PRECISION)
202
+ elsif data.is_a?(String)
203
+ if data.empty?
204
+ nil
205
+ elsif @write_options.write_type == WriteType::BATCHING
206
+ BatchItem.new(BatchItemKey.new(bucket, org, precision), data)
207
+ else
208
+ data
209
+ end
210
+ elsif data.is_a?(Hash)
211
+ _generate_payload(Point.from_hash(data), bucket: bucket, org: org, precision: precision)
212
+ elsif data.respond_to? :map
213
+ data.map do |item|
214
+ _generate_payload(item, bucket: bucket, org: org, precision: precision)
215
+ end.reject(&:nil?).join("\n".freeze)
216
+ end
217
+ end
218
+ end
219
+ end