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): void { 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 propertyVariableNames: [string, any][] = view.__getDecoratorPropertyName__V2View__Internal(); 130 // no decorated variables, return view info directly 131 if (propertyVariableNames.length === 0) { 132 return; 133 } 134 propertyVariableNames 135 .filter((entry) => !entry[0].startsWith(ProviderConsumerUtilV2.ALIAS_PREFIX)) 136 .forEach((entry) => { 137 dumpInfo.observedPropertiesInfo.push(stateMgmtDFX.dumpSingleV2VariableInfo(view, entry)); 138 }); 139 } 140 141 private static dumpSingleV2VariableInfo<T>(view: ViewV2, entry: [string, any]): ObservedPropertyInfo<T> { 142 const varName = entry[0]; 143 const deco: any = entry[1]; 144 const decorators: string = ObserveV2.getObserve().parseDecorator(deco); 145 const prop: any = Reflect.get(view, varName); 146 let dependentElmIds: Set<number> | undefined = undefined; 147 if (view[ObserveV2.SYMBOL_REFS]) { 148 dependentElmIds = view[ObserveV2.SYMBOL_REFS][varName]; 149 } 150 151 return { 152 decorator: decorators, propertyName: varName, id: -1, value: stateMgmtDFX.getRawValue(prop), 153 dependentElementIds: 154 { mode: 'V2', trackPropertiesDependencies: [], propertyDependencies: stateMgmtDFX.dumpDepenetElementV2(dependentElmIds) } 155 , syncPeers: [] 156 }; 157 } 158 159 private static dumpDepenetElementV2(dependentElmIds: Set<number> | undefined): Array<ElementType | string> { 160 const dumpElementIds: Array<ElementType | string> = []; 161 dependentElmIds?.forEach((elmtId: number) => { 162 if (elmtId < ComputedV2.MIN_COMPUTED_ID) { 163 dumpElementIds.push(ObserveV2.getObserve().getElementInfoById(elmtId)); 164 } else if (elmtId < MonitorV2.MIN_WATCH_ID) { 165 dumpElementIds.push(`@Computed ${ObserveV2.getObserve().getComputedInfoById(elmtId)}`); 166 } else if (elmtId < PersistenceV2Impl.MIN_PERSISTENCE_ID) { 167 dumpElementIds.push(`@Monitor ${ObserveV2.getObserve().getMonitorInfoById(elmtId)}`); 168 } else { 169 dumpElementIds.push(`PersistenceV2[${elmtId}]`); 170 } 171 }); 172 return dumpElementIds; 173 } 174 175 private static getType(item: RawValue): string { 176 try { 177 return Object.prototype.toString.call(item); 178 } catch (e) { 179 stateMgmtConsole.warn(`Cannot get the type of current value, error message is: ${e.message}`); 180 return 'unknown type'; 181 } 182 } 183 184 /** 185 * Dump 10 items at most. 186 * If length > 10, the output will be the first 7 and last 3 items. 187 * eg: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 188 * output: [0, 1, 2, 3, 4, 5, 6, '...', 9, 10, 11] 189 * 190 */ 191 private static dumpItems(arr: Array<RawValue>): Array<DumpBuildInType> { 192 let dumpArr = arr.slice(0, stateMgmtDFX.DUMP_MAX_LENGTH); 193 if (arr.length > stateMgmtDFX.DUMP_MAX_LENGTH) { 194 dumpArr.splice(stateMgmtDFX.DUMP_MAX_LENGTH - stateMgmtDFX.DUMP_LAST_LENGTH, stateMgmtDFX.DUMP_LAST_LENGTH, '...', ...arr.slice(-stateMgmtDFX.DUMP_LAST_LENGTH)); 195 } 196 return dumpArr.map(item => (item && typeof item === 'object') ? this.getType(item) : item); 197 } 198 199 private static dumpMap(map: Map<RawValue, RawValue> | SendableMap<RawValue, RawValue>): Array<DumpBuildInType> { 200 let dumpKey = this.dumpItems(Array.from(map.keys())); 201 let dumpValue = this.dumpItems(Array.from(map.values())); 202 return dumpKey.map((item: any, index: number) => [item, dumpValue[index]]); 203 } 204 205 private static dumpObjectProperty(value: any): DumpObjectType | string { 206 let tempObj: DumpObjectType = {}; 207 try { 208 let properties: string[] = Object.getOwnPropertyNames(value); 209 properties 210 .slice(0, stateMgmtDFX.DUMP_MAX_PROPERTY_COUNT) 211 .forEach((varName: string) => { 212 const propertyValue = Reflect.get(value as Object, varName); 213 tempObj[varName] = (propertyValue && typeof propertyValue === 'object') ? this.getType(propertyValue) : propertyValue; 214 }); 215 if (properties.length > stateMgmtDFX.DUMP_MAX_PROPERTY_COUNT) { 216 tempObj['...'] = '...'; 217 } 218 } catch (e) { 219 stateMgmtConsole.warn(`can not dump Obj, error msg ${e.message}`); 220 return 'unknown type'; 221 } 222 return tempObj; 223 } 224 225 private static getRawValue<T>(prop: T | ObservedPropertyAbstractPU<T>): DumpObjectType | Array<DumpBuildInType> | T | string { 226 let rawValue: T; 227 if (prop instanceof ObservedPropertyAbstract) { 228 let wrappedValue: T = prop.getUnmonitored(); 229 rawValue = ObservedObject.GetRawObject(wrappedValue); 230 } else { 231 rawValue = ObserveV2.IsProxiedObservedV2(prop) ? prop[ObserveV2.SYMBOL_PROXY_GET_TARGET] : prop; 232 } 233 if (!rawValue || typeof rawValue !== 'object') { 234 return rawValue; 235 } 236 if (rawValue instanceof Map || SendableType.isMap(rawValue as unknown as object)) { 237 return stateMgmtDFX.dumpMap(rawValue as unknown as Map<RawValue, RawValue> | SendableMap<RawValue, RawValue>); 238 } else if (rawValue instanceof Set || SendableType.isSet(rawValue as unknown as object)) { 239 return stateMgmtDFX.dumpItems(Array.from((rawValue as unknown as Set<RawValue> | SendableSet<RawValue>).values())); 240 } else if (rawValue instanceof Array || SendableType.isArray(rawValue as unknown as object)) { 241 return stateMgmtDFX.dumpItems(Array.from(rawValue as unknown as Array<RawValue>)); 242 } else if (rawValue instanceof Date) { 243 return rawValue; 244 } else { 245 return stateMgmtDFX.dumpObjectProperty(rawValue); 246 } 247 } 248 249 private static getRawValueLength<T>(observedProp: ObservedPropertyAbstractPU<T>): number { 250 let wrappedValue: T = observedProp.getUnmonitored(); 251 if (!wrappedValue || typeof wrappedValue !== 'object') { 252 return -1; 253 } 254 let rawObject: T = ObservedObject.GetRawObject(wrappedValue); 255 if (rawObject instanceof Map || rawObject instanceof Set) { 256 return rawObject.size; 257 } else if (rawObject instanceof Array) { 258 return rawObject.length; 259 } 260 try { 261 return Object.getOwnPropertyNames(rawObject).length; 262 } catch (e) { 263 return -1; 264 } 265 } 266} 267 268function setProfilerStatus(profilerStatus: boolean): void { 269 stateMgmtConsole.warn(`${profilerStatus ? `start` : `stop`} stateMgmt Profiler`); 270 stateMgmtDFX.enableProfiler = profilerStatus; 271} 272type PropertyDependenciesInfo = { 273 mode: string, 274 trackPropertiesDependencies?: MapItem<string, Array<ElementType | number | string>>[], 275 propertyDependencies?: Array<ElementType | string> 276} 277 278type ElementType = { 279 elementId: number, 280 elementTag: string, 281 isCustomNode: boolean, 282} 283 284// Data struct send to profiler or Inspector 285type TargetInfo = { 286 id: number, componentName?: string, className?: string, isV2?: boolean, 287 isCompFreezeAllowed_?: boolean, isViewActive_?: boolean 288}; 289type ObservedPropertyInfo<T> = { 290 decorator: string, propertyName: string, value: any, id?: number, idV2?: string, inRenderingElementId?: number, 291 changedTrackPropertyName?: string | undefined, 292 dependentElementIds: PropertyDependenciesInfo, 293 length?: number 294 owningView?: TargetInfo, syncPeers?: ObservedPropertyInfo<T>[] 295}; 296 297type SimpleType = string | number | boolean; 298type DumpObjectType = Record<string, SimpleType>; 299type DumpBuildInType = Array<SimpleType> | Array<[SimpleType, SimpleType]>; 300type RawValue = any; 301 302class DumpInfo { 303 public viewInfo?: TargetInfo; 304 public observedPropertiesInfo: ObservedPropertyInfo<any>[] = [] 305} 306 307// global function used to throw error in Promise 308declare function _arkUIUncaughtPromiseError(error: any); 309 310function setAceDebugMode(): void { 311 stateMgmtDFX.enableDebug = true; 312} 313 314class aceDebugTrace { 315 public static begin(...args: any): void { 316 if (stateMgmtDFX.enableDebug) { 317 aceTrace.begin(...args); 318 } 319 } 320 public static end(): void { 321 if (stateMgmtDFX.enableDebug) { 322 aceTrace.end(); 323 } 324 } 325}