github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/event/event.go (about) 1 package event 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/google/uuid" 12 "github.com/pkg/errors" 13 14 "github.com/machinefi/w3bstream/pkg/depends/conf/logger" 15 "github.com/machinefi/w3bstream/pkg/depends/kit/logr" 16 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx/builder" 17 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx/datatypes" 18 "github.com/machinefi/w3bstream/pkg/depends/kit/statusx" 19 "github.com/machinefi/w3bstream/pkg/enums" 20 "github.com/machinefi/w3bstream/pkg/errors/status" 21 "github.com/machinefi/w3bstream/pkg/models" 22 "github.com/machinefi/w3bstream/pkg/modules/metrics" 23 "github.com/machinefi/w3bstream/pkg/modules/publisher" 24 "github.com/machinefi/w3bstream/pkg/modules/strategy" 25 "github.com/machinefi/w3bstream/pkg/modules/trafficlimit" 26 "github.com/machinefi/w3bstream/pkg/modules/vm" 27 "github.com/machinefi/w3bstream/pkg/types" 28 ) 29 30 // HandleEvent support other module call 31 // TODO the full project info is not in context so query and set here. this impl 32 // is for support other module, which is temporary. 33 // And it will be deprecated when rpc/http is ready 34 func HandleEvent(ctx context.Context, tpe string, data []byte) (*EventRsp, error) { 35 prj := &models.Project{ProjectName: models.ProjectName{ 36 Name: types.MustProjectFromContext(ctx).Name, 37 }} 38 39 err := prj.FetchByName(types.MustMgrDBExecutorFromContext(ctx)) 40 if err != nil { 41 return nil, err 42 } 43 ctx = types.WithProject(ctx, prj) 44 45 ctx = types.WithPublisher(ctx, &models.Publisher{ 46 PrimaryID: datatypes.PrimaryID{ID: 0}, 47 RelProject: models.RelProject{ProjectID: prj.ProjectID}, 48 RelPublisher: models.RelPublisher{PublisherID: 0}, 49 PublisherInfo: models.PublisherInfo{ 50 Key: "w3b_monitor", 51 Name: "w3b_monitor", 52 }, 53 }) 54 55 return Create(ctx, &EventReq{ 56 From: enums.EVENT_SOURCE__MONITOR, 57 Channel: prj.Name, 58 EventType: tpe, 59 EventID: uuid.NewString() + "_monitor", 60 Timestamp: time.Now().UTC().UnixMilli(), 61 Payload: *bytes.NewBuffer(data), 62 }) 63 } 64 65 func SyncHandleEvent(ctx context.Context, tpe string, data []byte) (*EventRsp, error) { 66 rsp, err := HandleEvent(ctx, tpe, data) 67 if err != nil { 68 return nil, err 69 } 70 events := make([]models.Event, 0) 71 72 d := types.MustMgrDBExecutorFromContext(ctx) 73 m := &models.Event{} 74 t := d.T(m) 75 76 sig := make(chan struct{}) 77 time.Sleep(time.Second * 2) 78 79 go func() { 80 for { 81 completed := true 82 err := d.QueryAndScan(builder.Select(nil).From(t, builder.Where(m.ColEventID().Eq(rsp.EventID))), &events) 83 if err != nil { 84 goto RETRY 85 } 86 for _, ev := range events { 87 if ev.Stage != enums.EVENT_STAGE__COMPLETED { 88 completed = false 89 break 90 } 91 } 92 if !completed { 93 goto RETRY 94 } 95 sig <- struct{}{} 96 return 97 RETRY: 98 time.Sleep(time.Second * 3) 99 } 100 }() 101 102 <-sig 103 msg := make([]string, 0, len(events)) 104 for _, ev := range events { 105 if ev.ResultCode != 0 { 106 msg = append(msg, fmt.Sprintf("event: %s result code: %d msg: %s", ev.EventID, ev.ResultCode, ev.Error)) 107 } 108 } 109 if len(msg) == 0 { 110 return nil, errors.New(strings.Join(msg, "|")) 111 } 112 return rsp, nil 113 } 114 115 func OnEvent(ctx context.Context, data []byte) (ret []*Result) { 116 ctx, l := logr.Start(ctx, "event.OnEvent", "event_id", types.MustEventIDFromContext(ctx)) 117 defer l.End() 118 119 var ( 120 r = types.MustStrategyResultsFromContext(ctx) 121 results = make(chan *Result, len(r)) 122 ) 123 124 wg := &sync.WaitGroup{} 125 for _, v := range r { 126 l = l.WithValues( 127 "prj", v.ProjectName, 128 "app", v.AppletName, 129 ) 130 ins := vm.GetConsumer(v.InstanceID) 131 if ins == nil { 132 l.Warn(errors.New("instance not running")) 133 results <- &Result{ 134 AppletName: v.AppletName, 135 InstanceID: v.InstanceID, 136 Handler: v.Handler, 137 ReturnValue: nil, 138 ReturnCode: -1, 139 Error: status.InstanceNotRunning.Key(), 140 } 141 continue 142 } 143 144 wg.Add(1) 145 go func(v *types.StrategyResult) { 146 defer wg.Done() 147 l.WithValues("ins", v.InstanceID, "hdl", v.Handler, "tpe", v.EventType).Info("handled") 148 rv := ins.HandleEvent(ctx, v.Handler, v.EventType, data) 149 results <- &Result{ 150 AppletName: v.AppletName, 151 InstanceID: v.InstanceID, 152 Handler: v.Handler, 153 ReturnValue: nil, 154 ReturnCode: int(rv.Code), 155 Error: rv.ErrMsg, 156 } 157 }(v) 158 159 go func(v *types.StrategyResult) { 160 if v.AutoCollect == datatypes.BooleanValue(true) { 161 // metrics.GeoCollect(ctx, data) 162 } 163 }(v) 164 } 165 wg.Wait() 166 close(results) 167 168 for v := range results { 169 if v == nil { 170 continue 171 } 172 ret = append(ret, v) 173 } 174 return ret 175 } 176 177 func Create(ctx context.Context, r *EventReq) (*EventRsp, error) { 178 _, l := logr.Start(ctx, "event.Create") 179 defer l.End() 180 181 prj := types.MustProjectFromContext(ctx) 182 pub := types.MustPublisherFromContext(ctx) 183 184 if err := trafficlimit.TrafficLimit(ctx, prj.ProjectID, enums.TRAFFIC_LIMIT_TYPE__EVENT); err != nil { 185 return nil, err 186 } 187 188 strategies, err := strategy.FilterByProjectAndEvent(ctx, prj.ProjectID, r.EventType) 189 if err != nil { 190 return nil, err 191 } 192 193 events := make([]*models.Event, 0, len(strategies)) 194 results := make([]*Result, 0, len(strategies)) 195 for i, s := range strategies { 196 if state, _ := vm.GetInstanceState(s.InstanceID); state != enums.INSTANCE_STATE__STARTED { 197 continue 198 } 199 events = append(events, &models.Event{ 200 EventContext: models.EventContext{ 201 Stage: enums.EVENT_STAGE__RECEIVED, 202 From: r.From, 203 AccountID: prj.AccountID, 204 ProjectID: prj.ProjectID, 205 ProjectName: prj.Name, 206 PublisherID: pub.PublisherID, 207 PublisherKey: pub.Key, 208 EventID: r.EventID, 209 Index: i, 210 EventType: r.EventType, 211 InstanceID: s.InstanceID, 212 Handler: s.Handler, 213 Input: r.Payload.Bytes(), 214 Total: len(strategies), 215 PublishedAt: r.Timestamp, 216 ReceivedAt: time.Now().UTC().UnixMilli(), 217 AutoCollect: s.AutoCollect, 218 }, 219 }) 220 results = append(results, &Result{ 221 AppletName: s.AppletName, 222 InstanceID: s.InstanceID, 223 Handler: s.Handler, 224 }) 225 } 226 if err = models.BatchCreateEvents(types.MustMgrDBExecutorFromContext(ctx), events...); err != nil { 227 return nil, status.DatabaseError.StatusErr(). 228 WithDesc(fmt.Sprintf("batch create event failed: %v", err)) 229 } 230 return &EventRsp{ 231 Channel: r.Channel, 232 PublisherID: pub.PublisherID, 233 PublisherKey: pub.Key, 234 EventID: r.EventID, 235 Timestamp: time.Now().UTC().UnixMilli(), 236 Results: results, 237 }, nil 238 } 239 240 func BatchCreate(ctx context.Context, reqs DataPushReqs) (DataPushRsps, error) { 241 _, l := logr.Start(ctx, "event.BatchCreate") 242 defer l.End() 243 244 ret := make(DataPushRsps, 0, len(reqs)) 245 prj := types.MustProjectFromContext(ctx) 246 for i, v := range reqs { 247 pub, err := publisher.CreateIfNotExist(ctx, &publisher.CreateReq{ 248 Name: v.DeviceID, 249 Key: v.DeviceID, 250 }) 251 if err != nil { 252 return nil, err 253 } 254 ctx = types.WithPublisher(ctx, pub) 255 r := &EventReq{ 256 Channel: prj.Name, 257 EventType: v.EventType, 258 EventID: uuid.NewString() + "_event2", 259 Timestamp: v.Timestamp, 260 Payload: *bytes.NewBuffer([]byte(v.Payload)), 261 } 262 res, err := Create(ctx, r) 263 if err != nil { 264 if se, ok := statusx.IsStatusErr(err); ok && se.Key == status.TrafficLimitExceededFailed.Key() { 265 break 266 } 267 return nil, err 268 } 269 ret = append(ret, &DataPushRsp{ 270 Index: i, 271 Results: res.Results, 272 }) 273 } 274 return ret, nil 275 } 276 277 func handle(ctx context.Context, batch int64, prj types.SFID) int { 278 ctx, l := logger.NewSpanContext(ctx, "event.handle") 279 defer l.End() 280 281 d := types.MustMgrDBExecutorFromContext(ctx) 282 283 evs, err := models.BatchFetchLastUnhandledEvents(ctx, d, batch, prj) 284 if err != nil { 285 l.Error(err) 286 return 0 287 } 288 if len(evs) == 0 { 289 return 0 290 } 291 l.WithValues("batch", len(evs)).Info("") 292 293 for _, v := range evs { 294 v.HandledAt = time.Now().UnixMilli() 295 v.Stage = enums.EVENT_STAGE__HANDLED 296 297 if err = v.UpdateByIDWithFVs(d, builder.FieldValues{ 298 v.FieldStage(): v.Stage, 299 v.FieldHandledAt(): v.HandledAt, 300 }); err != nil { 301 l.WithValues("evt", v.EventID).Error(err) 302 continue 303 } 304 305 ins := vm.GetConsumer(v.InstanceID) 306 if ins == nil { 307 v.CompletedAt = time.Now().UTC().UnixMilli() 308 v.ResultCode = -1 309 v.Error = status.InstanceNotRunning.Key() + "_nil" 310 } else { 311 res := ins.HandleEvent(types.WithEventID(ctx, v.EventID), v.Handler, v.EventType, v.Input) 312 v.CompletedAt = time.Now().UTC().UnixMilli() 313 v.ResultCode = int32(res.Code) 314 v.Error = res.ErrMsg 315 } 316 v.Stage = enums.EVENT_STAGE__COMPLETED 317 l := l.WithValues( 318 "evt", v.EventID, 319 "ins", v.InstanceID, 320 "hdl", v.Handler, 321 "res_code", v.ResultCode, 322 ) 323 if v.Error != "" { 324 l.Error(errors.New(v.Error)) 325 } 326 327 if err = v.UpdateByIDWithFVs(d, builder.FieldValues{ 328 v.FieldStage(): v.Stage, 329 v.FieldHandledAt(): v.HandledAt, 330 v.FieldCompletedAt(): v.CompletedAt, 331 v.FieldError(): v.Error, 332 v.FieldResultCode(): v.ResultCode, 333 }); err != nil { 334 l.Error(err) 335 } 336 go func(ctx context.Context, v *models.Event) { 337 metrics.EventMetricsInc(ctx, v) 338 if v.AutoCollect == datatypes.TRUE { 339 metrics.GeoCollect(ctx, v) 340 } 341 }(ctx, v) 342 } 343 return len(evs) 344 } 345 346 func NewDefaultEventHandleScheduler(prj types.SFID) *EventHandleScheduler { 347 return NewEventHandleScheduler(time.Second*10, 100, prj) 348 } 349 350 func NewEventHandleScheduler(d time.Duration, batch int64, prj types.SFID) *EventHandleScheduler { 351 return &EventHandleScheduler{ 352 prj: prj, 353 batch: batch, 354 du: d, 355 } 356 } 357 358 type EventHandleScheduler struct { 359 prj types.SFID // prj project sfid 360 batch int64 // batch fetch 361 du time.Duration // du interval 362 } 363 364 func (s *EventHandleScheduler) Run(ctx context.Context) { 365 for { 366 if handled := handle(ctx, s.batch, s.prj); handled == 0 { 367 time.Sleep(s.du) 368 } 369 } 370 } 371 372 func NewDefaultEventCleanupScheduler() *EventCleanupScheduler { 373 return NewEventCleanupScheduler(time.Hour, 3*time.Hour*24) 374 } 375 376 func NewEventCleanupScheduler(d time.Duration, keep time.Duration) *EventCleanupScheduler { 377 return &EventCleanupScheduler{ 378 d: d, 379 keep: keep, 380 } 381 } 382 383 type EventCleanupScheduler struct { 384 d time.Duration 385 keep time.Duration 386 } 387 388 func (s *EventCleanupScheduler) Run(ctx context.Context) { 389 ticker := time.NewTicker(s.d) 390 m := &models.Event{} 391 d := types.MustMgrDBExecutorFromContext(ctx) 392 t := d.T(m) 393 for { 394 _, l := logger.NewSpanContext(ctx, "event.cleanup") 395 396 ts := time.Now().UTC().UnixMilli() - s.keep.Milliseconds() 397 _, err := d.Exec(builder.Delete().From(t, builder.Where(m.ColReceivedAt().Lt(ts)))) 398 if err != nil { 399 l.Error(errors.Wrap(err, "event cleanup")) 400 l.End() 401 } else { 402 l.Info("event cleanup") 403 } 404 l.End() 405 <-ticker.C 406 } 407 }