Skip to content

Commit 509cfa3

Browse files
committed
llbsolver: add systemusage samples to provenance attestation
Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 32dcdff commit 509cfa3

File tree

11 files changed

+223
-31
lines changed

11 files changed

+223
-31
lines changed

executor/resources/monitor.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type cgroupRecord struct {
3131
once sync.Once
3232
ns string
3333
sampler *Sub[*types.Sample]
34+
closeSampler func() error
3435
samples []*types.Sample
3536
err error
3637
done chan struct{}
@@ -52,6 +53,7 @@ func (r *cgroupRecord) Start() {
5253
}
5354
s := NewSampler(2*time.Second, r.sample)
5455
r.sampler = s.Record()
56+
r.closeSampler = s.Close
5557
}
5658

5759
func (r *cgroupRecord) CloseAsync(next func(context.Context) error) error {
@@ -79,6 +81,7 @@ func (r *cgroupRecord) close() {
7981
} else {
8082
r.samples = s
8183
}
84+
r.closeSampler()
8285

8386
if r.startCPUStat != nil {
8487
stat, err := r.monitor.proc.Stat()
@@ -98,7 +101,6 @@ func (r *cgroupRecord) close() {
98101
r.sysCPUStat = cpu
99102
}
100103
}
101-
102104
})
103105
}
104106

executor/resources/sampler.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func (s *Sampler[T]) Record() *Sub[T] {
8484
}
8585

