feat(platform): expose vehicle service runtime health
This commit is contained in:
@@ -37,7 +37,9 @@ func NewServer(cfg config.Config) http.Handler {
|
|||||||
log.Printf("production mysql store enabled")
|
log.Printf("production mysql store enabled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api := platform.NewHandler(platform.NewService(store))
|
api := platform.NewHandler(platform.NewServiceWithRuntime(store, platform.RuntimeInfo{
|
||||||
|
RequestTimeoutMs: int(cfg.RequestTimeout / time.Millisecond),
|
||||||
|
}))
|
||||||
return withRequestTimeout(static.Handler(cfg.StaticDir, api), cfg.RequestTimeout)
|
return withRequestTimeout(static.Handler(cfg.StaticDir, api), cfg.RequestTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,6 +195,25 @@ func TestHandlerHistoryMileageQualityOps(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandlerOpsHealthIncludesVehicleServiceRuntime(t *testing.T) {
|
||||||
|
handler := NewHandler(NewServiceWithRuntime(NewMockStore(), RuntimeInfo{RequestTimeoutMs: 1500}))
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/ops/health", nil)
|
||||||
|
handler.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
var body struct {
|
||||||
|
Data OpsHealth `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||||
|
t.Fatalf("response JSON should decode: %v body=%s", err, rec.Body.String())
|
||||||
|
}
|
||||||
|
if body.Data.Runtime.RequestTimeoutMs != 1500 {
|
||||||
|
t.Fatalf("ops health should include request timeout runtime, got %+v body=%s", body.Data.Runtime, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandlerVehicleDataAPIsResolveVehicleKeyword(t *testing.T) {
|
func TestHandlerVehicleDataAPIsResolveVehicleKeyword(t *testing.T) {
|
||||||
handler := NewHandler(NewService(NewMockStore()))
|
handler := NewHandler(NewService(NewMockStore()))
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
|||||||
@@ -184,4 +184,9 @@ type OpsHealth struct {
|
|||||||
RedisOnlineKeys *int `json:"redisOnlineKeys"`
|
RedisOnlineKeys *int `json:"redisOnlineKeys"`
|
||||||
TDengineWritable bool `json:"tdengineWritable"`
|
TDengineWritable bool `json:"tdengineWritable"`
|
||||||
MySQLWritable bool `json:"mysqlWritable"`
|
MySQLWritable bool `json:"mysqlWritable"`
|
||||||
|
Runtime RuntimeInfo `json:"runtime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeInfo struct {
|
||||||
|
RequestTimeoutMs int `json:"requestTimeoutMs"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,12 +37,17 @@ type RawFrameQuery struct {
|
|||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store Store
|
store Store
|
||||||
|
runtime RuntimeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(store Store) *Service {
|
func NewService(store Store) *Service {
|
||||||
return &Service{store: store}
|
return &Service{store: store}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewServiceWithRuntime(store Store, runtime RuntimeInfo) *Service {
|
||||||
|
return &Service{store: store, runtime: runtime}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) DashboardSummary(ctx context.Context) (DashboardSummary, error) {
|
func (s *Service) DashboardSummary(ctx context.Context) (DashboardSummary, error) {
|
||||||
return s.store.DashboardSummary(ctx)
|
return s.store.DashboardSummary(ctx)
|
||||||
}
|
}
|
||||||
@@ -282,7 +287,12 @@ func (s *Service) QualityIssues(ctx context.Context, query url.Values) (Page[Qua
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) OpsHealth(ctx context.Context) (OpsHealth, error) {
|
func (s *Service) OpsHealth(ctx context.Context) (OpsHealth, error) {
|
||||||
return s.store.OpsHealth(ctx)
|
health, err := s.store.OpsHealth(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return OpsHealth{}, err
|
||||||
|
}
|
||||||
|
health.Runtime = s.runtime
|
||||||
|
return health, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) resolveVehicleQuery(ctx context.Context, query url.Values) (url.Values, error) {
|
func (s *Service) resolveVehicleQuery(ctx context.Context, query url.Values) (url.Values, error) {
|
||||||
|
|||||||
@@ -174,6 +174,11 @@ export interface OpsHealth {
|
|||||||
redisOnlineKeys: number | null;
|
redisOnlineKeys: number | null;
|
||||||
tdengineWritable: boolean;
|
tdengineWritable: boolean;
|
||||||
mysqlWritable: boolean;
|
mysqlWritable: boolean;
|
||||||
|
runtime: RuntimeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuntimeInfo {
|
||||||
|
requestTimeoutMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Page<T> {
|
export interface Page<T> {
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ function storageReadStatus(health: OpsHealth | null) {
|
|||||||
return health?.tdengineWritable && health.mysqlWritable ? 'ok' : 'error';
|
return health?.tdengineWritable && health.mysqlWritable ? 'ok' : 'error';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatRequestTimeout(health: OpsHealth | null) {
|
||||||
|
const value = health?.runtime?.requestTimeoutMs;
|
||||||
|
return value == null || value <= 0 ? '未限制' : `${value.toLocaleString()} ms`;
|
||||||
|
}
|
||||||
|
|
||||||
const emptySummary: QualitySummary = {
|
const emptySummary: QualitySummary = {
|
||||||
issueVehicleCount: 0,
|
issueVehicleCount: 0,
|
||||||
issueRecordCount: 0,
|
issueRecordCount: 0,
|
||||||
@@ -125,9 +130,10 @@ export function Quality({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={8}><Card bordered title="Kafka Lag">{formatLag(health?.kafkaLag)}</Card></Col>
|
<Col span={6}><Card bordered title="Kafka Lag">{formatLag(health?.kafkaLag)}</Card></Col>
|
||||||
<Col span={8}><Card bordered title="Redis 在线 Key">{formatLag(health?.redisOnlineKeys)}</Card></Col>
|
<Col span={6}><Card bordered title="Redis 在线 Key">{formatLag(health?.redisOnlineKeys)}</Card></Col>
|
||||||
<Col span={8}>
|
<Col span={6}><Card bordered title="BFF 请求超时">{formatRequestTimeout(health)}</Card></Col>
|
||||||
|
<Col span={6}>
|
||||||
<Card bordered title="存储读取">
|
<Card bordered title="存储读取">
|
||||||
<Tag color={statusColor[storageReadStatus(health)]}>{storageReadStatus(health) === 'ok' ? '正常' : '异常'}</Tag>
|
<Tag color={statusColor[storageReadStatus(health)]}>{storageReadStatus(health) === 'ok' ? '正常' : '异常'}</Tag>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user