mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
Add Shardok profiling admin controls (#8755)
This commit is contained in:
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
shardokpb "github.com/nolen777/eagle0/src/main/protobuf/net/eagle0/common/shardok_internal_interface"
|
||||
@@ -75,3 +77,23 @@ func TestShardokProfileEndpointsRequireExpectedMethod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLayoutTemplateIncludesShardokProfileControls(t *testing.T) {
|
||||
parsed, err := template.ParseFS(templatesFS, "templates/layout.html", "templates/games.html")
|
||||
if err != nil {
|
||||
t.Fatalf("parse layout: %v", err)
|
||||
}
|
||||
if parsed.Lookup("layout.html") == nil {
|
||||
t.Fatal("layout template was not parsed")
|
||||
}
|
||||
|
||||
layout, err := templatesFS.ReadFile("templates/layout.html")
|
||||
if err != nil {
|
||||
t.Fatalf("read layout: %v", err)
|
||||
}
|
||||
for _, expected := range []string{"shardok-profile-controls", "shardok-profile-dialog"} {
|
||||
if !strings.Contains(string(layout), expected) {
|
||||
t.Fatalf("layout does not include %q", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +496,8 @@ dialog .modal-actions button {
|
||||
}
|
||||
|
||||
/* JFR controls in nav */
|
||||
#jfr-controls {
|
||||
#jfr-controls,
|
||||
#shardok-profile-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
@@ -559,7 +560,8 @@ dialog .modal-actions button {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
#jfr-controls .btn-small {
|
||||
#jfr-controls .btn-small,
|
||||
#shardok-profile-controls .btn-small {
|
||||
padding: 0.25rem 0.5rem !important;
|
||||
font-size: 0.75rem;
|
||||
height: auto !important;
|
||||
@@ -568,19 +570,22 @@ dialog .modal-actions button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#jfr-controls button.btn-small {
|
||||
#jfr-controls button.btn-small,
|
||||
#shardok-profile-controls button.btn-small {
|
||||
background: var(--pico-primary-background);
|
||||
color: var(--pico-primary-inverse);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#jfr-controls button.btn-small.secondary {
|
||||
#jfr-controls button.btn-small.secondary,
|
||||
#shardok-profile-controls button.btn-small.secondary {
|
||||
background: var(--pico-secondary-background);
|
||||
color: var(--pico-secondary-inverse);
|
||||
}
|
||||
|
||||
#jfr-controls a.btn-small {
|
||||
#jfr-controls a.btn-small,
|
||||
#shardok-profile-controls a.btn-small {
|
||||
background: var(--pico-primary-background);
|
||||
color: var(--pico-primary-inverse);
|
||||
border-radius: var(--pico-border-radius);
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<li id="jfr-controls" hx-get="/jfr/status" hx-trigger="load" hx-swap="innerHTML">
|
||||
<span class="jfr-loading">JFR: ...</span>
|
||||
</li>
|
||||
<li id="shardok-profile-controls" hx-get="/shardok-profile/status" hx-trigger="load, every 5s" hx-swap="none">
|
||||
<span class="jfr-loading">Shardok CPU: ...</span>
|
||||
</li>
|
||||
<li>
|
||||
<button onclick="confirmEagleRestart()" class="btn-small secondary outline">Restart Eagle</button>
|
||||
<span id="eagle-restart-result"></span>
|
||||
@@ -47,6 +50,20 @@
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
<!-- Shardok CPU Profile Dialog -->
|
||||
<dialog id="shardok-profile-dialog">
|
||||
<article style="max-width: 440px;">
|
||||
<h3>Record Shardok CPU Profile</h3>
|
||||
<p>Choose a bounded recording duration. The recording stops automatically.</p>
|
||||
<footer style="display: flex; gap: 0.5rem; justify-content: flex-end;">
|
||||
<button class="secondary outline" onclick="document.getElementById('shardok-profile-dialog').close()">Cancel</button>
|
||||
<button class="secondary" onclick="startShardokProfile(30)">30 sec</button>
|
||||
<button class="secondary" onclick="startShardokProfile(60)">60 sec</button>
|
||||
<button onclick="startShardokProfile(120)">120 sec</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
<!-- JFR Stop Dialog -->
|
||||
<dialog id="jfr-stop-dialog">
|
||||
<article style="max-width: 400px;">
|
||||
@@ -87,6 +104,16 @@
|
||||
// Refresh status on error
|
||||
htmx.ajax('GET', '/jfr/status', '#jfr-controls');
|
||||
}
|
||||
} else if (evt.detail.pathInfo.requestPath.startsWith('/shardok-profile/')) {
|
||||
if (evt.detail.pathInfo.requestPath === '/shardok-profile/download') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(evt.detail.xhr.responseText);
|
||||
updateShardokProfileControls(data);
|
||||
} catch(e) {
|
||||
updateShardokProfileControls({error: evt.detail.xhr.responseText || e.message});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -139,6 +166,59 @@
|
||||
document.getElementById('jfr-stop-dialog').close();
|
||||
}
|
||||
|
||||
function updateShardokProfileControls(data) {
|
||||
const container = document.getElementById('shardok-profile-controls');
|
||||
if (data.error) {
|
||||
container.innerHTML = `<span class="jfr-error" title="${escapeHtml(data.error)}">Shardok CPU: Error</span>`;
|
||||
return;
|
||||
}
|
||||
if (data.status === 'disabled') {
|
||||
container.innerHTML = '<span class="jfr-status jfr-off">Shardok CPU: OFF</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.recording) {
|
||||
container.innerHTML = `
|
||||
<span class="jfr-status jfr-on">Shardok CPU: ON (${data.duration_seconds}s)</span>
|
||||
<button onclick="stopShardokProfile()" class="btn-small secondary">Stop</button>
|
||||
`;
|
||||
} else {
|
||||
const download = data.profile_available
|
||||
? '<a href="/shardok-profile/download" class="btn-small">Download</a>'
|
||||
: '';
|
||||
container.innerHTML = `
|
||||
<span class="jfr-status jfr-off">Shardok CPU: ${data.profile_available ? 'READY' : 'OFF'}</span>
|
||||
<button onclick="showShardokProfileDialog()" class="btn-small">Start</button>
|
||||
${download}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
const element = document.createElement('span');
|
||||
element.textContent = value || '';
|
||||
return element.innerHTML;
|
||||
}
|
||||
|
||||
function showShardokProfileDialog() {
|
||||
document.getElementById('shardok-profile-dialog').showModal();
|
||||
}
|
||||
|
||||
function startShardokProfile(durationSeconds) {
|
||||
document.getElementById('shardok-profile-dialog').close();
|
||||
htmx.ajax('POST', `/shardok-profile/start?duration=${durationSeconds}`, {
|
||||
target: '#shardok-profile-controls',
|
||||
swap: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
function stopShardokProfile() {
|
||||
htmx.ajax('POST', '/shardok-profile/stop', {
|
||||
target: '#shardok-profile-controls',
|
||||
swap: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
function confirmEagleRestart() {
|
||||
document.getElementById('eagle-restart-dialog').showModal();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user