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