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 }