Add job metricdata rest endpoint

Fixes #102
This commit is contained in:
Jan Eitzinger 2023-06-14 15:03:01 +02:00
parent c662ced7e7
commit 54aa940d3e
4 changed files with 383 additions and 15 deletions

View File

@ -624,7 +624,7 @@
} }
}, },
"/jobs/{id}": { "/jobs/{id}": {
"get": { "post": {
"security": [ "security": [
{ {
"ApiKeyAuth": [] "ApiKeyAuth": []
@ -666,7 +666,7 @@
"200": { "200": {
"description": "Job resource", "description": "Job resource",
"schema": { "schema": {
"$ref": "#/definitions/schema.JobMeta" "$ref": "#/definitions/api.GetJobApiResponse"
} }
}, },
"400": { "400": {
@ -769,6 +769,20 @@
} }
} }
}, },
"api.GetJobApiResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/api.JobMetricWithName"
}
},
"meta": {
"$ref": "#/definitions/schema.Job"
}
}
},
"api.GetJobsApiResponse": { "api.GetJobsApiResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -789,6 +803,20 @@
} }
} }
}, },
"api.JobMetricWithName": {
"type": "object",
"properties": {
"metric": {
"$ref": "#/definitions/schema.JobMetric"
},
"name": {
"type": "string"
},
"scope": {
"$ref": "#/definitions/schema.MetricScope"
}
}
},
"api.StartJobApiResponse": { "api.StartJobApiResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1148,6 +1176,26 @@
} }
} }
}, },
"schema.JobMetric": {
"type": "object",
"properties": {
"series": {
"type": "array",
"items": {
"$ref": "#/definitions/schema.Series"
}
},
"statisticsSeries": {
"$ref": "#/definitions/schema.StatsSeries"
},
"timestep": {
"type": "integer"
},
"unit": {
"$ref": "#/definitions/schema.Unit"
}
}
},
"schema.JobState": { "schema.JobState": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -1198,6 +1246,41 @@
} }
} }
}, },
"schema.MetricScope": {
"type": "string",
"enum": [
"invalid_scope",
"node",
"socket",
"memoryDomain",
"core",
"hwthread",
"accelerator"
],
"x-enum-varnames": [
"MetricScopeInvalid",
"MetricScopeNode",
"MetricScopeSocket",
"MetricScopeMemoryDomain",
"MetricScopeCore",
"MetricScopeHWThread",
"MetricScopeAccelerator"
]
},
"schema.MetricStatistics": {
"type": "object",
"properties": {
"avg": {
"type": "number"
},
"max": {
"type": "number"
},
"min": {
"type": "number"
}
}
},
"schema.Resource": { "schema.Resource": {
"description": "A resource used by a job", "description": "A resource used by a job",
"type": "object", "type": "object",
@ -1226,6 +1309,58 @@
} }
} }
}, },
"schema.Series": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "number"
}
},
"hostname": {
"type": "string"
},
"id": {
"type": "string"
},
"statistics": {
"$ref": "#/definitions/schema.MetricStatistics"
}
}
},
"schema.StatsSeries": {
"type": "object",
"properties": {
"max": {
"type": "array",
"items": {
"type": "number"
}
},
"mean": {
"type": "array",
"items": {
"type": "number"
}
},
"min": {
"type": "array",
"items": {
"type": "number"
}
},
"percentiles": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "number"
}
}
}
}
},
"schema.Tag": { "schema.Tag": {
"description": "Defines a tag using name and type.", "description": "Defines a tag using name and type.",
"type": "object", "type": "object",

View File

@ -42,6 +42,15 @@ definitions:
description: Statustext of Errorcode description: Statustext of Errorcode
type: string type: string
type: object type: object
api.GetJobApiResponse:
properties:
data:
items:
$ref: '#/definitions/api.JobMetricWithName'
type: array
meta:
$ref: '#/definitions/schema.Job'
type: object
api.GetJobsApiResponse: api.GetJobsApiResponse:
properties: properties:
items: items:
@ -56,6 +65,15 @@ definitions:
description: Page id returned description: Page id returned
type: integer type: integer
type: object type: object
api.JobMetricWithName:
properties:
metric:
$ref: '#/definitions/schema.JobMetric'
name:
type: string
scope:
$ref: '#/definitions/schema.MetricScope'
type: object
api.StartJobApiResponse: api.StartJobApiResponse:
properties: properties:
id: id:
@ -338,6 +356,19 @@ definitions:
minimum: 1 minimum: 1
type: integer type: integer
type: object type: object
schema.JobMetric:
properties:
series:
items:
$ref: '#/definitions/schema.Series'
type: array
statisticsSeries:
$ref: '#/definitions/schema.StatsSeries'
timestep:
type: integer
unit:
$ref: '#/definitions/schema.Unit'
type: object
schema.JobState: schema.JobState:
enum: enum:
- running - running
@ -379,6 +410,33 @@ definitions:
unit: unit:
$ref: '#/definitions/schema.Unit' $ref: '#/definitions/schema.Unit'
type: object type: object
schema.MetricScope:
enum:
- invalid_scope
- node
- socket
- memoryDomain
- core
- hwthread
- accelerator
type: string
x-enum-varnames:
- MetricScopeInvalid
- MetricScopeNode
- MetricScopeSocket
- MetricScopeMemoryDomain
- MetricScopeCore
- MetricScopeHWThread
- MetricScopeAccelerator
schema.MetricStatistics:
properties:
avg:
type: number
max:
type: number
min:
type: number
type: object
schema.Resource: schema.Resource:
description: A resource used by a job description: A resource used by a job
properties: properties:
@ -399,6 +457,40 @@ definitions:
type: integer type: integer
type: array type: array
type: object type: object
schema.Series:
properties:
data:
items:
type: number
type: array
hostname:
type: string
id:
type: string
statistics:
$ref: '#/definitions/schema.MetricStatistics'
type: object
schema.StatsSeries:
properties:
max:
items:
type: number
type: array
mean:
items:
type: number
type: array
min:
items:
type: number
type: array
percentiles:
additionalProperties:
items:
type: number
type: array
type: object
type: object
schema.Tag: schema.Tag:
description: Defines a tag using name and type. description: Defines a tag using name and type.
properties: properties:
@ -502,7 +594,7 @@ paths:
tags: tags:
- query - query
/jobs/{id}: /jobs/{id}:
get: post:
consumes: consumes:
- application/json - application/json
description: |- description: |-
@ -528,7 +620,7 @@ paths:
"200": "200":
description: Job resource description: Job resource
schema: schema:
$ref: '#/definitions/schema.JobMeta' $ref: '#/definitions/api.GetJobApiResponse'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:

View File

@ -630,7 +630,7 @@ const docTemplate = `{
} }
}, },
"/jobs/{id}": { "/jobs/{id}": {
"get": { "post": {
"security": [ "security": [
{ {
"ApiKeyAuth": [] "ApiKeyAuth": []
@ -672,7 +672,7 @@ const docTemplate = `{
"200": { "200": {
"description": "Job resource", "description": "Job resource",
"schema": { "schema": {
"$ref": "#/definitions/schema.JobMeta" "$ref": "#/definitions/api.GetJobApiResponse"
} }
}, },
"400": { "400": {
@ -775,6 +775,20 @@ const docTemplate = `{
} }
} }
}, },
"api.GetJobApiResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/api.JobMetricWithName"
}
},
"meta": {
"$ref": "#/definitions/schema.Job"
}
}
},
"api.GetJobsApiResponse": { "api.GetJobsApiResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -795,6 +809,20 @@ const docTemplate = `{
} }
} }
}, },
"api.JobMetricWithName": {
"type": "object",
"properties": {
"metric": {
"$ref": "#/definitions/schema.JobMetric"
},
"name": {
"type": "string"
},
"scope": {
"$ref": "#/definitions/schema.MetricScope"
}
}
},
"api.StartJobApiResponse": { "api.StartJobApiResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1154,6 +1182,26 @@ const docTemplate = `{
} }
} }
}, },
"schema.JobMetric": {
"type": "object",
"properties": {
"series": {
"type": "array",
"items": {
"$ref": "#/definitions/schema.Series"
}
},
"statisticsSeries": {
"$ref": "#/definitions/schema.StatsSeries"
},
"timestep": {
"type": "integer"
},
"unit": {
"$ref": "#/definitions/schema.Unit"
}
}
},
"schema.JobState": { "schema.JobState": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -1204,6 +1252,41 @@ const docTemplate = `{
} }
} }
}, },
"schema.MetricScope": {
"type": "string",
"enum": [
"invalid_scope",
"node",
"socket",
"memoryDomain",
"core",
"hwthread",
"accelerator"
],
"x-enum-varnames": [
"MetricScopeInvalid",
"MetricScopeNode",
"MetricScopeSocket",
"MetricScopeMemoryDomain",
"MetricScopeCore",
"MetricScopeHWThread",
"MetricScopeAccelerator"
]
},
"schema.MetricStatistics": {
"type": "object",
"properties": {
"avg": {
"type": "number"
},
"max": {
"type": "number"
},
"min": {
"type": "number"
}
}
},
"schema.Resource": { "schema.Resource": {
"description": "A resource used by a job", "description": "A resource used by a job",
"type": "object", "type": "object",
@ -1232,6 +1315,58 @@ const docTemplate = `{
} }
} }
}, },
"schema.Series": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "number"
}
},
"hostname": {
"type": "string"
},
"id": {
"type": "string"
},
"statistics": {
"$ref": "#/definitions/schema.MetricStatistics"
}
}
},
"schema.StatsSeries": {
"type": "object",
"properties": {
"max": {
"type": "array",
"items": {
"type": "number"
}
},
"mean": {
"type": "array",
"items": {
"type": "number"
}
},
"min": {
"type": "array",
"items": {
"type": "number"
}
},
"percentiles": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "number"
}
}
}
}
},
"schema.Tag": { "schema.Tag": {
"description": "Defines a tag using name and type.", "description": "Defines a tag using name and type.",
"type": "object", "type": "object",

View File

@ -69,7 +69,7 @@ func (api *RestApi) MountRoutes(r *mux.Router) {
// r.HandleFunc("/jobs/import/", api.importJob).Methods(http.MethodPost, http.MethodPut) // r.HandleFunc("/jobs/import/", api.importJob).Methods(http.MethodPost, http.MethodPut)
r.HandleFunc("/jobs/", api.getJobs).Methods(http.MethodGet) r.HandleFunc("/jobs/", api.getJobs).Methods(http.MethodGet)
r.HandleFunc("/jobs/{id}", api.getJobById).Methods(http.MethodGet) r.HandleFunc("/jobs/{id}", api.getJobById).Methods(http.MethodPost)
r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch) r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch)
r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet) r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet)
r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete)
@ -147,7 +147,13 @@ type GetJobApiRequest []string
type GetJobApiResponse struct { type GetJobApiResponse struct {
Meta *schema.Job Meta *schema.Job
Data []*model.JobMetricWithName Data []*JobMetricWithName
}
type JobMetricWithName struct {
Name string `json:"name"`
Scope schema.MetricScope `json:"scope"`
Metric *schema.JobMetric `json:"metric"`
} }
func handleError(err error, statusCode int, rw http.ResponseWriter) { func handleError(err error, statusCode int, rw http.ResponseWriter) {
@ -317,8 +323,8 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
// @accept json // @accept json
// @produce json // @produce json
// @param id path int true "Database ID of Job" // @param id path int true "Database ID of Job"
// @param request body api.GetJobApiRequest true "Array of metric names" // @param request body api.GetJobApiRequest true "Array of metric names"
// @success 200 {object} schema.JobMeta "Job resource" // @success 200 {object} api.GetJobApiResponse "Job resource"
// @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"
@ -326,7 +332,7 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: finding job failed: sql: no rows in result set" // @failure 422 {object} api.ErrorResponse "Unprocessable Entity: finding job failed: sql: no rows in result set"
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /jobs/{id} [get] // @router /jobs/{id} [post]
func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) { if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) {
handleError(fmt.Errorf("missing role: %v", handleError(fmt.Errorf("missing role: %v",
@ -356,7 +362,7 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
} }
var metrics GetJobApiRequest var metrics GetJobApiRequest
if err := decode(r.Body, &metrics); err != nil { if err = decode(r.Body, &metrics); err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest) http.Error(rw, err.Error(), http.StatusBadRequest)
return return
} }
@ -375,10 +381,10 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
return return
} }
res := []*model.JobMetricWithName{} res := []*JobMetricWithName{}
for name, md := range data { for name, md := range data {
for scope, metric := range md { for scope, metric := range md {
res = append(res, &model.JobMetricWithName{ res = append(res, &JobMetricWithName{
Name: name, Name: name,
Scope: scope, Scope: scope,
Metric: metric, Metric: metric,
@ -386,7 +392,7 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
} }
} }
log.Debugf("/api/job/%d: get job %d", id, job.JobID) log.Debugf("/api/job/%s: get job %d", id, job.JobID)
rw.Header().Add("Content-Type", "application/json") rw.Header().Add("Content-Type", "application/json")
bw := bufio.NewWriter(rw) bw := bufio.NewWriter(rw)
defer bw.Flush() defer bw.Flush()