Skip to content

Commit 14548da

Browse files
committed
add serde module
1 parent 6f12a8f commit 14548da

12 files changed

Lines changed: 1601 additions & 9 deletions

File tree

Cargo.lock

Lines changed: 120 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

open-tsdb/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ edition.workspace = true
66
[dependencies]
77
slatedb.workspace = true
88
opendata-common.workspace = true
9+
roaring = "0.7"
10+
tsz = "0.1"
11+
12+
[dev-dependencies]
13+
rstest = "0.19"

open-tsdb/README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ Examples of this convention:
2323

2424
All records use a standard, 2-byte prefix: a single `u8` for the record version and
2525
another `u8` for the record tag. The record tag is encoded as two 4-bit fields. The
26-
high 4 bits are the record type. The lower 4 bits depend on the scope of scope of
27-
the reocrd. Globally scoped records set the lower 4 bits to 0x00 for future use and
28-
bucket scoped records set the lower 4 bits to encdoe the `TimeBucketSize` (e.g. 1
29-
for a single Hour) allowing different time granularities to coexist.
26+
high 4 bits are the record type. The lower 4 bits depend on the scope of the record.
27+
Globally scoped records set the lower 4 bits to 0x00 for future use and bucket scoped
28+
records set the lower 4 bits to encode the `TimeBucketSize` allowing different time
29+
granularities to coexist.
30+
31+
The `TimeBucketSize` is encoded exponentially: a value of `n` represents `2^(n-1)` hours.
32+
For example, `1` = 1 hour, `2` = 2 hours, `3` = 4 hours, `4` = 8 hours, etc. This allows
33+
the system to efficiently represent a wide range of time bucket sizes (1 hour to 16,384 hours)
34+
using only 4 bits.
3035

3136
```
3237
record_tag byte layout (global-scoped):
@@ -215,9 +220,7 @@ dictionaries.
215220
├──────────────────────────────────────────────────────────────────────────┤
216221
│ metric_unit: OptionalNonEmptyUtf8 │
217222
│ metric_meta: MetricMeta │
218-
│ resource_count: u16 │
219-
│ scope_count: u16 │
220-
│ point_count: u16 │
223+
│ attr_count: u16 │
221224
│ attrs: Array<AttributeBinding> │
222225
│ │
223226
│ MetricMeta │
@@ -240,8 +243,8 @@ dictionaries.
240243
- `metric_meta` (`MetricMeta`): Encodes the series' metric type and auxiliary flags.
241244
- `metric_type` (u8): Enumeration matching `TimeSeriesSpec::metric_type``1=Gauge`, `2=Sum`, `3=Histogram`, `4=ExponentialHistogram`, `5=Summary`.
242245
- `flags` (u8): Bit-packed metadata; bits `0-1` store temporality (`0=Unspecified`, `1=Cumulative`, `2=Delta`), bit `2` is the `monotonic` flag (only meaningful when `metric_type=2`), remaining bits are reserved and must be zero.
243-
- `resource_count` / `scope_count` / `point_count` (u16): Cardinalities of each attribute group.
244-
- `resource_attrs`, `scope_attrs`, `point_attrs` (`Array<AttributeBinding>`): Attribute bindings grouped by their source context. Each is encoded as `resource_count` / `scope_count` / `point_count` followed by that many `AttributeBinding` entries.
246+
- `attr_count` (u16): Total number of attribute bindings.
247+
- `attrs` (`Array<AttributeBinding>`): All attribute bindings encoded as `attr_count` followed by that many `AttributeBinding` entries.
245248
- `attr` (`Utf8`): Attribute name.
246249
- `value` (`Utf8`): Attribute value.
247250

open-tsdb/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#![allow(dead_code)]
2+
mod serde;
3+
14
fn main() {
25
println!("open-tsdb: timeseries store");
36
}

open-tsdb/src/serde/bucket_list.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// BucketList value structure
2+
3+
use super::common::*;
4+
5+
/// BucketList value: SingleArray<(bucket_size: u8, time_bucket: u32)>
6+
#[derive(Debug, Clone, PartialEq, Eq)]
7+
pub struct BucketListValue {
8+
pub buckets: Vec<(TimeBucketSize, TimeBucket)>,
9+
}
10+
11+
impl BucketListValue {
12+
pub fn encode(&self) -> Vec<u8> {
13+
let mut buf = Vec::new();
14+
encode_single_array(&self.buckets, &mut buf);
15+
buf
16+
}
17+
18+
pub fn decode(buf: &[u8], count: usize) -> Result<Self, EncodingError> {
19+
let mut slice = buf;
20+
let buckets = decode_single_array(&mut slice, count)?;
21+
Ok(BucketListValue { buckets })
22+
}
23+
}
24+
25+
impl Encode for (TimeBucketSize, TimeBucket) {
26+
fn encode(&self, buf: &mut Vec<u8>) {
27+
buf.push(self.0);
28+
buf.extend_from_slice(&self.1.to_le_bytes());
29+
}
30+
}
31+
32+
impl Decode for (TimeBucketSize, TimeBucket) {
33+
fn decode(buf: &mut &[u8]) -> Result<Self, EncodingError> {
34+
if buf.len() < 1 + 4 {
35+
return Err(EncodingError {
36+
message: "Buffer too short for (TimeBucketSize, TimeBucket)".to_string(),
37+
});
38+
}
39+
let bucket_size = buf[0];
40+
*buf = &buf[1..];
41+
let time_bucket = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
42+
*buf = &buf[4..];
43+
Ok((bucket_size, time_bucket))
44+
}
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use super::*;
50+
51+
#[test]
52+
fn should_encode_and_decode_bucket_list_value() {
53+
// given
54+
let value = BucketListValue {
55+
buckets: vec![(1, 100), (2, 200), (3, 300)],
56+
};
57+
58+
// when
59+
let encoded = value.encode();
60+
let decoded = BucketListValue::decode(&encoded, 3).unwrap();
61+
62+
// then
63+
assert_eq!(decoded, value);
64+
}
65+
66+
#[test]
67+
fn should_encode_and_decode_empty_bucket_list_value() {
68+
// given
69+
let value = BucketListValue { buckets: vec![] };
70+
71+
// when
72+
let encoded = value.encode();
73+
let decoded = BucketListValue::decode(&encoded, 0).unwrap();
74+
75+
// then
76+
assert_eq!(decoded, value);
77+
}
78+
}

0 commit comments

Comments
 (0)