influxdb-lineprotocol-parser 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2490d6234974b7e68904ea3e1e0f8097d43aafd15946632222e6428bf84920b
4
- data.tar.gz: 501f6bf3750299aea99024b1180841bbaee15e343cf7d9ab843726c0bfcdb5c1
3
+ metadata.gz: e8e81800340bcf80271e56f5c19d03ea4cd8b9535e8d0fd5bd86e605f3dae124
4
+ data.tar.gz: 1baa9ee265fc57e8b7bf987b8314c9305c8ab3f0240066ea9db2d901b425a35d
5
5
  SHA512:
6
- metadata.gz: '094ae5413c86aab7448ca3b4b1e2a6ff90b70f8559434493e6efd02ade64cbf60e965eaf8f760e2fa00afe18e9c86880e3501c627e8aee569ba990b209d7d4b8'
7
- data.tar.gz: 9f03253c475b67532099c956609ec833e5fb934c19979574ec4aafe382c8b8553b61e1bb2b533cedd20743adddcaf5f7e118675f7de914eccf7984dc6e0730bf
6
+ metadata.gz: 3655611e6edbfce8c960707bf72f1f3972a7d8ced12488280a088646145551e5668329766143df26f4258acc17ffa17233e7ea9f802240d4a579d01b99e4b55f
7
+ data.tar.gz: ba6b7f5b37fcdc32f4e46a51d4fb7ed92b34921cb0c173ba80664a838dafef33008423745b21193592f483161edb66e916116dd353d7234f53d4db4932e05f2d
@@ -12,18 +12,592 @@
12
12
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
+ require 'logger'
16
+
17
+ ##
18
+ # Extension to InfluxDB module.
19
+ #
15
20
  module InfluxDBExt
21
+ ##
22
+ # Line Protocol module.
23
+ #
16
24
  module LineProtocol
25
+
26
+ ##
27
+ # Line Protocol parser.
28
+ #
17
29
  class Parser
30
+ def initialize(logger: nil, log_level: :warn)
31
+ @log = logger || ::Logger.new(STDERR)
32
+ @log.level = log_level
33
+ enter_initial
34
+ end
35
+
36
+ ##
37
+ # Parse the points from data.
38
+ #
18
39
  # If block is given, yields each point in data.
40
+ #
19
41
  # If block is not given, returns a list of points in data.