8686
func (s *Sampler[T]) run() {
87-
ticker := time.NewTicker(s.minInterval)
87+
ticker := time.NewTimer(s.minInterval)
8888
for {
8989
select {
9090
case <-s.done:
@@ -102,6 +102,10 @@ func (s *Sampler[T]) run() {
102102
active = append(active, ss)
103103
}
104104
s.mu.RUnlock()
105+
ticker = time.NewTimer(s.minInterval)
106+
if len(active) == 0 {
107+
continue
108+
}
105109
value, err := s.callback(tm)
106110
for _, ss := range active {
107111
if err != nil {
@@ -111,7 +115,7 @@ func (s *Sampler[T]) run() {
111115
ss.err = nil
112116
}
113117
dur := ss.last.Sub(ss.first)
114-
if time.Duration(ss.interval)*10 >= dur {
118+
if time.Duration(ss.interval)*10 <= dur {
115119
ss.interval *= 2
116120
}
117121
}

executor/resources/sys.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package resources
2+
3+
import (
4+
"os"
5+
"time"
6+
7+
"github.com/moby/buildkit/executor/resources/types"
8+
"github.com/prometheus/procfs"
9+
)
10+
11+
type SysSampler = Sub[*types.SysSample]
12+
13+
func NewSysSampler() (*Sampler[*types.SysSample], error) {
14+
procfs, err := procfs.NewDefaultFS()
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
return NewSampler(2*time.Second, func(tm time.Time) (*types.SysSample, error) {
20+
return sampleSys(procfs, tm)
21+
}), nil
22+
}
23+
24+
func sampleSys(proc procfs.FS, tm time.Time) (*types.SysSample, error) {
25+
stat, err := proc.Stat()
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
s := &types.SysSample{
31+
Timestamp_: tm,
32+
}
33+
34+
s.CPUStat = &types.SysCPUStat{
35+
User: stat.CPUTotal.User,
36+
Nice: stat.CPUTotal.Nice,
37+
System: stat.CPUTotal.System,
38+
Idle: stat.CPUTotal.Idle,
39+
Iowait: stat.CPUTotal.Iowait,
40+
IRQ: stat.CPUTotal.IRQ,
41+
SoftIRQ: stat.CPUTotal.SoftIRQ,
42+
Steal: stat.CPUTotal.Steal,
43+
Guest: stat.CPUTotal.Guest,
44+
GuestNice: stat.CPUTotal.GuestNice,
45+
}
46+
47+
s.ProcStat = &types.ProcStat{
48+
ContextSwitches: stat.ContextSwitches,
49+
ProcessCreated: stat.ProcessCreated,
50+
ProcessesRunning: stat.ProcessesRunning,
51+
}
52+
53+
mem, err := proc.Meminfo()
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
s.MemoryStat = &types.SysMemoryStat{
59+
Total: mem.MemTotal,
60+
Free: mem.MemFree,
61+
Buffers: mem.Buffers,
62+
Cached: mem.Cached,
63+
Active: mem.Active,
64+
Inactive: mem.Inactive,
65+
Swap: mem.SwapTotal,
66+
Available: mem.MemAvailable,
67+
Dirty: mem.Dirty,
68+
Writeback: mem.Writeback,
69+
Slab: mem.Slab,
70+
}
71+
72+
if _, err := os.Lstat("/proc/pressure"); err != nil {
73+
return s, nil
74+
}
75+
76+
cp, err := parsePressureFile("/proc/pressure/cpu")
77+
if err != nil {
78+
return nil, err
79+
}
80+
s.CPUPressure = cp
81+
82+
mp, err := parsePressureFile("/proc/pressure/memory")
83+
if err != nil {
84+
return nil, err
85+
}
86+
s.MemoryPressure = mp
87+
88+
ip, err := parsePressureFile("/proc/pressure/io")
89+
if err != nil {
90+
return nil, err
91+
}
92+
s.IOPressure = ip
93+
94+
return s, nil
95+
}

executor/resources/types/systypes.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"math"
6+
"time"
7+
)
8+
9+
type SysCPUStat struct {
10+
User float64 `json:"user"`
11+
Nice float64 `json:"nice"`
12+
System float64 `json:"system"`
13+
Idle float64 `json:"idle"`
14+
Iowait float64 `json:"iowait"`
15+
IRQ float64 `json:"irq"`
16+
SoftIRQ float64 `json:"softirq"`
17+
Steal float64 `json:"steal"`
18+
Guest float64 `json:"guest"`
19+
GuestNice float64 `json:"guestNice"`
20+
}
21+
22+
type sysCPUStatAlias SysCPUStat // avoid recursion of MarshalJSON
23+
24+
func (s SysCPUStat) MarshalJSON() ([]byte, error) {
25+
return json.Marshal(sysCPUStatAlias{
26+
User: math.Round(s.User*1000) / 1000,
27+
Nice: math.Round(s.Nice*1000) / 1000,
28+
System: math.Round(s.System*1000) / 1000,
29+
Idle: math.Round(s.Idle*1000) / 1000,
30+
Iowait: math.Round(s.Iowait*1000) / 1000,
31+
IRQ: math.Round(s.IRQ*1000) / 1000,
32+
SoftIRQ: math.Round(s.SoftIRQ*1000) / 1000,
33+
Steal: math.Round(s.Steal*1000) / 1000,
34+
Guest: math.Round(s.Guest*1000) / 1000,
35+
GuestNice: math.Round(s.GuestNice*1000) / 1000,
36+
})
37+
}
38+
39+
type ProcStat struct {
40+
ContextSwitches uint64 `json:"contextSwitches"`
41+
ProcessCreated uint64 `json:"processCreated"`
42+
ProcessesRunning uint64 `json:"processesRunning"`
43+
}
44+
45+
type SysMemoryStat struct {
46+
Total *uint64 `json:"total"`
47+
Free *uint64 `json:"free"`
48+
Available *uint64 `json:"available"`
49+
Buffers *uint64 `json:"buffers"`
50+
Cached *uint64 `json:"cached"`
51+
Active *uint64 `json:"active"`
52+
Inactive *uint64 `json:"inactive"`
53+
Swap *uint64 `json:"swap"`
54+
Dirty *uint64 `json:"dirty"`
55+
Writeback *uint64 `json:"writeback"`
56+
Slab *uint64 `json:"slab"`
57+
}
58+
59+
type SysSample struct {
60+
//nolint
61+
Timestamp_ time.Time `json:"timestamp"`
62+
CPUStat *SysCPUStat `json:"cpuStat,omitempty"`
63+
ProcStat *ProcStat `json:"procStat,omitempty"`
64+
MemoryStat *SysMemoryStat `json:"memoryStat,omitempty"`
65+
CPUPressure *Pressure `json:"cpuPressure,omitempty"`
66+
MemoryPressure *Pressure `json:"memoryPressure,omitempty"`
67+
IOPressure *Pressure `json:"ioPressure,omitempty"`
68+
}
69+
70+
func (s *SysSample) Timestamp() time.Time {
71+
return s.Timestamp_
72+
}

executor/resources/types/types.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,14 @@ type Recorder interface {
1414
Samples() (*Samples, error)
1515
}
1616

17-
type SysCPUStat struct {
18-
User float64 `json:"user"`
19-
Nice float64 `json:"nice"`
20-
System float64 `json:"system"`
21-
Idle float64 `json:"idle"`
22-
Iowait float64 `json:"iowait"`
23-
IRQ float64 `json:"irq"`
24-
SoftIRQ float64 `json:"softirq"`
25-
Steal float64 `json:"steal"`
26-
Guest float64 `json:"guest"`
27-
GuestNice float64 `json:"guestNice"`
28-
}
29-
3017
type Samples struct {
3118
Samples []*Sample `json:"samples,omitempty"`
3219
SysCPUStat *SysCPUStat `json:"sysCPUStat,omitempty"`
3320
}
3421

3522
// Sample represents a wrapper for sampled data of cgroupv2 controllers
3623
type Sample struct {
24+
//nolint
3725
Timestamp_ time.Time `json:"timestamp"`
3826
CPUStat *CPUStat `json:"cpuStat,omitempty"`
3927
MemoryStat *MemoryStat `json:"memoryStat,omitempty"`

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ require (
5757
github.com/pelletier/go-toml v1.9.5
5858
github.com/pkg/errors v0.9.1
5959
github.com/pkg/profile v1.5.0
60+
github.com/prometheus/procfs v0.9.0
6061
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
6162
github.com/sirupsen/logrus v1.9.0
6263
github.com/spdx/tools-golang v0.5.1
@@ -146,7 +147,6 @@ require (
146147
github.com/prometheus/client_golang v1.14.0 // indirect
147148
github.com/prometheus/client_model v0.3.0 // indirect
148149
github.com/prometheus/common v0.42.0 // indirect
149-
github.com/prometheus/procfs v0.9.0 // indirect
150150
github.com/russross/blackfriday/v2 v2.1.0 // indirect
151151
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
152152
github.com/shibumi/go-pathspec v1.3.0 // indirect

solver/llbsolver/proc/provenance.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strconv"
77

88
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
9+
"github.com/moby/buildkit/executor/resources"
910
"github.com/moby/buildkit/exporter/containerimage/exptypes"
1011
gatewaypb "github.com/moby/buildkit/frontend/gateway/pb"
1112
"github.com/moby/buildkit/solver"
@@ -15,7 +16,7 @@ import (
1516
)
1617

1718
func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor {
18-
return func(ctx context.Context, res *llbsolver.Result, s *llbsolver.Solver, j *solver.Job) (*llbsolver.Result, error) {
19+
return func(ctx context.Context, res *llbsolver.Result, s *llbsolver.Solver, j *solver.Job, usage *resources.SysSampler) (*llbsolver.Result, error) {
1920
ps, err := exptypes.ParsePlatforms(res.Metadata)
2021
if err != nil {
2122
return nil, err
@@ -41,7 +42,7 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor {
4142
return nil, errors.Errorf("could not find ref %s", p.ID)
4243
}
4344

44-
pc, err := llbsolver.NewProvenanceCreator(ctx, cp, ref, attrs, j)
45+
pc, err := llbsolver.NewProvenanceCreator(ctx, cp, ref, attrs, j, usage)
4546
if err != nil {
4647
return nil, err
4748
}

solver/llbsolver/proc/sbom.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55

66
"github.com/moby/buildkit/client/llb"
7+
"github.com/moby/buildkit/executor/resources"
78
"github.com/moby/buildkit/exporter/containerimage/exptypes"
89
"github.com/moby/buildkit/frontend"
910
"github.com/moby/buildkit/frontend/attestations/sbom"
@@ -14,7 +15,7 @@ import (
1415
)
1516

1617
func SBOMProcessor(scannerRef string, useCache bool) llbsolver.Processor {
17-
return func(ctx context.Context, res *llbsolver.Result, s *llbsolver.Solver, j *solver.Job) (*llbsolver.Result, error) {
18+
return func(ctx context.Context, res *llbsolver.Result, s *llbsolver.Solver, j *solver.Job, usage *resources.SysSampler) (*llbsolver.Result, error) {
1819
// skip sbom generation if we already have an sbom
1920
if sbom.HasSBOM(res.Result) {
2021
return res, nil

solver/llbsolver/provenance.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/moby/buildkit/cache"
1313
"github.com/moby/buildkit/cache/config"
1414
"github.com/moby/buildkit/client/llb"
15+
"github.com/moby/buildkit/executor/resources"
1516
"github.com/moby/buildkit/exporter/containerimage"
1617
"github.com/moby/buildkit/exporter/containerimage/exptypes"
1718
"github.com/moby/buildkit/frontend"
@@ -378,10 +379,11 @@ func captureProvenance(ctx context.Context, res solver.CachedResultWithProvenanc
378379
type ProvenanceCreator struct {
379380
pr *provenance.ProvenancePredicate
380381
j *solver.Job
382+
sampler *resources.SysSampler
381383
addLayers func() error
382384
}
383385

384-
func NewProvenanceCreator(ctx context.Context, cp *provenance.Capture, res solver.ResultProxy, attrs map[string]string, j *solver.Job) (*ProvenanceCreator, error) {
386+
func NewProvenanceCreator(ctx context.Context, cp *provenance.Capture, res solver.ResultProxy, attrs map[string]string, j *solver.Job, usage *resources.SysSampler) (*ProvenanceCreator, error) {
385387
var reproducible bool
386388
if v, ok := attrs["reproducible"]; ok {
387389
b, err := strconv.ParseBool(v)
@@ -493,11 +495,15 @@ func NewProvenanceCreator(ctx context.Context, cp *provenance.Capture, res solve
493495
return nil, errors.Errorf("invalid mode %q", mode)
494496
}
495497

496-
return &ProvenanceCreator{
498+
pc := &ProvenanceCreator{
497499
pr: pr,
498500
j: j,
499501
addLayers: addLayers,
500-
}, nil
502+
}
503+
if withUsage {
504+
pc.sampler = usage
505+
}
506+
return pc, nil
501507
}
502508

503509
func (p *ProvenanceCreator) Predicate() (*provenance.ProvenancePredicate, error) {
@@ -510,6 +516,14 @@ func (p *ProvenanceCreator) Predicate() (*provenance.ProvenancePredicate, error)
510516
}
511517
}
512518

519+
if p.sampler != nil {
520+
sysSamples, err := p.sampler.Close(true)
521+
if err != nil {
522+
return nil, err
523+
}
524+
p.pr.Metadata.BuildKitMetadata.SysUsage = sysSamples
525+
}
526+
513527
return p.pr, nil
514528
}
515529

solver/llbsolver/provenance/predicate.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/containerd/containerd/platforms"
77
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
88
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
9+
resourcetypes "github.com/moby/buildkit/executor/resources/types"
910
"github.com/moby/buildkit/util/purl"
1011
"github.com/moby/buildkit/util/urlutil"
1112
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
@@ -50,9 +51,10 @@ type ProvenanceMetadata struct {
5051
}
5152

5253
type BuildKitMetadata struct {
53-
VCS map[string]string `json:"vcs,omitempty"`
54-
Source *Source `json:"source,omitempty"`
55-
Layers map[string][][]ocispecs.Descriptor `json:"layers,omitempty"`
54+
VCS map[string]string `json:"vcs,omitempty"`
55+
Source *Source `json:"source,omitempty"`
56+
Layers map[string][][]ocispecs.Descriptor `json:"layers,omitempty"`
57+
SysUsage []*resourcetypes.SysSample `json:"sysUsage,omitempty"`
5658
}
5759

5860
func slsaMaterials(srcs Sources) ([]slsa.ProvenanceMaterial, error) {

0 commit comments

Comments
 (0)