fork of indigo with slightly nicer lexgen

goat relay host diff

Changed files
+141
cmd
goat
+141
cmd/goat/relay.go
··· 1 1 package main 2 2 3 3 import ( 4 + "context" 4 5 "encoding/json" 5 6 "fmt" 7 + "sort" 6 8 7 9 comatproto "github.com/bluesky-social/indigo/api/atproto" 8 10 "github.com/bluesky-social/indigo/atproto/syntax" ··· 92 94 }, 93 95 }, 94 96 Action: runRelayHostStatus, 97 + }, 98 + &cli.Command{ 99 + Name: "diff", 100 + Usage: "compare host set (and seq) between two relay instances", 101 + ArgsUsage: `<relay-one> <relay-two>`, 102 + Flags: []cli.Flag{ 103 + &cli.BoolFlag{ 104 + Name: "verbose", 105 + Usage: "print all hosts", 106 + }, 107 + &cli.IntFlag{ 108 + Name: "seq-slop", 109 + Value: 100, 110 + Usage: "sequence delta allowed as close enough", 111 + }, 112 + }, 113 + Action: runRelayHostDiff, 95 114 }, 96 115 }, 97 116 }, ··· 326 345 327 346 return nil 328 347 } 348 + 349 + type hostInfo struct { 350 + Hostname string 351 + Status string 352 + Seq int64 353 + } 354 + 355 + func fetchHosts(ctx context.Context, relayHost string) ([]hostInfo, error) { 356 + 357 + client := xrpc.Client{ 358 + Host: relayHost, 359 + } 360 + 361 + hosts := []hostInfo{} 362 + cursor := "" 363 + var size int64 = 500 364 + for { 365 + resp, err := comatproto.SyncListHosts(ctx, &client, cursor, size) 366 + if err != nil { 367 + return nil, err 368 + } 369 + 370 + for _, h := range resp.Hosts { 371 + if h.Status == nil || h.Seq == nil || *h.Seq <= 0 { 372 + continue 373 + } 374 + 375 + // TODO: only active or idle hosts? 376 + info := hostInfo{ 377 + Hostname: h.Hostname, 378 + Status: *h.Status, 379 + Seq: *h.Seq, 380 + } 381 + hosts = append(hosts, info) 382 + } 383 + 384 + if resp.Cursor == nil || *resp.Cursor == "" { 385 + break 386 + } 387 + cursor = *resp.Cursor 388 + } 389 + return hosts, nil 390 + } 391 + 392 + func runRelayHostDiff(cctx *cli.Context) error { 393 + ctx := cctx.Context 394 + verbose := cctx.Bool("verbose") 395 + seqSlop := cctx.Int64("seq-slop") 396 + 397 + if cctx.Args().Len() != 2 { 398 + return fmt.Errorf("expected two relay URLs are args") 399 + } 400 + 401 + urlOne := cctx.Args().Get(0) 402 + urlTwo := cctx.Args().Get(1) 403 + 404 + listOne, err := fetchHosts(ctx, urlOne) 405 + if err != nil { 406 + return err 407 + } 408 + listTwo, err := fetchHosts(ctx, urlTwo) 409 + if err != nil { 410 + return err 411 + } 412 + 413 + allHosts := make(map[string]bool) 414 + mapOne := make(map[string]hostInfo) 415 + for _, val := range listOne { 416 + allHosts[val.Hostname] = true 417 + mapOne[val.Hostname] = val 418 + } 419 + mapTwo := make(map[string]hostInfo) 420 + for _, val := range listTwo { 421 + allHosts[val.Hostname] = true 422 + mapTwo[val.Hostname] = val 423 + } 424 + 425 + names := []string{} 426 + for k, _ := range allHosts { 427 + names = append(names, k) 428 + } 429 + sort.Strings(names) 430 + 431 + for _, k := range names { 432 + one, okOne := mapOne[k] 433 + two, okTwo := mapTwo[k] 434 + if !okOne { 435 + if !verbose && two.Status != "active" { 436 + continue 437 + } 438 + fmt.Printf("%s\t\t%s/%d\tone-missing\n", k, two.Status, two.Seq) 439 + } else if !okTwo { 440 + if !verbose && one.Status != "active" { 441 + continue 442 + } 443 + fmt.Printf("%s\t%s/%d\t\ttwo-missing\n", k, one.Status, one.Seq) 444 + } else { 445 + status := "" 446 + if one.Status != two.Status { 447 + status = "diff" 448 + } else { 449 + delta := max(one.Seq, two.Seq) - min(one.Seq, two.Seq) 450 + if delta == 0 { 451 + status = "exact" 452 + if !verbose { 453 + continue 454 + } 455 + } else if delta < seqSlop { 456 + status = "close" 457 + if !verbose { 458 + continue 459 + } 460 + } else { 461 + status = fmt.Sprintf("delta=%d", delta) 462 + } 463 + } 464 + fmt.Printf("%s\t%s/%d\t%s/%d\t%s\n", k, one.Status, one.Seq, two.Status, two.Seq, status) 465 + } 466 + } 467 + 468 + return nil 469 + }