From 6a43dfb0d7bc15333f7a51c8cc1e4b408fd9a9af Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 14 Oct 2025 18:43:00 +0200 Subject: [PATCH 1/5] Fix missing model.Aggregate entry, fix status queries and refresh --- internal/repository/stats.go | 9 +++--- web/frontend/src/status/StatisticsDash.svelte | 11 +++---- web/frontend/src/status/StatusDash.svelte | 6 ++++ web/frontend/src/status/UsageDash.svelte | 29 ++++++++++--------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/internal/repository/stats.go b/internal/repository/stats.go index 19d17bd..825033d 100644 --- a/internal/repository/stats.go +++ b/internal/repository/stats.go @@ -21,9 +21,10 @@ import ( // GraphQL validation should make sure that no unkown values can be specified. var groupBy2column = map[model.Aggregate]string{ - model.AggregateUser: "job.hpc_user", - model.AggregateProject: "job.project", - model.AggregateCluster: "job.hpc_cluster", + model.AggregateUser: "job.hpc_user", + model.AggregateProject: "job.project", + model.AggregateCluster: "job.hpc_cluster", + model.AggregateSubcluster: "job.subcluster", } var sortBy2column = map[model.SortByAggregate]string{ @@ -176,7 +177,7 @@ func (r *JobRepository) JobsStatsGrouped( var name sql.NullString var jobs, users, walltime, nodes, nodeHours, cores, coreHours, accs, accHours sql.NullInt64 if err := rows.Scan(&id, &name, &jobs, &users, &walltime, &nodes, &nodeHours, &cores, &coreHours, &accs, &accHours); err != nil { - cclog.Warn("Error while scanning rows") + cclog.Warnf("Error while scanning rows: %s", err.Error()) return nil, err } diff --git a/web/frontend/src/status/StatisticsDash.svelte b/web/frontend/src/status/StatisticsDash.svelte index f299439..fb2161b 100644 --- a/web/frontend/src/status/StatisticsDash.svelte +++ b/web/frontend/src/status/StatisticsDash.svelte @@ -43,8 +43,6 @@ let cluster = $state(presetCluster); // Histogram let isHistogramSelectionOpen = $state(false); - let from = $state(new Date(Date.now() - (30 * 24 * 60 * 60 * 1000))); // Simple way to retrigger GQL: Jobs Started last Month - let to = $state(new Date(Date.now())); /* Derived */ let selectedHistograms = $derived(cluster @@ -74,11 +72,11 @@ } `, variables: { - filter: [{ state: ["running"] }, { cluster: { eq: cluster}}, {startTime: { from, to }}], - selectedHistograms: selectedHistograms, + filter: [{ state: ["running"] }, { cluster: { eq: cluster} }], + selectedHistograms: selectedHistograms }, + requestPolicy: "network-only" })); - @@ -96,8 +94,7 @@ { - from = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000)); // Triggers GQL - to = new Date(Date.now()); + selectedHistograms = [...$state.snapshot(selectedHistograms)] }} /> diff --git a/web/frontend/src/status/StatusDash.svelte b/web/frontend/src/status/StatusDash.svelte index 280b04b..5c679a3 100644 --- a/web/frontend/src/status/StatusDash.svelte +++ b/web/frontend/src/status/StatusDash.svelte @@ -185,6 +185,7 @@ paging: { itemsPerPage: -1, page: 1 }, // Get all: -1 sorting: { field: "startTime", type: "col", order: "DESC" } }, + requestPolicy: "network-only" })); /* Effects */ @@ -233,6 +234,10 @@ } }); + $inspect('From', from) + $inspect('To', to) + $inspect('Query', statusQuery) + /* Const Functions */ const sumUp = (data, subcluster, metric) => data.reduce( @@ -363,6 +368,7 @@ { + console.log('Trigger Refresh StatusTab') from = new Date(Date.now() - 5 * 60 * 1000); to = new Date(Date.now()); }} diff --git a/web/frontend/src/status/UsageDash.svelte b/web/frontend/src/status/UsageDash.svelte index 16575e4..3b39e55 100644 --- a/web/frontend/src/status/UsageDash.svelte +++ b/web/frontend/src/status/UsageDash.svelte @@ -47,8 +47,8 @@ /* State Init */ let cluster = $state(presetCluster) - let from = $state(new Date(Date.now() - (30 * 24 * 60 * 60 * 1000))); // Simple way to retrigger GQL: Jobs Started last Month - let to = $state(new Date(Date.now())); + let pagingState = $state({page: 1, itemsPerPage: 10}) // Top 10 + let selectedHistograms = $state([]) // Dummy For Refresh let colWidthJobs = $state(0); let colWidthNodes = $state(0); let colWidthAccs = $state(0); @@ -84,9 +84,10 @@ } `, variables: { - filter: [{ state: ["running"] }, { cluster: { eq: cluster}}, {startTime: { from, to }}], - paging: { itemsPerPage: 10, page: 1 } // Top 10 + filter: [{ state: ["running"] }, { cluster: { eq: cluster} }], + paging: pagingState // Top 10 }, + requestPolicy: "network-only" })); const topNodesQuery = $derived(queryStore({ @@ -118,9 +119,10 @@ } `, variables: { - filter: [{ state: ["running"] }, { cluster: { eq: cluster }}, {startTime: { from, to }}], - paging: { itemsPerPage: 10, page: 1 } // Top 10 + filter: [{ state: ["running"] }, { cluster: { eq: cluster } }], + paging: pagingState }, + requestPolicy: "network-only" })); const topAccsQuery = $derived(queryStore({ @@ -152,9 +154,10 @@ } `, variables: { - filter: [{ state: ["running"] }, { cluster: { eq: cluster }}, {startTime: { from, to }}], - paging: { itemsPerPage: 10, page: 1 } // Top 10 + filter: [{ state: ["running"] }, { cluster: { eq: cluster } }], + paging: pagingState }, + requestPolicy: "network-only" })); // Note: nodeMetrics are requested on configured $timestep resolution @@ -183,10 +186,11 @@ } `, variables: { - filter: [{ state: ["running"] }, { cluster: { eq: cluster }}, {startTime: { from, to }}], - selectedHistograms: [], // No Metrics requested for node hardware stats + filter: [{ state: ["running"] }, { cluster: { eq: cluster } }], + selectedHistograms: selectedHistograms, // No Metrics requested for node hardware stats numDurationBins: numDurationBins, }, + requestPolicy: "network-only" })); /* Functions */ @@ -202,7 +206,6 @@ } return c[(c.length + targetIdx) % c.length]; } - @@ -226,8 +229,8 @@ { - from = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000)); // Triggers GQL - to = new Date(Date.now()); + pagingState = { page:1, itemsPerPage: 10 }; + selectedHistograms = [...$state.snapshot(selectedHistograms)]; }} /> From 413166528473a91154e3f8a734d5a03509f21eff Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 14 Oct 2025 18:43:16 +0200 Subject: [PATCH 2/5] remove gql auto comment --- internal/graph/schema.resolvers.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index cbe3650..d4c4c8c 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -854,15 +854,3 @@ type mutationResolver struct{ *Resolver } type nodeResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type subClusterResolver struct{ *Resolver } - -// !!! WARNING !!! -// The code below was going to be deleted when updating resolvers. It has been copied here so you have -// one last chance to move it out of harms way if you want. There are two reasons this happens: -// - When renaming or deleting a resolver the old code will be put in here. You can safely delete -// it when you're done. -// - You have helper methods in this file. Move them out to keep these resolver files clean. -/* - func (r *jobResolver) Exclusive(ctx context.Context, obj *schema.Job) (int, error) { - panic(fmt.Errorf("not implemented: Exclusive - exclusive")) -} -*/ From 5908ae790596625ae0457935cabd25ab4a46e8f9 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 14 Oct 2025 18:45:05 +0200 Subject: [PATCH 3/5] adapt status node query resolution to new node_state table --- internal/repository/node.go | 38 +++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/internal/repository/node.go b/internal/repository/node.go index 80ec573..620b8f4 100644 --- a/internal/repository/node.go +++ b/internal/repository/node.go @@ -49,11 +49,8 @@ func GetNodeRepository() *NodeRepository { return nodeRepoInstance } -var nodeColumns []string = []string{ - // "node.id," - "node.hostname", "node.cluster", "node.subcluster", - "node.node_state", "node.health_state", // "node.meta_data", -} +// "node.id,", "node.meta_data" +var nodeColumns []string = []string{"node.hostname", "node.cluster", "node.subcluster"} func (r *NodeRepository) FetchMetadata(node *schema.Node) (map[string]string, error) { start := time.Now() @@ -195,6 +192,7 @@ const NamedNodeStateInsert string = ` INSERT INTO node (hostname, cluster, subcluster) VALUES (:hostname, :cluster, :subcluster);` +// Outdated? func (r *NodeRepository) UpdateNodeState(hostname string, cluster string, nodeState *schema.NodeState) error { var id int64 @@ -262,6 +260,10 @@ func (r *NodeRepository) QueryNodes( return nil, qerr } + // Get latest Info aka closest Timestamp to $now + now := time.Now().Unix() + query = query.Join("node_state ON node_state.node_id = node.id").Where(sq.Gt{"node_state.time_stamp": (now - 60)}) // .Distinct() + for _, f := range filters { if f.Hostname != nil { query = buildStringCondition("node.hostname", f.Hostname, query) @@ -304,12 +306,16 @@ func (r *NodeRepository) QueryNodes( } func (r *NodeRepository) ListNodes(cluster string) ([]*schema.Node, error) { - q := sq.Select("hostname", "cluster", "subcluster", "node_state", - "health_state").From("node").Where("node.cluster = ?", cluster).OrderBy("node.hostname ASC") + // Get latest Info aka closest Timestamo to $now + now := time.Now().Unix() + q := sq.Select("hostname", "cluster", "subcluster", "node_state", "health_state"). + From("node"). + Join("node_state ON node_state.node_id = node.id").Where(sq.Gt{"node_state.time_stamp": (now - 60)}). + Where("node.cluster = ?", cluster).OrderBy("node.hostname ASC") rows, err := q.RunWith(r.DB).Query() if err != nil { - cclog.Warn("Error while querying user list") + cclog.Warn("Error while querying node list") return nil, err } nodeList := make([]*schema.Node, 0, 100) @@ -329,11 +335,15 @@ func (r *NodeRepository) ListNodes(cluster string) ([]*schema.Node, error) { } func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { - query, qerr := AccessCheck(ctx, sq.Select("node_state AS state", "count(*) AS count").From("node")) + query, qerr := AccessCheck(ctx, sq.Select("node_state", "count(*) AS count").From("node")) if qerr != nil { return nil, qerr } + // Get latest Info aka closest Timestamp to $now + now := time.Now().Unix() + query = query.Join("node_state ON node_state.node_id = node.id").Where(sq.Gt{"node_state.time_stamp": (now - 60)}) // .Distinct() + for _, f := range filters { if f.Hostname != nil { query = buildStringCondition("node.hostname", f.Hostname, query) @@ -353,7 +363,7 @@ func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.N } // Add Group and Order - query = query.GroupBy("state").OrderBy("count DESC") + query = query.GroupBy("node_state").OrderBy("count DESC") rows, err := query.RunWith(r.stmtCache).Query() if err != nil { @@ -378,11 +388,15 @@ func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.N } func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { - query, qerr := AccessCheck(ctx, sq.Select("health_state AS state", "count(*) AS count").From("node")) + query, qerr := AccessCheck(ctx, sq.Select("health_state", "count(*) AS count").From("node")) if qerr != nil { return nil, qerr } + // Get latest Info aka closest Timestamp to $now + now := time.Now().Unix() + query = query.Join("node_state ON node_state.node_id = node.id").Where(sq.Gt{"node_state.time_stamp": (now - 60)}) // .Distinct() + for _, f := range filters { if f.Hostname != nil { query = buildStringCondition("node.hostname", f.Hostname, query) @@ -402,7 +416,7 @@ func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model } // Add Group and Order - query = query.GroupBy("state").OrderBy("count DESC") + query = query.GroupBy("health_state").OrderBy("count DESC") rows, err := query.RunWith(r.stmtCache).Query() if err != nil { From 845905d9c85ecdc7ef945f21f5441bcd42095b6d Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Wed, 15 Oct 2025 10:35:35 +0200 Subject: [PATCH 4/5] remove inspect commands for dev --- web/frontend/src/status/StatusDash.svelte | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/frontend/src/status/StatusDash.svelte b/web/frontend/src/status/StatusDash.svelte index 5c679a3..9579c0f 100644 --- a/web/frontend/src/status/StatusDash.svelte +++ b/web/frontend/src/status/StatusDash.svelte @@ -234,10 +234,6 @@ } }); - $inspect('From', from) - $inspect('To', to) - $inspect('Query', statusQuery) - /* Const Functions */ const sumUp = (data, subcluster, metric) => data.reduce( From cda10788fb97d60c200dab0edb1251e966d2b248 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Wed, 15 Oct 2025 10:46:24 +0200 Subject: [PATCH 5/5] adapt migrated indices to new database structure, include node tables, update job indices --- .../migrations/sqlite3/10_node-table.up.sql | 103 +++++++++++++++++- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/internal/repository/migrations/sqlite3/10_node-table.up.sql b/internal/repository/migrations/sqlite3/10_node-table.up.sql index 2291aea..943ca4f 100644 --- a/internal/repository/migrations/sqlite3/10_node-table.up.sql +++ b/internal/repository/migrations/sqlite3/10_node-table.up.sql @@ -32,14 +32,105 @@ CREATE TABLE "node_state" ( FOREIGN KEY (node_id) REFERENCES node (id) ); --- Add Indices For New Node Table VARCHAR Fields +-- DROP indices using old column name "cluster" +DROP INDEX IF EXISTS jobs_cluster; +DROP INDEX IF EXISTS jobs_cluster_user; +DROP INDEX IF EXISTS jobs_cluster_project; +DROP INDEX IF EXISTS jobs_cluster_subcluster; +DROP INDEX IF EXISTS jobs_cluster_starttime; +DROP INDEX IF EXISTS jobs_cluster_duration; +DROP INDEX IF EXISTS jobs_cluster_numnodes; +DROP INDEX IF EXISTS jobs_cluster_numhwthreads; +DROP INDEX IF EXISTS jobs_cluster_numacc; +DROP INDEX IF EXISTS jobs_cluster_energy; +DROP INDEX IF EXISTS jobs_cluster_partition; +DROP INDEX IF EXISTS jobs_cluster_partition_starttime; +DROP INDEX IF EXISTS jobs_cluster_partition_duration; +DROP INDEX IF EXISTS jobs_cluster_partition_numnodes; +DROP INDEX IF EXISTS jobs_cluster_partition_numhwthreads; +DROP INDEX IF EXISTS jobs_cluster_partition_numacc; +DROP INDEX IF EXISTS jobs_cluster_partition_energy; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_user; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_project; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_starttime; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_duration; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_numnodes; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_numhwthreads; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_numacc; +DROP INDEX IF EXISTS jobs_cluster_partition_jobstate_energy; +DROP INDEX IF EXISTS jobs_cluster_jobstate; +DROP INDEX IF EXISTS jobs_cluster_jobstate_user; +DROP INDEX IF EXISTS jobs_cluster_jobstate_project; +DROP INDEX IF EXISTS jobs_cluster_jobstate_starttime; +DROP INDEX IF EXISTS jobs_cluster_jobstate_duration; +DROP INDEX IF EXISTS jobs_cluster_jobstate_numnodes; +DROP INDEX IF EXISTS jobs_cluster_jobstate_numhwthreads; +DROP INDEX IF EXISTS jobs_cluster_jobstate_numacc; +DROP INDEX IF EXISTS jobs_cluster_jobstate_energy; + +-- -- CREATE UPDATED indices with new column names +-- Cluster Filter +CREATE INDEX IF NOT EXISTS jobs_cluster ON job (hpc_cluster); +CREATE INDEX IF NOT EXISTS jobs_cluster_user ON job (hpc_cluster, hpc_user); +CREATE INDEX IF NOT EXISTS jobs_cluster_project ON job (hpc_cluster, project); +CREATE INDEX IF NOT EXISTS jobs_cluster_subcluster ON job (hpc_cluster, subcluster); +-- Cluster Filter Sorting +CREATE INDEX IF NOT EXISTS jobs_cluster_starttime ON job (hpc_cluster, start_time); +CREATE INDEX IF NOT EXISTS jobs_cluster_duration ON job (hpc_cluster, duration); +CREATE INDEX IF NOT EXISTS jobs_cluster_numnodes ON job (hpc_cluster, num_nodes); +CREATE INDEX IF NOT EXISTS jobs_cluster_numhwthreads ON job (hpc_cluster, num_hwthreads); +CREATE INDEX IF NOT EXISTS jobs_cluster_numacc ON job (hpc_cluster, num_acc); +CREATE INDEX IF NOT EXISTS jobs_cluster_energy ON job (hpc_cluster, energy); +-- Cluster+Partition Filter +CREATE INDEX IF NOT EXISTS jobs_cluster_partition ON job (hpc_cluster, cluster_partition); +-- Cluster+Partition Filter Sorting +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_starttime ON job (hpc_cluster, cluster_partition, start_time); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_duration ON job (hpc_cluster, cluster_partition, duration); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_numnodes ON job (hpc_cluster, cluster_partition, num_nodes); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_numhwthreads ON job (hpc_cluster, cluster_partition, num_hwthreads); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_numacc ON job (hpc_cluster, cluster_partition, num_acc); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_energy ON job (hpc_cluster, cluster_partition, energy); +-- Cluster+Partition+Jobstate Filter +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate ON job (hpc_cluster, cluster_partition, job_state); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_user ON job (hpc_cluster, cluster_partition, job_state, hpc_user); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_project ON job (hpc_cluster, cluster_partition, job_state, project); +-- Cluster+Partition+Jobstate Filter Sorting +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_starttime ON job (hpc_cluster, cluster_partition, job_state, start_time); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_duration ON job (hpc_cluster, cluster_partition, job_state, duration); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_numnodes ON job (hpc_cluster, cluster_partition, job_state, num_nodes); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_numhwthreads ON job (hpc_cluster, cluster_partition, job_state, num_hwthreads); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_numacc ON job (hpc_cluster, cluster_partition, job_state, num_acc); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_energy ON job (hpc_cluster, cluster_partition, job_state, energy); +-- Cluster+JobState Filter +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate ON job (hpc_cluster, job_state); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_user ON job (hpc_cluster, job_state, hpc_user); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_project ON job (hpc_cluster, job_state, project); +-- Cluster+JobState Filter Sorting +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_starttime ON job (hpc_cluster, job_state, start_time); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_duration ON job (hpc_cluster, job_state, duration); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_numnodes ON job (hpc_cluster, job_state, num_nodes); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_numhwthreads ON job (hpc_cluster, job_state, num_hwthreads); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_numacc ON job (hpc_cluster, job_state, num_acc); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_energy ON job (hpc_cluster, job_state, energy); +--- --- END UPDATE existing indices + +-- Add NEW Indices For New Job Table Columns +CREATE INDEX IF NOT EXISTS jobs_cluster_submittime ON job (hpc_cluster, submit_time); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_submittime ON job (hpc_cluster, cluster_partition, submit_time); +CREATE INDEX IF NOT EXISTS jobs_cluster_partition_jobstate_submittime ON job (hpc_cluster, cluster_partition, job_state, submit_time); +CREATE INDEX IF NOT EXISTS jobs_cluster_jobstate_submittime ON job (hpc_cluster, job_state, submit_time); + +-- Add NEW Indices For New Node Table VARCHAR Fields CREATE INDEX IF NOT EXISTS nodes_cluster ON node (cluster); CREATE INDEX IF NOT EXISTS nodes_cluster_subcluster ON node (cluster, subcluster); -CREATE INDEX IF NOT EXISTS nodes_state ON node (node_state); -CREATE INDEX IF NOT EXISTS nodes_cluster_state ON node (cluster, node_state); -CREATE INDEX IF NOT EXISTS nodes_health ON node (health_state); -CREATE INDEX IF NOT EXISTS nodes_cluster_health ON node (cluster, health_state); --- Add Indices For Increased Amounts of Tags +-- Add NEW Indices For New Node_State Table Fields +CREATE INDEX IF NOT EXISTS nodeStates_state ON node_state (node_state); +CREATE INDEX IF NOT EXISTS nodeStates_health ON node_state (health_state); +CREATE INDEX IF NOT EXISTS nodeStates_nodeid_state ON node (node_id, node_state); +CREATE INDEX IF NOT EXISTS nodeStates_nodeid_health ON node (node_id, health_state); + +-- Add NEW Indices For Increased Amounts of Tags CREATE INDEX IF NOT EXISTS tags_jobid ON jobtag (job_id); CREATE INDEX IF NOT EXISTS tags_tagid ON jobtag (tag_id);