xref: /aosp_15_r20/external/pigweed/pw_snapshot/module_usage.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _module-pw_snapshot-module_usage:
2
3============
4Module Usage
5============
6Right now, pw_snapshot just dictates a *format*. That means there is no provided
7system information collection integration, underlying storage, or transport
8mechanism to fetch a snapshot from a device. These must be set up independently
9by your project.
10
11-------------------
12Building a Snapshot
13-------------------
14Even though a Snapshot is just a proto message, the potential size of the proto
15makes it important to consider the encoder.
16
17Nanopb is a popular encoder for embedded devices, it's impractical to use
18with the pw_snapshot proto. Nanopb works by generating in-memory structs that
19represent the protobuf message. Repeated, optional, and variable-length fields
20increase the size of the in-memory struct. The struct representation
21of snapshot-like protos can quickly near 10KB in size. Allocating 10KB
22
23Pigweed's pw_protobuf is a better choice as its design is centered around
24incrementally writing a proto directly to the final wire format. If you only
25write a few fields in a snapshot, you can do so with minimal memory overhead.
26
27.. code-block:: cpp
28
29   #include "pw_bytes/span.h"
30   #include "pw_protobuf/encoder.h"
31   #include "pw_snapshot_protos/snapshot.pwpb.h"
32   #include "pw_status/status.h"
33   #include "pw_stream/stream.h"
34
35   pw::Status EncodeSnapshot(pw::stream::Writer& writer,
36                             pw::ByteSpan submessage_encode_buffer,
37                             const CrashInfo &crash_info) {
38     // Create a snapshot proto encoder.
39     pw::snapshot::Snapshot::StreamEncoder snapshot_encoder(
40         writer, submessage_encode_buffer);
41     {  // This scope is required to handle RAII behavior of the submessage.
42       // Start writing the Metadata submessage.
43       pw::snapshot::Metadata::StreamEncoder metadata_encoder =
44           snapshot_encoder.GetMetadataEncoder();
45       metadata_encoder.WriteReason(EncodeReasonLog(crash_info));
46       metadata_encoder.WriteFatal(true);
47       metadata_encoder.WriteProjectName(pw::as_bytes(pw::span("smart-shoe")));
48       metadata_encoder.WriteDeviceName(
49           pw::as_bytes(pw::span("smart-shoe-p1")));
50     }
51     return proto_encoder.status();
52   }
53
54-------------------
55Custom Project Data
56-------------------
57There are two main ways to add custom project-specific data to a snapshot. Tags
58are the simplest way to capture small snippets of information that require
59no or minimal post-processing. For more complex data, it's usually more
60practical to extend the Snapshot proto.
61
62Tags
63====
64Adding a key/value pair to the tags map is straightforward when using
65pw_protobuf.
66
67.. code-block:: cpp
68
69   {
70     pw::Snapshot::TagsEntry::StreamEncoder tags_encoder =
71         snapshot_encoder.GetTagsEncoder();
72     tags_encoder.WriteKey("BtState");
73     tags_encoder.WriteValue("connected");
74   }
75
76Extending the Proto
77===================
78Extending the Snapshot proto relies on proto behavior details that are explained
79in the :ref:`Snapshot Proto Format<module-pw_snapshot-proto_format>`. Extending
80the snapshot proto is as simple as defining a proto message that **only**
81declares fields with numbers that are reserved by the Snapshot proto for
82downstream projects. When encoding your snapshot, you can then write both the
83upstream Snapshot proto and your project's custom extension proto message to the
84same proto encoder.
85
86The upstream snapshot tooling will ignore any project-specific proto data,
87the proto data can be decoded a second time using a project-specific proto. At
88that point, any handling logic of the project-specific data would have to be
89done as part of project-specific tooling.
90
91-------------------
92Analyzing Snapshots
93-------------------
94Snapshots can be processed for analysis using the ``pw_snapshot.process`` python
95tool. This tool turns a binary snapshot proto into human readable, actionable
96information. As some snapshot fields may optionally be tokenized, a
97pw_tokenizer database or ELF file with embedded pw_tokenizer tokens may
98optionally be passed to the tool to detokenize applicable fields.
99
100.. code-block:: sh
101
102   # Example invocation, which dumps to stdout by default.
103   $ python -m pw_snapshot.processor path/to/serialized_snapshot.bin
104
105
106           ____ _       __    _____ _   _____    ____  _____ __  ______  ______
107          / __ \ |     / /   / ___// | / /   |  / __ \/ ___// / / / __ \/_  __/
108         / /_/ / | /| / /    \__ \/  |/ / /| | / /_/ /\__ \/ /_/ / / / / / /
109        / ____/| |/ |/ /    ___/ / /|  / ___ |/ ____/___/ / __  / /_/ / / /
110       /_/     |__/|__/____/____/_/ |_/_/  |_/_/    /____/_/ /_/\____/ /_/
111                     /_____/
112
113
114                               ▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·
115                               █▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █
116                               █ ▪ ▄█▀▀█   █. ▄█▀▀█ █
117                               ▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌
118                               ▀    ▀  ▀ ·  ▀  ▀  ▀ .▀▀
119
120   Device crash cause:
121       ../examples/example_rpc.cc: Assert failed: 1+1 == 42
122
123   Project name:      gShoe
124   Device:            GSHOE-QUANTUM_CORE-REV_0.1
125   Device FW version: QUANTUM_CORE-0.1.325-e4a84b1a
126   FW build UUID:     ad2d39258c1bc487f07ca7e04991a836fdf7d0a0
127   Snapshot UUID:     8481bb12a162164f5c74855f6d94ea1a
128
129   Thread State
130     2 threads running, Main Stack (Handler Mode) active at the time of capture.
131                        ~~~~~~~~~~~~~~~~~~~~~~~~~
132
133   Thread (INTERRUPT_HANDLER): Main Stack (Handler Mode) <-- [ACTIVE]
134   Est CPU usage: unknown
135   Stack info
136     Stack used:   0x2001b000 - 0x2001ae20 (480 bytes)
137     Stack limits: 0x2001b000 - 0x???????? (size unknown)
138   Raw Stack
139   00caadde
140
141
142   Thread (RUNNING): Idle
143   Est CPU usage: unknown
144   Stack info
145     Stack used:   0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)
146     Stack limits: 0x2001ac00 - 0x2001aa00 (512 bytes)
147
148---------------------
149Symbolizing Addresses
150---------------------
151The snapshot processor tool has built-in support for symbolization of some data
152embedded into snapshots. Taking advantage of this requires the use of a
153project-provided ``SymbolizerMatcher`` callback. This is used by the snapshot
154processor to understand which ELF file should be used to symbolize which
155snapshot in cases where a snapshot has related snapshots embedded inside of it.
156
157Here's an example implementation that uses the device name:
158
159.. code-block:: py
160
161   # Given a firmware bundle directory, determine the ELF file associated with
162   # the provided snapshot.
163   def _snapshot_symbolizer_matcher(fw_bundle_dir: Path,
164                                    snapshot: snapshot_pb2.Snapshot
165       ) -> Symbolizer:
166       metadata = MetadataProcessor(snapshot.metadata, DETOKENIZER)
167       if metadata.device_name().startswith('GSHOE_MAIN_CORE'):
168           return LlvmSymbolizer(fw_bundle_dir / 'main.elf')
169       if metadata.device_name().startswith('GSHOE_SENSOR_CORE'):
170           return LlvmSymbolizer(fw_bundle_dir / 'sensors.elf')
171       return LlvmSymbolizer()
172
173
174   # A project specific wrapper to decode snapshots that provides a detokenizer
175   # and ElfMatcher.
176   def decode_snapshots(snapshot: bytes, fw_bundle_dir: Path) -> str:
177
178       # This is the actual ElfMatcher, which wraps the helper in a lambda that
179       # captures the passed firmware artifacts directory.
180       matcher: processor.SymbolizerMatcher = (
181           lambda snapshot: _snapshot_symbolizer_matcher(
182               fw_bundle_dir, snapshot))
183       return processor.process_snapshots(snapshot, DETOKENIZER, matcher)
184
185-------------
186C++ Utilities
187-------------
188
189UUID utilities
190==============
191Snapshot UUIDs are used to uniquely identify snapshots. Pigweed strongly
192recommends using randomly generated data as a snapshot UUID. The
193more entropy and random bits, the lower the probability that two devices will
194produce the same UUID for a snapshot. 16 bytes should be sufficient for most
195projects, so this module provides ``UuidSpan`` and ``ConstUuidSpan`` types that
196can be helpful for referring to UUID-sized byte spans.
197
198Reading a snapshot's UUID
199-------------------------
200An in-memory snapshot's UUID may be read using ``ReadUuidFromSnapshot()``.
201
202.. code-block:: cpp
203
204   void NotifyNewSnapshot(ConstByteSpan snapshot) {
205     std::array<std::byte, pw::snapshot::kUuidSizeBytes> uuid;
206     pw::Result<pw::ConstByteSpan> result =
207         pw::snapshot::ReadUuidFromSnapshot(snapshot, uuid);
208     if (!result.ok()) {
209       PW_LOG_ERROR("Failed to read UUID from new snapshot, error code %d",
210                    static_cast<int>(result.status().code()));
211       return;
212     }
213     LogNewSnapshotUuid(result.value());
214   }
215