1/* 2 * Copyright (c) 2024 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 */ 15class stateMgmtDFX { 16 // enable profile 17 public static enableProfiler: boolean = false; 18 public static inRenderingElementId: Array<number> = new Array<number>(); 19 private static readonly DUMP_MAX_PROPERTY_COUNT: number = 50; 20 private static readonly DUMP_MAX_LENGTH: number = 10; 21 private static readonly DUMP_LAST_LENGTH: number = 3; 22 public static enableDebug: boolean = false; 23 24 public static getObservedPropertyInfo<T>(observedProp: ObservedPropertyAbstractPU<T>, isProfiler: boolean, 25 changedTrackPropertyName?: string): ObservedPropertyInfo<T> { 26 return { 27 decorator: observedProp.debugInfoDecorator(), propertyName: observedProp.info(), id: observedProp.id__(), 28 changedTrackPropertyName: changedTrackPropertyName, 29 value: stateMgmtDFX.getRawValue(observedProp), 30 inRenderingElementId: stateMgmtDFX.inRenderingElementId.length === 0 ? 31 -1 : stateMgmtDFX.inRenderingElementId[stateMgmtDFX.inRenderingElementId.length - 1], 32 dependentElementIds: observedProp.dumpDependentElmtIdsObj(typeof observedProp.getUnmonitored() === 'object' ? 33 !TrackedObject.isCompatibilityMode(observedProp.getUnmonitored()) : false, isProfiler), 34 owningView: observedProp.getOwningView(), 35 length: stateMgmtDFX.getRawValueLength(observedProp), 36 syncPeers: observedProp.dumpSyncPeers(isProfiler, changedTrackPropertyName) 37 }; 38 } 39 40 public static reportStateInfoToProfilerV2(target: object, attrName: string, changeIdSet: Set<number>): void { 41 const stateInfo: DumpInfo = new DumpInfo(); 42 try { 43 stateMgmtDFX.HandlerStateInfoToProfilerV2(target, attrName, changeIdSet, stateInfo); 44 ViewStackProcessor.sendStateInfo(JSON.stringify(stateInfo)); 45 } catch (error) { 46 stateMgmtConsole.applicationError(`An ${error.message} occurred when reporting ${target.constructor.name} information`); 47 } 48 } 49 50 private static HandlerStateInfoToProfilerV2(target: object, attrName: string, changeIdSet: Set<number>, stateInfo: DumpInfo) { 51 const decoratorInfo: string = ObserveV2.getObserve().getDecoratorInfo(target, attrName); 52 let val; 53 let id; 54 // get state value and id 55 if (Array.isArray(target) || target instanceof Set || target instanceof Map || target instanceof Date) { 56 val = target; 57 id = Utils.getArkTsUtil().getHash(target)?.toString(); 58 } 59 if ((target instanceof ViewV2) || ObserveV2.IsObservedObjectV2(target)) { 60 val = Reflect.get(target, attrName); 61 id = Utils.getArkTsUtil().getHash(target)?.toString() + attrName; 62 } 63 // handle MakeObserved 64 if (target[RefInfo.MAKE_OBSERVED_PROXY]) { 65 let raw = UIUtilsImpl.instance().getTarget(target[RefInfo.MAKE_OBSERVED_PROXY]); 66 if (Array.isArray(raw) || raw instanceof Set || raw instanceof Map || raw instanceof Date || 67 SendableType.isArray(raw) || SendableType.isMap(raw) || SendableType.isSet(raw)) { 68 val = raw; 69 id = Utils.getArkTsUtil().getHash(target)?.toString(); 70 } else { 71 val = Reflect.get(raw, attrName); 72 id = Utils.getArkTsUtil().getHash(raw)?.toString() + attrName; 73 } 74 } 75 76 // dump dependent element id 77 const dependentElementIds: Array<ElementType | string> = Array<ElementType | string>(); 78 changeIdSet.forEach((id: number) => { 79 if (id < ComputedV2.MIN_COMPUTED_ID) { 80 dependentElementIds.push(ObserveV2.getObserve().getElementInfoById(id, true)); 81 } 82 }); 83 84 // dump owned view or class 85 let ownedTarget: TargetInfo; 86 if (target instanceof ViewV2) { 87 ownedTarget = { componentName: target.constructor.name, id: target.id__() }; 88 } else if (target[RefInfo.MAKE_OBSERVED_PROXY]) { 89 let raw = UIUtilsImpl.instance().getTarget(target[RefInfo.MAKE_OBSERVED_PROXY]); 90 ownedTarget = { className: raw.constructor.name, id: Utils.getArkTsUtil().getHash(raw) }; 91 } else { 92 ownedTarget = { className: target.constructor.name, id: Utils.getArkTsUtil().getHash(target) }; 93 } 94 95 stateInfo.observedPropertiesInfo.push({ 96 decorator: decoratorInfo, propertyName: attrName, idV2: id, 97 value: stateMgmtDFX.getRawValue(val), inRenderingElementId: ObserveV2.getCurrentRecordedId(), 98 dependentElementIds: { mode: 'v2', propertyDependencies: dependentElementIds }, owningView: ownedTarget 99 }); 100 } 101 102 /** 103 * Dump decorated variable for v1 and v2 104 * 105 * @param view viewPU or ViewV2 106 * @param dumpInfo contains state variable decorator, propertyName, etc. 107 */ 108 public static getDecoratedVariableInfo(view: PUV2ViewBase, dumpInfo: DumpInfo): void { 109 if (view instanceof ViewV2) { 110 stateMgmtDFX.dumpV2VariableInfo(view, dumpInfo); 111 } else if (view instanceof ViewPU) { 112 stateMgmtDFX.dumpV1VariableInfo(view, dumpInfo); 113 } 114 } 115 116 private static dumpV1VariableInfo(view: ViewPU, dumpInfo: DumpInfo): void { 117 Object.getOwnPropertyNames(view) 118 .filter((varName: string) => varName.startsWith('__') && !varName.startsWith(ObserveV2.OB_PREFIX)) 119 .forEach((varName) => { 120 const prop: any = Reflect.get(view, varName); 121 if (prop && typeof prop === 'object' && 'debugInfoDecorator' in prop) { 122 const observedProp: ObservedPropertyAbstractPU<any> = prop as ObservedPropertyAbstractPU<any>; 123 dumpInfo.observedPropertiesInfo.push(stateMgmtDFX.getObservedPropertyInfo(observedProp, false)); 124 } 125 }); 126 } 127 128 private static dumpV2VariableInfo(view: ViewV2, dumpInfo: DumpInfo): void { 129 const meta = view[ObserveV2.V2_DECO_META]; 130 // no decorated variables, return view info directly 131 if (!meta) { 132 return; 133 } 134 Object.getOwnPropertyNames(meta) 135 .filter((varName) => !varName.startsWith(ProviderConsumerUtilV2.ALIAS_PREFIX)) // remove provider & consumer prefix 136 .forEach((varName) => { 137 dumpInfo.observedPropertiesInfo.push(stateMgmtDFX.dumpSingleV2VariableInfo(view, varName)); 138 }); 139 } 140 141 private static dumpSingleV2VariableInfo<T>(view: ViewV2, varName: string): ObservedPropertyInfo<T> { 142 const decorators: string = ObserveV2.getObserve().getDecoratorInfo(view, varName); 143 const prop: any = Reflect.get(view, varName); 144 let dependentElmIds: Set<number> | undefined = undefined; 145 if (view[ObserveV2.SYMBOL_REFS]) { 146 dependentElmIds = view[ObserveV2.SYMBOL_REFS][varName]; 147 } 148 149 return { 150 decorator: decorators, propertyName: varName, id: -1, value: stateMgmtDFX.getRawValue(prop), 151 dependentElementIds: 152 { mode: 'V2', trackPropertiesDependencies: [], propertyDependencies: stateMgmtDFX.dumpDepenetElementV2(dependentElmIds) } 153 , syncPeers: [] 154 }; 155 } 156 157 private static dumpDepenetElementV2(dependentElmIds: Set<number> | undefined): Array<ElementType | string> { 158 const dumpElementIds: Array<ElementType | string> = []; 159 dependentElmIds?.forEach((elmtId: number) => { 160 if (elmtId < ComputedV2.MIN_COMPUTED_ID) { 161 dumpElementIds.push(ObserveV2.getObserve().getElementInfoById(elmtId)); 162 } else if (elmtId < MonitorV2.MIN_WATCH_ID) { 163 dumpElementIds.push(`@Computed ${ObserveV2.getObserve().getComputedInfoById(elmtId)}`); 164 } else if (elmtId < PersistenceV2Impl.MIN_PERSISTENCE_ID) { 165 dumpElementIds.push(`@Monitor ${ObserveV2.getObserve().getMonitorInfoById(elmtId)}`); 166 } else { 167 dumpElementIds.push(`PersistenceV2[${elmtId}]`); 168 } 169 }); 170 return dumpElementIds; 171 } 172 173 private static getType(item: RawValue): string { 174 try { 175 return Object.prototype.toString.call(item); 176 } catch (e) { 177 stateMgmtConsole.warn(`Cannot get the type of current value, error message is: ${e.message}`); 178 return 'unknown type'; 179 } 180 } 181 182 /** 183 * Dump 10 items at most. 184 * If length > 10, the output will be the first 7 and last 3 items. 185 * eg: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 186 * output: [0, 1, 2, 3, 4, 5, 6, '...', 9, 10, 11] 187 * 188 */ 189 private static dumpItems(arr: Array<RawValue>): Array<DumpBuildInType> { 190 let dumpArr = arr.slice(0, stateMgmtDFX.DUMP_MAX_LENGTH); 191 if (arr.length > stateMgmtDFX.DUMP_MAX_LENGTH) { 192 dumpArr.splice(stateMgmtDFX.DUMP_MAX_LENGTH - stateMgmtDFX.DUMP_LAST_LENGTH, stateMgmtDFX.DUMP_LAST_LENGTH, '...', ...arr.slice(-stateMgmtDFX.DUMP_LAST_LENGTH)); 193 } 194 return dumpArr.map(item => (item && typeof item === 'object') ? this.getType(item) : item); 195 } 196 197 private static dumpMap(map: Map<RawValue, RawValue> | SendableMap<RawValue, RawValue>): Array<DumpBuildInType> { 198 let dumpKey = this.dumpItems(Array.from(map.keys())); 199 let dumpValue = this.dumpItems(Array.from(map.values())); 200 return dumpKey.map((item: any, index: number) => [item, dumpValue[index]]); 201 } 202 203 private static dumpObjectProperty(value: any): DumpObjectType | string { 204 let tempObj: DumpObjectType = {}; 205 try { 206 let properties: string[] = Object.getOwnPropertyNames(value); 207 properties 208 .slice(0, stateMgmtDFX.DUMP_MAX_PROPERTY_COUNT) 209 .forEach((varName: string) => { 210 const propertyValue = Reflect.get(value as Object, varName); 211 tempObj[varName] = (propertyValue && typeof propertyValue === 'object') ? this.getType(propertyValue) : propertyValue; 212 }); 213 if (properties.length > stateMgmtDFX.DUMP_MAX_PROPERTY_COUNT) { 214 tempObj['...'] = '...'; 215 } 216 } catch (e) { 217 stateMgmtConsole.warn(`can not dump Obj, error msg ${e.message}`); 218 return 'unknown type'; 219 } 220 return tempObj; 221 } 222 223 private static getRawValue<T>(prop: T | ObservedPropertyAbstractPU<T>): DumpObjectType | Array<DumpBuildInType> | T | string { 224 let rawValue: T; 225 if (prop instanceof ObservedPropertyAbstract) { 226 let wrappedValue: T = prop.getUnmonitored(); 227 rawValue = ObservedObject.GetRawObject(wrappedValue); 228 } else { 229 rawValue = ObserveV2.IsProxiedObservedV2(prop) ? prop[ObserveV2.SYMBOL_PROXY_GET_TARGET] : prop; 230 } 231 if (!rawValue || typeof rawValue !== 'object') { 232 return rawValue; 233 } 234 if (rawValue instanceof Map || SendableType.isMap(rawValue as unknown as object)) { 235 return stateMgmtDFX.dumpMap(rawValue as unknown as Map<RawValue, RawValue> | SendableMap<RawValue, RawValue>); 236 } else if (rawValue instanceof Set || SendableType.isSet(rawValue as unknown as object)) { 237 return stateMgmtDFX.dumpItems(Array.from((rawValue as unknown as Set<RawValue> | SendableSet<RawValue>).values())); 238 } else if (rawValue instanceof Array || SendableType.isArray(rawValue as unknown as object)) { 239 return stateMgmtDFX.dumpItems(Array.from(rawValue as unknown as Array<RawValue>)); 240 } else if (rawValue instanceof Date) { 241 return rawValue; 242 } else { 243 return stateMgmtDFX.dumpObjectProperty(rawValue); 244 } 245 } 246 247 private static getRawValueLength<T>(observedProp: ObservedPropertyAbstractPU<T>): number { 248 let wrappedValue: T = observedProp.getUnmonitored(); 249 if (!wrappedValue || typeof wrappedValue !== 'object') { 250 return -1; 251 } 252 let rawObject: T = ObservedObject.GetRawObject(wrappedValue); 253 if (rawObject instanceof Map || rawObject instanceof Set) { 254 return rawObject.size; 255 } else if (rawObject instanceof Array) { 256 return rawObject.length; 257 } 258 try { 259 return Object.getOwnPropertyNames(rawObject).length; 260 } catch (e) { 261 return -1; 262 } 263 } 264} 265 266function setProfilerStatus(profilerStatus: boolean): void { 267 stateMgmtConsole.warn(`${profilerStatus ? `start` : `stop`} stateMgmt Profiler`); 268 stateMgmtDFX.enableProfiler = profilerStatus; 269} 270type PropertyDependenciesInfo = { 271 mode: string, 272 trackPropertiesDependencies?: MapItem<string, Array<ElementType | number | string>>[], 273 propertyDependencies?: Array<ElementType | string> 274} 275 276type ElementType = { 277 elementId: number, 278 elementTag: string, 279 isCustomNode: boolean, 280} 281 282// Data struct send to profiler or Inspector 283type TargetInfo = { 284 id: number, componentName?: string, className?: string, isV2?: boolean, 285 isCompFreezeAllowed_?: boolean, isViewActive_?: boolean 286}; 287type ObservedPropertyInfo<T> = { 288 decorator: string, propertyName: string, value: any, id?: number, idV2?: string, inRenderingElementId?: number, 289 changedTrackPropertyName?: string | undefined, 290 dependentElementIds: PropertyDependenciesInfo, 291 length?: number 292 owningView?: TargetInfo, syncPeers?: ObservedPropertyInfo<T>[] 293}; 294 295type SimpleType = string | number | boolean; 296type DumpObjectType = Record<string, SimpleType>; 297type DumpBuildInType = Array<SimpleType> | Array<[SimpleType, SimpleType]>; 298type RawValue = any; 299 300class DumpInfo { 301 public viewInfo?: TargetInfo; 302 public observedPropertiesInfo: ObservedPropertyInfo<any>[] = [] 303} 304 305// global function used to throw error in Promise 306declare function _arkUIUncaughtPromiseError(error: any); 307 308function setAceDebugMode(): void { 309 stateMgmtDFX.enableDebug = true; 310} 311 312class aceDebugTrace { 313 public static begin(...args: any): void { 314 if (stateMgmtDFX.enableDebug) { 315 aceTrace.begin(...args); 316 } 317 } 318 public static end(): void { 319 if (stateMgmtDFX.enableDebug) { 320 aceTrace.end(); 321 } 322 } 323}