mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-03-25 17:17:29 +01:00
@@ -18,6 +18,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
@@ -399,16 +400,6 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
return fmt.Errorf("dropping privileges: %w", err)
|
return fmt.Errorf("dropping privileges: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle context cancellation for graceful shutdown
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.server.Shutdown(shutdownCtx); err != nil {
|
|
||||||
cclog.Errorf("Server shutdown error: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = s.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
if err = s.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
return fmt.Errorf("server failed: %w", err)
|
return fmt.Errorf("server failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -416,8 +407,7 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Shutdown(ctx context.Context) {
|
func (s *Server) Shutdown(ctx context.Context) {
|
||||||
// Create a shutdown context with timeout
|
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
nc := nats.GetClient()
|
nc := nats.GetClient()
|
||||||
@@ -425,20 +415,36 @@ func (s *Server) Shutdown(ctx context.Context) {
|
|||||||
nc.Close()
|
nc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// First shut down the server gracefully (waiting for all ongoing requests)
|
|
||||||
if err := s.server.Shutdown(shutdownCtx); err != nil {
|
if err := s.server.Shutdown(shutdownCtx); err != nil {
|
||||||
cclog.Errorf("Server shutdown error: %v", err)
|
cclog.Errorf("Server shutdown error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Archive all the metric store data
|
// Run metricstore and archiver shutdown concurrently.
|
||||||
ms := metricstore.GetMemoryStore()
|
// They are independent: metricstore writes .bin snapshots,
|
||||||
|
// archiver flushes pending job archives.
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
if ms != nil {
|
if ms := metricstore.GetMemoryStore(); ms != nil {
|
||||||
metricstore.Shutdown()
|
wg.Go(func() {
|
||||||
}
|
metricstore.Shutdown()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Shutdown archiver with 10 second timeout for fast shutdown
|
wg.Go(func() {
|
||||||
if err := archiver.Shutdown(10 * time.Second); err != nil {
|
if err := archiver.Shutdown(10 * time.Second); err != nil {
|
||||||
cclog.Warnf("Archiver shutdown: %v", err)
|
cclog.Warnf("Archiver shutdown: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
cclog.Warn("Shutdown deadline exceeded, forcing exit")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,11 +280,11 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select
|
|||||||
|
|
||||||
// buildIntCondition creates clauses for integer range filters, using BETWEEN only if required.
|
// buildIntCondition creates clauses for integer range filters, using BETWEEN only if required.
|
||||||
func buildIntCondition(field string, cond *config.IntRange, query sq.SelectBuilder) sq.SelectBuilder {
|
func buildIntCondition(field string, cond *config.IntRange, query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
if cond.From != 1 && cond.To != 0 {
|
if cond.From > 0 && cond.To > 0 {
|
||||||
return query.Where(field+" BETWEEN ? AND ?", cond.From, cond.To)
|
return query.Where(field+" BETWEEN ? AND ?", cond.From, cond.To)
|
||||||
} else if cond.From != 1 && cond.To == 0 {
|
} else if cond.From > 0 && cond.To == 0 {
|
||||||
return query.Where(field+" >= ?", cond.From)
|
return query.Where(field+" >= ?", cond.From)
|
||||||
} else if cond.From == 1 && cond.To != 0 {
|
} else if cond.From == 0 && cond.To > 0 {
|
||||||
return query.Where(field+" <= ?", cond.To)
|
return query.Where(field+" <= ?", cond.To)
|
||||||
} else {
|
} else {
|
||||||
return query
|
return query
|
||||||
@@ -293,11 +293,11 @@ func buildIntCondition(field string, cond *config.IntRange, query sq.SelectBuild
|
|||||||
|
|
||||||
// buildFloatCondition creates a clauses for float range filters, using BETWEEN only if required.
|
// buildFloatCondition creates a clauses for float range filters, using BETWEEN only if required.
|
||||||
func buildFloatCondition(field string, cond *model.FloatRange, query sq.SelectBuilder) sq.SelectBuilder {
|
func buildFloatCondition(field string, cond *model.FloatRange, query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
if cond.From != 1.0 && cond.To != 0.0 {
|
if cond.From > 0.0 && cond.To > 0.0 {
|
||||||
return query.Where(field+" BETWEEN ? AND ?", cond.From, cond.To)
|
return query.Where(field+" BETWEEN ? AND ?", cond.From, cond.To)
|
||||||
} else if cond.From != 1.0 && cond.To == 0.0 {
|
} else if cond.From > 0.0 && cond.To == 0.0 {
|
||||||
return query.Where(field+" >= ?", cond.From)
|
return query.Where(field+" >= ?", cond.From)
|
||||||
} else if cond.From == 1.0 && cond.To != 0.0 {
|
} else if cond.From == 0.0 && cond.To > 0.0 {
|
||||||
return query.Where(field+" <= ?", cond.To)
|
return query.Where(field+" <= ?", cond.To)
|
||||||
} else {
|
} else {
|
||||||
return query
|
return query
|
||||||
@@ -339,11 +339,11 @@ func buildTimeCondition(field string, cond *config.TimeRange, query sq.SelectBui
|
|||||||
// buildFloatJSONCondition creates a filter on a numeric field within the footprint JSON column, using BETWEEN only if required.
|
// buildFloatJSONCondition creates a filter on a numeric field within the footprint JSON column, using BETWEEN only if required.
|
||||||
func buildFloatJSONCondition(jsonField string, cond *model.FloatRange, query sq.SelectBuilder) sq.SelectBuilder {
|
func buildFloatJSONCondition(jsonField string, cond *model.FloatRange, query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
query = query.Where("JSON_VALID(footprint)")
|
query = query.Where("JSON_VALID(footprint)")
|
||||||
if cond.From != 1.0 && cond.To != 0.0 {
|
if cond.From > 0.0 && cond.To > 0.0 {
|
||||||
return query.Where("JSON_EXTRACT(footprint, \"$."+jsonField+"\") BETWEEN ? AND ?", cond.From, cond.To)
|
return query.Where("JSON_EXTRACT(footprint, \"$."+jsonField+"\") BETWEEN ? AND ?", cond.From, cond.To)
|
||||||
} else if cond.From != 1.0 && cond.To == 0.0 {
|
} else if cond.From > 0.0 && cond.To == 0.0 {
|
||||||
return query.Where("JSON_EXTRACT(footprint, \"$."+jsonField+"\") >= ?", cond.From)
|
return query.Where("JSON_EXTRACT(footprint, \"$."+jsonField+"\") >= ?", cond.From)
|
||||||
} else if cond.From == 1.0 && cond.To != 0.0 {
|
} else if cond.From == 0.0 && cond.To > 0.0 {
|
||||||
return query.Where("JSON_EXTRACT(footprint, \"$."+jsonField+"\") <= ?", cond.To)
|
return query.Where("JSON_EXTRACT(footprint, \"$."+jsonField+"\") <= ?", cond.To)
|
||||||
} else {
|
} else {
|
||||||
return query
|
return query
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ func buildFilterPresets(query url.Values) map[string]any {
|
|||||||
if parts[0] == "lessthan" {
|
if parts[0] == "lessthan" {
|
||||||
lt, lte := strconv.Atoi(parts[1])
|
lt, lte := strconv.Atoi(parts[1])
|
||||||
if lte == nil {
|
if lte == nil {
|
||||||
filterPresets["numNodes"] = map[string]int{"from": 1, "to": lt}
|
filterPresets["numNodes"] = map[string]int{"from": 0, "to": lt}
|
||||||
}
|
}
|
||||||
} else if parts[0] == "morethan" {
|
} else if parts[0] == "morethan" {
|
||||||
mt, mte := strconv.Atoi(parts[1])
|
mt, mte := strconv.Atoi(parts[1])
|
||||||
@@ -330,7 +330,7 @@ func buildFilterPresets(query url.Values) map[string]any {
|
|||||||
if parts[0] == "lessthan" {
|
if parts[0] == "lessthan" {
|
||||||
lt, lte := strconv.Atoi(parts[1])
|
lt, lte := strconv.Atoi(parts[1])
|
||||||
if lte == nil {
|
if lte == nil {
|
||||||
filterPresets["numHWThreads"] = map[string]int{"from": 1, "to": lt}
|
filterPresets["numHWThreads"] = map[string]int{"from": 0, "to": lt}
|
||||||
}
|
}
|
||||||
} else if parts[0] == "morethan" {
|
} else if parts[0] == "morethan" {
|
||||||
mt, mte := strconv.Atoi(parts[1])
|
mt, mte := strconv.Atoi(parts[1])
|
||||||
@@ -352,7 +352,7 @@ func buildFilterPresets(query url.Values) map[string]any {
|
|||||||
if parts[0] == "lessthan" {
|
if parts[0] == "lessthan" {
|
||||||
lt, lte := strconv.Atoi(parts[1])
|
lt, lte := strconv.Atoi(parts[1])
|
||||||
if lte == nil {
|
if lte == nil {
|
||||||
filterPresets["numAccelerators"] = map[string]int{"from": 1, "to": lt}
|
filterPresets["numAccelerators"] = map[string]int{"from": 0, "to": lt}
|
||||||
}
|
}
|
||||||
} else if parts[0] == "morethan" {
|
} else if parts[0] == "morethan" {
|
||||||
mt, mte := strconv.Atoi(parts[1])
|
mt, mte := strconv.Atoi(parts[1])
|
||||||
@@ -408,7 +408,7 @@ func buildFilterPresets(query url.Values) map[string]any {
|
|||||||
if parts[0] == "lessthan" {
|
if parts[0] == "lessthan" {
|
||||||
lt, lte := strconv.Atoi(parts[1])
|
lt, lte := strconv.Atoi(parts[1])
|
||||||
if lte == nil {
|
if lte == nil {
|
||||||
filterPresets["energy"] = map[string]int{"from": 1, "to": lt}
|
filterPresets["energy"] = map[string]int{"from": 0, "to": lt}
|
||||||
}
|
}
|
||||||
} else if parts[0] == "morethan" {
|
} else if parts[0] == "morethan" {
|
||||||
mt, mte := strconv.Atoi(parts[1])
|
mt, mte := strconv.Atoi(parts[1])
|
||||||
@@ -434,7 +434,7 @@ func buildFilterPresets(query url.Values) map[string]any {
|
|||||||
if lte == nil {
|
if lte == nil {
|
||||||
statEntry := map[string]any{
|
statEntry := map[string]any{
|
||||||
"field": parts[0],
|
"field": parts[0],
|
||||||
"from": 1,
|
"from": 0,
|
||||||
"to": lt,
|
"to": lt,
|
||||||
}
|
}
|
||||||
statList = append(statList, statEntry)
|
statList = append(statList, statEntry)
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
||||||
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
||||||
@@ -111,10 +112,16 @@ type walRotateReq struct {
|
|||||||
|
|
||||||
// walFileState holds an open WAL file handle and buffered writer for one host directory.
|
// walFileState holds an open WAL file handle and buffered writer for one host directory.
|
||||||
type walFileState struct {
|
type walFileState struct {
|
||||||
f *os.File
|
f *os.File
|
||||||
w *bufio.Writer
|
w *bufio.Writer
|
||||||
|
dirty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// walFlushInterval controls how often dirty WAL files are flushed to disk.
|
||||||
|
// Decoupling flushes from message processing lets the consumer run at memory
|
||||||
|
// speed, amortizing syscall overhead across many writes.
|
||||||
|
const walFlushInterval = 5 * time.Second
|
||||||
|
|
||||||
// walShardIndex computes which shard a message belongs to based on cluster+node.
|
// walShardIndex computes which shard a message belongs to based on cluster+node.
|
||||||
// Uses FNV-1a hash for fast, well-distributed mapping.
|
// Uses FNV-1a hash for fast, well-distributed mapping.
|
||||||
func walShardIndex(cluster, node string) int {
|
func walShardIndex(cluster, node string) int {
|
||||||
@@ -222,6 +229,7 @@ func WALStaging(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
if err := writeWALRecordDirect(ws.w, msg); err != nil {
|
if err := writeWALRecordDirect(ws.w, msg); err != nil {
|
||||||
cclog.Errorf("[METRICSTORE]> WAL: write record: %v", err)
|
cclog.Errorf("[METRICSTORE]> WAL: write record: %v", err)
|
||||||
}
|
}
|
||||||
|
ws.dirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
processRotate := func(req walRotateReq) {
|
processRotate := func(req walRotateReq) {
|
||||||
@@ -238,10 +246,11 @@ func WALStaging(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
close(req.done)
|
close(req.done)
|
||||||
}
|
}
|
||||||
|
|
||||||
flushAll := func() {
|
flushDirty := func() {
|
||||||
for _, ws := range hostFiles {
|
for _, ws := range hostFiles {
|
||||||
if ws.f != nil {
|
if ws.dirty {
|
||||||
ws.w.Flush()
|
ws.w.Flush()
|
||||||
|
ws.dirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,12 +266,35 @@ func WALStaging(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
case req := <-rotateCh:
|
case req := <-rotateCh:
|
||||||
processRotate(req)
|
processRotate(req)
|
||||||
default:
|
default:
|
||||||
flushAll()
|
flushDirty()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(walFlushInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// drainBatch processes up to 4096 pending messages without blocking.
|
||||||
|
// Returns false if the channel was closed.
|
||||||
|
drainBatch := func() bool {
|
||||||
|
for range 4096 {
|
||||||
|
select {
|
||||||
|
case msg, ok := <-msgCh:
|
||||||
|
if !ok {
|
||||||
|
flushDirty()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
processMsg(msg)
|
||||||
|
case req := <-rotateCh:
|
||||||
|
processRotate(req)
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -273,23 +305,12 @@ func WALStaging(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
processMsg(msg)
|
processMsg(msg)
|
||||||
|
if !drainBatch() {
|
||||||
// Drain up to 256 more messages without blocking to batch writes.
|
return
|
||||||
for range 256 {
|
|
||||||
select {
|
|
||||||
case msg, ok := <-msgCh:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
processMsg(msg)
|
|
||||||
case req := <-rotateCh:
|
|
||||||
processRotate(req)
|
|
||||||
default:
|
|
||||||
goto flushed
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flushed:
|
// No flush here — timer handles periodic flushing.
|
||||||
flushAll()
|
case <-ticker.C:
|
||||||
|
flushDirty()
|
||||||
case req := <-rotateCh:
|
case req := <-rotateCh:
|
||||||
processRotate(req)
|
processRotate(req)
|
||||||
}
|
}
|
||||||
@@ -413,69 +434,6 @@ func writeWALRecordDirect(w *bufio.Writer, msg *WALMessage) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildWALPayload encodes a WALMessage into a binary payload (without magic/length/CRC).
|
|
||||||
func buildWALPayload(msg *WALMessage) []byte {
|
|
||||||
size := 8 + 2 + len(msg.MetricName) + 1 + 4
|
|
||||||
for _, s := range msg.Selector {
|
|
||||||
size += 1 + len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 0, size)
|
|
||||||
|
|
||||||
// Timestamp (8 bytes, little-endian int64)
|
|
||||||
var ts [8]byte
|
|
||||||
binary.LittleEndian.PutUint64(ts[:], uint64(msg.Timestamp))
|
|
||||||
buf = append(buf, ts[:]...)
|
|
||||||
|
|
||||||
// Metric name (2-byte length prefix + bytes)
|
|
||||||
var mLen [2]byte
|
|
||||||
binary.LittleEndian.PutUint16(mLen[:], uint16(len(msg.MetricName)))
|
|
||||||
buf = append(buf, mLen[:]...)
|
|
||||||
buf = append(buf, msg.MetricName...)
|
|
||||||
|
|
||||||
// Selector count (1 byte)
|
|
||||||
buf = append(buf, byte(len(msg.Selector)))
|
|
||||||
|
|
||||||
// Selectors (1-byte length prefix + bytes each)
|
|
||||||
for _, sel := range msg.Selector {
|
|
||||||
buf = append(buf, byte(len(sel)))
|
|
||||||
buf = append(buf, sel...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value (4 bytes, float32 bit representation)
|
|
||||||
var val [4]byte
|
|
||||||
binary.LittleEndian.PutUint32(val[:], math.Float32bits(float32(msg.Value)))
|
|
||||||
buf = append(buf, val[:]...)
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeWALRecord appends a binary WAL record to the writer.
|
|
||||||
// Format: [4B magic][4B payload_len][payload][4B CRC32]
|
|
||||||
func writeWALRecord(w io.Writer, msg *WALMessage) error {
|
|
||||||
payload := buildWALPayload(msg)
|
|
||||||
crc := crc32.ChecksumIEEE(payload)
|
|
||||||
|
|
||||||
record := make([]byte, 0, 4+4+len(payload)+4)
|
|
||||||
|
|
||||||
var magic [4]byte
|
|
||||||
binary.LittleEndian.PutUint32(magic[:], walRecordMagic)
|
|
||||||
record = append(record, magic[:]...)
|
|
||||||
|
|
||||||
var pLen [4]byte
|
|
||||||
binary.LittleEndian.PutUint32(pLen[:], uint32(len(payload)))
|
|
||||||
record = append(record, pLen[:]...)
|
|
||||||
|
|
||||||
record = append(record, payload...)
|
|
||||||
|
|
||||||
var crcBytes [4]byte
|
|
||||||
binary.LittleEndian.PutUint32(crcBytes[:], crc)
|
|
||||||
record = append(record, crcBytes[:]...)
|
|
||||||
|
|
||||||
_, err := w.Write(record)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// readWALRecord reads one WAL record from the reader.
|
// readWALRecord reads one WAL record from the reader.
|
||||||
// Returns (nil, nil) on clean EOF. Returns error on data corruption.
|
// Returns (nil, nil) on clean EOF. Returns error on data corruption.
|
||||||
// A CRC mismatch indicates a truncated trailing record (expected on crash).
|
// A CRC mismatch indicates a truncated trailing record (expected on crash).
|
||||||
|
|||||||
@@ -166,12 +166,12 @@
|
|||||||
items.push({ project: { [filters.projectMatch]: filters.project } });
|
items.push({ project: { [filters.projectMatch]: filters.project } });
|
||||||
if (filters.user)
|
if (filters.user)
|
||||||
items.push({ user: { [filters.userMatch]: filters.user } });
|
items.push({ user: { [filters.userMatch]: filters.user } });
|
||||||
if (filters.numNodes.from != null || filters.numNodes.to != null) {
|
if (filters.numNodes.from != null && filters.numNodes.to != null) {
|
||||||
items.push({
|
items.push({
|
||||||
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
|
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) {
|
if (filters.numAccelerators.from != null && filters.numAccelerators.to != null) {
|
||||||
items.push({
|
items.push({
|
||||||
numAccelerators: {
|
numAccelerators: {
|
||||||
from: filters.numAccelerators.from,
|
from: filters.numAccelerators.from,
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) {
|
if (filters.numHWThreads.from != null && filters.numHWThreads.to != null) {
|
||||||
items.push({
|
items.push({
|
||||||
numHWThreads: {
|
numHWThreads: {
|
||||||
from: filters.numHWThreads.from,
|
from: filters.numHWThreads.from,
|
||||||
@@ -206,14 +206,21 @@
|
|||||||
items.push({ duration: { to: filters.duration.lessThan, from: 0 } });
|
items.push({ duration: { to: filters.duration.lessThan, from: 0 } });
|
||||||
if (filters.duration.moreThan)
|
if (filters.duration.moreThan)
|
||||||
items.push({ duration: { to: 0, from: filters.duration.moreThan } });
|
items.push({ duration: { to: 0, from: filters.duration.moreThan } });
|
||||||
if (filters.energy.from != null || filters.energy.to != null)
|
if (filters.energy.from != null && filters.energy.to != null)
|
||||||
items.push({
|
items.push({
|
||||||
energy: { from: filters.energy.from, to: filters.energy.to },
|
energy: { from: filters.energy.from, to: filters.energy.to },
|
||||||
});
|
});
|
||||||
if (filters.jobId)
|
if (filters.jobId)
|
||||||
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } });
|
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } });
|
||||||
if (filters.stats.length != 0)
|
if (filters.stats.length != 0) {
|
||||||
items.push({ metricStats: filters.stats.map((st) => { return { metricName: st.field, range: { from: st.from, to: st.to }} }) });
|
const metricStats = [];
|
||||||
|
filters.stats.forEach((st) => {
|
||||||
|
if (st.from != null && st.to != null)
|
||||||
|
metricStats.push({ metricName: st.field, range: { from: st.from, to: st.to }});
|
||||||
|
});
|
||||||
|
if (metricStats.length != 0)
|
||||||
|
items.push({metricStats})
|
||||||
|
};
|
||||||
if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
|
if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
|
||||||
if (filters.jobName) items.push({ jobName: { contains: filters.jobName } });
|
if (filters.jobName) items.push({ jobName: { contains: filters.jobName } });
|
||||||
if (filters.schedule) items.push({ schedule: filters.schedule });
|
if (filters.schedule) items.push({ schedule: filters.schedule });
|
||||||
@@ -280,40 +287,40 @@
|
|||||||
opts.push(`duration=morethan-${filters.duration.moreThan}`);
|
opts.push(`duration=morethan-${filters.duration.moreThan}`);
|
||||||
if (filters.tags.length != 0)
|
if (filters.tags.length != 0)
|
||||||
for (let tag of filters.tags) opts.push(`tag=${tag}`);
|
for (let tag of filters.tags) opts.push(`tag=${tag}`);
|
||||||
if (filters.numNodes.from > 1 && filters.numNodes.to > 0)
|
if (filters.numNodes.from > 0 && filters.numNodes.to > 0)
|
||||||
opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`);
|
opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`);
|
||||||
else if (filters.numNodes.from > 1 && filters.numNodes.to == 0)
|
else if (filters.numNodes.from > 0 && filters.numNodes.to == 0)
|
||||||
opts.push(`numNodes=morethan-${filters.numNodes.from}`);
|
opts.push(`numNodes=morethan-${filters.numNodes.from}`);
|
||||||
else if (filters.numNodes.from == 1 && filters.numNodes.to > 0)
|
else if (filters.numNodes.from == 0 && filters.numNodes.to > 0)
|
||||||
opts.push(`numNodes=lessthan-${filters.numNodes.to}`);
|
opts.push(`numNodes=lessthan-${filters.numNodes.to}`);
|
||||||
if (filters.numHWThreads.from > 1 && filters.numHWThreads.to > 0)
|
if (filters.numHWThreads.from > 0 && filters.numHWThreads.to > 0)
|
||||||
opts.push(`numHWThreads=${filters.numHWThreads.from}-${filters.numHWThreads.to}`);
|
opts.push(`numHWThreads=${filters.numHWThreads.from}-${filters.numHWThreads.to}`);
|
||||||
else if (filters.numHWThreads.from > 1 && filters.numHWThreads.to == 0)
|
else if (filters.numHWThreads.from > 0 && filters.numHWThreads.to == 0)
|
||||||
opts.push(`numHWThreads=morethan-${filters.numHWThreads.from}`);
|
opts.push(`numHWThreads=morethan-${filters.numHWThreads.from}`);
|
||||||
else if (filters.numHWThreads.from == 1 && filters.numHWThreads.to > 0)
|
else if (filters.numHWThreads.from == 0 && filters.numHWThreads.to > 0)
|
||||||
opts.push(`numHWThreads=lessthan-${filters.numHWThreads.to}`);
|
opts.push(`numHWThreads=lessthan-${filters.numHWThreads.to}`);
|
||||||
if (filters.numAccelerators.from && filters.numAccelerators.to)
|
if (filters.numAccelerators.from > 0 && filters.numAccelerators.to > 0)
|
||||||
opts.push(`numAccelerators=${filters.numAccelerators.from}-${filters.numAccelerators.to}`);
|
opts.push(`numAccelerators=${filters.numAccelerators.from}-${filters.numAccelerators.to}`);
|
||||||
else if (filters.numAccelerators.from > 1 && filters.numAccelerators.to == 0)
|
else if (filters.numAccelerators.from > 0 && filters.numAccelerators.to == 0)
|
||||||
opts.push(`numAccelerators=morethan-${filters.numAccelerators.from}`);
|
opts.push(`numAccelerators=morethan-${filters.numAccelerators.from}`);
|
||||||
else if (filters.numAccelerators.from == 1 && filters.numAccelerators.to > 0)
|
else if (filters.numAccelerators.from == 0 && filters.numAccelerators.to > 0)
|
||||||
opts.push(`numAccelerators=lessthan-${filters.numAccelerators.to}`);
|
opts.push(`numAccelerators=lessthan-${filters.numAccelerators.to}`);
|
||||||
if (filters.node) opts.push(`node=${filters.node}`);
|
if (filters.node) opts.push(`node=${filters.node}`);
|
||||||
if (filters.node && filters.nodeMatch != "eq") // "eq" is default-case
|
if (filters.node && filters.nodeMatch != "eq") // "eq" is default-case
|
||||||
opts.push(`nodeMatch=${filters.nodeMatch}`);
|
opts.push(`nodeMatch=${filters.nodeMatch}`);
|
||||||
if (filters.energy.from > 1 && filters.energy.to > 0)
|
if (filters.energy.from > 0 && filters.energy.to > 0)
|
||||||
opts.push(`energy=${filters.energy.from}-${filters.energy.to}`);
|
opts.push(`energy=${filters.energy.from}-${filters.energy.to}`);
|
||||||
else if (filters.energy.from > 1 && filters.energy.to == 0)
|
else if (filters.energy.from > 0 && filters.energy.to == 0)
|
||||||
opts.push(`energy=morethan-${filters.energy.from}`);
|
opts.push(`energy=morethan-${filters.energy.from}`);
|
||||||
else if (filters.energy.from == 1 && filters.energy.to > 0)
|
else if (filters.energy.from == 0 && filters.energy.to > 0)
|
||||||
opts.push(`energy=lessthan-${filters.energy.to}`);
|
opts.push(`energy=lessthan-${filters.energy.to}`);
|
||||||
if (filters.stats.length > 0)
|
if (filters.stats.length > 0)
|
||||||
for (let stat of filters.stats) {
|
for (let stat of filters.stats) {
|
||||||
if (stat.from > 1 && stat.to > 0)
|
if (stat.from > 0 && stat.to > 0)
|
||||||
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
|
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
|
||||||
else if (stat.from > 1 && stat.to == 0)
|
else if (stat.from > 0 && stat.to == 0)
|
||||||
opts.push(`stat=${stat.field}-morethan-${stat.from}`);
|
opts.push(`stat=${stat.field}-morethan-${stat.from}`);
|
||||||
else if (stat.from == 1 && stat.to > 0)
|
else if (stat.from == 0 && stat.to > 0)
|
||||||
opts.push(`stat=${stat.field}-lessthan-${stat.to}`);
|
opts.push(`stat=${stat.field}-lessthan-${stat.to}`);
|
||||||
}
|
}
|
||||||
// Build && Return
|
// Build && Return
|
||||||
@@ -511,43 +518,43 @@
|
|||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.numNodes.from > 1 && filters.numNodes.to > 0}
|
{#if filters.numNodes.from > 0 && filters.numNodes.to > 0}
|
||||||
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
||||||
Nodes: {filters.numNodes.from} - {filters.numNodes.to}
|
Nodes: {filters.numNodes.from} - {filters.numNodes.to}
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.numNodes.from > 1 && filters.numNodes.to == 0}
|
{:else if filters.numNodes.from > 0 && filters.numNodes.to == 0}
|
||||||
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
||||||
≥ {filters.numNodes.from} Node(s)
|
≥ {filters.numNodes.from} Node(s)
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.numNodes.from == 1 && filters.numNodes.to > 0}
|
{:else if filters.numNodes.from == 0 && filters.numNodes.to > 0}
|
||||||
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
||||||
≤ {filters.numNodes.to} Node(s)
|
≤ {filters.numNodes.to} Node(s)
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.numHWThreads.from > 1 && filters.numHWThreads.to > 0}
|
{#if filters.numHWThreads.from > 0 && filters.numHWThreads.to > 0}
|
||||||
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
|
||||||
HWThreads: {filters.numHWThreads.from} - {filters.numHWThreads.to}
|
HWThreads: {filters.numHWThreads.from} - {filters.numHWThreads.to}
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.numHWThreads.from > 1 && filters.numHWThreads.to == 0}
|
{:else if filters.numHWThreads.from > 0 && filters.numHWThreads.to == 0}
|
||||||
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
|
||||||
≥ {filters.numHWThreads.from} HWThread(s)
|
≥ {filters.numHWThreads.from} HWThread(s)
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.numHWThreads.from == 1 && filters.numHWThreads.to > 0}
|
{:else if filters.numHWThreads.from == 0 && filters.numHWThreads.to > 0}
|
||||||
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
|
||||||
≤ {filters.numHWThreads.to} HWThread(s)
|
≤ {filters.numHWThreads.to} HWThread(s)
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.numAccelerators.from > 1 && filters.numAccelerators.to > 0}
|
{#if filters.numAccelerators.from > 0 && filters.numAccelerators.to > 0}
|
||||||
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
|
||||||
Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to}
|
Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to}
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.numAccelerators.from > 1 && filters.numAccelerators.to == 0}
|
{:else if filters.numAccelerators.from > 0 && filters.numAccelerators.to == 0}
|
||||||
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
|
||||||
≥ {filters.numAccelerators.from} Acc(s)
|
≥ {filters.numAccelerators.from} Acc(s)
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.numAccelerators.from == 1 && filters.numAccelerators.to > 0}
|
{:else if filters.numAccelerators.from == 0 && filters.numAccelerators.to > 0}
|
||||||
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
|
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
|
||||||
≤ {filters.numAccelerators.to} Acc(s)
|
≤ {filters.numAccelerators.to} Acc(s)
|
||||||
</Info>
|
</Info>
|
||||||
@@ -559,15 +566,15 @@
|
|||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.energy.from > 1 && filters.energy.to > 0}
|
{#if filters.energy.from > 0 && filters.energy.to > 0}
|
||||||
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
|
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
|
||||||
Total Energy: {filters.energy.from} - {filters.energy.to} kWh
|
Total Energy: {filters.energy.from} - {filters.energy.to} kWh
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.energy.from > 1 && filters.energy.to == 0}
|
{:else if filters.energy.from > 0 && filters.energy.to == 0}
|
||||||
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
|
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
|
||||||
Total Energy ≥ {filters.energy.from} kWh
|
Total Energy ≥ {filters.energy.from} kWh
|
||||||
</Info>
|
</Info>
|
||||||
{:else if filters.energy.from == 1 && filters.energy.to > 0}
|
{:else if filters.energy.from == 0 && filters.energy.to > 0}
|
||||||
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
|
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
|
||||||
Total Energy ≤ {filters.energy.to} kWh
|
Total Energy ≤ {filters.energy.to} kWh
|
||||||
</Info>
|
</Info>
|
||||||
@@ -575,15 +582,15 @@
|
|||||||
|
|
||||||
{#if filters.stats.length > 0}
|
{#if filters.stats.length > 0}
|
||||||
{#each filters.stats as stat}
|
{#each filters.stats as stat}
|
||||||
{#if stat.from > 1 && stat.to > 0}
|
{#if stat.from > 0 && stat.to > 0}
|
||||||
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
|
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
|
||||||
{stat.field}: {stat.from} - {stat.to} {stat.unit}
|
{stat.field}: {stat.from} - {stat.to} {stat.unit}
|
||||||
</Info> 
|
</Info> 
|
||||||
{:else if stat.from > 1 && stat.to == 0}
|
{:else if stat.from > 0 && stat.to == 0}
|
||||||
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
|
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
|
||||||
{stat.field} ≥ {stat.from} {stat.unit}
|
{stat.field} ≥ {stat.from} {stat.unit}
|
||||||
</Info> 
|
</Info> 
|
||||||
{:else if stat.from == 1 && stat.to > 0}
|
{:else if stat.from == 0 && stat.to > 0}
|
||||||
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
|
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
|
||||||
{stat.field} ≤ {stat.to} {stat.unit}
|
{stat.field} ≤ {stat.to} {stat.unit}
|
||||||
</Info> 
|
</Info> 
|
||||||
|
|||||||
@@ -28,31 +28,29 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const */
|
/* Const */
|
||||||
const minEnergyPreset = 1;
|
const minEnergyPreset = 0;
|
||||||
const maxEnergyPreset = 100;
|
const maxEnergyPreset = 100;
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
// Pending
|
// Pending
|
||||||
let pendingEnergyState = $derived({
|
let pendingEnergyState = $derived({
|
||||||
from: presetEnergy?.from ? presetEnergy.from : minEnergyPreset,
|
from: presetEnergy?.from || minEnergyPreset,
|
||||||
to: !(presetEnergy.to == null || presetEnergy.to == 0) ? presetEnergy.to : maxEnergyPreset,
|
to: (presetEnergy.to == 0) ? null : presetEnergy.to,
|
||||||
});
|
});
|
||||||
// Changable
|
// Changable
|
||||||
let energyState = $derived({
|
let energyState = $derived({
|
||||||
from: presetEnergy?.from ? presetEnergy.from : minEnergyPreset,
|
from: presetEnergy?.from || minEnergyPreset,
|
||||||
to: !(presetEnergy.to == null || presetEnergy.to == 0) ? presetEnergy.to : maxEnergyPreset,
|
to: (presetEnergy.to == 0) ? null : presetEnergy.to,
|
||||||
});
|
});
|
||||||
|
|
||||||
const energyActive = $derived(!(JSON.stringify(energyState) === JSON.stringify({ from: minEnergyPreset, to: maxEnergyPreset })));
|
const energyActive = $derived(!(JSON.stringify(energyState) === JSON.stringify({ from: minEnergyPreset, to: null })));
|
||||||
// Block Apply if null
|
|
||||||
const disableApply = $derived(energyState.from === null || energyState.to === null);
|
|
||||||
|
|
||||||
/* Function */
|
/* Function */
|
||||||
function setEnergy() {
|
function setEnergy() {
|
||||||
if (energyActive) {
|
if (energyActive) {
|
||||||
pendingEnergyState = {
|
pendingEnergyState = {
|
||||||
from: energyState.from,
|
from: (!energyState?.from) ? 0 : energyState.from,
|
||||||
to: (energyState.to == maxEnergyPreset) ? 0 : energyState.to
|
to: (energyState.to === null) ? 0 : energyState.to
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
pendingEnergyState = { from: null, to: null};
|
pendingEnergyState = { from: null, to: null};
|
||||||
@@ -86,7 +84,6 @@
|
|||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={disableApply}
|
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
setEnergy();
|
setEnergy();
|
||||||
|
|||||||
@@ -98,44 +98,38 @@
|
|||||||
// Pending
|
// Pending
|
||||||
let pendingNumNodes = $derived({
|
let pendingNumNodes = $derived({
|
||||||
from: presetNumNodes.from,
|
from: presetNumNodes.from,
|
||||||
to: (presetNumNodes.to == 0) ? maxNumNodes : presetNumNodes.to
|
to: (presetNumNodes.to == 0) ? null : presetNumNodes.to
|
||||||
});
|
});
|
||||||
let pendingNumHWThreads = $derived({
|
let pendingNumHWThreads = $derived({
|
||||||
from: presetNumHWThreads.from,
|
from: presetNumHWThreads.from,
|
||||||
to: (presetNumHWThreads.to == 0) ? maxNumHWThreads : presetNumHWThreads.to
|
to: (presetNumHWThreads.to == 0) ? null : presetNumHWThreads.to
|
||||||
});
|
});
|
||||||
let pendingNumAccelerators = $derived({
|
let pendingNumAccelerators = $derived({
|
||||||
from: presetNumAccelerators.from,
|
from: presetNumAccelerators.from,
|
||||||
to: (presetNumAccelerators.to == 0) ? maxNumAccelerators : presetNumAccelerators.to
|
to: (presetNumAccelerators.to == 0) ? null : presetNumAccelerators.to
|
||||||
});
|
});
|
||||||
let pendingNamedNode = $derived(presetNamedNode);
|
let pendingNamedNode = $derived(presetNamedNode);
|
||||||
let pendingNodeMatch = $derived(presetNodeMatch);
|
let pendingNodeMatch = $derived(presetNodeMatch);
|
||||||
// Changable States
|
// Changable States
|
||||||
let nodesState = $derived({
|
let nodesState = $derived({
|
||||||
from: presetNumNodes.from,
|
from: presetNumNodes?.from || 0,
|
||||||
to: (presetNumNodes.to == 0) ? maxNumNodes : presetNumNodes.to
|
to: (presetNumNodes.to == 0) ? null : presetNumNodes.to
|
||||||
});
|
});
|
||||||
let threadState = $derived({
|
let threadState = $derived({
|
||||||
from: presetNumHWThreads.from,
|
from: presetNumHWThreads?.from || 0,
|
||||||
to: (presetNumHWThreads.to == 0) ? maxNumHWThreads : presetNumHWThreads.to
|
to: (presetNumHWThreads.to == 0) ? null : presetNumHWThreads.to
|
||||||
});
|
});
|
||||||
let accState = $derived({
|
let accState = $derived({
|
||||||
from: presetNumAccelerators.from,
|
from: presetNumAccelerators?.from || 0,
|
||||||
to: (presetNumAccelerators.to == 0) ? maxNumAccelerators : presetNumAccelerators.to
|
to: (presetNumAccelerators.to == 0) ? null : presetNumAccelerators.to
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialized = $derived(getContext("initialized") || false);
|
const initialized = $derived(getContext("initialized") || false);
|
||||||
const clusterInfos = $derived($initialized ? getContext("clusters") : null);
|
const clusterInfos = $derived($initialized ? getContext("clusters") : null);
|
||||||
// Is Selection Active
|
// Is Selection Active
|
||||||
const nodesActive = $derived(!(JSON.stringify(nodesState) === JSON.stringify({ from: 1, to: maxNumNodes })));
|
const nodesActive = $derived(!(JSON.stringify(nodesState) === JSON.stringify({ from: 0, to: null })));
|
||||||
const threadActive = $derived(!(JSON.stringify(threadState) === JSON.stringify({ from: 1, to: maxNumHWThreads })));
|
const threadActive = $derived(!(JSON.stringify(threadState) === JSON.stringify({ from: 0, to: null })));
|
||||||
const accActive = $derived(!(JSON.stringify(accState) === JSON.stringify({ from: 1, to: maxNumAccelerators })));
|
const accActive = $derived(!(JSON.stringify(accState) === JSON.stringify({ from: 0, to: null })));
|
||||||
// Block Apply if null
|
|
||||||
const disableApply = $derived(
|
|
||||||
nodesState.from === null || nodesState.to === null ||
|
|
||||||
threadState.from === null || threadState.to === null ||
|
|
||||||
accState.from === null || accState.to === null
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Reactive Effects | Svelte 5 onMount */
|
/* Reactive Effects | Svelte 5 onMount */
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -153,58 +147,28 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (
|
|
||||||
$initialized &&
|
|
||||||
pendingNumNodes.from == null &&
|
|
||||||
pendingNumNodes.to == null
|
|
||||||
) {
|
|
||||||
nodesState = { from: 1, to: maxNumNodes };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (
|
|
||||||
$initialized &&
|
|
||||||
pendingNumHWThreads.from == null &&
|
|
||||||
pendingNumHWThreads.to == null
|
|
||||||
) {
|
|
||||||
threadState = { from: 1, to: maxNumHWThreads };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (
|
|
||||||
$initialized &&
|
|
||||||
pendingNumAccelerators.from == null &&
|
|
||||||
pendingNumAccelerators.to == null
|
|
||||||
) {
|
|
||||||
accState = { from: 1, to: maxNumAccelerators };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
function setResources() {
|
function setResources() {
|
||||||
if (nodesActive) {
|
if (nodesActive) {
|
||||||
pendingNumNodes = {
|
pendingNumNodes = {
|
||||||
from: nodesState.from,
|
from: (!nodesState?.from) ? 0 : nodesState.from,
|
||||||
to: (nodesState.to == maxNumNodes) ? 0 : nodesState.to
|
to: (nodesState.to === null) ? 0 : nodesState.to
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
pendingNumNodes = { from: null, to: null};
|
pendingNumNodes = { from: null, to: null};
|
||||||
};
|
};
|
||||||
if (threadActive) {
|
if (threadActive) {
|
||||||
pendingNumHWThreads = {
|
pendingNumHWThreads = {
|
||||||
from: threadState.from,
|
from: (!threadState?.from) ? 0 : threadState.from,
|
||||||
to: (threadState.to == maxNumHWThreads) ? 0 : threadState.to
|
to: (threadState.to === null) ? 0 : threadState.to
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
pendingNumHWThreads = { from: null, to: null};
|
pendingNumHWThreads = { from: null, to: null};
|
||||||
};
|
};
|
||||||
if (accActive) {
|
if (accActive) {
|
||||||
pendingNumAccelerators = {
|
pendingNumAccelerators = {
|
||||||
from: accState.from,
|
from: (!accState?.from) ? 0 : accState.from,
|
||||||
to: (accState.to == maxNumAccelerators) ? 0 : accState.to
|
to: (accState.to === null) ? 0 : accState.to
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
pendingNumAccelerators = { from: null, to: null};
|
pendingNumAccelerators = { from: null, to: null};
|
||||||
@@ -249,7 +213,7 @@
|
|||||||
nodesState.from = detail[0];
|
nodesState.from = detail[0];
|
||||||
nodesState.to = detail[1];
|
nodesState.to = detail[1];
|
||||||
}}
|
}}
|
||||||
sliderMin={1}
|
sliderMin={0}
|
||||||
sliderMax={maxNumNodes}
|
sliderMax={maxNumNodes}
|
||||||
fromPreset={nodesState.from}
|
fromPreset={nodesState.from}
|
||||||
toPreset={nodesState.to}
|
toPreset={nodesState.to}
|
||||||
@@ -269,7 +233,7 @@
|
|||||||
threadState.from = detail[0];
|
threadState.from = detail[0];
|
||||||
threadState.to = detail[1];
|
threadState.to = detail[1];
|
||||||
}}
|
}}
|
||||||
sliderMin={1}
|
sliderMin={0}
|
||||||
sliderMax={maxNumHWThreads}
|
sliderMax={maxNumHWThreads}
|
||||||
fromPreset={threadState.from}
|
fromPreset={threadState.from}
|
||||||
toPreset={threadState.to}
|
toPreset={threadState.to}
|
||||||
@@ -289,7 +253,7 @@
|
|||||||
accState.from = detail[0];
|
accState.from = detail[0];
|
||||||
accState.to = detail[1];
|
accState.to = detail[1];
|
||||||
}}
|
}}
|
||||||
sliderMin={1}
|
sliderMin={0}
|
||||||
sliderMax={maxNumAccelerators}
|
sliderMax={maxNumAccelerators}
|
||||||
fromPreset={accState.from}
|
fromPreset={accState.from}
|
||||||
toPreset={accState.to}
|
toPreset={accState.to}
|
||||||
@@ -300,7 +264,6 @@
|
|||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={disableApply}
|
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
setResources();
|
setResources();
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
function setRanges() {
|
function setRanges() {
|
||||||
for (let as of availableStats) {
|
for (let as of availableStats) {
|
||||||
if (as.enabled) {
|
if (as.enabled) {
|
||||||
as.to = (as.to == as.peak) ? 0 : as.to
|
as.from = (!as?.from) ? 0 : as.from,
|
||||||
|
as.to = (as.to == null) ? 0 : as.to
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -42,8 +43,8 @@
|
|||||||
function resetRanges() {
|
function resetRanges() {
|
||||||
for (let as of availableStats) {
|
for (let as of availableStats) {
|
||||||
as.enabled = false
|
as.enabled = false
|
||||||
as.from = 1
|
as.from = null
|
||||||
as.to = as.peak
|
as.to = null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -66,13 +67,13 @@
|
|||||||
changeRange={(detail) => {
|
changeRange={(detail) => {
|
||||||
aStat.from = detail[0];
|
aStat.from = detail[0];
|
||||||
aStat.to = detail[1];
|
aStat.to = detail[1];
|
||||||
if (aStat.from == 1 && aStat.to == aStat.peak) {
|
if (aStat.from == 0 && aStat.to === null) {
|
||||||
aStat.enabled = false;
|
aStat.enabled = false;
|
||||||
} else {
|
} else {
|
||||||
aStat.enabled = true;
|
aStat.enabled = true;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sliderMin={1}
|
sliderMin={0}
|
||||||
sliderMax={aStat.peak}
|
sliderMax={aStat.peak}
|
||||||
fromPreset={aStat.from}
|
fromPreset={aStat.from}
|
||||||
toPreset={aStat.to}
|
toPreset={aStat.to}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
let {
|
let {
|
||||||
sliderMin,
|
sliderMin,
|
||||||
sliderMax,
|
sliderMax,
|
||||||
fromPreset = 1,
|
fromPreset = 0,
|
||||||
toPreset = 100,
|
toPreset = 100,
|
||||||
changeRange
|
changeRange
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
/* Derived */
|
/* Derived */
|
||||||
let pendingValues = $derived([fromPreset, toPreset]);
|
let pendingValues = $derived([fromPreset, toPreset]);
|
||||||
let sliderFrom = $derived(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
|
let sliderFrom = $derived(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
|
||||||
let sliderTo = $derived(Math.min(((toPreset == null ? sliderMin : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
|
let sliderTo = $derived(Math.min(((toPreset == null ? sliderMax : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
|
||||||
let inputFieldFrom = $derived(fromPreset ? fromPreset.toString() : null);
|
let inputFieldFrom = $derived(fromPreset != null ? fromPreset.toString() : null);
|
||||||
let inputFieldTo = $derived(toPreset ? toPreset.toString() : null);
|
let inputFieldTo = $derived(toPreset != null ? toPreset.toString() : null);
|
||||||
|
|
||||||
/* Var Init */
|
/* Var Init */
|
||||||
let timeoutId = null;
|
let timeoutId = null;
|
||||||
@@ -79,17 +79,22 @@
|
|||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
const newV = Number.parseInt(evt.target.value);
|
const newV = Number.parseInt(evt.target.value);
|
||||||
const newP = clamp((newV - sliderMin) / (sliderMax - sliderMin), 0., 1.)
|
const newP = clamp((newV - sliderMin) / (sliderMax - sliderMin), 0., 1., target)
|
||||||
updateStates(newV, newP, target);
|
updateStates(newV, newP, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clamp(x, testMin, testMax) {
|
function clamp(x, testMin, testMax, target) {
|
||||||
return x < testMin
|
if (isNaN(x)) {
|
||||||
? testMin
|
if (target == 'from') return testMin
|
||||||
: (x > testMax
|
else if (target == 'to') return testMax
|
||||||
? testMax
|
} else {
|
||||||
: x
|
return x < testMin
|
||||||
);
|
? testMin
|
||||||
|
: (x > testMax
|
||||||
|
? testMax
|
||||||
|
: x
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function draggable(node) {
|
function draggable(node) {
|
||||||
@@ -159,23 +164,23 @@
|
|||||||
|
|
||||||
<div class="double-range-container">
|
<div class="double-range-container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<input class="form-control" type="text" placeholder="from..." value={inputFieldFrom}
|
<input class="form-control" type="text" placeholder={`${sliderMin} ...`} value={inputFieldFrom}
|
||||||
oninput={(e) => {
|
oninput={(e) => {
|
||||||
inputChanged(e, 'from');
|
inputChanged(e, 'from');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if inputFieldFrom != sliderMin?.toString() && inputFieldTo != sliderMax?.toString() }
|
{#if (inputFieldFrom && inputFieldFrom != sliderMin?.toString()) && inputFieldTo != null }
|
||||||
<span>Selected: Range <b> {inputFieldFrom} </b> - <b> {inputFieldTo} </b></span>
|
<span>Selected: Range <b> {inputFieldFrom} </b> - <b> {inputFieldTo} </b></span>
|
||||||
{:else if inputFieldFrom != sliderMin?.toString() && inputFieldTo == sliderMax?.toString() }
|
{:else if (inputFieldFrom && inputFieldFrom != sliderMin?.toString()) && inputFieldTo == null }
|
||||||
<span>Selected: More than <b> {inputFieldFrom} </b> </span>
|
<span>Selected: More Than Equal <b> {inputFieldFrom} </b> </span>
|
||||||
{:else if inputFieldFrom == sliderMin?.toString() && inputFieldTo != sliderMax?.toString() }
|
{:else if (!inputFieldFrom || inputFieldFrom == sliderMin?.toString()) && inputFieldTo != null }
|
||||||
<span>Selected: Less than <b> {inputFieldTo} </b></span>
|
<span>Selected: Less Than Equal <b> {inputFieldTo} </b></span>
|
||||||
{:else}
|
{:else}
|
||||||
<span><i>No Selection</i></span>
|
<span><i>No Selection</i></span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<input class="form-control" type="text" placeholder="to..." value={inputFieldTo}
|
<input class="form-control" type="text" placeholder={`... ${sliderMax} ...`} value={inputFieldTo}
|
||||||
oninput={(e) => {
|
oninput={(e) => {
|
||||||
inputChanged(e, 'to');
|
inputChanged(e, 'to');
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -347,8 +347,8 @@ export function getStatsItems(presetStats = []) {
|
|||||||
field: presetEntry.field,
|
field: presetEntry.field,
|
||||||
text: `${gm.name} (${gm.footprint})`,
|
text: `${gm.name} (${gm.footprint})`,
|
||||||
metric: gm.name,
|
metric: gm.name,
|
||||||
from: presetEntry.from,
|
from: presetEntry?.from || 0,
|
||||||
to: (presetEntry.to == 0) ? mc.peak : presetEntry.to,
|
to: (presetEntry.to == 0) ? null : presetEntry.to,
|
||||||
peak: mc.peak,
|
peak: mc.peak,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
unit: `${gm?.unit?.prefix ? gm.unit.prefix : ''}${gm.unit.base}`
|
unit: `${gm?.unit?.prefix ? gm.unit.prefix : ''}${gm.unit.base}`
|
||||||
@@ -358,8 +358,8 @@ export function getStatsItems(presetStats = []) {
|
|||||||
field: `${gm.name}_${gm.footprint}`,
|
field: `${gm.name}_${gm.footprint}`,
|
||||||
text: `${gm.name} (${gm.footprint})`,
|
text: `${gm.name} (${gm.footprint})`,
|
||||||
metric: gm.name,
|
metric: gm.name,
|
||||||
from: 1,
|
from: 0,
|
||||||
to: mc.peak,
|
to: null,
|
||||||
peak: mc.peak,
|
peak: mc.peak,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
unit: `${gm?.unit?.prefix ? gm.unit.prefix : ''}${gm.unit.base}`
|
unit: `${gm?.unit?.prefix ? gm.unit.prefix : ''}${gm.unit.base}`
|
||||||
|
|||||||
Reference in New Issue
Block a user