|
| 1 | +import { XMLParser } from "fast-xml-parser"; |
| 2 | +import { Result, Row, Thread, isRefField } from "./utils/xmlTypes"; |
| 3 | +import fs from "fs"; |
| 4 | +import { CpuMeasure, Measure } from "@perf-profiler/types"; |
| 5 | + |
| 6 | +const FAKE_RAM = 200; |
| 7 | +const FAKE_FPS = 60; |
| 8 | +const TIME_INTERVAL = 500; |
| 9 | +const NANOSEC_TO_MILLISEC = 1_000_000; |
| 10 | +const CPU_TIME_INTERVAL = 10; |
| 11 | + |
| 12 | +const initThreadMap = (row: Row[]): { [id: number]: string } => { |
| 13 | + const threadRef: { [id: number]: Thread } = {}; |
| 14 | + row.forEach((row: Row) => { |
| 15 | + if (!isRefField(row.thread)) { |
| 16 | + threadRef[row.thread.id] = row.thread; |
| 17 | + } |
| 18 | + }); |
| 19 | + return Object.values(threadRef).reduce((acc: { [id: number]: string }, thread) => { |
| 20 | + const currentThreadName = thread.fmt |
| 21 | + .split(" ") |
| 22 | + .slice(0, thread.fmt.split(" ").indexOf("")) |
| 23 | + .join(" "); |
| 24 | + const currentTid = thread.tid.value; |
| 25 | + const numberOfThread = Object.values(threadRef).filter((thread: Thread) => { |
| 26 | + return thread.fmt.includes(currentThreadName) && thread.tid.value < currentTid; |
| 27 | + }).length; |
| 28 | + acc[thread.id] = |
| 29 | + numberOfThread > 0 ? `${currentThreadName} (${numberOfThread})` : currentThreadName; |
| 30 | + return acc; |
| 31 | + }, {}); |
| 32 | +}; |
| 33 | + |
| 34 | +const getMeasures = (row: Row[]): Map<number, Map<string, number>> => { |
| 35 | + const sampleTimeRef: { [id: number]: number } = {}; |
| 36 | + const threadRef: { [id: number]: string } = initThreadMap(row); |
| 37 | + const classifiedMeasures = row.reduce((acc: Map<number, Map<string, number>>, row: Row) => { |
| 38 | + const sampleTime = isRefField(row.sampleTime) |
| 39 | + ? sampleTimeRef[row.sampleTime.ref] |
| 40 | + : row.sampleTime.value / NANOSEC_TO_MILLISEC; |
| 41 | + if (!isRefField(row.sampleTime)) { |
| 42 | + sampleTimeRef[row.sampleTime.id] = sampleTime; |
| 43 | + } |
| 44 | + |
| 45 | + const threadName = isRefField(row.thread) |
| 46 | + ? threadRef[row.thread.ref] |
| 47 | + : threadRef[row.thread.id]; |
| 48 | + |
| 49 | + const correspondingTimeInterval = |
| 50 | + parseInt((sampleTime / TIME_INTERVAL).toFixed(0), 10) * TIME_INTERVAL; |
| 51 | + |
| 52 | + const timeIntervalMap = acc.get(correspondingTimeInterval) ?? new Map<string, number>(); |
| 53 | + |
| 54 | + const numberOfPointsIn = timeIntervalMap.get(threadName) ?? 0; |
| 55 | + |
| 56 | + timeIntervalMap.set(threadName, numberOfPointsIn + 1); |
| 57 | + |
| 58 | + acc.set(correspondingTimeInterval, timeIntervalMap); |
| 59 | + |
| 60 | + return acc; |
| 61 | + }, new Map<number, Map<string, number>>()); |
| 62 | + return classifiedMeasures; |
| 63 | +}; |
| 64 | + |
| 65 | +export const computeMeasures = (inputFileName: string) => { |
| 66 | + const xml = fs.readFileSync(inputFileName, "utf8"); |
| 67 | + const options = { |
| 68 | + attributeNamePrefix: "", |
| 69 | + ignoreAttributes: false, |
| 70 | + parseAttributeValue: true, |
| 71 | + textNodeName: "value", |
| 72 | + updateTag(tagName: string, jPath: string, attrs: { [x: string]: string | number }) { |
| 73 | + switch (tagName) { |
| 74 | + case "trace-query-result": { |
| 75 | + return "result"; |
| 76 | + } |
| 77 | + case "sample-time": { |
| 78 | + return "sampleTime"; |
| 79 | + } |
| 80 | + default: { |
| 81 | + return tagName; |
| 82 | + } |
| 83 | + } |
| 84 | + }, |
| 85 | + }; |
| 86 | + const parser = new XMLParser(options); |
| 87 | + const jsonObject: Result = parser.parse(xml); |
| 88 | + if (!jsonObject.result.node.row) { |
| 89 | + throw new Error("No rows in the xml file"); |
| 90 | + } |
| 91 | + const measures: Map<number, Map<string, number>> = getMeasures(jsonObject.result.node.row); |
| 92 | + const formattedMeasures: Measure[] = Array.from(measures.entries()).map( |
| 93 | + (classifiedMeasures: [number, Map<string, number>]) => { |
| 94 | + const timeInterval = classifiedMeasures[0]; |
| 95 | + const timeIntervalMap = classifiedMeasures[1]; |
| 96 | + const cpuMeasure: CpuMeasure = { |
| 97 | + perName: {}, |
| 98 | + perCore: {}, |
| 99 | + }; |
| 100 | + timeIntervalMap.forEach((value: number, key: string) => { |
| 101 | + cpuMeasure.perName[key] = (value * 10) / (TIME_INTERVAL / CPU_TIME_INTERVAL); |
| 102 | + }); |
| 103 | + return { |
| 104 | + cpu: cpuMeasure, |
| 105 | + ram: FAKE_RAM, |
| 106 | + fps: FAKE_FPS, |
| 107 | + time: timeInterval, |
| 108 | + }; |
| 109 | + } |
| 110 | + ); |
| 111 | + return formattedMeasures; |
| 112 | +}; |
0 commit comments