1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 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 */ 15 16import { BaseElement, element } from '../../base-ui/BaseElement'; 17import { SpFlagHtml } from './SpFlag.html'; 18const NUM = '000000'; 19//vsync二级下拉选框对应的value和content 20const VSYNC_CONTENT = [ 21 { value: 'H:VsyncGenerator', content: 'VsyncGeneratior' }, 22 { value: 'H:rs_SendVsync', content: 'Vsync-rs' }, 23 { value: 'H:rs_SendVsync', content: 'Vsync-app' } 24]; 25//cat二级下拉选框对应的value和content 26const CAT_CONTENT = [ 27 { value: 'business', content: 'Business first' }, 28 { value: 'thread', content: 'Thread first' } 29]; 30//hang二级下拉选框对应的value和content 31const HANG_CONTENT = [ 32 { value: `33${NUM}`, content: 'Instant' }, 33 { value: `100${NUM}`, content: 'Circumstantial' }, 34 { value: `250${NUM}`, content: 'Micro' }, 35 { value: `500${NUM}`, content: 'Severe' } 36]; 37 38//整合默认值 39const CONFIG_STATE: unknown = { 40 'VSync': ['vsyncValue', 'VsyncGeneratior'], 41 'Start&Finish Trace Category': ['catValue', 'Business first'], 42 'Hangs Detection': ['hangValue', 'Instant'], 43}; 44 45@element('sp-flags') 46export class SpFlags extends BaseElement { 47 private bodyEl: HTMLElement | undefined | null; 48 private xiaoLubanEl: Element | null | undefined; 49 50 initElements(): void { 51 let parentElement = this.parentNode as HTMLElement; 52 parentElement.style.overflow = 'hidden'; 53 this.bodyEl = this.shadowRoot?.querySelector('.body'); 54 this.initConfigList(); 55 } 56 57 initHtml(): string { 58 return SpFlagHtml; 59 } 60 61 private createConfigDiv(): HTMLDivElement { 62 let configDiv = document.createElement('div'); 63 configDiv.className = 'flag-widget'; 64 return configDiv; 65 } 66 //控制按钮设置为'Disabled'时,我们需要给一个默认值 67 private createCustomDiv(config: FlagConfigItem, configDiv: HTMLDivElement): void { 68 let configHadDiv = document.createElement('div'); 69 configHadDiv.className = 'flag-head-div'; 70 let titleLabel = document.createElement('label'); 71 titleLabel.textContent = config.title; 72 titleLabel.className = 'flag-title-label'; 73 let configSelect = document.createElement('select'); 74 configSelect.className = 'flag-select'; 75 configSelect.setAttribute('title', config.title); 76 config.switchOptions.forEach((optionItem) => { 77 let configOption = document.createElement('option'); 78 configOption.value = optionItem.option; 79 configOption.textContent = optionItem.option; 80 if (optionItem.selected) { 81 configOption.selected = true; 82 } 83 configSelect.appendChild(configOption); 84 }); 85 // 页面刷新时,选项并未改变,小鲁班也应当展示 86 this.xiaoLubanEl = document.querySelector('sp-application')?.shadowRoot?.querySelector('#sp-bubbles') 87 ?.shadowRoot?.querySelector('#xiao-luban-help'); 88 if (configSelect.title === 'AI' && configSelect.selectedOptions[0].value === 'Enabled') { 89 this.xiaoLubanEl?.setAttribute('enabled', ''); 90 } 91 configSelect.addEventListener('change', () => { 92 this.flagSelectListener(configSelect); 93 if (configSelect.title === 'AI') { 94 let userIdInput: HTMLInputElement | null | undefined = this.shadowRoot?.querySelector('#user_id_input'); 95 if (configSelect.selectedOptions[0].value === 'Enabled') { 96 if (userIdInput?.value === '') { 97 userIdInput.style.border = '1px solid red'; 98 } 99 this.xiaoLubanEl?.setAttribute('enabled', ''); 100 } else { 101 userIdInput!.style.border = '1px solid #ccc'; 102 this.xiaoLubanEl?.removeAttribute('enabled'); 103 } 104 } 105 }); 106 let userIdInput: HTMLInputElement | null | undefined = this.shadowRoot?.querySelector('#user_id_input'); 107 if (configSelect.title === 'AI' && configSelect.selectedOptions[0].value === 'Enabled' && userIdInput?.value === '') { 108 userIdInput.style.border = '1px solid red'; 109 } 110 let description = document.createElement('div'); 111 description.className = 'flag-des-div'; 112 description.textContent = config.describeContent; 113 configHadDiv.appendChild(titleLabel); 114 configHadDiv.appendChild(configSelect); 115 configDiv.appendChild(configHadDiv); 116 configDiv.appendChild(description); 117 } 118 //监听flag-select的状态选择 119 private flagSelectListener(configSelect: HTMLSelectElement): void { 120 // @ts-ignore 121 let title = configSelect.getAttribute('title'); 122 //@ts-ignore 123 let listSelect = this.shadowRoot?.querySelector(`#${CONFIG_STATE[title]?.[0]}`); 124 // @ts-ignore 125 FlagsConfig.updateFlagsConfig(title!, configSelect.selectedOptions[0].value); 126 //@ts-ignore 127 if (listSelect) { 128 // @ts-ignore 129 if (configSelect.selectedOptions[0].value === 'Enabled') { 130 listSelect?.removeAttribute('disabled'); 131 } else { 132 listSelect?.childNodes.forEach((child: ChildNode) => { 133 let selectEl = child as HTMLOptionElement; 134 //@ts-ignore 135 if (child.textContent === CONFIG_STATE[title]?.[1]) { 136 selectEl.selected = true; 137 //@ts-ignore 138 FlagsConfig.updateFlagsConfig(CONFIG_STATE[title]?.[0], selectEl.value); 139 } else { 140 selectEl.selected = false; 141 } 142 }); 143 listSelect?.setAttribute('disabled', 'disabled'); 144 } 145 } 146 } 147 148 //初始化Flag对应的内容 149 private initConfigList(): void { 150 let allConfig = FlagsConfig.getAllFlagConfig(); 151 allConfig.forEach((config) => { 152 let configDiv = this.createConfigDiv(); 153 this.createCustomDiv(config, configDiv); 154 if (config.title === 'AnimationAnalysis') { 155 let configFooterDiv = document.createElement('div'); 156 configFooterDiv.className = 'config_footer'; 157 let deviceWidthLabelEl = document.createElement('label'); 158 deviceWidthLabelEl.className = 'device_label'; 159 deviceWidthLabelEl.textContent = 'PhysicalWidth :'; 160 let deviceWidthEl = document.createElement('input'); 161 deviceWidthEl.value = <string>config.addInfo!.physicalWidth; 162 deviceWidthEl.addEventListener('keyup', () => { 163 deviceWidthEl.value = deviceWidthEl.value.replace(/\D/g, ''); 164 }); 165 deviceWidthEl.addEventListener('blur', () => { 166 if (deviceWidthEl.value !== '') { 167 FlagsConfig.updateFlagsConfig('physicalWidth', Number(deviceWidthEl.value)); 168 } 169 }); 170 deviceWidthEl.className = 'device_input'; 171 let deviceHeightLabelEl = document.createElement('label'); 172 deviceHeightLabelEl.textContent = 'PhysicalHeight :'; 173 deviceHeightLabelEl.className = 'device_label'; 174 let deviceHeightEl = document.createElement('input'); 175 deviceHeightEl.className = 'device_input'; 176 deviceHeightEl.value = <string>config.addInfo!.physicalHeight; 177 deviceHeightEl.addEventListener('keyup', () => { 178 deviceHeightEl.value = deviceHeightEl.value.replace(/\D/g, ''); 179 }); 180 deviceHeightEl.addEventListener('blur', () => { 181 if (deviceWidthEl.value !== '') { 182 FlagsConfig.updateFlagsConfig('physicalHeight', Number(deviceHeightEl.value)); 183 } 184 }); 185 configFooterDiv.appendChild(deviceWidthLabelEl); 186 configFooterDiv.appendChild(deviceWidthEl); 187 configFooterDiv.appendChild(deviceHeightLabelEl); 188 configFooterDiv.appendChild(deviceHeightEl); 189 configDiv.appendChild(configFooterDiv); 190 } 191 //@ts-ignore 192 let configKey = CONFIG_STATE[config.title]?.[0]; 193 if (config.title === 'VSync') {//初始化Vsync 194 let configFooterDiv = this.createPersonOption(VSYNC_CONTENT, configKey, config); 195 configDiv.appendChild(configFooterDiv); 196 } 197 198 if (config.title === 'Start&Finish Trace Category') {//初始化Start&Finish Trace Category 199 let configFooterDiv = this.createPersonOption(CAT_CONTENT, configKey, config); 200 configDiv.appendChild(configFooterDiv); 201 } 202 203 if (config.title === 'Hangs Detection') {//初始化Hangs Detection 204 let configFooterDiv = this.createPersonOption(HANG_CONTENT, configKey, config); 205 configDiv.appendChild(configFooterDiv); 206 } 207 if (config.title === 'AI') { 208 let configFooterDiv = document.createElement('div'); 209 configFooterDiv.className = 'config_footer'; 210 let userIdLabelEl = document.createElement('label'); 211 userIdLabelEl.className = 'device_label'; 212 userIdLabelEl.textContent = 'User Id: '; 213 let userIdInputEl = document.createElement('input'); 214 userIdInputEl.value = <string>config.addInfo!.userId; 215 userIdInputEl.addEventListener('blur', () => { 216 if (userIdInputEl.value !== '') { 217 userIdInputEl.style.border = '1px solid #ccc'; 218 FlagsConfig.updateFlagsConfig('userId', userIdInputEl.value); 219 } else { 220 userIdInputEl.style.border = '1px solid red'; 221 } 222 }); 223 userIdInputEl.className = 'device_input'; 224 userIdInputEl.id = 'user_id_input'; 225 configFooterDiv.appendChild(userIdLabelEl); 226 configFooterDiv.appendChild(userIdInputEl); 227 configDiv.appendChild(configFooterDiv); 228 } 229 this.bodyEl!.appendChild(configDiv); 230 }); 231 } 232 233 private createPersonOption(list: unknown, key: string, config: unknown): HTMLDivElement { 234 let configFooterDiv = document.createElement('div'); 235 configFooterDiv.className = 'config_footer'; 236 let lableEl = document.createElement('lable'); 237 lableEl.className = 'list_lable'; 238 let typeEl = document.createElement('select'); 239 typeEl.setAttribute('id', key); 240 typeEl.className = 'flag-select'; 241 //根据给出的list遍历添加option下来选框 242 //@ts-ignore 243 for (const settings of list) { 244 let option = document.createElement('option'); 245 option.value = settings.value; 246 option.textContent = settings.content; 247 //初始化二级按钮状态 248 //@ts-ignore 249 if (option.value === config.addInfo?.[key]) { 250 option.selected = true; 251 FlagsConfig.updateFlagsConfig(key, option.value); 252 } 253 typeEl.appendChild(option); 254 } 255 typeEl.addEventListener('change', function () { 256 let selectValue = this.selectedOptions[0].value; 257 FlagsConfig.updateFlagsConfig(key, selectValue); 258 }); 259 let flagsItem = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); 260 let flagsItemJson = JSON.parse(flagsItem!); 261 //@ts-ignore 262 let vsync = flagsItemJson[config.title]; 263 if (vsync === 'Enabled') { 264 typeEl.removeAttribute('disabled'); 265 } else { 266 typeEl.setAttribute('disabled', 'disabled'); 267 //@ts-ignore 268 FlagsConfig.updateFlagsConfig(key, config.addInfo?.[key]); 269 } 270 configFooterDiv.appendChild(lableEl); 271 configFooterDiv.appendChild(typeEl); 272 return configFooterDiv; 273 } 274} 275 276export type Params = { 277 [key: string]: unknown; 278}; 279 280export class FlagsConfig { 281 static FLAGS_CONFIG_KEY = 'FlagsConfig'; 282 static DEFAULT_CONFIG: Array<FlagConfigItem> = [ 283 { 284 title: 'TaskPool', 285 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 286 describeContent: 'Analyze TaskPool templates', 287 }, 288 { 289 title: 'AnimationAnalysis', 290 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 291 describeContent: 'Analyze Animation effect templates', 292 addInfo: { physicalWidth: 0, physicalHeight: 0 }, 293 }, 294 { 295 title: 'AppStartup', 296 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 297 describeContent: 'App Startup templates', 298 }, 299 { 300 title: 'SchedulingAnalysis', 301 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 302 describeContent: 'Scheduling analysis templates', 303 }, 304 { 305 title: 'BinderRunnable', 306 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 307 describeContent: 'support Cpu State Binder-Runnable', 308 }, 309 { 310 title: 'FfrtConvert', 311 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 312 describeContent: 'Ffrt Convert templates', 313 }, 314 { 315 title: 'HMKernel', 316 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 317 describeContent: '', 318 }, 319 { 320 title: 'VSync', 321 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 322 describeContent: 'VSync Signal drawing', 323 addInfo: { vsyncValue: VSYNC_CONTENT[0].value }, 324 }, 325 { 326 title: 'Hangs Detection', 327 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 328 describeContent: 'hangs type:Instant(33ms~100ms),Circumstantial(100ms~250ms),Micro(250ms~500ms),Severe(>=500ms)', 329 addInfo: { hangValue: HANG_CONTENT[0].value }, 330 }, 331 { 332 title: 'LTPO', 333 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 334 describeContent: 'Lost Frame and HitchTime templates', 335 }, 336 { 337 title: 'Start&Finish Trace Category', 338 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 339 describeContent: 'Asynchronous trace aggregation', 340 addInfo: { catValue: CAT_CONTENT[0].value }, 341 }, 342 { 343 title: 'UserPluginsRow', 344 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 345 describeContent: 'User Upload Plugin To Draw', 346 }, 347 { 348 title: 'CPU by Irq', 349 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 350 describeContent: 'The real CPU after being split by irq and softirq', 351 }, 352 { 353 title: 'RawTraceCutStartTs', 354 switchOptions: [{ option: 'Enabled', selected: true }, { option: 'Disabled' }], 355 describeContent: 'Raw Trace Cut By StartTs, StartTs = Max(Cpu1 StartTs, Cpu2 StartTs, ..., CpuN StartTs)', 356 }, 357 { 358 title: 'AI', 359 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 360 describeContent: 'Start AI', 361 addInfo: { userId: '' }, 362 } 363 ]; 364 365 static getAllFlagConfig(): Array<FlagConfigItem> { 366 let flagsConfigStr = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); 367 if (flagsConfigStr === null) { 368 let flagConfigObj: Params = {}; 369 FlagsConfig.DEFAULT_CONFIG.forEach((config) => { 370 let selectedOption = config.switchOptions.filter((option) => { 371 return option.selected; 372 }); 373 let value = config.switchOptions[0].option; 374 if (selectedOption[0] !== undefined) { 375 value = selectedOption[0].option; 376 } 377 flagConfigObj[config.title] = value; 378 if (config.addInfo) { 379 for (const [key, value] of Object.entries(config.addInfo)) { 380 flagConfigObj[key] = value; 381 } 382 } 383 }); 384 window.localStorage.setItem(FlagsConfig.FLAGS_CONFIG_KEY, JSON.stringify(flagConfigObj)); 385 return FlagsConfig.DEFAULT_CONFIG; 386 } else { 387 let flagsConfig = JSON.parse(flagsConfigStr); 388 FlagsConfig.DEFAULT_CONFIG.forEach((config) => { 389 let cfg = flagsConfig[config.title]; 390 if (cfg) { 391 config.switchOptions.forEach((option) => { 392 if (option.option === cfg) { 393 option.selected = true; 394 } else { 395 option.selected = false; 396 } 397 }); 398 } 399 if (config.addInfo) { 400 for (const [key, value] of Object.entries(config.addInfo)) { 401 let cfg = flagsConfig[key]; 402 if (cfg) { 403 config.addInfo[key] = cfg; 404 } 405 } 406 } 407 }); 408 } 409 return FlagsConfig.DEFAULT_CONFIG; 410 } 411 412 static getSpTraceStreamParseConfig(): string { 413 let parseConfig = {}; 414 FlagsConfig.getAllFlagConfig().forEach((configItem) => { 415 let selectedOption = configItem.switchOptions.filter((option) => { 416 return option.selected; 417 }); 418 // @ts-ignore 419 parseConfig[configItem.title] = selectedOption[0].option === 'Enabled' ? 1 : 0; 420 }); 421 return JSON.stringify({ config: parseConfig }); 422 } 423 424 static getFlagsConfig(flagName: string): Params | undefined { 425 let flagConfigObj: Params = {}; 426 let configItem = FlagsConfig.getAllFlagConfig().find((config) => { 427 return config.title === flagName; 428 }); 429 if (configItem) { 430 let selectedOption = configItem.switchOptions.filter((option) => { 431 return option.selected; 432 }); 433 let value = configItem.switchOptions[0].option; 434 if (selectedOption[0] !== undefined) { 435 value = selectedOption[0].option; 436 } 437 flagConfigObj[configItem.title] = value; 438 if (configItem.addInfo) { 439 for (const [key, value] of Object.entries(configItem.addInfo)) { 440 flagConfigObj[key] = value; 441 } 442 } 443 return flagConfigObj; 444 } else { 445 return configItem; 446 } 447 } 448 449 static getFlagsConfigEnableStatus(flagName: string): boolean { 450 let config = FlagsConfig.getFlagsConfig(flagName); 451 let enable: boolean = false; 452 if (config && config[flagName]) { 453 enable = config[flagName] === 'Enabled'; 454 } 455 return enable; 456 } 457 //获取Cat的二级下拉选框所选的内容 458 static getSecondarySelectValue(value: string): string { 459 let list = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); 460 let listJson = JSON.parse(list!); 461 let catSelectValue = listJson[value]; 462 return catSelectValue; 463 } 464 465 static updateFlagsConfig(key: string, value: unknown): void { 466 let flagsConfigStr = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); 467 let flagConfigObj: Params = {}; 468 if (flagsConfigStr !== null) { 469 flagConfigObj = JSON.parse(flagsConfigStr); 470 } 471 flagConfigObj[key] = value; 472 window.localStorage.setItem(FlagsConfig.FLAGS_CONFIG_KEY, JSON.stringify(flagConfigObj)); 473 } 474} 475 476export interface FlagConfigItem { 477 title: string; 478 switchOptions: OptionItem[]; 479 describeContent: string; 480 addInfo?: Params; 481} 482 483export interface OptionItem { 484 option: string; 485 selected?: boolean; 486} 487