mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25:06 +01:00 
			
		
		
		
	@@ -23,6 +23,8 @@ import (
 | 
			
		||||
const Version = 1
 | 
			
		||||
 | 
			
		||||
var ar FsArchive
 | 
			
		||||
var srcPath string
 | 
			
		||||
var dstPath string
 | 
			
		||||
 | 
			
		||||
func loadJobData(filename string) (*JobData, error) {
 | 
			
		||||
 | 
			
		||||
@@ -243,17 +245,65 @@ func deepCopyClusterConfig(co *Cluster) schema.Cluster {
 | 
			
		||||
	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() {
 | 
			
		||||
	var flagLogLevel, flagConfigFile string
 | 
			
		||||
	var flagLogDateTime bool
 | 
			
		||||
	var srcPath string
 | 
			
		||||
	var dstPath string
 | 
			
		||||
	var flagLogDateTime, debug bool
 | 
			
		||||
 | 
			
		||||
	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(&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(&dstPath, "d", "./var/job-archive-new", "Specify the destination job archive path")
 | 
			
		||||
	flag.StringVar(&srcPath, "src", "./var/job-archive", "Specify the source job archive path")
 | 
			
		||||
	flag.StringVar(&dstPath, "dst", "./var/job-archive-new", "Specify the destination job archive path")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if _, err := os.Stat(filepath.Join(srcPath, "version.txt")); !errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
@@ -302,59 +352,18 @@ func main() {
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
 | 
			
		||||
	for job := range ar.Iter() {
 | 
			
		||||
		// fmt.Printf("Job %d\n", job.JobID)
 | 
			
		||||
		job := job
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
		if debug {
 | 
			
		||||
			fmt.Printf("Job %d\n", job.JobID)
 | 
			
		||||
			convertJob(job)
 | 
			
		||||
		} else {
 | 
			
		||||
			job := job
 | 
			
		||||
			wg.Add(1)
 | 
			
		||||
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
			// 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)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
			go func() {
 | 
			
		||||
				defer wg.Done()
 | 
			
		||||
				convertJob(job)
 | 
			
		||||
			}()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<script>
 | 
			
		||||
    import { onMount, getContext } from 'svelte'
 | 
			
		||||
    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 Filters from './filters/Filters.svelte'
 | 
			
		||||
    import JobList from './joblist/JobList.svelte'
 | 
			
		||||
@@ -25,6 +25,13 @@
 | 
			
		||||
    let metrics = ccconfig.plot_list_selectedMetrics, isMetricsSelectionOpen = false
 | 
			
		||||
    let w1, w2, histogramHeight = 250
 | 
			
		||||
    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();
 | 
			
		||||
    $: stats = queryStore({
 | 
			
		||||
@@ -130,27 +137,47 @@
 | 
			
		||||
                        <th scope="row">Total Core Hours</th>
 | 
			
		||||
                        <td>{$stats.data.jobsStatistics[0].totalCoreHours}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <th scope="row">Toggle Histogram Resizing</th>
 | 
			
		||||
                        <td><Input id="c3" value={resize} type="switch" on:change={() => (resize = !resize)}/></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </Table>
 | 
			
		||||
        </Col>
 | 
			
		||||
        <div class="col-4" style="text-align: center;" bind:clientWidth={w1}>
 | 
			
		||||
            <b>Duration Distribution</b>
 | 
			
		||||
            {#key $stats.data.jobsStatistics[0].histDuration}
 | 
			
		||||
                {#if resize == true}
 | 
			
		||||
                <Histogram
 | 
			
		||||
                    data={$stats.data.jobsStatistics[0].histDuration}
 | 
			
		||||
                    width={w1 - 25} height={histogramHeight}
 | 
			
		||||
                    xlabel="Current Runtimes [h]" 
 | 
			
		||||
                    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}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-4" style="text-align: center;" bind:clientWidth={w2}>
 | 
			
		||||
            <b>Number of Nodes Distribution</b>
 | 
			
		||||
            {#key $stats.data.jobsStatistics[0].histNumNodes}
 | 
			
		||||
                {#if resize == true}
 | 
			
		||||
                <Histogram
 | 
			
		||||
                    data={$stats.data.jobsStatistics[0].histNumNodes}
 | 
			
		||||
                    width={w2 - 25} height={histogramHeight}
 | 
			
		||||
                    xlabel="Allocated Nodes [#]"
 | 
			
		||||
                    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}
 | 
			
		||||
        </div>
 | 
			
		||||
    {/if}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@
 | 
			
		||||
                    <th>Running Jobs</th>
 | 
			
		||||
                    <th>Total Jobs</th>
 | 
			
		||||
                    {{if .User.HasRole .Roles.admin}}
 | 
			
		||||
                        <th>Status View</th>
 | 
			
		||||
                        <th>System View</th>
 | 
			
		||||
                        <th>Analysis View</th>
 | 
			
		||||
                    {{end}}
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
@@ -21,8 +21,8 @@
 | 
			
		||||
                            <td>{{.ID}}</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/status/{{.ID}}">Status View</a></td>
 | 
			
		||||
                            <td><a href="/monitoring/systems/{{.ID}}">System View</a></td>
 | 
			
		||||
                            <td><a href="/monitoring/analysis/{{.ID}}">Analysis View</a></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    {{end}}
 | 
			
		||||
                {{else}}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								web/web.go
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								web/web.go
									
									
									
									
									
								
							@@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/ClusterCockpit/cc-backend/internal/auth"
 | 
			
		||||
@@ -46,6 +47,23 @@ func init() {
 | 
			
		||||
			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))
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user