• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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