pw_chrono: Add timestamp analyzer
Added timestamp analyzer change snapshot to support printing
timestamp.
Change-Id: I540c845718fb37e9ecb048cbcc70d3d114d57329
Requires: pigweed-internal:32003
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/95841
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
Pigweed-Auto-Submit: Tina Mashhour <tmashhour@google.com>
diff --git a/pw_chrono/BUILD.bazel b/pw_chrono/BUILD.bazel
index f8a41f4..1952c57 100644
--- a/pw_chrono/BUILD.bazel
+++ b/pw_chrono/BUILD.bazel
@@ -19,6 +19,7 @@
"pw_cc_test",
)
load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
+load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
package(default_visibility = ["//visibility:public"])
@@ -102,6 +103,13 @@
srcs = [
"chrono.proto",
],
+ import_prefix = "pw_chrono_protos",
+ strip_import_prefix = "//pw_chrono",
+)
+
+py_proto_library(
+ name = "chrono_proto_pb2",
+ srcs = ["chrono.proto"],
)
pw_proto_library(
diff --git a/pw_chrono/BUILD.gn b/pw_chrono/BUILD.gn
index 6bfcd15..8c8004f 100644
--- a/pw_chrono/BUILD.gn
+++ b/pw_chrono/BUILD.gn
@@ -103,7 +103,7 @@
pw_proto_library("protos") {
sources = [ "chrono.proto" ]
- prefix = "pw_chrono"
+ prefix = "pw_chrono_protos"
}
pw_doc_group("docs") {
diff --git a/pw_chrono/CMakeLists.txt b/pw_chrono/CMakeLists.txt
index b98c812..4f16a4e 100644
--- a/pw_chrono/CMakeLists.txt
+++ b/pw_chrono/CMakeLists.txt
@@ -60,7 +60,7 @@
SOURCES
chrono.proto
PREFIX
- pw_chrono
+ pw_chrono_protos
)
# TODO(ewout): Renable this once we've resolved the backend variable definition
diff --git a/pw_chrono/docs.rst b/pw_chrono/docs.rst
index 468b922..dea99da 100644
--- a/pw_chrono/docs.rst
+++ b/pw_chrono/docs.rst
@@ -527,6 +527,8 @@
in device snapshots. Simplified capture utilies and host-side tooling to
interpret this data are not yet provided by ``pw_chrono``.
+There is tooling that take these proto and make them more human readable.
+
---------------
Software Timers
---------------
diff --git a/pw_chrono/py/BUILD.bazel b/pw_chrono/py/BUILD.bazel
new file mode 100644
index 0000000..4242501
--- /dev/null
+++ b/pw_chrono/py/BUILD.bazel
@@ -0,0 +1,37 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+package(default_visibility = ["//visibility:public"])
+
+py_library(
+ name = "pw_chrono",
+ srcs = [
+ "pw_chrono/__init__.py",
+ "pw_chrono/timestamp_analyzer.py",
+ ],
+ deps = [
+ "//pw_chrono:chrono_proto_pb2",
+ ],
+)
+
+py_test(
+ name = "timestamp_analyzer_test",
+ srcs = [
+ "timestamp_analyzer_test.py",
+ ],
+ deps = [
+ ":pw_chrono",
+ "//pw_chrono:chrono_proto_pb2",
+ ],
+)
diff --git a/pw_chrono/py/BUILD.gn b/pw_chrono/py/BUILD.gn
new file mode 100644
index 0000000..dbdb304
--- /dev/null
+++ b/pw_chrono/py/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# 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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+import("$dir_pw_docgen/docs.gni")
+
+pw_python_package("py") {
+ generate_setup = {
+ metadata = {
+ name = "pw_chrono"
+ version = "0.0.1"
+ }
+ }
+
+ sources = [
+ "pw_chrono/__init__.py",
+ "pw_chrono/timestamp_analyzer.py",
+ ]
+ tests = [ "timestamp_analyzer_test.py" ]
+ python_deps = [ "..:protos.python" ]
+ pylintrc = "$dir_pigweed/.pylintrc"
+}
diff --git a/pw_chrono/py/pw_chrono/__init__.py b/pw_chrono/py/pw_chrono/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_chrono/py/pw_chrono/__init__.py
diff --git a/pw_chrono/py/pw_chrono/py.typed b/pw_chrono/py/pw_chrono/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_chrono/py/pw_chrono/py.typed
diff --git a/pw_chrono/py/pw_chrono/timestamp_analyzer.py b/pw_chrono/py/pw_chrono/timestamp_analyzer.py
new file mode 100644
index 0000000..d9d934b
--- /dev/null
+++ b/pw_chrono/py/pw_chrono/timestamp_analyzer.py
@@ -0,0 +1,64 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+"""Library to analyze timestamp."""
+
+from typing import List
+import datetime
+from pw_chrono_protos import chrono_pb2
+
+_UTC_EPOCH = datetime.datetime(1970, 1, 1, 00, 00, 00)
+
+_UNKNOWN = chrono_pb2.EpochType.Enum.UNKNOWN
+_TIME_SINCE_BOOT = chrono_pb2.EpochType.Enum.TIME_SINCE_BOOT
+_UTC_WALL_CLOCK = chrono_pb2.EpochType.Enum.UTC_WALL_CLOCK
+
+
+def process_snapshot(serialized_snapshot: bytes):
+ captured_timestamps = chrono_pb2.SnapshotTimestamps()
+ captured_timestamps.ParseFromString(serialized_snapshot)
+ return timestamp_output(captured_timestamps)
+
+
+def timestamp_output(timestamps: chrono_pb2.SnapshotTimestamps):
+ output: List[str] = []
+ if not timestamps.timestamps:
+ return ''
+
+ plural = '' if len(timestamps.timestamps) == 1 else 's'
+ output.append(f'Snapshot capture timestamp{plural}')
+ for timepoint in timestamps.timestamps:
+ time = timestamp_snapshot_analyzer(timepoint)
+ clock_epoch_type = timepoint.clock_parameters.epoch_type
+ if clock_epoch_type == _TIME_SINCE_BOOT:
+ output.append(f' Time since boot: {time}')
+ elif clock_epoch_type == _UTC_WALL_CLOCK:
+ utc_time = time + _UTC_EPOCH
+ output.append(f' UTC time: {utc_time}')
+ else:
+ output.append(f' Time since unknown epoch {_UNKNOWN}: unknown')
+
+ return '\n'.join(output)
+
+
+def timestamp_snapshot_analyzer(
+ captured_timepoint: chrono_pb2.TimePoint) -> datetime.timedelta:
+ ticks = captured_timepoint.timestamp
+ clock_period = (
+ captured_timepoint.clock_parameters.tick_period_seconds_numerator /
+ captured_timepoint.clock_parameters.tick_period_seconds_denominator)
+ elapsed_seconds = ticks * clock_period
+
+ time_delta = datetime.timedelta(seconds=elapsed_seconds)
+
+ return time_delta
diff --git a/pw_chrono/py/timestamp_analyzer_test.py b/pw_chrono/py/timestamp_analyzer_test.py
new file mode 100644
index 0000000..3153516
--- /dev/null
+++ b/pw_chrono/py/timestamp_analyzer_test.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+"""Tests for the timestamp analyzer."""
+
+import unittest
+from pw_chrono.timestamp_analyzer import process_snapshot
+from pw_chrono_protos import chrono_pb2
+
+
+class TimestampTest(unittest.TestCase):
+ """Test for the timestamp analyzer."""
+ def test_no_timepoint(self):
+ time_stamps = chrono_pb2.SnapshotTimestamps()
+ self.assertEqual('', str(process_snapshot(time_stamps)))
+
+ def test_timestamp_unknown_epoch_type(self):
+ time_stamps = chrono_pb2.SnapshotTimestamps()
+
+ time_point = chrono_pb2.TimePoint()
+ unkown = chrono_pb2.EpochType.Enum.UNKNOWN
+ time_point.clock_parameters.epoch_type = unkown
+
+ time_stamps.timestamps.append(time_point)
+
+ expected = '\n'.join(('Snapshot capture timestamp',
+ ' Time since unknown epoch 0: unknown'))
+
+ self.assertEqual(expected, str(process_snapshot(time_stamps)))
+
+ def test_timestamp_with_time_since_boot(self):
+ time_stamps = chrono_pb2.SnapshotTimestamps()
+
+ time_point = chrono_pb2.TimePoint()
+ time_since_boot = chrono_pb2.EpochType.Enum.TIME_SINCE_BOOT
+ time_point.clock_parameters.epoch_type = time_since_boot
+ time_point.timestamp = 100
+ time_point.clock_parameters.tick_period_seconds_numerator = 1
+ time_point.clock_parameters.tick_period_seconds_denominator = 1000
+
+ time_stamps.timestamps.append(time_point)
+
+ expected = '\n'.join(
+ ('Snapshot capture timestamp', ' Time since boot: 2:24:00'))
+
+ self.assertEqual(expected, str(process_snapshot(time_stamps)))
+
+ def test_timestamp_with_utc_wall_clock(self):
+ time_stamps = chrono_pb2.SnapshotTimestamps()
+
+ time_point = chrono_pb2.TimePoint()
+ utc_wall_clock = chrono_pb2.EpochType.Enum.UTC_WALL_CLOCK
+ time_point.clock_parameters.epoch_type = utc_wall_clock
+ time_point.timestamp = 100
+ time_point.clock_parameters.tick_period_seconds_numerator = 1
+ time_point.clock_parameters.tick_period_seconds_denominator = 1000
+
+ time_stamps.timestamps.append(time_point)
+
+ expected = '\n'.join(('Snapshot capture timestamp',
+ ' UTC time: 1970-01-01 02:24:00'))
+
+ self.assertEqual(expected, str(process_snapshot(time_stamps)))
+
+ def test_timestamp_with_time_since_boot_and_utc_wall_clock(self):
+ time_stamps = chrono_pb2.SnapshotTimestamps()
+
+ time_point = chrono_pb2.TimePoint()
+ time_since_boot = chrono_pb2.EpochType.Enum.TIME_SINCE_BOOT
+ time_point.clock_parameters.epoch_type = time_since_boot
+ time_point.timestamp = 100
+ time_point.clock_parameters.tick_period_seconds_numerator = 1
+ time_point.clock_parameters.tick_period_seconds_denominator = 1000
+ time_stamps.timestamps.append(time_point)
+
+ time_point = chrono_pb2.TimePoint()
+ utc_wall_clock = chrono_pb2.EpochType.Enum.UTC_WALL_CLOCK
+ time_point.clock_parameters.epoch_type = utc_wall_clock
+ time_point.timestamp = 100
+ time_point.clock_parameters.tick_period_seconds_numerator = 1
+ time_point.clock_parameters.tick_period_seconds_denominator = 1000
+ time_stamps.timestamps.append(time_point)
+
+ expected = '\n'.join(
+ ('Snapshot capture timestamps', ' Time since boot: 2:24:00',
+ ' UTC time: 1970-01-01 02:24:00'))
+
+ self.assertEqual(expected, str(process_snapshot(time_stamps)))
diff --git a/pw_env_setup/BUILD.gn b/pw_env_setup/BUILD.gn
index f7c2969..2e57404 100644
--- a/pw_env_setup/BUILD.gn
+++ b/pw_env_setup/BUILD.gn
@@ -34,6 +34,7 @@
"$dir_pw_build/py",
"$dir_pw_build_info/py",
"$dir_pw_build_mcuxpresso/py",
+ "$dir_pw_chrono/py",
"$dir_pw_cli/py",
"$dir_pw_compilation_testing/py",
"$dir_pw_console/py",
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index 3af3933..f18f956 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -420,6 +420,8 @@
'-//pw_blob_store/...:all',
'-//pw_boot/...:all',
'-//pw_cpu_exception_cortex_m/...:all',
+ '-//pw_chrono:chrono_proto_pb2',
+ '-//pw_chrono/py/...:all',
'-//pw_crypto/...:all', # TODO(b/236321905) Remove when passing.
'-//pw_file/...:all',
'-//pw_function:function_test', # TODO(b/241821115) Remove when passing.
diff --git a/pw_snapshot/pw_snapshot_protos/snapshot.proto b/pw_snapshot/pw_snapshot_protos/snapshot.proto
index 6c0277e..789400c 100644
--- a/pw_snapshot/pw_snapshot_protos/snapshot.proto
+++ b/pw_snapshot/pw_snapshot_protos/snapshot.proto
@@ -18,7 +18,7 @@
option java_package = "pw.snapshot.proto";
option java_outer_classname = "Snapshot";
-import "pw_chrono/chrono.proto";
+import "pw_chrono_protos/chrono.proto";
import "pw_cpu_exception_cortex_m_protos/cpu_state.proto";
import "pw_log/proto/log.proto";
import "pw_thread_protos/thread.proto";
diff --git a/pw_snapshot/py/BUILD.bazel b/pw_snapshot/py/BUILD.bazel
index 30b6566..e8a4a83 100644
--- a/pw_snapshot/py/BUILD.bazel
+++ b/pw_snapshot/py/BUILD.bazel
@@ -25,6 +25,7 @@
deps = [
":pw_snapshot_metadata",
"//pw_build_info/py:pw_build_info",
+ "//pw_chrono/py:pw_chrono",
"//pw_cpu_exception_cortex_m/py:exception_analyzer",
"//pw_snapshot:snapshot_proto_py_pb2",
"//pw_symbolizer/py:pw_symbolizer",
diff --git a/pw_snapshot/py/BUILD.gn b/pw_snapshot/py/BUILD.gn
index c5b731f..644cb05 100644
--- a/pw_snapshot/py/BUILD.gn
+++ b/pw_snapshot/py/BUILD.gn
@@ -52,6 +52,8 @@
python_deps = [
":pw_snapshot_metadata",
"$dir_pw_build_info/py",
+ "$dir_pw_chrono:protos.python",
+ "$dir_pw_chrono/py",
"$dir_pw_cpu_exception_cortex_m/py",
"$dir_pw_symbolizer/py",
"$dir_pw_thread:protos.python",
diff --git a/pw_snapshot/py/pw_snapshot/processor.py b/pw_snapshot/py/pw_snapshot/processor.py
index d96691028..faa6f79 100644
--- a/pw_snapshot/py/pw_snapshot/processor.py
+++ b/pw_snapshot/py/pw_snapshot/processor.py
@@ -26,6 +26,7 @@
from pw_snapshot_protos import snapshot_pb2
from pw_symbolizer import LlvmSymbolizer, Symbolizer
from pw_thread import thread_analyzer
+from pw_chrono import timestamp_analyzer
_LOG = logging.getLogger('snapshot_processor')
@@ -85,9 +86,15 @@
thread_info = thread_analyzer.process_snapshot(serialized_snapshot,
detokenizer, symbolizer)
+
if thread_info:
output.append(thread_info)
+ timestamp_info = timestamp_analyzer.process_snapshot(serialized_snapshot)
+
+ if timestamp_info:
+ output.append(timestamp_info)
+
# Check and emit the number of related snapshots embedded in this snapshot.
if snapshot.related_snapshots:
snapshot_count = len(snapshot.related_snapshots)