in pkg/server/clickhouse.go [245:356]
func (t *StatsServer) CreateProcessMetricDataHandler() http.HandlerFunc {
return func(w http.ResponseWriter, request *http.Request) {
type requestParams struct {
TestName string `json:"testName"`
Branch string `json:"branch"`
Machine string `json:"machine"`
Product string `json:"product"`
MetricName string `json:"metricName"`
Mode string `json:"mode"`
}
var params requestParams
decoder := json.NewDecoder(request.Body)
defer request.Body.Close()
if err := decoder.Decode(¶ms); err != nil {
http.Error(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
return
}
// Replace all matches with %
machine := removeLastPart(params.Machine) + "%"
// Map product to table - this is a simplified mapping, adjust as needed
table := mapProductToTable(params.Product)
if table == "" {
http.Error(w, "Unknown product: "+params.Product, http.StatusBadRequest)
return
}
// Query the database for metric values
sql := fmt.Sprintf(`
SELECT groupArray(metric_value) AS MetricValues
FROM (
SELECT measures.value as metric_value
FROM perfintDev.%s ARRAY JOIN measures
WHERE branch = '%s'
AND measures.name = '%s'
AND machine LIKE '%s'
AND project = '%s'
AND mode = '%s'
AND generated_time >= now() - INTERVAL 1 MONTH
ORDER BY generated_time
)
`, table, params.Branch, params.MetricName, machine, params.TestName, params.Mode)
db, err := t.openDatabaseConnection()
if err != nil {
http.Error(w, "Failed to open database: "+err.Error(), http.StatusInternalServerError)
return
}
defer func(db driver.Conn) {
_ = db.Close()
}(db)
var queryResult struct {
MetricValues []int
}
err = db.QueryRow(request.Context(), sql).Scan(&queryResult.MetricValues)
if err != nil {
http.Error(w, "Database query failed: "+err.Error(), http.StatusInternalServerError)
return
}
if len(queryResult.MetricValues) == 0 {
http.Error(w, "No data found for the specified parameters", http.StatusNotFound)
return
}
// Run Change Point algorithm
changePoints := statistic.GetChangePointIndexes(queryResult.MetricValues, 3)
// Median difference and effect size thresholds (same as degradation detector)
medianDifferenceThreshold := 10.0
effectSizeThreshold := 2.0
// Filter change points based on median difference and effect size
validChangePoints := filterValidChangePoints(queryResult.MetricValues, changePoints, medianDifferenceThreshold, effectSizeThreshold)
// Get the segment to analyze for max value
// Strategy: use data after the last significant behavior change
var segmentForAnalysis []int
if len(validChangePoints) == 0 {
// No valid change points - use all data
segmentForAnalysis = queryResult.MetricValues
} else {
// Use data after the last valid change point
lastChangePoint := validChangePoints[len(validChangePoints)-1]
segmentForAnalysis = queryResult.MetricValues[lastChangePoint:]
}
// Remove outliers using MAD-based detection (windowSize=5, threshold=3)
processedData := outlier_detection.RemoveOutliers(segmentForAnalysis, 5, 3.0)
type processMetricResponse struct {
MaxValue int `json:"maxValue"`
}
response := processMetricResponse{
MaxValue: slices.Max(processedData),
}
jsonData, err := json.Marshal(response)
if err != nil {
http.Error(w, "Failed to marshal response: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(jsonData)
}
}