ghamma 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -1
- data/Gemfile.lock +12 -1
- data/README.md +31 -5
- data/exe/ghamma +2 -2
- data/lib/ghamma/cli/duration.rb +61 -0
- data/lib/ghamma/cli/version.rb +15 -0
- data/lib/ghamma/cli.rb +8 -75
- data/lib/ghamma/github_api_client.rb +61 -0
- data/lib/ghamma/version.rb +1 -1
- metadata +33 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26ed59a6dae9c41b8fe6a3aa7b24f7ba2cc430696acfa061094c2e1e690a437d
|
4
|
+
data.tar.gz: 7bc89adfb0d48a8c4cfed55c1633771fcf57889e56e67efcbe2cc4f5d4ab5d7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0df963608fd632710848105a954214aa5c6fbc33a7a789230d372567d0de534f5320e82ce2cd0d4b5854b960528faa5f1ca64962e2ed5c7f56219b19a0bfcb4
|
7
|
+
data.tar.gz: 7817262789b374aa634ac3c5087d88c3e9672bcb50b783928c2bf349461278c41da2b4b9702c062b4bed7116940b1eea4e584cd67f6ba72d1774a9286be53fcb
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
-
##
|
1
|
+
## Unreleased
|
2
|
+
|
3
|
+
## [0.3.0] - 2023-07-04
|
4
|
+
|
5
|
+
- Feature: Allow optionally specifying an output file for the CSV
|
6
|
+
- Change: Add ID of the workflow run to the output
|
7
|
+
- Feature: Support restricting workflow runs examined since a given date
|
8
|
+
- Feature: Fetch all the workflow runs, not just the first 100
|
9
|
+
- Breaking: Removed the `list-workflows` command and renamed the `duration-history` to just `duration`
|
10
|
+
- Breaking: The `duration` command now accepts the workflow file name (or ID) instead of the name
|
11
|
+
|
12
|
+
## [0.2.0] - 2023-06-01
|
13
|
+
|
14
|
+
- Breaking: Add commands to the CLI: `version`, `list-worklows`, `duration-history`
|
2
15
|
|
3
16
|
## [0.1.1] - 2023-06-01
|
4
17
|
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ghamma (0.
|
4
|
+
ghamma (0.3.0)
|
5
|
+
dry-cli
|
5
6
|
http
|
7
|
+
tty-progressbar
|
6
8
|
|
7
9
|
GEM
|
8
10
|
remote: https://rubygems.org/
|
@@ -12,6 +14,7 @@ GEM
|
|
12
14
|
ast (2.4.2)
|
13
15
|
domain_name (0.5.20190701)
|
14
16
|
unf (>= 0.0.5, < 1.0.0)
|
17
|
+
dry-cli (1.0.0)
|
15
18
|
ffi (1.15.5)
|
16
19
|
ffi-compiler (1.0.1)
|
17
20
|
ffi (>= 1.0.0)
|
@@ -66,8 +69,16 @@ GEM
|
|
66
69
|
standard-performance (1.0.1)
|
67
70
|
lint_roller (~> 1.0)
|
68
71
|
rubocop-performance (~> 1.16.0)
|
72
|
+
strings-ansi (0.2.0)
|
69
73
|
test-unit (3.5.9)
|
70
74
|
power_assert
|
75
|
+
tty-cursor (0.7.1)
|
76
|
+
tty-progressbar (0.18.2)
|
77
|
+
strings-ansi (~> 0.2)
|
78
|
+
tty-cursor (~> 0.7)
|
79
|
+
tty-screen (~> 0.8)
|
80
|
+
unicode-display_width (>= 1.6, < 3.0)
|
81
|
+
tty-screen (0.8.1)
|
71
82
|
unf (0.1.4)
|
72
83
|
unf_ext
|
73
84
|
unf_ext (0.0.8.2)
|
data/README.md
CHANGED
@@ -13,17 +13,43 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
13
13
|
|
14
14
|
## Usage
|
15
15
|
|
16
|
-
|
16
|
+
### Authentication
|
17
|
+
|
18
|
+
The Github workflows API requires an authenticated user, even for public repos.
|
19
|
+
To use this tool, you will need a
|
20
|
+
[Github API Token](https://docs.github.com/en/rest/overview/authenticating-to-the-rest-api#authenticating-with-a-personal-access-token)
|
21
|
+
with at least `read` access to the repo you want to look at.
|
22
|
+
|
23
|
+
Once you have that, make it available.
|
24
|
+
|
25
|
+
```
|
26
|
+
$ export GH_TOKEN="YOUR_API_TOKEN"
|
27
|
+
```
|
28
|
+
|
29
|
+
Then fetch the list of available workflows for the repo. The name will be necessary in the next step.
|
30
|
+
|
31
|
+
```
|
32
|
+
$ ghamma list-workflows tony-rowan ghamma
|
33
|
+
Found 1 workflows
|
34
|
+
Ruby
|
35
|
+
```
|
36
|
+
|
37
|
+
Then fetch a CSV of the durations of the latest workflow runs.
|
38
|
+
|
39
|
+
```
|
40
|
+
$ ghamma duration-history tony-rowan ghamma Ruby
|
41
|
+
Date,Duration
|
42
|
+
2023-06-01T15:44:49Z,21000
|
43
|
+
2023-06-01T15:37:21Z,17000
|
44
|
+
2023-06-01T15:35:16Z,23000
|
45
|
+
2023-06-01T15:31:33Z,19000
|
46
|
+
```
|
17
47
|
|
18
48
|
## Development
|
19
49
|
|
20
50
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests.
|
21
51
|
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
22
52
|
|
23
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update
|
24
|
-
the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the
|
25
|
-
version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
26
|
-
|
27
53
|
## Contributing
|
28
54
|
|
29
55
|
Bug reports and pull requests are welcome on GitHub at https://github.com/tony-rowan/ghamma. This project is
|
data/exe/ghamma
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
require "csv"
|
2
|
+
require "dry/cli"
|
3
|
+
require "http"
|
4
|
+
require "tty/progressbar"
|
5
|
+
|
6
|
+
require_relative "../github_api_client"
|
7
|
+
|
8
|
+
module Ghamma
|
9
|
+
module CLI
|
10
|
+
class Duration < Dry::CLI::Command
|
11
|
+
desc "Extract the durations of successful runs of a workflow over time"
|
12
|
+
|
13
|
+
example [
|
14
|
+
"tony-rowan ghamma main.yml" \
|
15
|
+
" # Prints durations of all workflow runs of main.yml to STDOUT",
|
16
|
+
"tony-rowan ghamma main.yml --since='2023-07-01' --output='/tmp/main.csv'" \
|
17
|
+
" # Saves durations of all workflow runs of main.yml since 2023-07-01 to /tmp/main.csv"
|
18
|
+
]
|
19
|
+
|
20
|
+
argument :owner, required: true, desc: "The user or organisation to whom the repo belongs"
|
21
|
+
argument :repo, required: true, desc: "The repo to which the workflow belongs"
|
22
|
+
argument :workflow, required: true, desc: "The filename for the desired workflow, e.g. main.yml"
|
23
|
+
|
24
|
+
option :since, desc: "Optionally restrict workflows to those created since this date"
|
25
|
+
option :output, desc: "Optional file to which to output results, defaults to STDOUT"
|
26
|
+
|
27
|
+
def call(owner:, repo:, workflow:, since: nil, output: nil)
|
28
|
+
workflow_statement = "Fetching the durations of all workflow runs of #{workflow}"
|
29
|
+
since_statment = "since #{since}" if since
|
30
|
+
|
31
|
+
puts [workflow_statement, since_statment].join(" ")
|
32
|
+
|
33
|
+
progressbar = TTY::ProgressBar.new("Fetching workflow runs [:bar] :current/:total ETA: :eta", total: nil)
|
34
|
+
|
35
|
+
durations = GithubApiClient.new(owner: owner, repo: repo, token: ENV["GH_TOKEN"])
|
36
|
+
.fetch_workflow_duration_history(workflow, since, progressbar)
|
37
|
+
|
38
|
+
progressbar.finish
|
39
|
+
|
40
|
+
puts "Generating output"
|
41
|
+
|
42
|
+
durations_output = CSV.generate do |csv|
|
43
|
+
csv << ["ID", "Date", "Duration"]
|
44
|
+
durations.each do |duration|
|
45
|
+
csv << duration
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if output
|
50
|
+
File.write(output, durations_output)
|
51
|
+
else
|
52
|
+
puts
|
53
|
+
puts durations_output
|
54
|
+
puts
|
55
|
+
end
|
56
|
+
|
57
|
+
puts "Done"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/ghamma/cli.rb
CHANGED
@@ -1,80 +1,13 @@
|
|
1
|
-
require "
|
2
|
-
require "http"
|
1
|
+
require "dry/cli"
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
BASE_URL = "https://api.github.com".freeze
|
8
|
-
OWNER = ARGV[0].freeze
|
9
|
-
REPOSITORY = ARGV[1].freeze
|
10
|
-
|
11
|
-
def api_url(resource)
|
12
|
-
"#{BASE_URL}#{resource}"
|
13
|
-
end
|
14
|
-
|
15
|
-
def get(resource, params = {})
|
16
|
-
response = HTTP
|
17
|
-
.auth("Bearer #{ENV["GH_TOKEN"]}")
|
18
|
-
.get(api_url(resource), params: params)
|
19
|
-
.body
|
20
|
-
JSON.parse(response)
|
21
|
-
end
|
22
|
-
|
23
|
-
def fetch_workflows
|
24
|
-
response = get "/repos/#{OWNER}/#{REPOSITORY}/actions/workflows"
|
25
|
-
response["workflows"].map { |json| {id: json["id"], name: json["name"]} }.tap do |workflows|
|
26
|
-
puts "Found #{workflows.size} workflows"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def fetch_workflows_with_runs
|
31
|
-
fetch_workflows.map do |workflow|
|
32
|
-
response = get "/repos/#{OWNER}/#{REPOSITORY}/actions/workflows/#{workflow[:id]}/runs",
|
33
|
-
{per_page: 100, status: "success", exclude_pull_requests: true, created: ">2023-05-01"}
|
34
|
-
puts "Fetched runs for #{workflow[:name]}"
|
3
|
+
require_relative "./cli/version"
|
4
|
+
require_relative "./cli/duration"
|
35
5
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
id: workflow[:id],
|
40
|
-
name: workflow[:name],
|
41
|
-
runs: response["workflow_runs"].map { |json| {id: json["id"], date: json["created_at"]} }
|
42
|
-
}
|
43
|
-
end.compact
|
44
|
-
end
|
45
|
-
|
46
|
-
def fetch_workflow_duration_metrics
|
47
|
-
fetch_workflows_with_runs.map do |workflow|
|
48
|
-
runs_with_timing = workflow[:runs].map do |workflow_run|
|
49
|
-
response = get "/repos/#{OWNER}/#{REPOSITORY}/actions/runs/#{workflow_run[:id]}/timing"
|
50
|
-
|
51
|
-
{
|
52
|
-
duration: response["run_duration_ms"],
|
53
|
-
date: workflow_run[:date]
|
54
|
-
}
|
55
|
-
end
|
56
|
-
|
57
|
-
puts "Fetched timings for #{workflow[:name]}"
|
58
|
-
|
59
|
-
{
|
60
|
-
name: workflow[:name],
|
61
|
-
runs: runs_with_timing
|
62
|
-
}
|
63
|
-
end
|
64
|
-
end
|
6
|
+
module Ghamma
|
7
|
+
module CLI
|
8
|
+
extend Dry::CLI::Registry
|
65
9
|
|
66
|
-
|
67
|
-
|
68
|
-
puts workflow_metrics[:name]
|
69
|
-
metrics_table = CSV.generate do |csv|
|
70
|
-
csv << ["Date", "Duration"]
|
71
|
-
workflow_metrics[:runs].each do |metric_datum|
|
72
|
-
csv << [metric_datum[:date], metric_datum[:duration]]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
puts metrics_table
|
76
|
-
puts
|
77
|
-
end
|
78
|
-
end
|
10
|
+
register "version", Version, aliases: ["v", "-v", "--version"]
|
11
|
+
register "duration", Duration
|
79
12
|
end
|
80
13
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "http"
|
2
|
+
|
3
|
+
module Ghamma
|
4
|
+
class GithubApiClient
|
5
|
+
BASE_URL = "https://api.github.com".freeze
|
6
|
+
|
7
|
+
def initialize(owner:, repo:, token:)
|
8
|
+
@repo_base_url = "#{BASE_URL}/repos/#{owner}/#{repo}"
|
9
|
+
@authorization_header = "Bearer #{token}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch_workflow_duration_history(workflow_id, since, progressbar)
|
13
|
+
pages = 2 # enough to do the first loop
|
14
|
+
page = 1
|
15
|
+
timings = []
|
16
|
+
|
17
|
+
while page <= pages
|
18
|
+
since_param = ">#{since}" if since
|
19
|
+
|
20
|
+
response = get(
|
21
|
+
"/workflows/#{workflow_id}/runs",
|
22
|
+
{page: page, per_page: 100, status: "success", exclude_pull_requests: true, created: since_param}.compact
|
23
|
+
)
|
24
|
+
|
25
|
+
total_count = response["total_count"]
|
26
|
+
progressbar.update(total: total_count)
|
27
|
+
pages = total_count / 100
|
28
|
+
page += 1
|
29
|
+
|
30
|
+
response.fetch("workflow_runs").each do |workflow_run|
|
31
|
+
run_timing = get("/runs/#{workflow_run["id"]}/timing")
|
32
|
+
|
33
|
+
timings << [
|
34
|
+
workflow_run["id"],
|
35
|
+
workflow_run["created_at"],
|
36
|
+
run_timing["run_duration_ms"]
|
37
|
+
]
|
38
|
+
|
39
|
+
progressbar.advance
|
40
|
+
|
41
|
+
sleep 0.1 # Try to keep under the Github rate limit
|
42
|
+
end
|
43
|
+
|
44
|
+
sleep 1 # Try to keep under the Github rate limit
|
45
|
+
end
|
46
|
+
|
47
|
+
timings
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :repo_base_url, :authorization_header
|
53
|
+
|
54
|
+
def get(resource, params = {})
|
55
|
+
HTTP
|
56
|
+
.auth(authorization_header)
|
57
|
+
.get("#{repo_base_url}/actions#{resource}", params: params)
|
58
|
+
.parse
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/ghamma/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ghamma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Rowan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-cli
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: http
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -24,6 +38,20 @@ dependencies:
|
|
24
38
|
- - ">="
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: tty-progressbar
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rake
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -85,6 +113,9 @@ files:
|
|
85
113
|
- exe/ghamma
|
86
114
|
- lib/ghamma.rb
|
87
115
|
- lib/ghamma/cli.rb
|
116
|
+
- lib/ghamma/cli/duration.rb
|
117
|
+
- lib/ghamma/cli/version.rb
|
118
|
+
- lib/ghamma/github_api_client.rb
|
88
119
|
- lib/ghamma/version.rb
|
89
120
|
- sig/ghamma.rbs
|
90
121
|
homepage: https://github.com/tony-rowan/ghamma
|