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  }