1/* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {assertDefined} from 'common/assert_utils'; 18import {FileUtils} from 'common/file_utils'; 19import {TimezoneInfo} from 'common/time'; 20import {UserNotificationsListener} from 'messaging/user_notifications_listener'; 21import {TraceOverridden} from 'messaging/user_warnings'; 22import {TraceFile} from 'trace/trace_file'; 23 24export interface FilterResult { 25 legacy: TraceFile[]; 26 perfetto?: TraceFile; 27 timezoneInfo?: TimezoneInfo; 28} 29 30export class TraceFileFilter { 31 private static readonly BUGREPORT_SYSTRACE_PATH = 32 'FS/data/misc/perfetto-traces/bugreport/systrace.pftrace'; 33 private static readonly BUGREPORT_LEGACY_FILES_ALLOWLIST = [ 34 'FS/data/misc/wmtrace/', 35 'FS/data/misc/perfetto-traces/', 36 'proto/window_CRITICAL.proto', 37 'proto/input_method_CRITICAL.proto', 38 'proto/SurfaceFlinger_CRITICAL.proto', 39 ]; 40 private static readonly PERFETTO_EXTENSIONS = [ 41 '.pftrace', 42 '.perfetto-trace', 43 '.perfetto', 44 ]; 45 46 async filter( 47 files: TraceFile[], 48 UserNotificationsListener: UserNotificationsListener, 49 ): Promise<FilterResult> { 50 const bugreportMainEntry = files.find((file) => 51 file.file.name.endsWith('main_entry.txt'), 52 ); 53 54 const perfettoFiles = files.filter((file) => this.isPerfettoFile(file)); 55 const legacyFiles = files.filter((file) => !this.isPerfettoFile(file)); 56 if (!(await this.isBugreport(bugreportMainEntry, files))) { 57 const perfettoFile = this.pickLargestFile( 58 perfettoFiles, 59 UserNotificationsListener, 60 ); 61 return { 62 perfetto: perfettoFile, 63 legacy: legacyFiles, 64 }; 65 } 66 67 const timezoneInfo = await this.processRawBugReport( 68 assertDefined(bugreportMainEntry), 69 files, 70 ); 71 72 return await this.filterBugreport( 73 assertDefined(bugreportMainEntry), 74 perfettoFiles, 75 legacyFiles, 76 timezoneInfo, 77 ); 78 } 79 80 private async processRawBugReport( 81 bugreportMainEntry: TraceFile, 82 files: TraceFile[], 83 ): Promise<TimezoneInfo | undefined> { 84 const bugreportName = (await bugreportMainEntry.file.text()).trim(); 85 const rawBugReport = files.find((file) => file.file.name === bugreportName); 86 if (!rawBugReport) { 87 return undefined; 88 } 89 90 const traceBuffer = new Uint8Array(await rawBugReport.file.arrayBuffer()); 91 const fileData = new TextDecoder().decode(traceBuffer); 92 93 const timezoneStartIndex = fileData.indexOf('[persist.sys.timezone]'); 94 if (timezoneStartIndex === -1) { 95 return undefined; 96 } 97 const timezone = this.extractValueFromRawBugReport( 98 fileData, 99 timezoneStartIndex, 100 ); 101 102 return {timezone, locale: 'en-US'}; 103 } 104 105 private extractValueFromRawBugReport( 106 fileData: string, 107 startIndex: number, 108 ): string { 109 return fileData 110 .slice(startIndex) 111 .split(']', 2) 112 .map((substr) => { 113 const start = substr.lastIndexOf('['); 114 return substr.slice(start + 1); 115 })[1]; 116 } 117 118 private async isBugreport( 119 bugreportMainEntry: TraceFile | undefined, 120 files: TraceFile[], 121 ): Promise<boolean> { 122 if (!bugreportMainEntry) { 123 return false; 124 } 125 const bugreportName = (await bugreportMainEntry.file.text()).trim(); 126 return ( 127 files.find((file) => { 128 return ( 129 file.parentArchive === bugreportMainEntry.parentArchive && 130 file.file.name === bugreportName 131 ); 132 }) !== undefined 133 ); 134 } 135 136 private async filterBugreport( 137 bugreportMainEntry: TraceFile, 138 perfettoFiles: TraceFile[], 139 legacyFiles: TraceFile[], 140 timezoneInfo?: TimezoneInfo, 141 ): Promise<FilterResult> { 142 const isFileAllowlisted = (file: TraceFile) => { 143 for (const traceDir of TraceFileFilter.BUGREPORT_LEGACY_FILES_ALLOWLIST) { 144 if (file.file.name.startsWith(traceDir)) { 145 return true; 146 } 147 } 148 return false; 149 }; 150 151 const fileBelongsToBugreport = (file: TraceFile) => 152 file.parentArchive === bugreportMainEntry.parentArchive; 153 154 legacyFiles = legacyFiles.filter((file) => { 155 return isFileAllowlisted(file) || !fileBelongsToBugreport(file); 156 }); 157 158 const unzippedLegacyFiles: TraceFile[] = []; 159 160 for (const file of legacyFiles) { 161 if (await FileUtils.isZipFile(file.file)) { 162 try { 163 const subFiles = await FileUtils.unzipFile(file.file); 164 const subTraceFiles = subFiles.map((subFile) => { 165 return new TraceFile(subFile, file.file); 166 }); 167 unzippedLegacyFiles.push(...subTraceFiles); 168 } catch { 169 unzippedLegacyFiles.push(file); 170 } 171 } else { 172 unzippedLegacyFiles.push(file); 173 } 174 } 175 const perfettoFile = perfettoFiles.find( 176 (file) => file.file.name === TraceFileFilter.BUGREPORT_SYSTRACE_PATH, 177 ); 178 return {perfetto: perfettoFile, legacy: unzippedLegacyFiles, timezoneInfo}; 179 } 180 181 private isPerfettoFile(file: TraceFile): boolean { 182 return TraceFileFilter.PERFETTO_EXTENSIONS.some((perfettoExt) => { 183 return ( 184 file.file.name.endsWith(perfettoExt) || 185 file.file.name.endsWith(`${perfettoExt}.gz`) 186 ); 187 }); 188 } 189 190 private pickLargestFile( 191 files: TraceFile[], 192 UserNotificationsListener: UserNotificationsListener, 193 ): TraceFile | undefined { 194 if (files.length === 0) { 195 return undefined; 196 } 197 return files.reduce((largestSoFar, file) => { 198 const [largest, overridden] = 199 largestSoFar.file.size > file.file.size 200 ? [largestSoFar, file] 201 : [file, largestSoFar]; 202 UserNotificationsListener.onNotifications([ 203 new TraceOverridden(overridden.getDescriptor()), 204 ]); 205 return largest; 206 }); 207 } 208} 209