mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-04-08 05:05:55 +02:00
Merge branch 'master' into hotfix
This commit is contained in:
commit
0adda4bf7b
api
go.modgo.sumgqlgen.ymlinternal
api
config
graph
importer
metricDataDispatcher
repository
pkg/archive
web/frontend/src
Analysis.root.svelteJob.root.svelteJobs.root.svelteStatus.root.svelteSystems.root.svelteUser.root.svelte
generic
job
systems
@ -137,6 +137,11 @@ type JobMetricWithName {
|
|||||||
metric: JobMetric!
|
metric: JobMetric!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JobMetricStatWithName {
|
||||||
|
name: String!
|
||||||
|
stats: MetricStatistics!
|
||||||
|
}
|
||||||
|
|
||||||
type JobMetric {
|
type JobMetric {
|
||||||
unit: Unit
|
unit: Unit
|
||||||
timestep: Int!
|
timestep: Int!
|
||||||
@ -242,6 +247,7 @@ type Query {
|
|||||||
|
|
||||||
job(id: ID!): Job
|
job(id: ID!): Job
|
||||||
jobMetrics(id: ID!, metrics: [String!], scopes: [MetricScope!], resolution: Int): [JobMetricWithName!]!
|
jobMetrics(id: ID!, metrics: [String!], scopes: [MetricScope!], resolution: Int): [JobMetricWithName!]!
|
||||||
|
jobMetricStats(id: ID!, metrics: [String!]): [JobMetricStatWithName!]!
|
||||||
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!
|
||||||
|
100
api/swagger.json
100
api/swagger.json
@ -202,7 +202,7 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "Success message",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.DeleteJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -272,7 +272,7 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "Success message",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.DeleteJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -342,7 +342,7 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "Success message",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.DeleteJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -487,7 +487,7 @@
|
|||||||
"201": {
|
"201": {
|
||||||
"description": "Job added successfully",
|
"description": "Job added successfully",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.StartJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -581,7 +581,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"description": "Unprocessable Entity: finding job failed: sql: no rows in result set",
|
"description": "Unprocessable Entity: job has already been stopped",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.ErrorResponse"
|
"$ref": "#/definitions/api.ErrorResponse"
|
||||||
}
|
}
|
||||||
@ -827,6 +827,72 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/notice/": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Modifies the content of notice.txt, shown as notice box on the homepage.\nIf more than one formValue is set then only the highest priority field is used.\nOnly accessible from IPs registered with apiAllowedIPs configuration option.",
|
||||||
|
"consumes": [
|
||||||
|
"multipart/form-data"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"User"
|
||||||
|
],
|
||||||
|
"summary": "Updates or empties the notice box content",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Priority 1: New content to display",
|
||||||
|
"name": "new-content",
|
||||||
|
"in": "formData"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success Response Message",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity: The user could not be updated",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/{id}": {
|
"/user/{id}": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -1207,6 +1273,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.DefaultJobApiResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"msg": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.DeleteJobApiRequest": {
|
"api.DeleteJobApiRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -1230,14 +1304,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.DeleteJobApiResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"msg": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api.EditMetaRequest": {
|
"api.EditMetaRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -1324,14 +1390,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.StartJobApiResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"msg": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api.StopJobApiRequest": {
|
"api.StopJobApiRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -32,6 +32,11 @@ definitions:
|
|||||||
example: Debug
|
example: Debug
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
api.DefaultJobApiResponse:
|
||||||
|
properties:
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
api.DeleteJobApiRequest:
|
api.DeleteJobApiRequest:
|
||||||
properties:
|
properties:
|
||||||
cluster:
|
cluster:
|
||||||
@ -49,11 +54,6 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- jobId
|
- jobId
|
||||||
type: object
|
type: object
|
||||||
api.DeleteJobApiResponse:
|
|
||||||
properties:
|
|
||||||
msg:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
api.EditMetaRequest:
|
api.EditMetaRequest:
|
||||||
properties:
|
properties:
|
||||||
key:
|
key:
|
||||||
@ -112,11 +112,6 @@ definitions:
|
|||||||
scope:
|
scope:
|
||||||
$ref: '#/definitions/schema.MetricScope'
|
$ref: '#/definitions/schema.MetricScope'
|
||||||
type: object
|
type: object
|
||||||
api.StartJobApiResponse:
|
|
||||||
properties:
|
|
||||||
msg:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
api.StopJobApiRequest:
|
api.StopJobApiRequest:
|
||||||
properties:
|
properties:
|
||||||
cluster:
|
cluster:
|
||||||
@ -906,7 +901,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Success message
|
description: Success message
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api.DeleteJobApiResponse'
|
$ref: '#/definitions/api.DefaultJobApiResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
@ -953,7 +948,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Success message
|
description: Success message
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api.DeleteJobApiResponse'
|
$ref: '#/definitions/api.DefaultJobApiResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
@ -1000,7 +995,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Success message
|
description: Success message
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api.DeleteJobApiResponse'
|
$ref: '#/definitions/api.DefaultJobApiResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
@ -1098,7 +1093,7 @@ paths:
|
|||||||
"201":
|
"201":
|
||||||
description: Job added successfully
|
description: Job added successfully
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api.StartJobApiResponse'
|
$ref: '#/definitions/api.DefaultJobApiResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
@ -1161,8 +1156,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api.ErrorResponse'
|
$ref: '#/definitions/api.ErrorResponse'
|
||||||
"422":
|
"422":
|
||||||
description: 'Unprocessable Entity: finding job failed: sql: no rows in
|
description: 'Unprocessable Entity: job has already been stopped'
|
||||||
result set'
|
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api.ErrorResponse'
|
$ref: '#/definitions/api.ErrorResponse'
|
||||||
"500":
|
"500":
|
||||||
@ -1224,6 +1218,51 @@ paths:
|
|||||||
summary: Adds one or more tags to a job
|
summary: Adds one or more tags to a job
|
||||||
tags:
|
tags:
|
||||||
- Job add and modify
|
- Job add and modify
|
||||||
|
/notice/:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- multipart/form-data
|
||||||
|
description: |-
|
||||||
|
Modifies the content of notice.txt, shown as notice box on the homepage.
|
||||||
|
If more than one formValue is set then only the highest priority field is used.
|
||||||
|
Only accessible from IPs registered with apiAllowedIPs configuration option.
|
||||||
|
parameters:
|
||||||
|
- description: 'Priority 1: New content to display'
|
||||||
|
in: formData
|
||||||
|
name: new-content
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Success Response Message
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"403":
|
||||||
|
description: Forbidden
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"422":
|
||||||
|
description: 'Unprocessable Entity: The user could not be updated'
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Updates or empties the notice box content
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
/user/{id}:
|
/user/{id}:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
47
go.mod
47
go.mod
@ -3,33 +3,33 @@ module github.com/ClusterCockpit/cc-backend
|
|||||||
go 1.23.5
|
go 1.23.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.17.63
|
github.com/99designs/gqlgen v0.17.66
|
||||||
github.com/ClusterCockpit/cc-units v0.4.0
|
github.com/ClusterCockpit/cc-units v0.4.0
|
||||||
github.com/Masterminds/squirrel v1.5.4
|
github.com/Masterminds/squirrel v1.5.4
|
||||||
github.com/coreos/go-oidc/v3 v3.11.0
|
github.com/coreos/go-oidc/v3 v3.12.0
|
||||||
github.com/go-co-op/gocron/v2 v2.9.0
|
github.com/go-co-op/gocron/v2 v2.16.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.8
|
github.com/go-ldap/ldap/v3 v3.4.10
|
||||||
github.com/go-sql-driver/mysql v1.8.1
|
github.com/go-sql-driver/mysql v1.9.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||||
github.com/google/gops v0.3.28
|
github.com/google/gops v0.3.28
|
||||||
github.com/gorilla/handlers v1.5.2
|
github.com/gorilla/handlers v1.5.2
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/sessions v1.4.0
|
github.com/gorilla/sessions v1.4.0
|
||||||
github.com/influxdata/influxdb-client-go/v2 v2.13.0
|
github.com/influxdata/influxdb-client-go/v2 v2.14.0
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.22
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.21.0
|
||||||
github.com/prometheus/common v0.55.0
|
github.com/prometheus/common v0.62.0
|
||||||
github.com/qustavo/sqlhooks/v2 v2.1.0
|
github.com/qustavo/sqlhooks/v2 v2.1.0
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
||||||
github.com/swaggo/http-swagger v1.3.4
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
github.com/vektah/gqlparser/v2 v2.5.22
|
github.com/vektah/gqlparser/v2 v2.5.22
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.35.0
|
||||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
|
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
|
||||||
golang.org/x/oauth2 v0.21.0
|
golang.org/x/oauth2 v0.27.0
|
||||||
golang.org/x/time v0.10.0
|
golang.org/x/time v0.5.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -43,7 +43,7 @@ require (
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.3 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
github.com/go-openapi/spec v0.21.0 // indirect
|
github.com/go-openapi/spec v0.21.0 // indirect
|
||||||
@ -56,7 +56,7 @@ require (
|
|||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
|
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
|
||||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/jpillora/backoff v1.0.0 // indirect
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
@ -68,7 +68,6 @@ require (
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||||
github.com/oapi-codegen/runtime v1.1.1 // indirect
|
github.com/oapi-codegen/runtime v1.1.1 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
|
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
@ -78,13 +77,13 @@ require (
|
|||||||
github.com/urfave/cli/v2 v2.27.5 // indirect
|
github.com/urfave/cli/v2 v2.27.5 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.23.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/tools v0.29.0 // indirect
|
golang.org/x/tools v0.30.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.1 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
|
160
go.sum
160
go.sum
@ -1,7 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/99designs/gqlgen v0.17.63 h1:HCdaYDPd9HqUXRchEvmE3EFzELRwLlaJ8DBuyC8Cqto=
|
github.com/99designs/gqlgen v0.17.66 h1:2/SRc+h3115fCOZeTtsqrB5R5gTGm+8qCAwcrZa+CXA=
|
||||||
github.com/99designs/gqlgen v0.17.63/go.mod h1:sVCM2iwIZisJjTI/DEC3fpH+HFgxY1496ZJ+jbT9IjA=
|
github.com/99designs/gqlgen v0.17.66/go.mod h1:gucrb5jK5pgCKzAGuOMMVU9C8PnReecHEHd2UxLQwCg=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||||
@ -12,8 +12,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
|
|||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||||
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0=
|
github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0=
|
||||||
github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U=
|
github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U=
|
||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
@ -34,8 +34,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
|||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
|
||||||
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -43,27 +43,30 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
|
||||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg=
|
github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8=
|
||||||
github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA=
|
github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM=
|
||||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
|
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
|
||||||
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-co-op/gocron/v2 v2.9.0 h1:+0nTyI3mjc2FGIClBdDWpaLPCNrJ+62o9xbS0ZklEKQ=
|
github.com/go-co-op/gocron/v2 v2.16.0 h1:uqUF6WFZ4enRU45pWFNcn1xpDLc+jBOTKhPQI16Z1xs=
|
||||||
github.com/go-co-op/gocron/v2 v2.9.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
|
github.com/go-co-op/gocron/v2 v2.16.0/go.mod h1:opexeOFy5BplhsKdA7bzY9zeYih8I8/WNJ4arTIFPVc=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.3 h1:o8aphO8Hv6RPmH+GfzVuyf7YXSBibp+8YyHdOoDESGo=
|
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.3/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
|
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||||
@ -73,16 +76,17 @@ github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5
|
|||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
|
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
|
||||||
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
|
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@ -115,8 +119,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
|
|||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM=
|
github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
|
||||||
github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4=
|
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
|
||||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU=
|
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU=
|
||||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
@ -133,8 +137,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
|
|||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||||
@ -142,6 +146,8 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
|
|||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -156,8 +162,11 @@ github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@ -175,27 +184,27 @@ github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmt
|
|||||||
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/qustavo/sqlhooks/v2 v2.1.0 h1:54yBemHnGHp/7xgT+pxwmIlMSDNYKx5JW5dfRAiCZi0=
|
github.com/qustavo/sqlhooks/v2 v2.1.0 h1:54yBemHnGHp/7xgT+pxwmIlMSDNYKx5JW5dfRAiCZi0=
|
||||||
github.com/qustavo/sqlhooks/v2 v2.1.0/go.mod h1:aMREyKo7fOKTwiLuWPsaHRXEmtqG4yREztO0idF83AU=
|
github.com/qustavo/sqlhooks/v2 v2.1.0/go.mod h1:aMREyKo7fOKTwiLuWPsaHRXEmtqG4yREztO0idF83AU=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
||||||
@ -229,6 +238,14 @@ github.com/vektah/gqlparser/v2 v2.5.22/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnH
|
|||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||||
|
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||||
|
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
@ -236,16 +253,21 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
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.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
|
||||||
|
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||||
|
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
@ -253,17 +275,23 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||||
|
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -271,35 +299,45 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
|
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||||
|
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||||
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=
|
||||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
@ -30,6 +30,7 @@ resolver:
|
|||||||
# gqlgen will search for any type names in the schema in these go packages
|
# gqlgen will search for any type names in the schema in these go packages
|
||||||
# if they match it will use them, otherwise it will generate them.
|
# if they match it will use them, otherwise it will generate them.
|
||||||
autobind:
|
autobind:
|
||||||
|
- "github.com/99designs/gqlgen/graphql/introspection"
|
||||||
- "github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
- "github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
||||||
|
|
||||||
# This section declares type mapping between the GraphQL and go type systems
|
# This section declares type mapping between the GraphQL and go type systems
|
||||||
|
@ -208,7 +208,7 @@ const docTemplate = `{
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "Success message",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.DeleteJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -278,7 +278,7 @@ const docTemplate = `{
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "Success message",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.DeleteJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -348,7 +348,7 @@ const docTemplate = `{
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "Success message",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.DeleteJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -493,7 +493,7 @@ const docTemplate = `{
|
|||||||
"201": {
|
"201": {
|
||||||
"description": "Job added successfully",
|
"description": "Job added successfully",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.StartJobApiResponse"
|
"$ref": "#/definitions/api.DefaultJobApiResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@ -587,7 +587,7 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"description": "Unprocessable Entity: finding job failed: sql: no rows in result set",
|
"description": "Unprocessable Entity: job has already been stopped",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/api.ErrorResponse"
|
"$ref": "#/definitions/api.ErrorResponse"
|
||||||
}
|
}
|
||||||
@ -833,6 +833,72 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/notice/": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Modifies the content of notice.txt, shown as notice box on the homepage.\nIf more than one formValue is set then only the highest priority field is used.\nOnly accessible from IPs registered with apiAllowedIPs configuration option.",
|
||||||
|
"consumes": [
|
||||||
|
"multipart/form-data"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"User"
|
||||||
|
],
|
||||||
|
"summary": "Updates or empties the notice box content",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Priority 1: New content to display",
|
||||||
|
"name": "new-content",
|
||||||
|
"in": "formData"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success Response Message",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity: The user could not be updated",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/{id}": {
|
"/user/{id}": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -1213,6 +1279,14 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.DefaultJobApiResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"msg": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.DeleteJobApiRequest": {
|
"api.DeleteJobApiRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -1236,14 +1310,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.DeleteJobApiResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"msg": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api.EditMetaRequest": {
|
"api.EditMetaRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -1330,14 +1396,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.StartJobApiResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"msg": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api.StopJobApiRequest": {
|
"api.StopJobApiRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -757,7 +757,7 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @accept json
|
// @accept json
|
||||||
// @produce json
|
// @produce json
|
||||||
// @param request body schema.JobMeta true "Job to add"
|
// @param request body schema.JobMeta true "Job to add"
|
||||||
// @success 201 {object} api.StartJobApiResponse "Job added successfully"
|
// @success 201 {object} api.DefaultJobApiResponse "Job added successfully"
|
||||||
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
||||||
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
||||||
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
||||||
@ -772,9 +772,8 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.State == "" {
|
|
||||||
req.State = schema.JobStateRunning
|
req.State = schema.JobStateRunning
|
||||||
}
|
|
||||||
if err := importer.SanityChecks(&req.BaseJob); err != nil {
|
if err := importer.SanityChecks(&req.BaseJob); err != nil {
|
||||||
handleError(err, http.StatusBadRequest, rw)
|
handleError(err, http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
@ -835,7 +834,7 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
||||||
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
||||||
// @failure 404 {object} api.ErrorResponse "Resource not found"
|
// @failure 404 {object} api.ErrorResponse "Resource not found"
|
||||||
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: finding job failed: sql: no rows in result set"
|
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: job has already been stopped"
|
||||||
// @failure 500 {object} api.ErrorResponse "Internal Server Error"
|
// @failure 500 {object} api.ErrorResponse "Internal Server Error"
|
||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /jobs/stop_job/ [post]
|
// @router /jobs/stop_job/ [post]
|
||||||
@ -871,7 +870,7 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @description Job to remove is specified by database ID. This will not remove the job from the job archive.
|
// @description Job to remove is specified by database ID. This will not remove the job from the job archive.
|
||||||
// @produce json
|
// @produce json
|
||||||
// @param id path int true "Database ID of Job"
|
// @param id path int true "Database ID of Job"
|
||||||
// @success 200 {object} api.DeleteJobApiResponse "Success message"
|
// @success 200 {object} api.DefaultJobApiResponse "Success message"
|
||||||
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
||||||
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
||||||
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
||||||
@ -914,7 +913,7 @@ func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @accept json
|
// @accept json
|
||||||
// @produce json
|
// @produce json
|
||||||
// @param request body api.DeleteJobApiRequest true "All fields required"
|
// @param request body api.DeleteJobApiRequest true "All fields required"
|
||||||
// @success 200 {object} api.DeleteJobApiResponse "Success message"
|
// @success 200 {object} api.DefaultJobApiResponse "Success message"
|
||||||
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
||||||
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
||||||
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
||||||
@ -964,7 +963,7 @@ func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request)
|
|||||||
// @description Remove all jobs with start time before timestamp. The jobs will not be removed from the job archive.
|
// @description Remove all jobs with start time before timestamp. The jobs will not be removed from the job archive.
|
||||||
// @produce json
|
// @produce json
|
||||||
// @param ts path int true "Unix epoch timestamp"
|
// @param ts path int true "Unix epoch timestamp"
|
||||||
// @success 200 {object} api.DeleteJobApiResponse "Success message"
|
// @success 200 {object} api.DefaultJobApiResponse "Success message"
|
||||||
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
||||||
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
||||||
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
||||||
@ -1004,8 +1003,13 @@ func (api *RestApi) deleteJobBefore(rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobApiRequest) {
|
func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobApiRequest) {
|
||||||
// Sanity checks
|
// Sanity checks
|
||||||
if job == nil || job.StartTime.Unix() >= req.StopTime || job.State != schema.JobStateRunning {
|
if job.State != schema.JobStateRunning {
|
||||||
handleError(fmt.Errorf("jobId %d (id %d) on %s : stopTime %d must be larger than startTime %d and only running jobs can be stopped (state is: %s)", job.JobID, job.ID, job.Cluster, req.StopTime, job.StartTime.Unix(), job.State), http.StatusBadRequest, rw)
|
handleError(fmt.Errorf("jobId %d (id %d) on %s : job has already been stopped (state is: %s)", job.JobID, job.ID, job.Cluster, job.State), http.StatusUnprocessableEntity, rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if job == nil || job.StartTime.Unix() > req.StopTime {
|
||||||
|
handleError(fmt.Errorf("jobId %d (id %d) on %s : stopTime %d must be larger/equal than startTime %d", job.JobID, job.ID, job.Cluster, req.StopTime, job.StartTime.Unix()), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ type DefaultMetricsConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadDefaultMetricsConfig() (*DefaultMetricsConfig, error) {
|
func LoadDefaultMetricsConfig() (*DefaultMetricsConfig, error) {
|
||||||
filePath := "configs/default_metrics.json"
|
filePath := "default_metrics.json"
|
||||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -81,6 +81,11 @@ type JobLinkResultList struct {
|
|||||||
Count *int `json:"count,omitempty"`
|
Count *int `json:"count,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JobMetricStatWithName struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Stats *schema.MetricStatistics `json:"stats"`
|
||||||
|
}
|
||||||
|
|
||||||
type JobMetricWithName struct {
|
type JobMetricWithName struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Scope schema.MetricScope `json:"scope"`
|
Scope schema.MetricScope `json:"scope"`
|
||||||
@ -168,9 +173,6 @@ type PageRequest struct {
|
|||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
type StringInput struct {
|
type StringInput struct {
|
||||||
Eq *string `json:"eq,omitempty"`
|
Eq *string `json:"eq,omitempty"`
|
||||||
Neq *string `json:"neq,omitempty"`
|
Neq *string `json:"neq,omitempty"`
|
||||||
@ -224,7 +226,7 @@ func (e Aggregate) String() string {
|
|||||||
return string(e)
|
return string(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Aggregate) UnmarshalGQL(v interface{}) error {
|
func (e *Aggregate) UnmarshalGQL(v any) error {
|
||||||
str, ok := v.(string)
|
str, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("enums must be strings")
|
return fmt.Errorf("enums must be strings")
|
||||||
@ -277,7 +279,7 @@ func (e SortByAggregate) String() string {
|
|||||||
return string(e)
|
return string(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SortByAggregate) UnmarshalGQL(v interface{}) error {
|
func (e *SortByAggregate) UnmarshalGQL(v any) error {
|
||||||
str, ok := v.(string)
|
str, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("enums must be strings")
|
return fmt.Errorf("enums must be strings")
|
||||||
@ -318,7 +320,7 @@ func (e SortDirectionEnum) String() string {
|
|||||||
return string(e)
|
return string(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SortDirectionEnum) UnmarshalGQL(v interface{}) error {
|
func (e *SortDirectionEnum) UnmarshalGQL(v any) error {
|
||||||
str, ok := v.(string)
|
str, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("enums must be strings")
|
return fmt.Errorf("enums must be strings")
|
||||||
|
@ -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.57
|
// Code generated by github.com/99designs/gqlgen version v0.17.66
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -301,6 +301,32 @@ func (r *queryResolver) JobMetrics(ctx context.Context, id string, metrics []str
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JobMetricStats is the resolver for the jobMetricStats field.
|
||||||
|
func (r *queryResolver) JobMetricStats(ctx context.Context, id string, metrics []string) ([]*model.JobMetricStatWithName, error) {
|
||||||
|
|
||||||
|
job, err := r.Query().Job(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while querying job for metrics")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := metricDataDispatcher.LoadStatData(job, metrics, ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while loading job stat data")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*model.JobMetricStatWithName{}
|
||||||
|
for name, md := range data {
|
||||||
|
res = append(res, &model.JobMetricStatWithName{
|
||||||
|
Name: name,
|
||||||
|
Stats: &md,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
// JobsFootprints is the resolver for the jobsFootprints field.
|
// JobsFootprints is the resolver for the jobsFootprints field.
|
||||||
func (r *queryResolver) JobsFootprints(ctx context.Context, filter []*model.JobFilter, metrics []string) (*model.Footprints, error) {
|
func (r *queryResolver) JobsFootprints(ctx context.Context, filter []*model.JobFilter, metrics []string) (*model.Footprints, error) {
|
||||||
// NOTE: Legacy Naming! This resolver is for normalized histograms in analysis view only - *Not* related to DB "footprint" column!
|
// NOTE: Legacy Naming! This resolver is for normalized histograms in analysis view only - *Not* related to DB "footprint" column!
|
||||||
|
@ -96,27 +96,35 @@ func HandleImportFlag(flag string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
job.EnergyFootprint = make(map[string]float64)
|
job.EnergyFootprint = make(map[string]float64)
|
||||||
var totalEnergy float64
|
|
||||||
var energy float64
|
|
||||||
|
|
||||||
|
// Total Job Energy Outside Loop
|
||||||
|
totalEnergy := 0.0
|
||||||
for _, fp := range sc.EnergyFootprint {
|
for _, fp := range sc.EnergyFootprint {
|
||||||
|
// Always Init Metric Energy Inside Loop
|
||||||
|
metricEnergy := 0.0
|
||||||
if i, err := archive.MetricIndex(sc.MetricConfig, fp); err == nil {
|
if i, err := archive.MetricIndex(sc.MetricConfig, fp); err == nil {
|
||||||
// Note: For DB data, calculate and save as kWh
|
// Note: For DB data, calculate and save as kWh
|
||||||
// Energy: Power (in Watts) * Time (in Seconds)
|
|
||||||
if sc.MetricConfig[i].Energy == "energy" { // this metric has energy as unit (Joules)
|
if sc.MetricConfig[i].Energy == "energy" { // this metric has energy as unit (Joules)
|
||||||
|
log.Warnf("Update EnergyFootprint for Job %d and Metric %s on cluster %s: Set to 'energy' in cluster.json: Not implemented, will return 0.0", job.JobID, job.Cluster, fp)
|
||||||
|
// FIXME: Needs sum as stats type
|
||||||
} else if sc.MetricConfig[i].Energy == "power" { // this metric has power as unit (Watt)
|
} else if sc.MetricConfig[i].Energy == "power" { // this metric has power as unit (Watt)
|
||||||
// Unit: ( W * s ) / 3600 / 1000 = kWh ; Rounded to 2 nearest digits
|
// Energy: Power (in Watts) * Time (in Seconds)
|
||||||
energy = math.Round(((repository.LoadJobStat(&job, fp, "avg")*float64(job.Duration))/3600/1000)*100) / 100
|
// Unit: (W * (s / 3600)) / 1000 = kWh
|
||||||
|
// Round 2 Digits: round(Energy * 100) / 100
|
||||||
|
// Here: (All-Node Metric Average * Number of Nodes) * (Job Duration in Seconds / 3600) / 1000
|
||||||
|
// Note: Shared Jobs handled correctly since "Node Average" is based on partial resources, while "numNodes" factor is 1
|
||||||
|
rawEnergy := ((repository.LoadJobStat(&job, fp, "avg") * float64(job.NumNodes)) * (float64(job.Duration) / 3600.0)) / 1000.0
|
||||||
|
metricEnergy = math.Round(rawEnergy*100.0) / 100.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Error while collecting energy metric %s for job, DB ID '%v', return '0.0'", fp, job.ID)
|
log.Warnf("Error while collecting energy metric %s for job, DB ID '%v', return '0.0'", fp, job.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
job.EnergyFootprint[fp] = energy
|
job.EnergyFootprint[fp] = metricEnergy
|
||||||
totalEnergy += energy
|
totalEnergy += metricEnergy
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Energy = (math.Round(totalEnergy*100) / 100)
|
job.Energy = (math.Round(totalEnergy*100.0) / 100.0)
|
||||||
if job.RawEnergyFootprint, err = json.Marshal(job.EnergyFootprint); err != nil {
|
if job.RawEnergyFootprint, err = json.Marshal(job.EnergyFootprint); err != nil {
|
||||||
log.Warnf("Error while marshaling energy footprint for job INTO BYTES, DB ID '%v'", job.ID)
|
log.Warnf("Error while marshaling energy footprint for job INTO BYTES, DB ID '%v'", job.ID)
|
||||||
return err
|
return err
|
||||||
|
@ -93,27 +93,35 @@ func InitDB() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
job.EnergyFootprint = make(map[string]float64)
|
job.EnergyFootprint = make(map[string]float64)
|
||||||
var totalEnergy float64
|
|
||||||
var energy float64
|
|
||||||
|
|
||||||
|
// Total Job Energy Outside Loop
|
||||||
|
totalEnergy := 0.0
|
||||||
for _, fp := range sc.EnergyFootprint {
|
for _, fp := range sc.EnergyFootprint {
|
||||||
|
// Always Init Metric Energy Inside Loop
|
||||||
|
metricEnergy := 0.0
|
||||||
if i, err := archive.MetricIndex(sc.MetricConfig, fp); err == nil {
|
if i, err := archive.MetricIndex(sc.MetricConfig, fp); err == nil {
|
||||||
// Note: For DB data, calculate and save as kWh
|
// Note: For DB data, calculate and save as kWh
|
||||||
// Energy: Power (in Watts) * Time (in Seconds)
|
|
||||||
if sc.MetricConfig[i].Energy == "energy" { // this metric has energy as unit (Joules)
|
if sc.MetricConfig[i].Energy == "energy" { // this metric has energy as unit (Joules)
|
||||||
|
log.Warnf("Update EnergyFootprint for Job %d and Metric %s on cluster %s: Set to 'energy' in cluster.json: Not implemented, will return 0.0", jobMeta.JobID, jobMeta.Cluster, fp)
|
||||||
|
// FIXME: Needs sum as stats type
|
||||||
} else if sc.MetricConfig[i].Energy == "power" { // this metric has power as unit (Watt)
|
} else if sc.MetricConfig[i].Energy == "power" { // this metric has power as unit (Watt)
|
||||||
// Unit: ( W * s ) / 3600 / 1000 = kWh ; Rounded to 2 nearest digits
|
// Energy: Power (in Watts) * Time (in Seconds)
|
||||||
energy = math.Round(((repository.LoadJobStat(jobMeta, fp, "avg")*float64(jobMeta.Duration))/3600/1000)*100) / 100
|
// Unit: (W * (s / 3600)) / 1000 = kWh
|
||||||
|
// Round 2 Digits: round(Energy * 100) / 100
|
||||||
|
// Here: (All-Node Metric Average * Number of Nodes) * (Job Duration in Seconds / 3600) / 1000
|
||||||
|
// Note: Shared Jobs handled correctly since "Node Average" is based on partial resources, while "numNodes" factor is 1
|
||||||
|
rawEnergy := ((repository.LoadJobStat(jobMeta, fp, "avg") * float64(jobMeta.NumNodes)) * (float64(jobMeta.Duration) / 3600.0)) / 1000.0
|
||||||
|
metricEnergy = math.Round(rawEnergy*100.0) / 100.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Error while collecting energy metric %s for job, DB ID '%v', return '0.0'", fp, jobMeta.ID)
|
log.Warnf("Error while collecting energy metric %s for job, DB ID '%v', return '0.0'", fp, jobMeta.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
job.EnergyFootprint[fp] = energy
|
job.EnergyFootprint[fp] = metricEnergy
|
||||||
totalEnergy += energy
|
totalEnergy += metricEnergy
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Energy = (math.Round(totalEnergy*100) / 100)
|
job.Energy = (math.Round(totalEnergy*100.0) / 100.0)
|
||||||
if job.RawEnergyFootprint, err = json.Marshal(job.EnergyFootprint); err != nil {
|
if job.RawEnergyFootprint, err = json.Marshal(job.EnergyFootprint); err != nil {
|
||||||
log.Warnf("Error while marshaling energy footprint for job INTO BYTES, DB ID '%v'", jobMeta.ID)
|
log.Warnf("Error while marshaling energy footprint for job INTO BYTES, DB ID '%v'", jobMeta.ID)
|
||||||
return err
|
return err
|
||||||
|
@ -7,6 +7,7 @@ package metricDataDispatcher
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
"github.com/ClusterCockpit/cc-backend/internal/config"
|
||||||
@ -223,6 +224,52 @@ func LoadAverages(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for polar plots in frontend
|
||||||
|
func LoadStatData(
|
||||||
|
job *schema.Job,
|
||||||
|
metrics []string,
|
||||||
|
ctx context.Context,
|
||||||
|
) (map[string]schema.MetricStatistics, error) {
|
||||||
|
if job.State != schema.JobStateRunning && !config.Keys.DisableArchive {
|
||||||
|
return archive.LoadStatsFromArchive(job, metrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]schema.MetricStatistics, len(metrics))
|
||||||
|
repo, err := metricdata.GetMetricDataRepo(job.Cluster)
|
||||||
|
if err != nil {
|
||||||
|
return data, fmt.Errorf("METRICDATA/METRICDATA > no metric data repository configured for '%s'", job.Cluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := repo.LoadStats(job, metrics, ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while loading statistics for job %v (User %v, Project %v)", job.JobID, job.User, job.Project)
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range metrics {
|
||||||
|
sum, avg, min, max := 0.0, 0.0, 0.0, 0.0
|
||||||
|
nodes, ok := stats[m]
|
||||||
|
if !ok {
|
||||||
|
data[m] = schema.MetricStatistics{Min: min, Avg: avg, Max: max}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
sum += node.Avg
|
||||||
|
min = math.Min(min, node.Min)
|
||||||
|
max = math.Max(max, node.Max)
|
||||||
|
}
|
||||||
|
|
||||||
|
data[m] = schema.MetricStatistics{
|
||||||
|
Avg: (math.Round((sum/float64(job.NumNodes))*100) / 100),
|
||||||
|
Min: (math.Round(min*100) / 100),
|
||||||
|
Max: (math.Round(max*100) / 100),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Used for the classic node/system view. Returns a map of nodes to a map of metrics.
|
// Used for the classic node/system view. Returns a map of nodes to a map of metrics.
|
||||||
func LoadNodeData(
|
func LoadNodeData(
|
||||||
cluster string,
|
cluster string,
|
||||||
|
@ -590,28 +590,34 @@ func (r *JobRepository) UpdateEnergy(
|
|||||||
return stmt, err
|
return stmt, err
|
||||||
}
|
}
|
||||||
energyFootprint := make(map[string]float64)
|
energyFootprint := make(map[string]float64)
|
||||||
var totalEnergy float64
|
|
||||||
var energy float64
|
|
||||||
|
|
||||||
|
// Total Job Energy Outside Loop
|
||||||
|
totalEnergy := 0.0
|
||||||
for _, fp := range sc.EnergyFootprint {
|
for _, fp := range sc.EnergyFootprint {
|
||||||
|
// Always Init Metric Energy Inside Loop
|
||||||
|
metricEnergy := 0.0
|
||||||
if i, err := archive.MetricIndex(sc.MetricConfig, fp); err == nil {
|
if i, err := archive.MetricIndex(sc.MetricConfig, fp); err == nil {
|
||||||
// Note: For DB data, calculate and save as kWh
|
// Note: For DB data, calculate and save as kWh
|
||||||
if sc.MetricConfig[i].Energy == "energy" { // this metric has energy as unit (Joules or Wh)
|
if sc.MetricConfig[i].Energy == "energy" { // this metric has energy as unit (Joules or Wh)
|
||||||
|
log.Warnf("Update EnergyFootprint for Job %d and Metric %s on cluster %s: Set to 'energy' in cluster.json: Not implemented, will return 0.0", jobMeta.JobID, jobMeta.Cluster, fp)
|
||||||
// FIXME: Needs sum as stats type
|
// FIXME: Needs sum as stats type
|
||||||
} else if sc.MetricConfig[i].Energy == "power" { // this metric has power as unit (Watt)
|
} else if sc.MetricConfig[i].Energy == "power" { // this metric has power as unit (Watt)
|
||||||
// Energy: Power (in Watts) * Time (in Seconds)
|
// Energy: Power (in Watts) * Time (in Seconds)
|
||||||
// Unit: (( W * s ) / 3600) / 1000 = kWh ; Rounded to 2 nearest digits: (Energy * 100) / 100
|
// Unit: (W * (s / 3600)) / 1000 = kWh
|
||||||
// Here: All-Node Metric Average * Number of Nodes * Job Runtime
|
// Round 2 Digits: round(Energy * 100) / 100
|
||||||
|
// Here: (All-Node Metric Average * Number of Nodes) * (Job Duration in Seconds / 3600) / 1000
|
||||||
// Note: Shared Jobs handled correctly since "Node Average" is based on partial resources, while "numNodes" factor is 1
|
// Note: Shared Jobs handled correctly since "Node Average" is based on partial resources, while "numNodes" factor is 1
|
||||||
metricNodeSum := LoadJobStat(jobMeta, fp, "avg") * float64(jobMeta.NumNodes) * float64(jobMeta.Duration)
|
rawEnergy := ((LoadJobStat(jobMeta, fp, "avg") * float64(jobMeta.NumNodes)) * (float64(jobMeta.Duration) / 3600.0)) / 1000.0
|
||||||
energy = math.Round(((metricNodeSum/3600)/1000)*100) / 100
|
metricEnergy = math.Round(rawEnergy*100.0) / 100.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Error while collecting energy metric %s for job, DB ID '%v', return '0.0'", fp, jobMeta.ID)
|
log.Warnf("Error while collecting energy metric %s for job, DB ID '%v', return '0.0'", fp, jobMeta.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
energyFootprint[fp] = energy
|
energyFootprint[fp] = metricEnergy
|
||||||
totalEnergy += energy
|
totalEnergy += metricEnergy
|
||||||
|
|
||||||
|
// log.Infof("Metric %s Average %f -> %f kWh | Job %d Total -> %f kWh", fp, LoadJobStat(jobMeta, fp, "avg"), energy, jobMeta.JobID, totalEnergy)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawFootprint []byte
|
var rawFootprint []byte
|
||||||
@ -620,7 +626,7 @@ func (r *JobRepository) UpdateEnergy(
|
|||||||
return stmt, err
|
return stmt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return stmt.Set("energy_footprint", string(rawFootprint)).Set("energy", (math.Round(totalEnergy*100) / 100)), nil
|
return stmt.Set("energy_footprint", string(rawFootprint)).Set("energy", (math.Round(totalEnergy*100.0) / 100.0)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *JobRepository) UpdateFootprint(
|
func (r *JobRepository) UpdateFootprint(
|
||||||
|
@ -176,6 +176,9 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select
|
|||||||
now := time.Now().Unix() // There does not seam to be a portable way to get the current unix timestamp accross different DBs.
|
now := time.Now().Unix() // There does not seam to be a portable way to get the current unix timestamp accross different DBs.
|
||||||
query = query.Where("(job.job_state != 'running' OR (? - job.start_time) > ?)", now, *filter.MinRunningFor)
|
query = query.Where("(job.job_state != 'running' OR (? - job.start_time) > ?)", now, *filter.MinRunningFor)
|
||||||
}
|
}
|
||||||
|
if filter.Exclusive != nil {
|
||||||
|
query = query.Where("job.exclusive = ?", *filter.Exclusive)
|
||||||
|
}
|
||||||
if filter.State != nil {
|
if filter.State != nil {
|
||||||
states := make([]string, len(filter.State))
|
states := make([]string, len(filter.State))
|
||||||
for i, val := range filter.State {
|
for i, val := range filter.State {
|
||||||
|
@ -102,7 +102,7 @@ func GetHandle() ArchiveBackend {
|
|||||||
return ar
|
return ar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to metricdata.LoadAverages().
|
// Helper to metricdataloader.LoadAverages().
|
||||||
func LoadAveragesFromArchive(
|
func LoadAveragesFromArchive(
|
||||||
job *schema.Job,
|
job *schema.Job,
|
||||||
metrics []string,
|
metrics []string,
|
||||||
@ -125,6 +125,35 @@ func LoadAveragesFromArchive(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to metricdataloader.LoadStatData().
|
||||||
|
func LoadStatsFromArchive(
|
||||||
|
job *schema.Job,
|
||||||
|
metrics []string,
|
||||||
|
) (map[string]schema.MetricStatistics, error) {
|
||||||
|
data := make(map[string]schema.MetricStatistics, len(metrics))
|
||||||
|
metaFile, err := ar.LoadJobMeta(job)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while loading job metadata from archiveBackend")
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range metrics {
|
||||||
|
stat, ok := metaFile.Statistics[m]
|
||||||
|
if !ok {
|
||||||
|
data[m] = schema.MetricStatistics{Min: 0.0, Avg: 0.0, Max: 0.0}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data[m] = schema.MetricStatistics{
|
||||||
|
Avg: stat.Avg,
|
||||||
|
Min: stat.Min,
|
||||||
|
Max: stat.Max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetStatistics(job *schema.Job) (map[string]schema.JobStatistics, error) {
|
func GetStatistics(job *schema.Job) (map[string]schema.JobStatistics, error) {
|
||||||
metaFile, err := ar.LoadJobMeta(job)
|
metaFile, err := ar.LoadJobMeta(job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
Card,
|
Card,
|
||||||
Table,
|
Table,
|
||||||
Icon,
|
Icon,
|
||||||
|
Tooltip
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import {
|
import {
|
||||||
init,
|
init,
|
||||||
@ -70,6 +71,8 @@
|
|||||||
...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]),
|
...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$: clusterName = cluster?.name ? cluster.name : cluster;
|
||||||
|
|
||||||
const sortOptions = [
|
const sortOptions = [
|
||||||
{ key: "totalWalltime", label: "Walltime" },
|
{ key: "totalWalltime", label: "Walltime" },
|
||||||
{ key: "totalNodeHours", label: "Node Hours" },
|
{ key: "totalNodeHours", label: "Node Hours" },
|
||||||
@ -159,6 +162,7 @@
|
|||||||
groupBy: $groupBy
|
groupBy: $groupBy
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
totalWalltime
|
totalWalltime
|
||||||
totalNodeHours
|
totalNodeHours
|
||||||
totalCoreHours
|
totalCoreHours
|
||||||
@ -422,15 +426,22 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
|
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
|
||||||
{#if groupSelection.key == "user"}
|
{#if groupSelection.key == "user"}
|
||||||
<th scope="col"
|
<th scope="col" id="topName-{te.id}"
|
||||||
><a href="/monitoring/user/{te.id}?cluster={cluster}"
|
><a href="/monitoring/user/{te.id}?cluster={clusterName}"
|
||||||
>{te.id}</a
|
>{te.id}</a
|
||||||
></th
|
></th
|
||||||
>
|
>
|
||||||
|
{#if te?.name}
|
||||||
|
<Tooltip
|
||||||
|
target={`topName-${te.id}`}
|
||||||
|
placement="left"
|
||||||
|
>{te.name}</Tooltip
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
><a
|
><a
|
||||||
href="/monitoring/jobs/?cluster={cluster}&project={te.id}&projectMatch=eq"
|
href="/monitoring/jobs/?cluster={clusterName}&project={te.id}&projectMatch=eq"
|
||||||
>{te.id}</a
|
>{te.id}</a
|
||||||
></th
|
></th
|
||||||
>
|
>
|
||||||
|
@ -31,17 +31,16 @@
|
|||||||
init,
|
init,
|
||||||
groupByScope,
|
groupByScope,
|
||||||
checkMetricDisabled,
|
checkMetricDisabled,
|
||||||
transformDataForRoofline,
|
|
||||||
} from "./generic/utils.js";
|
} from "./generic/utils.js";
|
||||||
import Metric from "./job/Metric.svelte";
|
import Metric from "./job/Metric.svelte";
|
||||||
import StatsTable from "./job/StatsTable.svelte";
|
|
||||||
import JobSummary from "./job/JobSummary.svelte";
|
|
||||||
import EnergySummary from "./job/EnergySummary.svelte";
|
|
||||||
import ConcurrentJobs from "./generic/helper/ConcurrentJobs.svelte";
|
|
||||||
import PlotGrid from "./generic/PlotGrid.svelte";
|
|
||||||
import Roofline from "./generic/plots/Roofline.svelte";
|
|
||||||
import JobInfo from "./generic/joblist/JobInfo.svelte";
|
|
||||||
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
||||||
|
import JobInfo from "./generic/joblist/JobInfo.svelte";
|
||||||
|
import ConcurrentJobs from "./generic/helper/ConcurrentJobs.svelte";
|
||||||
|
import JobSummary from "./job/JobSummary.svelte";
|
||||||
|
import JobRoofline from "./job/JobRoofline.svelte";
|
||||||
|
import EnergySummary from "./job/EnergySummary.svelte";
|
||||||
|
import PlotGrid from "./generic/PlotGrid.svelte";
|
||||||
|
import StatsTable from "./job/StatsTable.svelte";
|
||||||
|
|
||||||
export let dbid;
|
export let dbid;
|
||||||
export let username;
|
export let username;
|
||||||
@ -57,10 +56,10 @@
|
|||||||
selectedScopes = [];
|
selectedScopes = [];
|
||||||
|
|
||||||
let plots = {},
|
let plots = {},
|
||||||
roofWidth,
|
|
||||||
statsTable
|
statsTable
|
||||||
|
|
||||||
let missingMetrics = [],
|
let availableMetrics = new Set(),
|
||||||
|
missingMetrics = [],
|
||||||
missingHosts = [],
|
missingHosts = [],
|
||||||
somethingMissing = false;
|
somethingMissing = false;
|
||||||
|
|
||||||
@ -117,43 +116,36 @@
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const roofQuery = gql`
|
|
||||||
query ($dbid: ID!, $selectedMetrics: [String!]!, $selectedScopes: [MetricScope!]!, $selectedResolution: Int) {
|
|
||||||
jobMetrics(id: $dbid, metrics: $selectedMetrics, scopes: $selectedScopes, resolution: $selectedResolution) {
|
|
||||||
name
|
|
||||||
scope
|
|
||||||
metric {
|
|
||||||
series {
|
|
||||||
data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
$: jobMetrics = queryStore({
|
$: jobMetrics = queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: query,
|
query: query,
|
||||||
variables: { dbid, selectedMetrics, selectedScopes },
|
variables: { dbid, selectedMetrics, selectedScopes },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Roofline: Always load roofMetrics with configured timestep (Resolution: 0)
|
|
||||||
$: roofMetrics = queryStore({
|
|
||||||
client: client,
|
|
||||||
query: roofQuery,
|
|
||||||
variables: { dbid, selectedMetrics: ["flops_any", "mem_bw"], selectedScopes: ["node"], selectedResolution: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle Job Query on Init -> is not executed anymore
|
// Handle Job Query on Init -> is not executed anymore
|
||||||
getContext("on-init")(() => {
|
getContext("on-init")(() => {
|
||||||
let job = $initq.data.job;
|
let job = $initq.data.job;
|
||||||
if (!job) return;
|
if (!job) return;
|
||||||
|
|
||||||
const pendingMetrics = [
|
const pendingMetrics = [
|
||||||
...(ccconfig[`job_view_selectedMetrics:${job.cluster}`] ||
|
...(
|
||||||
ccconfig[`job_view_selectedMetrics`]
|
(
|
||||||
|
ccconfig[`job_view_selectedMetrics:${job.cluster}:${job.subCluster}`] ||
|
||||||
|
ccconfig[`job_view_selectedMetrics:${job.cluster}`]
|
||||||
|
) ||
|
||||||
|
$initq.data.globalMetrics
|
||||||
|
.reduce((names, gm) => {
|
||||||
|
if (gm.availability.find((av) => av.cluster === job.cluster && av.subClusters.includes(job.subCluster))) {
|
||||||
|
names.push(gm.name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}, [])
|
||||||
),
|
),
|
||||||
...(ccconfig[`job_view_nodestats_selectedMetrics:${job.cluster}`] ||
|
...(
|
||||||
|
(
|
||||||
|
ccconfig[`job_view_nodestats_selectedMetrics:${job.cluster}:${job.subCluster}`] ||
|
||||||
|
ccconfig[`job_view_nodestats_selectedMetrics:${job.cluster}`]
|
||||||
|
) ||
|
||||||
ccconfig[`job_view_nodestats_selectedMetrics`]
|
ccconfig[`job_view_nodestats_selectedMetrics`]
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -235,7 +227,7 @@ const roofQuery = gql`
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row class="mb-3">
|
<Row class="mb-3">
|
||||||
<!-- Column 1: Job Info, Job Tags, Concurrent Jobs, Admin Message if found-->
|
<!-- Row 1, Column 1: Job Info, Job Tags, Concurrent Jobs, Admin Message if found-->
|
||||||
<Col xs={12} md={6} xl={3} class="mb-3 mb-xxl-0">
|
<Col xs={12} md={6} xl={3} class="mb-3 mb-xxl-0">
|
||||||
{#if $initq.error}
|
{#if $initq.error}
|
||||||
<Card body color="danger">{$initq.error.message}</Card>
|
<Card body color="danger">{$initq.error.message}</Card>
|
||||||
@ -277,51 +269,30 @@ const roofQuery = gql`
|
|||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<!-- Column 2: Job Footprint, Polar Representation, Heuristic Summary -->
|
<!-- Row 1, Column 2: Job Footprint, Polar Representation -->
|
||||||
<Col xs={12} md={6} xl={4} xxl={3} class="mb-3 mb-xxl-0">
|
<Col xs={12} md={6} xl={4} xxl={3} class="mb-3 mb-xxl-0">
|
||||||
{#if $initq.error}
|
{#if $initq.error}
|
||||||
<Card body color="danger">{$initq.error.message}</Card>
|
<Card body color="danger">{$initq.error.message}</Card>
|
||||||
{:else if $initq?.data && $jobMetrics?.data}
|
{:else if $initq?.data}
|
||||||
<JobSummary job={$initq.data.job} jobMetrics={$jobMetrics.data.jobMetrics}/>
|
<JobSummary job={$initq.data.job}/>
|
||||||
{:else}
|
{:else}
|
||||||
<Spinner secondary />
|
<Spinner secondary />
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<!-- Column 3: Job Roofline; If footprint Enabled: full width, else half width -->
|
<!-- Row 1, Column 3: Job Roofline; If footprint Enabled: full width, else half width -->
|
||||||
<Col xs={12} md={12} xl={5} xxl={6}>
|
<Col xs={12} md={12} xl={5} xxl={6}>
|
||||||
{#if $initq.error || $roofMetrics.error}
|
{#if $initq.error}
|
||||||
<Card body color="danger">
|
<Card body color="danger">{$initq.error.message}</Card>
|
||||||
<p>Initq Error: {$initq.error?.message}</p>
|
{:else if $initq?.data}
|
||||||
<p>roofMetrics (jobMetrics) Error: {$roofMetrics.error?.message}</p>
|
<JobRoofline job={$initq.data.job} clusters={$initq.data.clusters}/>
|
||||||
</Card>
|
|
||||||
{:else if $initq?.data && $roofMetrics?.data}
|
|
||||||
<Card style="height: 400px;">
|
|
||||||
<div bind:clientWidth={roofWidth}>
|
|
||||||
<Roofline
|
|
||||||
allowSizeChange={true}
|
|
||||||
width={roofWidth}
|
|
||||||
renderTime={true}
|
|
||||||
subCluster={$initq.data.clusters
|
|
||||||
.find((c) => c.name == $initq.data.job.cluster)
|
|
||||||
.subClusters.find((sc) => sc.name == $initq.data.job.subCluster)}
|
|
||||||
data={transformDataForRoofline(
|
|
||||||
$roofMetrics.data?.jobMetrics?.find(
|
|
||||||
(m) => m.name == "flops_any" && m.scope == "node",
|
|
||||||
)?.metric,
|
|
||||||
$roofMetrics.data?.jobMetrics?.find(
|
|
||||||
(m) => m.name == "mem_bw" && m.scope == "node",
|
|
||||||
)?.metric,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
{:else}
|
{:else}
|
||||||
<Spinner secondary />
|
<Spinner secondary />
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
<!-- Row 2: Energy Information if available -->
|
||||||
{#if $initq?.data && $initq.data.job.energyFootprint.length != 0}
|
{#if $initq?.data && $initq.data.job.energyFootprint.length != 0}
|
||||||
<Row class="mb-3">
|
<Row class="mb-3">
|
||||||
<Col>
|
<Col>
|
||||||
@ -330,13 +301,14 @@ const roofQuery = gql`
|
|||||||
</Row>
|
</Row>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Metric Plot Grid -->
|
||||||
<Card class="mb-3">
|
<Card class="mb-3">
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Row class="mb-2">
|
<Row class="mb-2">
|
||||||
{#if $initq.data}
|
{#if $initq.data}
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
<Button outline on:click={() => (isMetricsSelectionOpen = true)} color="primary">
|
<Button outline on:click={() => (isMetricsSelectionOpen = true)} color="primary">
|
||||||
Select Metrics
|
Select Metrics (Selected {selectedMetrics.length} of {availableMetrics.size} available)
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
{/if}
|
{/if}
|
||||||
@ -390,6 +362,7 @@ const roofQuery = gql`
|
|||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- Statistcics Table -->
|
||||||
<Row class="mb-3">
|
<Row class="mb-3">
|
||||||
<Col>
|
<Col>
|
||||||
{#if $initq.data}
|
{#if $initq.data}
|
||||||
@ -470,9 +443,11 @@ const roofQuery = gql`
|
|||||||
{#if $initq.data}
|
{#if $initq.data}
|
||||||
<MetricSelection
|
<MetricSelection
|
||||||
cluster={$initq.data.job.cluster}
|
cluster={$initq.data.job.cluster}
|
||||||
|
subCluster={$initq.data.job.subCluster}
|
||||||
configName="job_view_selectedMetrics"
|
configName="job_view_selectedMetrics"
|
||||||
bind:metrics={selectedMetrics}
|
bind:metrics={selectedMetrics}
|
||||||
bind:isOpen={isMetricsSelectionOpen}
|
bind:isOpen={isMetricsSelectionOpen}
|
||||||
|
bind:allMetrics={availableMetrics}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -137,5 +137,5 @@
|
|||||||
bind:metrics
|
bind:metrics
|
||||||
bind:isOpen={isMetricsSelectionOpen}
|
bind:isOpen={isMetricsSelectionOpen}
|
||||||
bind:showFootprint
|
bind:showFootprint
|
||||||
footprintSelect={true}
|
footprintSelect
|
||||||
/>
|
/>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
Progress,
|
Progress,
|
||||||
Icon,
|
Icon,
|
||||||
Button,
|
Button,
|
||||||
|
Tooltip
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import {
|
import {
|
||||||
queryStore,
|
queryStore,
|
||||||
@ -75,9 +76,9 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
let isHistogramSelectionOpen = false;
|
let isHistogramSelectionOpen = false;
|
||||||
$: metricsInHistograms = cluster
|
$: selectedHistograms = cluster
|
||||||
? ccconfig[`user_view_histogramMetrics:${cluster}`] || []
|
? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
||||||
: ccconfig.user_view_histogramMetrics || [];
|
: ccconfig['user_view_histogramMetrics'] || [];
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
// Note: nodeMetrics are requested on configured $timestep resolution
|
// Note: nodeMetrics are requested on configured $timestep resolution
|
||||||
@ -90,7 +91,7 @@
|
|||||||
$metrics: [String!]
|
$metrics: [String!]
|
||||||
$from: Time!
|
$from: Time!
|
||||||
$to: Time!
|
$to: Time!
|
||||||
$metricsInHistograms: [String!]
|
$selectedHistograms: [String!]
|
||||||
) {
|
) {
|
||||||
nodeMetrics(
|
nodeMetrics(
|
||||||
cluster: $cluster
|
cluster: $cluster
|
||||||
@ -116,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats: jobsStatistics(filter: $filter, metrics: $metricsInHistograms) {
|
stats: jobsStatistics(filter: $filter, metrics: $selectedHistograms) {
|
||||||
histDuration {
|
histDuration {
|
||||||
count
|
count
|
||||||
value
|
value
|
||||||
@ -157,7 +158,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,
|
selectedHistograms: selectedHistograms,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -177,6 +178,7 @@
|
|||||||
groupBy: USER
|
groupBy: USER
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
totalJobs
|
totalJobs
|
||||||
totalNodes
|
totalNodes
|
||||||
totalCores
|
totalCores
|
||||||
@ -515,12 +517,19 @@
|
|||||||
{#each $topUserQuery.data.topUser as tu, i}
|
{#each $topUserQuery.data.topUser as tu, i}
|
||||||
<tr>
|
<tr>
|
||||||
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
|
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
|
||||||
<th scope="col"
|
<th scope="col" id="topName-{tu.id}"
|
||||||
><a
|
><a
|
||||||
href="/monitoring/user/{tu.id}?cluster={cluster}&state=running"
|
href="/monitoring/user/{tu.id}?cluster={cluster}&state=running"
|
||||||
>{tu.id}</a
|
>{tu.id}</a
|
||||||
></th
|
></th
|
||||||
>
|
>
|
||||||
|
{#if tu?.name}
|
||||||
|
<Tooltip
|
||||||
|
target={`topName-${tu.id}`}
|
||||||
|
placement="left"
|
||||||
|
>{tu.name}</Tooltip
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
<td>{tu[topUserSelection.key]}</td>
|
<td>{tu[topUserSelection.key]}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
@ -652,7 +661,7 @@
|
|||||||
|
|
||||||
<!-- Selectable Stats as Histograms : Average Values of Running Jobs -->
|
<!-- Selectable Stats as Histograms : Average Values of Running Jobs -->
|
||||||
|
|
||||||
{#if metricsInHistograms}
|
{#if selectedHistograms}
|
||||||
{#key $mainQuery.data.stats[0].histMetrics}
|
{#key $mainQuery.data.stats[0].histMetrics}
|
||||||
<PlotGrid
|
<PlotGrid
|
||||||
let:item
|
let:item
|
||||||
@ -675,6 +684,6 @@
|
|||||||
|
|
||||||
<HistogramSelection
|
<HistogramSelection
|
||||||
bind:cluster
|
bind:cluster
|
||||||
bind:metricsInHistograms
|
bind:selectedHistograms
|
||||||
bind:isOpen={isHistogramSelectionOpen}
|
bind:isOpen={isHistogramSelectionOpen}
|
||||||
/>
|
/>
|
||||||
|
@ -29,8 +29,8 @@
|
|||||||
import Refresher from "./generic/helper/Refresher.svelte";
|
import Refresher from "./generic/helper/Refresher.svelte";
|
||||||
|
|
||||||
export let displayType;
|
export let displayType;
|
||||||
export let cluster;
|
export let cluster = null;
|
||||||
export let subCluster = "";
|
export let subCluster = null;
|
||||||
export let from = null;
|
export let from = null;
|
||||||
export let to = null;
|
export let to = null;
|
||||||
|
|
||||||
@ -60,7 +60,10 @@
|
|||||||
let hostnameFilter = "";
|
let hostnameFilter = "";
|
||||||
let pendingHostnameFilter = "";
|
let pendingHostnameFilter = "";
|
||||||
let selectedMetric = ccconfig.system_view_selectedMetric || "";
|
let selectedMetric = ccconfig.system_view_selectedMetric || "";
|
||||||
let selectedMetrics = ccconfig[`node_list_selectedMetrics:${cluster}`] || [ccconfig.system_view_selectedMetric];
|
let selectedMetrics = (
|
||||||
|
ccconfig[`node_list_selectedMetrics:${cluster}:${subCluster}`] ||
|
||||||
|
ccconfig[`node_list_selectedMetrics:${cluster}`]
|
||||||
|
) || [ccconfig.system_view_selectedMetric];
|
||||||
let isMetricsSelectionOpen = false;
|
let isMetricsSelectionOpen = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -191,6 +194,7 @@
|
|||||||
|
|
||||||
<MetricSelection
|
<MetricSelection
|
||||||
{cluster}
|
{cluster}
|
||||||
|
{subCluster}
|
||||||
configName="node_list_selectedMetrics"
|
configName="node_list_selectedMetrics"
|
||||||
metrics={selectedMetrics}
|
metrics={selectedMetrics}
|
||||||
bind:isOpen={isMetricsSelectionOpen}
|
bind:isOpen={isMetricsSelectionOpen}
|
||||||
|
@ -68,16 +68,16 @@
|
|||||||
let durationBinOptions = ["1m","10m","1h","6h","12h"];
|
let durationBinOptions = ["1m","10m","1h","6h","12h"];
|
||||||
let metricBinOptions = [10, 20, 50, 100];
|
let metricBinOptions = [10, 20, 50, 100];
|
||||||
|
|
||||||
$: metricsInHistograms = selectedCluster
|
$: selectedHistograms = selectedCluster
|
||||||
? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || []
|
? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
||||||
: ccconfig.user_view_histogramMetrics || [];
|
: 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!]!, $metricsInHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) {
|
query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) {
|
||||||
jobsStatistics(filter: $jobFilters, metrics: $metricsInHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) {
|
jobsStatistics(filter: $jobFilters, metrics: $selectedHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) {
|
||||||
totalJobs
|
totalJobs
|
||||||
shortJobs
|
shortJobs
|
||||||
totalWalltime
|
totalWalltime
|
||||||
@ -104,7 +104,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: { jobFilters, metricsInHistograms, numDurationBins, numMetricBins },
|
variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins },
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => filterComponent.updateFilters());
|
onMount(() => filterComponent.updateFilters());
|
||||||
@ -290,7 +290,7 @@
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{#if metricsInHistograms?.length > 0}
|
{#if selectedHistograms?.length > 0}
|
||||||
{#if $stats.error}
|
{#if $stats.error}
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
@ -352,11 +352,11 @@
|
|||||||
bind:metrics
|
bind:metrics
|
||||||
bind:isOpen={isMetricsSelectionOpen}
|
bind:isOpen={isMetricsSelectionOpen}
|
||||||
bind:showFootprint
|
bind:showFootprint
|
||||||
footprintSelect={true}
|
footprintSelect
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HistogramSelection
|
<HistogramSelection
|
||||||
bind:cluster={selectedCluster}
|
bind:cluster={selectedCluster}
|
||||||
bind:metricsInHistograms
|
bind:selectedHistograms
|
||||||
bind:isOpen={isHistogramSelectionOpen}
|
bind:isOpen={isHistogramSelectionOpen}
|
||||||
/>
|
/>
|
||||||
|
@ -45,6 +45,14 @@
|
|||||||
export let startTimeQuickSelect = false;
|
export let startTimeQuickSelect = false;
|
||||||
export let matchedJobs = -2;
|
export let matchedJobs = -2;
|
||||||
|
|
||||||
|
const startTimeSelectOptions = [
|
||||||
|
{ range: "", rangeLabel: "No Selection"},
|
||||||
|
{ range: "last6h", rangeLabel: "Last 6hrs"},
|
||||||
|
{ range: "last24h", rangeLabel: "Last 24hrs"},
|
||||||
|
{ range: "last7d", rangeLabel: "Last 7 days"},
|
||||||
|
{ range: "last30d", rangeLabel: "Last 30 days"}
|
||||||
|
];
|
||||||
|
|
||||||
let filters = {
|
let filters = {
|
||||||
projectMatch: filterPresets.projectMatch || "contains",
|
projectMatch: filterPresets.projectMatch || "contains",
|
||||||
userMatch: filterPresets.userMatch || "contains",
|
userMatch: filterPresets.userMatch || "contains",
|
||||||
@ -56,7 +64,7 @@
|
|||||||
filterPresets.states || filterPresets.state
|
filterPresets.states || filterPresets.state
|
||||||
? [filterPresets.state].flat()
|
? [filterPresets.state].flat()
|
||||||
: allJobStates,
|
: allJobStates,
|
||||||
startTime: filterPresets.startTime || { from: null, to: null },
|
startTime: filterPresets.startTime || { from: null, to: null, range: ""},
|
||||||
tags: filterPresets.tags || [],
|
tags: filterPresets.tags || [],
|
||||||
duration: filterPresets.duration || {
|
duration: filterPresets.duration || {
|
||||||
lessThan: null,
|
lessThan: null,
|
||||||
@ -268,16 +276,17 @@
|
|||||||
{#if startTimeQuickSelect}
|
{#if startTimeQuickSelect}
|
||||||
<DropdownItem divider />
|
<DropdownItem divider />
|
||||||
<DropdownItem disabled>Start Time Quick Selection</DropdownItem>
|
<DropdownItem disabled>Start Time Quick Selection</DropdownItem>
|
||||||
{#each [{ text: "Last 6hrs", range: "last6h" }, { text: "Last 24hrs", range: "last24h" }, { text: "Last 7 days", range: "last7d" }, { text: "Last 30 days", range: "last30d" }] as { text, range }}
|
{#each startTimeSelectOptions.filter((stso) => stso.range !== "") as { rangeLabel, range }}
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
filters.startTime.from = null
|
||||||
|
filters.startTime.to = null
|
||||||
filters.startTime.range = range;
|
filters.startTime.range = range;
|
||||||
filters.startTime.text = text;
|
|
||||||
updateFilters();
|
updateFilters();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="calendar-range" />
|
<Icon name="calendar-range" />
|
||||||
{text}
|
{rangeLabel}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
@ -316,7 +325,7 @@
|
|||||||
|
|
||||||
{#if filters.startTime.range}
|
{#if filters.startTime.range}
|
||||||
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
||||||
{filters?.startTime?.text ? filters.startTime.text : filters.startTime.range }
|
{startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel }
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -414,11 +423,8 @@
|
|||||||
bind:from={filters.startTime.from}
|
bind:from={filters.startTime.from}
|
||||||
bind:to={filters.startTime.to}
|
bind:to={filters.startTime.to}
|
||||||
bind:range={filters.startTime.range}
|
bind:range={filters.startTime.range}
|
||||||
on:set-filter={() => {
|
{startTimeSelectOptions}
|
||||||
delete filters.startTime["text"];
|
on:set-filter={() => updateFilters()}
|
||||||
delete filters.startTime["range"];
|
|
||||||
updateFilters();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Duration
|
<Duration
|
||||||
|
@ -43,6 +43,10 @@
|
|||||||
<ModalBody>
|
<ModalBody>
|
||||||
{#if $initialized}
|
{#if $initialized}
|
||||||
<h4>Cluster</h4>
|
<h4>Cluster</h4>
|
||||||
|
{#if disableClusterSelection}
|
||||||
|
<Button color="info" class="w-100 mb-2" disabled><b>Info: Cluster Selection Disabled in This View</b></Button>
|
||||||
|
<Button outline color="primary" class="w-100 mb-2" disabled><b>Selected Cluster: {cluster}</b></Button>
|
||||||
|
{:else}
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
<ListGroupItem
|
<ListGroupItem
|
||||||
disabled={disableClusterSelection}
|
disabled={disableClusterSelection}
|
||||||
@ -64,6 +68,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
{#if $initialized && pendingCluster != null}
|
{#if $initialized && pendingCluster != null}
|
||||||
<br />
|
<br />
|
||||||
<h4>Partiton</h4>
|
<h4>Partiton</h4>
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import { parse, format, sub } from "date-fns";
|
import { parse, format, sub } from "date-fns";
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
Col,
|
|
||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
@ -34,8 +33,7 @@
|
|||||||
export let from = null;
|
export let from = null;
|
||||||
export let to = null;
|
export let to = null;
|
||||||
export let range = "";
|
export let range = "";
|
||||||
|
export let startTimeSelectOptions;
|
||||||
let pendingFrom, pendingTo;
|
|
||||||
|
|
||||||
const now = new Date(Date.now());
|
const now = new Date(Date.now());
|
||||||
const ago = sub(now, { months: 1 });
|
const ago = sub(now, { months: 1 });
|
||||||
@ -48,12 +46,24 @@
|
|||||||
time: format(now, "HH:mm"),
|
time: format(now, "HH:mm"),
|
||||||
};
|
};
|
||||||
|
|
||||||
function reset() {
|
$: pendingFrom = (from == null) ? defaultFrom : fromRFC3339(from)
|
||||||
pendingFrom = from == null ? defaultFrom : fromRFC3339(from);
|
$: pendingTo = (to == null) ? defaultTo : fromRFC3339(to)
|
||||||
pendingTo = to == null ? defaultTo : fromRFC3339(to);
|
$: pendingRange = range
|
||||||
}
|
|
||||||
|
|
||||||
reset();
|
$: isModified =
|
||||||
|
(from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, "59")) &&
|
||||||
|
(range != pendingRange) &&
|
||||||
|
!(
|
||||||
|
from == null &&
|
||||||
|
pendingFrom.date == "0000-00-00" &&
|
||||||
|
pendingFrom.time == "00:00"
|
||||||
|
) &&
|
||||||
|
!(
|
||||||
|
to == null &&
|
||||||
|
pendingTo.date == "0000-00-00" &&
|
||||||
|
pendingTo.time == "00:00"
|
||||||
|
) &&
|
||||||
|
!( range == "" && pendingRange == "");
|
||||||
|
|
||||||
function toRFC3339({ date, time }, secs = "00") {
|
function toRFC3339({ date, time }, secs = "00") {
|
||||||
const parsedDate = parse(
|
const parsedDate = parse(
|
||||||
@ -71,19 +81,6 @@
|
|||||||
time: format(parsedDate, "HH:mm"),
|
time: format(parsedDate, "HH:mm"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
$: isModified =
|
|
||||||
(from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, "59")) &&
|
|
||||||
!(
|
|
||||||
from == null &&
|
|
||||||
pendingFrom.date == "0000-00-00" &&
|
|
||||||
pendingFrom.time == "00:00"
|
|
||||||
) &&
|
|
||||||
!(
|
|
||||||
to == null &&
|
|
||||||
pendingTo.date == "0000-00-00" &&
|
|
||||||
pendingTo.time == "00:00"
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
@ -92,31 +89,59 @@
|
|||||||
{#if range !== ""}
|
{#if range !== ""}
|
||||||
<h4>Current Range</h4>
|
<h4>Current Range</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<FormGroup class="col">
|
||||||
<Input type="text" value={range} disabled/>
|
<Input type ="select" bind:value={pendingRange} >
|
||||||
</Col>
|
{#each startTimeSelectOptions as { rangeLabel, range }}
|
||||||
|
<option label={rangeLabel} value={range}/>
|
||||||
|
{/each}
|
||||||
|
</Input>
|
||||||
|
</FormGroup>
|
||||||
</Row>
|
</Row>
|
||||||
{/if}
|
{/if}
|
||||||
<h4>From</h4>
|
<h4>From</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="date" bind:value={pendingFrom.date} />
|
<Input type="date" bind:value={pendingFrom.date} disabled={pendingRange !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="time" bind:value={pendingFrom.time} />
|
<Input type="time" bind:value={pendingFrom.time} disabled={pendingRange !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Row>
|
</Row>
|
||||||
<h4>To</h4>
|
<h4>To</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="date" bind:value={pendingTo.date} />
|
<Input type="date" bind:value={pendingTo.date} disabled={pendingRange !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="time" bind:value={pendingTo.time} />
|
<Input type="time" bind:value={pendingTo.time} disabled={pendingRange !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Row>
|
</Row>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
{#if pendingRange !== ""}
|
||||||
|
<Button
|
||||||
|
color="warning"
|
||||||
|
disabled={pendingRange === ""}
|
||||||
|
on:click={() => {
|
||||||
|
pendingRange = ""
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset Range
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
disabled={pendingRange === ""}
|
||||||
|
on:click={() => {
|
||||||
|
isOpen = false;
|
||||||
|
from = null;
|
||||||
|
to = null;
|
||||||
|
range = pendingRange;
|
||||||
|
dispatch("set-filter", { from, to, range });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close & Apply Range
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={pendingFrom.date == "0000-00-00" ||
|
disabled={pendingFrom.date == "0000-00-00" ||
|
||||||
@ -125,19 +150,21 @@
|
|||||||
isOpen = false;
|
isOpen = false;
|
||||||
from = toRFC3339(pendingFrom);
|
from = toRFC3339(pendingFrom);
|
||||||
to = toRFC3339(pendingTo, "59");
|
to = toRFC3339(pendingTo, "59");
|
||||||
dispatch("set-filter", { from, to });
|
range = "";
|
||||||
|
dispatch("set-filter", { from, to, range });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close & Apply
|
Close & Apply Dates
|
||||||
</Button>
|
</Button>
|
||||||
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
from = null;
|
from = null;
|
||||||
to = null;
|
to = null;
|
||||||
reset();
|
range = "";
|
||||||
dispatch("set-filter", { from, to });
|
dispatch("set-filter", { from, to, range });
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `polarMetrics [Object]?`: Metric names and scaled peak values for rendering polar plot [Default: [] ]
|
- `polarMetrics [Object]?`: Metric names and scaled peak values for rendering polar plot [Default: [] ]
|
||||||
- `jobMetrics [GraphQL.JobMetricWithName]?`: Metric data [Default: null]
|
- `polarData [GraphQL.JobMetricStatWithName]?`: Metric data [Default: null]
|
||||||
- `height Number?`: Plot height [Default: 365]
|
- `height Number?`: Plot height [Default: 365]
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -31,7 +31,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
export let polarMetrics = [];
|
export let polarMetrics = [];
|
||||||
export let jobMetrics = null;
|
export let polarData = [];
|
||||||
export let height = 350;
|
export let height = 350;
|
||||||
|
|
||||||
const labels = polarMetrics
|
const labels = polarMetrics
|
||||||
@ -40,53 +40,26 @@
|
|||||||
.sort(function (a, b) {return ((a > b) ? 1 : ((b > a) ? -1 : 0))});
|
.sort(function (a, b) {return ((a > b) ? 1 : ((b > a) ? -1 : 0))});
|
||||||
|
|
||||||
function loadData(type) {
|
function loadData(type) {
|
||||||
if (!labels) {
|
if (labels && (type == 'avg' || type == 'min' ||type == 'max')) {
|
||||||
|
return getValues(type)
|
||||||
|
} else if (!labels) {
|
||||||
console.warn("Empty 'polarMetrics' array prop! Cannot render Polar representation.")
|
console.warn("Empty 'polarMetrics' array prop! Cannot render Polar representation.")
|
||||||
return []
|
|
||||||
} else {
|
} else {
|
||||||
if (type === 'avg') {
|
console.warn('Unknown Type For Polar Data (must be one of [min, max, avg])')
|
||||||
return getValues(getAvg)
|
|
||||||
} else if (type === 'max') {
|
|
||||||
return getValues(getMax)
|
|
||||||
} else if (type === 'min') {
|
|
||||||
return getValues(getMin)
|
|
||||||
}
|
}
|
||||||
console.log('Unknown Type For Polar Data (must be one of [min, max, avg])')
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
// Helper
|
||||||
|
|
||||||
const getValues = (getStat) => labels.map(name => {
|
const getValues = (type) => labels.map(name => {
|
||||||
// Peak is adapted and scaled for job shared state
|
// Peak is adapted and scaled for job shared state
|
||||||
const peak = polarMetrics.find(m => m.name == name).peak
|
const peak = polarMetrics.find(m => m?.name == name)?.peak
|
||||||
const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
|
const metric = polarData.find(m => m?.name == name)?.stats
|
||||||
const value = getStat(metric.metric) / peak
|
const value = (peak && metric) ? (metric[type] / peak) : 0
|
||||||
return value <= 1. ? value : 1.
|
return value <= 1. ? value : 1.
|
||||||
})
|
})
|
||||||
|
|
||||||
function getMax(metric) {
|
|
||||||
let max = metric.series[0].statistics.max;
|
|
||||||
for (let series of metric.series)
|
|
||||||
max = Math.max(max, series.statistics.max)
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMin(metric) {
|
|
||||||
let min = metric.series[0].statistics.min;
|
|
||||||
for (let series of metric.series)
|
|
||||||
min = Math.min(min, series.statistics.min)
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAvg(metric) {
|
|
||||||
let avg = 0;
|
|
||||||
for (let series of metric.series)
|
|
||||||
avg += series.statistics.avg
|
|
||||||
return avg / metric.series.length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chart JS Objects
|
// Chart JS Objects
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -179,7 +179,7 @@
|
|||||||
function render(plotData) {
|
function render(plotData) {
|
||||||
if (plotData) {
|
if (plotData) {
|
||||||
const opts = {
|
const opts = {
|
||||||
title: "",
|
title: "CPU Roofline Diagram",
|
||||||
mode: 2,
|
mode: 2,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `cluster String`: Currently selected cluster
|
- `cluster String`: Currently selected cluster
|
||||||
- `metricsInHistograms [String]`: The currently selected metrics to display as histogram
|
- `selectedHistograms [String]`: The currently selected metrics to display as histogram
|
||||||
- ìsOpen Bool`: Is selection opened
|
- ìsOpen Bool`: Is selection opened
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -21,21 +21,26 @@
|
|||||||
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||||
|
|
||||||
export let cluster;
|
export let cluster;
|
||||||
export let metricsInHistograms;
|
export let selectedHistograms;
|
||||||
export let isOpen;
|
export let isOpen;
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
const initialized = getContext("initialized");
|
const initialized = getContext("initialized");
|
||||||
|
|
||||||
let availableMetrics = []
|
function loadHistoMetrics(isInitialized, thisCluster) {
|
||||||
|
if (!isInitialized) return [];
|
||||||
|
|
||||||
function loadHistoMetrics(isInitialized) {
|
if (!thisCluster) {
|
||||||
if (!isInitialized) return;
|
return getContext("globalMetrics")
|
||||||
const rawAvailableMetrics = getContext("globalMetrics").filter((gm) => gm?.footprint).map((fgm) => { return fgm.name })
|
.filter((gm) => gm?.footprint)
|
||||||
availableMetrics = [...rawAvailableMetrics]
|
.map((fgm) => { return fgm.name })
|
||||||
|
} else {
|
||||||
|
return getContext("globalMetrics")
|
||||||
|
.filter((gm) => gm?.availability.find((av) => av.cluster == thisCluster))
|
||||||
|
.filter((agm) => agm?.footprint)
|
||||||
|
.map((afgm) => { return afgm.name })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pendingMetrics = [...metricsInHistograms]; // Copy
|
|
||||||
|
|
||||||
const updateConfigurationMutation = ({ name, value }) => {
|
const updateConfigurationMutation = ({ name, value }) => {
|
||||||
return mutationStore({
|
return mutationStore({
|
||||||
@ -61,17 +66,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeAndApply() {
|
function closeAndApply() {
|
||||||
metricsInHistograms = [...pendingMetrics]; // Set for parent
|
|
||||||
isOpen = !isOpen;
|
isOpen = !isOpen;
|
||||||
updateConfiguration({
|
updateConfiguration({
|
||||||
name: cluster
|
name: cluster
|
||||||
? `user_view_histogramMetrics:${cluster}`
|
? `user_view_histogramMetrics:${cluster}`
|
||||||
: "user_view_histogramMetrics",
|
: "user_view_histogramMetrics",
|
||||||
value: metricsInHistograms,
|
value: selectedHistograms,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$: loadHistoMetrics($initialized);
|
$: availableMetrics = loadHistoMetrics($initialized, cluster);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -81,7 +85,7 @@
|
|||||||
<ListGroup>
|
<ListGroup>
|
||||||
{#each availableMetrics as metric (metric)}
|
{#each availableMetrics as metric (metric)}
|
||||||
<ListGroupItem>
|
<ListGroupItem>
|
||||||
<input type="checkbox" bind:group={pendingMetrics} value={metric} />
|
<input type="checkbox" bind:group={selectedHistograms} value={metric} />
|
||||||
{metric}
|
{metric}
|
||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
export let configName;
|
export let configName;
|
||||||
export let allMetrics = null;
|
export let allMetrics = null;
|
||||||
export let cluster = null;
|
export let cluster = null;
|
||||||
|
export let subCluster = null;
|
||||||
export let showFootprint = false;
|
export let showFootprint = false;
|
||||||
export let footprintSelect = false;
|
export let footprintSelect = false;
|
||||||
|
|
||||||
@ -46,12 +47,16 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (allMetrics != null) {
|
if (allMetrics != null) {
|
||||||
if (cluster == null) {
|
if (!cluster) {
|
||||||
for (let metric of globalMetrics) allMetrics.add(metric.name);
|
for (let metric of globalMetrics) allMetrics.add(metric.name);
|
||||||
} else {
|
} else {
|
||||||
allMetrics.clear();
|
allMetrics.clear();
|
||||||
for (let gm of globalMetrics) {
|
for (let gm of globalMetrics) {
|
||||||
|
if (!subCluster) {
|
||||||
if (gm.availability.find((av) => av.cluster === cluster)) allMetrics.add(gm.name);
|
if (gm.availability.find((av) => av.cluster === cluster)) allMetrics.add(gm.name);
|
||||||
|
} else {
|
||||||
|
if (gm.availability.find((av) => av.cluster === cluster && av.subClusters.includes(subCluster))) allMetrics.add(gm.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newMetricsOrder = [...allMetrics].filter((m) => !metrics.includes(m));
|
newMetricsOrder = [...allMetrics].filter((m) => !metrics.includes(m));
|
||||||
@ -62,7 +67,7 @@
|
|||||||
|
|
||||||
function printAvailability(metric, cluster) {
|
function printAvailability(metric, cluster) {
|
||||||
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
|
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
|
||||||
if (cluster == null) {
|
if (!cluster) {
|
||||||
return avail.map((av) => av.cluster).join(',')
|
return avail.map((av) => av.cluster).join(',')
|
||||||
} else {
|
} else {
|
||||||
return avail.find((av) => av.cluster === cluster).subClusters.join(',')
|
return avail.find((av) => av.cluster === cluster).subClusters.join(',')
|
||||||
@ -107,10 +112,17 @@
|
|||||||
metrics = newMetricsOrder.filter((m) => unorderedMetrics.includes(m));
|
metrics = newMetricsOrder.filter((m) => unorderedMetrics.includes(m));
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
|
|
||||||
showFootprint = !!pendingShowFootprint;
|
let configKey;
|
||||||
|
if (cluster && subCluster) {
|
||||||
|
configKey = `${configName}:${cluster}:${subCluster}`;
|
||||||
|
} else if (cluster && !subCluster) {
|
||||||
|
configKey = `${configName}:${cluster}`;
|
||||||
|
} else {
|
||||||
|
configKey = `${configName}`;
|
||||||
|
}
|
||||||
|
|
||||||
updateConfigurationMutation({
|
updateConfigurationMutation({
|
||||||
name: cluster == null ? configName : `${configName}:${cluster}`,
|
name: configKey,
|
||||||
value: JSON.stringify(metrics),
|
value: JSON.stringify(metrics),
|
||||||
}).subscribe((res) => {
|
}).subscribe((res) => {
|
||||||
if (res.fetching === false && res.error) {
|
if (res.fetching === false && res.error) {
|
||||||
@ -118,9 +130,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (footprintSelect) {
|
||||||
|
showFootprint = !!pendingShowFootprint;
|
||||||
updateConfigurationMutation({
|
updateConfigurationMutation({
|
||||||
name:
|
name:
|
||||||
cluster == null
|
!cluster
|
||||||
? "plot_list_showFootprint"
|
? "plot_list_showFootprint"
|
||||||
: `plot_list_showFootprint:${cluster}`,
|
: `plot_list_showFootprint:${cluster}`,
|
||||||
value: JSON.stringify(showFootprint),
|
value: JSON.stringify(showFootprint),
|
||||||
@ -129,6 +143,7 @@
|
|||||||
throw res.error;
|
throw res.error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
dispatch('update-metrics', metrics);
|
dispatch('update-metrics', metrics);
|
||||||
}
|
}
|
||||||
|
79
web/frontend/src/job/JobRoofline.svelte
Normal file
79
web/frontend/src/job/JobRoofline.svelte
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<!--
|
||||||
|
@component Job View Roofline component; Queries data for and renders roofline plot.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
- `job Object`: The GQL job object
|
||||||
|
- `clusters Array`: The GQL clusters array
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
queryStore,
|
||||||
|
gql,
|
||||||
|
getContextClient
|
||||||
|
} from "@urql/svelte";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Spinner
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
import {
|
||||||
|
transformDataForRoofline,
|
||||||
|
} from "../generic/utils.js";
|
||||||
|
import Roofline from "../generic/plots/Roofline.svelte";
|
||||||
|
|
||||||
|
export let job;
|
||||||
|
export let clusters;
|
||||||
|
|
||||||
|
let roofWidth;
|
||||||
|
|
||||||
|
const client = getContextClient();
|
||||||
|
const roofQuery = gql`
|
||||||
|
query ($dbid: ID!, $selectedMetrics: [String!]!, $selectedScopes: [MetricScope!]!, $selectedResolution: Int) {
|
||||||
|
jobMetrics(id: $dbid, metrics: $selectedMetrics, scopes: $selectedScopes, resolution: $selectedResolution) {
|
||||||
|
name
|
||||||
|
scope
|
||||||
|
metric {
|
||||||
|
series {
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Roofline: Always load roofMetrics with configured timestep (Resolution: 0)
|
||||||
|
$: roofMetrics = queryStore({
|
||||||
|
client: client,
|
||||||
|
query: roofQuery,
|
||||||
|
variables: { dbid: job.id, selectedMetrics: ["flops_any", "mem_bw"], selectedScopes: ["node"], selectedResolution: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $roofMetrics.error}
|
||||||
|
<Card body color="danger">{$roofMetrics.error.message}</Card>
|
||||||
|
{:else if $roofMetrics?.data}
|
||||||
|
<Card style="height: 400px;">
|
||||||
|
<div bind:clientWidth={roofWidth}>
|
||||||
|
<Roofline
|
||||||
|
width={roofWidth}
|
||||||
|
subCluster={clusters
|
||||||
|
.find((c) => c.name == job.cluster)
|
||||||
|
.subClusters.find((sc) => sc.name == job.subCluster)}
|
||||||
|
data={transformDataForRoofline(
|
||||||
|
$roofMetrics.data?.jobMetrics?.find(
|
||||||
|
(m) => m.name == "flops_any" && m.scope == "node",
|
||||||
|
)?.metric,
|
||||||
|
$roofMetrics.data?.jobMetrics?.find(
|
||||||
|
(m) => m.name == "mem_bw" && m.scope == "node",
|
||||||
|
)?.metric,
|
||||||
|
)}
|
||||||
|
allowSizeChange
|
||||||
|
renderTime
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
{:else}
|
||||||
|
<Spinner secondary />
|
||||||
|
{/if}
|
||||||
|
|
@ -1,9 +1,8 @@
|
|||||||
<!--
|
<!--
|
||||||
@component Job Summary component; Displays job.footprint data as bars in relation to thresholds, as polar plot, and summariziong comment
|
@component Job Summary component; Displays aggregated job footprint statistics and performance indicators
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `job Object`: The GQL job object
|
- `job Object`: The GQL job object
|
||||||
- `displayTitle Bool?`: If to display cardHeader with title [Default: true]
|
|
||||||
- `width String?`: Width of the card [Default: 'auto']
|
- `width String?`: Width of the card [Default: 'auto']
|
||||||
- `height String?`: Height of the card [Default: '310px']
|
- `height String?`: Height of the card [Default: '310px']
|
||||||
-->
|
-->
|
||||||
@ -12,317 +11,31 @@
|
|||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
|
||||||
Progress,
|
|
||||||
Icon,
|
|
||||||
Tooltip,
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
TabContent,
|
TabContent,
|
||||||
TabPane
|
TabPane
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import Polar from "../generic/plots/Polar.svelte";
|
import JobFootprintBars from "./jobsummary/JobFootprintBars.svelte";
|
||||||
import { findJobFootprintThresholds } from "../generic/utils.js";
|
import JobFootprintPolar from "./jobsummary/JobFootprintPolar.svelte";
|
||||||
|
|
||||||
|
|
||||||
export let job;
|
export let job;
|
||||||
export let jobMetrics;
|
|
||||||
export let width = "auto";
|
export let width = "auto";
|
||||||
export let height = "400px";
|
export let height = "400px";
|
||||||
|
|
||||||
const ccconfig = getContext("cc-config")
|
const showFootprintTab = !!getContext("cc-config")[`job_view_showFootprint`];
|
||||||
const globalMetrics = getContext("globalMetrics")
|
|
||||||
const showFootprintTab = !!ccconfig[`job_view_showFootprint`];
|
|
||||||
|
|
||||||
// Metrics Configured To Be Footprints For (sub)Cluster
|
|
||||||
const clusterFootprintMetrics = getContext("clusters")
|
|
||||||
.find((c) => c.name == job.cluster)?.subClusters
|
|
||||||
.find((sc) => sc.name == job.subCluster)?.footprint || []
|
|
||||||
|
|
||||||
// Data For Polarplot Will Be Calculated Based On JobMetrics And Thresholds
|
|
||||||
const polarMetrics = globalMetrics.reduce((pms, gm) => {
|
|
||||||
if (clusterFootprintMetrics.includes(gm.name)) {
|
|
||||||
const fmt = findJobFootprintThresholds(job, gm.footprint, getContext("getMetricConfig")(job.cluster, job.subCluster, gm.name));
|
|
||||||
pms.push({ name: gm.name, peak: fmt ? fmt.peak : null });
|
|
||||||
}
|
|
||||||
return pms;
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Prepare Job Footprint Data Based On Values Saved In Database
|
|
||||||
const jobFootprintData = !showFootprintTab ? null : job?.footprint?.map((jf) => {
|
|
||||||
const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name);
|
|
||||||
if (fmc) {
|
|
||||||
// Unit
|
|
||||||
const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "")
|
|
||||||
|
|
||||||
// Threshold / -Differences
|
|
||||||
const fmt = findJobFootprintThresholds(job, jf.stat, fmc);
|
|
||||||
|
|
||||||
// Define basic data -> Value: Use as Provided
|
|
||||||
const fmBase = {
|
|
||||||
name: jf.name,
|
|
||||||
stat: jf.stat,
|
|
||||||
value: jf.value,
|
|
||||||
unit: unit,
|
|
||||||
peak: fmt.peak,
|
|
||||||
dir: fmc.lowerIsBetter
|
|
||||||
};
|
|
||||||
|
|
||||||
if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "alert")) {
|
|
||||||
return {
|
|
||||||
...fmBase,
|
|
||||||
color: "danger",
|
|
||||||
message: `Footprint value way ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
|
||||||
impact: 3
|
|
||||||
};
|
|
||||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "caution")) {
|
|
||||||
return {
|
|
||||||
...fmBase,
|
|
||||||
color: "warning",
|
|
||||||
message: `Footprint value ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
|
||||||
impact: 2,
|
|
||||||
};
|
|
||||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "normal")) {
|
|
||||||
return {
|
|
||||||
...fmBase,
|
|
||||||
color: "success",
|
|
||||||
message: "Footprint value within expected thresholds.",
|
|
||||||
impact: 1,
|
|
||||||
};
|
|
||||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) {
|
|
||||||
return {
|
|
||||||
...fmBase,
|
|
||||||
color: "info",
|
|
||||||
message:
|
|
||||||
"Footprint value above expected normal threshold: Check for artifacts recommended.",
|
|
||||||
impact: 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
...fmBase,
|
|
||||||
color: "secondary",
|
|
||||||
message:
|
|
||||||
"Footprint value above expected peak threshold: Check for artifacts!",
|
|
||||||
impact: -1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else { // No matching metric config: display as single value
|
|
||||||
return {
|
|
||||||
name: jf.name,
|
|
||||||
stat: jf.stat,
|
|
||||||
value: jf.value,
|
|
||||||
message:
|
|
||||||
`No config for metric ${jf.name} found.`,
|
|
||||||
impact: 4,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}).sort(function (a, b) { // Sort by impact value primarily, within impact sort name alphabetically
|
|
||||||
return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
|
|
||||||
});;
|
|
||||||
|
|
||||||
function evalFootprint(value, thresholds, lowerIsBetter, level) {
|
|
||||||
// Handle Metrics in which less value is better
|
|
||||||
switch (level) {
|
|
||||||
case "peak":
|
|
||||||
if (lowerIsBetter)
|
|
||||||
return false; // metric over peak -> return false to trigger impact -1
|
|
||||||
else return value <= thresholds.peak && value > thresholds.normal;
|
|
||||||
case "alert":
|
|
||||||
if (lowerIsBetter)
|
|
||||||
return value <= thresholds.peak && value >= thresholds.alert;
|
|
||||||
else return value <= thresholds.alert && value >= 0;
|
|
||||||
case "caution":
|
|
||||||
if (lowerIsBetter)
|
|
||||||
return value < thresholds.alert && value >= thresholds.caution;
|
|
||||||
else return value <= thresholds.caution && value > thresholds.alert;
|
|
||||||
case "normal":
|
|
||||||
if (lowerIsBetter)
|
|
||||||
return value < thresholds.caution && value >= 0;
|
|
||||||
else return value <= thresholds.normal && value > thresholds.caution;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
function writeSummary(fpd) {
|
|
||||||
// Hardcoded! Needs to be retrieved from globalMetrics
|
|
||||||
const performanceMetrics = ['flops_any', 'mem_bw'];
|
|
||||||
const utilizationMetrics = ['cpu_load', 'acc_utilization'];
|
|
||||||
const energyMetrics = ['cpu_power'];
|
|
||||||
|
|
||||||
let performanceScore = 0;
|
|
||||||
let utilizationScore = 0;
|
|
||||||
let energyScore = 0;
|
|
||||||
|
|
||||||
let performanceMetricsCounted = 0;
|
|
||||||
let utilizationMetricsCounted = 0;
|
|
||||||
let energyMetricsCounted = 0;
|
|
||||||
|
|
||||||
fpd.forEach(metric => {
|
|
||||||
console.log('Metric, Impact', metric.name, metric.impact)
|
|
||||||
if (performanceMetrics.includes(metric.name)) {
|
|
||||||
performanceScore += metric.impact
|
|
||||||
performanceMetricsCounted += 1
|
|
||||||
} else if (utilizationMetrics.includes(metric.name)) {
|
|
||||||
utilizationScore += metric.impact
|
|
||||||
utilizationMetricsCounted += 1
|
|
||||||
} else if (energyMetrics.includes(metric.name)) {
|
|
||||||
energyScore += metric.impact
|
|
||||||
energyMetricsCounted += 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
performanceScore = (performanceMetricsCounted == 0) ? performanceScore : (performanceScore / performanceMetricsCounted);
|
|
||||||
utilizationScore = (utilizationMetricsCounted == 0) ? utilizationScore : (utilizationScore / utilizationMetricsCounted);
|
|
||||||
energyScore = (energyMetricsCounted == 0) ? energyScore : (energyScore / energyMetricsCounted);
|
|
||||||
|
|
||||||
let res = [];
|
|
||||||
|
|
||||||
console.log('Perf', performanceScore, performanceMetricsCounted)
|
|
||||||
console.log('Util', utilizationScore, utilizationMetricsCounted)
|
|
||||||
console.log('Energy', energyScore, energyMetricsCounted)
|
|
||||||
|
|
||||||
if (performanceScore == 1) {
|
|
||||||
res.push('<b>Performance:</b> Your job performs well.')
|
|
||||||
} else if (performanceScore != 0) {
|
|
||||||
res.push('<b>Performance:</b> Your job performs suboptimal.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utilizationScore == 1) {
|
|
||||||
res.push('<b>Utilization:</b> Your job utilizes resources well.')
|
|
||||||
} else if (utilizationScore != 0) {
|
|
||||||
res.push('<b>Utilization:</b> Your job utilizes resources suboptimal.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (energyScore == 1) {
|
|
||||||
res.push('<b>Energy:</b> Your job has good energy values.')
|
|
||||||
} else if (energyScore != 0) {
|
|
||||||
res.push('<b>Energy:</b> Your job consumes more energy than necessary.')
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
$: summaryMessages = writeSummary(jobFootprintData)
|
|
||||||
*/
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card class="overflow-auto" style="width: {width}; height: {height}">
|
<Card class="overflow-auto" style="width: {width}; height: {height}">
|
||||||
<TabContent> <!-- on:tab={(e) => (status = e.detail)} -->
|
<TabContent>
|
||||||
{#if showFootprintTab}
|
{#if showFootprintTab}
|
||||||
<TabPane tabId="foot" tab="Footprint" active>
|
<TabPane tabId="foot" tab="Footprint" active>
|
||||||
<CardBody>
|
<!-- Bars CardBody Here-->
|
||||||
{#each jobFootprintData as fpd, index}
|
<JobFootprintBars {job} />
|
||||||
{#if fpd.impact !== 4}
|
|
||||||
<div class="mb-1 d-flex justify-content-between">
|
|
||||||
<div> <b>{fpd.name} ({fpd.stat})</b></div>
|
|
||||||
<div
|
|
||||||
class="cursor-help d-inline-flex"
|
|
||||||
id={`footprint-${job.jobId}-${index}`}
|
|
||||||
>
|
|
||||||
<div class="mx-1">
|
|
||||||
{#if fpd.impact === 3}
|
|
||||||
<Icon name="exclamation-triangle-fill" class="text-danger" />
|
|
||||||
{:else if fpd.impact === 2}
|
|
||||||
<Icon name="exclamation-triangle" class="text-warning" />
|
|
||||||
{:else if fpd.impact === 0}
|
|
||||||
<Icon name="info-circle" class="text-info" />
|
|
||||||
{:else if fpd.impact === -1}
|
|
||||||
<Icon name="info-circle-fill" class="text-danger" />
|
|
||||||
{/if}
|
|
||||||
{#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-smile" class="text-info" />
|
|
||||||
{:else if fpd.impact === -1}
|
|
||||||
<Icon name="emoji-dizzy" class="text-danger" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{fpd.value} / {fpd.peak}
|
|
||||||
{fpd.unit}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
target={`footprint-${job.jobId}-${index}`}
|
|
||||||
placement="right"
|
|
||||||
>{fpd.message}</Tooltip
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<Row cols={12} class="{(jobFootprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
|
|
||||||
{#if fpd.dir}
|
|
||||||
<Col xs="1">
|
|
||||||
<Icon name="caret-left-fill" />
|
|
||||||
</Col>
|
|
||||||
{/if}
|
|
||||||
<Col xs="11" class="align-content-center">
|
|
||||||
<Progress value={fpd.value} max={fpd.peak} color={fpd.color} />
|
|
||||||
</Col>
|
|
||||||
{#if !fpd.dir}
|
|
||||||
<Col xs="1">
|
|
||||||
<Icon name="caret-right-fill" />
|
|
||||||
</Col>
|
|
||||||
{/if}
|
|
||||||
</Row>
|
|
||||||
{:else}
|
|
||||||
<div class="mb-1 d-flex justify-content-between">
|
|
||||||
<div>
|
|
||||||
<b>{fpd.name} ({fpd.stat})</b>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="cursor-help d-inline-flex"
|
|
||||||
id={`footprint-${job.jobId}-${index}`}
|
|
||||||
>
|
|
||||||
<div class="mx-1">
|
|
||||||
<Icon name="info-circle"/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{fpd.value}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
target={`footprint-${job.jobId}-${index}`}
|
|
||||||
placement="right"
|
|
||||||
>{fpd.message}</Tooltip
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</CardBody>
|
|
||||||
</TabPane>
|
</TabPane>
|
||||||
{/if}
|
{/if}
|
||||||
<TabPane tabId="polar" tab="Polar" active={!showFootprintTab}>
|
<TabPane tabId="polar" tab="Polar" active={!showFootprintTab}>
|
||||||
<CardBody>
|
<!-- Polar Plot CardBody Here -->
|
||||||
<Polar
|
<JobFootprintPolar {job} />
|
||||||
{polarMetrics}
|
|
||||||
{jobMetrics}
|
|
||||||
/>
|
|
||||||
</CardBody>
|
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<!--
|
|
||||||
<TabPane tabId="summary" tab="Summary">
|
|
||||||
<CardBody>
|
|
||||||
<p>Based on footprint data, this job performs as follows:</p>
|
|
||||||
<hr/>
|
|
||||||
<ul>
|
|
||||||
{#each summaryMessages as sm}
|
|
||||||
<li>
|
|
||||||
{@html sm}
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</CardBody>
|
|
||||||
</TabPane>
|
|
||||||
-->
|
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<style>
|
|
||||||
.cursor-help {
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupText,
|
InputGroupText,
|
||||||
Icon,
|
Icon,
|
||||||
|
Row,
|
||||||
|
Col
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import { maxScope } from "../generic/utils.js";
|
import { maxScope } from "../generic/utils.js";
|
||||||
import StatsTableEntry from "./StatsTableEntry.svelte";
|
import StatsTableEntry from "./StatsTableEntry.svelte";
|
||||||
@ -26,7 +28,7 @@
|
|||||||
export let job;
|
export let job;
|
||||||
export let jobMetrics;
|
export let jobMetrics;
|
||||||
|
|
||||||
const allMetrics = [...new Set(jobMetrics.map((m) => m.name))].sort()
|
const sortedJobMetrics = [...new Set(jobMetrics.map((m) => m.name))].sort()
|
||||||
const scopesForMetric = (metric) =>
|
const scopesForMetric = (metric) =>
|
||||||
jobMetrics.filter((jm) => jm.name == metric).map((jm) => jm.scope);
|
jobMetrics.filter((jm) => jm.name == metric).map((jm) => jm.scope);
|
||||||
|
|
||||||
@ -34,12 +36,13 @@
|
|||||||
selectedScopes = {},
|
selectedScopes = {},
|
||||||
sorting = {},
|
sorting = {},
|
||||||
isMetricSelectionOpen = false,
|
isMetricSelectionOpen = false,
|
||||||
selectedMetrics =
|
availableMetrics = new Set(),
|
||||||
getContext("cc-config")[
|
selectedMetrics = (
|
||||||
`job_view_nodestats_selectedMetrics:${job.cluster}`
|
getContext("cc-config")[`job_view_nodestats_selectedMetrics:${job.cluster}:${job.subCluster}`] ||
|
||||||
] || getContext("cc-config")["job_view_nodestats_selectedMetrics"];
|
getContext("cc-config")[`job_view_nodestats_selectedMetrics:${job.cluster}`]
|
||||||
|
) || getContext("cc-config")["job_view_nodestats_selectedMetrics"];
|
||||||
|
|
||||||
for (let metric of allMetrics) {
|
for (let metric of sortedJobMetrics) {
|
||||||
// Not Exclusive or Multi-Node: get maxScope directly (mostly: node)
|
// Not Exclusive or Multi-Node: get maxScope directly (mostly: node)
|
||||||
// -> Else: Load smallest available granularity as default as per availability
|
// -> Else: Load smallest available granularity as default as per availability
|
||||||
const availableScopes = scopesForMetric(metric);
|
const availableScopes = scopesForMetric(metric);
|
||||||
@ -96,15 +99,19 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col class="m-2">
|
||||||
|
<Button outline on:click={() => (isMetricSelectionOpen = true)} class="w-auto px-2" color="primary">
|
||||||
|
Select Metrics (Selected {selectedMetrics.length} of {availableMetrics.size} available)
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<hr class="mb-1 mt-1"/>
|
||||||
<Table class="mb-0">
|
<Table class="mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<!-- Header Row 1: Selectors -->
|
<!-- Header Row 1: Selectors -->
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th/>
|
||||||
<Button outline on:click={() => (isMetricSelectionOpen = true)} class="w-100 px-2" color="primary">
|
|
||||||
Select Metrics
|
|
||||||
</Button>
|
|
||||||
</th>
|
|
||||||
{#each selectedMetrics as metric}
|
{#each selectedMetrics as metric}
|
||||||
<!-- To Match Row-2 Header Field Count-->
|
<!-- To Match Row-2 Header Field Count-->
|
||||||
<th colspan={selectedScopes[metric] == "node" ? 3 : 4}>
|
<th colspan={selectedScopes[metric] == "node" ? 3 : 4}>
|
||||||
@ -163,8 +170,9 @@
|
|||||||
|
|
||||||
<MetricSelection
|
<MetricSelection
|
||||||
cluster={job.cluster}
|
cluster={job.cluster}
|
||||||
|
subCluster={job.subCluster}
|
||||||
configName="job_view_nodestats_selectedMetrics"
|
configName="job_view_nodestats_selectedMetrics"
|
||||||
allMetrics={new Set(allMetrics)}
|
bind:allMetrics={availableMetrics}
|
||||||
bind:metrics={selectedMetrics}
|
bind:metrics={selectedMetrics}
|
||||||
bind:isOpen={isMetricSelectionOpen}
|
bind:isOpen={isMetricSelectionOpen}
|
||||||
/>
|
/>
|
||||||
|
210
web/frontend/src/job/jobsummary/JobFootprintBars.svelte
Normal file
210
web/frontend/src/job/jobsummary/JobFootprintBars.svelte
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
<!--
|
||||||
|
@component Job Footprint Bar component; Displays job footprint db data as bars relative to thresholds. Displays quality indicators and tooltips.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
- `job Object`: The GQL job object
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte";
|
||||||
|
import {
|
||||||
|
CardBody,
|
||||||
|
Progress,
|
||||||
|
Icon,
|
||||||
|
Tooltip,
|
||||||
|
Row,
|
||||||
|
Col
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
import { findJobFootprintThresholds } from "../../generic/utils.js";
|
||||||
|
|
||||||
|
export let job;
|
||||||
|
|
||||||
|
// Prepare Job Footprint Data Based On Values Saved In Database
|
||||||
|
const jobFootprintData = job?.footprint?.map((jf) => {
|
||||||
|
const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name);
|
||||||
|
if (fmc) {
|
||||||
|
// Unit
|
||||||
|
const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "")
|
||||||
|
|
||||||
|
// Threshold / -Differences
|
||||||
|
const fmt = findJobFootprintThresholds(job, jf.stat, fmc);
|
||||||
|
|
||||||
|
// Define basic data -> Value: Use as Provided
|
||||||
|
const fmBase = {
|
||||||
|
name: jf.name,
|
||||||
|
stat: jf.stat,
|
||||||
|
value: jf.value,
|
||||||
|
unit: unit,
|
||||||
|
peak: fmt.peak,
|
||||||
|
dir: fmc.lowerIsBetter
|
||||||
|
};
|
||||||
|
|
||||||
|
if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "alert")) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: "danger",
|
||||||
|
message: `Footprint value way ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
||||||
|
impact: 3
|
||||||
|
};
|
||||||
|
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "caution")) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: "warning",
|
||||||
|
message: `Footprint value ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
||||||
|
impact: 2,
|
||||||
|
};
|
||||||
|
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "normal")) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: "success",
|
||||||
|
message: "Footprint value within expected thresholds.",
|
||||||
|
impact: 1,
|
||||||
|
};
|
||||||
|
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: "info",
|
||||||
|
message:
|
||||||
|
"Footprint value above expected normal threshold: Check for artifacts recommended.",
|
||||||
|
impact: 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...fmBase,
|
||||||
|
color: "secondary",
|
||||||
|
message:
|
||||||
|
"Footprint value above expected peak threshold: Check for artifacts!",
|
||||||
|
impact: -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else { // No matching metric config: display as single value
|
||||||
|
return {
|
||||||
|
name: jf.name,
|
||||||
|
stat: jf.stat,
|
||||||
|
value: jf.value,
|
||||||
|
message:
|
||||||
|
`No config for metric ${jf.name} found.`,
|
||||||
|
impact: 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}).sort(function (a, b) { // Sort by impact value primarily, within impact sort name alphabetically
|
||||||
|
return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
|
||||||
|
});;
|
||||||
|
|
||||||
|
function evalFootprint(value, thresholds, lowerIsBetter, level) {
|
||||||
|
// Handle Metrics in which less value is better
|
||||||
|
switch (level) {
|
||||||
|
case "peak":
|
||||||
|
if (lowerIsBetter)
|
||||||
|
return false; // metric over peak -> return false to trigger impact -1
|
||||||
|
else return value <= thresholds.peak && value > thresholds.normal;
|
||||||
|
case "alert":
|
||||||
|
if (lowerIsBetter)
|
||||||
|
return value <= thresholds.peak && value >= thresholds.alert;
|
||||||
|
else return value <= thresholds.alert && value >= 0;
|
||||||
|
case "caution":
|
||||||
|
if (lowerIsBetter)
|
||||||
|
return value < thresholds.alert && value >= thresholds.caution;
|
||||||
|
else return value <= thresholds.caution && value > thresholds.alert;
|
||||||
|
case "normal":
|
||||||
|
if (lowerIsBetter)
|
||||||
|
return value < thresholds.caution && value >= 0;
|
||||||
|
else return value <= thresholds.normal && value > thresholds.caution;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CardBody>
|
||||||
|
{#if jobFootprintData.length === 0}
|
||||||
|
<div class="text-center">No footprint data for job available.</div>
|
||||||
|
{:else}
|
||||||
|
{#each jobFootprintData as fpd, index}
|
||||||
|
{#if fpd.impact !== 4}
|
||||||
|
<div class="mb-1 d-flex justify-content-between">
|
||||||
|
<div> <b>{fpd.name} ({fpd.stat})</b></div>
|
||||||
|
<div
|
||||||
|
class="cursor-help d-inline-flex"
|
||||||
|
id={`footprint-${job.jobId}-${index}`}
|
||||||
|
>
|
||||||
|
<div class="mx-1">
|
||||||
|
{#if fpd.impact === 3}
|
||||||
|
<Icon name="exclamation-triangle-fill" class="text-danger" />
|
||||||
|
{:else if fpd.impact === 2}
|
||||||
|
<Icon name="exclamation-triangle" class="text-warning" />
|
||||||
|
{:else if fpd.impact === 0}
|
||||||
|
<Icon name="info-circle" class="text-info" />
|
||||||
|
{:else if fpd.impact === -1}
|
||||||
|
<Icon name="info-circle-fill" class="text-danger" />
|
||||||
|
{/if}
|
||||||
|
{#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-smile" class="text-info" />
|
||||||
|
{:else if fpd.impact === -1}
|
||||||
|
<Icon name="emoji-dizzy" class="text-danger" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{fpd.value} / {fpd.peak}
|
||||||
|
{fpd.unit}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
target={`footprint-${job.jobId}-${index}`}
|
||||||
|
placement="right"
|
||||||
|
>{fpd.message}</Tooltip
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<Row cols={12} class="{(jobFootprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}">
|
||||||
|
{#if fpd.dir}
|
||||||
|
<Col xs="1">
|
||||||
|
<Icon name="caret-left-fill" />
|
||||||
|
</Col>
|
||||||
|
{/if}
|
||||||
|
<Col xs="11" class="align-content-center">
|
||||||
|
<Progress value={fpd.value} max={fpd.peak} color={fpd.color} />
|
||||||
|
</Col>
|
||||||
|
{#if !fpd.dir}
|
||||||
|
<Col xs="1">
|
||||||
|
<Icon name="caret-right-fill" />
|
||||||
|
</Col>
|
||||||
|
{/if}
|
||||||
|
</Row>
|
||||||
|
{:else}
|
||||||
|
<div class="mb-1 d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
<b>{fpd.name} ({fpd.stat})</b>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="cursor-help d-inline-flex"
|
||||||
|
id={`footprint-${job.jobId}-${index}`}
|
||||||
|
>
|
||||||
|
<div class="mx-1">
|
||||||
|
<Icon name="info-circle"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{fpd.value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
target={`footprint-${job.jobId}-${index}`}
|
||||||
|
placement="right"
|
||||||
|
>{fpd.message}</Tooltip
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</CardBody>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cursor-help {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
</style>
|
72
web/frontend/src/job/jobsummary/JobFootprintPolar.svelte
Normal file
72
web/frontend/src/job/jobsummary/JobFootprintPolar.svelte
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<!--
|
||||||
|
@component Job Footprint Polar Plot component; Displays queried job metric statistics polar plot.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
- `job Object`: The GQL job object
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte";
|
||||||
|
import {
|
||||||
|
queryStore,
|
||||||
|
gql,
|
||||||
|
getContextClient
|
||||||
|
} from "@urql/svelte";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
Spinner
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
import Polar from "../../generic/plots/Polar.svelte";
|
||||||
|
import { findJobFootprintThresholds } from "../../generic/utils.js";
|
||||||
|
|
||||||
|
export let job;
|
||||||
|
|
||||||
|
// Metric Names Configured To Be Footprints For (sub)Cluster
|
||||||
|
const clusterFootprintMetrics = getContext("clusters")
|
||||||
|
.find((c) => c.name == job.cluster)?.subClusters
|
||||||
|
.find((sc) => sc.name == job.subCluster)?.footprint || []
|
||||||
|
|
||||||
|
// Get Scaled Peak Threshold Based on Footprint Type ([min, max, avg]) and Job Exclusivity
|
||||||
|
const polarMetrics = getContext("globalMetrics").reduce((pms, gm) => {
|
||||||
|
if (clusterFootprintMetrics.includes(gm.name)) {
|
||||||
|
const fmt = findJobFootprintThresholds(job, gm.footprint, getContext("getMetricConfig")(job.cluster, job.subCluster, gm.name));
|
||||||
|
pms.push({ name: gm.name, peak: fmt ? fmt.peak : null });
|
||||||
|
}
|
||||||
|
return pms;
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Pull All Series For Footprint Metrics Statistics Only On Node Scope
|
||||||
|
const client = getContextClient();
|
||||||
|
const polarQuery = gql`
|
||||||
|
query ($dbid: ID!, $selectedMetrics: [String!]!) {
|
||||||
|
jobMetricStats(id: $dbid, metrics: $selectedMetrics) {
|
||||||
|
name
|
||||||
|
stats {
|
||||||
|
min
|
||||||
|
avg
|
||||||
|
max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
$: polarData = queryStore({
|
||||||
|
client: client,
|
||||||
|
query: polarQuery,
|
||||||
|
variables:{ dbid: job.id, selectedMetrics: clusterFootprintMetrics },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CardBody>
|
||||||
|
{#if $polarData.fetching}
|
||||||
|
<Spinner />
|
||||||
|
{:else if $polarData.error}
|
||||||
|
<Card body color="danger">{$polarData.error.message}</Card>
|
||||||
|
{:else}
|
||||||
|
<Polar
|
||||||
|
{polarMetrics}
|
||||||
|
polarData={$polarData.data.jobMetricStats}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</CardBody>
|
@ -217,6 +217,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan={selectedMetrics.length + 1}>
|
<td colspan={selectedMetrics.length + 1}>
|
||||||
<div style="text-align:center;">
|
<div style="text-align:center;">
|
||||||
|
{#if !usePaging}
|
||||||
<p><b>
|
<p><b>
|
||||||
Loading nodes {nodes.length + 1} to
|
Loading nodes {nodes.length + 1} to
|
||||||
{ matchedNodes
|
{ matchedNodes
|
||||||
@ -224,6 +225,7 @@
|
|||||||
: (nodes.length + paging.itemsPerPage)
|
: (nodes.length + paging.itemsPerPage)
|
||||||
}
|
}
|
||||||
</b></p>
|
</b></p>
|
||||||
|
{/if}
|
||||||
<Spinner secondary />
|
<Spinner secondary />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -102,6 +102,19 @@
|
|||||||
Shared
|
Shared
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
<!-- Fallback -->
|
||||||
|
{:else if nodeJobsData.jobs.count >= 1}
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupText>
|
||||||
|
<Icon name="circle-fill"/>
|
||||||
|
</InputGroupText>
|
||||||
|
<InputGroupText>
|
||||||
|
Status
|
||||||
|
</InputGroupText>
|
||||||
|
<Button color="success" disabled>
|
||||||
|
Allocated Jobs
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
{:else}
|
{:else}
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupText>
|
<InputGroupText>
|
||||||
|
@ -98,12 +98,12 @@
|
|||||||
|
|
||||||
let extendedLegendData = null;
|
let extendedLegendData = null;
|
||||||
$: if ($nodeJobsData?.data) {
|
$: if ($nodeJobsData?.data) {
|
||||||
// Get Shared State of Node: Only Build extended Legend For Shared Nodes
|
// Build Extended for allocated nodes [Commented: Only Build extended Legend For Shared Nodes]
|
||||||
if ($nodeJobsData.data.jobs.count >= 1 && !$nodeJobsData.data.jobs.items[0].exclusive) {
|
if ($nodeJobsData.data.jobs.count >= 1) { // "&& !$nodeJobsData.data.jobs.items[0].exclusive)"
|
||||||
const accSet = Array.from(new Set($nodeJobsData.data.jobs.items
|
const accSet = Array.from(new Set($nodeJobsData.data.jobs.items
|
||||||
.map((i) => i.resources
|
.map((i) => i.resources
|
||||||
.filter((r) => r.hostname === nodeData.host)
|
.filter((r) => (r.hostname === nodeData.host) && r?.accelerators)
|
||||||
.map((r) => r.accelerators)
|
.map((r) => r?.accelerators)
|
||||||
)
|
)
|
||||||
)).flat(2)
|
)).flat(2)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user