influxdb-lineprotocol-parser 0.0.1 → 0.0.2

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