156 lines
6.0 KiB
Go
156 lines
6.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"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"
|
|
)
|
|
|
|
// HandleECUStatsGetList 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 dbcs query []string true "DBC hashes"
|
|
// @Param vins query []string true "Array of VINs"
|
|
// @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"
|
|
// @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 [get]
|
|
func HandleECUStatsGetList(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 := parseStatsFilter(r)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
return
|
|
}
|
|
|
|
stats, err := getEcusStats(conn, filter)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
|
return
|
|
}
|
|
|
|
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{Data: stats})
|
|
}
|
|
|
|
func parseStatsFilter(r *http.Request) (common.StatsFilter, error) {
|
|
sch := schema.NewDecoder()
|
|
filter := common.StatsFilter{}
|
|
|
|
sch.SetAliasTag("json")
|
|
err := sch.Decode(&filter, r.URL.Query())
|
|
if err != nil {
|
|
return common.StatsFilter{}, errors.WithStack(err)
|
|
}
|
|
|
|
err = validator.GetValidator().Struct(filter)
|
|
if err != nil {
|
|
return common.StatsFilter{}, errors.WithStack(err)
|
|
}
|
|
|
|
return filter, nil
|
|
}
|
|
|
|
func getEcusStats(conn clickhouse.ConnInterface, filter common.StatsFilter) ([]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),
|
|
"vins": "['" + strings.Join(filter.VINs, "','") + "']",
|
|
"dbcs": "['" + strings.Join(filter.DBCs, "','") + "']",
|
|
"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 ( 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 in {dbcs:Array(String)}
|
|
|
|
) as b
|
|
on a.Name=b.signal_name
|
|
|
|
where
|
|
a.Timestamp> (select max(Timestamp) from vehicle_signal where VIN in {vins:Array(String)}) - toIntervalHour({hours:UInt64})
|
|
and
|
|
a.VIN in {vins:Array(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 in {dbcs:Array(String)} and dbcm.dbc_hash in {dbcs:Array(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
|
|
}
|