groundwork 0.1.0

A library that provides a status page for your Rust process
Documentation
<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/groundwork/w3.css" rel>
    <title>Status</title>
</head>

<body>
    <div style="position: sticky; top: 0;">
        <div class="w3-green">
            <span id="name" class="stats-api-value"></span>
            runing on hostname:
            <span id="hostname" class="stats-api-value"></span>
        </div>
        <div class="w3-bar w3-dark-gray" id="tab-bar">
            <button class="w3-bar-item w3-button w3-circle w3-black" onclick="refreshCurrent()">
                <svg fill="#e2e2e2" width="15px" version="1.1" viewBox="0 0 487.23 487.23" stroke="#e2e2e2">
                    <path
                        d="M55.323,203.641c15.664,0,29.813-9.405,35.872-23.854c25.017-59.604,83.842-101.61,152.42-101.61 c37.797,0,72.449,12.955,100.23,34.442l-21.775,3.371c-7.438,1.153-13.224,7.054-14.232,14.512 c-1.01,7.454,3.008,14.686,9.867,17.768l119.746,53.872c5.249,2.357,11.33,1.904,16.168-1.205 c4.83-3.114,7.764-8.458,7.796-14.208l0.621-131.943c0.042-7.506-4.851-14.144-12.024-16.332 c-7.185-2.188-14.947,0.589-19.104,6.837l-16.505,24.805C370.398,26.778,310.1,0,243.615,0C142.806,0,56.133,61.562,19.167,149.06 c-5.134,12.128-3.84,26.015,3.429,36.987C29.865,197.023,42.152,203.641,55.323,203.641z">
                    </path>
                    <path
                        d="M464.635,301.184c-7.27-10.977-19.558-17.594-32.728-17.594c-15.664,0-29.813,9.405-35.872,23.854 c-25.018,59.604-83.843,101.61-152.42,101.61c-37.798,0-72.45-12.955-100.232-34.442l21.776-3.369 c7.437-1.153,13.223-7.055,14.233-14.514c1.009-7.453-3.008-14.686-9.867-17.768L49.779,285.089 c-5.25-2.356-11.33-1.905-16.169,1.205c-4.829,3.114-7.764,8.458-7.795,14.207l-0.622,131.943 c-0.042,7.506,4.85,14.144,12.024,16.332c7.185,2.188,14.948-0.59,19.104-6.839l16.505-24.805 c44.004,43.32,104.303,70.098,170.788,70.098c100.811,0,187.481-61.561,224.446-149.059 C473.197,326.043,471.903,312.157,464.635,301.184z">
                    </path>
                </svg>
            </button>
            <button class="w3-bar-item w3-button" onclick="openTab('Resources', this, getStats)">Resources</button>
            <button class="w3-bar-item w3-button" onclick="openTab('Logs', this, getLogs)">Logs</button>
            <button class="w3-bar-item w3-button" onclick="openTab('Calls', this, getCalls)">Calls</button>
            <button class="w3-bar-item w3-button" onclick="openTab('Descriptors', this, getDescriptors)">
                Descriptors
            </button>
        </div>
    </div>

    <div id="Resources" class="w3-container tab">
        <table>
            <tr>
                <td>Virtual Memory</td>
                <td id="memVirtual" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>RSS Memory</td>
                <td id="memRss" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>RSS Memory Peak</td>
                <td id="memRssPeak" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>Allocated bytes</td>
                <td id="memAllocatedBytes" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>Allocations</td>
                <td id="allocations" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>Open Descriptors</td>
                <td id="fdCount" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>Thread count</td>
                <td id="threadsCount" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>Uset Time</td>
                <td id="userTimeUs" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>System Time</td>
                <td id="systemTimeUs" class="stats-api-value"></td>
            </tr>
            <tr>
                <td>Start Time</td>
                <td id="startTimeMs" class="stats-api-value"></td>
            </tr>
        </table>
    </div>

    <div id="Logs" class="w3-container tab" style="display:none">
        <div id="logs" class="w3-code">
        </div>
    </div>

    <div id="Calls" class="w3-container tab" style="display:none">
        <table class="w3-table-all w3-bordered w3-striped w3-border test w3-hoverable">
            <tbody>
                <tr class="w3-green">
                    <th>Timestamp</th>
                    <th>Duration</th>
                    <th>URL</th>
                    <th>Result</th>
                </tr>
            </tbody>
            <tbody id="callsTable">
            </tbody>
        </table>
    </div>

    <div id="Descriptors" class="w3-container tab" style="display:none">
        <table class="w3-table-all w3-bordered w3-striped w3-border test w3-hoverable">
            <tbody>
                <tr class="w3-green">
                    <th>#</th>
                    <th>Type</th>
                    <th>Details</th>
                </tr>
            </tbody>
            <tbody id="descriptorTable">
            </tbody>
        </table>
    </div>

    <script>
        function getStats() {
            fetch('/groundwork/stats')
                .then(c => c.json())
                .then(c => {
                    const fields = document.getElementsByClassName("stats-api-value");
                    for (const filed of fields) {
                        if (filed.id.startsWith('mem')) {
                            filed.innerText = humanSize(c[filed.id]);
                        } else if (filed.id.endsWith('TimeMs')) {
                            filed.innerText = humanTimestampMs(c[filed.id]);
                        } else if (filed.id.endsWith('TimeUs')) {
                            filed.innerText = humanDurationUs(c[filed.id]);
                        } else {
                            filed.innerText = c[filed.id];
                        }
                    }
                });
        }

        function getLogs() {
            fetch('/groundwork/logs')
                .then(c => c.json())
                .then(c => {
                    document.getElementById("logs").innerText = c.map(l => l.trim()).join('\n');
                });
        }

        function getCalls() {
            const convertResponse = (response) => {
                if (response.ok) {
                    return `<span class="w3-green">OK length: ${response.ok.length}</span>`;
                }
                return `<span class="w3-red">ERROR code: ${response.error.code}</span>`;
            };

            fetch('/groundwork/calls')
                .then(c => c.json())
                .then(cs => {
                    document.getElementById("callsTable").innerHTML =
                        cs.map(c => `<tr><td>${humanTimestampMs(c.timestampMs)}</td><td>${humanDurationUs(c.durationUs)}</td><td>${c.path}</td><td>${convertResponse(c.response)}</td><tr>`)
                            .join('');
                });
        }

        function getDescriptors() {
            fetch('/groundwork/descriptors')
                .then(c => c.json())
                .then(ds => {
                    document.getElementById("descriptorTable").innerHTML =
                        ds.map(d => `<tr><td>${d.n}</td><td>${d.kind}</td><td>${d.details}</td><tr>`)
                            .join('');
                });
        }

        window.onload = getStats;
        currentFunction = getStats

        function refreshCurrent() {
            currentFunction()
        }

        function openTab(name, button, func) {
            func()
            currentFunction = func
            const bar = document.getElementById("tab-bar");
            for (const item of bar.children) {
                item.classList.remove("w3-gray")
            }
            button.classList.add("w3-gray")
            const tabs = document.getElementsByClassName("tab");
            for (const tab of tabs) {
                tab.style.display = "none";
            }
            document.getElementById(name).style.display = "block";
        }

        // from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
        function humanSize(size) {
            var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
            return +((size / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
        }

        function humanDurationUs(us) {
            const hours = us / 3600_000_000;
            const seconds = (us / 1000_000).toFixed(3);
            if (hours > 1) {
                return `${hours} hours (${seconds} seconds)`
            }
            if (seconds > 0) {
                return `${seconds} seconds`;
            }
            return `${us} µs`;
        }

        function humanTimestampMs(ms) {
            return new Date(ms).toISOString();
        }

    </script>
</body>

</html>