function getLockType(eventType) {
if (eventType.includes("mutex")) return "mutex";
if (eventType.includes("rwlock")) return "rwlock";
if (eventType.includes("condvar")) return "condvar";
return "mutex"; }
function isAcquisitionEvent(eventType) {
return eventType.includes("acquired") || eventType === "acquired";
}
function isAttemptEvent(eventType) {
return eventType.includes("attempt") || eventType === "attempt";
}
function isReleaseEvent(eventType) {
return eventType.includes("released") || eventType === "released";
}
function isWaitBeginEvent(eventType) {
return eventType.includes("wait_begin");
}
function isWaitEndEvent(eventType) {
return eventType.includes("wait_end");
}
function isSpawnEvent(eventType) {
return eventType.includes("spawn") || eventType === "spawn";
}
function isExitEvent(eventType) {
return eventType.includes("exit") || eventType === "exit";
}
function mapLockId(lockNum) {
return lockNum.toString()
}
function transformLogs(rawLogs, resourceMapping) {
const threadMapping = {}
let nextThreadIdx = 1;
const sortedLogs = rawLogs.slice().sort((a, b) => a[0] - b[0]);
let mainThreadId = null;
for (const log of sortedLogs) {
const [sequence, rawThread, lockNum, eventCode, timestamp, parentId, wokenThread] = log;
if (parentId !== 0 && mainThreadId === null) {
mainThreadId = parentId;
break;
}
}
console.log("Identified main thread ID:", mainThreadId);
const logs = [];
const initLog = {
step: 1,
timestamp: null, type: "init",
description: "Waiting for threads and resources to arrive...<br>Sit back, relax, and enjoy the calm before the deadlocks.",
};
logs.push(initLog);
const eventTypes = {
0: "thread_spawn",
1: "thread_exit",
2: "mutex_spawn",
3: "mutex_exit",
4: "rwlock_spawn",
5: "rwlock_exit",
6: "condvar_spawn",
7: "condvar_exit",
10: "mutex_attempt",
11: "mutex_acquired",
12: "mutex_released",
20: "rwlock_read_attempt",
21: "rwlock_read_acquired",
22: "rwlock_read_released",
23: "rwlock_write_attempt",
24: "rwlock_write_acquired",
25: "rwlock_write_released",
30: "condvar_wait_begin",
31: "condvar_wait_end",
32: "condvar_notify_one",
33: "condvar_notify_all",
40: "attempt",
41: "acquired",
42: "released"
};
const resourceOwners = {}; const threadWaiting = {};
let deadlockDetected = false;
for (let idx = 0; idx < sortedLogs.length; idx++) {
if (deadlockDetected) break;
const log = sortedLogs[idx];
const [sequence, rawThread, lockNum, eventCode, timestamp, parentId, wokenThread] = log;
if (!(rawThread in threadMapping) && rawThread !== 0) {
threadMapping[rawThread] = nextThreadIdx++;
}
const type = eventTypes[eventCode] || "unknown";
if (type === "unknown") continue;
if (isAcquisitionEvent(type) && rawThread !== 0 && lockNum !== 0) {
resourceOwners[lockNum] = rawThread;
delete threadWaiting[rawThread];
}
else if (isAttemptEvent(type) && rawThread !== 0 && lockNum !== 0) {
threadWaiting[rawThread] = lockNum;
const deadlockCycle = detectDeadlockCycle(resourceOwners, threadWaiting);
if (deadlockCycle && deadlockCycle.length >= 2) {
deadlockDetected = true;
}
}
else if (isWaitBeginEvent(type) && rawThread !== 0 && lockNum !== 0) {
threadWaiting[rawThread] = lockNum;
const deadlockCycle = detectDeadlockCycle(resourceOwners, threadWaiting);
if (deadlockCycle && deadlockCycle.length >= 2) {
deadlockDetected = true;
}
}
else if (isReleaseEvent(type) && rawThread !== 0 && lockNum !== 0) {
if (resourceOwners[lockNum] === rawThread) {
delete resourceOwners[lockNum];
}
}
else if (isWaitEndEvent(type) && rawThread !== 0) {
delete threadWaiting[rawThread];
}
if (isSpawnEvent(type)) {
let description = "";
const lockType = getLockType(type);
if (type === "thread_spawn" && rawThread !== 0) {
let parentName;
if (parentId === 0) {
parentName = "main thread";
} else if (parentId === mainThreadId) {
parentName = `<span class="main-thread">Main Thread</span>`;
} else {
parentName = `<span class="thread-id">Thread ${parentId}</span>`;
}
description = `${parentName} spawned <span class="thread-id">Thread ${rawThread}</span>.`;
} else if (lockNum !== 0) {
let parentName;
if (parentId === 0) {
parentName = "main thread";
} else if (parentId === mainThreadId) {
parentName = `<span class="main-thread">Main Thread</span>`;
} else {
parentName = `<span class="thread-id">Thread ${parentId}</span>`;
}
let lockTypeDesc = "";
switch (lockType) {
case "mutex": lockTypeDesc = "Mutex"; break;
case "rwlock": lockTypeDesc = "RwLock"; break;
case "condvar": lockTypeDesc = "Condvar"; break;
default: lockTypeDesc = "Resource"; break;
}
description = `<span class="resource-id ${lockType}">${lockTypeDesc} ${resourceMapping[lockNum]}</span> created by ${parentName}.`;
}
logs.push({
step: idx + 2, timestamp: Math.floor(timestamp * 1000), type,
lock_type: lockType,
thread_id: rawThread,
resource_id: lockNum !== 0 ? resourceMapping[lockNum] : null,
parent_id: parentId,
description,
is_main_thread: rawThread === mainThreadId, });
}
else if (isExitEvent(type)) {
let description = "";
const lockType = getLockType(type);
if (type === "thread_exit" && rawThread !== 0) {
if (rawThread === mainThreadId) {
description = `<span class="main-thread">Main Thread</span> exited.`;
} else {
description = `<span class="thread-id">Thread ${rawThread}</span> exited.`;
}
delete threadWaiting[rawThread];
Object.keys(resourceOwners).forEach(res => {
if (resourceOwners[res] === rawThread) {
delete resourceOwners[res];
}
});
} else if (lockNum !== 0) {
let lockTypeDesc = "";
switch (lockType) {
case "mutex": lockTypeDesc = "Mutex"; break;
case "rwlock": lockTypeDesc = "RwLock"; break;
case "condvar": lockTypeDesc = "Condvar"; break;
default: lockTypeDesc = "Resource"; break;
}
description = `<span class="resource-id ${lockType}">${lockTypeDesc} ${resourceMapping[lockNum]}</span> dropped.`;
delete resourceOwners[lockNum];
}
logs.push({
step: idx + 2, timestamp: Math.floor(timestamp * 1000), type,
lock_type: lockType,
thread_id: rawThread,
resource_id: lockNum !== 0 ? resourceMapping[lockNum] : null,
description,
is_main_thread: rawThread === mainThreadId, });
}
else {
let threadDescription;
if (rawThread === mainThreadId) {
threadDescription = `<span class="main-thread">Main Thread</span>`;
} else {
threadDescription = `<span class="thread-id">Thread ${rawThread}</span>`;
}
const lockType = getLockType(type);
let actionText = "";
let lockTypeDesc = "";
let extraSuffix = "";
switch (lockType) {
case "mutex": lockTypeDesc = "Mutex"; break;
case "rwlock": lockTypeDesc = "RwLock"; break;
case "condvar": lockTypeDesc = "Condvar"; break;
default: lockTypeDesc = "Resource"; break;
}
switch (type) {
case "mutex_attempt":
case "attempt":
actionText = `attempted to acquire`;
break;
case "mutex_acquired":
case "acquired":
actionText = `acquired`;
break;
case "mutex_released":
case "released":
actionText = `released`;
break;
case "rwlock_read_attempt":
actionText = `attempted to read-lock`;
break;
case "rwlock_read_acquired":
actionText = `acquired read-lock on`;
break;
case "rwlock_read_released":
actionText = `released read-lock on`;
break;
case "rwlock_write_attempt":
actionText = `attempted to write-lock`;
break;
case "rwlock_write_acquired":
actionText = `acquired write-lock on`;
break;
case "rwlock_write_released":
actionText = `released write-lock on`;
break;
case "condvar_wait_begin":
actionText = `began waiting on`;
break;
case "condvar_wait_end":
actionText = `finished waiting on`;
break;
case "condvar_notify_one":
actionText = `notified one waiter on`;
if (wokenThread && wokenThread !== 0) {
const wokenDisplay = wokenThread === mainThreadId ? `<span class="main-thread">Main Thread</span>` : `<span class="thread-id">Thread ${wokenThread}</span>`;
extraSuffix = ` (woke ${wokenDisplay})`;
} else {
extraSuffix = ` (no waiters)`;
}
break;
case "condvar_notify_all":
actionText = `notified all waiters on`;
if (wokenThread && wokenThread !== 0) {
const wokenDisplay = wokenThread === mainThreadId ? `Main Thread` : `Thread ${wokenThread}`;
extraSuffix = ` (woke ${wokenDisplay} and others)`;
} else {
extraSuffix = ` (no waiters)`;
}
break;
default:
actionText = type;
break;
}
const baseEntry = {
step: idx + 2, timestamp: Math.floor(timestamp * 1000), sequence, type,
lock_type: lockType,
thread_id: rawThread,
resource_id: resourceMapping[lockNum],
description: `${threadDescription} ${actionText} <span class="resource-id ${lockType}">${lockTypeDesc} ${resourceMapping[lockNum]}</span>${extraSuffix}`,
is_main_thread: rawThread === mainThreadId, };
if (type === "condvar_notify_one" && wokenThread && wokenThread !== 0) {
baseEntry.woken_thread_id = wokenThread;
} else if (type === "condvar_notify_all" && wokenThread && wokenThread !== 0) {
baseEntry.woken_threads = [wokenThread];
}
logs.push(baseEntry);
}
}
return logs;
}
function detectDeadlockCycle(resourceOwners, threadWaiting) {
if (Object.keys(threadWaiting).length === 0) {
return null;
}
const waitForGraph = {};
Object.entries(threadWaiting).forEach(([waitingThreadId, resourceId]) => {
const waitingThread = parseInt(waitingThreadId);
const resourceOwner = resourceOwners[resourceId];
if (resourceOwner !== undefined) {
waitForGraph[waitingThread] = resourceOwner;
}
});
if (Object.keys(waitForGraph).length === 0) {
return null;
}
const visited = {};
const recStack = {};
let cycle = null;
function detectCycle(node, path = []) {
visited[node] = true;
recStack[node] = true;
path.push(node);
const neighbor = waitForGraph[node];
if (neighbor !== undefined) {
if (recStack[neighbor]) {
const cycleStart = path.indexOf(neighbor);
cycle = path.slice(cycleStart);
return true;
}
if (!visited[neighbor] && detectCycle(neighbor, path)) {
return true;
}
}
path.pop();
recStack[node] = false;
return false;
}
for (const node in waitForGraph) {
if (!visited[node]) {
if (detectCycle(parseInt(node))) {
break;
}
}
}
return cycle;
}
function generateGraphStateFromLogs(logs, graphThreadMapping, resourceMapping) {
const activeThreads = new Set();
const activeResources = new Set();
const graphStates = [];
const threadNodes = Object.keys(graphThreadMapping).map(threadId => ({
id: `T${threadId}`,
name: `Thread ${threadId}`,
type: "thread",
}));
const resourceNodes = Object.keys(resourceMapping).map((lockNum) => {
return {
id: `R${lockNum}`,
name: `Resource ${resourceMapping[lockNum]}`,
type: "resource",
lock_type: "mutex", };
});
const nodesMap = {};
threadNodes.forEach(node => {
nodesMap[node.id] = node;
});
resourceNodes.forEach(node => {
nodesMap[node.id] = node;
});
graphStates.push({
step: 1,
nodes: [],
links: []
});
const cumulativeLinks = {};
logs.forEach((log, idx) => {
if (log.type === "init") return;
const prevState = graphStates[graphStates.length - 1];
const currentNodes = [...prevState.nodes];
let currentLinks = [...prevState.links];
const transientLinks = [];
if (isSpawnEvent(log.type)) {
if (log.type === "thread_spawn" && log.thread_id !== 0) {
const threadId = log.thread_id;
const nodeId = `T${threadId}`;
if (!activeThreads.has(nodeId)) {
activeThreads.add(nodeId);
if (!nodesMap[nodeId]) {
nodesMap[nodeId] = {
id: nodeId,
name: `Thread ${threadId}`,
type: "thread",
};
}
currentNodes.push(nodesMap[nodeId]);
}
} else if (log.resource_id) {
const resourceId = `R${log.resource_id.replace(/^[A-Z]/, '')}`;
const lockType = log.lock_type || "mutex";
if (!activeResources.has(resourceId)) {
activeResources.add(resourceId);
let lockTypeDesc = "";
switch (lockType) {
case "mutex": lockTypeDesc = "Mutex"; break;
case "rwlock": lockTypeDesc = "RwLock"; break;
case "condvar": lockTypeDesc = "Condvar"; break;
default: lockTypeDesc = "Resource"; break;
}
if (!nodesMap[resourceId]) {
nodesMap[resourceId] = {
id: resourceId,
name: `${lockTypeDesc} ${log.resource_id}`,
type: "resource",
lock_type: lockType,
};
} else {
nodesMap[resourceId].lock_type = lockType;
nodesMap[resourceId].name = `${lockTypeDesc} ${log.resource_id}`;
}
currentNodes.push(nodesMap[resourceId]);
}
}
}
else if (isExitEvent(log.type)) {
if (log.type === "thread_exit" && log.thread_id !== 0) {
const threadId = log.thread_id;
const nodeId = `T${threadId}`;
activeThreads.delete(nodeId);
const nodeIndex = currentNodes.findIndex(n => n.id === nodeId);
if (nodeIndex !== -1) {
currentNodes.splice(nodeIndex, 1);
}
Object.keys(cumulativeLinks).forEach(key => {
if (key.startsWith(`${nodeId}-`)) {
delete cumulativeLinks[key];
}
});
currentLinks = Object.keys(cumulativeLinks).map(key => {
const [source, target] = key.split("-");
return { source, target, type: cumulativeLinks[key] };
});
} else if (log.resource_id) {
const resourceId = `R${log.resource_id.replace(/^[A-Z]/, '')}`;
activeResources.delete(resourceId);
const nodeIndex = currentNodes.findIndex(n => n.id === resourceId);
if (nodeIndex !== -1) {
currentNodes.splice(nodeIndex, 1);
}
Object.keys(cumulativeLinks).forEach(key => {
if (key.endsWith(`-${resourceId}`)) {
delete cumulativeLinks[key];
}
});
currentLinks = Object.keys(cumulativeLinks).map(key => {
const [source, target] = key.split("-");
return { source, target, type: cumulativeLinks[key] };
});
}
}
else if ((isAttemptEvent(log.type) || isWaitBeginEvent(log.type) || isAcquisitionEvent(log.type) || isReleaseEvent(log.type) || isWaitEndEvent(log.type)) &&
log.thread_id !== 0 && log.resource_id) {
const threadId = log.thread_id;
const sourceId = `T${threadId}`;
const resourceLetter = log.resource_id;
const resourceNum = Object.keys(resourceMapping).find(key => resourceMapping[key] === resourceLetter);
const targetId = `R${resourceNum}`;
const linkKey = `${sourceId}-${targetId}`;
if (!activeThreads.has(sourceId)) {
activeThreads.add(sourceId);
if (!nodesMap[sourceId]) {
nodesMap[sourceId] = {
id: sourceId,
name: `Thread ${threadId}`,
type: "thread",
};
}
currentNodes.push(nodesMap[sourceId]);
}
if (!activeResources.has(targetId)) {
activeResources.add(targetId);
if (!nodesMap[targetId]) {
nodesMap[targetId] = {
id: targetId,
name: `Resource ${log.resource_id}`,
type: "resource",
};
}
currentNodes.push(nodesMap[targetId]);
}
if (isReleaseEvent(log.type) || isWaitEndEvent(log.type)) {
delete cumulativeLinks[linkKey];
} else {
cumulativeLinks[linkKey] = log.type;
}
currentLinks = Object.keys(cumulativeLinks).map(key => {
const [s, t] = key.split("-");
return { source: s, target: t, type: cumulativeLinks[key] };
});
}
else if (log.type === "deadlock") {
if (log.cycle && log.cycle.length >= 2) {
log.cycle.forEach(threadId => {
const nodeId = `T${threadId}`;
const node = currentNodes.find(n => n.id === nodeId);
if (node) {
node.inDeadlock = true;
}
});
}
}
graphStates.push({
step: graphStates.length + 1,
nodes: currentNodes,
links: [...currentLinks, ...transientLinks],
});
});
const deadlockLog = logs.find(log => log.type === "deadlock");
if (deadlockLog && deadlockLog.cycle && deadlockLog.cycle.length >= 2) {
console.log("DEADLOCK DETECTED - Creating deadlock links", deadlockLog);
const lastState = graphStates[graphStates.length - 1];
const deadlockLinks = [...lastState.links];
const deadlockThreads = deadlockLog.cycle;
console.log("Deadlock threads in cycle:", deadlockThreads);
const waitingForInfo = deadlockLog.deadlock_details.thread_waiting_for_locks;
console.log("Thread waiting for locks info:", waitingForInfo);
const threadToResource = {};
waitingForInfo.forEach(info => {
threadToResource[info.thread_id] = info.resource_id;
});
console.log("Thread to resource mapping:", threadToResource);
const resourceToThread = {};
lastState.links.forEach(link => {
if (link.type === "acquired" && link.source.startsWith("T") && link.target.startsWith("R")) {
const threadId = link.source.substring(1); const resourceId = link.target.substring(1); resourceToThread[resourceId] = threadId;
}
});
console.log("Resource to thread mapping:", resourceToThread);
for (let i = 0; i < deadlockThreads.length; i++) {
const currentThread = deadlockThreads[i];
const nextThread = deadlockThreads[(i + 1) % deadlockThreads.length];
console.log(`Creating deadlock link: T${currentThread} -> T${nextThread}`);
const sourceNode = lastState.nodes.find(node => node.id === `T${currentThread}`);
const targetNode = lastState.nodes.find(node => node.id === `T${nextThread}`);
if (sourceNode && targetNode) {
deadlockLinks.push({
source: sourceNode,
target: targetNode,
type: "deadlock",
isDeadlockEdge: true
});
console.log("Added deadlock link with object references");
} else {
console.error("Could not find nodes for threads:", currentThread, nextThread);
console.log("Available nodes:", lastState.nodes.map(n => n.id));
}
}
console.log("Final deadlock links count:", deadlockLinks.length);
graphStates.push({
step: graphStates.length + 1,
nodes: lastState.nodes.map(node => ({ ...node })), links: deadlockLinks,
});
console.log("Added new graph state with deadlock links");
}
return graphStates;
}
function transformRawObject(rawData) {
try { console.log('[transformRawObject] rawData keys:', Array.isArray(rawData) ? 'array' : Object.keys(rawData || {})); } catch (e) { }
const rawLogs = Array.isArray(rawData)
? rawData[0]
: (Array.isArray(rawData.events) ? rawData.events : []);
const graphThreadMapping = {};
rawLogs.forEach((log) => {
const rawThread = log[1]; if (rawThread !== 0 && !(rawThread in graphThreadMapping)) {
graphThreadMapping[rawThread] = rawThread;
}
});
const resourceMapping = {};
rawLogs.forEach((log) => {
const lockNum = log[2]; if (lockNum !== 0 && !(lockNum in resourceMapping)) {
resourceMapping[lockNum] = mapLockId(lockNum);
}
});
const logs = transformLogs(rawLogs, resourceMapping);
try {
if (Array.isArray(rawData)) {
const second = (rawData.length > 1) ? rawData[1] : null;
let dl = null;
if (second && typeof second === 'object' && !Array.isArray(second)) {
dl = second.deadlock ? second.deadlock : (Array.isArray(second.thread_cycle) ? second : null);
} else if (Array.isArray(second) && second.length >= 3) {
const [thread_cycle, thread_waiting_for_locks, timestamp] = second;
if (Array.isArray(thread_cycle)) {
dl = { thread_cycle, thread_waiting_for_locks: thread_waiting_for_locks || [], timestamp };
console.log('[transformRawObject] Interpreted tuple-form deadlock:', dl);
}
}
if (dl && Array.isArray(dl.thread_cycle)) {
console.log('[transformRawObject] Found terminal deadlock (array form):', dl);
const waits = (dl.thread_waiting_for_locks || []).map(w => Array.isArray(w) ? { thread_id: w[0], resource_id: w[1] } : w);
const resourceTypeById = {};
try {
logs.forEach(e => {
if (e && typeof e.type === 'string' && e.type.endsWith('_spawn') && e.resource_id) {
const num = parseInt(e.resource_id);
if (!isNaN(num)) resourceTypeById[num] = e.lock_type || 'mutex';
}
});
} catch (e) { }
const descLines = [];
for (let i = 0; i < dl.thread_cycle.length; i++) {
const threadId = dl.thread_cycle[i];
const nextThread = dl.thread_cycle[(i + 1) % dl.thread_cycle.length];
const wait = waits.find(w => w.thread_id === threadId);
if (!wait) continue;
const resId = parseInt(wait.resource_id);
const rType = resourceTypeById[resId] || 'mutex';
const rLabel = rType === 'rwlock' ? 'RwLock' : (rType === 'condvar' ? 'Condvar' : 'Mutex');
descLines.push(`<span class="thread-id">Thread ${threadId}</span> is waiting for <span class="resource-id ${rType}">${rLabel} ${resId}</span> held by <span class="thread-id">Thread ${nextThread}</span>`);
}
const description = `<strong>DEADLOCK DETECTED:</strong><br>` + (descLines.length ? descLines.join('<br>') : '');
logs.push({
step: logs.length + 1,
timestamp: Date.now(),
type: 'deadlock',
cycle: dl.thread_cycle,
description,
deadlock_details: {
thread_cycle: dl.thread_cycle,
thread_waiting_for_locks: waits.map(w => ({ thread_id: w.thread_id, lock_id: w.resource_id, resource_id: w.resource_id })),
timestamp: dl.timestamp,
},
});
console.log('[transformRawObject] Appended deadlock entry (array) at step', logs[logs.length - 1].step);
} else {
console.log('[transformRawObject] No terminal deadlock found in array payload second element:', second);
}
} else {
const dl = (rawData && rawData.deadlock) ? rawData.deadlock : null;
if (dl) {
console.log('[transformRawObject] Found terminal deadlock (object form):', dl);
const waits = (dl.thread_waiting_for_locks || []).map(w => Array.isArray(w) ? { thread_id: w[0], resource_id: w[1] } : w);
const resourceTypeById = {};
try {
logs.forEach(e => {
if (e && typeof e.type === 'string' && e.type.endsWith('_spawn') && e.resource_id) {
const num = parseInt(e.resource_id);
if (!isNaN(num)) resourceTypeById[num] = e.lock_type || 'mutex';
}
});
} catch (e) { }
const descLines = [];
for (let i = 0; i < dl.thread_cycle.length; i++) {
const threadId = dl.thread_cycle[i];
const nextThread = dl.thread_cycle[(i + 1) % dl.thread_cycle.length];
const wait = waits.find(w => w.thread_id === threadId);
if (!wait) continue;
const resId = parseInt(wait.resource_id);
const rType = resourceTypeById[resId] || 'mutex';
const rLabel = rType === 'rwlock' ? 'RwLock' : (rType === 'condvar' ? 'Condvar' : 'Mutex');
descLines.push(`<span class="thread-id">Thread ${threadId}</span> is waiting for <span class="resource-id ${rType}">${rLabel} ${resId}</span> held by <span class="thread-id">Thread ${nextThread}</span>`);
}
const description = `<strong>DEADLOCK DETECTED:</strong><br>` + (descLines.length ? descLines.join('<br>') : '');
logs.push({
step: logs.length + 1,
timestamp: Date.now(),
type: 'deadlock',
cycle: dl.thread_cycle,
description,
deadlock_details: {
thread_cycle: dl.thread_cycle,
thread_waiting_for_locks: waits.map(w => ({ thread_id: w.thread_id, lock_id: w.resource_id, resource_id: w.resource_id })),
timestamp: dl.timestamp,
},
});
console.log('[transformRawObject] Appended deadlock entry (object) at step', logs[logs.length - 1].step);
}
}
} catch (e) { console.warn('No terminal deadlock info or failed to parse', e); }
const graph_state = generateGraphStateFromLogs(
logs,
graphThreadMapping,
resourceMapping
);
return { logs, graph_state };
}
function decodeLogs(encodedStr) {
try {
if (typeof encodedStr !== 'string') {
throw new Error("encodedStr must be a string");
}
var base64 = encodedStr.replace(/-/g, "+").replace(/_/g, "/");
var binaryStr = atob(base64);
var len = binaryStr.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
var decompressed = pako.ungzip(bytes);
var logsData = msgpack.decode(decompressed);
return logsData; } catch (error) {
console.error("Error decoding logs:", error);
throw new Error("Failed to decode the logs data: " + error.message);
}
}
function processEncodedLog(encodedStr) {
try {
if (typeof encodedStr !== 'string') {
const out = transformRawObject(encodedStr);
console.log('[processEncodedLog] non-string input; logs:', out.logs.length, 'graph steps:', out.graph_state.length);
return out;
}
const decoded = decodeLogs(encodedStr);
const out = transformRawObject(decoded);
console.log('[processEncodedLog] decoded; logs:', out.logs.length, 'graph steps:', out.graph_state.length);
if (out.logs.length > 0) {
console.log('[processEncodedLog] last log type:', out.logs[out.logs.length - 1].type);
}
return out;
} catch (error) {
console.error("Error in processEncodedLog:", error);
throw error;
}
}
function processNewFormatLogs(logText) {
try {
let jsonData;
if (typeof logText === 'string') {
const lines = logText.split(/\r?\n/).filter(l => l.trim().length > 0);
const events = [];
let terminalDeadlock = null;
for (const rawLine of lines) {
const line = rawLine.trim().replace(/^\uFEFF/, '');
try {
const obj = JSON.parse(line);
if (obj.event) {
const { thread_id, lock_id, event, timestamp, parent_id } = obj;
const eventCode = getEventCode(event);
events.push([thread_id, lock_id, eventCode, timestamp, parent_id || 0]);
} else if (obj.deadlock) {
terminalDeadlock = obj.deadlock;
}
} catch (e) {
console.error('Error parsing JSON line:', e, line);
}
}
jsonData = { events, deadlock: terminalDeadlock };
} else {
jsonData = logText;
}
const rawLogs = Array.isArray(jsonData.events) ? jsonData.events : [];
const allThreads = new Set();
const allLocks = new Set();
rawLogs.forEach(log => {
const threadId = log[1]; const lockId = log[2];
if (threadId !== 0) {
allThreads.add(threadId);
}
if (lockId !== 0) {
allLocks.add(lockId);
}
});
const resourceMapping = {};
Array.from(allLocks).forEach((lockId) => {
resourceMapping[lockId] = mapLockId(lockId);
});
const graphThreadMapping = {};
Array.from(allThreads).forEach((threadId) => {
graphThreadMapping[threadId] = threadId;
});
const logs = transformLogs(rawLogs, resourceMapping);
if (jsonData.deadlock) {
const dl = jsonData.deadlock;
const waits = (dl.thread_waiting_for_locks || []).map(w => Array.isArray(w) ? { thread_id: w[0], resource_id: w[1] } : w);
const resourceTypeById = {};
try {
logs.forEach(e => {
if (e && typeof e.type === 'string' && e.type.endsWith('_spawn') && e.resource_id) {
const num = parseInt(e.resource_id);
if (!isNaN(num)) resourceTypeById[num] = e.lock_type || 'mutex';
}
});
} catch (_) { }
const descLines = [];
for (let i = 0; i < dl.thread_cycle.length; i++) {
const threadId = dl.thread_cycle[i];
const nextThread = dl.thread_cycle[(i + 1) % dl.thread_cycle.length];
const wait = waits.find(w => w.thread_id === threadId);
if (!wait) continue;
const resId = parseInt(wait.resource_id);
const rType = resourceTypeById[resId] || 'mutex';
const rLabel = rType === 'rwlock' ? 'RwLock' : (rType === 'condvar' ? 'Condvar' : 'Mutex');
descLines.push(`<span class="thread-id">Thread ${threadId}</span> is waiting for <span class="resource-id ${rType}">${rLabel} ${resId}</span> held by <span class="thread-id">Thread ${nextThread}</span>`);
}
const description = `<strong>DEADLOCK DETECTED:</strong><br>` + (descLines.length ? descLines.join('<br>') : '');
logs.push({
step: logs.length + 1,
timestamp: Date.now(),
type: 'deadlock',
cycle: dl.thread_cycle,
description,
deadlock_details: {
thread_cycle: dl.thread_cycle,
thread_waiting_for_locks: waits.map(w => ({ thread_id: w.thread_id, lock_id: w.resource_id, resource_id: w.resource_id })),
timestamp: dl.timestamp,
},
});
}
const graphStates = generateGraphStateFromLogs(
logs,
graphThreadMapping,
resourceMapping
);
return {
logs,
graph_state: graphStates,
};
} catch (error) {
console.error("Error processing logs:", error);
throw error;
}
}
function getEventCode(event) {
switch (event) {
case 'ThreadSpawn': return 0;
case 'ThreadExit': return 1;
case 'MutexSpawn': return 2;
case 'MutexExit': return 3;
case 'RwSpawn': return 4;
case 'RwExit': return 5;
case 'CondvarSpawn': return 6;
case 'CondvarExit': return 7;
case 'MutexAttempt': return 10;
case 'MutexAcquired': return 11;
case 'MutexReleased': return 12;
case 'RwReadAttempt': return 20;
case 'RwReadAcquired': return 21;
case 'RwReadReleased': return 22;
case 'RwWriteAttempt': return 23;
case 'RwWriteAcquired': return 24;
case 'RwWriteReleased': return 25;
case 'CondvarWaitBegin': return 30;
case 'CondvarWaitEnd': return 31;
case 'CondvarNotifyOne': return 32;
case 'CondvarNotifyAll': return 33;
case 'Attempt': return 40;
case 'Acquired': return 41;
case 'Released': return 42;
case 'Spawn': return 3; case 'Exit': return 4;
default: return -1; }
}