1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16 17import {assertExists} from '../base/logging'; 18import {Actions} from '../common/actions'; 19import {VERSION} from '../gen/perfetto_version'; 20import {StatusResult, TraceProcessorApiVersion} from '../protos'; 21import {HttpRpcEngine} from '../trace_processor/http_rpc_engine'; 22import {showModal} from '../widgets/modal'; 23import {Router} from './router'; 24 25import {globals} from './globals'; 26import {publishHttpRpcState} from './publish'; 27 28const CURRENT_API_VERSION = 29 TraceProcessorApiVersion.TRACE_PROCESSOR_CURRENT_API_VERSION; 30 31function getPromptMessage(tpStatus: StatusResult): string { 32 return `Trace Processor Native Accelerator detected on ${HttpRpcEngine.hostAndPort} with: 33${tpStatus.loadedTraceName} 34 35YES, use loaded trace: 36Will load from the current state of Trace Processor. If you did run 37trace_processor_shell --httpd file.pftrace this is likely what you want. 38 39YES, but reset state: 40Use this if you want to open another trace but still use the 41accelerator. This is the equivalent of killing and restarting 42trace_processor_shell --httpd. 43 44NO, Use builtin WASM: 45Will not use the accelerator in this tab. 46 47Using the native accelerator has some minor caveats: 48- Only one tab can be using the accelerator. 49- Sharing, downloading and conversion-to-legacy aren't supported. 50`; 51} 52 53function getIncompatibleRpcMessage(tpStatus: StatusResult): string { 54 return `The Trace Processor instance on ${HttpRpcEngine.hostAndPort} is too old. 55 56This UI requires TraceProcessor features that are not present in the 57Trace Processor native accelerator you are currently running. 58If you continue, this is almost surely going to cause UI failures. 59 60Please update your local Trace Processor binary: 61 62curl -LO https://get.perfetto.dev/trace_processor 63chmod +x ./trace_processor 64./trace_processor --httpd 65 66UI version code: ${VERSION} 67UI RPC API: ${CURRENT_API_VERSION} 68 69Trace processor version: ${tpStatus.humanReadableVersion} 70Trace processor version code: ${tpStatus.versionCode} 71Trace processor RPC API: ${tpStatus.apiVersion} 72`; 73} 74 75function getVersionMismatchMessage(tpStatus: StatusResult): string { 76 return `The trace processor instance on ${HttpRpcEngine.hostAndPort} is a different build from the UI. 77 78This may cause problems. Where possible it is better to use the matched version of the UI. 79You can do this by clicking the button below. 80 81UI version code: ${VERSION} 82UI RPC API: ${CURRENT_API_VERSION} 83 84Trace processor version: ${tpStatus.humanReadableVersion} 85Trace processor version code: ${tpStatus.versionCode} 86Trace processor RPC API: ${tpStatus.apiVersion} 87`; 88} 89 90// The flow is fairly complicated: 91// +-----------------------------------+ 92// | User loads the UI | 93// +-----------------+-----------------+ 94// | 95// +-----------------+-----------------+ 96// | Is trace_processor present at | 97// | HttpRpcEngine.hostAndPort? | 98// +--------------------------+--------+ 99// |No |Yes 100// | +--------------+-------------------------------+ 101// | | Does version code of UI and TP match? | 102// | +--------------+----------------------------+--+ 103// | |No |Yes 104// | | | 105// | | | 106// | +-------------+-------------+ | 107// | |Is a build of the UI at the| | 108// | |TP version code existant | | 109// | |and reachable? | | 110// | +---+----------------+------+ | 111// | | No | Yes | 112// | | | | 113// | | +--------+-------+ | 114// | | |Dialog: Mismatch| | 115// | | |Load matched UI +-------------------------------+ 116// | | |Continue +-+ | | 117// | | +----------------+ | | | 118// | | | | | 119// | +------+--------------------------+----+ | | 120// | |TP RPC version >= UI RPC version | | | 121// | +----+-------------------+-------------+ | | 122// | | No |Yes | | 123// | +----+--------------+ | | | 124// | |Dialog: Bad RPC | | | | 125// | +---+Use built-in WASM | | | | 126// | | |Continue anyway +----| | | 127// | | +-------------------+ | +-----------+-----------+ | 128// | | +--------+TP has preloaded trace?| | 129// | | +-+---------------+-----+ | 130// | | |No |Yes | 131// | | | +---------------------+ | 132// | | | | Dialog: Preloaded? | | 133// | | | + YES, use loaded trace | 134// | | +--------| YES, but reset state| | 135// | | +---------------------------------------| NO, Use builtin Wasm| | 136// | | | | | +---------------------+ | 137// | | | | | | 138// | | | Reset TP | | 139// | | | | | | 140// | | | | | | 141// Show the UI Show the UI Link to 142// (WASM mode) (RPC mode) matched UI 143 144// There are three options in the end: 145// - Show the UI (WASM mode) 146// - Show the UI (RPC mode) 147// - Redirect to a matched version of the UI 148 149// Try to connect to the external Trace Processor HTTP RPC accelerator (if 150// available, often it isn't). If connected it will populate the 151// |httpRpcState| in the frontend local state. In turn that will show the UI 152// chip in the sidebar. trace_controller.ts will repeat this check before 153// trying to load a new trace. We do this ahead of time just to have a 154// consistent UX (i.e. so that the user can tell if the RPC is working without 155// having to open a trace). 156export async function CheckHttpRpcConnection(): Promise<void> { 157 const state = await HttpRpcEngine.checkConnection(); 158 publishHttpRpcState(state); 159 if (!state.connected) { 160 // No RPC = exit immediately to the WASM UI. 161 return; 162 } 163 const tpStatus = assertExists(state.status); 164 165 function forceWasm() { 166 globals.dispatch(Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'})); 167 } 168 169 // Check short version: 170 if (tpStatus.versionCode !== '' && tpStatus.versionCode !== VERSION) { 171 const url = await Router.isVersionAvailable(tpStatus.versionCode); 172 if (url !== undefined) { 173 // If matched UI available show a dialog asking the user to 174 // switch. 175 const result = await showDialogVersionMismatch(tpStatus, url); 176 switch (result) { 177 case MismatchedVersionDialog.Dismissed: 178 case MismatchedVersionDialog.UseMatchingUi: 179 Router.navigateToVersion(tpStatus.versionCode); 180 return; 181 case MismatchedVersionDialog.UseMismatchedRpc: 182 break; 183 case MismatchedVersionDialog.UseWasm: 184 forceWasm(); 185 return; 186 default: 187 const x: never = result; 188 throw new Error(`Unsupported result ${x}`); 189 } 190 } 191 } 192 193 // Check the RPC version: 194 if (tpStatus.apiVersion < CURRENT_API_VERSION) { 195 const result = await showDialogIncompatibleRPC(tpStatus); 196 switch (result) { 197 case IncompatibleRpcDialogResult.Dismissed: 198 case IncompatibleRpcDialogResult.UseWasm: 199 forceWasm(); 200 return; 201 case IncompatibleRpcDialogResult.UseIncompatibleRpc: 202 break; 203 default: 204 const x: never = result; 205 throw new Error(`Unsupported result ${x}`); 206 } 207 } 208 209 // Check if pre-loaded: 210 if (tpStatus.loadedTraceName) { 211 // If a trace is already loaded in the trace processor (e.g., the user 212 // launched trace_processor_shell -D trace_file.pftrace), prompt the user to 213 // initialize the UI with the already-loaded trace. 214 const result = await showDialogToUsePreloadedTrace(tpStatus); 215 switch (result) { 216 case PreloadedDialogResult.Dismissed: 217 case PreloadedDialogResult.UseRpcWithPreloadedTrace: 218 globals.dispatch(Actions.openTraceFromHttpRpc({})); 219 return; 220 case PreloadedDialogResult.UseRpc: 221 // Resetting state is the default. 222 return; 223 case PreloadedDialogResult.UseWasm: 224 forceWasm(); 225 return; 226 default: 227 const x: never = result; 228 throw new Error(`Unsupported result ${x}`); 229 } 230 } 231} 232 233enum MismatchedVersionDialog { 234 UseMatchingUi = 'useMatchingUi', 235 UseWasm = 'useWasm', 236 UseMismatchedRpc = 'useMismatchedRpc', 237 Dismissed = 'dismissed', 238} 239 240async function showDialogVersionMismatch( 241 tpStatus: StatusResult, 242 url: string, 243): Promise<MismatchedVersionDialog> { 244 let result = MismatchedVersionDialog.Dismissed; 245 await showModal({ 246 title: 'Version mismatch', 247 content: m('.modal-pre', getVersionMismatchMessage(tpStatus)), 248 buttons: [ 249 { 250 primary: true, 251 text: `Open ${url}`, 252 action: () => { 253 result = MismatchedVersionDialog.UseMatchingUi; 254 }, 255 }, 256 { 257 text: 'Use builtin Wasm', 258 action: () => { 259 result = MismatchedVersionDialog.UseWasm; 260 }, 261 }, 262 { 263 text: 'Use mismatched version regardless (might crash)', 264 action: () => { 265 result = MismatchedVersionDialog.UseMismatchedRpc; 266 }, 267 }, 268 ], 269 }); 270 return result; 271} 272 273enum IncompatibleRpcDialogResult { 274 UseWasm = 'useWasm', 275 UseIncompatibleRpc = 'useIncompatibleRpc', 276 Dismissed = 'dismissed', 277} 278 279async function showDialogIncompatibleRPC( 280 tpStatus: StatusResult, 281): Promise<IncompatibleRpcDialogResult> { 282 let result = IncompatibleRpcDialogResult.Dismissed; 283 await showModal({ 284 title: 'Incompatible RPC version', 285 content: m('.modal-pre', getIncompatibleRpcMessage(tpStatus)), 286 buttons: [ 287 { 288 text: 'Use builtin Wasm', 289 primary: true, 290 action: () => { 291 result = IncompatibleRpcDialogResult.UseWasm; 292 }, 293 }, 294 { 295 text: 'Use old version regardless (will crash)', 296 action: () => { 297 result = IncompatibleRpcDialogResult.UseIncompatibleRpc; 298 }, 299 }, 300 ], 301 }); 302 return result; 303} 304 305enum PreloadedDialogResult { 306 UseRpcWithPreloadedTrace = 'useRpcWithPreloadedTrace', 307 UseRpc = 'useRpc', 308 UseWasm = 'useWasm', 309 Dismissed = 'dismissed', 310} 311 312async function showDialogToUsePreloadedTrace( 313 tpStatus: StatusResult, 314): Promise<PreloadedDialogResult> { 315 let result = PreloadedDialogResult.Dismissed; 316 await showModal({ 317 title: 'Use trace processor native acceleration?', 318 content: m('.modal-pre', getPromptMessage(tpStatus)), 319 buttons: [ 320 { 321 text: 'YES, use loaded trace', 322 primary: true, 323 action: () => { 324 result = PreloadedDialogResult.UseRpcWithPreloadedTrace; 325 }, 326 }, 327 { 328 text: 'YES, but reset state', 329 action: () => { 330 result = PreloadedDialogResult.UseRpc; 331 }, 332 }, 333 { 334 text: 'NO, Use builtin WASM', 335 action: () => { 336 result = PreloadedDialogResult.UseWasm; 337 }, 338 }, 339 ], 340 }); 341 return result; 342} 343