|
<!DOCTYPE html> |
|
<html lang="en"> |
|
|
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>nvline</title> |
|
<style> |
|
body { |
|
background-color: #121212; |
|
color: #ffffff; |
|
font-family: Arial, sans-serif; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
height: 100vh; |
|
margin: 0; |
|
padding: 20px; |
|
box-sizing: border-box; |
|
} |
|
|
|
h1 { |
|
margin-bottom: 10px; |
|
font-size: 24px; |
|
} |
|
|
|
#chart-container { |
|
width: 90%; |
|
height: 80%; |
|
} |
|
|
|
#chart { |
|
width: 100%; |
|
height: 100%; |
|
} |
|
|
|
.axis-label { |
|
fill: #ffffff; |
|
} |
|
|
|
.tooltip { |
|
position: absolute; |
|
background: rgba(0, 0, 0, 0.7); |
|
color: #fff; |
|
padding: 5px; |
|
border-radius: 3px; |
|
pointer-events: none; |
|
font-size: 12px; |
|
} |
|
|
|
.grid line { |
|
stroke: #444; |
|
} |
|
|
|
.grid path { |
|
stroke: none; |
|
} |
|
|
|
|
|
input[type="file"] { |
|
display: none; |
|
} |
|
|
|
.custom-file-upload { |
|
border: 1px solid #ccc; |
|
display: inline-block; |
|
padding: 6px 12px; |
|
cursor: pointer; |
|
} |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<h1>Import nvline JSONL File and Display Memory Usage</h1> |
|
|
|
|
|
<label for="fileInput" class="custom-file-upload"> |
|
Add nvline JSONL File |
|
</label> |
|
<input type="file" id="fileInput" accept=".jsonl"> |
|
|
|
<div id="chart-container"> |
|
<svg id="chart"></svg> |
|
</div> |
|
<div id="tooltip" class="tooltip" style="display: none;"></div> |
|
|
|
<script> |
|
document.getElementById('fileInput').addEventListener('change', handleFile, false); |
|
|
|
function handleFile(event) { |
|
const file = event.target.files[0]; |
|
const reader = new FileReader(); |
|
reader.onload = function (event) { |
|
const lines = event.target.result.split('\n').filter(line => line.trim() !== ''); |
|
const data = lines.map(line => JSON.parse(line)); |
|
drawChart(data); |
|
}; |
|
reader.readAsText(file); |
|
} |
|
|
|
function drawChart(data) { |
|
const svg = document.getElementById('chart'); |
|
svg.innerHTML = ''; |
|
const width = svg.clientWidth; |
|
const height = svg.clientHeight; |
|
const padding = 30; |
|
const xScale = width / (data.length - 1); |
|
const yMax = Math.max(...data.map(d => d.memory_total)); |
|
const yScale = (height - padding * 2) / yMax; |
|
|
|
const line = (points) => points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0]},${p[1]}`).join(' '); |
|
|
|
const memoryUsedPoints = data.map((d, i) => [i * xScale, height - padding - d.memory_used * yScale]); |
|
const memoryFreePoints = data.map((d, i) => [i * xScale, height - padding - d.memory_free * yScale]); |
|
const memoryTotalPoints = data.map((d, i) => [i * xScale, height - padding - d.memory_total * yScale]); |
|
|
|
const gridX = document.createElementNS('http://www.w3.org/2000/svg', 'g'); |
|
const gridY = document.createElementNS('http://www.w3.org/2000/svg', 'g'); |
|
|
|
for (let i = 0; i < data.length; i++) { |
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); |
|
line.setAttribute('x1', i * xScale); |
|
line.setAttribute('y1', height - padding); |
|
line.setAttribute('x2', i * xScale); |
|
line.setAttribute('y2', padding); |
|
line.setAttribute('class', 'grid'); |
|
line.setAttribute('stroke', '#444'); |
|
gridX.appendChild(line); |
|
} |
|
|
|
for (let i = 0; i <= yMax; i += yMax / 10) { |
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); |
|
line.setAttribute('x1', 0); |
|
line.setAttribute('y1', height - padding - i * yScale); |
|
line.setAttribute('x2', width); |
|
line.setAttribute('y2', height - padding - i * yScale); |
|
line.setAttribute('class', 'grid'); |
|
line.setAttribute('stroke', '#444'); |
|
gridY.appendChild(line); |
|
} |
|
|
|
svg.appendChild(gridX); |
|
svg.appendChild(gridY); |
|
|
|
const pathUsed = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
|
pathUsed.setAttribute('d', line(memoryUsedPoints)); |
|
pathUsed.setAttribute('stroke', 'red'); |
|
pathUsed.setAttribute('fill', 'none'); |
|
svg.appendChild(pathUsed); |
|
|
|
const pathFree = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
|
pathFree.setAttribute('d', line(memoryFreePoints)); |
|
pathFree.setAttribute('stroke', 'green'); |
|
pathFree.setAttribute('fill', 'none'); |
|
svg.appendChild(pathFree); |
|
|
|
const pathTotal = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
|
pathTotal.setAttribute('d', line(memoryTotalPoints)); |
|
pathTotal.setAttribute('stroke', 'blue'); |
|
pathTotal.setAttribute('fill', 'none'); |
|
svg.appendChild(pathTotal); |
|
|
|
memoryUsedPoints.forEach((point, i) => { |
|
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); |
|
circle.setAttribute('cx', point[0]); |
|
circle.setAttribute('cy', point[1]); |
|
circle.setAttribute('r', 3); |
|
circle.setAttribute('fill', 'red'); |
|
circle.setAttribute('class', 'dot dot-used'); |
|
circle.setAttribute('data-index', i); |
|
svg.appendChild(circle); |
|
}); |
|
|
|
memoryFreePoints.forEach((point, i) => { |
|
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); |
|
circle.setAttribute('cx', point[0]); |
|
circle.setAttribute('cy', point[1]); |
|
circle.setAttribute('r', 3); |
|
circle.setAttribute('fill', 'green'); |
|
circle.setAttribute('class', 'dot dot-free'); |
|
circle.setAttribute('data-index', i); |
|
svg.appendChild(circle); |
|
}); |
|
|
|
memoryTotalPoints.forEach((point, i) => { |
|
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); |
|
circle.setAttribute('cx', point[0]); |
|
circle.setAttribute('cy', point[1]); |
|
circle.setAttribute('r', 3); |
|
circle.setAttribute('fill', 'blue'); |
|
circle.setAttribute('class', 'dot dot-total'); |
|
circle.setAttribute('data-index', i); |
|
svg.appendChild(circle); |
|
}); |
|
|
|
const xAxisLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); |
|
xAxisLabel.setAttribute('x', width / 2); |
|
xAxisLabel.setAttribute('y', height - 10); |
|
xAxisLabel.setAttribute('class', 'axis-label'); |
|
xAxisLabel.setAttribute('text-anchor', 'middle'); |
|
xAxisLabel.textContent = 'Data Points'; |
|
svg.appendChild(xAxisLabel); |
|
|
|
const yAxisLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text'); |
|
yAxisLabel.setAttribute('x', -height / 2); |
|
yAxisLabel.setAttribute('y', 20); |
|
yAxisLabel.setAttribute('class', 'axis-label'); |
|
yAxisLabel.setAttribute('text-anchor', 'middle'); |
|
yAxisLabel.setAttribute('transform', 'rotate(-90)'); |
|
yAxisLabel.textContent = 'Memory Usage'; |
|
svg.appendChild(yAxisLabel); |
|
|
|
const tooltip = document.getElementById('tooltip'); |
|
svg.addEventListener('mousemove', (event) => { |
|
const rect = svg.getBoundingClientRect(); |
|
const x = event.clientX - rect.left; |
|
const index = Math.round(x / xScale); |
|
|
|
document.querySelectorAll('.dot').forEach(dot => dot.style.display = 'none'); |
|
|
|
if (index >= 0 && index < data.length) { |
|
const memoryUsed = data[index].memory_used; |
|
const memoryFree = data[index].memory_free; |
|
const memoryTotal = data[index].memory_total; |
|
const timestamp = new Date(1000 * data[index].timestamp).toISOString(); |
|
|
|
tooltip.style.display = 'block'; |
|
tooltip.style.left = event.pageX + 10 + 'px'; |
|
tooltip.style.top = event.pageY - 10 + 'px'; |
|
tooltip.innerHTML = `Total: ${memoryTotal} Used: ${memoryUsed}<br>Free: ${memoryFree}<br> Timestamp: ${timestamp}`; |
|
|
|
document.querySelector(`.dot-used[data-index='${index}']`).style.display = 'block'; |
|
document.querySelector(`.dot-free[data-index='${index}']`).style.display = 'block'; |
|
document.querySelector(`.dot-total[data-index='${index}']`).style.display = 'block'; |
|
} else { |
|
tooltip.style.display = 'none'; |
|
} |
|
}); |
|
|
|
svg.addEventListener('mouseout', () => { |
|
tooltip.style.display = 'none'; |
|
document.querySelectorAll('.dot').forEach(dot => dot.style.display = 'none'); |
|
}); |
|
} |
|
</script> |
|
</body> |
|
|
|
</html> |