mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-10-31 16:05:06 +01:00 
			
		
		
		
	Add REST endpoint for metrics
This commit is contained in:
		
							
								
								
									
										48
									
								
								api/rest.go
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								api/rest.go
									
									
									
									
									
								
							| @@ -43,6 +43,8 @@ func (api *RestApi) MountRoutes(r *mux.Router) { | ||||
| 	r.HandleFunc("/jobs/{id}", api.getJob).Methods(http.MethodGet) | ||||
| 	r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch) | ||||
|  | ||||
| 	r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet) | ||||
|  | ||||
| 	if api.MachineStateDir != "" { | ||||
| 		r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet) | ||||
| 		r.HandleFunc("/machine_state/{cluster}/{host}", api.putMachineState).Methods(http.MethodPut, http.MethodPost) | ||||
| @@ -370,6 +372,52 @@ func (api *RestApi) stopJob(rw http.ResponseWriter, r *http.Request) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (api *RestApi) getJobMetrics(rw http.ResponseWriter, r *http.Request) { | ||||
| 	id := mux.Vars(r)["id"] | ||||
| 	metrics := r.URL.Query()["metric"] | ||||
| 	var scopes []schema.MetricScope | ||||
| 	for _, scope := range r.URL.Query()["scope"] { | ||||
| 		var s schema.MetricScope | ||||
| 		if err := s.UnmarshalGQL(scope); err != nil { | ||||
| 			http.Error(rw, err.Error(), http.StatusBadRequest) | ||||
| 			return | ||||
| 		} | ||||
| 		scopes = append(scopes, s) | ||||
| 	} | ||||
|  | ||||
| 	rw.Header().Add("Content-Type", "application/json") | ||||
| 	rw.WriteHeader(http.StatusOK) | ||||
|  | ||||
| 	type Respone struct { | ||||
| 		Data *struct { | ||||
| 			JobMetrics []*model.JobMetricWithName `json:"jobMetrics"` | ||||
| 		} `json:"data"` | ||||
| 		Error *struct { | ||||
| 			Message string `json:"message"` | ||||
| 		} `json:"error"` | ||||
| 	} | ||||
|  | ||||
| 	data, err := api.Resolver.Query().JobMetrics(r.Context(), id, metrics, scopes) | ||||
| 	if err != nil { | ||||
| 		if err := json.NewEncoder(rw).Encode(Respone{ | ||||
| 			Error: &struct { | ||||
| 				Message string "json:\"message\"" | ||||
| 			}{Message: err.Error()}, | ||||
| 		}); err != nil { | ||||
| 			log.Println(err.Error()) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := json.NewEncoder(rw).Encode(Respone{ | ||||
| 		Data: &struct { | ||||
| 			JobMetrics []*model.JobMetricWithName "json:\"jobMetrics\"" | ||||
| 		}{JobMetrics: data}, | ||||
| 	}); err != nil { | ||||
| 		log.Println(err.Error()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (api *RestApi) putMachineState(rw http.ResponseWriter, r *http.Request) { | ||||
| 	if api.MachineStateDir == "" { | ||||
| 		http.Error(rw, "not enabled", http.StatusNotFound) | ||||
|   | ||||
| @@ -103,9 +103,9 @@ type Series { | ||||
| } | ||||
|  | ||||
| type MetricStatistics { | ||||
|   avg: NullableFloat! | ||||
|   min: NullableFloat! | ||||
|   max: NullableFloat! | ||||
|   avg: Float! | ||||
|   min: Float! | ||||
|   max: Float! | ||||
| } | ||||
|  | ||||
| type StatsSeries { | ||||
|   | ||||
| @@ -65,3 +65,41 @@ func (f Float) MarshalGQL(w io.Writer) { | ||||
| 		w.Write(strconv.AppendFloat(make([]byte, 0, 10), float64(f), 'f', 2, 64)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Only used via REST-API, not via GraphQL. | ||||
| // This uses a lot less allocations per series, | ||||
| // but it turns out that the performance increase | ||||
| // from using this is not that big. | ||||
| func (s *Series) MarshalJSON() ([]byte, error) { | ||||
| 	buf := make([]byte, 0, 512+len(s.Data)*8) | ||||
| 	buf = append(buf, `{"hostname":"`...) | ||||
| 	buf = append(buf, s.Hostname...) | ||||
| 	buf = append(buf, '"') | ||||
| 	if s.Id != nil { | ||||
| 		buf = append(buf, `,"id":`...) | ||||
| 		buf = strconv.AppendInt(buf, int64(*s.Id), 10) | ||||
| 	} | ||||
| 	if s.Statistics != nil { | ||||
| 		buf = append(buf, `,"statistics":{"min":`...) | ||||
| 		buf = strconv.AppendFloat(buf, s.Statistics.Min, 'f', 2, 64) | ||||
| 		buf = append(buf, `,"avg":`...) | ||||
| 		buf = strconv.AppendFloat(buf, s.Statistics.Avg, 'f', 2, 64) | ||||
| 		buf = append(buf, `,"max":`...) | ||||
| 		buf = strconv.AppendFloat(buf, s.Statistics.Max, 'f', 2, 64) | ||||
| 		buf = append(buf, '}') | ||||
| 	} | ||||
| 	buf = append(buf, `,"data":[`...) | ||||
| 	for i := 0; i < len(s.Data); i++ { | ||||
| 		if i != 0 { | ||||
| 			buf = append(buf, ',') | ||||
| 		} | ||||
|  | ||||
| 		if s.Data[i].IsNaN() { | ||||
| 			buf = append(buf, `null`...) | ||||
| 		} else { | ||||
| 			buf = strconv.AppendFloat(buf, float64(s.Data[i]), 'f', 2, 32) | ||||
| 		} | ||||
| 	} | ||||
| 	buf = append(buf, ']', '}') | ||||
| 	return buf, nil | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user