Files
cloud-services/services/ota_update_go/handlers/ecu_stats_vin_get.go

166 lines
5.7 KiB
Go

package handlers
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/julienschmidt/httprouter"
"otaupdate/services"
"github.com/fiskerinc/cloud-services/pkg/clickhouse"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils"
"github.com/fiskerinc/cloud-services/pkg/validator"
ch "github.com/ClickHouse/clickhouse-go/v2"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
)
// HandleVINECUStatsGetList godoc
// @Summary List API tokens
// @Description List API tokens. Requires API token permission
// @Accept json
// @Produce json
// @Param Authorization header string false "Bearer <ID token>"
// @Param Api-Key header string false "<API token>"
// @Param ecus query []string true "ECU names"
// @Param hours query int true "Past hours that must be included into the request"
// @Param min_zero_pct query float32 true "Minimum zero values percent"
// @Param min_out_of_range_pct query int true "Minimum out of range percent"
// @Param dbc path string true "DBC hash"
// @Param vin path string true "VIN"
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.ECUStat}
// @Failure 400 {object} common.JSONError "Bad request"
// @Failure 401 {object} common.JSONError "Unauthorized"
// @Failure 503 {object} common.JSONError "Service unavailable"
// @Router /ecu_stats/{vin}/{dbc} [get]
func HandleVINECUStatsGetList(w http.ResponseWriter, r *http.Request) {
conn, err := services.GetClickhouseConn()
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
logger.Error().Err(err).Msg("cannot get clickhouse client")
return
}
filter, err := parseVINStatsFilter(r)
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
return
}
stats, err := getEcusVINStats(conn, filter)
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
return
}
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{Data: stats})
}
func parseVINStatsFilter(r *http.Request) (common.VINStatsFilter, error) {
sch := schema.NewDecoder()
filter := common.VINStatsFilter{}
sch.SetAliasTag("json")
err := sch.Decode(&filter, r.URL.Query())
if err != nil {
return common.VINStatsFilter{}, errors.WithStack(err)
}
params := httprouter.ParamsFromContext(r.Context())
filter.VIN = params.ByName("vin")
filter.DBC = params.ByName("dbc")
err = validator.GetValidator().Struct(filter)
if err != nil {
return common.VINStatsFilter{}, errors.WithStack(err)
}
return filter, nil
}
func getEcusVINStats(conn clickhouse.ConnInterface, filter common.VINStatsFilter) ([]common.ECUStat, error) {
var result []common.ECUStat
chCtx := ch.Context(context.Background(), ch.WithParameters(ch.Parameters{
"minOutOfRangePct": fmt.Sprint(filter.MinOutOfRangePct),
"minZeroPct": fmt.Sprint(filter.MinZeroPct),
"hours": fmt.Sprint(filter.Hours),
"vin": filter.VIN,
"dbc": filter.DBC,
"ecus": "['" + strings.Join(filter.ECUs, "','") + "']",
}))
if err := conn.Select(chCtx, &result, `
select ecu_name,
sum(case when value_out_range_pct>{minOutOfRangePct:Float32} then 1 else 0 end) as signals_w_incorrect_values,
sum(case when zero_pct>{minZeroPct:Float32} then 1 else 0 end ) as signals_all_zero,
count(*) as number_of_ecu_signals,
sum(tot_cnt) as total_signal_records,
(signals_w_incorrect_values/number_of_ecu_signals) incorrect_val_signal_pct,
signals_all_zero/number_of_ecu_signals as zero_signals_pct
from
(/* add missing signals and ecus from dbc as full outer join and generate per a CAN signal stats */
select
case when aa.Name<>'' then aa.Name
when bb.signal_name<>'' then bb.signal_name
else null end as signal_name,
case when aa.ID<>'0' then aa.ID
when bb.message_id<>'0' then bb.message_id
else null end as can_id,
case when aa.ecu_name<>'' then aa.ecu_name
when dbcm.ecu_name<>'' then dbcm.ecu_name
else null end as ecu_name,
zero_count,value_out_range_cnt, tot_cnt,
case when tot_cnt <> 0 then value_out_range_pct else 0 end as value_out_range_pct,
case when tot_cnt <> 0 then zero_pct else 0 end as zero_pct
from
(/* check if signals are within dbc value range and if 0 on joined CAN signals and dbc*/
select Name, ID, signal_name, ecu_name,cycle_time_ns,sender_node,
sum(case when Value=0 then 1 else 0 end) as zero_count,
sum(case when a.Value<b.min or a.Value>b.max then 1 else 0 end) as value_out_range_cnt,
count(*) as tot_cnt,
value_out_range_cnt/tot_cnt as value_out_range_pct,
zero_count/tot_cnt as zero_pct
from vehicle_signal as a
inner join
(/* select dbc_messages and dbc_signals */
select b1.*, b2.message_id, b2.ecu_name, b2.cycle_time_ns, b2.sender_node
from dbc_signals as b1
inner join dbc_messages as b2
on b1.dbc_hash=b2.dbc_hash
and b1.message_id=b2.message_id
where b1.dbc_hash = {dbc:String}
) as b
on a.Name=b.signal_name
where
a.Timestamp> (select max(Timestamp) from vehicle_signal where VIN = {vin:String}) - toIntervalHour({hours:UInt64})
and
a.VIN = {vin:String}
group by 1,2,3,4,5,6
) as aa
full outer join dbc_signals as bb
on aa.Name=bb.signal_name
and aa.ID=bb.message_id
inner join dbc_messages as dbcm
on bb.message_id=dbcm.message_id
where bb.dbc_hash = {dbc:String} and dbcm.dbc_hash = {dbc:String}
and ecu_name in {ecus:Array(String)}
)
group by ecu_name
order by zero_signals_pct desc, incorrect_val_signal_pct desc
`); err != nil {
return result, err
}
return result, nil
}