diff --git a/go.mod b/go.mod index 35dfc82..1779a3b 100644 --- a/go.mod +++ b/go.mod @@ -15,12 +15,14 @@ require ( github.com/go-openapi/strfmt v0.23.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/jedib0t/go-pretty/v6 v6.5.6 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.16.0 // indirect ) require ( diff --git a/go.sum b/go.sum index 4ae151d..c23aa60 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= +github.com/jedib0t/go-pretty/v6 v6.5.6 h1:nKXVLqPfAwY7sWcYXdNZZZ2fjqDpAtj9UeWupgfUxSg= +github.com/jedib0t/go-pretty/v6 v6.5.6/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= @@ -44,6 +46,8 @@ go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= diff --git a/leaderboard.go b/leaderboard.go index 5b5baca..c0c7711 100644 --- a/leaderboard.go +++ b/leaderboard.go @@ -6,20 +6,29 @@ import ( "fmt" "log" "log/slog" + "net/http" "os" "regexp" "strconv" "strings" "sync" + "sync/atomic" "time" "github.com/cli/go-gh/pkg/auth" "github.com/gofri/go-github-ratelimit/github_ratelimit" + "github.com/jedib0t/go-pretty/progress" "github.com/jedib0t/go-pretty/table" "github.com/google/go-github/v60/github" ) +var ( + amountOfRequests atomic.Uint64 + ratelimitRemaining atomic.Uint64 + ratelimitReset atomic.Pointer[github.Timestamp] +) + func fetchRepositories(client *github.Client, options *Options, clientNumber int, totalClients int, repos chan *github.Repository) { page := clientNumber for i := 0; true; i++ { @@ -56,6 +65,8 @@ func processPullRequest(client *github.Client, options *Options, repo *github.Re if err != nil { panic(err) } + ratelimitRemaining.Store(uint64(response.Rate.Remaining)) + ratelimitReset.Store(&response.Rate.Reset) if response.NextPage != 0 { log.Fatalf("Found to many reviews in pull request %s/%s#%d to handle\n", repo.GetOwner().GetLogin(), repo.GetName(), pr.GetNumber()) } @@ -83,6 +94,8 @@ func processPullRequest(client *github.Client, options *Options, repo *github.Re if err != nil { panic(err) } + ratelimitRemaining.Store(uint64(response.Rate.Remaining)) + ratelimitReset.Store(&response.Rate.Reset) for _, comment := range comments { if comment.GetCreatedAt().After(options.Since) { @@ -103,9 +116,11 @@ func processPullRequest(client *github.Client, options *Options, repo *github.Re } } -func processRepository(client *github.Client, options *Options, repo *github.Repository, stats chan *Stats) { +func processRepository(client *github.Client, pw progress.Writer, options *Options, repo *github.Repository, stats chan *Stats) { slog.Debug("Fetch pull requests", "repository", repo.GetName()) + page := 0 + var tracker progress.Tracker for { pullRequests, response, err := client.PullRequests.List(context.Background(), repo.GetOwner().GetLogin(), repo.GetName(), &github.PullRequestListOptions{ State: "all", @@ -118,6 +133,14 @@ func processRepository(client *github.Client, options *Options, repo *github.Rep if err != nil { panic(err) } + ratelimitRemaining.Store(uint64(response.Rate.Remaining)) + ratelimitReset.Store(&response.Rate.Reset) + + if page == 0 { + tracker = progress.Tracker{Message: repo.GetName(), Total: int64(response.LastPage)} + pw.AppendTracker(&tracker) + } + tracker.Increment(1) var wg sync.WaitGroup for _, pullRequest := range pullRequests { @@ -132,6 +155,8 @@ func processRepository(client *github.Client, options *Options, repo *github.Rep wg.Wait() if response.NextPage == 0 { + tracker.Increment(1) + tracker.MarkAsDone() return } page = response.NextPage @@ -146,7 +171,7 @@ type Stats struct { CommentLinesWritten int } -func processRepositories(client *github.Client, options *Options, repos chan *github.Repository, stats chan *Stats) { +func processRepositories(client *github.Client, pw progress.Writer, options *Options, repos chan *github.Repository, stats chan *Stats) { fmt.Printf("Processing data since %v matching repository name pattern %s\n", options.Since, options.NamePattern) var wg sync.WaitGroup @@ -159,7 +184,7 @@ func processRepositories(client *github.Client, options *Options, repos chan *gi wg.Add(1) go func(repo *github.Repository) { defer wg.Done() - processRepository(client, options, repo, stats) + processRepository(client, pw, options, repo, stats) }(repo) } } @@ -167,6 +192,18 @@ func processRepositories(client *github.Client, options *Options, repos chan *gi close(stats) } +type CountingRoundTripper struct { + Proxied http.RoundTripper +} + +func (crt CountingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) { + res, e = crt.Proxied.RoundTrip(req) + + amountOfRequests.Add(1) + + return res, e +} + func createClient() *github.Client { host, _ := auth.DefaultHost() authToken, _ := auth.TokenForHost(host) @@ -174,7 +211,8 @@ func createClient() *github.Client { log.Fatalf("authentication token not found for host %s", host) } - rateLimiter, err := github_ratelimit.NewRateLimitWaiterClient(nil) + roundTrip := CountingRoundTripper{http.DefaultTransport} + rateLimiter, err := github_ratelimit.NewRateLimitWaiterClient(roundTrip) if err != nil { panic(err) } @@ -287,6 +325,12 @@ func showResults(statsPerUser map[string]*Stats) { } func main() { + pw := progress.NewWriter() + pw.SetSortBy(progress.SortByPercentDsc) + pw.SetStyle(progress.StyleDefault) + pw.Style().Colors = progress.StyleColorsDefault + go pw.Render() + initializedLogger() options := parseCliArgs() @@ -297,9 +341,14 @@ func main() { go fetchAllRepositories(client, options, repos, 5) stats := make(chan *Stats, 128) - go processRepositories(client, options, repos, stats) + go processRepositories(client, pw, options, repos, stats) accumulated := accumulateStatsPerUser(stats) + // wait until progress bar rendering is done + for pw.LengthActive() != 0 { + } + fmt.Printf("%d requests sent to Github\n", amountOfRequests.Load()) + fmt.Printf("Rate Limit: remaining=%d reset=%v\n", ratelimitRemaining.Load(), ratelimitReset.Load()) showResults(accumulated) }