mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25:06 +01:00 
			
		
		
		
	simplify polar plot data code, add scaling for shared jobs to polar
This commit is contained in:
		@@ -303,6 +303,7 @@ func (r *queryResolver) JobMetrics(ctx context.Context, id string, metrics []str
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// JobsFootprints is the resolver for the jobsFootprints field.
 | 
					// JobsFootprints is the resolver for the jobsFootprints field.
 | 
				
			||||||
func (r *queryResolver) JobsFootprints(ctx context.Context, filter []*model.JobFilter, metrics []string) (*model.Footprints, error) {
 | 
					func (r *queryResolver) JobsFootprints(ctx context.Context, filter []*model.JobFilter, metrics []string) (*model.Footprints, error) {
 | 
				
			||||||
 | 
						// NOTE: Legacy Naming! This resolver is for normalized histograms in analysis view only - *Not* related to DB "footprint" column!
 | 
				
			||||||
	return r.jobsFootprints(ctx, filter, metrics)
 | 
						return r.jobsFootprints(ctx, filter, metrics)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,16 +2,12 @@
 | 
				
			|||||||
    @component Polar Plot based on chartJS Radar
 | 
					    @component Polar Plot based on chartJS Radar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Properties:
 | 
					    Properties:
 | 
				
			||||||
    - `footprintData [Object]?`: job.footprint content, evaluated in regards to peak config in jobSummary.svelte [Default: null]
 | 
					    - `polarMetrics [Object]?`: Metric names and scaled peak values for rendering polar plot [Default: [] ]
 | 
				
			||||||
    - `metrics [String]?`: Metric names to display as polar plot [Default: null]
 | 
					 | 
				
			||||||
    - `cluster GraphQL.Cluster?`: Cluster Object of the parent job [Default: null]
 | 
					 | 
				
			||||||
    - `subCluster GraphQL.SubCluster?`: SubCluster Object of the parent job [Default: null]
 | 
					 | 
				
			||||||
    - `jobMetrics [GraphQL.JobMetricWithName]?`: Metric data [Default: null]
 | 
					    - `jobMetrics [GraphQL.JobMetricWithName]?`: Metric data [Default: null]
 | 
				
			||||||
    - `height Number?`: Plot height [Default: 365]
 | 
					    - `height Number?`: Plot height [Default: 365]
 | 
				
			||||||
 -->
 | 
					 -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { getContext } from 'svelte'
 | 
					 | 
				
			||||||
    import { Radar } from 'svelte-chartjs';
 | 
					    import { Radar } from 'svelte-chartjs';
 | 
				
			||||||
    import {
 | 
					    import {
 | 
				
			||||||
        Chart as ChartJS,
 | 
					        Chart as ChartJS,
 | 
				
			||||||
@@ -34,54 +30,37 @@
 | 
				
			|||||||
        LineElement
 | 
					        LineElement
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let footprintData = null;
 | 
					    export let polarMetrics = [];
 | 
				
			||||||
    export let metrics = null;
 | 
					 | 
				
			||||||
    export let cluster = null;
 | 
					 | 
				
			||||||
    export let subCluster = null;
 | 
					 | 
				
			||||||
    export let jobMetrics = null;
 | 
					    export let jobMetrics = null;
 | 
				
			||||||
    export let height = 350;
 | 
					    export let height = 350;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getLabels() {
 | 
					    const labels = polarMetrics
 | 
				
			||||||
        if (footprintData) {
 | 
					        .filter((m) => (m.peak != null))
 | 
				
			||||||
            return footprintData.filter(fpd => {
 | 
					        .map(pm => pm.name)
 | 
				
			||||||
                if (!jobMetrics.find(m => m.name == fpd.name && m.scope == "node" || fpd.impact == 4)) {
 | 
					        .sort(function (a, b) {return ((a > b) ? 1 : ((b > a) ? -1 : 0))});
 | 
				
			||||||
                    console.warn(`PolarPlot: No metric data for '${fpd.name}'`)
 | 
					
 | 
				
			||||||
                    return false
 | 
					    function loadData(type) {
 | 
				
			||||||
                }
 | 
					        if (!labels) {
 | 
				
			||||||
                return true
 | 
					            console.warn("Empty 'metrics' array prop! Cannot render Polar.")
 | 
				
			||||||
            })
 | 
					            return []
 | 
				
			||||||
            .map(filtered => filtered.name)
 | 
					 | 
				
			||||||
            .sort(function (a, b) {
 | 
					 | 
				
			||||||
                return ((a > b) ? 1 : ((b > a) ? -1 : 0));
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return metrics.filter(name => {
 | 
					            if (type === 'avg') {
 | 
				
			||||||
                if (!jobMetrics.find(m => m.name == name && m.scope == "node")) {
 | 
					                return getValues(getAvg)
 | 
				
			||||||
                    console.warn(`PolarPlot: No metric data for '${name}'`)
 | 
					            } else if (type === 'max') {
 | 
				
			||||||
                    return false
 | 
					                return getValues(getMax)
 | 
				
			||||||
 | 
					            } else if (type === 'min') {
 | 
				
			||||||
 | 
					                return getValues(getMin)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                return true
 | 
					            console.log('Unknown Type For Polar Data')
 | 
				
			||||||
            })
 | 
					            return []
 | 
				
			||||||
            .sort(function (a, b) {
 | 
					 | 
				
			||||||
                return ((a > b) ? 1 : ((b > a) ? -1 : 0));
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const labels = getLabels();
 | 
					    // Helpers
 | 
				
			||||||
    const getMetricConfig = getContext("getMetricConfig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const getValuesForStatGeneric = (getStat) => labels.map(name => {
 | 
					    const getValues = (getStat) => labels.map(name => {
 | 
				
			||||||
        // TODO: Requires Scaling if Shared Job
 | 
					        // Peak is adapted and scaled for job shared state
 | 
				
			||||||
        const peak = getMetricConfig(cluster, subCluster, name).peak
 | 
					        const peak = polarMetrics.find(m => m.name == name).peak
 | 
				
			||||||
        const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
 | 
					 | 
				
			||||||
        const value = getStat(metric.metric) / peak
 | 
					 | 
				
			||||||
        return value <= 1. ? value : 1.
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const getValuesForStatFootprint = (getStat) => labels.map(name => {
 | 
					 | 
				
			||||||
        // FootprintData 'Peak' is pre-scaled for Shared Jobs in JobSummary Component
 | 
					 | 
				
			||||||
        const peak = footprintData.find(fpd => fpd.name === name).peak
 | 
					 | 
				
			||||||
        const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
 | 
					        const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
 | 
				
			||||||
        const value = getStat(metric.metric) / peak
 | 
					        const value = getStat(metric.metric) / peak
 | 
				
			||||||
        return value <= 1. ? value : 1.
 | 
					        return value <= 1. ? value : 1.
 | 
				
			||||||
@@ -108,36 +87,14 @@
 | 
				
			|||||||
        return avg / metric.series.length
 | 
					        return avg / metric.series.length
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function loadDataGeneric(type) {
 | 
					    // Chart JS Objects
 | 
				
			||||||
        if (type === 'avg') {
 | 
					 | 
				
			||||||
            return getValuesForStatGeneric(getAvg)
 | 
					 | 
				
			||||||
        } else if (type === 'max') {
 | 
					 | 
				
			||||||
            return getValuesForStatGeneric(getMax)
 | 
					 | 
				
			||||||
        } else if (type === 'min') {
 | 
					 | 
				
			||||||
            return getValuesForStatGeneric(getMin)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        console.log('Unknown Type For Polar Data')
 | 
					 | 
				
			||||||
        return []
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function loadDataForFootprint(type) {
 | 
					 | 
				
			||||||
        if (type === 'avg') {
 | 
					 | 
				
			||||||
            return getValuesForStatFootprint(getAvg)
 | 
					 | 
				
			||||||
        } else if (type === 'max') {
 | 
					 | 
				
			||||||
            return getValuesForStatFootprint(getMax)
 | 
					 | 
				
			||||||
        } else if (type === 'min') {
 | 
					 | 
				
			||||||
            return getValuesForStatFootprint(getMin)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        console.log('Unknown Type For Polar Data')
 | 
					 | 
				
			||||||
        return []
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const data = {
 | 
					    const data = {
 | 
				
			||||||
        labels: labels,
 | 
					        labels: labels,
 | 
				
			||||||
        datasets: [
 | 
					        datasets: [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                label: 'Max',
 | 
					                label: 'Max',
 | 
				
			||||||
                data: footprintData ? loadDataForFootprint('max') : loadDataGeneric('max'), // Node Scope Only
 | 
					                data: loadData('max'), // Node Scope Only
 | 
				
			||||||
                fill: 1,
 | 
					                fill: 1,
 | 
				
			||||||
                backgroundColor: 'rgba(0, 0, 255, 0.25)',
 | 
					                backgroundColor: 'rgba(0, 0, 255, 0.25)',
 | 
				
			||||||
                borderColor: 'rgb(0, 0, 255)',
 | 
					                borderColor: 'rgb(0, 0, 255)',
 | 
				
			||||||
@@ -148,7 +105,7 @@
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                label: 'Avg',
 | 
					                label: 'Avg',
 | 
				
			||||||
                data: footprintData ? loadDataForFootprint('avg') : loadDataGeneric('avg'), // Node Scope Only
 | 
					                data: loadData('avg'), // Node Scope Only
 | 
				
			||||||
                fill: 2,
 | 
					                fill: 2,
 | 
				
			||||||
                backgroundColor: 'rgba(255, 210, 0, 0.25)',
 | 
					                backgroundColor: 'rgba(255, 210, 0, 0.25)',
 | 
				
			||||||
                borderColor: 'rgb(255, 210, 0)',
 | 
					                borderColor: 'rgb(255, 210, 0)',
 | 
				
			||||||
@@ -159,7 +116,7 @@
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                label: 'Min',
 | 
					                label: 'Min',
 | 
				
			||||||
                data: footprintData ? loadDataForFootprint('min') : loadDataGeneric('min'), // Node Scope Only
 | 
					                data: loadData('min'), // Node Scope Only
 | 
				
			||||||
                fill: true,
 | 
					                fill: true,
 | 
				
			||||||
                backgroundColor: 'rgba(255, 0, 0, 0.25)',
 | 
					                backgroundColor: 'rgba(255, 0, 0, 0.25)',
 | 
				
			||||||
                borderColor: 'rgb(255, 0, 0)',
 | 
					                borderColor: 'rgb(255, 0, 0)',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,9 +30,18 @@
 | 
				
			|||||||
  export let height = "400px";
 | 
					  export let height = "400px";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const ccconfig = getContext("cc-config")
 | 
					  const ccconfig = getContext("cc-config")
 | 
				
			||||||
  const showFootprint = !!ccconfig[`job_view_showFootprint`];
 | 
					  const showFootprintTab = !!ccconfig[`job_view_showFootprint`];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const footprintData = job?.footprint?.map((jf) => {
 | 
					  const polarMetrics = job?.footprint?.map((jf) => {
 | 
				
			||||||
 | 
					      const fmt = findJobFootprintThresholds(job, jf.stat, getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name));
 | 
				
			||||||
 | 
					      // If no matching metric config: Metric will be omitted in polar
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        name: jf.name,
 | 
				
			||||||
 | 
					        peak: fmt ? fmt.peak : null
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const footprintData = !showFootprintTab ? null : job?.footprint?.map((jf) => {
 | 
				
			||||||
    const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name);
 | 
					    const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name);
 | 
				
			||||||
    if (fmc) {
 | 
					    if (fmc) {
 | 
				
			||||||
      // Unit
 | 
					      // Unit
 | 
				
			||||||
@@ -193,7 +202,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<Card class="overflow-auto" style="width: {width}; height: {height}">
 | 
					<Card class="overflow-auto" style="width: {width}; height: {height}">
 | 
				
			||||||
  <TabContent> <!-- on:tab={(e) => (status = e.detail)} -->
 | 
					  <TabContent> <!-- on:tab={(e) => (status = e.detail)} -->
 | 
				
			||||||
    {#if showFootprint}
 | 
					    {#if showFootprintTab}
 | 
				
			||||||
      <TabPane tabId="foot" tab="Footprint" active>
 | 
					      <TabPane tabId="foot" tab="Footprint" active>
 | 
				
			||||||
        <CardBody>
 | 
					        <CardBody>
 | 
				
			||||||
          {#each footprintData as fpd, index}
 | 
					          {#each footprintData as fpd, index}
 | 
				
			||||||
@@ -279,10 +288,10 @@
 | 
				
			|||||||
        </CardBody>
 | 
					        </CardBody>
 | 
				
			||||||
      </TabPane>
 | 
					      </TabPane>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
    <TabPane tabId="polar" tab="Polar" active={!showFootprint}>
 | 
					    <TabPane tabId="polar" tab="Polar" active={!showFootprintTab}>
 | 
				
			||||||
      <CardBody>
 | 
					      <CardBody>
 | 
				
			||||||
        <Polar
 | 
					        <Polar
 | 
				
			||||||
          {footprintData}
 | 
					          {polarMetrics}
 | 
				
			||||||
          {jobMetrics}
 | 
					          {jobMetrics}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </CardBody>
 | 
					      </CardBody>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user