Init commit for standalone enodebd

Change-Id: I88eeef5135dd7ba8551ddd9fb6a0695f5325337b
diff --git a/common/metrics_export.py b/common/metrics_export.py
new file mode 100644
index 0000000..964e3fb
--- /dev/null
+++ b/common/metrics_export.py
@@ -0,0 +1,212 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import logging
+import time
+
+import metrics_pb2
+from orc8r.protos import metricsd_pb2
+from prometheus_client import REGISTRY
+
+
+def get_metrics(registry=REGISTRY, verbose=False):
+    """
+    Collects timeseries samples from prometheus metric collector registry
+    adds a common timestamp, and encodes them to protobuf
+
+    Arguments:
+        regsitry: a prometheus CollectorRegistry instance
+        verbose: whether to optimize for bandwidth and ignore metric name/help
+
+    Returns:
+        a prometheus MetricFamily protobuf stream
+    """
+    timestamp_ms = int(time.time() * 1000)
+    for metric_family in registry.collect():
+        if metric_family.type in ('counter', 'gauge'):
+            family_proto = encode_counter_gauge(metric_family, timestamp_ms)
+        elif metric_family.type == 'summary':
+            family_proto = encode_summary(metric_family, timestamp_ms)
+        elif metric_family.type == 'histogram':
+            family_proto = encode_histogram(metric_family, timestamp_ms)
+
+        if verbose:
+            family_proto.help = metric_family.documentation
+            family_proto.name = metric_family.name
+        else:
+            try:
+                family_proto.name = \
+                    str(metricsd_pb2.MetricName.Value(metric_family.name))
+            except ValueError as e:
+                logging.debug(e)  # If enum is not defined
+                family_proto.name = metric_family.name
+        yield family_proto
+
+
+def encode_counter_gauge(family, timestamp_ms):
+    """
+    Takes a Counter/Gauge family which is a collection of timeseries
+    samples that share a name (uniquely identified by labels) and yields
+    equivalent protobufs.
+
+    Each timeseries corresponds to a single sample tuple of the format:
+        (NAME, LABELS, VALUE)
+
+    Arguments:
+        family: a prometheus gauge metric family
+        timestamp_ms: the timestamp to attach to the samples
+    Raises:
+        ValueError if metric name is not defined in MetricNames protobuf
+    Returns:
+        A Counter or Gauge prometheus MetricFamily protobuf
+    """
+    family_proto = metrics_pb2.MetricFamily()
+    family_proto.type = \
+        metrics_pb2.MetricType.Value(family.type.upper())
+    for sample in family.samples:
+        metric_proto = metrics_pb2.Metric()
+        if family_proto.type == metrics_pb2.COUNTER:
+            metric_proto.counter.value = sample[2]
+        elif family_proto.type == metrics_pb2.GAUGE:
+            metric_proto.gauge.value = sample[2]
+        # Add meta-data to the timeseries
+        metric_proto.timestamp_ms = timestamp_ms
+        metric_proto.label.extend(_convert_labels_to_enums(sample[1].items()))
+        # Append metric sample to family
+        family_proto.metric.extend([metric_proto])
+    return family_proto
+
+
+def encode_summary(family, timestamp_ms):
+    """
+    Takes a Summary Metric family which is a collection of timeseries
+    samples that share a name (uniquely identified by labels) and yields
+    equivalent protobufs.
+
+    Each summary timeseries consists of sample tuples for the count, sum,
+    and quantiles in the format (NAME,LABELS,VALUE). The NAME is suffixed
+    with either _count, _sum to indicate count and sum respectively.
+    Quantile samples will be of the same NAME with quantile label.
+
+    Arguments:
+        family: a prometheus summary metric family
+        timestamp_ms: the timestamp to attach to the samples
+    Raises:
+        ValueError if metric name is not defined in MetricNames protobuf
+    Returns:
+        a Summary prometheus MetricFamily protobuf
+    """
+    family_proto = metrics_pb2.MetricFamily()
+    family_proto.type = metrics_pb2.SUMMARY
+    metric_protos = {}
+    # Build a map of each of the summary timeseries from the samples
+    for sample in family.samples:
+        quantile = sample[1].pop('quantile', None)  # Remove from label set
+        # Each time series identified by label set excluding the quantile
+        metric_proto = \
+            metric_protos.setdefault(
+                frozenset(sample[1].items()),
+                metrics_pb2.Metric(),
+            )
+        if sample[0].endswith('_count'):
+            metric_proto.summary.sample_count = int(sample[2])
+        elif sample[0].endswith('_sum'):
+            metric_proto.summary.sample_sum = sample[2]
+        elif quantile:
+            quantile = metric_proto.summary.quantile.add()
+            quantile.value = sample[2]
+            quantile.quantile = _goStringToFloat(quantile)
+    # Go back and add meta-data to the timeseries
+    for labels, metric_proto in metric_protos.items():
+        metric_proto.timestamp_ms = timestamp_ms
+        metric_proto.label.extend(_convert_labels_to_enums(labels))
+        # Add it to the family
+        family_proto.metric.extend([metric_proto])
+    return family_proto
+
+
+def encode_histogram(family, timestamp_ms):
+    """
+    Takes a Histogram Metric family which is a collection of timeseries
+    samples that share a name (uniquely identified by labels) and yields
+    equivalent protobufs.
+
+    Each summary timeseries consists of sample tuples for the count, sum,
+    and quantiles in the format (NAME,LABELS,VALUE). The NAME is suffixed
+    with either _count, _sum, _buckets to indicate count, sum and buckets
+    respectively. Bucket samples will also contain a le to indicate its
+    upper bound.
+
+    Arguments:
+        family: a prometheus histogram metric family
+        timestamp_ms: the timestamp to attach to the samples
+    Raises:
+        ValueError if metric name is not defined in MetricNames protobuf
+    Returns:
+        a Histogram prometheus MetricFamily protobuf
+    """
+    family_proto = metrics_pb2.MetricFamily()
+    family_proto.type = metrics_pb2.HISTOGRAM
+    metric_protos = {}
+    for sample in family.samples:
+        upper_bound = sample[1].pop('le', None)  # Remove from label set
+        metric_proto = \
+            metric_protos.setdefault(
+                frozenset(sample[1].items()),
+                metrics_pb2.Metric(),
+            )
+        if sample[0].endswith('_count'):
+            metric_proto.histogram.sample_count = int(sample[2])
+        elif sample[0].endswith('_sum'):
+            metric_proto.histogram.sample_sum = sample[2]
+        elif sample[0].endswith('_bucket'):
+            quantile = metric_proto.histogram.bucket.add()
+            quantile.cumulative_count = int(sample[2])
+            quantile.upper_bound = _goStringToFloat(upper_bound)
+    # Go back and add meta-data to the timeseries
+    for labels, metric_proto in metric_protos.items():
+        metric_proto.timestamp_ms = timestamp_ms
+        metric_proto.label.extend(_convert_labels_to_enums(labels))
+        # Add it to the family
+        family_proto.metric.extend([metric_proto])
+    return family_proto
+
+
+def _goStringToFloat(s):
+    if s == '+Inf':
+        return float("inf")
+    elif s == '-Inf':
+        return float("-inf")
+    elif s == 'NaN':
+        return float('nan')
+    else:
+        return float(s)
+
+
+def _convert_labels_to_enums(labels):
+    """
+    Try to convert both the label names and label values to enum values.
+    Defaults to the given name and value if it fails to convert.
+    Arguments:
+        labels: an array of label pairs that may contain enum names
+    Returns:
+        an array of label pairs with enum names converted to enum values
+    """
+    new_labels = []
+    for name, value in labels:
+        try:
+            name = str(metricsd_pb2.MetricLabelName.Value(name))
+        except ValueError as e:
+            logging.debug(e)
+        new_labels.append(metrics_pb2.LabelPair(name=name, value=value))
+    return new_labels