mayu-live 0.0.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.
- checksums.yaml +7 -0
- data/COPYING +661 -0
- data/README.md +598 -0
- data/exe/mayu +33 -0
- data/lib/mayu/app_metrics.rb +93 -0
- data/lib/mayu/banner.rb +12 -0
- data/lib/mayu/client/README.md +17 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.br +0 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map.br +0 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js.map +1 -0
- data/lib/mayu/client/dist/entries.json +3 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.br +0 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map.br +0 -0
- data/lib/mayu/client/package.json +39 -0
- data/lib/mayu/client/rollup.config.js +81 -0
- data/lib/mayu/client/src/DecompressionStream.ts +15 -0
- data/lib/mayu/client/src/DecompressionStreamPolyfill.ts +43 -0
- data/lib/mayu/client/src/MimeTypes.ts +4 -0
- data/lib/mayu/client/src/NodeTree.ts +445 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.html +137 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.ts +62 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.html +134 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.ts +51 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.html +79 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.ts +28 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.html +70 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.ts +42 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.html +36 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.ts +53 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.html +44 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.ts +40 -0
- data/lib/mayu/client/src/custom-elements/types.d.ts +4 -0
- data/lib/mayu/client/src/global.d.ts +26 -0
- data/lib/mayu/client/src/h.ts +27 -0
- data/lib/mayu/client/src/logger.ts +56 -0
- data/lib/mayu/client/src/main.ts +271 -0
- data/lib/mayu/client/src/serializeEvent.ts +90 -0
- data/lib/mayu/client/src/stream.ts +175 -0
- data/lib/mayu/client/src/types.ts +1 -0
- data/lib/mayu/client/src/utils.ts +71 -0
- data/lib/mayu/client/tsconfig.json +18 -0
- data/lib/mayu/colors.rb +34 -0
- data/lib/mayu/commands/base.rb +22 -0
- data/lib/mayu/commands/build.rb +82 -0
- data/lib/mayu/commands.rb +53 -0
- data/lib/mayu/component/base.rb +177 -0
- data/lib/mayu/component/handler_ref.rb +99 -0
- data/lib/mayu/component/helpers.rb +93 -0
- data/lib/mayu/component/interface.rb +18 -0
- data/lib/mayu/component/wrapper.rb +165 -0
- data/lib/mayu/component.rb +54 -0
- data/lib/mayu/configuration.rb +195 -0
- data/lib/mayu/disable_sorbet.rb +23 -0
- data/lib/mayu/environment.rb +151 -0
- data/lib/mayu/event_stream.rb +158 -0
- data/lib/mayu/fetch.rb +88 -0
- data/lib/mayu/html.rb +53 -0
- data/lib/mayu/html.yaml +767 -0
- data/lib/mayu/message_cipher.rb +172 -0
- data/lib/mayu/message_cipher.test.rb +16 -0
- data/lib/mayu/metrics/collector.rb +161 -0
- data/lib/mayu/metrics/exporter.rb +47 -0
- data/lib/mayu/metrics/reporter.rb +187 -0
- data/lib/mayu/metrics.rb +82 -0
- data/lib/mayu/ref_counter.rb +57 -0
- data/lib/mayu/resources/README.md +14 -0
- data/lib/mayu/resources/asset.rb +71 -0
- data/lib/mayu/resources/assets.rb +76 -0
- data/lib/mayu/resources/dependency_graph.rb +306 -0
- data/lib/mayu/resources/dot_exporter.rb +167 -0
- data/lib/mayu/resources/generators/base.rb +18 -0
- data/lib/mayu/resources/generators/copy_file.rb +26 -0
- data/lib/mayu/resources/generators/image.rb +106 -0
- data/lib/mayu/resources/generators/write_file.rb +39 -0
- data/lib/mayu/resources/hot_swap/file_watcher.rb +69 -0
- data/lib/mayu/resources/hot_swap.rb +46 -0
- data/lib/mayu/resources/mermaid_exporter.rb +210 -0
- data/lib/mayu/resources/registry.rb +190 -0
- data/lib/mayu/resources/resolver/base.rb +32 -0
- data/lib/mayu/resources/resolver/filesystem.rb +94 -0
- data/lib/mayu/resources/resolver/static.rb +27 -0
- data/lib/mayu/resources/resolver.rb +13 -0
- data/lib/mayu/resources/resource.rb +150 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.in.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/has.in.css +7 -0
- data/lib/mayu/resources/transformers/__test__/css/has.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.in.css +8 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.out.css +12 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.in.css +5 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/README.md +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.rb +15 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.haml +13 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.rb +26 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.rb +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.haml +1 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.rb +14 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.haml +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.rb +10 -0
- data/lib/mayu/resources/transformers/css/rouge_lexer.rb +841 -0
- data/lib/mayu/resources/transformers/css.rb +100 -0
- data/lib/mayu/resources/transformers/css.test.rb +87 -0
- data/lib/mayu/resources/transformers/haml.rb +984 -0
- data/lib/mayu/resources/transformers/haml.test.rb +114 -0
- data/lib/mayu/resources/types/README.md +36 -0
- data/lib/mayu/resources/types/base.rb +35 -0
- data/lib/mayu/resources/types/component.rb +198 -0
- data/lib/mayu/resources/types/image.rb +169 -0
- data/lib/mayu/resources/types/javascript.rb +50 -0
- data/lib/mayu/resources/types/nil.rb +23 -0
- data/lib/mayu/resources/types/stylesheet.rb +119 -0
- data/lib/mayu/resources/types/svg.rb +69 -0
- data/lib/mayu/resources/types.rb +37 -0
- data/lib/mayu/routes.rb +170 -0
- data/lib/mayu/routing/builder.rb +108 -0
- data/lib/mayu/routing/matcher.rb +58 -0
- data/lib/mayu/routing/routes.rb +85 -0
- data/lib/mayu/routing.rb +17 -0
- data/lib/mayu/server/app.rb +494 -0
- data/lib/mayu/server/controller.rb +152 -0
- data/lib/mayu/server/errors.rb +110 -0
- data/lib/mayu/server/file_server.rb +140 -0
- data/lib/mayu/server.rb +63 -0
- data/lib/mayu/session.rb +358 -0
- data/lib/mayu/state/README.md +6 -0
- data/lib/mayu/state/action_creator.rb +191 -0
- data/lib/mayu/state/action_wrapper.rb +30 -0
- data/lib/mayu/state/loader.rb +220 -0
- data/lib/mayu/state/store.rb +82 -0
- data/lib/mayu/state.rb +8 -0
- data/lib/mayu/state.test.rb +97 -0
- data/lib/mayu/utils.rb +114 -0
- data/lib/mayu/vdom/children.rb +117 -0
- data/lib/mayu/vdom/component_marshaler.rb +53 -0
- data/lib/mayu/vdom/css_attributes.rb +131 -0
- data/lib/mayu/vdom/descriptor.rb +151 -0
- data/lib/mayu/vdom/descriptor.test.rb +26 -0
- data/lib/mayu/vdom/dom.rb +239 -0
- data/lib/mayu/vdom/h.rb +22 -0
- data/lib/mayu/vdom/id_generator.rb +55 -0
- data/lib/mayu/vdom/interfaces.rb +186 -0
- data/lib/mayu/vdom/marshalling.rb +78 -0
- data/lib/mayu/vdom/reconciliation.rb +205 -0
- data/lib/mayu/vdom/reconciliation.test.rb +56 -0
- data/lib/mayu/vdom/special_elements.rb +108 -0
- data/lib/mayu/vdom/update_context.rb +180 -0
- data/lib/mayu/vdom/vdom.perf.test.rb +146 -0
- data/lib/mayu/vdom/vnode.rb +266 -0
- data/lib/mayu/vdom/vtree.rb +672 -0
- data/lib/mayu/vdom/vtree.test.rb +68 -0
- data/lib/mayu/vdom.rb +8 -0
- data/lib/mayu/vdom.test.rb +73 -0
- data/lib/mayu/version.rb +6 -0
- data/lib/mayu.rb +8 -0
- data/mayu-live.gemspec +70 -0
- metadata +612 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require "time"
|
6
|
+
require "digest/sha2"
|
7
|
+
require "openssl"
|
8
|
+
require "securerandom"
|
9
|
+
require "brotli"
|
10
|
+
|
11
|
+
module Mayu
|
12
|
+
class MessageCipher
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
DEFAULT_TTL_SECONDS = T.let(10, Integer)
|
16
|
+
|
17
|
+
Message = T.type_alias { { iss: Float, exp: Float, payload: T.untyped } }
|
18
|
+
|
19
|
+
class Error < StandardError
|
20
|
+
end
|
21
|
+
class ExpiredError < Error
|
22
|
+
end
|
23
|
+
class IssuedInTheFutureError < Error
|
24
|
+
end
|
25
|
+
class EncryptError < Error
|
26
|
+
end
|
27
|
+
class DecryptError < Error
|
28
|
+
end
|
29
|
+
class InvalidHMACError < Error
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(key: String, ttl: Integer).void }
|
33
|
+
def initialize(key:, ttl: DEFAULT_TTL_SECONDS)
|
34
|
+
raise ArgumentError, "ttl must be positive" unless ttl.positive?
|
35
|
+
@default_ttl_seconds = ttl
|
36
|
+
@key = T.let(Digest::SHA256.digest(key), String)
|
37
|
+
end
|
38
|
+
|
39
|
+
sig do
|
40
|
+
params(payload: T.untyped, auth_data: String, ttl: Integer).returns(
|
41
|
+
String
|
42
|
+
)
|
43
|
+
end
|
44
|
+
def dump(payload, auth_data: "", ttl: @default_ttl_seconds)
|
45
|
+
raise ArgumentError, "ttl must be positive" unless ttl.positive?
|
46
|
+
now = Time.now.to_f
|
47
|
+
message = { iss: now, exp: now + ttl, payload: Marshal.dump(payload) }
|
48
|
+
encode_message(message, auth_data:)
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { params(data: String, auth_data: String).returns(T.untyped) }
|
52
|
+
def load(data, auth_data: "")
|
53
|
+
Marshal.load(decode_message(data, auth_data:))
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
sig { params(message: Message, auth_data: String).returns(String) }
|
59
|
+
def encode_message(message, auth_data: "")
|
60
|
+
message
|
61
|
+
.then { Marshal.dump(_1) }
|
62
|
+
.then { prepend_hmac(_1) }
|
63
|
+
.then { Brotli.deflate(_1) }
|
64
|
+
.then { encrypt(_1, auth_data:) }
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { params(message: String, auth_data: String).returns(String) }
|
68
|
+
def decode_message(message, auth_data: "")
|
69
|
+
message
|
70
|
+
.then { decrypt(_1, auth_data:) }
|
71
|
+
.then { Brotli.inflate(_1) }
|
72
|
+
.then { validate_hmac(_1) }
|
73
|
+
.then { Marshal.load(_1) }
|
74
|
+
.tap { validate_times(_1) }
|
75
|
+
.fetch(:payload)
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { params(input: String).returns(String) }
|
79
|
+
def prepend_hmac(input)
|
80
|
+
hmac = Digest::SHA256.digest(input)
|
81
|
+
input.prepend(hmac)
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { params(input: String).returns(String) }
|
85
|
+
def validate_hmac(input)
|
86
|
+
hmac, message = input.unpack("a32 a*")
|
87
|
+
|
88
|
+
unless OpenSSL.fixed_length_secure_compare(
|
89
|
+
hmac,
|
90
|
+
Digest::SHA256.digest(message.to_s)
|
91
|
+
)
|
92
|
+
raise InvalidHMACError
|
93
|
+
end
|
94
|
+
|
95
|
+
message.to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(message: { iss: Float, exp: Float, payload: String }).void }
|
99
|
+
def validate_times(message)
|
100
|
+
message => { iss:, exp: }
|
101
|
+
now = Time.now.to_f
|
102
|
+
validate_iss(now, iss)
|
103
|
+
validate_exp(now, exp)
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { params(now: Float, iss: Float).void }
|
107
|
+
def validate_iss(now, iss)
|
108
|
+
return if iss < now
|
109
|
+
|
110
|
+
raise IssuedInTheFutureError,
|
111
|
+
"The message was issued at #{Time.at(iss).iso8601}, which is in the future"
|
112
|
+
end
|
113
|
+
|
114
|
+
sig { params(now: Float, exp: Float).void }
|
115
|
+
def validate_exp(now, exp)
|
116
|
+
return if exp > now
|
117
|
+
|
118
|
+
raise ExpiredError,
|
119
|
+
"The message expired at #{Time.at(exp).iso8601}, which is in the past"
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { params(message: String, auth_data: String).returns(String) }
|
123
|
+
def encrypt(message, auth_data: "")
|
124
|
+
cipher = OpenSSL::Cipher.new("aes-256-gcm")
|
125
|
+
cipher.encrypt
|
126
|
+
salt = SecureRandom.random_bytes(8)
|
127
|
+
cipher.key = generate_key(salt)
|
128
|
+
cipher.iv = iv = cipher.random_iv
|
129
|
+
cipher.auth_data = auth_data
|
130
|
+
cipher_text = cipher.update(message) + cipher.final
|
131
|
+
auth_tag = cipher.auth_tag
|
132
|
+
[auth_tag.bytesize, auth_tag, salt, iv, cipher_text].pack("C a* a* a* a*")
|
133
|
+
rescue OpenSSL::Cipher::CipherError
|
134
|
+
raise EncryptError
|
135
|
+
end
|
136
|
+
|
137
|
+
sig { params(data: String, auth_data: String).returns(String) }
|
138
|
+
def decrypt(data, auth_data: "")
|
139
|
+
data.unpack("C a*") => [Integer => auth_tag_len, String => data]
|
140
|
+
|
141
|
+
data.unpack("a#{auth_tag_len} a8 a12 a*") => [
|
142
|
+
auth_tag,
|
143
|
+
salt,
|
144
|
+
iv,
|
145
|
+
cipher_text
|
146
|
+
]
|
147
|
+
|
148
|
+
cipher = OpenSSL::Cipher.new("aes-256-gcm")
|
149
|
+
cipher.iv = iv
|
150
|
+
cipher.key = generate_key(salt)
|
151
|
+
cipher.auth_data = auth_data
|
152
|
+
cipher.auth_tag = auth_tag
|
153
|
+
cipher.update(cipher_text) + cipher.final
|
154
|
+
rescue NoMatchingPatternError
|
155
|
+
raise DecryptError
|
156
|
+
rescue OpenSSL::Cipher::CipherError
|
157
|
+
raise DecryptError
|
158
|
+
end
|
159
|
+
|
160
|
+
sig { params(salt: String).returns(String) }
|
161
|
+
def generate_key(salt)
|
162
|
+
OpenSSL::KDF.scrypt(
|
163
|
+
@key,
|
164
|
+
salt:, # Salt.
|
165
|
+
N: 2**14, # CPU/memory cost parameter. This must be a power of 2.
|
166
|
+
r: 8, # Block size parameter.
|
167
|
+
p: 1, # Parallelization parameter
|
168
|
+
length: 32 # Length in octets of the derived key
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
require_relative "message_cipher"
|
7
|
+
|
8
|
+
class TestMessageCipher < Minitest::Test
|
9
|
+
def test_dump_and_load
|
10
|
+
message_cipher = Mayu::MessageCipher.new(key: "test")
|
11
|
+
|
12
|
+
dumped = message_cipher.dump("hello")
|
13
|
+
loaded = message_cipher.load(dumped)
|
14
|
+
assert(loaded == "hello")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mayu
|
4
|
+
module Metrics
|
5
|
+
module Collector
|
6
|
+
class DataStore
|
7
|
+
class MetricStore
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(DataStore) }
|
11
|
+
attr_reader :store
|
12
|
+
|
13
|
+
sig do
|
14
|
+
params(
|
15
|
+
store: DataStore,
|
16
|
+
metric_name: Symbol,
|
17
|
+
metric_type: Symbol,
|
18
|
+
metric_settings: T::Hash[Symbol, T.untyped]
|
19
|
+
).void
|
20
|
+
end
|
21
|
+
def initialize(store, metric_name, metric_type:, metric_settings: {})
|
22
|
+
@store = store
|
23
|
+
@metric_name = metric_name
|
24
|
+
@metric_type = metric_type
|
25
|
+
@metric_settings = metric_settings
|
26
|
+
@aggregation_mode =
|
27
|
+
T.let(metric_settings.fetch(:aggregation, :sum), Symbol)
|
28
|
+
end
|
29
|
+
|
30
|
+
sig do
|
31
|
+
params(
|
32
|
+
val: T.any(Integer, Float),
|
33
|
+
labels: T::Hash[Symbol, T.untyped]
|
34
|
+
).void
|
35
|
+
end
|
36
|
+
def set(val:, labels: {})
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { returns(T::Hash[T::Array[Symbol], Float]) }
|
40
|
+
def all_values
|
41
|
+
@store
|
42
|
+
.values_for_metric(@metric_name)
|
43
|
+
.transform_values { aggregate(_1) }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
sig { params(values: T::Array[Float]).returns(Float) }
|
49
|
+
def aggregate(values)
|
50
|
+
case @aggregation_mode
|
51
|
+
when :min
|
52
|
+
values.min
|
53
|
+
when :max
|
54
|
+
values.max
|
55
|
+
when :sum
|
56
|
+
values.sum
|
57
|
+
else
|
58
|
+
raise "Invalid aggregation setting"
|
59
|
+
end.to_f
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
extend T::Sig
|
64
|
+
|
65
|
+
sig { returns(InternalStore) }
|
66
|
+
attr_reader :internal_store
|
67
|
+
|
68
|
+
sig { params(internal_store: InternalStore).void }
|
69
|
+
def initialize(internal_store = {})
|
70
|
+
@internal_store = internal_store
|
71
|
+
end
|
72
|
+
|
73
|
+
sig do
|
74
|
+
params(metric_name: Symbol).returns(
|
75
|
+
T::Hash[T::Array[Symbol], T::Array[Float]]
|
76
|
+
)
|
77
|
+
end
|
78
|
+
def values_for_metric(metric_name)
|
79
|
+
@internal_store
|
80
|
+
.values
|
81
|
+
.map { _1[metric_name] }
|
82
|
+
.compact
|
83
|
+
.each_with_object(Hash.new { |h, k| h[k] = [] }) do |entries, obj|
|
84
|
+
entries.each { |labels, value| obj[labels] << value }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
sig do
|
89
|
+
params(
|
90
|
+
metric_name: Symbol,
|
91
|
+
metric_type: Symbol,
|
92
|
+
metric_settings: T::Hash[Symbol, T.untyped]
|
93
|
+
).returns(MetricStore)
|
94
|
+
end
|
95
|
+
def for_metric(metric_name, metric_type:, metric_settings: {})
|
96
|
+
MetricStore.new(self, metric_name, metric_type:, metric_settings: {})
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Server
|
101
|
+
extend T::Sig
|
102
|
+
|
103
|
+
sig { returns(Async::IO::Endpoint) }
|
104
|
+
attr_reader :endpoint
|
105
|
+
|
106
|
+
sig { params(endpoint: Async::IO::UNIXEndpoint).void }
|
107
|
+
def initialize(endpoint)
|
108
|
+
@endpoint = endpoint
|
109
|
+
end
|
110
|
+
|
111
|
+
sig { params(metric_name: Symbol).void }
|
112
|
+
def all_values(metric_name)
|
113
|
+
raise NotImplementedError, "Should this even be implemented?"
|
114
|
+
end
|
115
|
+
|
116
|
+
sig { void }
|
117
|
+
def start
|
118
|
+
end
|
119
|
+
|
120
|
+
sig { void }
|
121
|
+
def stop
|
122
|
+
end
|
123
|
+
|
124
|
+
sig do
|
125
|
+
params(
|
126
|
+
internal_store: InternalStore,
|
127
|
+
name: String,
|
128
|
+
restart: T::Boolean
|
129
|
+
).void
|
130
|
+
end
|
131
|
+
def run(internal_store, name: self.class.name.to_s, restart: true)
|
132
|
+
wrapper = Wrapper.new
|
133
|
+
|
134
|
+
Console.logger.info(
|
135
|
+
self,
|
136
|
+
"Starting metrics collection on #{File.expand_path(@endpoint.path)}"
|
137
|
+
)
|
138
|
+
|
139
|
+
@endpoint.accept do |peer|
|
140
|
+
store = internal_store.store(peer, {})
|
141
|
+
|
142
|
+
unpacker = wrapper.unpacker(peer)
|
143
|
+
|
144
|
+
unpacker.each do |message|
|
145
|
+
case message
|
146
|
+
in [:store, data]
|
147
|
+
store.merge!(data)
|
148
|
+
else
|
149
|
+
Console
|
150
|
+
.logger
|
151
|
+
.warn(self) { "Unhandled mesage: #{message.inspect}" }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
ensure
|
155
|
+
internal_store.delete(peer)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mayu
|
4
|
+
module Metrics
|
5
|
+
class Exporter
|
6
|
+
class Server
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig do
|
10
|
+
params(
|
11
|
+
endpoint: Async::HTTP::Endpoint,
|
12
|
+
registry: Prometheus::Client::Registry
|
13
|
+
).returns(Async::HTTP::Server)
|
14
|
+
end
|
15
|
+
def self.setup(endpoint:, registry:)
|
16
|
+
Console.logger.info(
|
17
|
+
self,
|
18
|
+
"Starting metrics exporter on #{endpoint.to_url}"
|
19
|
+
)
|
20
|
+
|
21
|
+
Async::HTTP::Server.for(
|
22
|
+
endpoint,
|
23
|
+
protocol: Async::HTTP::Protocol::HTTP11
|
24
|
+
) do |request|
|
25
|
+
if request.path == "/favicon.ico"
|
26
|
+
next(
|
27
|
+
Protocol::HTTP::Response[
|
28
|
+
404,
|
29
|
+
{ "content-type": "text/plain" },
|
30
|
+
["Not found"]
|
31
|
+
]
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
body = Prometheus::Client::Formats::Text.marshal(registry)
|
36
|
+
|
37
|
+
Protocol::HTTP::Response[
|
38
|
+
200,
|
39
|
+
{ "content-type": "text/plain" },
|
40
|
+
[body]
|
41
|
+
]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mayu
|
4
|
+
module Metrics
|
5
|
+
module Reporter
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig do
|
9
|
+
type_parameters(:M)
|
10
|
+
.params(
|
11
|
+
collector_endpoint: Async::IO::UNIXEndpoint,
|
12
|
+
block:
|
13
|
+
T
|
14
|
+
.proc
|
15
|
+
.params(arg0: Prometheus::Client::Registry)
|
16
|
+
.returns(T.type_parameter(:M))
|
17
|
+
)
|
18
|
+
.returns(T.type_parameter(:M))
|
19
|
+
end
|
20
|
+
def self.run(collector_endpoint, &block)
|
21
|
+
data_store = DataStore.new
|
22
|
+
Prometheus::Client.config.data_store = data_store
|
23
|
+
metrics = yield(Prometheus::Client::Registry.new)
|
24
|
+
Client.connect_and_sync(collector_endpoint:, data_store:, interval: 1)
|
25
|
+
metrics
|
26
|
+
end
|
27
|
+
|
28
|
+
class Client
|
29
|
+
extend T::Sig
|
30
|
+
|
31
|
+
sig do
|
32
|
+
params(
|
33
|
+
collector_endpoint: Async::IO::UNIXEndpoint,
|
34
|
+
block: T.proc.params(arg0: Client).void
|
35
|
+
).void
|
36
|
+
end
|
37
|
+
def self.connect(collector_endpoint, &block)
|
38
|
+
Console.logger.info(
|
39
|
+
self,
|
40
|
+
"Connecting to #{File.expand_path(collector_endpoint.path)}"
|
41
|
+
)
|
42
|
+
|
43
|
+
collector_endpoint.connect { |peer| yield new(peer) }
|
44
|
+
end
|
45
|
+
|
46
|
+
sig do
|
47
|
+
params(
|
48
|
+
collector_endpoint: Async::IO::UNIXEndpoint,
|
49
|
+
data_store: DataStore,
|
50
|
+
interval: Integer,
|
51
|
+
task: Async::Task
|
52
|
+
).returns(Async::Task)
|
53
|
+
end
|
54
|
+
def self.connect_and_sync(
|
55
|
+
collector_endpoint:,
|
56
|
+
data_store:,
|
57
|
+
interval: 1,
|
58
|
+
task: Async::Task.current
|
59
|
+
)
|
60
|
+
task.async do
|
61
|
+
connect(collector_endpoint) do |client|
|
62
|
+
loop do
|
63
|
+
client.sync(data_store)
|
64
|
+
sleep(interval)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rescue Errno::EPIPE
|
68
|
+
Console.logger.error(self, "Broken pipe")
|
69
|
+
rescue Errno::ECONNREFUSED
|
70
|
+
Console.logger.error(self, "Connection refused")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(peer: Async::IO::Peer).void }
|
75
|
+
def initialize(peer)
|
76
|
+
wrapper = Wrapper.new
|
77
|
+
@packer = T.let(wrapper.packer(peer), MessagePack::Packer)
|
78
|
+
end
|
79
|
+
|
80
|
+
sig { params(data_store: DataStore, task: Async::Task).void }
|
81
|
+
def sync(data_store, task: Async::Task.current)
|
82
|
+
send(:store, data_store.store)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
sig { params(args: T.untyped).void }
|
88
|
+
def send(*args)
|
89
|
+
@packer.write(args)
|
90
|
+
@packer.flush
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class DataStore
|
95
|
+
class MetricStore
|
96
|
+
extend T::Sig
|
97
|
+
|
98
|
+
sig do
|
99
|
+
params(
|
100
|
+
store: ValueHash,
|
101
|
+
metric_name: Symbol,
|
102
|
+
metric_type: Symbol,
|
103
|
+
metric_settings: T::Hash[Symbol, T.untyped]
|
104
|
+
).void
|
105
|
+
end
|
106
|
+
def initialize(store, metric_name:, metric_type:, metric_settings:)
|
107
|
+
@store = store
|
108
|
+
@metric_name = metric_name
|
109
|
+
@semaphore = T.let(Async::Semaphore.new, Async::Semaphore)
|
110
|
+
end
|
111
|
+
|
112
|
+
sig do
|
113
|
+
type_parameters(:T)
|
114
|
+
.params(block: T.proc.returns(T.type_parameter(:T)))
|
115
|
+
.returns(T.type_parameter(:T))
|
116
|
+
end
|
117
|
+
def synchronize(&block)
|
118
|
+
@semaphore.async { yield }.wait
|
119
|
+
end
|
120
|
+
|
121
|
+
sig do
|
122
|
+
params(
|
123
|
+
val: T.any(Integer, Float),
|
124
|
+
labels: T::Hash[Symbol, T.untyped]
|
125
|
+
).void
|
126
|
+
end
|
127
|
+
def set(val:, labels: {})
|
128
|
+
@store.store(labels, val.to_f)
|
129
|
+
end
|
130
|
+
|
131
|
+
sig do
|
132
|
+
params(
|
133
|
+
by: T.any(Integer, Float),
|
134
|
+
labels: T::Hash[Symbol, T.untyped]
|
135
|
+
).void
|
136
|
+
end
|
137
|
+
def increment(by: 1, labels: {})
|
138
|
+
@store.store(labels, @store.fetch(labels, 0.0) + by.to_f)
|
139
|
+
end
|
140
|
+
|
141
|
+
sig { params(labels: T::Hash[Symbol, T.untyped]).void }
|
142
|
+
def get(labels:)
|
143
|
+
@store.fetch(labels)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
extend T::Sig
|
148
|
+
|
149
|
+
sig { returns(MetricHash) }
|
150
|
+
attr_reader :store
|
151
|
+
|
152
|
+
sig { void }
|
153
|
+
def initialize
|
154
|
+
@store = T.let(init_metric_hash, MetricHash)
|
155
|
+
end
|
156
|
+
|
157
|
+
sig do
|
158
|
+
params(
|
159
|
+
metric_name: Symbol,
|
160
|
+
metric_type: Symbol,
|
161
|
+
metric_settings: T::Hash[Symbol, T.untyped]
|
162
|
+
).returns(MetricStore)
|
163
|
+
end
|
164
|
+
def for_metric(metric_name, metric_type:, metric_settings: {})
|
165
|
+
MetricStore.new(
|
166
|
+
T.must(@store[metric_name]),
|
167
|
+
metric_name:,
|
168
|
+
metric_type:,
|
169
|
+
metric_settings:
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
sig { returns(MetricHash) }
|
176
|
+
def init_metric_hash
|
177
|
+
Hash.new { |hash, metric_name| hash[metric_name] = init_value_hash }
|
178
|
+
end
|
179
|
+
|
180
|
+
sig { returns(ValueHash) }
|
181
|
+
def init_value_hash
|
182
|
+
Hash.new { |hash, labels| hash[labels] = 0 }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/lib/mayu/metrics.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "async"
|
5
|
+
require "async/container"
|
6
|
+
require "async/semaphore"
|
7
|
+
require "async/http"
|
8
|
+
require "async/io/unix_endpoint"
|
9
|
+
require "async/io/shared_endpoint"
|
10
|
+
require "msgpack"
|
11
|
+
require "nanoid"
|
12
|
+
require "prometheus/client"
|
13
|
+
require "prometheus/client/formats/text"
|
14
|
+
|
15
|
+
require_relative "metrics/collector"
|
16
|
+
require_relative "metrics/exporter"
|
17
|
+
require_relative "metrics/reporter"
|
18
|
+
|
19
|
+
module Mayu
|
20
|
+
module Metrics
|
21
|
+
InternalStore = T.type_alias { T::Hash[Symbol, MetricHash] }
|
22
|
+
MetricHash = T.type_alias { T::Hash[Symbol, ValueHash] }
|
23
|
+
ValueHash = T.type_alias { T::Hash[LabelsHash, Float] }
|
24
|
+
LabelsHash = T.type_alias { T::Hash[Symbol, T.untyped] }
|
25
|
+
|
26
|
+
class Wrapper < MessagePack::Factory
|
27
|
+
extend T::Sig
|
28
|
+
|
29
|
+
sig { void }
|
30
|
+
def initialize
|
31
|
+
super()
|
32
|
+
|
33
|
+
self.register_type(0x01, Symbol)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
extend T::Sig
|
38
|
+
|
39
|
+
sig do
|
40
|
+
params(
|
41
|
+
container: Async::Container::Generic,
|
42
|
+
exporter_endpoint: Async::HTTP::Endpoint,
|
43
|
+
collector_endpoint: Async::IO::UNIXEndpoint,
|
44
|
+
block: T.proc.params(arg0: Prometheus::Client::Registry).void
|
45
|
+
).void
|
46
|
+
end
|
47
|
+
def self.start_collect_and_export(
|
48
|
+
container,
|
49
|
+
exporter_endpoint:,
|
50
|
+
collector_endpoint:,
|
51
|
+
&block
|
52
|
+
)
|
53
|
+
collector = Metrics::Collector::Server.new(collector_endpoint)
|
54
|
+
collector.start
|
55
|
+
|
56
|
+
container.spawn(
|
57
|
+
name: "Metrics collector/exporter",
|
58
|
+
restart: true
|
59
|
+
) do |instance|
|
60
|
+
Async do
|
61
|
+
internal_store = {}
|
62
|
+
|
63
|
+
Prometheus::Client.config.data_store =
|
64
|
+
Metrics::Collector::DataStore.new(internal_store)
|
65
|
+
|
66
|
+
registry = Prometheus::Client::Registry.new
|
67
|
+
|
68
|
+
yield registry
|
69
|
+
|
70
|
+
Metrics::Exporter::Server.setup(
|
71
|
+
endpoint: exporter_endpoint,
|
72
|
+
registry:
|
73
|
+
).run
|
74
|
+
|
75
|
+
collector.run(internal_store)
|
76
|
+
|
77
|
+
instance.ready!
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|