this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

goat relay host diff (#1052)

little helper for comparing the host lists of two distinct relay
instances.

sequences are always counting up, and won't be exact matches! this
implementation allows "slop" between the sequences.

an alternative implementation would be do do multiple interleaved
enumeration requests for each relay, then see if there is overlap
between the ranges; if there is overlap, presumably they are both
relatively in sync.

authored by

bnewbold and committed by
GitHub
786a781b d6fa2c23

+142
+1
Makefile
··· 16 16 17 17 .PHONY: build 18 18 build: ## Build all executables 19 + go build ./cmd/goat 19 20 go build ./cmd/gosky 20 21 go build ./cmd/bigsky 21 22 go build ./cmd/relay
+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-A-url> <relay-B-url>`, 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\tA-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\tB-missing\n", k, one.Status, one.Seq) 444 + } else { 445 + status := "" 446 + if one.Status != two.Status { 447 + status = "diff-status" 448 + } else { 449 + delta := max(one.Seq, two.Seq) - min(one.Seq, two.Seq) 450 + if delta == 0 { 451 + status = "sync" 452 + if !verbose { 453 + continue 454 + } 455 + } else if delta < seqSlop { 456 + status = "nearly" 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 + }