aboutsummaryrefslogtreecommitdiff
path: root/pw_snapshot/py/pw_snapshot_metadata/metadata.py
blob: ea3938096da9054834a757ec404b7b19bb46e112 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 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_log_tokenized
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:
        if not self._metadata.reason:
            return 'UNKNOWN (field missing)'

        log = pw_log_tokenized.FormatStringWithMetadata(
            self._metadata.reason.decode())

        return f'{log.file}: {log.message}' if log.file else log.message

    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)