Created
June 22, 2023 07:00
-
-
Save snichme/c468dd2aaff61d6bb4e73ec179a46f7f to your computer and use it in GitHub Desktop.
Parse Prometheus in Crystal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "json" | |
HASH = 35 | |
NEWLINE = 10 | |
SPACE = 32 | |
CURLY_BRACKET_LEFT = 123 | |
CURLY_BRACKET_RIGHT = 125 | |
EQUALSIGN = 61 | |
BACKSLASH = 92 | |
QUOTE = 34 | |
class Cursor | |
@buf : Bytes | |
def initialize(io) | |
@buf = io.to_slice | |
@i = 0 | |
end | |
def peek | |
@buf[@i] | |
end | |
def next | |
v = @buf[@i] | |
@i += 1 | |
v | |
end | |
def pos | |
@i | |
end | |
def size | |
@buf.size | |
end | |
end | |
class Reader | |
record Metric, name : String, attributes : Hash(String, String) | Nil, value : Float64 do | |
def to_json(json : JSON::Builder) | |
json.object do | |
json.field "name", name | |
if attrs = attributes | |
json.field "attributes" do | |
json.object do | |
attrs.each do |k, v| | |
json.field k, v | |
end | |
end | |
end | |
end | |
if value.nan? | |
json.field "value", "NaN" | |
else | |
json.field "value", value | |
end | |
end | |
end | |
end | |
record Comment, type : String, name : String, desc : String | |
def initialize(io : IO::Memory) | |
@buf = Cursor.new io | |
end | |
def as_json | |
b = @buf | |
size = b.size | |
res = IO::Memory.new | |
res.write("[".to_slice) | |
first = true | |
until b.pos >= size - 1 | |
case b.peek | |
when HASH | |
read_comment(b) | |
when NEWLINE | |
b.next | |
else | |
res.write(",".to_slice) unless first | |
read_metric(b).to_json(res) | |
first = false | |
end | |
end | |
res.write("]".to_slice) | |
res | |
end | |
def read : Array(Metric) | |
b = @buf | |
size = b.size | |
res = Array(Metric).new | |
until b.pos >= size - 1 | |
case b.peek | |
when HASH | |
read_comment(b) | |
when NEWLINE | |
b.next | |
else | |
res << read_metric(b) | |
end | |
end | |
res | |
end | |
def read_comment(buf) : Comment | |
buf.next | |
buf.next | |
typ = read_comment_type(buf) | |
name = read_metric_name(buf) | |
desc = read_comment_desc(buf) | |
Comment.new(typ, name, desc) | |
end | |
def read_comment_type(buf) : String | |
line = String::Builder.new | |
loop do | |
v = buf.next | |
break unless upcase_char?(v) | |
line.write_byte(v) | |
end | |
line.to_s | |
end | |
def read_comment_desc(buf) : String | |
line = String::Builder.new | |
buf.next | |
loop do | |
v = buf.next | |
break if v == NEWLINE | |
line.write_byte(v) | |
end | |
line.to_s | |
end | |
def read_metric_name(buf) : String | |
line = String::Builder.new | |
loop do | |
v = buf.peek | |
break if v == CURLY_BRACKET_LEFT || v == SPACE | |
line.write_byte(v) | |
buf.next | |
end | |
line.to_s | |
end | |
# "rabbitmq_detailed_queue_messages_ready{vhost=\"gyktijvv\",queue=\"global_{9ff82397-54d9-4ccc-813f-f6c9709ba846}_BaseLayerService_0\"} 0\n" + | |
def read_metric_attribute_name(buf) : String | |
line = String::Builder.new | |
loop do | |
v = buf.peek | |
break if v == EQUALSIGN | |
line.write_byte(v) | |
buf.next | |
end | |
line.to_s | |
end | |
def lowcase_char?(v) | |
v >= 97 && v <= 122 | |
end | |
def upcase_char?(v) | |
v >= 65 && v <= 90 | |
end | |
def char?(v) | |
upcase_char?(v) || lowcase_char?(v) | |
end | |
def read_metric_attribute_value(buf) : String | |
line = String::Builder.new | |
buf.next # move beyond first " | |
escaped = false | |
loop do | |
v = buf.next | |
if escaped | |
case v | |
when QUOTE, BACKSLASH | |
line.write_byte(v) | |
when 110 # lowercase n | |
line.write_byte(34) | |
when 116 # lowercase t | |
line.write_byte(10) | |
else | |
line.write_byte(92) | |
end | |
escaped = false | |
next | |
end | |
case v | |
when QUOTE | |
break | |
when NEWLINE | |
raise "label value '#{line.to_s}' contains unescaped new-line" | |
when BACKSLASH | |
escaped = true | |
else | |
line.write_byte(v) | |
end | |
end | |
line.to_s | |
end | |
def read_metric_attributes(buf) : Hash(String, String) | |
res = Hash(String, String).new | |
buf.next | |
loop do | |
name = read_metric_attribute_name(buf) | |
buf.next | |
value = read_metric_attribute_value(buf) | |
v = buf.next | |
res[name] = value unless value.empty? | |
break if v == CURLY_BRACKET_RIGHT | |
end | |
res | |
end | |
def read_metric_value(buf) : Float64 | |
line = String::Builder.new | |
loop do | |
v = buf.peek | |
break if v == NEWLINE | |
line.write_byte(v) | |
buf.next | |
end | |
line.to_s.to_f | |
end | |
def read_metric(buf) : Metric | |
name = read_metric_name(buf) | |
attrs = nil | |
if buf.peek == CURLY_BRACKET_LEFT | |
attrs = read_metric_attributes(buf) | |
end | |
value = read_metric_value(buf) | |
Metric.new(name, attrs, value) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment