src/coordinator/StatsCodec.bend

mylsm-lsm-store@0.6.0.1 · 222 lines · 23 definitions

1import Base
2import mylsm-lsm-store@0.6.0.1/src/StorageBytes.bend as StorageBytes
3import mylsm-lsm-store@0.6.0.1/src/coordinator/Protocol.bend as Protocol
4import bend-kit-bytes@0.3.2.0/bytes.bend as Bytes
5
6# Carries visible data and live owner-retention counters.
7type Stats is Data:
8  ByteStats{entries: Nat, payload_bytes: Nat, snapshots: Nat, retained_bytes: Nat, cursors: Nat}
9
10# Distinguishes a decoded statistics payload from malformed bytes.
11type Decision is Data:
12  Parsed{stats: Stats}
13  Malformed{}
14
15# Tracks decimal fields while parsing the bounded owner counter payload.
16type State is Data:
17  EntryDigits{entries: Nat, seen: Bool}
18  PayloadDigits{entries: Nat, payload_bytes: Nat, seen: Bool}
19  SnapshotDigits{entries: Nat, payload_bytes: Nat, snapshots: Nat, seen: Bool}
20  RetainedDigits{entries: Nat, payload_bytes: Nat, snapshots: Nat,
21    retained_bytes: Nat, seen: Bool}
22  CursorDigits{entries: Nat, payload_bytes: Nat, snapshots: Nat,
23    retained_bytes: Nat, cursors: Nat, seen: Bool}
24  Invalid{}
25
26def decimal.length(value: Nat) -> Nat:
27  String.length(Nat.show(value))
28
29def payload.length(
30  entries: Nat,
31  payload_bytes: Nat,
32  snapshots: Nat,
33  retained_bytes: Nat,
34  cursors: Nat
35) -> Nat:
36  Nat.add(4n, Nat.add(decimal.length(entries),
37    Nat.add(decimal.length(payload_bytes),
38      Nat.add(decimal.length(snapshots),
39        Nat.add(decimal.length(retained_bytes), decimal.length(cursors))))))
40
41def digit.value(scalar: U32) -> Nat:
42  Nat.sub(U32.to_nat(scalar), 48n)
43
44def digit.valid.checked(copies: U32 & U32) -> Bool:
45  match copies:
46    case (lower, upper): U32.is_le(48, lower) && U32.is_le(upper, 57)
47
48def digit.valid.copies(copies: U32 & U32 & U32) -> Bool:
49  match copies:
50    case (_, (lower, upper)): digit.valid.checked((lower, upper))
51
52def digit.valid(scalar: U32) -> Bool:
53  digit.valid.copies(Protocol.u32.duplicate.triple(scalar))
54
55def separator.character(parser: State, scalar: U32) -> State:
56  match parser:
57    case EntryDigits{entries, _}:
58      match scalar:
59        case 58: PayloadDigits{entries, 0n, False{}}
60        case _: Invalid{}
61    case PayloadDigits{entries, payload_bytes, _}:
62      match scalar:
63        case 58: SnapshotDigits{entries, payload_bytes, 0n, False{}}
64        case _: Invalid{}
65    case SnapshotDigits{entries, payload_bytes, snapshots, _}:
66      match scalar:
67        case 58: RetainedDigits{entries, payload_bytes, snapshots, 0n, False{}}
68        case _: Invalid{}
69    case RetainedDigits{entries, payload_bytes, snapshots, retained_bytes, _}:
70      match scalar:
71        case 58:
72          CursorDigits{entries, payload_bytes, snapshots, retained_bytes, 0n,
73            False{}}
74        case _: Invalid{}
75    case _: Invalid{}
76
77# Advances to a new stats field only after the current field has digits.
78def separator(parser: State, seen: Bool, scalar: U32) -> State:
79  match seen:
80    case False{}: Invalid{}
81    case True{}: separator.character(parser, scalar)
82
83def entry.step(
84  entries: Nat,
85  scalar: U32,
86  is_digit: Bool,
87  seen: Bool
88) -> State:
89  match is_digit:
90    case True{}:
91      EntryDigits{Nat.add(Nat.mul(entries, 10n), digit.value(scalar)), True{}}
92    case False{}:
93      match seen:
94        case False{}: Invalid{}
95        case True{}: separator(EntryDigits{entries, True{}}, True{}, scalar)
96
97def payload.step(
98  entries: Nat,
99  payload_bytes: Nat,
100  scalar: U32,
101  is_digit: Bool,
102  seen: Bool
103) -> State:
104  match is_digit:
105    case True{}:
106      PayloadDigits{entries,
107        Nat.add(Nat.mul(payload_bytes, 10n), digit.value(scalar)), True{}}
108    case False{}:
109      match seen:
110        case False{}: Invalid{}
111        case True{}:
112          separator(PayloadDigits{entries, payload_bytes, True{}}, True{},
113            scalar)
114
115def snapshot.step(
116  entries: Nat,
117  payload_bytes: Nat,
118  snapshots: Nat,
119  scalar: U32,
120  is_digit: Bool,
121  seen: Bool
122) -> State:
123  match is_digit:
124    case True{}:
125      SnapshotDigits{entries, payload_bytes,
126        Nat.add(Nat.mul(snapshots, 10n), digit.value(scalar)), True{}}
127    case False{}:
128      match seen:
129        case False{}: Invalid{}
130        case True{}:
131          separator(SnapshotDigits{entries, payload_bytes, snapshots, True{}},
132            True{}, scalar)
133
134def retained.step(
135  entries: Nat,
136  payload_bytes: Nat,
137  snapshots: Nat,
138  retained_bytes: Nat,
139  scalar: U32,
140  is_digit: Bool,
141  seen: Bool
142) -> State:
143  match is_digit:
144    case True{}:
145      RetainedDigits{entries, payload_bytes, snapshots,
146        Nat.add(Nat.mul(retained_bytes, 10n), digit.value(scalar)), True{}}
147    case False{}:
148      match seen:
149        case False{}: Invalid{}
150        case True{}:
151          separator(RetainedDigits{entries, payload_bytes, snapshots,
152            retained_bytes, True{}}, True{}, scalar)
153
154def cursor.step(
155  entries: Nat,
156  payload_bytes: Nat,
157  snapshots: Nat,
158  retained_bytes: Nat,
159  cursors: Nat,
160  scalar: U32,
161  is_digit: Bool
162) -> State:
163  match is_digit:
164    case True{}:
165      CursorDigits{entries, payload_bytes, snapshots, retained_bytes,
166        Nat.add(Nat.mul(cursors, 10n), digit.value(scalar)), True{}}
167    case False{}: Invalid{}
168
169def parse.step(state: State, scalar: U32, is_digit: Bool) -> State:
170  match state:
171    case EntryDigits{entries, seen}:
172      entry.step(entries, scalar, is_digit, seen)
173    case PayloadDigits{entries, payload_bytes, seen}:
174      payload.step(entries, payload_bytes, scalar, is_digit, seen)
175    case SnapshotDigits{entries, payload_bytes, snapshots, seen}:
176      snapshot.step(entries, payload_bytes, snapshots, scalar, is_digit, seen)
177    case RetainedDigits{entries, payload_bytes, snapshots, retained_bytes, seen}:
178      retained.step(entries, payload_bytes, snapshots, retained_bytes,
179        scalar, is_digit, seen)
180    case CursorDigits{entries, payload_bytes, snapshots, retained_bytes,
181        cursors, seen}:
182      cursor.step(entries, payload_bytes, snapshots, retained_bytes, cursors,
183        scalar, is_digit)
184    case Invalid{}: Invalid{}
185
186def parse.step.copies(
187  state: State,
188  copies: U32 & U32 & U32
189) -> State:
190  match copies:
191    case (scalar, (checked_scalar, _)):
192      parse.step(state, scalar, digit.valid(checked_scalar))
193
194def parse.go(text: String, state: State) -> State:
195  match text:
196    case SNil{}: state
197    case SCon{Chr{scalar}, rest}:
198      parse.go(rest, parse.step.copies(state,
199        Protocol.u32.duplicate.triple(scalar)))
200
201# Completes parsing only when the payload byte field contains digits.
202def finish(state: State) -> Decision:
203  match state:
204    case CursorDigits{entries, payload_bytes, snapshots, retained_bytes,
205        cursors, True{}}:
206      Parsed{ByteStats{entries, payload_bytes, snapshots, retained_bytes,
207        cursors}}
208    case _: Malformed{}
209
210def parse.text(text: String) -> Decision:
211  finish(parse.go(text, EntryDigits{0n, False{}}))
212
213def parse.result(
214  result: Result<&1, &1, U32 & String, String>
215) -> Decision:
216  match result:
217    case Done{text}: parse.text(text)
218    case Fail{_}: Malformed{}
219
220# Parses a strict UTF-8 decimal pair from the owner response bytes.
221def parse(input: Bytes.Bytes) -> Decision:
222  parse.result(StorageBytes.to_string_strict(input))