runnerd_core/api/jobs.rs
1//! Job history as clients read it: one record per GitHub job a runner of this daemon ran, its timeline, steps,
2//! metrics and log size; the queries that list them; sample series for graphs; per-repository aggregates; cache
3//! sizes. Times are seconds since the Unix epoch, sizes are bytes, CPU is in millicores (1000 = one core busy).
4
5use serde::{Deserialize, Serialize};
6
7/// Where a job is in its life, as this daemon saw it.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "kebab-case")]
10pub enum JobState {
11 /// Waiting for a runner.
12 Queued,
13 /// A runner of this daemon took it.
14 Running,
15 /// GitHub reports it completed; `conclusion` says how.
16 Completed,
17 /// Its runner's container is gone and GitHub did not report the job completed in time.
18 Lost,
19 /// A runner of another machine took it, or it was cancelled before any runner did.
20 Elsewhere,
21}
22
23impl JobState {
24 /// Whether the job can still change.
25 pub fn is_final(self) -> bool {
26 matches!(
27 self,
28 JobState::Completed | JobState::Lost | JobState::Elsewhere
29 )
30 }
31}
32
33/// A job without its steps: what lists and events carry.
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct JobSummary {
36 /// GitHub's job ID, also its check run ID.
37 pub id: u64,
38 /// `owner/name` on GitHub.
39 pub repository: String,
40 /// The workflow run it belongs to.
41 pub run_id: u64,
42 /// 1 for the first attempt of the run, higher for re-runs.
43 pub run_attempt: u32,
44 /// The workflow's name.
45 pub workflow: Option<String>,
46 /// The job's name in the workflow.
47 pub name: String,
48 /// The branch the run is for.
49 pub branch: Option<String>,
50 /// The job's page on GitHub.
51 pub url: Option<String>,
52 /// The workflow's `runs-on` labels.
53 pub labels: Vec<String>,
54 /// Where the job is.
55 pub state: JobState,
56 /// GitHub's conclusion once completed: `success`, `failure`, `cancelled`, `skipped`, `timed_out`, ….
57 pub conclusion: Option<String>,
58 /// The runner that ran it.
59 pub runner: Option<JobRunner>,
60 /// When each phase started.
61 pub timeline: Timeline,
62 /// CPU, memory and network use while it ran.
63 pub metrics: JobMetrics,
64 /// How much of its log is stored.
65 pub log: LogSummary,
66}
67
68/// `GET /v1/jobs/{id}`: a job with its steps.
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct Job {
71 /// Everything but the steps.
72 #[serde(flatten)]
73 pub summary: JobSummary,
74 /// The steps as GitHub numbers them, including `Set up job` and `Complete job`.
75 pub steps: Vec<Step>,
76 /// Its record, for a failed job of a profile that records failures.
77 #[serde(default)]
78 pub record: Option<super::RecordNote>,
79 /// How its runner was admitted; `None` for one started before runnerd recorded it.
80 #[serde(default)]
81 pub admission: Option<super::Admission>,
82}
83
84/// The runner a job ran on.
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86pub struct JobRunner {
87 /// The runner's name, also its container's.
88 pub name: String,
89 /// The profile it ran in.
90 pub profile: String,
91 /// The slot it ran in, 1-based.
92 pub slot: usize,
93 /// Its container's memory cap in bytes, which a reload since may have changed in the profile. `None` in records
94 /// from before runnerd kept it.
95 pub memory_cap: Option<u64>,
96 /// Its container's CPU limit in millicores, the unit of the CPU use in `Sample` and `JobMetrics`. `None` in
97 /// records from before runnerd kept it, or when Docker reports no limit.
98 pub cpu_cap: Option<u64>,
99}
100
101/// A job's phases, in the order they happen. A runner's container starts before GitHub gives it a job.
102#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
103pub struct Timeline {
104 /// GitHub queued the job.
105 pub queued_at: u64,
106 /// The container of the runner that took it started.
107 pub container_started_at: Option<u64>,
108 /// The runner started the job.
109 pub started_at: Option<u64>,
110 /// The job finished, by GitHub's clock; or when its container was found gone.
111 pub finished_at: Option<u64>,
112 /// The container and the runner's registration were removed.
113 pub cleaned_at: Option<u64>,
114}
115
116/// What a job used, over the samples taken while it ran (every few seconds). `None` until a sample exists.
117#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
118pub struct JobMetrics {
119 /// CPU time, in milliseconds.
120 pub cpu_time: Option<u64>,
121 /// The highest CPU use of a sample interval, in millicores.
122 pub cpu_peak: Option<u64>,
123 /// CPU time over wall time, in millicores.
124 pub cpu_average: Option<u64>,
125 /// The highest memory use without page cache.
126 pub memory_peak: Option<u64>,
127 /// The average memory use without page cache.
128 pub memory_average: Option<u64>,
129 /// Bytes the container received.
130 pub network_received: Option<u64>,
131 /// Bytes the container sent.
132 pub network_sent: Option<u64>,
133 /// Of the bytes received, those the Docker Hub mirror served.
134 pub mirror_bytes: u64,
135}
136
137/// A job's stored log.
138#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
139pub struct LogSummary {
140 /// Lines stored.
141 pub lines: u64,
142 /// Bytes stored.
143 pub bytes: u64,
144 /// Set once the log reached `history.log_max` and later lines were dropped.
145 pub truncated: bool,
146}
147
148/// One step of a job, as GitHub reports it.
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub struct Step {
151 /// GitHub's step number; numbers may skip.
152 pub number: u32,
153 /// The name GitHub shows, such as `Set up job` or `Run docker run --rm hello-world`.
154 pub name: String,
155 /// `pending`, `queued`, `in_progress` or `completed`.
156 pub status: String,
157 /// Once completed: `success`, `failure`, `skipped`, `cancelled`, ….
158 pub conclusion: Option<String>,
159 /// When it started.
160 pub started_at: Option<u64>,
161 /// When it completed.
162 pub completed_at: Option<u64>,
163}
164
165/// `GET /v1/jobs` query parameters. Jobs come newest first (by ID, which grows with time).
166#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
167pub struct JobQuery {
168 /// Only this `owner/name`.
169 pub repository: Option<String>,
170 /// Only jobs in this state.
171 pub state: Option<JobState>,
172 /// Only jobs with this conclusion, such as `failure`.
173 pub conclusion: Option<String>,
174 /// Only jobs queued at or after this time.
175 pub since: Option<u64>,
176 /// Only jobs queued before this time.
177 pub until: Option<u64>,
178 /// Only jobs with a smaller ID: the `next` of the previous page.
179 pub before: Option<u64>,
180 /// At most this many jobs; default 50, at most 500.
181 pub limit: Option<u32>,
182}
183
184/// `GET /v1/jobs`: one page of jobs.
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct JobPage {
187 /// Newest first.
188 pub jobs: Vec<JobSummary>,
189 /// Pass as `before` for the next page; `None` on the last page.
190 pub next: Option<u64>,
191}
192
193/// `GET /v1/jobs/{id}/logs` query parameters. The response is `text/plain`, one log line per line.
194#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
195pub struct LogQuery {
196 /// Start at this line, 0-based.
197 pub from: Option<u64>,
198 /// Keep the response open and send lines as they are stored, until the job finishes.
199 #[serde(default)]
200 pub follow: bool,
201}
202
203/// One sample of a container's use.
204#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
205pub struct Sample {
206 /// When it was taken.
207 pub at: u64,
208 /// CPU use since the previous sample, in millicores.
209 pub cpu: u64,
210 /// Memory use without page cache.
211 pub memory: u64,
212 /// Bytes received since the container started.
213 pub network_received: u64,
214 /// Bytes sent since the container started.
215 pub network_sent: u64,
216}
217
218/// `GET /v1/jobs/{id}/samples`: what the job's container used over time, from its start to its removal. Samples
219/// older than `history.samples` are deleted; the job's metrics stay.
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
221pub struct SampleSeries {
222 /// The job.
223 pub job: u64,
224 /// Seconds between samples.
225 pub interval: u64,
226 /// Oldest first.
227 pub samples: Vec<Sample>,
228}
229
230/// One sample of the whole slice runners run in.
231#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
232pub struct HostSample {
233 /// When it was taken.
234 pub at: u64,
235 /// CPU use of the slice since the previous sample, in millicores.
236 pub cpu: u64,
237 /// Memory use of the slice without page cache.
238 pub memory: u64,
239}
240
241/// `GET /v1/host/samples` query parameters.
242#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
243pub struct HostSampleQuery {
244 /// Only samples taken at or after this time; default the last hour, at most the last 24 hours.
245 pub since: Option<u64>,
246}
247
248/// `GET /v1/host/samples`: the slice's use over time, oldest first.
249#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
250pub struct HostSamples {
251 /// Seconds between samples.
252 pub interval: u64,
253 /// Oldest first.
254 pub samples: Vec<HostSample>,
255}
256
257/// How `GET /v1/stats` groups jobs in time.
258#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
259#[serde(rename_all = "kebab-case")]
260pub enum Bucket {
261 /// By UTC hour.
262 Hour,
263 /// By UTC day.
264 #[default]
265 Day,
266}
267
268/// `GET /v1/stats` query parameters.
269#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
270pub struct StatsQuery {
271 /// Only this `owner/name`.
272 pub repository: Option<String>,
273 /// Only jobs queued at or after this time; default 30 days ago. At most 744 buckets before `until` (or now)
274 /// are counted: 31 days of hours, two years of days.
275 pub since: Option<u64>,
276 /// Only jobs queued before this time.
277 pub until: Option<u64>,
278 /// The bucket size; default a day.
279 #[serde(default)]
280 pub bucket: Bucket,
281}
282
283/// `GET /v1/stats`: finished jobs per repository and time bucket, for trends. Sums, so clients can average.
284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
285pub struct Stats {
286 /// The bucket size.
287 pub bucket: Bucket,
288 /// By bucket start, then repository.
289 pub rows: Vec<StatsRow>,
290}
291
292/// The finished jobs of one repository in one bucket.
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
294pub struct StatsRow {
295 /// The bucket's start.
296 pub start: u64,
297 /// `owner/name`.
298 pub repository: String,
299 /// Jobs completed or lost.
300 pub jobs: u64,
301 /// Of those, concluded `success`.
302 pub succeeded: u64,
303 /// Of those, concluded `failure` or `timed_out`, or lost.
304 pub failed: u64,
305 /// Of those, concluded `cancelled`.
306 pub cancelled: u64,
307 /// Seconds from queued to started, summed.
308 pub queue_seconds: u64,
309 /// Seconds from started to finished, summed.
310 pub run_seconds: u64,
311 /// CPU time, in milliseconds, summed.
312 pub cpu_time: u64,
313 /// The highest memory peak of a job.
314 pub memory_peak: u64,
315 /// Bytes received, summed.
316 pub network_received: u64,
317 /// Bytes from the mirror, summed.
318 pub mirror_bytes: u64,
319}
320
321/// What a cache volume holds.
322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
323#[serde(rename_all = "kebab-case", tag = "kind")]
324pub enum CacheKind {
325 /// A profile's read-only snapshot.
326 Snapshot {
327 /// The profile.
328 profile: String,
329 },
330 /// A slot's layer changes and Docker Engine data.
331 Slot {
332 /// The profile.
333 profile: String,
334 /// 1-based.
335 slot: usize,
336 },
337 /// The pnpm store all slots share.
338 Pnpm,
339 /// The Maven repository all slots share.
340 Maven,
341 /// The Docker Hub mirror's storage.
342 Mirror,
343}
344
345/// One cache volume and its size.
346#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
347pub struct CacheVolume {
348 /// The Docker volume.
349 pub name: String,
350 /// What it holds.
351 #[serde(flatten)]
352 pub kind: CacheKind,
353 /// Its size at `measured_at`.
354 pub bytes: u64,
355 /// The size above which runnerd empties it, if it has one.
356 pub limit: Option<u64>,
357}
358
359/// `GET /v1/caches`: every cache volume of the configured profiles, as last measured. Docker measures volumes
360/// slowly, so runnerd measures them at start and whenever no runner runs after jobs ended.
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362pub struct Caches {
363 /// When the sizes were measured; `None` before the first measurement.
364 pub measured_at: Option<u64>,
365 /// By name.
366 pub volumes: Vec<CacheVolume>,
367}
368
369/// `GET /v1/caches/history` query parameters.
370#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
371pub struct CacheHistoryQuery {
372 /// Only measurements at or after this time; default 30 days ago.
373 pub since: Option<u64>,
374}
375
376/// One volume's size at one measurement.
377#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
378pub struct CacheSize {
379 /// When it was measured.
380 pub at: u64,
381 /// The Docker volume.
382 pub name: String,
383 /// Its size.
384 pub bytes: u64,
385}
386
387/// `GET /v1/caches/history`: every measurement, oldest first.
388#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
389pub struct CacheHistory {
390 /// Oldest first.
391 pub sizes: Vec<CacheSize>,
392}