///|
/// Resolution policy for duplicate series while merging snapshots.
pub(all) enum ConflictPolicy {
Reject
KeepFirst
KeepLast
} derive(Eq)
///|
/// Failure returned by document merging.
pub(all) struct MergeError {
family : String
sample : String?
message : String
} derive(Eq)
///|
/// Render a compact merge failure.
pub fn MergeError::to_string(self : MergeError) -> String {
let sample = match self.sample {
Some(name) => ", sample \{name}"
None => ""
}
"family \{self.family}\{sample}: \{self.message}"
}
///|
fn merge_error(
family : String,
message : String,
sample? : String,
) -> Result[MetricFamily, MergeError] {
Err({ family, sample, message })
}
///|
fn merge_help(
family : String,
left : String?,
right : String?,
) -> Result[String?, MergeError] {
match (left, right) {
(None, value) | (value, None) => Ok(value)
(Some(left), Some(right)) =>
if left == right {
Ok(Some(left))
} else {
Err({ family, sample: None, message: "conflicting HELP text" })
}
}
}
///|
fn merge_unit(
family : String,
left : String?,
right : String?,
) -> Result[String?, MergeError] {
match (left, right) {
(None, value) | (value, None) => Ok(value)
(Some(left), Some(right)) =>
if left == right {
Ok(Some(left))
} else {
Err({
family,
sample: None,
message: "conflicting UNIT values '\{left}' and '\{right}'",
})
}
}
}
///|
fn merge_metric_type(
family : String,
left : MetricType,
right : MetricType,
) -> Result[MetricType, MergeError] {
if left == right {
Ok(left)
} else if left == Unknown {
Ok(right)
} else if right == Unknown {
Ok(left)
} else {
Err({
family,
sample: None,
message: "conflicting TYPE values '\{left.to_keyword()}' and '\{right.to_keyword()}'",
})
}
}
///|
fn merge_family(
left : MetricFamily,
right : MetricFamily,
policy : ConflictPolicy,
) -> Result[MetricFamily, MergeError] {
let metric_type = match
merge_metric_type(left.name, left.metric_type, right.metric_type) {
Ok(value) => value
Err(error) => return Err(error)
}
let help = match merge_help(left.name, left.help, right.help) {
Ok(value) => value
Err(error) => return Err(error)
}
let unit = match merge_unit(left.name, left.unit, right.unit) {
Ok(value) => value
Err(error) => return Err(error)
}
let samples = left.samples.copy()
for sample in right.samples {
let index = find_series_index(samples, sample.name, sample.labels)
if index < 0 {
samples.push(sample)
} else {
match policy {
KeepFirst => ()
KeepLast => samples[index] = sample
Reject =>
return merge_error(
left.name,
"duplicate series exists in more than one document",
sample=sample.name,
)
}
}
}
Ok({ name: left.name, help, metric_type, unit, samples })
}
///|
fn copy_document(document : Document) -> Document {
let families : Array[MetricFamily] = []
for family in document.families {
families.push({ ..family, samples: family.samples.copy() })
}
{ families, has_eof: document.has_eof }
}
///|
/// Merge two parsed snapshots.
///
/// Metadata for a shared family must be compatible. A concrete TYPE wins over
/// `unknown`; conflicting concrete types, HELP strings, or UNIT values fail.
/// `policy` only resolves duplicate series with the same name and label set.
pub fn merge(
left : Document,
right : Document,
policy? : ConflictPolicy = Reject,
) -> Result[Document, MergeError] {
let result = copy_document(left)
for incoming in right.families {
let index = find_family_index(result.families, incoming.name)
if index < 0 {
result.families.push({ ..incoming, samples: incoming.samples.copy() })
} else {
result.families[index] = match
merge_family(result.families[index], incoming, policy) {
Ok(family) => family
Err(error) => return Err(error)
}
}
}
Ok({ ..result, has_eof: left.has_eof && right.has_eof })
}
///|
/// Merge any number of documents from left to right.
pub fn merge_all(
documents : Array[Document],
policy? : ConflictPolicy = Reject,
) -> Result[Document, MergeError] {
if documents.is_empty() {
return Ok(Document::new())
}
let mut result = copy_document(documents[0])
for index in 1.. document
Err(error) => return Err(error)
}
}
Ok(result)
}
///|
fn contains_name(names : Array[String], name : String) -> Bool {
for candidate in names {
if candidate == name {
return true
}
}
false
}
///|
/// Copy only named metric families into a new document.
pub fn Document::select_families(
self : Document,
names : Array[String],
) -> Document {
let families : Array[MetricFamily] = []
for family in self.families {
if contains_name(names, family.name) {
families.push({ ..family, samples: family.samples.copy() })
}
}
{ families, has_eof: self.has_eof }
}
///|
fn sample_has_labels(sample : Sample, required : Array[Label]) -> Bool {
for expected in required {
let mut found = false
for actual in sample.labels {
if actual == expected {
found = true
break
}
}
if !found {
return false
}
}
true
}
///|
/// Copy a document while retaining only samples that contain all requested
/// label pairs. Empty families are omitted.
pub fn Document::select_labels(
self : Document,
required : Array[Label],
) -> Document {
let families : Array[MetricFamily] = []
for family in self.families {
let samples : Array[Sample] = []
for sample in family.samples {
if sample_has_labels(sample, required) {
samples.push(sample)
}
}
if !samples.is_empty() {
families.push({ ..family, samples, })
}
}
{ families, has_eof: self.has_eof }
}