File: symbol_fetcher.py

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (265 lines) | stat: -rw-r--r-- 10,877 bytes parent folder | download | duplicates (6)
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Extracts breakpad symbol file from Google Cloud Platform."""

import logging
import os
import zipfile

import breakpad_file_extractor
import flag_utils
from metadata_extractor import OSName
import py_utils.cloud_storage as cloud_storage
import rename_breakpad

ANDROID_X86_FOLDERS = {'x86', 'x86_64', 'next-x86', 'next-x86_64'}
ANDROID_ARM_FOLDERS = {'arm', 'arm_64', 'next-arm', 'next-arm_64'}
GCS_SYMBOLS = {
    'symbols.zip', 'Monochrome_symbols.zip', 'Monochrome_symbols-secondary.zip'
}


def GetTraceBreakpadSymbols(cloud_storage_bucket,
                            metadata,
                            breakpad_output_dir,
                            dump_syms_path=None):
  """Fetch trace symbols from GCS and convert to breakpad format, if needed.

  Args:
    cloud_storage_bucket: bucket in cloud storage where symbols reside.
    metadata: MetadataExtractor class that contains necessary trace file
      metadata for fetching its symbol file.
    breakpad_output_dir: local path to store trace symbol breakpad file.
    dump_syms_path: local path to dump_syms binary. Parameter required for
      official Android traces; not required for mac or linux traces.

  Raises:
    Exception: if failed to extract trace OS name or version number, or
      they are not supported or recognized.
  """
  metadata.Initialize()
  if metadata.os_name is None:
    raise Exception('Failed to extract trace OS name: ' + metadata.trace_file)
  if metadata.version_number is None:
    raise Exception('Failed to extract trace version number: ' +
                    metadata.trace_file)

  # Obtain breakpad symbols by platform.
  if metadata.os_name == OSName.ANDROID:
    GetAndroidSymbols(cloud_storage_bucket, metadata, breakpad_output_dir)
    breakpad_file_extractor.ExtractBreakpadOnSubtree(breakpad_output_dir,
                                                     metadata, dump_syms_path)
    rename_breakpad.RenameBreakpadFiles(breakpad_output_dir,
                                        breakpad_output_dir)
  elif metadata.os_name == OSName.WINDOWS:
    raise Exception(
        'Windows platform is not currently supported for symbolization.')
  elif metadata.os_name == OSName.LINUX or metadata.os_name == OSName.MAC:
    _FetchBreakpadSymbols(cloud_storage_bucket, metadata, breakpad_output_dir)
    rename_breakpad.RenameBreakpadFiles(breakpad_output_dir,
                                        breakpad_output_dir)
  else:
    raise Exception('Trace OS "%s" is not supported: %s' %
                    (metadata.os_name, metadata.trace_file))

  flag_utils.GetTracingLogger().info('Breakpad symbols located at: %s',
                                     os.path.abspath(breakpad_output_dir))


def GetAndroidSymbols(cloud_storage_bucket, metadata, breakpad_output_dir):
  """Fetches Android symbols from GCS.

  Args:
    cloud_storage_bucket: bucket in cloud storage where symbols reside.
    metadata: MetadataExtractor class that contains necessary trace file
      metadata for fetching its symbol file.
    breakpad_output_dir: local path to store trace symbol breakpad file.

  Raises:
    Exception: if fails to extract architecture or version code from trace.
    RuntimeError: if fails to determine correct GCS folder.
  """
  if metadata.architecture is None:
    raise Exception('Failed to extract architecture: ' + metadata._trace_file)
  # Version code should exist for official builds.
  if metadata.version_code is None:
    raise Exception('Failed to extract version code: ' + metadata._trace_file)

  # Determine GCS folder.
  flag_utils.GetTracingLogger().debug('Determining Android GCS folder.')
  possible_arch_folders = set()
  if 'arm' in metadata.architecture:
    possible_arch_folders = ANDROID_ARM_FOLDERS
  else:
    possible_arch_folders = ANDROID_X86_FOLDERS

  gcs_folder = None
  for arch_folder in possible_arch_folders:
    possible_gcs_folder = ('android-B0urB0N/' + metadata.version_number + '/' +
                           arch_folder)
    # The correct folder's 'version_codes.txt' file, which contains all the
    # folder's Chrome release version codes, will match the trace's version
    # code.
    if _IsAndroidVersionCodeInFile(cloud_storage_bucket, metadata.version_code,
                                   possible_gcs_folder, breakpad_output_dir):
      gcs_folder = possible_gcs_folder
      break

  if gcs_folder is None:
    raise RuntimeError('Failed to determine architecture folder: ' +
                       str(metadata._trace_file))
  flag_utils.GetTracingLogger().debug(
      'Determined correct architecture folder is: %s', gcs_folder)

  # Fetch and unzip GCS symbol files.
  flag_utils.GetTracingLogger().info('Fetching Android symbols from GCS.')
  did_fetch_symbol_file = False
  for symbol in GCS_SYMBOLS:
    # Explicitly use backslashes for GCS paths to ensure that they are valid
    # on windows machines that use forward slashes. Local paths use python
    # |os.path.join| to utilize the correct slash for the system.
    gcs_symbol_file = gcs_folder + '/' + symbol
    symbol_zip_file = os.path.join(breakpad_output_dir, symbol)
    unzip_output_dir = os.path.join(breakpad_output_dir, symbol.split('.')[0])
    if not _FetchAndUnzipGCSFile(cloud_storage_bucket, gcs_symbol_file,
                                 symbol_zip_file, unzip_output_dir):
      flag_utils.GetTracingLogger().warning('Failed to find symbols on GCS: %s',
                                            gcs_symbol_file)
    else:
      did_fetch_symbol_file = True

  if not did_fetch_symbol_file:
    raise Exception('No symbol files could be found on GCS: ' + gcs_folder)


def _IsAndroidVersionCodeInFile(cloud_storage_bucket, version_code,
                                possible_gcs_folder, local_folder):
  """Determines if Android version code is in GCS 'version_codes.txt' file.

  The 'version_codes.txt' files contains all the version codes of the Chrome
  releases that were created from the current build directory. We determine
  the correct build directory to download symbols from by checking if the
  trace's version code matches any version code's in the build directory's
  'version_codes.txt' file. The trace's version code should uniquely match
  to one build directory.

  Args:
    cloud_storage_bucket: bucket in cloud storage where symbols reside.
    version_code: trace's version code.
    possible_gcs_folder: GCS build directory containing 'version_codes.txt'
      file.
    local_folder: local folder to download 'version_codes.txt' file to.

  Returns:
    True if version code in gcs folder's 'version_codes.txt' file;
    false otherwise.
  """
  gcs_version_code_file = possible_gcs_folder + '/version_codes.txt'
  local_version_code_file = os.path.join(local_folder, 'version_codes.txt')
  if not _FetchGCSFile(cloud_storage_bucket, gcs_version_code_file,
                       local_version_code_file):
    flag_utils.GetTracingLogger().debug(
        'Failed to download version code file: %s', gcs_version_code_file)
    return False

  with open(local_version_code_file, encoding='utf-8') as version_file:
    return version_code in version_file.read()


def _FetchBreakpadSymbols(cloud_storage_bucket, metadata, breakpad_output_dir):
  """Fetches and extracts Mac or Linux breakpad format symbolization file.

  Args:
    cloud_storage_bucket: bucket in cloud storage where symbols reside.
    metadata: MetadataExtractor class that contains necessary trace file
      metadata for fetching its symbol file.
    breakpad_output_dir: local path to store trace symbol breakpad file.

  Raises:
    Exception: if trace OS is not mac or linux, or failed to extract
      version number.
    ValueError: if linux trace is of 32 bit bitness.
  """
  # Determine GCS folder.
  folder = None
  if metadata.os_name == OSName.LINUX:
    if metadata.bitness == '32':
      raise ValueError('32 bit Linux traces are not supported.')
    folder = 'linux64'
  elif metadata.os_name == OSName.MAC:
    if (metadata.architecture is
        not None) and 'arm' in metadata.architecture.lower():
      folder = 'mac-arm64'
    else:
      if metadata.architecture is None:
        flag_utils.GetTracingLogger().warning(
            'Architecture not found, so using x86-64.')
      folder = 'mac64'
  else:
    raise Exception('Expected OS "%s" to be Linux or Mac: %s' %
                    (metadata.os_name, metadata.trace_file))

  # Build Google Cloud Storage path to the symbols.
  assert folder is not None
  gcs_folder = 'desktop-*/' + metadata.version_number + '/' + folder
  gcs_file = gcs_folder + '/breakpad-info'
  gcs_zip_file = gcs_file + '.zip'

  # Local path to downloaded symbols.
  breakpad_zip = os.path.join(breakpad_output_dir, 'breakpad-info.zip')

  # Fetch and unzip symbol files from GCS. Some version, like mac,
  # don't have the .zip extension on GCS. Assumes that 'breakpad-info'
  # file (without .zip extension) from GCS is a zip file.
  if not _FetchAndUnzipGCSFile(cloud_storage_bucket, gcs_zip_file, breakpad_zip,
                               breakpad_output_dir):
    if not _FetchAndUnzipGCSFile(cloud_storage_bucket, gcs_file, breakpad_zip,
                                 breakpad_output_dir):
      raise Exception('Failed to find symbols on GCS: %s[.zip].' % (gcs_file))


def _FetchAndUnzipGCSFile(cloud_storage_bucket, gcs_file, gcs_output,
                          output_dir):
  """Fetch file from GCS to local |gcs_output|, then unzip it into |output_dir|.

  Returns:
    True if successfully fetches and unzips file; false, otherwise.

  Raises:
    zipfile.BadZipfile: if file is not a zip file
  """
  if _FetchGCSFile(cloud_storage_bucket, gcs_file, gcs_output):
    _UnzipFile(gcs_output, output_dir)
    return True
  return False


def _FetchGCSFile(cloud_storage_bucket, gcs_file, output_file):
  """Fetch and save file from GCS to |output_file|.

  Args:
    cloud_storage_bucket: bucket in cloud storage where symbols reside.
    gcs_file: path to file in GCS.
    output_file: local file to store fetched GCS file.

  Returns:
    True if successfully fetches file; False, otherwise.
  """
  if cloud_storage.Exists(cloud_storage_bucket, gcs_file):
    flag_utils.GetTracingLogger().info('Downloading files from GCS: %s',
                                       gcs_file)
    cloud_storage.Get(cloud_storage_bucket, gcs_file, output_file)
    flag_utils.GetTracingLogger().info('Saved file locally to: %s', output_file)
    return True
  return False


def _UnzipFile(zip_file, output_dir):
  """Unzips file into provided output directory.

  Raises:
    zipfile.BadZipfile: if file is not a zip file
  """
  with zipfile.ZipFile(zip_file, 'r') as zip_f:
    zip_f.extractall(output_dir)