code.vegaprotocol.io/vega@v0.79.0/vegatools/stream/stream.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package stream
    17  
    18  import (
    19  	"context"
    20  	"flag"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"os/signal"
    25  	"strconv"
    26  	"sync"
    27  	"syscall"
    28  	"time"
    29  
    30  	api "code.vegaprotocol.io/vega/protos/vega/api/v1"
    31  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    32  
    33  	"github.com/golang/protobuf/jsonpb"
    34  	"google.golang.org/grpc"
    35  )
    36  
    37  func connect(ctx context.Context,
    38  	batchSize uint,
    39  	party, market, serverAddr string, types []string,
    40  ) (conn *grpc.ClientConn, stream api.CoreService_ObserveEventBusClient, err error) {
    41  	defer func() {
    42  		if err != nil {
    43  			_ = conn.Close()
    44  		}
    45  	}()
    46  
    47  	conn, err = grpc.Dial(serverAddr, grpc.WithInsecure())
    48  	if err != nil {
    49  		return
    50  	}
    51  
    52  	stream, err = api.NewCoreServiceClient(conn).ObserveEventBus(ctx)
    53  	if err != nil {
    54  		return
    55  	}
    56  
    57  	busEventTypes, err := typesToBETypes(types)
    58  	if err != nil {
    59  		return
    60  	}
    61  
    62  	req := &api.ObserveEventBusRequest{
    63  		MarketId:  market,
    64  		PartyId:   party,
    65  		BatchSize: int64(batchSize),
    66  		Type:      busEventTypes,
    67  	}
    68  
    69  	if err = stream.Send(req); err != nil {
    70  		err = fmt.Errorf("error when sending initial message in stream: %w", err)
    71  		return
    72  	}
    73  
    74  	return conn, stream, nil
    75  }
    76  
    77  func typesToBETypes(types []string) ([]eventspb.BusEventType, error) {
    78  	if len(types) == 0 {
    79  		return []eventspb.BusEventType{
    80  			eventspb.BusEventType_BUS_EVENT_TYPE_ALL,
    81  		}, nil
    82  	}
    83  
    84  	dedup := map[string]struct{}{}
    85  	beTypes := make([]eventspb.BusEventType, 0, len(types))
    86  
    87  	for _, t := range types {
    88  		// check if t is numeric:
    89  		if n, err := strconv.ParseInt(t, 10, 32); err != nil && n > 0 {
    90  			// it was numeric, and we found the name to match
    91  			if ts, ok := eventspb.BusEventType_name[int32(n)]; ok {
    92  				t = ts
    93  			}
    94  		}
    95  		// deduplicate
    96  		if _, ok := dedup[t]; ok {
    97  			continue
    98  		}
    99  
   100  		dedup[t] = struct{}{}
   101  		// now get the constant value and add it to the slice if possible
   102  		if i, ok := eventspb.BusEventType_value[t]; ok {
   103  			bet := eventspb.BusEventType(i)
   104  			if bet == eventspb.BusEventType_BUS_EVENT_TYPE_ALL {
   105  				return typesToBETypes(nil)
   106  			}
   107  
   108  			beTypes = append(beTypes, bet)
   109  		} else {
   110  			// We could not match the event string to the list defined in the proto file so stop now
   111  			// so the user does not think everything is fine
   112  			return nil, fmt.Errorf("no such event %s", t)
   113  		}
   114  	}
   115  
   116  	if len(beTypes) == 0 {
   117  		// default to ALL
   118  		return typesToBETypes(nil)
   119  	}
   120  
   121  	return beTypes, nil
   122  }
   123  
   124  // ReadEvents reads all the events from the server.
   125  func ReadEvents(
   126  	ctx context.Context,
   127  	cancel context.CancelFunc,
   128  	wg *sync.WaitGroup,
   129  	batchSize uint,
   130  	party, market, serverAddr string,
   131  	handleEvent func(event *eventspb.BusEvent),
   132  	reconnect bool,
   133  	types []string,
   134  ) error {
   135  	if len(types) == 0 || (len(types) == 1 && len(types[0]) == 0) {
   136  		types = nil
   137  	}
   138  
   139  	conn, stream, err := connect(ctx, batchSize, party, market, serverAddr, types)
   140  	if err != nil {
   141  		return fmt.Errorf("failed to connect to event stream: %w", err)
   142  	}
   143  
   144  	poll := &api.ObserveEventBusRequest{
   145  		BatchSize: int64(batchSize),
   146  	}
   147  
   148  	wg.Add(1)
   149  
   150  	go func() {
   151  		defer func() {
   152  			wg.Done()
   153  			cancel()
   154  			_ = conn.Close()
   155  			_ = stream.CloseSend()
   156  		}()
   157  
   158  		for {
   159  			for {
   160  				o, err := stream.Recv()
   161  				if err == io.EOF {
   162  					fmt.Printf("stream closed by server err=%v", err)
   163  					break
   164  				}
   165  
   166  				if err != nil {
   167  					fmt.Printf("stream closed err=%v", err)
   168  					break
   169  				}
   170  
   171  				for _, e := range o.Events {
   172  					handleEvent(e)
   173  				}
   174  
   175  				if batchSize > 0 {
   176  					if err := stream.SendMsg(poll); err != nil {
   177  						fmt.Printf("failed to poll next event batch err=%v", err)
   178  						return
   179  					}
   180  				}
   181  			}
   182  
   183  			if reconnect {
   184  				// Keep waiting and retrying until we reconnect
   185  				for {
   186  					select {
   187  					case <-ctx.Done():
   188  						return
   189  					default:
   190  						time.Sleep(time.Second * 5)
   191  						fmt.Printf("Attempting to reconnect to the node")
   192  						conn, stream, err = connect(ctx, batchSize, party, market, serverAddr, types)
   193  						if err == nil {
   194  							break
   195  						}
   196  					}
   197  					if err == nil {
   198  						break
   199  					}
   200  				}
   201  			} else {
   202  				break
   203  			}
   204  		}
   205  	}()
   206  
   207  	return nil
   208  }
   209  
   210  // Run is the main function of `stream` package.
   211  func Run(
   212  	batchSize uint,
   213  	party, market, serverAddr, logFormat string,
   214  	reconnect bool,
   215  	types []string,
   216  ) error {
   217  	flag.Parse()
   218  
   219  	if len(serverAddr) <= 0 {
   220  		return fmt.Errorf("error: missing grpc server address")
   221  	}
   222  
   223  	handleEvent, err := NewLogEventToConsoleFn(logFormat)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	ctx, cancel := context.WithCancel(context.Background())
   229  	defer cancel()
   230  
   231  	wg := sync.WaitGroup{}
   232  
   233  	if err := ReadEvents(ctx, cancel, &wg, batchSize, party, market, serverAddr, handleEvent, reconnect, types); err != nil {
   234  		return fmt.Errorf("error when starting the stream: %v", err)
   235  	}
   236  
   237  	WaitSig(ctx, cancel)
   238  	wg.Wait()
   239  
   240  	return nil
   241  }
   242  
   243  // NewLogEventToConsoleFn returns a common logging function for use across tools that deal with events.
   244  func NewLogEventToConsoleFn(logFormat string) (func(e *eventspb.BusEvent), error) {
   245  	var printEvent func(string)
   246  	switch logFormat {
   247  	case "raw":
   248  		printEvent = func(event string) { fmt.Printf("%v\n", event) }
   249  	case "text":
   250  		printEvent = func(event string) {
   251  			fmt.Printf("%v;%v", time.Now().UTC().Format(time.RFC3339Nano), event)
   252  		}
   253  	case "json":
   254  		printEvent = func(event string) {
   255  			fmt.Printf("{\"time\":\"%v\",%v\n", time.Now().UTC().Format(time.RFC3339Nano), event[1:])
   256  		}
   257  	default:
   258  		return nil, fmt.Errorf("error: unknown log-format: \"%v\". Allowed values: raw, text, json", logFormat)
   259  	}
   260  
   261  	m := jsonpb.Marshaler{}
   262  	handleEvent := func(e *eventspb.BusEvent) {
   263  		estr, err := m.MarshalToString(e)
   264  		if err != nil {
   265  			fmt.Printf("unable to marshal event err=%v", err)
   266  		}
   267  		printEvent(estr)
   268  	}
   269  
   270  	return handleEvent, nil
   271  }
   272  
   273  // WaitSig waits until Terminate or interrupt event is received.
   274  func WaitSig(ctx context.Context, cancel func()) {
   275  	gracefulStop := make(chan os.Signal, 1)
   276  
   277  	signal.Notify(gracefulStop, syscall.SIGTERM)
   278  	signal.Notify(gracefulStop, syscall.SIGINT)
   279  
   280  	select {
   281  	case sig := <-gracefulStop:
   282  		fmt.Printf("Caught signal name=%v", sig)
   283  		fmt.Printf("closing client connections")
   284  		cancel()
   285  	case <-ctx.Done():
   286  		return
   287  	}
   288  }