mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-25 04:49:05 +01:00
Merge branch 'master' into 37-provide-a-s3-compatible-storage-backend-for-the-job-archive
This commit is contained in:
commit
03a496d477
@ -28,6 +28,11 @@ type Job {
|
|||||||
resources: [Resource!]!
|
resources: [Resource!]!
|
||||||
concurrentJobs: JobLinkResultList
|
concurrentJobs: JobLinkResultList
|
||||||
|
|
||||||
|
memUsedMax: Float
|
||||||
|
flopsAnyAvg: Float
|
||||||
|
memBwAvg: Float
|
||||||
|
loadAvg: Float
|
||||||
|
|
||||||
metaData: Any
|
metaData: Any
|
||||||
userData: User
|
userData: User
|
||||||
}
|
}
|
||||||
@ -198,7 +203,7 @@ type Query {
|
|||||||
jobsFootprints(filter: [JobFilter!], metrics: [String!]!): Footprints
|
jobsFootprints(filter: [JobFilter!], metrics: [String!]!): Footprints
|
||||||
|
|
||||||
jobs(filter: [JobFilter!], page: PageRequest, order: OrderByInput): JobResultList!
|
jobs(filter: [JobFilter!], page: PageRequest, order: OrderByInput): JobResultList!
|
||||||
jobsStatistics(filter: [JobFilter!], page: PageRequest, sortBy: SortByAggregate, groupBy: Aggregate): [JobsStatistics!]!
|
jobsStatistics(filter: [JobFilter!], metrics: [String!], page: PageRequest, sortBy: SortByAggregate, groupBy: Aggregate): [JobsStatistics!]!
|
||||||
|
|
||||||
rooflineHeatmap(filter: [JobFilter!]!, rows: Int!, cols: Int!, minX: Float!, minY: Float!, maxX: Float!, maxY: Float!): [[Float!]!]!
|
rooflineHeatmap(filter: [JobFilter!]!, rows: Int!, cols: Int!, minX: Float!, minY: Float!, maxX: Float!, maxY: Float!): [[Float!]!]!
|
||||||
|
|
||||||
@ -286,6 +291,19 @@ type HistoPoint {
|
|||||||
value: Int!
|
value: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetricHistoPoints {
|
||||||
|
metric: String!
|
||||||
|
unit: String!
|
||||||
|
data: [MetricHistoPoint!]
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetricHistoPoint {
|
||||||
|
bin: Int
|
||||||
|
count: Int!
|
||||||
|
min: Int
|
||||||
|
max: Int
|
||||||
|
}
|
||||||
|
|
||||||
type JobsStatistics {
|
type JobsStatistics {
|
||||||
id: ID! # If `groupBy` was used, ID of the user/project/cluster
|
id: ID! # If `groupBy` was used, ID of the user/project/cluster
|
||||||
name: String! # if User-Statistics: Given Name of Account (ID) Owner
|
name: String! # if User-Statistics: Given Name of Account (ID) Owner
|
||||||
@ -303,6 +321,7 @@ type JobsStatistics {
|
|||||||
histNumNodes: [HistoPoint!]! # value: number of nodes, count: number of jobs with that number of nodes
|
histNumNodes: [HistoPoint!]! # value: number of nodes, count: number of jobs with that number of nodes
|
||||||
histNumCores: [HistoPoint!]! # value: number of cores, count: number of jobs with that number of cores
|
histNumCores: [HistoPoint!]! # value: number of cores, count: number of jobs with that number of cores
|
||||||
histNumAccs: [HistoPoint!]! # value: number of accs, count: number of jobs with that number of accs
|
histNumAccs: [HistoPoint!]! # value: number of accs, count: number of jobs with that number of accs
|
||||||
|
histMetrics: [MetricHistoPoints!]! # metric: metricname, data array of histopoints: value: metric average bin, count: number of jobs with that metric average
|
||||||
}
|
}
|
||||||
|
|
||||||
input PageRequest {
|
input PageRequest {
|
||||||
|
8
go.mod
8
go.mod
@ -26,7 +26,7 @@ require (
|
|||||||
github.com/swaggo/http-swagger v1.3.3
|
github.com/swaggo/http-swagger v1.3.3
|
||||||
github.com/swaggo/swag v1.16.2
|
github.com/swaggo/swag v1.16.2
|
||||||
github.com/vektah/gqlparser/v2 v2.5.10
|
github.com/vektah/gqlparser/v2 v2.5.10
|
||||||
golang.org/x/crypto v0.15.0
|
golang.org/x/crypto v0.16.0
|
||||||
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea
|
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,11 +84,11 @@ require (
|
|||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/net v0.18.0 // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/oauth2 v0.5.0 // indirect
|
golang.org/x/oauth2 v0.5.0 // indirect
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.15.0 // indirect
|
golang.org/x/tools v0.16.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
17
go.sum
17
go.sum
@ -1302,8 +1302,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
|
|||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -1425,8 +1425,8 @@ golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@ -1585,9 +1585,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@ -1694,8 +1693,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
|
||||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -85,22 +85,23 @@ type JobResultList struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type JobsStatistics struct {
|
type JobsStatistics struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TotalJobs int `json:"totalJobs"`
|
TotalJobs int `json:"totalJobs"`
|
||||||
RunningJobs int `json:"runningJobs"`
|
RunningJobs int `json:"runningJobs"`
|
||||||
ShortJobs int `json:"shortJobs"`
|
ShortJobs int `json:"shortJobs"`
|
||||||
TotalWalltime int `json:"totalWalltime"`
|
TotalWalltime int `json:"totalWalltime"`
|
||||||
TotalNodes int `json:"totalNodes"`
|
TotalNodes int `json:"totalNodes"`
|
||||||
TotalNodeHours int `json:"totalNodeHours"`
|
TotalNodeHours int `json:"totalNodeHours"`
|
||||||
TotalCores int `json:"totalCores"`
|
TotalCores int `json:"totalCores"`
|
||||||
TotalCoreHours int `json:"totalCoreHours"`
|
TotalCoreHours int `json:"totalCoreHours"`
|
||||||
TotalAccs int `json:"totalAccs"`
|
TotalAccs int `json:"totalAccs"`
|
||||||
TotalAccHours int `json:"totalAccHours"`
|
TotalAccHours int `json:"totalAccHours"`
|
||||||
HistDuration []*HistoPoint `json:"histDuration"`
|
HistDuration []*HistoPoint `json:"histDuration"`
|
||||||
HistNumNodes []*HistoPoint `json:"histNumNodes"`
|
HistNumNodes []*HistoPoint `json:"histNumNodes"`
|
||||||
HistNumCores []*HistoPoint `json:"histNumCores"`
|
HistNumCores []*HistoPoint `json:"histNumCores"`
|
||||||
HistNumAccs []*HistoPoint `json:"histNumAccs"`
|
HistNumAccs []*HistoPoint `json:"histNumAccs"`
|
||||||
|
HistMetrics []*MetricHistoPoints `json:"histMetrics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetricFootprints struct {
|
type MetricFootprints struct {
|
||||||
@ -108,6 +109,19 @@ type MetricFootprints struct {
|
|||||||
Data []schema.Float `json:"data"`
|
Data []schema.Float `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetricHistoPoint struct {
|
||||||
|
Bin *int `json:"bin,omitempty"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
Min *int `json:"min,omitempty"`
|
||||||
|
Max *int `json:"max,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetricHistoPoints struct {
|
||||||
|
Metric string `json:"metric"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
Data []*MetricHistoPoint `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type NodeMetrics struct {
|
type NodeMetrics struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
SubCluster string `json:"subCluster"`
|
SubCluster string `json:"subCluster"`
|
||||||
|
@ -2,7 +2,7 @@ package graph
|
|||||||
|
|
||||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
// will be copied through when generating and any unknown code will be moved to the end.
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
// Code generated by github.com/99designs/gqlgen version v0.17.36
|
// Code generated by github.com/99designs/gqlgen version v0.17.40
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -244,7 +244,7 @@ func (r *queryResolver) Jobs(ctx context.Context, filter []*model.JobFilter, pag
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JobsStatistics is the resolver for the jobsStatistics field.
|
// JobsStatistics is the resolver for the jobsStatistics field.
|
||||||
func (r *queryResolver) JobsStatistics(ctx context.Context, filter []*model.JobFilter, page *model.PageRequest, sortBy *model.SortByAggregate, groupBy *model.Aggregate) ([]*model.JobsStatistics, error) {
|
func (r *queryResolver) JobsStatistics(ctx context.Context, filter []*model.JobFilter, metrics []string, page *model.PageRequest, sortBy *model.SortByAggregate, groupBy *model.Aggregate) ([]*model.JobsStatistics, error) {
|
||||||
var err error
|
var err error
|
||||||
var stats []*model.JobsStatistics
|
var stats []*model.JobsStatistics
|
||||||
|
|
||||||
@ -291,6 +291,17 @@ func (r *queryResolver) JobsStatistics(ctx context.Context, filter []*model.JobF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if requireField(ctx, "histMetrics") {
|
||||||
|
if groupBy == nil {
|
||||||
|
stats[0], err = r.Repo.AddMetricHistograms(ctx, filter, metrics, stats[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("metric histograms only implemented without groupBy argument")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ func GetJobRepository() *JobRepository {
|
|||||||
var jobColumns []string = []string{
|
var jobColumns []string = []string{
|
||||||
"job.id", "job.job_id", "job.user", "job.project", "job.cluster", "job.subcluster", "job.start_time", "job.partition", "job.array_job_id",
|
"job.id", "job.job_id", "job.user", "job.project", "job.cluster", "job.subcluster", "job.start_time", "job.partition", "job.array_job_id",
|
||||||
"job.num_nodes", "job.num_hwthreads", "job.num_acc", "job.exclusive", "job.monitoring_status", "job.smt", "job.job_state",
|
"job.num_nodes", "job.num_hwthreads", "job.num_acc", "job.exclusive", "job.monitoring_status", "job.smt", "job.job_state",
|
||||||
"job.duration", "job.walltime", "job.resources", // "job.meta_data",
|
"job.duration", "job.walltime", "job.resources", "job.mem_used_max", "job.flops_any_avg", "job.mem_bw_avg", "job.load_avg", // "job.meta_data",
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) {
|
func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) {
|
||||||
@ -68,7 +68,7 @@ func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) {
|
|||||||
if err := row.Scan(
|
if err := row.Scan(
|
||||||
&job.ID, &job.JobID, &job.User, &job.Project, &job.Cluster, &job.SubCluster, &job.StartTimeUnix, &job.Partition, &job.ArrayJobId,
|
&job.ID, &job.JobID, &job.User, &job.Project, &job.Cluster, &job.SubCluster, &job.StartTimeUnix, &job.Partition, &job.ArrayJobId,
|
||||||
&job.NumNodes, &job.NumHWThreads, &job.NumAcc, &job.Exclusive, &job.MonitoringStatus, &job.SMT, &job.State,
|
&job.NumNodes, &job.NumHWThreads, &job.NumAcc, &job.Exclusive, &job.MonitoringStatus, &job.SMT, &job.State,
|
||||||
&job.Duration, &job.Walltime, &job.RawResources /*&job.RawMetaData*/); err != nil {
|
&job.Duration, &job.Walltime, &job.RawResources, &job.MemUsedMax, &job.FlopsAnyAvg, &job.MemBwAvg, &job.LoadAvg /*&job.RawMetaData*/); err != nil {
|
||||||
log.Warnf("Error while scanning rows (Job): %v", err)
|
log.Warnf("Error while scanning rows (Job): %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -483,6 +483,7 @@ func (r *JobRepository) MarkArchived(
|
|||||||
case "mem_bw":
|
case "mem_bw":
|
||||||
stmt = stmt.Set("mem_bw_avg", stats.Avg)
|
stmt = stmt.Set("mem_bw_avg", stats.Avg)
|
||||||
case "load":
|
case "load":
|
||||||
|
stmt = stmt.Set("load_avg", stats.Avg)
|
||||||
case "cpu_load":
|
case "cpu_load":
|
||||||
stmt = stmt.Set("load_avg", stats.Avg)
|
stmt = stmt.Set("load_avg", stats.Avg)
|
||||||
case "net_bw":
|
case "net_bw":
|
||||||
|
@ -96,7 +96,7 @@ func SecurityCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilde
|
|||||||
user := GetUserFromContext(ctx)
|
user := GetUserFromContext(ctx)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
var qnil sq.SelectBuilder
|
var qnil sq.SelectBuilder
|
||||||
return qnil, fmt.Errorf("user context is nil!")
|
return qnil, fmt.Errorf("user context is nil")
|
||||||
} else if user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport, schema.RoleApi}) { // Admin & Co. : All jobs
|
} else if user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport, schema.RoleApi}) { // Admin & Co. : All jobs
|
||||||
return query, nil
|
return query, nil
|
||||||
} else if user.HasRole(schema.RoleManager) { // Manager : Add filter for managed projects' jobs only + personal jobs
|
} else if user.HasRole(schema.RoleManager) { // Manager : Add filter for managed projects' jobs only + personal jobs
|
||||||
|
@ -8,11 +8,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
"github.com/ClusterCockpit/cc-backend/internal/config"
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
||||||
|
"github.com/ClusterCockpit/cc-backend/internal/metricdata"
|
||||||
|
"github.com/ClusterCockpit/cc-backend/pkg/archive"
|
||||||
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
||||||
|
"github.com/ClusterCockpit/cc-backend/pkg/schema"
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -450,6 +454,39 @@ func (r *JobRepository) AddHistograms(
|
|||||||
return stat, nil
|
return stat, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Requires thresholds for metric from config for cluster? Of all clusters and use largest? split to 10 + 1 for artifacts?
|
||||||
|
func (r *JobRepository) AddMetricHistograms(
|
||||||
|
ctx context.Context,
|
||||||
|
filter []*model.JobFilter,
|
||||||
|
metrics []string,
|
||||||
|
stat *model.JobsStatistics) (*model.JobsStatistics, error) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Running Jobs Only: First query jobdata from sqlite, then query data and make bins
|
||||||
|
for _, f := range filter {
|
||||||
|
if f.State != nil {
|
||||||
|
if len(f.State) == 1 && f.State[0] == "running" {
|
||||||
|
stat.HistMetrics = r.runningJobsMetricStatisticsHistogram(ctx, metrics, filter)
|
||||||
|
log.Debugf("Timer AddMetricHistograms %s", time.Since(start))
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other cases: Query and make bins in sqlite directly
|
||||||
|
for _, m := range metrics {
|
||||||
|
metricHisto, err := r.jobsMetricStatisticsHistogram(ctx, m, filter)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Error while loading job metric statistics histogram: %s", m)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stat.HistMetrics = append(stat.HistMetrics, metricHisto)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Timer AddMetricHistograms %s", time.Since(start))
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
// `value` must be the column grouped by, but renamed to "value"
|
// `value` must be the column grouped by, but renamed to "value"
|
||||||
func (r *JobRepository) jobsStatisticsHistogram(
|
func (r *JobRepository) jobsStatisticsHistogram(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@ -487,3 +524,231 @@ func (r *JobRepository) jobsStatisticsHistogram(
|
|||||||
log.Debugf("Timer jobsStatisticsHistogram %s", time.Since(start))
|
log.Debugf("Timer jobsStatisticsHistogram %s", time.Since(start))
|
||||||
return points, nil
|
return points, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *JobRepository) jobsMetricStatisticsHistogram(
|
||||||
|
ctx context.Context,
|
||||||
|
metric string,
|
||||||
|
filters []*model.JobFilter) (*model.MetricHistoPoints, error) {
|
||||||
|
|
||||||
|
var dbMetric string
|
||||||
|
switch metric {
|
||||||
|
case "cpu_load":
|
||||||
|
dbMetric = "load_avg"
|
||||||
|
case "flops_any":
|
||||||
|
dbMetric = "flops_any_avg"
|
||||||
|
case "mem_bw":
|
||||||
|
dbMetric = "mem_bw_avg"
|
||||||
|
case "mem_used":
|
||||||
|
dbMetric = "mem_used_max"
|
||||||
|
case "net_bw":
|
||||||
|
dbMetric = "net_bw_avg"
|
||||||
|
case "file_bw":
|
||||||
|
dbMetric = "file_bw_avg"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%s not implemented", metric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get specific Peak or largest Peak
|
||||||
|
var metricConfig *schema.MetricConfig
|
||||||
|
var peak float64 = 0.0
|
||||||
|
var unit string = ""
|
||||||
|
|
||||||
|
for _, f := range filters {
|
||||||
|
if f.Cluster != nil {
|
||||||
|
metricConfig = archive.GetMetricConfig(*f.Cluster.Eq, metric)
|
||||||
|
peak = metricConfig.Peak
|
||||||
|
unit = metricConfig.Unit.Prefix + metricConfig.Unit.Base
|
||||||
|
log.Debugf("Cluster %s filter found with peak %f for %s", *f.Cluster.Eq, peak, metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if peak == 0.0 {
|
||||||
|
for _, c := range archive.Clusters {
|
||||||
|
for _, m := range c.MetricConfig {
|
||||||
|
if m.Name == metric {
|
||||||
|
if m.Peak > peak {
|
||||||
|
peak = m.Peak
|
||||||
|
}
|
||||||
|
if unit == "" {
|
||||||
|
unit = m.Unit.Prefix + m.Unit.Base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Debugf("Metric %s: DB %s, Peak %f, Unit %s", metric, dbMetric, peak, unit)
|
||||||
|
// Make bins, see https://jereze.com/code/sql-histogram/
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
crossJoinQuery := sq.Select(
|
||||||
|
fmt.Sprintf(`max(%s) as max`, dbMetric),
|
||||||
|
fmt.Sprintf(`min(%s) as min`, dbMetric),
|
||||||
|
).From("job").Where(
|
||||||
|
fmt.Sprintf(`%s is not null`, dbMetric),
|
||||||
|
).Where(
|
||||||
|
fmt.Sprintf(`%s <= %f`, dbMetric, peak),
|
||||||
|
)
|
||||||
|
|
||||||
|
crossJoinQuery, cjqerr := SecurityCheck(ctx, crossJoinQuery)
|
||||||
|
|
||||||
|
if cjqerr != nil {
|
||||||
|
return nil, cjqerr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range filters {
|
||||||
|
crossJoinQuery = BuildWhereClause(f, crossJoinQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
crossJoinQuerySql, crossJoinQueryArgs, sqlerr := crossJoinQuery.ToSql()
|
||||||
|
if sqlerr != nil {
|
||||||
|
return nil, sqlerr
|
||||||
|
}
|
||||||
|
|
||||||
|
bins := 10
|
||||||
|
binQuery := fmt.Sprintf(`CAST( (case when job.%s = value.max then value.max*0.999999999 else job.%s end - value.min) / (value.max - value.min) * %d as INTEGER )`, dbMetric, dbMetric, bins)
|
||||||
|
|
||||||
|
mainQuery := sq.Select(
|
||||||
|
fmt.Sprintf(`%s + 1 as bin`, binQuery),
|
||||||
|
fmt.Sprintf(`count(job.%s) as count`, dbMetric),
|
||||||
|
fmt.Sprintf(`CAST(((value.max / %d) * (%s )) as INTEGER ) as min`, bins, binQuery),
|
||||||
|
fmt.Sprintf(`CAST(((value.max / %d) * (%s + 1 )) as INTEGER ) as max`, bins, binQuery),
|
||||||
|
).From("job").CrossJoin(
|
||||||
|
fmt.Sprintf(`(%s) as value`, crossJoinQuerySql), crossJoinQueryArgs...,
|
||||||
|
).Where(fmt.Sprintf(`job.%s is not null and job.%s <= %f`, dbMetric, dbMetric, peak))
|
||||||
|
|
||||||
|
mainQuery, qerr := SecurityCheck(ctx, mainQuery)
|
||||||
|
|
||||||
|
if qerr != nil {
|
||||||
|
return nil, qerr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range filters {
|
||||||
|
mainQuery = BuildWhereClause(f, mainQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize query with Grouping and Ordering
|
||||||
|
mainQuery = mainQuery.GroupBy("bin").OrderBy("bin")
|
||||||
|
|
||||||
|
rows, err := mainQuery.RunWith(r.DB).Query()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while running mainQuery: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
points := make([]*model.MetricHistoPoint, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
point := model.MetricHistoPoint{}
|
||||||
|
if err := rows.Scan(&point.Bin, &point.Count, &point.Min, &point.Max); err != nil {
|
||||||
|
log.Warnf("Error while scanning rows for %s", metric)
|
||||||
|
return nil, err // Totally bricks cc-backend if returned and if all metrics requested?
|
||||||
|
}
|
||||||
|
|
||||||
|
points = append(points, &point)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := model.MetricHistoPoints{Metric: metric, Unit: unit, Data: points}
|
||||||
|
|
||||||
|
log.Debugf("Timer jobsStatisticsHistogram %s", time.Since(start))
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *JobRepository) runningJobsMetricStatisticsHistogram(
|
||||||
|
ctx context.Context,
|
||||||
|
metrics []string,
|
||||||
|
filters []*model.JobFilter) []*model.MetricHistoPoints {
|
||||||
|
|
||||||
|
// Get Jobs
|
||||||
|
jobs, err := r.QueryJobs(ctx, filters, &model.PageRequest{Page: 1, ItemsPerPage: 500 + 1}, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while querying jobs for footprint: %s", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(jobs) > 500 {
|
||||||
|
log.Errorf("too many jobs matched (max: %d)", 500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get AVGs from metric repo
|
||||||
|
avgs := make([][]schema.Float, len(metrics))
|
||||||
|
for i := range avgs {
|
||||||
|
avgs[i] = make([]schema.Float, 0, len(jobs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range jobs {
|
||||||
|
if job.MonitoringStatus == schema.MonitoringStatusDisabled || job.MonitoringStatus == schema.MonitoringStatusArchivingFailed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := metricdata.LoadAverages(job, metrics, avgs, ctx); err != nil {
|
||||||
|
log.Errorf("Error while loading averages for histogram: %s", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate metrics to fill endresult
|
||||||
|
data := make([]*model.MetricHistoPoints, 0)
|
||||||
|
for idx, metric := range metrics {
|
||||||
|
// Get specific Peak or largest Peak
|
||||||
|
var metricConfig *schema.MetricConfig
|
||||||
|
var peak float64 = 0.0
|
||||||
|
var unit string = ""
|
||||||
|
|
||||||
|
for _, f := range filters {
|
||||||
|
if f.Cluster != nil {
|
||||||
|
metricConfig = archive.GetMetricConfig(*f.Cluster.Eq, metric)
|
||||||
|
peak = metricConfig.Peak
|
||||||
|
unit = metricConfig.Unit.Prefix + metricConfig.Unit.Base
|
||||||
|
log.Debugf("Cluster %s filter found with peak %f for %s", *f.Cluster.Eq, peak, metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if peak == 0.0 {
|
||||||
|
for _, c := range archive.Clusters {
|
||||||
|
for _, m := range c.MetricConfig {
|
||||||
|
if m.Name == metric {
|
||||||
|
if m.Peak > peak {
|
||||||
|
peak = m.Peak
|
||||||
|
}
|
||||||
|
if unit == "" {
|
||||||
|
unit = m.Unit.Prefix + m.Unit.Base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make and fill bins
|
||||||
|
bins := 10.0
|
||||||
|
peakBin := peak / bins
|
||||||
|
|
||||||
|
points := make([]*model.MetricHistoPoint, 0)
|
||||||
|
for b := 0; b < 10; b++ {
|
||||||
|
count := 0
|
||||||
|
bindex := b + 1
|
||||||
|
bmin := math.Round(peakBin * float64(b))
|
||||||
|
bmax := math.Round(peakBin * (float64(b) + 1.0))
|
||||||
|
|
||||||
|
// Iterate AVG values for indexed metric and count for bins
|
||||||
|
for _, val := range avgs[idx] {
|
||||||
|
if float64(val) >= bmin && float64(val) < bmax {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bminint := int(bmin)
|
||||||
|
bmaxint := int(bmax)
|
||||||
|
|
||||||
|
// Append Bin to Metric Result Array
|
||||||
|
point := model.MetricHistoPoint{Bin: &bindex, Count: count, Min: &bminint, Max: &bmaxint}
|
||||||
|
points = append(points, &point)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append Metric Result Array to final results array
|
||||||
|
result := model.MetricHistoPoints{Metric: metric, Unit: unit, Data: points}
|
||||||
|
data = append(data, &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/pkg/schema"
|
|
||||||
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
||||||
|
"github.com/ClusterCockpit/cc-backend/pkg/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Clusters []*schema.Cluster
|
var Clusters []*schema.Cluster
|
||||||
|
@ -54,10 +54,10 @@ type Job struct {
|
|||||||
BaseJob
|
BaseJob
|
||||||
StartTimeUnix int64 `json:"-" db:"start_time" example:"1649723812"` // Start epoch time stamp in seconds
|
StartTimeUnix int64 `json:"-" db:"start_time" example:"1649723812"` // Start epoch time stamp in seconds
|
||||||
StartTime time.Time `json:"startTime"` // Start time as 'time.Time' data type
|
StartTime time.Time `json:"startTime"` // Start time as 'time.Time' data type
|
||||||
MemUsedMax float64 `json:"-" db:"mem_used_max"` // MemUsedMax as Float64
|
MemUsedMax float64 `json:"memUsedMax" db:"mem_used_max"` // MemUsedMax as Float64
|
||||||
FlopsAnyAvg float64 `json:"-" db:"flops_any_avg"` // FlopsAnyAvg as Float64
|
FlopsAnyAvg float64 `json:"flopsAnyAvg" db:"flops_any_avg"` // FlopsAnyAvg as Float64
|
||||||
MemBwAvg float64 `json:"-" db:"mem_bw_avg"` // MemBwAvg as Float64
|
MemBwAvg float64 `json:"memBwAvg" db:"mem_bw_avg"` // MemBwAvg as Float64
|
||||||
LoadAvg float64 `json:"-" db:"load_avg"` // LoadAvg as Float64
|
LoadAvg float64 `json:"loadAvg" db:"load_avg"` // LoadAvg as Float64
|
||||||
NetBwAvg float64 `json:"-" db:"net_bw_avg"` // NetBwAvg as Float64
|
NetBwAvg float64 `json:"-" db:"net_bw_avg"` // NetBwAvg as Float64
|
||||||
NetDataVolTotal float64 `json:"-" db:"net_data_vol_total"` // NetDataVolTotal as Float64
|
NetDataVolTotal float64 `json:"-" db:"net_data_vol_total"` // NetDataVolTotal as Float64
|
||||||
FileBwAvg float64 `json:"-" db:"file_bw_avg"` // FileBwAvg as Float64
|
FileBwAvg float64 `json:"-" db:"file_bw_avg"` // FileBwAvg as Float64
|
||||||
|
355
web/frontend/package-lock.json
generated
355
web/frontend/package-lock.json
generated
@ -12,7 +12,10 @@
|
|||||||
"@rollup/plugin-replace": "^5.0.2",
|
"@rollup/plugin-replace": "^5.0.2",
|
||||||
"@urql/svelte": "^4.0.1",
|
"@urql/svelte": "^4.0.1",
|
||||||
"chart.js": "^4.3.3",
|
"chart.js": "^4.3.3",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
|
"mathjs": "^12.0.0",
|
||||||
"svelte-chartjs": "^3.1.2",
|
"svelte-chartjs": "^3.1.2",
|
||||||
"sveltestrap": "^5.11.1",
|
"sveltestrap": "^5.11.1",
|
||||||
"uplot": "^1.6.24",
|
"uplot": "^1.6.24",
|
||||||
@ -42,6 +45,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.23.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz",
|
||||||
|
"integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||||
@ -90,9 +104,12 @@
|
|||||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.19",
|
"version": "0.3.20",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||||
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
|
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||||
|
"version": "0.3.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||||
|
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@ -139,9 +156,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/plugin-node-resolve": {
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
"version": "15.2.1",
|
"version": "15.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||||
"integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==",
|
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
|
||||||
|
"version": "15.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||||
|
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/pluginutils": "^5.0.1",
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
@ -155,7 +175,8 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"rollup": "^2.78.0||^3.0.0"
|
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||||
|
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"rollup": {
|
"rollup": {
|
||||||
@ -164,18 +185,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/plugin-replace": {
|
"node_modules/@rollup/plugin-replace": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz",
|
||||||
"integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==",
|
"integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==",
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/pluginutils": "^5.0.1",
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
"magic-string": "^0.27.0"
|
"magic-string": "^0.30.3"
|
||||||
|
"magic-string": "^0.30.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"rollup": "^1.20.0||^2.0.0||^3.0.0"
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"rollup": {
|
"rollup": {
|
||||||
@ -183,10 +209,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/plugin-replace/node_modules/magic-string": {
|
||||||
|
"version": "0.30.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
|
||||||
|
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-replace/node_modules/magic-string": {
|
||||||
|
"version": "0.30.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
|
||||||
|
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-terser": {
|
"node_modules/@rollup/plugin-terser": {
|
||||||
"version": "0.4.3",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
||||||
"integrity": "sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==",
|
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
|
||||||
|
"version": "0.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
||||||
|
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"serialize-javascript": "^6.0.1",
|
"serialize-javascript": "^6.0.1",
|
||||||
@ -197,7 +248,8 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"rollup": "^2.x || ^3.x"
|
"rollup": "^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
"rollup": "^2.0.0||^3.0.0||^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"rollup": {
|
"rollup": {
|
||||||
@ -206,9 +258,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/pluginutils": {
|
"node_modules/@rollup/pluginutils": {
|
||||||
"version": "5.0.3",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
|
||||||
"integrity": "sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==",
|
"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "^1.0.0",
|
"@types/estree": "^1.0.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
@ -218,7 +270,8 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"rollup": "^1.20.0||^2.0.0||^3.0.0"
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"rollup": {
|
"rollup": {
|
||||||
@ -227,15 +280,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@timohausmann/quadtree-js": {
|
"node_modules/@timohausmann/quadtree-js": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/@timohausmann/quadtree-js/-/quadtree-js-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@timohausmann/quadtree-js/-/quadtree-js-1.2.6.tgz",
|
||||||
"integrity": "sha512-WcH3pouYtpyLjTCRvNP0WuSV4m7mRyYhLzW44egveFryT7pJhpDsdIJASEe37iCFNA0vmEpqTYGoG0siyXEthA==",
|
"integrity": "sha512-EoAoLMFV2JfSG8+8XD9xWJQdyvfEB5xNpiQWGD7rTDSbDQQV8IVpkm0uOIxwJZ+1uC9hHKri9GmJ5wBSUO4jfg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||||
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
|
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.2",
|
||||||
@ -244,9 +300,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@urql/core": {
|
"node_modules/@urql/core": {
|
||||||
"version": "4.1.1",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@urql/core/-/core-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@urql/core/-/core-4.2.0.tgz",
|
||||||
"integrity": "sha512-iIoAy6BY+BUZZ7KIpnMT7C9q+ULf5ZCVxGe3/i7WZSJBrQa2h1QkIMhL+8fAKmOn9gt83jSIv5drWWnhZ9izEA==",
|
"integrity": "sha512-GRkZ4kECR9UohWAjiSk2UYUetco6/PqSrvyC4AH6g16tyqEShA63M232cfbE1J9XJPaGNjia14Gi+oOqzp144w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0no-co/graphql.web": "^1.0.1",
|
"@0no-co/graphql.web": "^1.0.1",
|
||||||
"wonka": "^6.3.2"
|
"wonka": "^6.3.2"
|
||||||
@ -265,9 +321,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.10.0",
|
"version": "8.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
|
||||||
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
|
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
|
||||||
|
"version": "8.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
|
||||||
|
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
@ -310,9 +369,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chart.js": {
|
"node_modules/chart.js": {
|
||||||
"version": "4.3.3",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
|
||||||
"integrity": "sha512-aTk7pBw+x6sQYhon/NR3ikfUJuym/LdgpTlgZRe2PaEhjUMKBKyNaFCMVRAyTEWYFNO7qRu7iQVqOw/OqzxZxQ==",
|
"integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kurkle/color": "^0.3.0"
|
"@kurkle/color": "^0.3.0"
|
||||||
},
|
},
|
||||||
@ -332,6 +391,38 @@
|
|||||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/complex.js": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/infusion"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "2.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||||
|
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.21.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.11"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/date-fns"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/decimal.js": {
|
||||||
|
"version": "10.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
|
||||||
|
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
|
||||||
|
},
|
||||||
"node_modules/deepmerge": {
|
"node_modules/deepmerge": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
@ -341,11 +432,28 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escape-latex": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
|
||||||
|
},
|
||||||
"node_modules/estree-walker": {
|
"node_modules/estree-walker": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/fraction.js": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://github.com/sponsors/rawify"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@ -367,10 +475,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
@ -392,23 +510,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/graphql": {
|
"node_modules/graphql": {
|
||||||
"version": "16.8.0",
|
"version": "16.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
|
||||||
"integrity": "sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg==",
|
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
|
||||||
|
"version": "16.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
|
||||||
|
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has": {
|
"node_modules/hasown": {
|
||||||
"version": "1.0.3",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
||||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.2"
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4"
|
||||||
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/inflight": {
|
"node_modules/inflight": {
|
||||||
@ -443,12 +570,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.13.0",
|
"version": "2.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||||
"integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
|
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||||
|
"version": "2.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||||
|
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has": "^1.0.3"
|
"hasown": "^2.0.0"
|
||||||
|
"hasown": "^2.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@ -469,10 +600,17 @@
|
|||||||
"@types/estree": "*"
|
"@types/estree": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/javascript-natural-sort": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||||
|
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
|
||||||
"integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
|
"integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
|
||||||
|
"dev": true,
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||||
},
|
},
|
||||||
@ -480,6 +618,28 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mathjs": {
|
||||||
|
"version": "12.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-12.0.0.tgz",
|
||||||
|
"integrity": "sha512-Oz3swPplNPe7taoP6WrkKhQzhDE2SwvOgLzu8H3EN+hEadw2GjEJUm6Xl+hrioHoB8g2BYb3gfw1glSzhdBKYw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2",
|
||||||
|
"complex.js": "^2.1.1",
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
|
"escape-latex": "^1.2.0",
|
||||||
|
"fraction.js": "4.3.4",
|
||||||
|
"javascript-natural-sort": "^0.7.1",
|
||||||
|
"seedrandom": "^3.0.5",
|
||||||
|
"tiny-emitter": "^2.1.0",
|
||||||
|
"typed-function": "^4.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mathjs": "bin/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "5.1.6",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||||
@ -527,10 +687,23 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||||
|
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
||||||
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||||
|
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.4",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
"integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==",
|
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||||
|
"version": "1.22.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.13.0",
|
||||||
@ -554,9 +727,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "3.28.1",
|
"version": "3.29.4",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||||
"integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
|
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
||||||
|
"version": "3.29.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||||
|
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
@ -570,9 +746,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup-plugin-css-only": {
|
"node_modules/rollup-plugin-css-only": {
|
||||||
"version": "4.3.0",
|
"version": "4.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-4.5.2.tgz",
|
||||||
"integrity": "sha512-BsiCqJJQzZh2lQiHY5irejRoJ3I1EUFHEi5PjVqsr+EmOh54YrWVwd3YZEXnQJ2+fzlhif0YM/Kf0GuH90GAdQ==",
|
"integrity": "sha512-7rj9+jB17Pz8LNcPgtMUb16JcgD8lxQMK9HcGfAVhMK3na/WXes3oGIo5QsrQQVqtgAU6q6KnQNXJrYunaUIQQ==",
|
||||||
|
"version": "4.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-4.5.2.tgz",
|
||||||
|
"integrity": "sha512-7rj9+jB17Pz8LNcPgtMUb16JcgD8lxQMK9HcGfAVhMK3na/WXes3oGIo5QsrQQVqtgAU6q6KnQNXJrYunaUIQQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/pluginutils": "5"
|
"@rollup/pluginutils": "5"
|
||||||
@ -581,7 +760,8 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"rollup": "<4"
|
"rollup": "<5"
|
||||||
|
"rollup": "<5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup-plugin-svelte": {
|
"node_modules/rollup-plugin-svelte": {
|
||||||
@ -634,6 +814,11 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/seedrandom": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
|
||||||
|
},
|
||||||
"node_modules/serialize-javascript": {
|
"node_modules/serialize-javascript": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
|
||||||
@ -644,9 +829,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/smob": {
|
"node_modules/smob": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
|
||||||
"integrity": "sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==",
|
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
@ -698,9 +886,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sveltestrap": {
|
"node_modules/sveltestrap": {
|
||||||
"version": "5.11.1",
|
"version": "5.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/sveltestrap/-/sveltestrap-5.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/sveltestrap/-/sveltestrap-5.11.2.tgz",
|
||||||
"integrity": "sha512-FIvPIEU1VolqMN1wi2XrC8aehWVbIJEST7zPfPbOUUfPimyx9giN4nA3We5wkXrBUaifXA8CSIwuHFvf3CmYQw==",
|
"integrity": "sha512-fkLqIUh2QHBoom7v6kHI85grLeOqplmvtnTiA5Ck2gchzpVmwXWaWpf8qWhCFxfDuMhJBPlWbJvtSmwpDEowrg==",
|
||||||
|
"version": "5.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sveltestrap/-/sveltestrap-5.11.2.tgz",
|
||||||
|
"integrity": "sha512-fkLqIUh2QHBoom7v6kHI85grLeOqplmvtnTiA5Ck2gchzpVmwXWaWpf8qWhCFxfDuMhJBPlWbJvtSmwpDEowrg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.8"
|
"@popperjs/core": "^2.11.8"
|
||||||
},
|
},
|
||||||
@ -709,9 +900,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.19.2",
|
"version": "5.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.25.0.tgz",
|
||||||
"integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
|
"integrity": "sha512-we0I9SIsfvNUMP77zC9HG+MylwYYsGFSBG8qm+13oud2Yh+O104y614FRbyjpxys16jZwot72Fpi827YvGzuqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
@ -726,10 +917,26 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-emitter": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||||
|
},
|
||||||
|
"node_modules/typed-function": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uplot": {
|
"node_modules/uplot": {
|
||||||
"version": "1.6.25",
|
"version": "1.6.27",
|
||||||
"resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.25.tgz",
|
"resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.27.tgz",
|
||||||
"integrity": "sha512-eWLAhEaGtIcVBiS67mC2UC0yV+G6eYLS2rU67N4F2JVWjt7uBMg4xKXUYGW0dEz9G+m7fNatjCVXHts4gjyuMQ=="
|
"integrity": "sha512-78U4ss5YeU65kQkOC/QAKiyII+4uo+TYUJJKvuxRzeSpk/s5sjpY1TL0agkmhHBBShpvLtmbHIEiM7+C5lBULg=="
|
||||||
|
"version": "1.6.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.27.tgz",
|
||||||
|
"integrity": "sha512-78U4ss5YeU65kQkOC/QAKiyII+4uo+TYUJJKvuxRzeSpk/s5sjpY1TL0agkmhHBBShpvLtmbHIEiM7+C5lBULg=="
|
||||||
},
|
},
|
||||||
"node_modules/wonka": {
|
"node_modules/wonka": {
|
||||||
"version": "6.3.4",
|
"version": "6.3.4",
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
"@rollup/plugin-replace": "^5.0.2",
|
"@rollup/plugin-replace": "^5.0.2",
|
||||||
"@urql/svelte": "^4.0.1",
|
"@urql/svelte": "^4.0.1",
|
||||||
"chart.js": "^4.3.3",
|
"chart.js": "^4.3.3",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
|
"mathjs": "^12.0.0",
|
||||||
"svelte-chartjs": "^3.1.2",
|
"svelte-chartjs": "^3.1.2",
|
||||||
"sveltestrap": "^5.11.1",
|
"sveltestrap": "^5.11.1",
|
||||||
"uplot": "^1.6.24",
|
"uplot": "^1.6.24",
|
||||||
|
@ -389,9 +389,10 @@
|
|||||||
<Histogram
|
<Histogram
|
||||||
data={convert2uplot(item.bins)}
|
data={convert2uplot(item.bins)}
|
||||||
width={width} height={250}
|
width={width} height={250}
|
||||||
|
usesBins={true}
|
||||||
title="Average Distribution of '{item.metric}'"
|
title="Average Distribution of '{item.metric}'"
|
||||||
xlabel={`${item.metric} bin maximum [${(metricConfig(cluster.name, item.metric)?.unit?.prefix ? metricConfig(cluster.name, item.metric)?.unit?.prefix : '') +
|
xlabel={`${item.metric} bin maximum ${(metricConfig(cluster.name, item.metric)?.unit?.prefix ? '[' + metricConfig(cluster.name, item.metric)?.unit?.prefix : '') +
|
||||||
(metricConfig(cluster.name, item.metric)?.unit?.base ? metricConfig(cluster.name, item.metric)?.unit?.base : '')}]`}
|
(metricConfig(cluster.name, item.metric)?.unit?.base ? metricConfig(cluster.name, item.metric)?.unit?.base + ']' : '')}`}
|
||||||
xunit={`${(metricConfig(cluster.name, item.metric)?.unit?.prefix ? metricConfig(cluster.name, item.metric)?.unit?.prefix : '') +
|
xunit={`${(metricConfig(cluster.name, item.metric)?.unit?.prefix ? metricConfig(cluster.name, item.metric)?.unit?.prefix : '') +
|
||||||
(metricConfig(cluster.name, item.metric)?.unit?.base ? metricConfig(cluster.name, item.metric)?.unit?.base : '')}`}
|
(metricConfig(cluster.name, item.metric)?.unit?.base ? metricConfig(cluster.name, item.metric)?.unit?.base : '')}`}
|
||||||
ylabel="Normalized Hours"
|
ylabel="Normalized Hours"
|
||||||
|
65
web/frontend/src/HistogramSelection.svelte
Normal file
65
web/frontend/src/HistogramSelection.svelte
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script>
|
||||||
|
import { Modal, ModalBody, ModalHeader, ModalFooter,
|
||||||
|
Button, ListGroup, ListGroupItem } from 'sveltestrap'
|
||||||
|
import { gql, getContextClient , mutationStore } from '@urql/svelte'
|
||||||
|
|
||||||
|
export let cluster
|
||||||
|
export let metricsInHistograms
|
||||||
|
export let isOpen
|
||||||
|
|
||||||
|
let availableMetrics = ['cpu_load', 'flops_any', 'mem_used', 'mem_bw'] // 'net_bw', 'file_bw'
|
||||||
|
let pendingMetrics = [...metricsInHistograms] // Copy
|
||||||
|
const client = getContextClient()
|
||||||
|
|
||||||
|
const updateConfigurationMutation = ({ name, value }) => {
|
||||||
|
return mutationStore({
|
||||||
|
client: client,
|
||||||
|
query: gql`mutation($name: String!, $value: String!) {
|
||||||
|
updateConfiguration(name: $name, value: $value)
|
||||||
|
}`,
|
||||||
|
variables: { name, value }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConfiguration(data) {
|
||||||
|
updateConfigurationMutation({
|
||||||
|
name: data.name,
|
||||||
|
value: JSON.stringify(data.value)
|
||||||
|
}).subscribe(res => {
|
||||||
|
if (res.fetching === false && res.error) {
|
||||||
|
throw res.error
|
||||||
|
// console.log('Error on subscription: ' + res.error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAndApply() {
|
||||||
|
metricsInHistograms = [...pendingMetrics] // Set for parent
|
||||||
|
isOpen = !isOpen
|
||||||
|
updateConfiguration({
|
||||||
|
name: cluster ? `user_view_histogramMetrics:${cluster}` : 'user_view_histogramMetrics',
|
||||||
|
value: metricsInHistograms
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal {isOpen}
|
||||||
|
toggle={() => (isOpen = !isOpen)}>
|
||||||
|
<ModalHeader>
|
||||||
|
Select metrics presented in histograms
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<ListGroup>
|
||||||
|
{#each availableMetrics as metric (metric)}
|
||||||
|
<ListGroupItem>
|
||||||
|
<input type="checkbox" bind:group={pendingMetrics} value={metric}>
|
||||||
|
{metric}
|
||||||
|
</ListGroupItem>
|
||||||
|
{/each}
|
||||||
|
</ListGroup>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" on:click={closeAndApply}> Close & Apply </Button>
|
||||||
|
<Button color="secondary" on:click={() => (isOpen = !isOpen)}> Close </Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
@ -27,6 +27,7 @@
|
|||||||
import TagManagement from "./TagManagement.svelte";
|
import TagManagement from "./TagManagement.svelte";
|
||||||
import MetricSelection from "./MetricSelection.svelte";
|
import MetricSelection from "./MetricSelection.svelte";
|
||||||
import StatsTable from "./StatsTable.svelte";
|
import StatsTable from "./StatsTable.svelte";
|
||||||
|
import JobFootprint from "./JobFootprint.svelte";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
export let dbid;
|
export let dbid;
|
||||||
@ -46,7 +47,8 @@
|
|||||||
resources { hostname, hwthreads, accelerators },
|
resources { hostname, hwthreads, accelerators },
|
||||||
metaData,
|
metaData,
|
||||||
userData { name, email },
|
userData { name, email },
|
||||||
concurrentJobs { items { id, jobId }, count, listQuery }
|
concurrentJobs { items { id, jobId }, count, listQuery },
|
||||||
|
flopsAnyAvg, memBwAvg, loadAvg
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@ -93,7 +95,9 @@
|
|||||||
startFetching(
|
startFetching(
|
||||||
job,
|
job,
|
||||||
[...toFetch],
|
[...toFetch],
|
||||||
job.numNodes > 2 ? ["node"] : ["node", "core"]
|
job.numNodes > 2
|
||||||
|
? ["node"]
|
||||||
|
: ["node", "socket", "core"]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Accels and not on node scope
|
// Accels and not on node scope
|
||||||
@ -102,7 +106,7 @@
|
|||||||
[...toFetch],
|
[...toFetch],
|
||||||
job.numNodes > 2
|
job.numNodes > 2
|
||||||
? ["node", "accelerator"]
|
? ["node", "accelerator"]
|
||||||
: ["node", "accelerator", "core"]
|
: ["node", "accelerator", "socket", "core"]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +136,9 @@
|
|||||||
|
|
||||||
let plots = {},
|
let plots = {},
|
||||||
jobTags,
|
jobTags,
|
||||||
statsTable;
|
statsTable,
|
||||||
|
jobFootprint;
|
||||||
|
|
||||||
$: document.title = $initq.fetching
|
$: document.title = $initq.fetching
|
||||||
? "Loading..."
|
? "Loading..."
|
||||||
: $initq.error
|
: $initq.error
|
||||||
@ -200,6 +206,17 @@
|
|||||||
<Spinner secondary />
|
<Spinner secondary />
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
|
{#if $jobMetrics.data}
|
||||||
|
{#key $jobMetrics.data}
|
||||||
|
<Col>
|
||||||
|
<JobFootprint
|
||||||
|
bind:this={jobFootprint}
|
||||||
|
job={$initq.data.job}
|
||||||
|
jobMetrics={$jobMetrics.data.jobMetrics}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
{/key}
|
||||||
|
{/if}
|
||||||
{#if $jobMetrics.data && $initq.data}
|
{#if $jobMetrics.data && $initq.data}
|
||||||
{#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0}
|
{#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0}
|
||||||
{#if authlevel > roles.manager}
|
{#if authlevel > roles.manager}
|
||||||
@ -390,8 +407,6 @@
|
|||||||
bind:this={statsTable}
|
bind:this={statsTable}
|
||||||
job={$initq.data.job}
|
job={$initq.data.job}
|
||||||
jobMetrics={$jobMetrics.data.jobMetrics}
|
jobMetrics={$jobMetrics.data.jobMetrics}
|
||||||
accMetrics={accMetrics}
|
|
||||||
accNodeOnly={accNodeOnly}
|
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
232
web/frontend/src/JobFootprint.svelte
Normal file
232
web/frontend/src/JobFootprint.svelte
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
<script>
|
||||||
|
import { getContext } from 'svelte'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardBody,
|
||||||
|
Progress,
|
||||||
|
Icon,
|
||||||
|
Tooltip
|
||||||
|
} from "sveltestrap";
|
||||||
|
import { mean, round } from 'mathjs'
|
||||||
|
|
||||||
|
export let job
|
||||||
|
export let jobMetrics
|
||||||
|
export let view = 'job'
|
||||||
|
export let width = 'auto'
|
||||||
|
|
||||||
|
const clusters = getContext('clusters')
|
||||||
|
const subclusterConfig = clusters.find((c) => c.name == job.cluster).subClusters.find((sc) => sc.name == job.subCluster)
|
||||||
|
|
||||||
|
const footprintMetrics = (job.numAcc !== 0)
|
||||||
|
? (job.exclusive !== 1)
|
||||||
|
? ['cpu_load', 'flops_any', 'acc_utilization']
|
||||||
|
: ['cpu_load', 'flops_any', 'acc_utilization', 'mem_bw']
|
||||||
|
: (job.exclusive !== 1)
|
||||||
|
? ['cpu_load', 'flops_any', 'mem_used']
|
||||||
|
: ['cpu_load', 'flops_any', 'mem_used', 'mem_bw']
|
||||||
|
|
||||||
|
const footprintData = footprintMetrics.map((fm) => {
|
||||||
|
// Mean: Primarily use backend sourced avgs from job.*, secondarily calculate/read from metricdata
|
||||||
|
let mv = null
|
||||||
|
if (fm === 'cpu_load' && job.loadAvg !== 0) {
|
||||||
|
mv = round(job.loadAvg, 2)
|
||||||
|
} else if (fm === 'flops_any' && job.flopsAnyAvg !== 0) {
|
||||||
|
mv = round(job.flopsAnyAvg, 2)
|
||||||
|
} else if (fm === 'mem_bw' && job.memBwAvg !== 0) {
|
||||||
|
mv = round(job.memBwAvg, 2)
|
||||||
|
} else { // Calculate from jobMetrics
|
||||||
|
const jm = jobMetrics.find((jm) => jm.name === fm && jm.scope === 'node')
|
||||||
|
if (jm?.metric?.statisticsSeries) {
|
||||||
|
mv = round(mean(jm.metric.statisticsSeries.mean), 2)
|
||||||
|
} else if (jm?.metric?.series?.length > 1) {
|
||||||
|
const avgs = jm.metric.series.map(jms => jms.statistics.avg)
|
||||||
|
mv = round(mean(avgs), 2)
|
||||||
|
} else if (jm?.metric?.series) {
|
||||||
|
mv = round(jm.metric.series[0].statistics.avg, 2)
|
||||||
|
} else {
|
||||||
|
mv = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit
|
||||||
|
const fmc = getContext('metrics')(job.cluster, fm)
|
||||||
|
let unit = ''
|
||||||
|
if (fmc?.unit?.base) unit = fmc.unit.prefix + fmc.unit.base
|
||||||
|
|
||||||
|
// Threshold / -Differences
|
||||||
|
const fmt = findJobThresholds(job, fmc, subclusterConfig)
|
||||||
|
if (fm === 'flops_any') fmt.peak = round((fmt.peak * 0.85), 0)
|
||||||
|
|
||||||
|
// Define basic data
|
||||||
|
const fmBase = {
|
||||||
|
name: fm,
|
||||||
|
unit: unit,
|
||||||
|
avg: mv,
|
||||||
|
max: fmt.peak
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evalFootprint(fm, mv, fmt, 'alert')) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: 'danger',
|
||||||
|
message:`Metric average way ${fm === 'mem_used' ? 'above' : 'below' } expected normal thresholds.`,
|
||||||
|
impact: 3
|
||||||
|
}
|
||||||
|
} else if (evalFootprint(fm, mv, fmt, 'caution')) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: 'warning',
|
||||||
|
message: `Metric average ${fm === 'mem_used' ? 'above' : 'below' } expected normal thresholds.`,
|
||||||
|
impact: 2
|
||||||
|
}
|
||||||
|
} else if (evalFootprint(fm, mv, fmt, 'normal')) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: 'success',
|
||||||
|
message: 'Metric average within expected thresholds.',
|
||||||
|
impact: 1
|
||||||
|
}
|
||||||
|
} else if (evalFootprint(fm, mv, fmt, 'peak')) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: 'info',
|
||||||
|
message: 'Metric average above expected normal thresholds: Check for artifacts recommended.',
|
||||||
|
impact: 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: 'secondary',
|
||||||
|
message: 'Metric average above expected peak threshold: Check for artifacts!',
|
||||||
|
impact: -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function evalFootprint(metric, mean, thresholds, level) {
|
||||||
|
// mem_used has inverse logic regarding threshold levels
|
||||||
|
switch (level) {
|
||||||
|
case 'peak':
|
||||||
|
if (metric === 'mem_used') return (mean <= thresholds.peak && mean > thresholds.alert)
|
||||||
|
else return (mean <= thresholds.peak && mean > thresholds.normal)
|
||||||
|
case 'alert':
|
||||||
|
if (metric === 'mem_used') return (mean <= thresholds.alert && mean > thresholds.caution)
|
||||||
|
else return (mean <= thresholds.alert && mean >= 0)
|
||||||
|
case 'caution':
|
||||||
|
if (metric === 'mem_used') return (mean <= thresholds.caution && mean > thresholds.normal)
|
||||||
|
else return (mean <= thresholds.caution && mean > thresholds.alert)
|
||||||
|
case 'normal':
|
||||||
|
if (metric === 'mem_used') return (mean <= thresholds.normal && mean >= 0)
|
||||||
|
else return (mean <= thresholds.normal && mean > thresholds.caution)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script context="module">
|
||||||
|
export function findJobThresholds(job, metricConfig, subClusterConfig) {
|
||||||
|
|
||||||
|
if (!job || !metricConfig || !subClusterConfig) {
|
||||||
|
console.warn('Argument missing for findJobThresholds!')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const subclusterThresholds = metricConfig.subClusters.find(sc => sc.name == subClusterConfig.name)
|
||||||
|
const defaultThresholds = {
|
||||||
|
peak: subclusterThresholds ? subclusterThresholds.peak : metricConfig.peak,
|
||||||
|
normal: subclusterThresholds ? subclusterThresholds.normal : metricConfig.normal,
|
||||||
|
caution: subclusterThresholds ? subclusterThresholds.caution : metricConfig.caution,
|
||||||
|
alert: subclusterThresholds ? subclusterThresholds.alert : metricConfig.alert
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.exclusive === 1) { // Exclusive: Use as defined
|
||||||
|
return defaultThresholds
|
||||||
|
} else { // Shared: Handle specifically
|
||||||
|
if (metricConfig.name === 'cpu_load') { // Special: Avg Aggregation BUT scaled based on #hwthreads
|
||||||
|
return {
|
||||||
|
peak: job.numHWThreads,
|
||||||
|
normal: job.numHWThreads,
|
||||||
|
caution: defaultThresholds.caution,
|
||||||
|
alert: defaultThresholds.alert
|
||||||
|
}
|
||||||
|
} else if (metricConfig.aggregation === 'avg' ){
|
||||||
|
return defaultThresholds
|
||||||
|
} else if (metricConfig.aggregation === 'sum' ){
|
||||||
|
const jobFraction = job.numHWThreads / subClusterConfig.topology.node.length
|
||||||
|
return {
|
||||||
|
peak: round((defaultThresholds.peak * jobFraction), 0),
|
||||||
|
normal: round((defaultThresholds.normal * jobFraction), 0),
|
||||||
|
caution: round((defaultThresholds.caution * jobFraction), 0),
|
||||||
|
alert: round((defaultThresholds.alert * jobFraction), 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('Missing or unkown aggregation mode (sum/avg) for metric:', metricConfig)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} // Other job.exclusive cases?
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card class="h-auto mt-1" style="width: {width}px;">
|
||||||
|
{#if view === 'job'}
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle class="mb-0 d-flex justify-content-center">
|
||||||
|
Core Metrics Footprint
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
{/if}
|
||||||
|
<CardBody>
|
||||||
|
{#each footprintData as fpd, index}
|
||||||
|
<div class="mb-1 d-flex justify-content-between">
|
||||||
|
<div> <b>{fpd.name}</b></div> <!-- For symmetry, see below ...-->
|
||||||
|
<div class="cursor-help d-inline-flex" id={`footprint-${job.jobId}-${index}`}>
|
||||||
|
<div class="mx-1">
|
||||||
|
<!-- Alerts Only -->
|
||||||
|
{#if fpd.impact === 3 || fpd.impact === -1}
|
||||||
|
<Icon name="exclamation-triangle-fill" class="text-danger"/>
|
||||||
|
{:else if fpd.impact === 2}
|
||||||
|
<Icon name="exclamation-triangle" class="text-warning"/>
|
||||||
|
{/if}
|
||||||
|
<!-- Emoji for all states-->
|
||||||
|
{#if fpd.impact === 3}
|
||||||
|
<Icon name="emoji-frown" class="text-danger"/>
|
||||||
|
{:else if fpd.impact === 2}
|
||||||
|
<Icon name="emoji-neutral" class="text-warning"/>
|
||||||
|
{:else if fpd.impact === 1}
|
||||||
|
<Icon name="emoji-smile" class="text-success"/>
|
||||||
|
{:else if fpd.impact === 0}
|
||||||
|
<Icon name="emoji-laughing" class="text-info"/>
|
||||||
|
{:else if fpd.impact === -1}
|
||||||
|
<Icon name="emoji-dizzy" class="text-danger"/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- Print Values -->
|
||||||
|
{fpd.avg} / {fpd.max} {fpd.unit} <!-- To increase margin to tooltip: No other way manageable ... -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tooltip target={`footprint-${job.jobId}-${index}`} placement="right" offset={[0, 20]}>{fpd.message}</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<Progress
|
||||||
|
value={fpd.avg}
|
||||||
|
max={fpd.max}
|
||||||
|
color={fpd.color}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{#if job?.metaData?.message}
|
||||||
|
<hr class="mt-1 mb-2"/>
|
||||||
|
{@html job.metaData.message}
|
||||||
|
{/if}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cursor-help {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
</style>
|
@ -23,6 +23,9 @@
|
|||||||
let metrics = filterPresets.cluster
|
let metrics = filterPresets.cluster
|
||||||
? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] || ccconfig.plot_list_selectedMetrics
|
? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] || ccconfig.plot_list_selectedMetrics
|
||||||
: ccconfig.plot_list_selectedMetrics
|
: ccconfig.plot_list_selectedMetrics
|
||||||
|
let showFootprint = filterPresets.cluster
|
||||||
|
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
|
||||||
|
: !!ccconfig.plot_list_showFootprint
|
||||||
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
|
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
|
||||||
|
|
||||||
// The filterPresets are handled by the Filters component,
|
// The filterPresets are handled by the Filters component,
|
||||||
@ -81,7 +84,8 @@
|
|||||||
bind:metrics={metrics}
|
bind:metrics={metrics}
|
||||||
bind:sorting={sorting}
|
bind:sorting={sorting}
|
||||||
bind:matchedJobs={matchedJobs}
|
bind:matchedJobs={matchedJobs}
|
||||||
bind:this={jobList} />
|
bind:this={jobList}
|
||||||
|
bind:showFootprint={showFootprint} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
@ -93,4 +97,6 @@
|
|||||||
bind:cluster={selectedCluster}
|
bind:cluster={selectedCluster}
|
||||||
configName="plot_list_selectedMetrics"
|
configName="plot_list_selectedMetrics"
|
||||||
bind:metrics={metrics}
|
bind:metrics={metrics}
|
||||||
bind:isOpen={isMetricsSelectionOpen} />
|
bind:isOpen={isMetricsSelectionOpen}
|
||||||
|
bind:showFootprint={showFootprint}
|
||||||
|
view='list'/>
|
||||||
|
@ -17,12 +17,15 @@
|
|||||||
export let configName
|
export let configName
|
||||||
export let allMetrics = null
|
export let allMetrics = null
|
||||||
export let cluster = null
|
export let cluster = null
|
||||||
|
export let showFootprint
|
||||||
|
export let view = 'job'
|
||||||
|
|
||||||
const clusters = getContext('clusters'),
|
const clusters = getContext('clusters'),
|
||||||
onInit = getContext('on-init')
|
onInit = getContext('on-init')
|
||||||
|
|
||||||
let newMetricsOrder = []
|
let newMetricsOrder = []
|
||||||
let unorderedMetrics = [...metrics]
|
let unorderedMetrics = [...metrics]
|
||||||
|
let pendingShowFootprint = !!showFootprint
|
||||||
|
|
||||||
onInit(() => {
|
onInit(() => {
|
||||||
if (allMetrics == null) allMetrics = new Set()
|
if (allMetrics == null) allMetrics = new Set()
|
||||||
@ -90,6 +93,8 @@
|
|||||||
metrics = newMetricsOrder.filter(m => unorderedMetrics.includes(m))
|
metrics = newMetricsOrder.filter(m => unorderedMetrics.includes(m))
|
||||||
isOpen = false
|
isOpen = false
|
||||||
|
|
||||||
|
showFootprint = !!pendingShowFootprint
|
||||||
|
|
||||||
updateConfigurationMutation({
|
updateConfigurationMutation({
|
||||||
name: cluster == null ? configName : `${configName}:${cluster}`,
|
name: cluster == null ? configName : `${configName}:${cluster}`,
|
||||||
value: JSON.stringify(metrics)
|
value: JSON.stringify(metrics)
|
||||||
@ -99,6 +104,16 @@
|
|||||||
// console.log('Error on subscription: ' + res.error)
|
// console.log('Error on subscription: ' + res.error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
updateConfigurationMutation({
|
||||||
|
name: cluster == null ? 'plot_list_showFootprint' : `plot_list_showFootprint:${cluster}`,
|
||||||
|
value: JSON.stringify(showFootprint)
|
||||||
|
}).subscribe(res => {
|
||||||
|
if (res.fetching === false && res.error) {
|
||||||
|
console.log('Error on footprint subscription: ' + res.error)
|
||||||
|
throw res.error
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -121,6 +136,12 @@
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
|
{#if view === 'list'}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<input type="checkbox" bind:checked={pendingShowFootprint}> Show Footprint
|
||||||
|
</li>
|
||||||
|
<hr/>
|
||||||
|
{/if}
|
||||||
{#each newMetricsOrder as metric, index (metric)}
|
{#each newMetricsOrder as metric, index (metric)}
|
||||||
<li class="cc-config-column list-group-item"
|
<li class="cc-config-column list-group-item"
|
||||||
draggable={true} ondragover="return false"
|
draggable={true} ondragover="return false"
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
export let job
|
export let job
|
||||||
export let jobMetrics
|
export let jobMetrics
|
||||||
export let accMetrics
|
|
||||||
export let accNodeOnly
|
|
||||||
|
|
||||||
const allMetrics = [...new Set(jobMetrics.map(m => m.name))].sort(),
|
const allMetrics = [...new Set(jobMetrics.map(m => m.name))].sort(),
|
||||||
scopesForMetric = (metric) => jobMetrics
|
scopesForMetric = (metric) => jobMetrics
|
||||||
@ -23,17 +21,23 @@
|
|||||||
|| getContext('cc-config')['job_view_nodestats_selectedMetrics']
|
|| getContext('cc-config')['job_view_nodestats_selectedMetrics']
|
||||||
|
|
||||||
for (let metric of allMetrics) {
|
for (let metric of allMetrics) {
|
||||||
// Not Exclusive or Single Node: Get maxScope()
|
// Not Exclusive or Multi-Node: get maxScope directly (mostly: node)
|
||||||
// No Accelerators in Job and not Acc-Metric: Use 'core'
|
// -> Else: Load smallest available granularity as default as per availability
|
||||||
// Accelerator Metric available on accelerator scope: Use 'accelerator'
|
const availableScopes = scopesForMetric(metric)
|
||||||
// Accelerator Metric only on node scope: Fallback to 'node'
|
if (job.exclusive != 1 || job.numNodes == 1) {
|
||||||
selectedScopes[metric] = (job.exclusive != 1 || job.numNodes == 1) ?
|
if (availableScopes.includes('accelerator')) {
|
||||||
(job.numAccs != 0 && accMetrics.includes(metric)) ?
|
selectedScopes[metric] = 'accelerator'
|
||||||
accNodeOnly ?
|
} else if (availableScopes.includes('core')) {
|
||||||
'node'
|
selectedScopes[metric] = 'core'
|
||||||
: 'accelerator'
|
} else if (availableScopes.includes('socket')) {
|
||||||
: 'core'
|
selectedScopes[metric] = 'socket'
|
||||||
: maxScope(scopesForMetric(metric))
|
} else {
|
||||||
|
selectedScopes[metric] = 'node'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedScopes[metric] = maxScope(availableScopes)
|
||||||
|
}
|
||||||
|
|
||||||
sorting[metric] = {
|
sorting[metric] = {
|
||||||
min: { dir: 'up', active: false },
|
min: { dir: 'up', active: false },
|
||||||
avg: { dir: 'up', active: false },
|
avg: { dir: 'up', active: false },
|
||||||
@ -84,8 +88,7 @@
|
|||||||
{metric}
|
{metric}
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<select class="form-select"
|
<select class="form-select"
|
||||||
bind:value={selectedScopes[metric]}
|
bind:value={selectedScopes[metric]}>
|
||||||
disabled={scopesForMetric(metric, jobMetrics).length == 1}>
|
|
||||||
{#each scopesForMetric(metric, jobMetrics) as scope}
|
{#each scopesForMetric(metric, jobMetrics) as scope}
|
||||||
<option value={scope}>{scope}</option>
|
<option value={scope}>{scope}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
Table,
|
Table,
|
||||||
Progress,
|
Progress,
|
||||||
Icon,
|
Icon,
|
||||||
|
Button
|
||||||
} from "sveltestrap";
|
} from "sveltestrap";
|
||||||
import { init, convert2uplot, transformPerNodeDataForRoofline } from "./utils.js";
|
import { init, convert2uplot, transformPerNodeDataForRoofline } from "./utils.js";
|
||||||
import { scaleNumbers } from "./units.js";
|
import { scaleNumbers } from "./units.js";
|
||||||
@ -24,6 +25,8 @@
|
|||||||
getContextClient,
|
getContextClient,
|
||||||
mutationStore,
|
mutationStore,
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
|
import PlotTable from './PlotTable.svelte'
|
||||||
|
import HistogramSelection from './HistogramSelection.svelte'
|
||||||
|
|
||||||
const { query: initq } = init();
|
const { query: initq } = init();
|
||||||
const ccconfig = getContext("cc-config");
|
const ccconfig = getContext("cc-config");
|
||||||
@ -63,6 +66,9 @@
|
|||||||
option.key == ccconfig.status_view_selectedTopUserCategory
|
option.key == ccconfig.status_view_selectedTopUserCategory
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let isHistogramSelectionOpen = false
|
||||||
|
$: metricsInHistograms = cluster ? (ccconfig[`user_view_histogramMetrics:${cluster}`] || []) : (ccconfig.user_view_histogramMetrics || [])
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
$: mainQuery = queryStore({
|
$: mainQuery = queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
@ -73,6 +79,7 @@
|
|||||||
$metrics: [String!]
|
$metrics: [String!]
|
||||||
$from: Time!
|
$from: Time!
|
||||||
$to: Time!
|
$to: Time!
|
||||||
|
$metricsInHistograms: [String!]
|
||||||
) {
|
) {
|
||||||
nodeMetrics(
|
nodeMetrics(
|
||||||
cluster: $cluster
|
cluster: $cluster
|
||||||
@ -98,7 +105,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats: jobsStatistics(filter: $filter) {
|
stats: jobsStatistics(filter: $filter, metrics: $metricsInHistograms) {
|
||||||
histDuration {
|
histDuration {
|
||||||
count
|
count
|
||||||
value
|
value
|
||||||
@ -115,6 +122,16 @@
|
|||||||
count
|
count
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
histMetrics {
|
||||||
|
metric
|
||||||
|
unit
|
||||||
|
data {
|
||||||
|
min
|
||||||
|
max
|
||||||
|
count
|
||||||
|
bin
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allocatedNodes(cluster: $cluster) {
|
allocatedNodes(cluster: $cluster) {
|
||||||
@ -129,6 +146,7 @@
|
|||||||
from: from.toISOString(),
|
from: from.toISOString(),
|
||||||
to: to.toISOString(),
|
to: to.toISOString(),
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
||||||
|
metricsInHistograms: metricsInHistograms
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -311,7 +329,7 @@
|
|||||||
<Col xs="auto" style="align-self: flex-end;">
|
<Col xs="auto" style="align-self: flex-end;">
|
||||||
<h4 class="mb-0">Current utilization of cluster "{cluster}"</h4>
|
<h4 class="mb-0">Current utilization of cluster "{cluster}"</h4>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="auto">
|
<Col xs="auto" style="margin-left: 0.25rem;">
|
||||||
{#if $initq.fetching || $mainQuery.fetching}
|
{#if $initq.fetching || $mainQuery.fetching}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else if $initq.error}
|
{:else if $initq.error}
|
||||||
@ -321,6 +339,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="auto" style="margin-left: auto;">
|
<Col xs="auto" style="margin-left: auto;">
|
||||||
|
<Button
|
||||||
|
outline color="secondary"
|
||||||
|
on:click={() => (isHistogramSelectionOpen = true)}>
|
||||||
|
<Icon name="bar-chart-line"/> Select Histograms
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
<Col xs="auto" style="margin-left: 0.25rem;">
|
||||||
<Refresher
|
<Refresher
|
||||||
initially={120}
|
initially={120}
|
||||||
on:reload={() => {
|
on:reload={() => {
|
||||||
@ -666,4 +691,35 @@
|
|||||||
{/key}
|
{/key}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<hr class="my-2" />
|
||||||
|
{#if metricsInHistograms}
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
{#key $mainQuery.data.stats[0].histMetrics}
|
||||||
|
<PlotTable
|
||||||
|
let:item
|
||||||
|
let:width
|
||||||
|
renderFor="user"
|
||||||
|
items={$mainQuery.data.stats[0].histMetrics}
|
||||||
|
itemsPerRow={3}>
|
||||||
|
|
||||||
|
<Histogram
|
||||||
|
data={convert2uplot(item.data)}
|
||||||
|
usesBins={true}
|
||||||
|
width={width} height={250}
|
||||||
|
title="Distribution of '{item.metric}' averages"
|
||||||
|
xlabel={`${item.metric} bin maximum ${item?.unit ? `[${item.unit}]` : ``}`}
|
||||||
|
xunit={item.unit}
|
||||||
|
ylabel="Number of Jobs"
|
||||||
|
yunit="Jobs"/>
|
||||||
|
</PlotTable>
|
||||||
|
{/key}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<HistogramSelection
|
||||||
|
bind:cluster={cluster}
|
||||||
|
bind:metricsInHistograms={metricsInHistograms}
|
||||||
|
bind:isOpen={isHistogramSelectionOpen} />
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from 'svelte'
|
import { onMount, getContext } from 'svelte'
|
||||||
import { init, convert2uplot } from './utils.js'
|
import { init, convert2uplot } from './utils.js'
|
||||||
import { Table, Row, Col, Button, Icon, Card, Spinner, Input } from 'sveltestrap'
|
import { Table, Row, Col, Button, Icon, Card, Spinner } 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'
|
||||||
@ -9,6 +9,8 @@
|
|||||||
import Refresher from './joblist/Refresher.svelte'
|
import Refresher from './joblist/Refresher.svelte'
|
||||||
import Histogram from './plots/Histogram.svelte'
|
import Histogram from './plots/Histogram.svelte'
|
||||||
import MetricSelection from './MetricSelection.svelte'
|
import MetricSelection from './MetricSelection.svelte'
|
||||||
|
import HistogramSelection from './HistogramSelection.svelte'
|
||||||
|
import PlotTable from './PlotTable.svelte'
|
||||||
import { scramble, scrambleNames } from './joblist/JobInfo.svelte'
|
import { scramble, scrambleNames } from './joblist/JobInfo.svelte'
|
||||||
|
|
||||||
const { query: initq } = init()
|
const { query: initq } = init()
|
||||||
@ -23,23 +25,29 @@
|
|||||||
let jobFilters = [];
|
let jobFilters = [];
|
||||||
let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false
|
let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false
|
||||||
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, isHistogramSelectionOpen = false
|
||||||
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
|
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
|
||||||
|
let showFootprint = filterPresets.cluster
|
||||||
|
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
|
||||||
|
: !!ccconfig.plot_list_showFootprint
|
||||||
|
|
||||||
|
$: metricsInHistograms = selectedCluster ? (ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || []) : (ccconfig.user_view_histogramMetrics || [])
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
$: stats = queryStore({
|
$: stats = queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query($jobFilters: [JobFilter!]!) {
|
query($jobFilters: [JobFilter!]!, $metricsInHistograms: [String!]) {
|
||||||
jobsStatistics(filter: $jobFilters) {
|
jobsStatistics(filter: $jobFilters, metrics: $metricsInHistograms) {
|
||||||
totalJobs
|
totalJobs
|
||||||
shortJobs
|
shortJobs
|
||||||
totalWalltime
|
totalWalltime
|
||||||
totalCoreHours
|
totalCoreHours
|
||||||
histDuration { count, value }
|
histDuration { count, value }
|
||||||
histNumNodes { count, value }
|
histNumNodes { count, value }
|
||||||
|
histMetrics { metric, unit, data { min, max, count, bin } }
|
||||||
}}`,
|
}}`,
|
||||||
variables: { jobFilters }
|
variables: { jobFilters, metricsInHistograms }
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => filterComponent.update())
|
onMount(() => filterComponent.update())
|
||||||
@ -68,6 +76,12 @@
|
|||||||
on:click={() => (isMetricsSelectionOpen = true)}>
|
on:click={() => (isMetricsSelectionOpen = true)}>
|
||||||
<Icon name="graph-up"/> Metrics
|
<Icon name="graph-up"/> Metrics
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
outline color="secondary"
|
||||||
|
on:click={() => (isHistogramSelectionOpen = true)}>
|
||||||
|
<Icon name="bar-chart-line"/> Select Histograms
|
||||||
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
<Filters
|
<Filters
|
||||||
@ -159,13 +173,49 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Row>
|
</Row>
|
||||||
|
{#if metricsInHistograms}
|
||||||
|
<Row>
|
||||||
|
{#if $stats.error}
|
||||||
|
<Col>
|
||||||
|
<Card body color="danger">{$stats.error.message}</Card>
|
||||||
|
</Col>
|
||||||
|
{:else if !$stats.data}
|
||||||
|
<Col>
|
||||||
|
<Spinner secondary />
|
||||||
|
</Col>
|
||||||
|
{:else}
|
||||||
|
<Col>
|
||||||
|
{#key $stats.data.jobsStatistics[0].histMetrics}
|
||||||
|
<PlotTable
|
||||||
|
let:item
|
||||||
|
let:width
|
||||||
|
renderFor="user"
|
||||||
|
items={$stats.data.jobsStatistics[0].histMetrics}
|
||||||
|
itemsPerRow={3}>
|
||||||
|
|
||||||
|
<Histogram
|
||||||
|
data={convert2uplot(item.data)}
|
||||||
|
usesBins={true}
|
||||||
|
width={width} height={250}
|
||||||
|
title="Distribution of '{item.metric}' averages"
|
||||||
|
xlabel={`${item.metric} bin maximum ${item?.unit ? `[${item.unit}]` : ``}`}
|
||||||
|
xunit={item.unit}
|
||||||
|
ylabel="Number of Jobs"
|
||||||
|
yunit="Jobs"/>
|
||||||
|
</PlotTable>
|
||||||
|
{/key}
|
||||||
|
</Col>
|
||||||
|
{/if}
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
<br/>
|
<br/>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<JobList
|
<JobList
|
||||||
bind:metrics={metrics}
|
bind:metrics={metrics}
|
||||||
bind:sorting={sorting}
|
bind:sorting={sorting}
|
||||||
bind:this={jobList} />
|
bind:this={jobList}
|
||||||
|
bind:showFootprint={showFootprint} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
@ -177,4 +227,11 @@
|
|||||||
bind:cluster={selectedCluster}
|
bind:cluster={selectedCluster}
|
||||||
configName="plot_list_selectedMetrics"
|
configName="plot_list_selectedMetrics"
|
||||||
bind:metrics={metrics}
|
bind:metrics={metrics}
|
||||||
bind:isOpen={isMetricsSelectionOpen} />
|
bind:isOpen={isMetricsSelectionOpen}
|
||||||
|
bind:showFootprint={showFootprint}
|
||||||
|
view='list'/>
|
||||||
|
|
||||||
|
<HistogramSelection
|
||||||
|
bind:cluster={selectedCluster}
|
||||||
|
bind:metricsInHistograms={metricsInHistograms}
|
||||||
|
bind:isOpen={isHistogramSelectionOpen} />
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { Row, Col, Button, Modal, ModalBody, ModalHeader, ModalFooter, FormGroup } from 'sveltestrap'
|
import { Row, Col, Button, Modal, ModalBody, ModalHeader, ModalFooter } from 'sveltestrap'
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let isOpen = false
|
export let isOpen = false
|
||||||
export let from = null
|
export let lessThan = null
|
||||||
export let to = null
|
export let moreThan = null
|
||||||
|
export let from = null
|
||||||
|
export let to = null
|
||||||
|
|
||||||
let pendingFrom, pendingTo
|
let pendingLessThan, pendingMoreThan, pendingFrom, pendingTo
|
||||||
|
let lessDisabled = false, moreDisabled = false, betweenDisabled = false
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
pendingFrom = from == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(from)
|
pendingLessThan = lessThan == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(lessThan)
|
||||||
pendingTo = to == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(to)
|
pendingMoreThan = moreThan == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(moreThan)
|
||||||
|
pendingFrom = from == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(from)
|
||||||
|
pendingTo = to == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
@ -27,18 +32,23 @@
|
|||||||
function hoursAndMinsToSecs({ hours, mins }) {
|
function hoursAndMinsToSecs({ hours, mins }) {
|
||||||
return hours * 3600 + mins * 60
|
return hours * 3600 + mins * 60
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: lessDisabled = pendingMoreThan.hours !== 0 || pendingMoreThan.mins !== 0 || pendingFrom.hours !== 0 || pendingFrom.mins !== 0 || pendingTo.hours !== 0 || pendingTo.mins !== 0
|
||||||
|
$: moreDisabled = pendingLessThan.hours !== 0 || pendingLessThan.mins !== 0 || pendingFrom.hours !== 0 || pendingFrom.mins !== 0 || pendingTo.hours !== 0 || pendingTo.mins !== 0
|
||||||
|
$: betweenDisabled = pendingMoreThan.hours !== 0 || pendingMoreThan.mins !== 0 || pendingLessThan.hours !== 0 || pendingLessThan.mins !== 0
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal isOpen={isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal isOpen={isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Select Start Time
|
Select Job Duration
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<h4>Between</h4>
|
<h4>Duration more than</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<div class="input-group mb-2 mr-sm-2">
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
<input type="number" class="form-control" bind:value={pendingFrom.hours}>
|
<input type="number" min="0" class="form-control" bind:value={pendingMoreThan.hours} disabled={moreDisabled}>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<div class="input-group-text">h</div>
|
<div class="input-group-text">h</div>
|
||||||
</div>
|
</div>
|
||||||
@ -46,7 +56,49 @@
|
|||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<div class="input-group mb-2 mr-sm-2">
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
<input type="number" class="form-control" bind:value={pendingFrom.mins}>
|
<input type="number" min="0" max="59" class="form-control" bind:value={pendingMoreThan.mins} disabled={moreDisabled}>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text">m</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h4>Duration less than</h4>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
|
<input type="number" min="0" class="form-control" bind:value={pendingLessThan.hours} disabled={lessDisabled}>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text">h</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
|
<input type="number" min="0" max="59" class="form-control" bind:value={pendingLessThan.mins} disabled={lessDisabled}>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text">m</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h4>Duration between</h4>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
|
<input type="number" min="0" class="form-control" bind:value={pendingFrom.hours} disabled={betweenDisabled}>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<div class="input-group-text">h</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
|
<input type="number" min="0" max="59" class="form-control" bind:value={pendingFrom.mins} disabled={betweenDisabled}>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<div class="input-group-text">m</div>
|
<div class="input-group-text">m</div>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +109,7 @@
|
|||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<div class="input-group mb-2 mr-sm-2">
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
<input type="number" class="form-control" bind:value={pendingTo.hours}>
|
<input type="number" min="0" class="form-control" bind:value={pendingTo.hours} disabled={betweenDisabled}>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<div class="input-group-text">h</div>
|
<div class="input-group-text">h</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,7 +117,7 @@
|
|||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<div class="input-group mb-2 mr-sm-2">
|
<div class="input-group mb-2 mr-sm-2">
|
||||||
<input type="number" class="form-control" bind:value={pendingTo.mins}>
|
<input type="number" min="0" max="59" class="form-control" bind:value={pendingTo.mins} disabled={betweenDisabled}>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<div class="input-group-text">m</div>
|
<div class="input-group-text">m</div>
|
||||||
</div>
|
</div>
|
||||||
@ -77,19 +129,30 @@
|
|||||||
<Button color="primary"
|
<Button color="primary"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
isOpen = false
|
isOpen = false
|
||||||
|
lessThan = hoursAndMinsToSecs(pendingLessThan)
|
||||||
|
moreThan = hoursAndMinsToSecs(pendingMoreThan)
|
||||||
from = hoursAndMinsToSecs(pendingFrom)
|
from = hoursAndMinsToSecs(pendingFrom)
|
||||||
to = hoursAndMinsToSecs(pendingTo)
|
to = hoursAndMinsToSecs(pendingTo)
|
||||||
dispatch('update', { from, to })
|
dispatch('update', { lessThan, moreThan, from, to })
|
||||||
}}>
|
}}>
|
||||||
Close & Apply
|
Close & Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="danger" on:click={() => {
|
<Button color="warning" on:click={() => {
|
||||||
isOpen = false
|
lessThan = null
|
||||||
|
moreThan = null
|
||||||
from = null
|
from = null
|
||||||
to = null
|
to = null
|
||||||
reset()
|
reset()
|
||||||
dispatch('update', { from, to })
|
}}>Reset Values</Button>
|
||||||
}}>Reset</Button>
|
<Button color="danger" on:click={() => {
|
||||||
|
isOpen = false
|
||||||
|
lessThan = null
|
||||||
|
moreThan = null
|
||||||
|
from = null
|
||||||
|
to = null
|
||||||
|
reset()
|
||||||
|
dispatch('update', { lessThan, moreThan, from, to })
|
||||||
|
}}>Reset Filter</Button>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
states: filterPresets.states || filterPresets.state ? [filterPresets.state].flat() : allJobStates,
|
states: filterPresets.states || filterPresets.state ? [filterPresets.state].flat() : allJobStates,
|
||||||
startTime: filterPresets.startTime || { from: null, to: null },
|
startTime: filterPresets.startTime || { from: null, to: null },
|
||||||
tags: filterPresets.tags || [],
|
tags: filterPresets.tags || [],
|
||||||
duration: filterPresets.duration || { from: null, to: null },
|
duration: filterPresets.duration || { lessThan: null, moreThan: null, from: null, to: null },
|
||||||
jobId: filterPresets.jobId || '',
|
jobId: filterPresets.jobId || '',
|
||||||
arrayJobId: filterPresets.arrayJobId || null,
|
arrayJobId: filterPresets.arrayJobId || null,
|
||||||
user: filterPresets.user || '',
|
user: filterPresets.user || '',
|
||||||
@ -88,6 +88,10 @@
|
|||||||
items.push({ tags: filters.tags })
|
items.push({ tags: filters.tags })
|
||||||
if (filters.duration.from || filters.duration.to)
|
if (filters.duration.from || filters.duration.to)
|
||||||
items.push({ duration: { from: filters.duration.from, to: filters.duration.to } })
|
items.push({ duration: { from: filters.duration.from, to: filters.duration.to } })
|
||||||
|
if (filters.duration.lessThan)
|
||||||
|
items.push({ duration: { from: 0, to: filters.duration.lessThan } })
|
||||||
|
if (filters.duration.moreThan)
|
||||||
|
items.push({ duration: { from: filters.duration.moreThan, to: 604800 } }) // 7 days to include special jobs with long runtimes
|
||||||
if (filters.jobId)
|
if (filters.jobId)
|
||||||
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } })
|
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } })
|
||||||
if (filters.arrayJobId != null)
|
if (filters.arrayJobId != null)
|
||||||
@ -144,6 +148,10 @@
|
|||||||
opts.push(`tag=${tag}`)
|
opts.push(`tag=${tag}`)
|
||||||
if (filters.duration.from && filters.duration.to)
|
if (filters.duration.from && filters.duration.to)
|
||||||
opts.push(`duration=${filters.duration.from}-${filters.duration.to}`)
|
opts.push(`duration=${filters.duration.from}-${filters.duration.to}`)
|
||||||
|
if (filters.duration.lessThan)
|
||||||
|
opts.push(`duration=0-${filters.duration.lessThan}`)
|
||||||
|
if (filters.duration.moreThan)
|
||||||
|
opts.push(`duration=${filters.duration.moreThan}-604800`)
|
||||||
if (filters.numNodes.from && filters.numNodes.to)
|
if (filters.numNodes.from && filters.numNodes.to)
|
||||||
opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`)
|
opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`)
|
||||||
if (filters.numAccelerators.from && filters.numAccelerators.to)
|
if (filters.numAccelerators.from && filters.numAccelerators.to)
|
||||||
@ -267,6 +275,18 @@
|
|||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if filters.duration.lessThan}
|
||||||
|
<Info icon="stopwatch" on:click={() => (isDurationOpen = true)}>
|
||||||
|
Duration less than {Math.floor(filters.duration.lessThan / 3600)}h:{Math.floor(filters.duration.lessThan % 3600 / 60)}m
|
||||||
|
</Info>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if filters.duration.moreThan}
|
||||||
|
<Info icon="stopwatch" on:click={() => (isDurationOpen = true)}>
|
||||||
|
Duration more than {Math.floor(filters.duration.moreThan / 3600)}h:{Math.floor(filters.duration.moreThan % 3600 / 60)}m
|
||||||
|
</Info>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if filters.tags.length != 0}
|
{#if filters.tags.length != 0}
|
||||||
<Info icon="tags" on:click={() => (isTagsOpen = true)}>
|
<Info icon="tags" on:click={() => (isTagsOpen = true)}>
|
||||||
{#each filters.tags as tagId}
|
{#each filters.tags as tagId}
|
||||||
@ -325,6 +345,8 @@
|
|||||||
|
|
||||||
<Duration
|
<Duration
|
||||||
bind:isOpen={isDurationOpen}
|
bind:isOpen={isDurationOpen}
|
||||||
|
bind:lessThan={filters.duration.lessThan}
|
||||||
|
bind:moreThan={filters.duration.moreThan}
|
||||||
bind:from={filters.duration.from}
|
bind:from={filters.duration.from}
|
||||||
bind:to={filters.duration.to}
|
bind:to={filters.duration.to}
|
||||||
on:update={() => update()} />
|
on:update={() => update()} />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, getContext } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { parse, format, sub } from 'date-fns'
|
||||||
import { Row, Button, Input, Modal, ModalBody, ModalHeader, ModalFooter, FormGroup } from 'sveltestrap'
|
import { Row, Button, Input, Modal, ModalBody, ModalHeader, ModalFooter, FormGroup } from 'sveltestrap'
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -11,34 +12,29 @@
|
|||||||
|
|
||||||
let pendingFrom, pendingTo
|
let pendingFrom, pendingTo
|
||||||
|
|
||||||
|
const now = new Date(Date.now())
|
||||||
|
const ago = sub(now, {months: 1})
|
||||||
|
const defaultFrom = {date: format(ago, 'yyyy-MM-dd'), time: format(ago, 'HH:mm')}
|
||||||
|
const defaultTo = {date: format(now, 'yyyy-MM-dd'), time: format(now, 'HH:mm')}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
pendingFrom = from == null ? { date: '0000-00-00', time: '00:00' } : fromRFC3339(from)
|
pendingFrom = from == null ? defaultFrom : fromRFC3339(from)
|
||||||
pendingTo = to == null ? { date: '0000-00-00', time: '00:00' } : fromRFC3339(to)
|
pendingTo = to == null ? defaultTo : fromRFC3339(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
function toRFC3339({ date, time }, secs = 0) {
|
function toRFC3339({ date, time }, secs = '00') {
|
||||||
const dparts = date.split('-')
|
const parsedDate = parse(date+' '+time+':'+secs, 'yyyy-MM-dd HH:mm:ss', new Date())
|
||||||
const tparts = time.split(':')
|
return parsedDate.toISOString()
|
||||||
const d = new Date(
|
|
||||||
Number.parseInt(dparts[0]),
|
|
||||||
Number.parseInt(dparts[1]) - 1,
|
|
||||||
Number.parseInt(dparts[2]),
|
|
||||||
Number.parseInt(tparts[0]),
|
|
||||||
Number.parseInt(tparts[1]), secs)
|
|
||||||
return d.toISOString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromRFC3339(rfc3339) {
|
function fromRFC3339(rfc3339) {
|
||||||
const d = new Date(rfc3339)
|
const parsedDate = new Date(rfc3339)
|
||||||
const pad = (n) => n.toString().padStart(2, '0')
|
return { date: format(parsedDate, 'yyyy-MM-dd'), time: format(parsedDate, 'HH:mm') }
|
||||||
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
|
||||||
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}`
|
|
||||||
return { date, time }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: isModified = (from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, 59))
|
$: isModified = (from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, '59'))
|
||||||
&& !(from == null && pendingFrom.date == '0000-00-00' && pendingFrom.time == '00:00')
|
&& !(from == null && pendingFrom.date == '0000-00-00' && pendingFrom.time == '00:00')
|
||||||
&& !(to == null && pendingTo.date == '0000-00-00' && pendingTo.time == '00:00')
|
&& !(to == null && pendingTo.date == '0000-00-00' && pendingTo.time == '00:00')
|
||||||
</script>
|
</script>
|
||||||
@ -73,7 +69,7 @@
|
|||||||
on:click={() => {
|
on:click={() => {
|
||||||
isOpen = false
|
isOpen = false
|
||||||
from = toRFC3339(pendingFrom)
|
from = toRFC3339(pendingFrom)
|
||||||
to = toRFC3339(pendingTo, 59)
|
to = toRFC3339(pendingTo, '59')
|
||||||
dispatch('update', { from, to })
|
dispatch('update', { from, to })
|
||||||
}}>
|
}}>
|
||||||
Close & Apply
|
Close & Apply
|
||||||
|
@ -28,6 +28,17 @@
|
|||||||
return `${hours}:${('0' + minutes).slice(-2)}:${('0' + seconds).slice(-2)}`;
|
return `${hours}:${('0' + minutes).slice(-2)}:${('0' + seconds).slice(-2)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStateColor(state) {
|
||||||
|
switch (state) {
|
||||||
|
case 'running':
|
||||||
|
return 'success'
|
||||||
|
case 'completed':
|
||||||
|
return 'primary'
|
||||||
|
default:
|
||||||
|
return 'danger'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -86,12 +97,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Start: <span class="fw-bold">{(new Date(job.startTime)).toLocaleString()}</span>
|
Start: <span class="fw-bold">{(new Date(job.startTime)).toLocaleString()}</span>
|
||||||
<br/>
|
<br/>
|
||||||
Duration: <span class="fw-bold">{formatDuration(job.duration)}</span>
|
Duration: <span class="fw-bold">{formatDuration(job.duration)}</span> <Badge color="{getStateColor(job.state)}">{job.state}</Badge>
|
||||||
{#if job.state == 'running'}
|
|
||||||
<Badge color="success">running</Badge>
|
|
||||||
{:else if job.state != 'completed'}
|
|
||||||
<Badge color="danger">{job.state}</Badge>
|
|
||||||
{/if}
|
|
||||||
{#if job.walltime}
|
{#if job.walltime}
|
||||||
<br/>
|
<br/>
|
||||||
Walltime: <span class="fw-bold">{formatDuration(job.walltime)}</span>
|
Walltime: <span class="fw-bold">{formatDuration(job.walltime)}</span>
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
export let sorting = { field: "startTime", order: "DESC" };
|
export let sorting = { field: "startTime", order: "DESC" };
|
||||||
export let matchedJobs = 0;
|
export let matchedJobs = 0;
|
||||||
export let metrics = ccconfig.plot_list_selectedMetrics;
|
export let metrics = ccconfig.plot_list_selectedMetrics;
|
||||||
|
export let showFootprint;
|
||||||
|
|
||||||
let itemsPerPage = ccconfig.plot_list_jobsPerPage;
|
let itemsPerPage = ccconfig.plot_list_jobsPerPage;
|
||||||
let page = 1;
|
let page = 1;
|
||||||
@ -73,6 +74,9 @@
|
|||||||
name
|
name
|
||||||
}
|
}
|
||||||
metaData
|
metaData
|
||||||
|
flopsAnyAvg
|
||||||
|
memBwAvg
|
||||||
|
loadAvg
|
||||||
}
|
}
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
@ -89,7 +93,7 @@
|
|||||||
|
|
||||||
// Force refresh list with existing unchanged variables (== usually would not trigger reactivity)
|
// Force refresh list with existing unchanged variables (== usually would not trigger reactivity)
|
||||||
export function refresh() {
|
export function refresh() {
|
||||||
queryStore({
|
jobs = queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: query,
|
query: query,
|
||||||
variables: { paging, sorting, filter },
|
variables: { paging, sorting, filter },
|
||||||
@ -134,12 +138,19 @@
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let plotWidth = null;
|
||||||
let tableWidth = null;
|
let tableWidth = null;
|
||||||
let jobInfoColumnWidth = 250;
|
let jobInfoColumnWidth = 250;
|
||||||
|
|
||||||
$: plotWidth = Math.floor(
|
$: if (showFootprint) {
|
||||||
(tableWidth - jobInfoColumnWidth) / metrics.length - 10
|
plotWidth = Math.floor(
|
||||||
);
|
(tableWidth - jobInfoColumnWidth) / (metrics.length + 1) - 10
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
plotWidth = Math.floor(
|
||||||
|
(tableWidth - jobInfoColumnWidth) / metrics.length - 10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let headerPaddingTop = 0;
|
let headerPaddingTop = 0;
|
||||||
stickyHeader(
|
stickyHeader(
|
||||||
@ -160,6 +171,15 @@
|
|||||||
>
|
>
|
||||||
Job Info
|
Job Info
|
||||||
</th>
|
</th>
|
||||||
|
{#if showFootprint}
|
||||||
|
<th
|
||||||
|
class="position-sticky top-0"
|
||||||
|
scope="col"
|
||||||
|
style="width: {plotWidth}px; padding-top: {headerPaddingTop}px"
|
||||||
|
>
|
||||||
|
Job Footprint
|
||||||
|
</th>
|
||||||
|
{/if}
|
||||||
{#each metrics as metric (metric)}
|
{#each metrics as metric (metric)}
|
||||||
<th
|
<th
|
||||||
class="position-sticky top-0 text-center"
|
class="position-sticky top-0 text-center"
|
||||||
@ -212,7 +232,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{:else if $jobs.data && $initialized}
|
{:else if $jobs.data && $initialized}
|
||||||
{#each $jobs.data.jobs.items as job (job)}
|
{#each $jobs.data.jobs.items as job (job)}
|
||||||
<JobListRow {job} {metrics} {plotWidth} />
|
<JobListRow {job} {metrics} {plotWidth} {showFootprint}/>
|
||||||
{:else}
|
{:else}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan={metrics.length + 1}>
|
<td colspan={metrics.length + 1}>
|
||||||
|
@ -14,22 +14,28 @@
|
|||||||
import { Card, Spinner } from "sveltestrap";
|
import { Card, Spinner } from "sveltestrap";
|
||||||
import MetricPlot from "../plots/MetricPlot.svelte";
|
import MetricPlot from "../plots/MetricPlot.svelte";
|
||||||
import JobInfo from "./JobInfo.svelte";
|
import JobInfo from "./JobInfo.svelte";
|
||||||
|
import JobFootprint from "../JobFootprint.svelte";
|
||||||
import { maxScope, checkMetricDisabled } from "../utils.js";
|
import { maxScope, checkMetricDisabled } from "../utils.js";
|
||||||
|
|
||||||
export let job;
|
export let job;
|
||||||
export let metrics;
|
export let metrics;
|
||||||
export let plotWidth;
|
export let plotWidth;
|
||||||
export let plotHeight = 275;
|
export let plotHeight = 275;
|
||||||
|
export let showFootprint;
|
||||||
|
|
||||||
let { id } = job;
|
let { id } = job;
|
||||||
let scopes = [job.numNodes == 1 ? "core" : "node"];
|
let scopes = [job.numNodes == 1 ? "core" : "node"];
|
||||||
|
|
||||||
|
function distinct(value, index, array) {
|
||||||
|
return array.indexOf(value) === index;
|
||||||
|
}
|
||||||
|
|
||||||
const cluster = getContext("clusters").find((c) => c.name == job.cluster);
|
const cluster = getContext("clusters").find((c) => c.name == job.cluster);
|
||||||
const metricConfig = getContext("metrics"); // Get all MetricConfs which include subCluster-specific settings for this job
|
const metricConfig = getContext("metrics"); // Get all MetricConfs which include subCluster-specific settings for this job
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
const query = gql`
|
const query = gql`
|
||||||
query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!) {
|
query ($id: ID!, $queryMetrics: [String!]!, $scopes: [MetricScope!]!) {
|
||||||
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes) {
|
jobMetrics(id: $id, metrics: $queryMetrics, scopes: $scopes) {
|
||||||
name
|
name
|
||||||
scope
|
scope
|
||||||
metric {
|
metric {
|
||||||
@ -61,14 +67,24 @@
|
|||||||
$: metricsQuery = queryStore({
|
$: metricsQuery = queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: query,
|
query: query,
|
||||||
variables: { id, metrics, scopes }
|
variables: { id, queryMetrics, scopes }
|
||||||
});
|
});
|
||||||
|
|
||||||
function refresh() {
|
let queryMetrics = null
|
||||||
queryStore({
|
$: if (showFootprint) {
|
||||||
|
queryMetrics = ['cpu_load', 'flops_any', 'mem_used', 'mem_bw', 'acc_utilization', ...metrics].filter(distinct)
|
||||||
|
scopes = ["node"]
|
||||||
|
} else {
|
||||||
|
queryMetrics = [...metrics]
|
||||||
|
scopes = [job.numNodes == 1 ? "core" : "node"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function refresh() {
|
||||||
|
metricsQuery = queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: query,
|
query: query,
|
||||||
variables: { id, metrics, scopes }
|
variables: { id, queryMetrics, scopes },
|
||||||
|
// requestPolicy: 'network-only' // use default cache-first for refresh
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +137,16 @@
|
|||||||
</Card>
|
</Card>
|
||||||
</td>
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
|
{#if showFootprint}
|
||||||
|
<td>
|
||||||
|
<JobFootprint
|
||||||
|
job={job}
|
||||||
|
jobMetrics={$metricsQuery.data.jobMetrics}
|
||||||
|
width={plotWidth}
|
||||||
|
view="list"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
{#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)}
|
{#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)}
|
||||||
<td>
|
<td>
|
||||||
<!-- Subluster Metricconfig remove keyword for jobtables (joblist main, user joblist, project joblist) to be used here as toplevel case-->
|
<!-- Subluster Metricconfig remove keyword for jobtables (joblist main, user joblist, project joblist) to be used here as toplevel case-->
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
import { Card } from 'sveltestrap'
|
import { Card } from 'sveltestrap'
|
||||||
|
|
||||||
export let data
|
export let data
|
||||||
|
export let usesBins = false
|
||||||
export let width = 500
|
export let width = 500
|
||||||
export let height = 300
|
export let height = 300
|
||||||
export let title = ''
|
export let title = ''
|
||||||
@ -160,6 +161,14 @@
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
label: xunit !== '' ? xunit : null,
|
label: xunit !== '' ? xunit : null,
|
||||||
|
value: (u, ts, sidx, didx) => {
|
||||||
|
if (usesBins) {
|
||||||
|
const min = u.data[sidx][didx - 1] ? u.data[sidx][didx - 1] : 0
|
||||||
|
const max = u.data[sidx][didx]
|
||||||
|
ts = min + ' - ' + max // narrow spaces
|
||||||
|
}
|
||||||
|
return ts
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Object.assign({
|
Object.assign({
|
||||||
label: yunit !== '' ? yunit : null,
|
label: yunit !== '' ? yunit : null,
|
||||||
|
@ -161,8 +161,8 @@
|
|||||||
? (statisticsSeries.max.reduce((max, x) => Math.max(max, x), thresholds.normal) || thresholds.normal)
|
? (statisticsSeries.max.reduce((max, x) => Math.max(max, x), thresholds.normal) || thresholds.normal)
|
||||||
: (series.reduce((max, series) => Math.max(max, series.statistics?.max), thresholds.normal) || thresholds.normal)
|
: (series.reduce((max, series) => Math.max(max, series.statistics?.max), thresholds.normal) || thresholds.normal)
|
||||||
|
|
||||||
if (maxY >= (10 * thresholds.normal)) { // Hard y-range render limit if outliers in series data
|
if (maxY >= (10 * thresholds.peak)) { // Hard y-range render limit if outliers in series data
|
||||||
maxY = (10 * thresholds.normal)
|
maxY = (10 * thresholds.peak)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,12 +390,12 @@
|
|||||||
if (scope == 'node' || metricConfig.aggregation == 'avg') {
|
if (scope == 'node' || metricConfig.aggregation == 'avg') {
|
||||||
if (metricConfig.subClusters && metricConfig.subClusters.length === 0) {
|
if (metricConfig.subClusters && metricConfig.subClusters.length === 0) {
|
||||||
// console.log('subClusterConfigs array empty, use metricConfig defaults')
|
// console.log('subClusterConfigs array empty, use metricConfig defaults')
|
||||||
return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert }
|
return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert, peak: metricConfig.peak }
|
||||||
} else if (metricConfig.subClusters && metricConfig.subClusters.length > 0) {
|
} else if (metricConfig.subClusters && metricConfig.subClusters.length > 0) {
|
||||||
// console.log('subClusterConfigs found, use subCluster Settings if matching jobs subcluster:')
|
// console.log('subClusterConfigs found, use subCluster Settings if matching jobs subcluster:')
|
||||||
let forSubCluster = metricConfig.subClusters.find(sc => sc.name == subCluster.name)
|
let forSubCluster = metricConfig.subClusters.find(sc => sc.name == subCluster.name)
|
||||||
if (forSubCluster && forSubCluster.normal && forSubCluster.caution && forSubCluster.alert) return forSubCluster
|
if (forSubCluster && forSubCluster.normal && forSubCluster.caution && forSubCluster.alert && forSubCluster.peak) return forSubCluster
|
||||||
else return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert }
|
else return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert, peak: metricConfig.peak}
|
||||||
} else {
|
} else {
|
||||||
console.warn('metricConfig.subClusters not found!')
|
console.warn('metricConfig.subClusters not found!')
|
||||||
return null
|
return null
|
||||||
@ -423,6 +423,7 @@
|
|||||||
|
|
||||||
let mc = metricConfig?.subClusters?.find(sc => sc.name == subCluster.name) || metricConfig
|
let mc = metricConfig?.subClusters?.find(sc => sc.name == subCluster.name) || metricConfig
|
||||||
return {
|
return {
|
||||||
|
peak: mc.peak / divisor,
|
||||||
normal: mc.normal / divisor,
|
normal: mc.normal / divisor,
|
||||||
caution: mc.caution / divisor,
|
caution: mc.caution / divisor,
|
||||||
alert: mc.alert / divisor
|
alert: mc.alert / divisor
|
||||||
|
@ -176,12 +176,12 @@
|
|||||||
// Debug get zoomLevel from browser
|
// Debug get zoomLevel from browser
|
||||||
// console.log("Zoom", Math.round(window.devicePixelRatio * 100))
|
// console.log("Zoom", Math.round(window.devicePixelRatio * 100))
|
||||||
|
|
||||||
if (scalarKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Top horizontal roofline
|
if (scalarKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Lower horizontal roofline
|
||||||
u.ctx.moveTo(scalarKneeX, flopRateScalarY)
|
u.ctx.moveTo(scalarKneeX, flopRateScalarY)
|
||||||
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateScalarY)
|
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateScalarY)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (simdKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Lower horitontal roofline
|
if (simdKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Top horitontal roofline
|
||||||
u.ctx.moveTo(simdKneeX, flopRateSimdY)
|
u.ctx.moveTo(simdKneeX, flopRateSimdY)
|
||||||
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateSimdY)
|
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateSimdY)
|
||||||
}
|
}
|
||||||
@ -214,6 +214,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// cursor: { drag: { x: true, y: true } } // Activate zoom
|
||||||
};
|
};
|
||||||
uplot = new uPlot(opts, plotData, plotWrapper);
|
uplot = new uPlot(opts, plotData, plotWrapper);
|
||||||
} else {
|
} else {
|
||||||
|
@ -316,11 +316,17 @@ export function checkMetricDisabled(m, c, s) { //[m]etric, [c]luster, [s]ubclust
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function convert2uplot(canvasData) {
|
export function convert2uplot(canvasData) {
|
||||||
// initial use: Canvas Histogram Data to Uplot
|
// Prep: Uplot Data Structure
|
||||||
let uplotData = [[],[]] // [X, Y1, Y2, ...]
|
let uplotData = [[],[]] // [X, Y1, Y2, ...]
|
||||||
canvasData.forEach( pair => {
|
// Iterate
|
||||||
uplotData[0].push(pair.value)
|
canvasData.forEach( cd => {
|
||||||
uplotData[1].push(pair.count)
|
if (Object.keys(cd).length == 4) { // MetricHisto Datafromat
|
||||||
|
uplotData[0].push(cd?.max ? cd.max : 0)
|
||||||
|
uplotData[1].push(cd.count)
|
||||||
|
} else { // Default
|
||||||
|
uplotData[0].push(cd.value)
|
||||||
|
uplotData[1].push(cd.count)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return uplotData
|
return uplotData
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user