Repository: firecracker-microvm/firecracker
File: tests/host_tools/fcmetrics.py (Python)

Code before:
        if key.endswith("_us"):
            return "Microseconds"
    return "Count"


def flush_fc_metrics_to_cw(fc_metrics, metrics):
    """
    Flush Firecracker metrics to CloudWatch. Use an existing metrics logger with existing dimensions so that it is
    easier to correlate the metrics with the test calling it. Add a prefix "fc_metrics." to differentiate these metrics,
    this also helps to avoid using this metrics in A/B tests.
    NOTE:
        There are metrics with keywords "fail", "err", "num_faults", "panic" in their name and represent some kind of
        failure in Firecracker.  We assert that all these are zero, to catch potentially silent failure modes. This
        means the FcMonitor cannot be used in negative tests that might cause such metrics to be emitted.
    """

    # Pre-order tree traversal to convert a tree into its list of paths with dot separate segments
    def flatten_dict(node):
        if not isinstance(node, dict):
            return {"": node}

        return {
            "." + child_key + full_key: leaf
            for child_key, child_node in node.items()
            for full_key, leaf in flatten_dict(child_node).items()
        }

    flattened_metrics = flatten_dict(fc_metrics)

    for key, value in flattened_metrics.items():
        if ".utc_timestamp_ms." in key:
            continue
        metrics.put_metric(f"fc_metrics{key}", value, get_emf_unit_for_fc_metrics(key))

    assert not {
        key: value
        for key, value in flattened_metrics.items()
        if "err" in key or "fail" in key or "panic" in key
        if value
    }

    metrics.flush()


class FCMetricsMonitor(Thread):
    """
    read Firecracker metrics from the microvm every `timer` secs and
    uploads the metrics to CW. `timer` is in seconds and is default to
    60sec to match default time Firecrackers takes to dump metrics.
    We do this as a daemon thread every `timer` sec, instead of
    collecting all metrics together in the end, to retain timestamp

Review comment (suggestion):
I think this is more readable:
```python
ret = {}
for child_key, child_node in node.items():
  for full_key, leaf in flatten_dict(child_node).items():
    key = "." + child_key + full_key
    ret[key] = leaf
```
Also not sure if this is a `pre-order` as we never visit the root nodes, but I guess this does not really matter.

Code after:
        if key.endswith("_us"):
            return "Microseconds"
    return "Count"


def flush_fc_metrics_to_cw(fc_metrics, metrics):
    """
    Flush Firecracker metrics to CloudWatch. Use an existing metrics logger with existing dimensions so that it is
    easier to correlate the metrics with the test calling it. Add a prefix "fc_metrics." to differentiate these metrics,
    this also helps to avoid using this metrics in A/B tests.
    NOTE:
        There are metrics with keywords "fail", "err", "num_faults", "panic" in their name and represent some kind of
        failure in Firecracker.  We assert that all these are zero, to catch potentially silent failure modes. This
        means the FcMonitor cannot be used in negative tests that might cause such metrics to be emitted.
    """

    # Pre-order tree traversal to convert a tree into its list of paths with dot separate segments
    def flatten_dict(node, prefix: str):
        if not isinstance(node, dict):
            return {prefix: node}

        result = {}
        for child_metric_name, child_metrics in node.items():
            result.update(flatten_dict(child_metrics, f"{prefix}.{child_metric_name}"))
        return result

    flattened_metrics = flatten_dict(fc_metrics, "fc_metrics")

    for key, value in flattened_metrics.items():
        if ".utc_timestamp_ms." in key:
            continue
        metrics.put_metric(key, value, get_emf_unit_for_fc_metrics(key))

    assert not {
        key: value
        for key, value in flattened_metrics.items()
        if "err" in key or "fail" in key or "panic" in key or "num_faults" in key
        if value
    }

    metrics.flush()


class FCMetricsMonitor(Thread):
    """
    read Firecracker metrics from the microvm every `timer` secs and
    uploads the metrics to CW. `timer` is in seconds and is default to
    60sec to match default time Firecrackers takes to dump metrics.
    We do this as a daemon thread every `timer` sec, instead of
    collecting all metrics together in the end, to retain timestamp
    of the metrics.