influxdb-client 1.1.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 (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