@cratis/pi 7.101.0 → 7.103.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.
- package/package/corpus/harnesses/copilot/instructions/github-actions.instructions.md +17 -0
- package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
- package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
- package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
- package/package/corpus/hooks/README.md +11 -1
- package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
- package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
- package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
- package/package/corpus/rules/github-actions.md +17 -0
- package/package/corpus/rules/profiles.md +10 -1
- package/package/corpus/rules/pull-requests.md +2 -0
- package/package/corpus/rules/secrets.md +4 -0
- package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
- package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
- package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
- package/package/corpus/skills/binlog-generation/LICENSE +23 -0
- package/package/corpus/skills/binlog-generation/NOTICE +22 -0
- package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
- package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
- package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
- package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
- package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
- package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
- package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
- package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
- package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
- package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
- package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
- package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
- package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
- package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
- package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/LICENSE +2 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/SKILL.md +201 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/build-once-and-shard.md +158 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/caching.md +86 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/flaky-and-hung-jobs.md +70 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/publish-pipelines.md +67 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/required-checks-and-path-filters.md +117 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/runner-sizing-and-images.md +65 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/security-and-pinning.md +73 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/sources.md +33 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/test-setup-cost.md +82 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/ci-timings.sh +356 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1003.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1007.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1010.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/runs.json +102 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/LICENSE +2 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/SKILL.md +244 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/docker-images.md +86 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/dotnet-nuget.md +87 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/go-jvm-python-elixir.md +83 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/npm-yarn.md +78 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/other-environments.md +60 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/worked-examples.md +97 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/cratis-local-packages.sh +778 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/npm-restore-check.cjs +109 -0
- package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
- package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
- package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
- package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
- package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
- package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
- package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
- package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
- package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
- package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
- package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
- package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
- package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
- package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
- package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
- package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
- package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
- package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
- package/package/corpus/skills/dump-collect/LICENSE +23 -0
- package/package/corpus/skills/dump-collect/NOTICE +40 -0
- package/package/corpus/skills/dump-collect/SKILL.md +97 -0
- package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
- package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
- package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
- package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
- package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
- package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
- package/package/corpus/skills/extension-points/LICENSE +23 -0
- package/package/corpus/skills/extension-points/NOTICE +18 -0
- package/package/corpus/skills/extension-points/SKILL.md +248 -0
- package/package/corpus/skills/filter-syntax/LICENSE +23 -0
- package/package/corpus/skills/filter-syntax/NOTICE +17 -0
- package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
- package/package/corpus/skills/including-generated-files/LICENSE +23 -0
- package/package/corpus/skills/including-generated-files/NOTICE +17 -0
- package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
- package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
- package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
- package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
- package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
- package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
- package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
- package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
- package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
- package/package/corpus/skills/msbuild/LICENSE +23 -0
- package/package/corpus/skills/msbuild/NOTICE +55 -0
- package/package/corpus/skills/msbuild/SKILL.md +117 -0
- package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
- package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
- package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
- package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
- package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
- package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
- package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
- package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
- package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
- package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
- package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
- package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
- package/package/corpus/skills/orleans/LICENSE +21 -0
- package/package/corpus/skills/orleans/NOTICE +64 -0
- package/package/corpus/skills/orleans/SKILL.md +140 -0
- package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
- package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
- package/package/corpus/skills/orleans/references/examples.md +66 -0
- package/package/corpus/skills/orleans/references/grain-api.md +784 -0
- package/package/corpus/skills/orleans/references/grains.md +75 -0
- package/package/corpus/skills/orleans/references/hosting.md +71 -0
- package/package/corpus/skills/orleans/references/implementation.md +591 -0
- package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
- package/package/corpus/skills/orleans/references/patterns.md +811 -0
- package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
- package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
- package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
- package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
- package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
- package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
- package/package/corpus/skills/platform-detection/LICENSE +23 -0
- package/package/corpus/skills/platform-detection/NOTICE +17 -0
- package/package/corpus/skills/platform-detection/SKILL.md +230 -0
- package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
- package/package/corpus/skills/profiling/LICENSE +21 -0
- package/package/corpus/skills/profiling/NOTICE +50 -0
- package/package/corpus/skills/profiling/SKILL.md +144 -0
- package/package/corpus/skills/profiling/references/commands.md +439 -0
- package/package/corpus/skills/profiling/references/patterns.md +586 -0
- package/package/corpus/skills/profiling/references/profiling.md +190 -0
- package/package/corpus/skills/run-tests/LICENSE +23 -0
- package/package/corpus/skills/run-tests/NOTICE +19 -0
- package/package/corpus/skills/run-tests/SKILL.md +305 -0
- package/package/corpus/skills/target-authoring/LICENSE +23 -0
- package/package/corpus/skills/target-authoring/NOTICE +17 -0
- package/package/corpus/skills/target-authoring/SKILL.md +165 -0
- package/package/corpus/skills/vectorization/LICENSE +23 -0
- package/package/corpus/skills/vectorization/NOTICE +17 -0
- package/package/corpus/skills/vectorization/SKILL.md +189 -0
- package/package/profile-catalog.json +87 -1
- package/package.json +3 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Test setup cost and where to test a guarantee
|
|
2
|
+
|
|
3
|
+
Use this when a test lane is slow but the runner's per-test times look small, when
|
|
4
|
+
a suite re-tests a dependency, or when compile errors surface late.
|
|
5
|
+
|
|
6
|
+
## Contents
|
|
7
|
+
|
|
8
|
+
- Time hidden outside per-test timing
|
|
9
|
+
- Profile the test host
|
|
10
|
+
- Fix setup upstream
|
|
11
|
+
- Test each guarantee where it can break
|
|
12
|
+
- Surface compile errors early
|
|
13
|
+
- Count compiles per push
|
|
14
|
+
|
|
15
|
+
## Time hidden outside per-test timing
|
|
16
|
+
|
|
17
|
+
Compare the test runner's reported total test time with the wall time of the test
|
|
18
|
+
step. A large gap means time is spent outside what the runner attributes to tests:
|
|
19
|
+
per-class or per-fixture setup (scenario, host or container construction), discovery,
|
|
20
|
+
or process start. Specification frameworks that build a context in a base-class
|
|
21
|
+
setup often charge it to no test at all.
|
|
22
|
+
|
|
23
|
+
1. Read both numbers from the same run: the runner's summary and the step duration
|
|
24
|
+
from `ci-timings.sh`'s slowest-steps table.
|
|
25
|
+
2. If setup dominates, sharding only divides the waste across more jobs. Fix the
|
|
26
|
+
setup first.
|
|
27
|
+
|
|
28
|
+
## Profile the test host
|
|
29
|
+
|
|
30
|
+
Measure before guessing, with the language's profiler on the test process:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
dotnet tool install --global dotnet-trace
|
|
34
|
+
dotnet-trace collect --format speedscope -- dotnet test Project.Specs --no-build --configuration Release
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Read the inclusive time of the setup methods. Pass means the profile names the
|
|
38
|
+
dominant constructor or discovery call; a flat profile means the cost is elsewhere
|
|
39
|
+
(I/O, container start, process start).
|
|
40
|
+
|
|
41
|
+
## Fix setup upstream
|
|
42
|
+
|
|
43
|
+
Per-test construction of identical, immutable data is the usual cause. Fix it where
|
|
44
|
+
the data originates rather than skipping specifications:
|
|
45
|
+
|
|
46
|
+
- Build shared metadata (type discovery, schemas, registrations) once per process
|
|
47
|
+
and reuse it across scenarios.
|
|
48
|
+
- Share expensive generators and caches instead of creating one per instance.
|
|
49
|
+
- Cache reflection results that are recomputed on every call.
|
|
50
|
+
- Use a prebuilt service collection as a template and copy it per test.
|
|
51
|
+
- Do not share mutable state between tests to save time; share only what is immutable.
|
|
52
|
+
|
|
53
|
+
A fix belongs in the library that owns the setup, not in each consuming repository.
|
|
54
|
+
|
|
55
|
+
## Test each guarantee where it can break
|
|
56
|
+
|
|
57
|
+
- A suite that tests a dependency's semantics belongs in that dependency's own
|
|
58
|
+
repository. In a consumer, run it only when the dependency pin changes, or nightly.
|
|
59
|
+
- Check a wire or API shape with a committed snapshot and a diff, not a live
|
|
60
|
+
integration run.
|
|
61
|
+
- Mixed-version or upgrade matrices run nightly or before a release, not on every
|
|
62
|
+
pull request.
|
|
63
|
+
- Where a behavior is only meaningful in a deployed environment, prefer a
|
|
64
|
+
post-deploy synthetic check plus unit specifications and contract snapshots over
|
|
65
|
+
a pull-request integration environment.
|
|
66
|
+
- Never remove the only check of a required guarantee; relocate it and say where it
|
|
67
|
+
now runs.
|
|
68
|
+
|
|
69
|
+
## Surface compile errors early
|
|
70
|
+
|
|
71
|
+
A pull request that fails to compile should fail in the first minutes, not after a
|
|
72
|
+
long test leg. Make a fast build or lint job a `needs` of the long legs, and keep
|
|
73
|
+
it short enough that it is not itself the critical path.
|
|
74
|
+
|
|
75
|
+
## Count compiles per push
|
|
76
|
+
|
|
77
|
+
Count how many times one push compiles the same code across all jobs and
|
|
78
|
+
workflows (`dotnet build` or `dotnet test` without `--no-build` in the step list).
|
|
79
|
+
Each extra compile is avoidable: build once, reuse the output (see
|
|
80
|
+
`build-once-and-shard.md`). Analyzers can roughly double compile time; run them once
|
|
81
|
+
in a parallel lint job and disable them in the shared build, while keeping the
|
|
82
|
+
strictest configuration that consumers enforce somewhere on the required path.
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copyright (c) Cratis. All rights reserved.
|
|
3
|
+
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
4
|
+
#
|
|
5
|
+
# Summarize the timing, failure and queue behavior of one GitHub Actions workflow
|
|
6
|
+
# from its recent runs. Read-only; uses gh, jq and POSIX utilities.
|
|
7
|
+
#
|
|
8
|
+
# Exit codes: 0 ok, 2 usage error, 3 no runs or no data, 4 missing gh/jq or gh auth failure.
|
|
9
|
+
|
|
10
|
+
set -u
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<'USAGE'
|
|
14
|
+
Usage: ci-timings.sh --repo OWNER/REPO --workflow FILE_OR_NAME [options]
|
|
15
|
+
ci-timings.sh --self-test
|
|
16
|
+
ci-timings.sh --help
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--repo OWNER/REPO Repository to analyze (required).
|
|
20
|
+
--workflow FILE_OR_NAME Workflow file name (ci.yml) or display name (required).
|
|
21
|
+
--limit N Runs to list, most recent first (default 50).
|
|
22
|
+
--jobs N Most recent successful first-attempt runs to fetch job
|
|
23
|
+
and step timings for (default 10).
|
|
24
|
+
--branch B Only runs on branch B.
|
|
25
|
+
--event E Only runs triggered by event E (pull_request, push, ...).
|
|
26
|
+
--self-test Run the analysis on bundled fixtures; needs no network or gh.
|
|
27
|
+
|
|
28
|
+
Prints a markdown report on stdout: conclusion counts, cancel/fail/rerun rates,
|
|
29
|
+
wall-clock median and p90, per-run queue, execution span and runner-minutes,
|
|
30
|
+
the slowest jobs and steps, and a classification hint.
|
|
31
|
+
|
|
32
|
+
Percentiles take the lower nearest rank: sorted[floor((n-1)*p)].
|
|
33
|
+
Exit codes: 0 ok, 2 usage error, 3 no runs or no data, 4 missing gh/jq or gh auth failure.
|
|
34
|
+
USAGE
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
die() { # die CODE MESSAGE
|
|
38
|
+
code=$1
|
|
39
|
+
shift
|
|
40
|
+
printf 'ci-timings: %s\n' "$*" >&2
|
|
41
|
+
exit "$code"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
45
|
+
|
|
46
|
+
# Selects the runs whose job timings are fetched: successful first attempts, newest first.
|
|
47
|
+
read -r -d '' SAMPLE_JQ <<'JQ' || true
|
|
48
|
+
map(select(.status == "completed" and .conclusion == "success" and .attempt == 1))
|
|
49
|
+
| sort_by(.createdAt) | reverse | .[0:$n] | map(.databaseId)
|
|
50
|
+
JQ
|
|
51
|
+
|
|
52
|
+
# Inputs: --slurpfile runs (gh run list JSON), --slurpfile jobs ([{id, jobs: [API job objects]}]),
|
|
53
|
+
# --arg repo, --arg workflow, --arg filters, --argjson njobs.
|
|
54
|
+
read -r -d '' REPORT_JQ <<'JQ' || true
|
|
55
|
+
def ts: fromdateiso8601;
|
|
56
|
+
def pct($p): sort as $s | if ($s | length) == 0 then null else $s[((($s | length) - 1) * $p) | floor] end;
|
|
57
|
+
def f1: if . == null then "n/a" else ((. * 10 | round) as $r | "\($r / 10 | floor).\($r % 10)") end;
|
|
58
|
+
def mins: if . == null then null else . / 60 end;
|
|
59
|
+
def rate($a; $b): if $b == 0 then 0 else $a / $b * 100 end;
|
|
60
|
+
def dur($o): (($o.completed_at | ts) - ($o.started_at | ts));
|
|
61
|
+
def usable: select(.started_at != null and .completed_at != null and .conclusion != "skipped");
|
|
62
|
+
|
|
63
|
+
$runs[0] as $all
|
|
64
|
+
| ($all | map(select(.status == "completed" and .conclusion != "skipped"))) as $done
|
|
65
|
+
| ($done | map(select(.conclusion == "success"))) as $succ
|
|
66
|
+
| ($done | map(select(.conclusion == "failure"))) as $fail
|
|
67
|
+
| ($done | map(select(.conclusion == "cancelled"))) as $canc
|
|
68
|
+
| ($done | map(select(.attempt > 1))) as $rerun
|
|
69
|
+
| ($done | map(select((.conclusion == "success" or .conclusion == "failure") and .attempt > 1)) | length) as $rerunExcluded
|
|
70
|
+
| ($done | map(select((.conclusion == "success" or .conclusion == "failure") and .attempt == 1)
|
|
71
|
+
| ((.updatedAt | ts) - (.createdAt | ts)))) as $walls
|
|
72
|
+
| (rate($fail | length; $done | length)) as $failP
|
|
73
|
+
| (rate($canc | length; $done | length)) as $cancP
|
|
74
|
+
| (rate($rerun | length; $done | length)) as $rerunP
|
|
75
|
+
| ($all | map({id: .databaseId, created: (.createdAt | ts), wall: ((.updatedAt | ts) - (.createdAt | ts))})) as $runIndex
|
|
76
|
+
| ($jobs[0]
|
|
77
|
+
| map(. as $e
|
|
78
|
+
| ($runIndex | map(select(.id == $e.id)) | first) as $r
|
|
79
|
+
| select($r != null)
|
|
80
|
+
| ($e.jobs | map(usable)) as $j
|
|
81
|
+
| select(($j | length) > 0)
|
|
82
|
+
| {id: $e.id, wall: $r.wall, jobs: $j,
|
|
83
|
+
count: ($j | length),
|
|
84
|
+
first: (($j | map(.started_at | ts) | min) - $r.created),
|
|
85
|
+
maxq: ($j | map(((.started_at | ts) - ((.created_at // .started_at) | ts))) | max),
|
|
86
|
+
sumq: ($j | map(((.started_at | ts) - ((.created_at // .started_at) | ts))) | add),
|
|
87
|
+
span: (($j | map(.completed_at | ts) | max) - ($j | map(.started_at | ts) | min)),
|
|
88
|
+
runner: ($j | map(dur(.)) | add)})) as $sample
|
|
89
|
+
| ($sample | map(.jobs | length) | add // 0) as $njobsTotal
|
|
90
|
+
| ($sample | map(.jobs[] | (.steps // [])[] | select(.started_at != null and .completed_at != null and .conclusion != "skipped")) | length) as $nsteps
|
|
91
|
+
| ($sample | map(.maxq / .wall * 100 | select(. != null))) as $shares
|
|
92
|
+
| (if ($shares | length) == 0 then null else ($shares | pct(0.5)) end) as $qshare
|
|
93
|
+
| ([ (if ($failP >= 15 or $rerunP >= 15) then "failure-bound" else empty end),
|
|
94
|
+
(if $cancP >= 20 then "cancel-bound" else empty end),
|
|
95
|
+
(if ($qshare != null and $qshare >= 25) then "queue-bound" else empty end) ]) as $flags
|
|
96
|
+
| (if ($flags | length) == 0 then "execution-bound" else $flags[0] end) as $primary
|
|
97
|
+
| (if ($flags | length) > 1 then " (also: " + ($flags[1:] | join(", ")) + ")" else "" end) as $also
|
|
98
|
+
| "@@COUNTS \($done | length) \($walls | length) \($sample | length) \($nsteps)",
|
|
99
|
+
"# CI timings: \($repo) / \($workflow)",
|
|
100
|
+
"",
|
|
101
|
+
"Sample: \($all | length) runs listed\($filters), \($done | length) completed, \($all | map(select(.status != "completed")) | length) not finished, \($all | map(select(.conclusion == "skipped")) | length) skipped.",
|
|
102
|
+
"Analyzed: \($done | length) completed runs, \($walls | length) first-attempt runs for wall clock, \($sample | length) sampled runs with \($njobsTotal) jobs and \($nsteps) steps.",
|
|
103
|
+
(if ($done | length) < 10 then "Small sample: fewer than 10 completed runs; treat every figure as indicative." else empty end),
|
|
104
|
+
"",
|
|
105
|
+
"| Conclusion | Runs |",
|
|
106
|
+
"|---|---|",
|
|
107
|
+
($all | map({k: (if .status == "completed" then .conclusion else .status end)})
|
|
108
|
+
| group_by(.k) | map({k: .[0].k, n: length}) | sort_by([-.n, .k])
|
|
109
|
+
| .[] | "| \(.k) | \(.n) |"),
|
|
110
|
+
"",
|
|
111
|
+
"Rates over \($done | length) completed runs: cancelled \($cancP | f1)%, failed \($failP | f1)%, rerun (attempt > 1) \($rerunP | f1)%.",
|
|
112
|
+
"Wall clock (success and failure, first attempts only, \($walls | length) runs; \($rerunExcluded) rerun runs excluded): median \($walls | pct(0.5) | mins | f1) min, p90 \($walls | pct(0.9) | mins | f1) min.",
|
|
113
|
+
"",
|
|
114
|
+
(
|
|
115
|
+
"## Per run (\($sample | length) most recent successful first-attempt runs)",
|
|
116
|
+
"",
|
|
117
|
+
"Wall is run creation to last update. First start wait is run creation to the first job start. Max job queue is the longest job wait for a runner. Execution span is first job start to last job end. Runner-min is the sum of job durations.",
|
|
118
|
+
"",
|
|
119
|
+
"| Run | Wall min | First start wait min | Max job queue min | Sum job queue min | Execution span min | Runner-min | Jobs |",
|
|
120
|
+
"|---|---|---|---|---|---|---|---|",
|
|
121
|
+
($sample | .[] | "| \(.id) | \(.wall | mins | f1) | \(.first | mins | f1) | \(.maxq | mins | f1) | \(.sumq | mins | f1) | \(.span | mins | f1) | \(.runner | mins | f1) | \(.count) |"),
|
|
122
|
+
"| median | \($sample | map(.wall) | pct(0.5) | mins | f1) | \($sample | map(.first) | pct(0.5) | mins | f1) | \($sample | map(.maxq) | pct(0.5) | mins | f1) | \($sample | map(.sumq) | pct(0.5) | mins | f1) | \($sample | map(.span) | pct(0.5) | mins | f1) | \($sample | map(.runner) | pct(0.5) | mins | f1) | \($sample | map(.count) | pct(0.5)) |",
|
|
123
|
+
"",
|
|
124
|
+
"## Slowest jobs (median minutes across the sampled runs)",
|
|
125
|
+
"",
|
|
126
|
+
"| Job | Median min | Runs |",
|
|
127
|
+
"|---|---|---|",
|
|
128
|
+
($sample | map(.jobs[] | {k: .name, d: dur(.)}) | group_by(.k)
|
|
129
|
+
| map({k: .[0].k, m: (map(.d) | pct(0.5)), n: length}) | sort_by([-.m, .k]) | .[0:8]
|
|
130
|
+
| .[] | "| \(.k) | \(.m | mins | f1) | \(.n) |"),
|
|
131
|
+
"",
|
|
132
|
+
"## Slowest steps (median minutes across the sampled runs)",
|
|
133
|
+
"",
|
|
134
|
+
"| Job / step | Median min | Runs |",
|
|
135
|
+
"|---|---|---|",
|
|
136
|
+
($sample | map(.jobs[] | .name as $jn | (.steps // [])[]
|
|
137
|
+
| select(.started_at != null and .completed_at != null and .conclusion != "skipped")
|
|
138
|
+
| {k: "\($jn) / \(.name)", d: dur(.)})
|
|
139
|
+
| group_by(.k) | map({k: .[0].k, m: (map(.d) | pct(0.5)), n: length}) | sort_by([-.m, .k]) | .[0:8]
|
|
140
|
+
| .[] | "| \(.k) | \(.m | mins | f1) | \(.n) |")
|
|
141
|
+
),
|
|
142
|
+
"",
|
|
143
|
+
"## Classification",
|
|
144
|
+
"",
|
|
145
|
+
"Classification: \($primary)\($also) (queue share \(if $qshare == null then "n/a" else ($qshare | f1) + "%" end), failed \($failP | f1)%, cancelled \($cancP | f1)%, rerun \($rerunP | f1)%).",
|
|
146
|
+
"Hint only: thresholds are failure-bound at failed or rerun >= 15%, cancel-bound at cancelled >= 20%, queue-bound at median max-job-queue share of wall >= 25%, otherwise execution-bound. Confirm on the critical path of the required checks."
|
|
147
|
+
JQ
|
|
148
|
+
|
|
149
|
+
# report RUNS_JSON JOBS_JSON REPO WORKFLOW FILTERS NJOBS
|
|
150
|
+
# Every analyzed population must be non-empty; otherwise exit 3 with the reason on stderr.
|
|
151
|
+
report() {
|
|
152
|
+
local count out counts done_n walls_n sample_n steps_n why
|
|
153
|
+
count=$(jq 'length' "$1") || return 4
|
|
154
|
+
if [ "$count" = "0" ]; then
|
|
155
|
+
printf 'ci-timings: no runs found for %s / %s%s\n' "$3" "$4" "$5" >&2
|
|
156
|
+
return 3
|
|
157
|
+
fi
|
|
158
|
+
out=$(jq -n -r --slurpfile runs "$1" --slurpfile jobs "$2" \
|
|
159
|
+
--arg repo "$3" --arg workflow "$4" --arg filters "$5" --argjson njobs "$6" \
|
|
160
|
+
"$REPORT_JQ") || return 4
|
|
161
|
+
counts=$(printf '%s\n' "$out" | head -n 1)
|
|
162
|
+
read -r _ done_n walls_n sample_n steps_n <<EOF_COUNTS
|
|
163
|
+
$counts
|
|
164
|
+
EOF_COUNTS
|
|
165
|
+
why=""
|
|
166
|
+
if [ "$done_n" = "0" ]; then
|
|
167
|
+
why="$count runs found but none completed (all unfinished or skipped)"
|
|
168
|
+
elif [ "$walls_n" = "0" ]; then
|
|
169
|
+
why="no first-attempt run ended in success or failure (all cancelled, rerun or skipped)"
|
|
170
|
+
elif [ "$sample_n" = "0" ]; then
|
|
171
|
+
why="no successful first-attempt run has usable job timestamps; job, queue and runner-minute figures cannot be computed"
|
|
172
|
+
elif [ "$steps_n" = "0" ]; then
|
|
173
|
+
why="the sampled jobs carry no step timings"
|
|
174
|
+
fi
|
|
175
|
+
if [ -n "$why" ]; then
|
|
176
|
+
printf 'ci-timings: no data for %s / %s%s: %s. Widen --limit, drop --branch/--event, or pick another workflow.\n' "$3" "$4" "$5" "$why" >&2
|
|
177
|
+
return 3
|
|
178
|
+
fi
|
|
179
|
+
printf '%s\n' "$out" | tail -n +2
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
sample_ids() { # sample_ids RUNS_JSON N
|
|
183
|
+
jq -r --argjson n "$2" "$SAMPLE_JQ | .[]" "$1"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
expect_exit3() { # expect_exit3 LABEL RUNS JOBS (uses and updates failures of self_test)
|
|
187
|
+
local out rc
|
|
188
|
+
out=$(report "$2" "$3" "example/repo" "ci.yml" "" 3 2>/dev/null)
|
|
189
|
+
rc=$?
|
|
190
|
+
if [ "$rc" -ne 3 ] || [ -n "$out" ]; then
|
|
191
|
+
printf 'FAIL %s: expected exit 3 and empty stdout, got exit %s\n' "$1" "$rc" >&2
|
|
192
|
+
failures=$((failures + 1))
|
|
193
|
+
fi
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
self_test() {
|
|
197
|
+
local fx="$SCRIPT_DIR/fixtures" tmp out rc ids line
|
|
198
|
+
failures=0
|
|
199
|
+
command -v jq >/dev/null 2>&1 || die 4 "jq is required"
|
|
200
|
+
tmp=$(mktemp -d) || die 4 "mktemp failed"
|
|
201
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
202
|
+
|
|
203
|
+
ids=$(sample_ids "$fx/runs.json" 3 | tr '\n' ' ')
|
|
204
|
+
if [ "$ids" != "1010 1007 1003 " ]; then
|
|
205
|
+
printf 'FAIL sample selection: expected "1010 1007 1003 " got "%s"\n' "$ids" >&2
|
|
206
|
+
failures=$((failures + 1))
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
: >"$tmp/jobs.ndjson"
|
|
210
|
+
for id in $ids; do
|
|
211
|
+
jq -n --argjson id "$id" --slurpfile j "$fx/jobs/$id.json" '{id: $id, jobs: $j[0]}' >>"$tmp/jobs.ndjson"
|
|
212
|
+
done
|
|
213
|
+
jq -s '.' "$tmp/jobs.ndjson" >"$tmp/jobs.json"
|
|
214
|
+
|
|
215
|
+
out=$(report "$fx/runs.json" "$tmp/jobs.json" "example/repo" "ci.yml" "" 3)
|
|
216
|
+
rc=$?
|
|
217
|
+
if [ "$rc" -ne 0 ]; then
|
|
218
|
+
printf 'FAIL report exited %s\n' "$rc" >&2
|
|
219
|
+
failures=$((failures + 1))
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
while IFS= read -r line; do
|
|
223
|
+
[ -z "$line" ] && continue
|
|
224
|
+
if ! printf '%s\n' "$out" | grep -F -x -q -- "$line"; then
|
|
225
|
+
printf 'FAIL missing expected line: %s\n' "$line" >&2
|
|
226
|
+
failures=$((failures + 1))
|
|
227
|
+
fi
|
|
228
|
+
done <<'EXPECTED'
|
|
229
|
+
Sample: 10 runs listed, 8 completed, 1 not finished, 1 skipped.
|
|
230
|
+
Analyzed: 8 completed runs, 6 first-attempt runs for wall clock, 3 sampled runs with 9 jobs and 21 steps.
|
|
231
|
+
| success | 6 |
|
|
232
|
+
| cancelled | 1 |
|
|
233
|
+
| failure | 1 |
|
|
234
|
+
| in_progress | 1 |
|
|
235
|
+
| skipped | 1 |
|
|
236
|
+
Rates over 8 completed runs: cancelled 12.5%, failed 12.5%, rerun (attempt > 1) 12.5%.
|
|
237
|
+
Wall clock (success and failure, first attempts only, 6 runs; 1 rerun runs excluded): median 15.0 min, p90 25.0 min.
|
|
238
|
+
| 1010 | 25.0 | 1.2 | 3.0 | 5.0 | 11.0 | 11.0 | 3 |
|
|
239
|
+
| 1007 | 15.0 | 1.2 | 3.0 | 5.0 | 11.0 | 11.0 | 3 |
|
|
240
|
+
| 1003 | 30.0 | 1.2 | 3.0 | 5.0 | 16.0 | 16.0 | 3 |
|
|
241
|
+
| median | 25.0 | 1.2 | 3.0 | 5.0 | 11.0 | 11.0 | 3 |
|
|
242
|
+
| build | 5.0 | 3 |
|
|
243
|
+
| test | 5.0 | 3 |
|
|
244
|
+
| lint | 1.0 | 3 |
|
|
245
|
+
| test / dotnet test | 4.7 | 3 |
|
|
246
|
+
| build / dotnet build | 4.5 | 3 |
|
|
247
|
+
| lint / eslint | 0.8 | 3 |
|
|
248
|
+
Classification: execution-bound (queue share 12.0%, failed 12.5%, cancelled 12.5%, rerun 12.5%).
|
|
249
|
+
EXPECTED
|
|
250
|
+
|
|
251
|
+
# A rerun that ended cancelled is not excluded from wall clock; only success or failure reruns are.
|
|
252
|
+
jq 'map(if .databaseId == 1005 then .attempt = 2 else . end)' "$fx/runs.json" >"$tmp/rerun-cancelled.json"
|
|
253
|
+
out=$(report "$tmp/rerun-cancelled.json" "$tmp/jobs.json" "example/repo" "ci.yml" "" 3)
|
|
254
|
+
if ! printf '%s\n' "$out" | grep -F -x -q -- 'Wall clock (success and failure, first attempts only, 6 runs; 1 rerun runs excluded): median 15.0 min, p90 25.0 min.'; then
|
|
255
|
+
printf 'FAIL rerun exclusion count: %s\n' "$(printf '%s\n' "$out" | grep -F 'Wall clock')" >&2
|
|
256
|
+
failures=$((failures + 1))
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
# A skipped job must not be counted as a job.
|
|
260
|
+
if printf '%s\n' "$out" | grep -F -q '| deploy |'; then
|
|
261
|
+
printf 'FAIL skipped job appeared in the report\n' >&2
|
|
262
|
+
failures=$((failures + 1))
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
# Non-vacuity: every empty population must exit 3 and print nothing on stdout.
|
|
266
|
+
printf '[]\n' >"$tmp/empty.json"
|
|
267
|
+
jq 'map(.conclusion = "cancelled")' "$fx/runs.json" >"$tmp/cancelled.json"
|
|
268
|
+
jq 'map(.jobs |= map(.started_at = null))' "$tmp/jobs.json" >"$tmp/jobs-untimed.json"
|
|
269
|
+
jq 'map(.jobs |= map(.steps = []))' "$tmp/jobs.json" >"$tmp/jobs-nosteps.json"
|
|
270
|
+
expect_exit3 "empty run list" "$tmp/empty.json" "$tmp/jobs.json"
|
|
271
|
+
expect_exit3 "all runs cancelled" "$tmp/cancelled.json" "$tmp/jobs.json"
|
|
272
|
+
expect_exit3 "no job data" "$fx/runs.json" "$tmp/empty.json"
|
|
273
|
+
expect_exit3 "jobs without timestamps" "$fx/runs.json" "$tmp/jobs-untimed.json"
|
|
274
|
+
expect_exit3 "jobs without steps" "$fx/runs.json" "$tmp/jobs-nosteps.json"
|
|
275
|
+
|
|
276
|
+
if [ "$failures" -ne 0 ]; then
|
|
277
|
+
printf 'ci-timings self-test: %s failure(s)\n' "$failures" >&2
|
|
278
|
+
exit 1
|
|
279
|
+
fi
|
|
280
|
+
printf 'ci-timings self-test: ok\n'
|
|
281
|
+
exit 0
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
is_uint() { case "$1" in '' | *[!0-9]*) return 1 ;; *) [ "$1" -gt 0 ] ;; esac }
|
|
285
|
+
|
|
286
|
+
REPO=""
|
|
287
|
+
WORKFLOW=""
|
|
288
|
+
LIMIT=50
|
|
289
|
+
JOBS=10
|
|
290
|
+
BRANCH=""
|
|
291
|
+
EVENT=""
|
|
292
|
+
SELF_TEST=0
|
|
293
|
+
|
|
294
|
+
while [ $# -gt 0 ]; do
|
|
295
|
+
case "$1" in
|
|
296
|
+
--help | -h) usage; exit 0 ;;
|
|
297
|
+
--self-test) SELF_TEST=1; shift ;;
|
|
298
|
+
--repo | --workflow | --limit | --jobs | --branch | --event)
|
|
299
|
+
[ $# -ge 2 ] || { usage >&2; die 2 "option $1 needs a value"; }
|
|
300
|
+
case "$1" in
|
|
301
|
+
--repo) REPO=$2 ;;
|
|
302
|
+
--workflow) WORKFLOW=$2 ;;
|
|
303
|
+
--limit) LIMIT=$2 ;;
|
|
304
|
+
--jobs) JOBS=$2 ;;
|
|
305
|
+
--branch) BRANCH=$2 ;;
|
|
306
|
+
--event) EVENT=$2 ;;
|
|
307
|
+
esac
|
|
308
|
+
shift 2
|
|
309
|
+
;;
|
|
310
|
+
*) usage >&2; die 2 "unknown argument: $1" ;;
|
|
311
|
+
esac
|
|
312
|
+
done
|
|
313
|
+
|
|
314
|
+
if [ "$SELF_TEST" -eq 1 ]; then
|
|
315
|
+
self_test
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
[ -n "$REPO" ] || { usage >&2; die 2 "--repo is required"; }
|
|
319
|
+
[ -n "$WORKFLOW" ] || { usage >&2; die 2 "--workflow is required"; }
|
|
320
|
+
case "$REPO" in */*) ;; *) die 2 "--repo must be OWNER/REPO" ;; esac
|
|
321
|
+
is_uint "$LIMIT" || die 2 "--limit must be a positive integer"
|
|
322
|
+
is_uint "$JOBS" || die 2 "--jobs must be a positive integer"
|
|
323
|
+
|
|
324
|
+
command -v jq >/dev/null 2>&1 || die 4 "jq is required"
|
|
325
|
+
command -v gh >/dev/null 2>&1 || die 4 "gh is required"
|
|
326
|
+
gh auth status >/dev/null 2>&1 || die 4 "gh is not authenticated; run 'gh auth login'"
|
|
327
|
+
|
|
328
|
+
TMP=$(mktemp -d) || die 4 "mktemp failed"
|
|
329
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
330
|
+
|
|
331
|
+
set -- run list --repo "$REPO" --workflow "$WORKFLOW" --limit "$LIMIT" \
|
|
332
|
+
--json databaseId,event,headBranch,status,conclusion,attempt,createdAt,updatedAt
|
|
333
|
+
[ -z "$BRANCH" ] || set -- "$@" --branch "$BRANCH"
|
|
334
|
+
[ -z "$EVENT" ] || set -- "$@" --event "$EVENT"
|
|
335
|
+
FILTERS=""
|
|
336
|
+
[ -z "$BRANCH" ] || FILTERS="$FILTERS, branch $BRANCH"
|
|
337
|
+
[ -z "$EVENT" ] || FILTERS="$FILTERS, event $EVENT"
|
|
338
|
+
|
|
339
|
+
if ! gh "$@" >"$TMP/runs.json"; then
|
|
340
|
+
die 3 "gh run list failed for $REPO / $WORKFLOW (see the gh message above)"
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
: >"$TMP/jobs.ndjson"
|
|
344
|
+
fetched=0
|
|
345
|
+
for id in $(sample_ids "$TMP/runs.json" "$JOBS"); do
|
|
346
|
+
if gh api --paginate "repos/$REPO/actions/runs/$id/jobs?per_page=100&filter=latest" --jq '.jobs[]' >"$TMP/job-$id.ndjson"; then
|
|
347
|
+
jq -s --argjson id "$id" '{id: $id, jobs: .}' "$TMP/job-$id.ndjson" >>"$TMP/jobs.ndjson"
|
|
348
|
+
fetched=$((fetched + 1))
|
|
349
|
+
else
|
|
350
|
+
printf 'ci-timings: skipping run %s: could not fetch its jobs\n' "$id" >&2
|
|
351
|
+
fi
|
|
352
|
+
done
|
|
353
|
+
jq -s '.' "$TMP/jobs.ndjson" >"$TMP/jobs.json"
|
|
354
|
+
|
|
355
|
+
report "$TMP/runs.json" "$TMP/jobs.json" "$REPO" "$WORKFLOW" "$FILTERS" "$JOBS"
|
|
356
|
+
exit $?
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "build",
|
|
4
|
+
"conclusion": "success",
|
|
5
|
+
"created_at": "2030-01-01T03:00:10Z",
|
|
6
|
+
"started_at": "2030-01-01T03:01:10Z",
|
|
7
|
+
"completed_at": "2030-01-01T03:11:10Z",
|
|
8
|
+
"steps": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Checkout",
|
|
11
|
+
"conclusion": "success",
|
|
12
|
+
"started_at": "2030-01-01T03:01:10Z",
|
|
13
|
+
"completed_at": "2030-01-01T03:01:20Z"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "dotnet build",
|
|
17
|
+
"conclusion": "success",
|
|
18
|
+
"started_at": "2030-01-01T03:01:20Z",
|
|
19
|
+
"completed_at": "2030-01-01T03:10:50Z"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Complete job",
|
|
23
|
+
"conclusion": "success",
|
|
24
|
+
"started_at": "2030-01-01T03:10:50Z",
|
|
25
|
+
"completed_at": "2030-01-01T03:11:10Z"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "test",
|
|
31
|
+
"conclusion": "success",
|
|
32
|
+
"created_at": "2030-01-01T03:11:10Z",
|
|
33
|
+
"started_at": "2030-01-01T03:12:10Z",
|
|
34
|
+
"completed_at": "2030-01-01T03:17:10Z",
|
|
35
|
+
"steps": [
|
|
36
|
+
{
|
|
37
|
+
"name": "dotnet test",
|
|
38
|
+
"conclusion": "success",
|
|
39
|
+
"started_at": "2030-01-01T03:12:10Z",
|
|
40
|
+
"completed_at": "2030-01-01T03:16:50Z"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "Complete job",
|
|
44
|
+
"conclusion": "success",
|
|
45
|
+
"started_at": "2030-01-01T03:16:50Z",
|
|
46
|
+
"completed_at": "2030-01-01T03:17:10Z"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "lint",
|
|
52
|
+
"conclusion": "success",
|
|
53
|
+
"created_at": "2030-01-01T03:00:10Z",
|
|
54
|
+
"started_at": "2030-01-01T03:03:10Z",
|
|
55
|
+
"completed_at": "2030-01-01T03:04:10Z",
|
|
56
|
+
"steps": [
|
|
57
|
+
{
|
|
58
|
+
"name": "eslint",
|
|
59
|
+
"conclusion": "success",
|
|
60
|
+
"started_at": "2030-01-01T03:03:10Z",
|
|
61
|
+
"completed_at": "2030-01-01T03:04:00Z"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "Complete job",
|
|
65
|
+
"conclusion": "success",
|
|
66
|
+
"started_at": "2030-01-01T03:04:00Z",
|
|
67
|
+
"completed_at": "2030-01-01T03:04:10Z"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "deploy",
|
|
73
|
+
"conclusion": "skipped",
|
|
74
|
+
"created_at": "2030-01-01T03:00:10Z",
|
|
75
|
+
"started_at": null,
|
|
76
|
+
"completed_at": null,
|
|
77
|
+
"steps": []
|
|
78
|
+
}
|
|
79
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "build",
|
|
4
|
+
"conclusion": "success",
|
|
5
|
+
"created_at": "2030-01-01T07:00:10Z",
|
|
6
|
+
"started_at": "2030-01-01T07:01:10Z",
|
|
7
|
+
"completed_at": "2030-01-01T07:06:10Z",
|
|
8
|
+
"steps": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Checkout",
|
|
11
|
+
"conclusion": "success",
|
|
12
|
+
"started_at": "2030-01-01T07:01:10Z",
|
|
13
|
+
"completed_at": "2030-01-01T07:01:20Z"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "dotnet build",
|
|
17
|
+
"conclusion": "success",
|
|
18
|
+
"started_at": "2030-01-01T07:01:20Z",
|
|
19
|
+
"completed_at": "2030-01-01T07:05:50Z"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Complete job",
|
|
23
|
+
"conclusion": "success",
|
|
24
|
+
"started_at": "2030-01-01T07:05:50Z",
|
|
25
|
+
"completed_at": "2030-01-01T07:06:10Z"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "test",
|
|
31
|
+
"conclusion": "success",
|
|
32
|
+
"created_at": "2030-01-01T07:06:10Z",
|
|
33
|
+
"started_at": "2030-01-01T07:07:10Z",
|
|
34
|
+
"completed_at": "2030-01-01T07:12:10Z",
|
|
35
|
+
"steps": [
|
|
36
|
+
{
|
|
37
|
+
"name": "dotnet test",
|
|
38
|
+
"conclusion": "success",
|
|
39
|
+
"started_at": "2030-01-01T07:07:10Z",
|
|
40
|
+
"completed_at": "2030-01-01T07:11:50Z"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "Complete job",
|
|
44
|
+
"conclusion": "success",
|
|
45
|
+
"started_at": "2030-01-01T07:11:50Z",
|
|
46
|
+
"completed_at": "2030-01-01T07:12:10Z"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "lint",
|
|
52
|
+
"conclusion": "success",
|
|
53
|
+
"created_at": "2030-01-01T07:00:10Z",
|
|
54
|
+
"started_at": "2030-01-01T07:03:10Z",
|
|
55
|
+
"completed_at": "2030-01-01T07:04:10Z",
|
|
56
|
+
"steps": [
|
|
57
|
+
{
|
|
58
|
+
"name": "eslint",
|
|
59
|
+
"conclusion": "success",
|
|
60
|
+
"started_at": "2030-01-01T07:03:10Z",
|
|
61
|
+
"completed_at": "2030-01-01T07:04:00Z"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "Complete job",
|
|
65
|
+
"conclusion": "success",
|
|
66
|
+
"started_at": "2030-01-01T07:04:00Z",
|
|
67
|
+
"completed_at": "2030-01-01T07:04:10Z"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "deploy",
|
|
73
|
+
"conclusion": "skipped",
|
|
74
|
+
"created_at": "2030-01-01T07:00:10Z",
|
|
75
|
+
"started_at": null,
|
|
76
|
+
"completed_at": null,
|
|
77
|
+
"steps": []
|
|
78
|
+
}
|
|
79
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "build",
|
|
4
|
+
"conclusion": "success",
|
|
5
|
+
"created_at": "2030-01-01T10:00:10Z",
|
|
6
|
+
"started_at": "2030-01-01T10:01:10Z",
|
|
7
|
+
"completed_at": "2030-01-01T10:06:10Z",
|
|
8
|
+
"steps": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Checkout",
|
|
11
|
+
"conclusion": "success",
|
|
12
|
+
"started_at": "2030-01-01T10:01:10Z",
|
|
13
|
+
"completed_at": "2030-01-01T10:01:20Z"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "dotnet build",
|
|
17
|
+
"conclusion": "success",
|
|
18
|
+
"started_at": "2030-01-01T10:01:20Z",
|
|
19
|
+
"completed_at": "2030-01-01T10:05:50Z"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Complete job",
|
|
23
|
+
"conclusion": "success",
|
|
24
|
+
"started_at": "2030-01-01T10:05:50Z",
|
|
25
|
+
"completed_at": "2030-01-01T10:06:10Z"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "test",
|
|
31
|
+
"conclusion": "success",
|
|
32
|
+
"created_at": "2030-01-01T10:06:10Z",
|
|
33
|
+
"started_at": "2030-01-01T10:07:10Z",
|
|
34
|
+
"completed_at": "2030-01-01T10:12:10Z",
|
|
35
|
+
"steps": [
|
|
36
|
+
{
|
|
37
|
+
"name": "dotnet test",
|
|
38
|
+
"conclusion": "success",
|
|
39
|
+
"started_at": "2030-01-01T10:07:10Z",
|
|
40
|
+
"completed_at": "2030-01-01T10:11:50Z"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "Complete job",
|
|
44
|
+
"conclusion": "success",
|
|
45
|
+
"started_at": "2030-01-01T10:11:50Z",
|
|
46
|
+
"completed_at": "2030-01-01T10:12:10Z"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "lint",
|
|
52
|
+
"conclusion": "success",
|
|
53
|
+
"created_at": "2030-01-01T10:00:10Z",
|
|
54
|
+
"started_at": "2030-01-01T10:03:10Z",
|
|
55
|
+
"completed_at": "2030-01-01T10:04:10Z",
|
|
56
|
+
"steps": [
|
|
57
|
+
{
|
|
58
|
+
"name": "eslint",
|
|
59
|
+
"conclusion": "success",
|
|
60
|
+
"started_at": "2030-01-01T10:03:10Z",
|
|
61
|
+
"completed_at": "2030-01-01T10:04:00Z"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "Complete job",
|
|
65
|
+
"conclusion": "success",
|
|
66
|
+
"started_at": "2030-01-01T10:04:00Z",
|
|
67
|
+
"completed_at": "2030-01-01T10:04:10Z"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "deploy",
|
|
73
|
+
"conclusion": "skipped",
|
|
74
|
+
"created_at": "2030-01-01T10:00:10Z",
|
|
75
|
+
"started_at": null,
|
|
76
|
+
"completed_at": null,
|
|
77
|
+
"steps": []
|
|
78
|
+
}
|
|
79
|
+
]
|