-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
298 lines (247 loc) · 10.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
<head>
<!-- Load plotly.js into the DOM -->
<script src='https://cdn.plot.ly/plotly-2.12.1.min.js'></script>
<style>
p.dotted {
border-style: dotted;
}
label {
height: 100px;
display: inline-block;
vertical-align: top;
padding-top: 25px;
padding-left: 25px;
}
</style>
<script src="https://unpkg.com/dayjs@1.11.3/dayjs.min.js"></script>
<!-- https://day.js.org/docs/en/plugin/loading-into-browser -->
<script src="https://unpkg.com/dayjs@1.11.3/plugin/utc.js"></script>
<script>dayjs.extend(window.dayjs_plugin_utc)</script>
<script src="https://unpkg.com/dayjs@1.11.3/plugin/customParseFormat.js"></script>
<script>dayjs.extend(window.dayjs_plugin_customParseFormat)</script>
<script src="https://unpkg.com/dayjs@1.11.3/plugin/duration.js"></script>
<script>dayjs.extend(window.dayjs_plugin_duration)</script>
</head>
<body>
<p class="dotted">
<label id='drop-area' for="javascript">Drag a /run/logs/debug.log file here</label>
</p>
<br><br>
<div id='actualGraphDiv'>
<!-- Plotly chart will be drawn inside this DIV -->
</div>
<script type="text/javascript">
var messagesToMatch = [
['(FabricLoader/Resolution) Mod resolution time'],
['(FabricLoader/Knot) Setting up languages.'],
['(FabricLoader/Knot) Setting up mods.'],
['(FabricLoader/Knot) Loading access wideners.'],
['(FabricLoader/Mixin) FabricMixin bootstrap init.'],
['(FabricLoader/Mixin) Computing mixin configs.'],
['(FabricLoader/Mixin) Applying mixin decorators.'],
['(FabricLoader/Mixin) FabricMixin bootstrap complete.'],
['(FabricLoader/Knot) Init transformers.'],
['(FabricLoader/Knot) Unlock classpath.'],
["(FabricLoader/Mixin) Selecting config of"],
["(FabricLoader/Mixin) Selecting extensions: total"],
["(FabricLoader/Mixin) Begin pendingConfigs preparing: total"],
["(FabricLoader/Mixin) Begin scanning other targets"],
["(FabricLoader/Mixin) Begin postInitialise pendingConfigs: total"],
["(FabricLoader/Mixin) Begin sorting pendingConfigs: total"],
// note: "Entrypoint scan preLaunch" must be excluded because it doesnt fully take into account Mixing Classloading
["(FabricLoader/Mixin) Mixin prepare complete for total", "Sponge: mixing classes for preLaunch entrypoint"],
["(FabricLoader/Knot) prepareModInit on"],
["(FabricLoader/Entrypoint) Iterating over entrypoint \'main\'", "All mods: Sponge Mixing & invoking entrypoint: Main"],
["(FabricLoader/Entrypoint) Iterating over entrypoint \'client\'", "All mods: Sponge Mixing & invoking entrypoint: Client"],
["Backend library: LWJGL version"],
// ####################
// ######## from now on, theres no code to identify phases. its all background loading
// #########################
["(KubeJS) Loaded client.properties", "Sponge: Mixing more"],
[ "(net.minecraft.class_1088) Exception loading blockstate definition: byg:blockstates/potted_hydrangea_bush.json: java.io.FileNotF", "mc blockstates"],
["(net.minecraft.class_1088) Unable to load model:", "mc models"], // doesnt exist when dash present
["(net.minecraft.class_1088) Unable to resolve texture reference: #missing in autoworkstations:block/iron_auto_anvil", "mc textures"],
["(net.minecraft.class_4010) Finished reloading class_1092"],
["(FabricLoader/Mixin) Mixing client.MixinLayerDefinitions from byg_fabric.mixins.json", "mixins"],
["(DashLoader) ? DashLoader Statistics."], // only appears when dash mod added
['Created: 256x256x0 minecraft:textures/atlas/mob_effects.png-atlas', "mc atlas"],
["(net.minecraft.class_4010) Total blocking time:", "game ready"]
]
var phases = [] // 2d array [e][MATCHED_LOGMSG, DATEJS_STAMP, PREV_LOG_STAMP, LABEL]
resetPhases()
function resetPhases() {
phases = []
for (idx in messagesToMatch) {
phases[idx] = ["", 0, "", "", 0] // initialize segments. [MATCHED_LOGMSG, DATEJS_STAMP, PREV_LOG_STAMP, LABEL]
phases[idx][0] = messagesToMatch[idx][0] // set value
}
}
// console.log(phases)
//https://web.dev/read-files/#select-dnd
function readFile(file) {
const reader = new FileReader();
reader.addEventListener('load', (event) => {
const result = event.target.result;
// Do something with result
//console.log(result)
resetPhases()
var log = readMinecraftLog(result)
buildReal()
});
reader.addEventListener('progress', (event) => {
if (event.loaded && event.total) {
const percent = (event.loaded / event.total) * 100;
// console.log(`Progress: ${Math.round(percent)}`);
}
});
reader.addEventListener('error', (event) => {
console.log(event)
});
reader.addEventListener('loadend', (event) => {
console.log(event)
});
reader.addEventListener('abort', (event) => {
console.log(event)
});
reader.readAsText(file);
}
var recordedTimestamps = []
// https://www.delftstack.com/howto/javascript/javascript-read-file-line-by-line/
function readMinecraftLog(result) {
console.log(`parsing log...`)
var fileContentArray = result.split(/\r\n|\n/);
var regex = /(\[\d\d:\d\d:\d\d\])( )(\[.*\])( )(.*)/
var lastTimestampRecorded = ""
for (idx in fileContentArray) {
var str = fileContentArray[idx]
//console.log(idx+" : "+fileContentArray[idx])
var matchGroups = str.match(regex)
//console.log(matchGroups)
if (matchGroups !== null && matchGroups !== undefined && matchGroups.length > 1) {
// we're getting rid of [0] which is full match all sections
var triGroup = [matchGroups[1], matchGroups[3], matchGroups[5]]
// format: [TIME, LOG_LEVEL, MESSAGE]
if (triGroup.length > 1) {
var thisPhaseTime = String(triGroup[0]).substring(1, triGroup[0].length - 1) // strip [] brackets
var prevlastTimestampRecorded = lastTimestampRecorded
if (lastTimestampRecorded !== thisPhaseTime) {
recordedTimestamps.push(thisPhaseTime)
lastTimestampRecorded = thisPhaseTime
}
var timestamp = dayjs(thisPhaseTime, 'HH:mm:ss')
// console.log(`timestamp ${thisPhaseTime} is ${timestamp} of ${triGroup} based on ${matchGroups}`)
for (idx2 in messagesToMatch) {
var key = messagesToMatch[idx2][0]
var key2 = triGroup[2]
//console.log(`compare ${key2} to ${key}`)
// matched a message we're looking for
if (key2.includes(key)) {
phases[idx2][1] = timestamp
phases[idx2][2] = prevlastTimestampRecorded
if (messagesToMatch[idx2].length >= 2)
phases[idx2][3] = messagesToMatch[idx2][1]
//console.log(`set ${phases[idx2][0]} as ${time}`)
}
}
}
}
}
console.log(lastTimestampRecorded)
console.log(recordedTimestamps)
function isValid(entry) {
return entry[1] instanceof dayjs
}
for (idx in phases) {
if (!isValid(phases[idx])) {
console.error(`phase not found in log! ${phases[idx]}`)
}
}
phases = phases.filter(isValid)
phases.sort(function(a, b) {
var isbefore = a[1].isBefore(b[1])
var issame = a[1].isSame(b[1])
return issame ? 0 : isbefore ? -1 : 1
})
console.log(phases)
for (idx in phases) {
if (idx >= phases.length) // no more target log messages
break
var thisStamp = phases[idx][1]
var thisPhaseTime = thisStamp.format('HH:mm:ss')
var nextId = parseInt(idx) + 1
var next = phases[nextId]
// edgecase: a phase not present. solved by pruning null timestamps and setting as value 0.
if ((thisStamp instanceof dayjs) === false) {
console.log(`no timestamp found for current phase ${idx} is ${thisStamp}`)
continue
}
// expected: final phase
if (next === undefined || next === null) {
console.log(`no next phase ${nextId} is ${next}. Current phase ${thisPhaseName} timestamp is ${thisPhaseTime}`)
continue
}
var nextStamp = next[1]
var diff = nextStamp.diff(thisStamp, 's')
var thisPhaseName = messagesToMatch[idx].length == 1 ? phases[idx][0] : messagesToMatch[idx][1]
phases[idx][4] = diff
console.log(`phase ${idx} ${thisPhaseName} took ${diff} from ${thisStamp} to ${nextStamp.format('HH:mm:ss')}`)
}
console.log(phases)
}
const dropArea = document.getElementById('drop-area');
dropArea.addEventListener('dragover', (event) => {
event.stopPropagation();
event.preventDefault();
// Style the drag-and-drop as a "copy file" operation.
event.dataTransfer.dropEffect = 'copy';
});
dropArea.addEventListener('drop', (event) => {
event.stopPropagation();
event.preventDefault();
const fileList = event.dataTransfer.files;
console.log(fileList);
readFile(fileList[0])
});
// https://plotly.com/javascript/reference/bar/#bar-textposition
function genPhase(idx, duration) {
var name = phases[idx][0]
var time = parseInt(duration)
if (phases[idx][3].length >= 4) {
name = phases[idx][3]
}
var result = {
x: [''],
y: [time],
name: "".concat(name) + " : " + time + "s",
type: 'bar',
orientation: 'v',
text: name + ": " + duration + " seconds",
insidetextanchor: 'middle',
hoverinfo: 'name+x'
}
return result
}
function buildReal() {
var phasesGraph = []
var ttl = 0
for (idx in phases) {
var time = phases[idx][4] // Math.floor(Math.random() * 6) // dummy data
ttl += parseInt(time)
var ph = genPhase(idx, time)
//console.log(ph)
phasesGraph.push(ph)
}
var layoutRealGraph = {
barmode: 'stack',
title: `Minecraft Loading Phase Intervals.
<br>Total modpack load time: ${ttl}`
+ ` seconds`,
yaxis: { title: 'Duration (seconds)' },
xaxis: { title: 'Loading Phase' },
autosize: true,
height: 800
};
Plotly.newPlot('actualGraphDiv', phasesGraph, layoutRealGraph);
}
</script>
</body>