feat(platform): expose vehicle service runtime health

This commit is contained in:
lingniu
2026-07-04 01:02:07 +08:00
parent 377cfcdfca
commit 3e76210d7d
6 changed files with 53 additions and 6 deletions

View File

@@ -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)
} }

View File

@@ -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 {

View File

@@ -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"`
} }

View File

@@ -36,13 +36,18 @@ 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) {

View File

@@ -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> {

View File

@@ -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>