20
- def each_point data
21
- if block_given?
22
- yield nil
42
+ #
43
+ def each_point(data)
44
+ # TODO: support byte arrays (client does not have to ensure that only complete UTF-8 characters are passed)
45
+ buf = data.to_s.encode(UTF_8).bytes.freeze
46
+ i = 0
47
+ len = buf.size
48
+
49
+ points = block_given? ? nil : []
50
+
51
+ while i < len
52
+ i = self.send(@state, buf, i, len)
53
+ if @state == :complete
54
+ if block_given?
55
+ yield @point
56
+ else
57
+ points << @point
58
+ end
59
+ enter_initial
60
+ end
61
+ end
62
+
63
+ points
64
+ end
65
+
66
+ private
67
+
68
+ def enter_initial
69
+ @point = nil
70
+ @state = :initial
71
+ @escaped = false
72
+ @buf = nil
73
+ @key = nil
74
+ end
75
+
76
+ UTF_8 = Encoding.find 'UTF-8'
77
+ UTF_8_PACK_FORMAT = 'C*'.freeze
78
+
79
+ # All the special bytes Line Protocol handles.
80
+ # In UTF-8, these are all single byte characters.
81
+ # Any multi-byte characters are just skipped as part of the tokens (measurement, tag key, tag value, ...).
82
+ BACKSLASH = 92
83
+ COMMA = 44
84
+ EQUALS = 61
85
+ HASH = 23
86
+ NEWLINE = 10
87
+ NULL = 0
88
+ SPACE = 32
89
+ TAB = 9
90
+
91
+ # Start (and end) marker of a string field value (not special anywhere else)
92
+ QUOTATION_MARK = 22
93
+
94
+ # Start markers of a numeric field value (not special anywhere else)
95
+ PLUS_SIGN = 43
96
+ MINUS_SIGN = 45
97
+ DECIMAL_POINT = 46
98
+ DIGIT_ZERO = 48
99
+ DIGIT_NINE = 57
100
+
101
+ # Start markers of a Boolean field value (not special anywhere else)
102
+ LATIN_CAPITAL_LETTER_F = 70
103
+ LATIN_CAPITAL_LETTER_T = 84
104
+ LATIN_SMALL_LETTER_F = 102
105
+ LATIN_SMALL_LETTER_T = 116
106
+
107
+
108
+
109
+ def initial(buf, i, len)
110
+ # whitespace consumes TAB, SPACE, and NULL.
111
+ # This method consumes NEWLINE, HASH, and COMMA.
112
+ # BACKSLASH and EQUAL (of the special bytes) are valid measurement starts; they are not consumed.
113
+ i, c = whitespace(buf, i, len)
114
+ case c
115
+ when nil # just whitespace
116
+ len
117
+ when COMMA
118
+ @log.error "initial: missing measurement"
119
+ @state = :invalid
120
+ invalid(buf, i, len)
121
+ when HASH # comment
122
+ @state = :comment
123
+ i + 1
124
+ when NEWLINE
125
+ i + 1
126
+ else
127
+ # don't advance i because the byte belongs to measurement
128
+ @log.info "initial: start measurement at offset #{i}"
129
+ @state = :measurement
130
+ i
131
+ end
132
+ end
133
+
134
+ def measurement(buf, i, len)
135
+ start = i
136
+ while i < len
137
+ if @escaped
138
+ @escaped = false
139
+ i += 1
140
+ else
141
+ c = buf[i]
142
+ case c
143
+ when BACKSLASH
144
+ @escaped = true
145
+ i += 1
146
+ when COMMA # start of tag set.
147
+ @point = {series: decode(buf[start..i-1]), tags: {}}
148
+ @state = :tag_key
149
+ return i+1
150
+ when NEWLINE
151
+ @log.error("measurement: missing fields")
152
+ # no need to go via :invalid; already at newline
153
+ enter_initial
154
+ return i+1
155
+ when SPACE # start of field set
156
+ @point = {series: decode(buf[start..i-1]), values: {}}
157
+ @state = :field_key
158
+ i, _ = whitespace(buf, i + 1, len)
159
+ return i
160
+ else # part of measurement
161
+ i += 1
162
+ end
163
+ end
164
+ end
165
+ if i == len && start < i
166
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
167
+ end
168
+ i
169
+ end
170
+
171
+ def tag_key(buf, i, len)
172
+ start = i
173
+ while i < len
174
+ if @escaped
175
+ @escaped = false
176
+ i += 1
177
+ else
178
+ c = buf[i]
179
+ case c
180
+ when BACKSLASH
181
+ @escaped = true
182
+ i += 1
183
+ when EQUALS
184
+ @key = decode(buf[start..i-1])
185
+ if @key == ""
186
+ @log.error("tag_key: empty key")
187
+ @state = :invalid
188
+ return invalid(buf, i, len)
189
+ end
190
+ @state = :tag_value
191
+ return i+1
192
+ when NEWLINE
193
+ @log.error("tag key: newline")
194
+ enter_initial
195
+ return i+1
196
+ else
197
+ i += 1
198
+ end
199
+ end
200
+ end
201
+ if i == len && start < i
202
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
203
+ end
204
+ i
205
+ end
206
+
207
+ def tag_value(buf, i, len)
208
+ start = i
209
+ while i < len
210
+ if @escaped
211
+ @escaped = false
212
+ i += 1
213
+ else
214
+ c = buf[i]
215
+ case c
216
+ when BACKSLASH
217
+ @escaped = true
218
+ i += 1
219
+ when COMMA
220
+ @point[:tags][@key] = decode(buf[start..i-1])
221
+ @key = nil
222
+ @state = :tag_key
223
+ return i+1
224
+ when NEWLINE
225
+ @log.error("tag value: newline")
226
+ enter_initial
227
+ return i+1
228
+ when SPACE
229
+ @point[:tags][@key] = decode(buf[start..i-1])
230
+ @key = nil
231
+ @state = :field_key
232
+ i, _ = whitespace(buf, i + 1, len)
233
+ return i
234
+ else
235
+ i += 1
236
+ end
237
+ end
238
+ end
239
+ if i == len && start < i
240
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
241
+ end
242
+ i
243
+ end
244
+
245
+ def field_key(buf, i, len)
246
+ start = i
247
+ while i < len
248
+ if @escaped
249
+ @escaped = false
250
+ i += 1
251
+ else
252
+ c = buf[i]
253
+ case c
254
+ when BACKSLASH
255
+ @escaped = true
256
+ i += 1
257
+ when EQUALS
258
+ @key = decode(buf[start..i-1])
259
+ if @key == ""
260
+ @log.error("field key: empty key")
261
+ @state = :invalid
262
+ return invalid(buf, i + 1, len)
263
+ end
264
+ @state = :field_value
265
+ return i+1
266
+ when NEWLINE
267
+ @log.error("field key: newline")
268
+ enter_initial
269
+ return i+1
270
+ else
271
+ i += 1
272
+ end
273
+ end
274
+ end
275
+ if i == len && start < i
276
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
277
+ end
278
+ i
279
+ end
280
+
281
+ def field_value(buf, i, len)
282
+ if i == len
283
+ return len
284
+ end
285
+ c = buf[i]
286
+ case c
287
+ when LATIN_CAPITAL_LETTER_F, LATIN_CAPITAL_LETTER_T, LATIN_SMALL_LETTER_F, LATIN_SMALL_LETTER_T
288
+ @state = :field_value_boolean
289
+ i
290
+ when DIGIT_ZERO..DIGIT_NINE, PLUS_SIGN, MINUS_SIGN, DECIMAL_POINT
291
+ @state = :field_value_numeric
292
+ i
293
+ when QUOTATION_MARK
294
+ @state = :field_value_string
295
+ i + 1
296
+ else
297
+ @log.error("field value: invalid")
298
+ @state = :invalid
299
+ invalid(buf, i, len)
300
+ end
301
+ end
302
+
303
+ def field_value_boolean(buf, i, len)
304
+ start = i
305
+ while i < len
306
+ if @escaped
307
+ @escaped = false
308
+ i += 1
309
+ else
310
+ c = buf[i]
311
+ case c
312
+ when BACKSLASH
313
+ @escaped = true
314
+ i += 1
315
+ when COMMA
316
+ value = decode(buf[start..i-1])
317
+ if value.nil?
318
+ @log.error("field value boolean: invalid boolean")
319
+ @state = :invalid
320
+ return invalid(buf, i, len)
321
+ end
322
+ @point[:values][@key] = value
323
+ @key = nil
324
+ @state = :field_key
325
+ return i+1
326
+ when NEWLINE
327
+ value = decode(buf[start..i-1])
328
+ if value.nil?
329
+ @log.error("field value boolean: invalid boolean")
330
+ enter_initial
331
+ return i + 1
332
+ end
333
+ @point[:values][@key] = value
334
+ @key = nil
335
+ @state = :complete
336
+ return i+1
337
+ when SPACE
338
+ value = decode(buf[start..i-1])
339
+ if value.nil?
340
+ @log.error("field value boolean: invalid boolean")
341
+ @state = :invalid
342
+ return invalid(buf, i, len)
343
+ end
344
+ @point[:values][@key] = value
345
+ @key = nil
346
+ @state = :timestamp
347
+ i, _ = whitespace(buf, i + 1, len)
348
+ return i
349
+ else
350
+ i += 1
351
+ end
352
+ end
353
+ end
354
+ if i == len && start < i
355
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
356
+ end
357
+ i
358
+ end
359
+
360
+ def field_value_numeric(buf, i, len)
361
+ start = i
362
+ while i < len
363
+ if @escaped
364
+ @escaped = false
365
+ i += 1
366
+ else
367
+ c = buf[i]
368
+ case c
369
+ when BACKSLASH
370
+ @escaped = true
371
+ i += 1
372
+ when COMMA
373
+ value = decode(buf[start..i-1])
374
+ if value.nil?
375
+ @log.error("field value numeric: invalid number")
376
+ @state = :invalid
377
+ return invalid(buf, i, len)
378
+ end
379
+ @point[:values][@key] = value
380
+ @key = nil
381
+ @state = :field_key
382
+ return i+1
383
+ when NEWLINE
384
+ value = decode(buf[start..i-1])
385
+ if value.nil?
386
+ @log.error("field value numeric: invalid number")
387
+ @state = :invalid
388
+ return invalid(buf, i, len)
389
+ end
390
+ @point[:values][@key] = value
391
+ @key = nil
392
+ @state = :complete
393
+ return i+1
394
+ when SPACE
395
+ value = decode(buf[start..i-1])
396
+ if value.nil?
397
+ @log.error("field value numeric: invalid number")
398
+ @state = :invalid
399
+ return invalid(buf, i, len)
400
+ end
401
+ @point[:values][@key] = value
402
+ @key = nil
403
+ @state = :timestamp
404
+ i, _ = whitespace(buf, i + 1, len)
405
+ return i
406
+ else
407
+ i += 1
408
+ end
409
+ end
410
+ end
411
+ if i == len && start < i
412
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
413
+ end
414
+ i
415
+ end
416
+
417
+ def field_value_string(buf, i, len)
418
+ start = i
419
+ while i < len
420
+ if @escaped
421
+ @escaped = false
422
+ i += 1
423
+ else
424
+ c = buf[i]
425
+ case c
426
+ when BACKSLASH
427
+ @escaped = true
428
+ i += 1
429
+ when QUOTATION_MARK
430
+ value = decode(buf[start..i-1])
431
+ if value.nil?
432
+ @log.error("field value string: invalid string")
433
+ @state = :invalid
434
+ return invalid(buf, i, len)
435
+ end
436
+ @point[:values][@key] = value
437
+ @key = nil
438
+ @state = :field_value_string_end
439
+ return i+1
440
+ else
441
+ i += 1
442
+ end
443
+ end
444
+ end
445
+ if i == len && start < i
446
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
447
+ end
448
+ i
449
+ end
450
+
451
+ def field_value_string_end(buf, i, len)
452
+ if i < len
453
+ case buf[i]
454
+ when COMMA
455
+ @state = :field_key
456
+ i + 1
457
+ when NEWLINE
458
+ @state = :complete
459
+ i + 1
460
+ when SPACE
461
+ @state = :timestamp
462
+ i, _ = whitespace(buf, i, len)
463
+ i
464
+ else
465
+ @state = :invalid
466
+ invalid(buf, i, len)
467
+ end
468
+ else
469
+ len
470
+ end
471
+ end
472
+
473
+ def timestamp(buf, i, len)
474
+ start = i
475
+ while i < len
476
+ if @escaped
477
+ @escaped = false
478
+ i += 1
479
+ else
480
+ c = buf[i]
481
+ case c
482
+ when BACKSLASH
483
+ @escaped = true
484
+ i += 1
485
+ when NEWLINE
486
+ value = decode(buf[start..i-1])
487
+ if value.nil?
488
+ @log.error("timestamp: invalid timestamp")
489
+ @state = :invalid
490
+ return invalid(buf, i, len)
491
+ end
492
+ @point[:timestamp] = value
493
+ @key = nil
494
+ @state = :complete
495
+ return i + 1
496
+ else
497
+ i += 1
498
+ end
499
+ end
500
+ end
501
+ if i == len && start < i
502
+ @buf.nil? ? @buf = buf[start..i-1] : @buf += buf[start..i-1]
503
+ end
504
+ i
505
+ end
506
+
507
+ def comment(buf, i, len)
508
+ i = line_end(buf, i, len)
509
+ if i < len
510
+ enter_initial
511
+ end
512
+ i
513
+ end
514
+
515
+ def invalid(buf, i, len)
516
+ i = line_end(buf, i, len)
517
+ if i < len
518
+ enter_initial
519
+ end
520
+ i
521
+ end
522
+
523
+ # Starting from position i,
524
+ # returns the index of the byte that follows next newline.
525
+ # Returns len if no such byte is found or it is at the end of buf.
526
+ def line_end(buf, i, len)
527
+ while i < len
528
+ c = buf[i]
529
+ if c == NEWLINE
530
+ i += 1
531
+ return i
532
+ end
533
+ i += 1
534
+ end
535
+ len
536
+ end
537
+
538
+ # Starting from position i,
539
+ # return index of the first non-whitespace byte and the byte itself.
540
+ # Return len and nil if no such byte is found.
541
+ def whitespace(buf, i, len)
542
+ while i < len
543
+ c = buf[i]
544
+ if c != SPACE && c != TAB && c != NULL
545
+ return [i, c]
546
+ end
547
+ i += 1
548
+ end
549
+ [len, nil]
550
+ end
551
+
552
+ def decode(buf)
553
+ str = @buf.nil? ? string(buf) : string(@buf + buf)
554
+ @buf = nil
555
+ case @state
556
+ when :measurement
557
+ str
558
+ when :tag_key
559
+ str
560
+ when :tag_value
561
+ str
562
+ when :field_key
563
+ str
564
+ when :field_value_boolean
565
+ case str
566
+ when 't', 'T', 'true', 'True'
567
+ true
568
+ when 'f', 'F', 'false', 'False'
569
+ false
570
+ else
571
+ @log.error("invalid Boolean: #{str}")
572
+ nil
573
+ end
574
+ when :field_value_numeric
575
+ case str
576
+ when /^[+-]?[0-9]*\.[0-9]*([eE][+-]?[0-9]+)?$/
577
+ str.to_f
578
+ when /^[+-]?[0-9]+[ui]$/
579
+ str.to_i
580
+ else
581
+ @log.error("invalid number: #{str}")
582
+ nil
583
+ end
584
+ when :field_value_string
585
+ str
586
+ when :timestamp
587
+ case str
588
+ when /^-?[0-9]+$/
589
+ str.to_i
590
+ else
591
+ @log.error("invalid timestamp: #{str}")
592
+ end
23
593
  else
24
- []
594
+ raise "error: decode: invalid state"
25
595
  end
26
596
  end
597
+
598
+ def string(buf)
599
+ buf.pack(UTF_8_PACK_FORMAT).force_encoding(UTF_8)
600
+ end
27
601
  end
28
602
  end
29
603
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: influxdb-lineprotocol-parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikko Värri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-09 00:00:00.000000000 Z
11
+ date: 2019-05-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'Streaming parser for InfluxDB line protocol.
14
14
 
@@ -33,7 +33,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
33
33
  requirements:
34
34
  - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: '0'
36
+ version: '2.3'
37
37
  required_rubygems_version: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="