mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
commit
88fe367ecb
@ -23,6 +23,8 @@ import (
|
|||||||
const Version = 1
|
const Version = 1
|
||||||
|
|
||||||
var ar FsArchive
|
var ar FsArchive
|
||||||
|
var srcPath string
|
||||||
|
var dstPath string
|
||||||
|
|
||||||
func loadJobData(filename string) (*JobData, error) {
|
func loadJobData(filename string) (*JobData, error) {
|
||||||
|
|
||||||
@ -243,17 +245,65 @@ func deepCopyClusterConfig(co *Cluster) schema.Cluster {
|
|||||||
return cn
|
return cn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertJob(job *JobMeta) {
|
||||||
|
// check if source data is available, otherwise skip job
|
||||||
|
src_data_path := getPath(job, srcPath, "data.json")
|
||||||
|
info, err := os.Stat(src_data_path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if info.Size() == 0 {
|
||||||
|
fmt.Printf("Skip path %s, filesize is 0 Bytes.", src_data_path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := getPath(job, dstPath, "meta.json")
|
||||||
|
err = os.MkdirAll(filepath.Dir(path), 0750)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jmn := deepCopyJobMeta(job)
|
||||||
|
if err = EncodeJobMeta(f, &jmn); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = f.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err = os.Create(getPath(job, dstPath, "data.json"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var jd *JobData
|
||||||
|
jd, err = loadJobData(src_data_path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
jdn := deepCopyJobData(jd, job.Cluster, job.SubCluster)
|
||||||
|
if err := EncodeJobData(f, jdn); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var flagLogLevel, flagConfigFile string
|
var flagLogLevel, flagConfigFile string
|
||||||
var flagLogDateTime bool
|
var flagLogDateTime, debug bool
|
||||||
var srcPath string
|
|
||||||
var dstPath string
|
|
||||||
|
|
||||||
flag.BoolVar(&flagLogDateTime, "logdate", false, "Set this flag to add date and time to log messages")
|
flag.BoolVar(&flagLogDateTime, "logdate", false, "Set this flag to add date and time to log messages")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Set this flag to force sequential execution for debugging")
|
||||||
flag.StringVar(&flagLogLevel, "loglevel", "warn", "Sets the logging level: `[debug,info,warn (default),err,fatal,crit]`")
|
flag.StringVar(&flagLogLevel, "loglevel", "warn", "Sets the logging level: `[debug,info,warn (default),err,fatal,crit]`")
|
||||||
flag.StringVar(&flagConfigFile, "config", "./config.json", "Specify alternative path to `config.json`")
|
flag.StringVar(&flagConfigFile, "config", "./config.json", "Specify alternative path to `config.json`")
|
||||||
flag.StringVar(&srcPath, "s", "./var/job-archive", "Specify the source job archive path")
|
flag.StringVar(&srcPath, "src", "./var/job-archive", "Specify the source job archive path")
|
||||||
flag.StringVar(&dstPath, "d", "./var/job-archive-new", "Specify the destination job archive path")
|
flag.StringVar(&dstPath, "dst", "./var/job-archive-new", "Specify the destination job archive path")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(srcPath, "version.txt")); !errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(filepath.Join(srcPath, "version.txt")); !errors.Is(err, os.ErrNotExist) {
|
||||||
@ -302,59 +352,18 @@ func main() {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for job := range ar.Iter() {
|
for job := range ar.Iter() {
|
||||||
// fmt.Printf("Job %d\n", job.JobID)
|
if debug {
|
||||||
job := job
|
fmt.Printf("Job %d\n", job.JobID)
|
||||||
wg.Add(1)
|
convertJob(job)
|
||||||
|
} else {
|
||||||
|
job := job
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
// check if source data is available, otherwise skip job
|
convertJob(job)
|
||||||
src_data_path := getPath(job, srcPath, "data.json")
|
}()
|
||||||
info, err := os.Stat(src_data_path)
|
}
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if info.Size() == 0 {
|
|
||||||
fmt.Printf("Skip path %s, filesize is 0 Bytes.", src_data_path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path := getPath(job, dstPath, "meta.json")
|
|
||||||
err = os.MkdirAll(filepath.Dir(path), 0750)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
jmn := deepCopyJobMeta(job)
|
|
||||||
if err = EncodeJobMeta(f, &jmn); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err = f.Close(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err = os.Create(getPath(job, dstPath, "data.json"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var jd *JobData
|
|
||||||
jd, err = loadJobData(src_data_path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
jdn := deepCopyJobData(jd, job.Cluster, job.SubCluster)
|
|
||||||
if err := EncodeJobData(f, jdn); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from 'svelte'
|
import { onMount, getContext } from 'svelte'
|
||||||
import { init } from './utils.js'
|
import { init } from './utils.js'
|
||||||
import { Table, Row, Col, Button, Icon, Card, Spinner } from 'sveltestrap'
|
import { Table, Row, Col, Button, Icon, Card, Spinner, Input } from 'sveltestrap'
|
||||||
import { queryStore, gql, getContextClient } from '@urql/svelte'
|
import { queryStore, gql, getContextClient } from '@urql/svelte'
|
||||||
import Filters from './filters/Filters.svelte'
|
import Filters from './filters/Filters.svelte'
|
||||||
import JobList from './joblist/JobList.svelte'
|
import JobList from './joblist/JobList.svelte'
|
||||||
@ -25,6 +25,13 @@
|
|||||||
let metrics = ccconfig.plot_list_selectedMetrics, isMetricsSelectionOpen = false
|
let metrics = ccconfig.plot_list_selectedMetrics, isMetricsSelectionOpen = false
|
||||||
let w1, w2, histogramHeight = 250
|
let w1, w2, histogramHeight = 250
|
||||||
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
|
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
|
||||||
|
let resize = false
|
||||||
|
/* Resize Context
|
||||||
|
* A) Each viewport change triggers histogram rerender due to variable dimensions clearing canvas if not rerendered
|
||||||
|
* B) Opening filters (and some other things) triggers small change in viewport dimensions (Fix here?)
|
||||||
|
* A+B) Histogram rerenders if filters opened, high performance impact if dataload heavy
|
||||||
|
* Solution: Default to fixed histogram dimensions, allow user to enable automatic resizing
|
||||||
|
*/
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
$: stats = queryStore({
|
$: stats = queryStore({
|
||||||
@ -130,27 +137,47 @@
|
|||||||
<th scope="row">Total Core Hours</th>
|
<th scope="row">Total Core Hours</th>
|
||||||
<td>{$stats.data.jobsStatistics[0].totalCoreHours}</td>
|
<td>{$stats.data.jobsStatistics[0].totalCoreHours}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Toggle Histogram Resizing</th>
|
||||||
|
<td><Input id="c3" value={resize} type="switch" on:change={() => (resize = !resize)}/></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Col>
|
</Col>
|
||||||
<div class="col-4" style="text-align: center;" bind:clientWidth={w1}>
|
<div class="col-4" style="text-align: center;" bind:clientWidth={w1}>
|
||||||
<b>Duration Distribution</b>
|
<b>Duration Distribution</b>
|
||||||
{#key $stats.data.jobsStatistics[0].histDuration}
|
{#key $stats.data.jobsStatistics[0].histDuration}
|
||||||
|
{#if resize == true}
|
||||||
<Histogram
|
<Histogram
|
||||||
data={$stats.data.jobsStatistics[0].histDuration}
|
data={$stats.data.jobsStatistics[0].histDuration}
|
||||||
width={w1 - 25} height={histogramHeight}
|
width={w1 - 25} height={histogramHeight}
|
||||||
xlabel="Current Runtimes [h]"
|
xlabel="Current Runtimes [h]"
|
||||||
ylabel="Number of Jobs"/>
|
ylabel="Number of Jobs"/>
|
||||||
|
{:else}
|
||||||
|
<Histogram
|
||||||
|
data={$stats.data.jobsStatistics[0].histDuration}
|
||||||
|
width={400} height={250}
|
||||||
|
xlabel="Current Runtimes [h]"
|
||||||
|
ylabel="Number of Jobs"/>
|
||||||
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4" style="text-align: center;" bind:clientWidth={w2}>
|
<div class="col-4" style="text-align: center;" bind:clientWidth={w2}>
|
||||||
<b>Number of Nodes Distribution</b>
|
<b>Number of Nodes Distribution</b>
|
||||||
{#key $stats.data.jobsStatistics[0].histNumNodes}
|
{#key $stats.data.jobsStatistics[0].histNumNodes}
|
||||||
|
{#if resize == true}
|
||||||
<Histogram
|
<Histogram
|
||||||
data={$stats.data.jobsStatistics[0].histNumNodes}
|
data={$stats.data.jobsStatistics[0].histNumNodes}
|
||||||
width={w2 - 25} height={histogramHeight}
|
width={w2 - 25} height={histogramHeight}
|
||||||
xlabel="Allocated Nodes [#]"
|
xlabel="Allocated Nodes [#]"
|
||||||
ylabel="Number of Jobs" />
|
ylabel="Number of Jobs" />
|
||||||
|
{:else}
|
||||||
|
<Histogram
|
||||||
|
data={$stats.data.jobsStatistics[0].histNumNodes}
|
||||||
|
width={400} height={250}
|
||||||
|
xlabel="Allocated Nodes [#]"
|
||||||
|
ylabel="Number of Jobs" />
|
||||||
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
<th>Running Jobs</th>
|
<th>Running Jobs</th>
|
||||||
<th>Total Jobs</th>
|
<th>Total Jobs</th>
|
||||||
{{if .User.HasRole .Roles.admin}}
|
{{if .User.HasRole .Roles.admin}}
|
||||||
|
<th>Status View</th>
|
||||||
<th>System View</th>
|
<th>System View</th>
|
||||||
<th>Analysis View</th>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -21,8 +21,8 @@
|
|||||||
<td>{{.ID}}</td>
|
<td>{{.ID}}</td>
|
||||||
<td><a href="/monitoring/jobs/?cluster={{.ID}}&state=running">{{.RunningJobs}} jobs</a></td>
|
<td><a href="/monitoring/jobs/?cluster={{.ID}}&state=running">{{.RunningJobs}} jobs</a></td>
|
||||||
<td><a href="/monitoring/jobs/?cluster={{.ID}}">{{.TotalJobs}} jobs</a></td>
|
<td><a href="/monitoring/jobs/?cluster={{.ID}}">{{.TotalJobs}} jobs</a></td>
|
||||||
|
<td><a href="/monitoring/status/{{.ID}}">Status View</a></td>
|
||||||
<td><a href="/monitoring/systems/{{.ID}}">System View</a></td>
|
<td><a href="/monitoring/systems/{{.ID}}">System View</a></td>
|
||||||
<td><a href="/monitoring/analysis/{{.ID}}">Analysis View</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
18
web/web.go
18
web/web.go
@ -9,6 +9,7 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/auth"
|
"github.com/ClusterCockpit/cc-backend/internal/auth"
|
||||||
@ -46,6 +47,23 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if path == "templates/imprint.tmpl" {
|
||||||
|
if _, err := os.Stat("./var/imprint.tmpl"); err == nil {
|
||||||
|
log.Info("overwrite imprint.tmpl with local file")
|
||||||
|
templates[strings.TrimPrefix(path, "templates/")] =
|
||||||
|
template.Must(template.Must(base.Clone()).ParseFiles("./var/imprint.tmpl"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path == "templates/privacy.tmpl" {
|
||||||
|
if _, err := os.Stat("./var/privacy.tmpl"); err == nil {
|
||||||
|
log.Info("overwrite privacy.tmpl with local file")
|
||||||
|
templates[strings.TrimPrefix(path, "templates/")] =
|
||||||
|
template.Must(template.Must(base.Clone()).ParseFiles("./var/privacy.tmpl"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
templates[strings.TrimPrefix(path, "templates/")] = template.Must(template.Must(base.Clone()).ParseFS(templateFiles, path))
|
templates[strings.TrimPrefix(path, "templates/")] = template.Must(template.Must(base.Clone()).ParseFS(templateFiles, path))
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user