blob: 15927f042488b94a1ba132d5225e60d37600e0df [file] [log] [blame]
# Copyright 2021 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 assist processing Snapshot Metadata protos into text"""
from typing import Optional, List, Mapping
import pw_tokenizer
from pw_tokenizer import proto as proto_detokenizer
from pw_snapshot_metadata_proto import snapshot_metadata_pb2
_PRETTY_FORMAT_DEFAULT_WIDTH = 80
def _process_tags(tags: Mapping[str, str]) -> Optional[str]:
"""Outputs snapshot tags as a multi-line string."""
if not tags:
return None
output: List[str] = ['Tags:']
for key, value in tags.items():
output.append(f' {key}: {value}')
return '\n'.join(output)
def process_snapshot(serialized_snapshot: bytes,
tokenizer_db: Optional[pw_tokenizer.Detokenizer]) -> str:
"""Processes snapshot metadata and tags, producing a multi-line string."""
snapshot = snapshot_metadata_pb2.SnapshotBasicInfo()
snapshot.ParseFromString(serialized_snapshot)
output: List[str] = []
if snapshot.HasField('metadata'):
output.extend((
str(MetadataProcessor(snapshot.metadata, tokenizer_db)),
'',
))
if snapshot.tags:
tags = _process_tags(snapshot.tags)
if tags:
output.append(tags)
# Trailing blank line for spacing.
output.append('')
return '\n'.join(output)
class MetadataProcessor:
"""This class simplifies dumping contents of a snapshot Metadata message."""
def __init__(self, metadata: snapshot_metadata_pb2.Metadata,
tokenizer_db: Optional[pw_tokenizer.Detokenizer]):
self._metadata = metadata
self._tokenizer_db = (tokenizer_db if tokenizer_db is not None else
pw_tokenizer.Detokenizer(None))
self._format_width = _PRETTY_FORMAT_DEFAULT_WIDTH
proto_detokenizer.detokenize_fields(self._tokenizer_db, self._metadata)
def is_fatal(self) -> bool:
return self._metadata.fatal
def reason(self) -> str:
return self._metadata.reason.decode(
) if self._metadata.reason else 'UNKNOWN (field missing)'
def project_name(self) -> str:
return self._metadata.project_name.decode()
def device_name(self) -> str:
return self._metadata.device_name.decode()
def device_fw_version(self) -> str:
return self._metadata.software_version
def snapshot_uuid(self) -> str:
return self._metadata.snapshot_uuid.hex()
def fw_build_uuid(self) -> str:
return self._metadata.software_build_uuid.hex()
def set_pretty_format_width(self, width: int):
"""Sets the centered width of the FATAL text for a formatted output."""
self._format_width = width
def __str__(self) -> str:
"""outputs a pw.snapshot.Metadata proto as a multi-line string."""
output: List[str] = []
if self._metadata.fatal:
output.extend((
'▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·'.center(self._format_width).rstrip(),
'█▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █ '.center(self._format_width).rstrip(),
'█ ▪ ▄█▀▀█ █. ▄█▀▀█ █ '.center(self._format_width).rstrip(),
'▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌ '.center(self._format_width).rstrip(),
'▀ ▀ ▀ · ▀ ▀ ▀ .▀▀'.center(self._format_width).rstrip(),
'',
'Device crash cause:',
))
else:
output.append('Snapshot capture reason:')
output.extend((
' ' + self.reason(),
'',
))
if self._metadata.project_name:
output.append(f'Project name: {self.project_name()}')
if self._metadata.device_name:
output.append(f'Device: {self.device_name()}')
if self._metadata.software_version:
output.append(f'Device FW version: {self.device_fw_version()}')
if self._metadata.software_build_uuid:
output.append(f'FW build UUID: {self.fw_build_uuid()}')
if self._metadata.snapshot_uuid:
output.append(f'Snapshot UUID: {self.snapshot_uuid()}')
return '\n'.join(output)