blob: 97c88ac6ea0864969ea13c1bf7cf42ce9c1ecff9 [file] [log] [blame]
# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Metrics for general consumption.
See infra/proto/metrics.proto for a description of the type of record that this
module will be creating.
"""
import collections
import logging
from chromite.lib import metrics_lib
def deserialize_metrics_log(output_events, prefix=None):
"""Read the current metrics events, adding to output_events.
This layer facilitates converting between the internal
chromite.utils.metrics representation of metric events and the
infra/proto/src/chromiumos/metrics.proto output type.
Args:
output_events: A chromiumos.MetricEvent protobuf message.
prefix: A string to prepend to all metric event names.
"""
counters = collections.defaultdict(int)
counter_times = {}
timers = {}
def make_name(name):
"""Prepend a closed-over prefix to the given name."""
if prefix:
return "%s.%s" % (prefix, name)
else:
return name
# Reduce over the input events to append output_events.
for input_event in metrics_lib.read_metrics_events():
if input_event.op == metrics_lib.OP_START_TIMER:
timers[input_event.arg] = (
input_event.name,
input_event.timestamp_epoch_millis,
)
elif input_event.op == metrics_lib.OP_STOP_TIMER:
# TODO(b/187788898): Drop the None fallback.
timer = timers.pop(input_event.arg, None)
if timer is None:
logging.error(
"%s: stop timer recorded, but missing start timer!?",
input_event.arg,
)
if timer:
assert input_event.name == timer[0]
output_event = output_events.add()
output_event.name = make_name(timer[0])
output_event.timestamp_milliseconds = (
input_event.timestamp_epoch_millis
)
output_event.duration_milliseconds = (
output_event.timestamp_milliseconds - timer[1]
)
elif input_event.op == metrics_lib.OP_NAMED_EVENT:
output_event = output_events.add()
output_event.name = make_name(input_event.name)
output_event.timestamp_milliseconds = (
input_event.timestamp_epoch_millis
)
elif input_event.op == metrics_lib.OP_GAUGE:
output_event = output_events.add()
output_event.name = make_name(input_event.name)
output_event.timestamp_milliseconds = (
input_event.timestamp_epoch_millis
)
output_event.gauge = input_event.arg
elif input_event.op == metrics_lib.OP_INCREMENT_COUNTER:
counters[input_event.name] += input_event.arg
counter_times[input_event.name] = max(
input_event.timestamp_epoch_millis,
counter_times.get(input_event.name, 0),
)
elif input_event.op == metrics_lib.OP_DECREMENT_COUNTER:
counters[input_event.name] -= input_event.arg
counter_times[input_event.name] = max(
input_event.timestamp_epoch_millis,
counter_times.get(input_event.name, 0),
)
else:
raise ValueError(
'unexpected op "%s" found in metric event: %s'
% (input_event.op, input_event)
)
for counter, value in counters.items():
output_event = output_events.add()
output_event.name = make_name(counter)
output_event.gauge = value
output_event.timestamp_milliseconds = counter_times[counter]
# Check for any unhandled timers.
# TODO(b/187788898): Turn this back into an assert.
if timers:
logging.error("excess timer metric data left over: %s", timers)