Skip to content

Commit 4f9bcdf

Browse files
committed
Enh(batch monitor): Various improvements
1 parent efa8a1c commit 4f9bcdf

3 files changed

Lines changed: 117 additions & 26 deletions

File tree

codeclash/viewer/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1384,7 +1384,8 @@ def batch_api_jobs():
13841384
hours_back = request.args.get("hours_back", default=24, type=int)
13851385
monitor = AWSBatchMonitor(job_queue="codeclash-queue", region="us-east-1", logs_base_dir=LOG_BASE_DIR)
13861386
jobs = monitor.get_formatted_jobs(hours_back=hours_back)
1387-
return jsonify({"success": True, "jobs": jobs})
1387+
total_cpus = monitor.get_total_cpus_running()
1388+
return jsonify({"success": True, "jobs": jobs, "total_cpus_running": total_cpus})
13881389
except Exception as e:
13891390
logger.error(f"Error fetching AWS Batch jobs: {e}", exc_info=True)
13901391
return jsonify({"success": False, "error": str(e)}), 500

codeclash/viewer/app_aws.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,52 @@ def get_formatted_jobs(self, *, limit: int | None = None, hours_back: int = 24)
221221
"""
222222
jobs = self.list_jobs(limit=limit, hours_back=hours_back)
223223
return [self.format_job_for_display(job) for job in jobs]
224+
225+
def get_total_cpus_running(self) -> int:
226+
"""Get total number of vCPUs currently allocated in the compute environment"""
227+
try:
228+
# Get the job queue details to find the compute environment
229+
queue_response = self.batch_client.describe_job_queues(jobQueues=[self.job_queue])
230+
231+
if not queue_response.get("jobQueues"):
232+
return 0
233+
234+
# Get compute environments from the job queue
235+
compute_env_orders = queue_response["jobQueues"][0].get("computeEnvironmentOrder", [])
236+
237+
if not compute_env_orders:
238+
return 0
239+
240+
total_vcpus = 0
241+
242+
# Get details for each compute environment
243+
for env_order in compute_env_orders:
244+
compute_env_name = env_order.get("computeEnvironment")
245+
if not compute_env_name:
246+
continue
247+
248+
# Extract just the name from the ARN if needed
249+
env_name = compute_env_name.split("/")[-1]
250+
251+
env_response = self.batch_client.describe_compute_environments(computeEnvironments=[env_name])
252+
253+
for env in env_response.get("computeEnvironments", []):
254+
# Get the actual allocated vCPUs from the compute resources
255+
compute_resources = env.get("computeResources", {})
256+
257+
# Use desiredvCpus if available (what's currently allocated)
258+
# Otherwise fall back to maxvCpus
259+
desired_vcpus = compute_resources.get("desiredvCpus")
260+
if desired_vcpus is not None:
261+
total_vcpus += desired_vcpus
262+
else:
263+
# If desired is not available, we can't get current allocation
264+
# This might happen with FARGATE environments
265+
max_vcpus = compute_resources.get("maxvCpus", 0)
266+
total_vcpus += max_vcpus
267+
268+
return total_vcpus
269+
270+
except Exception as e:
271+
logger.warning(f"Failed to get vCPU information from compute environment: {e}", exc_info=True)
272+
return 0

codeclash/viewer/templates/batch.html

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
font-size: 0.875rem;
112112
text-transform: uppercase;
113113
letter-spacing: 0.05em;
114+
white-space: nowrap;
114115
}
115116

116117
.batch-table td {
@@ -124,6 +125,11 @@
124125
text-align: right;
125126
}
126127

128+
.batch-table td.job-name-cell,
129+
.batch-table td.job-id-cell {
130+
white-space: normal;
131+
}
132+
127133
.batch-table tbody tr {
128134
transition: all 0.2s ease;
129135
border-left: 3px solid transparent;
@@ -173,8 +179,18 @@
173179
.job-id,
174180
.job-name {
175181
font-family: 'Courier New', monospace;
176-
font-size: 0.9rem;
182+
font-size: 0.7rem;
177183
text-align: left;
184+
word-break: break-all;
185+
white-space: normal;
186+
}
187+
188+
.job-name {
189+
max-width: 40ch;
190+
}
191+
192+
.job-id {
193+
max-width: 20ch;
178194
}
179195

180196
.copy-cell {
@@ -510,14 +526,6 @@
510526
accent-color: var(--accent-color);
511527
}
512528

513-
.search-hidden {
514-
position: absolute;
515-
left: -9999px;
516-
width: 1px;
517-
height: 1px;
518-
overflow: hidden;
519-
}
520-
521529
.update-info {
522530
color: var(--text-muted);
523531
font-size: 0.85rem;
@@ -593,6 +601,30 @@
593601
color: var(--text-primary);
594602
}
595603

604+
.cpu-counter {
605+
background: var(--bg-tertiary);
606+
padding: 0.75rem;
607+
border-radius: 0.375rem;
608+
border-left: 3px solid var(--accent-color);
609+
display: flex;
610+
flex-direction: column;
611+
gap: 0.25rem;
612+
}
613+
614+
.cpu-counter-label {
615+
font-size: 0.75rem;
616+
text-transform: uppercase;
617+
color: var(--text-secondary);
618+
font-weight: 600;
619+
letter-spacing: 0.05em;
620+
}
621+
622+
.cpu-counter-value {
623+
font-size: 1.5rem;
624+
font-weight: 700;
625+
color: var(--accent-color);
626+
}
627+
596628
.rounds-count {
597629
display: inline-block;
598630
background-color: var(--accent-color);
@@ -715,12 +747,14 @@ <h2>Bulk Actions</h2>
715747
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
716748
<script>
717749
let allJobs = [];
750+
// Default sort: most recently submitted jobs first (descending by timestamp)
718751
let currentSort = { column: 'created_at', ascending: false };
719752
let statusFilter = '';
720753
let roundsFilter = 'any';
721754
let timeRangeHours = 24;
722755
let lastRefreshTime = null;
723756
let selectedJobs = new Set();
757+
let totalCpusRunning = 0;
724758

725759
function updateStatusCounter() {
726760
const statusCounts = {
@@ -742,6 +776,14 @@ <h2>Bulk Actions</h2>
742776
const grid = document.getElementById('status-counter-grid');
743777
let html = '';
744778

779+
// Add CPU counter first
780+
html += `
781+
<div class="cpu-counter">
782+
<div class="cpu-counter-label"><i class="bi bi-cpu"></i> CPUs Running</div>
783+
<div class="cpu-counter-value">${totalCpusRunning} vCPUs</div>
784+
</div>
785+
`;
786+
745787
Object.keys(statusCounts).forEach(status => {
746788
const count = statusCounts[status];
747789
html += `
@@ -762,6 +804,7 @@ <h2>Bulk Actions</h2>
762804

763805
if (data.success) {
764806
allJobs = data.jobs;
807+
totalCpusRunning = data.total_cpus_running || 0;
765808
updateStatusCounter();
766809
renderJobs();
767810
clearError();
@@ -869,7 +912,7 @@ <h2>Bulk Actions</h2>
869912
<input type="checkbox" class="job-checkbox" id="master-checkbox" onchange="toggleAllCheckboxes()" title="Toggle all">
870913
</th>
871914
<th class="sortable" onclick="sortBy('created_at')">
872-
Time <i class="bi bi-arrow-down-up sort-icon"></i>
915+
Submitted <i class="bi bi-arrow-down-up sort-icon"></i>
873916
</th>
874917
<th>Status</th>
875918
<th class="sortable" onclick="sortBy('time_running_seconds')">
@@ -893,10 +936,6 @@ <h2>Bulk Actions</h2>
893936
const escapedJobId = job.job_id.replace(/'/g, "\\'");
894937
const isChecked = selectedJobs.has(job.job_id) ? 'checked' : '';
895938

896-
// Truncate job name and ID to 30 characters for display
897-
const displayJobName = job.job_name.length > 30 ? job.job_name.substring(0, 30) + '...' : job.job_name;
898-
const displayJobId = job.job_id.length > 30 ? job.job_id.substring(0, 30) + '...' : job.job_id;
899-
900939
let roundsHtml = '<span class="rounds-unknown">-</span>';
901940
if (job.round_info && Array.isArray(job.round_info) && job.round_info.length === 2) {
902941
const completed = job.round_info[0];
@@ -914,23 +953,17 @@ <h2>Bulk Actions</h2>
914953
<td><span class="status-tag status-${job.status}">${job.status}</span></td>
915954
<td class="time-running">${job.time_running || '-'}</td>
916955
<td>${roundsHtml}</td>
917-
<td>
956+
<td class="job-name-cell">
918957
<div class="copy-cell">
919-
<span class="job-name" title="${escapedJobName}">
920-
${displayJobName}
921-
<span class="search-hidden">${job.job_name}</span>
922-
</span>
958+
<span class="job-name">${job.job_name}</span>
923959
<button class="copy-btn" onclick="copyToClipboard('${escapedJobName}', this)" title="Copy job name">
924960
<i class="bi bi-clipboard"></i>
925961
</button>
926962
</div>
927963
</td>
928-
<td>
964+
<td class="job-id-cell">
929965
<div class="copy-cell">
930-
<span class="job-id" title="${escapedJobId}">
931-
${displayJobId}
932-
<span class="search-hidden">${job.job_id}</span>
933-
</span>
966+
<span class="job-id">${job.job_id}</span>
934967
<button class="copy-btn" onclick="copyToClipboard('${escapedJobId}', this)" title="Copy job ID">
935968
<i class="bi bi-clipboard"></i>
936969
</button>
@@ -962,7 +995,7 @@ <h2>Bulk Actions</h2>
962995
currentSort.ascending = !currentSort.ascending;
963996
} else {
964997
currentSort.column = column;
965-
// Default to descending for timestamps and time_running
998+
// Default to descending for timestamps and time_running (most recent/longest first)
966999
currentSort.ascending = (column === 'created_at' || column === 'time_running_seconds') ? false : true;
9671000
}
9681001
renderJobs();
@@ -1215,7 +1248,15 @@ <h2>Bulk Actions</h2>
12151248
});
12161249
}
12171250

1251+
// Initialize filter variables from dropdown values (in case browser restored them)
1252+
function initializeFilters() {
1253+
statusFilter = document.getElementById('status-filter').value;
1254+
roundsFilter = document.getElementById('rounds-filter').value;
1255+
timeRangeHours = parseInt(document.getElementById('time-range-filter').value);
1256+
}
1257+
12181258
// Initial fetch
1259+
initializeFilters();
12191260
updateStatusCounterTitle();
12201261
fetchJobs();
12211262

0 commit comments

Comments
 (0)