Skip to content

Commit a8b5611

Browse files
authored
[Snapshot-Agent Memory-Regions 1/N]: Adding proto and feature flag (#163)
* snapshot-agent: define the memory-regions backend in the API and route it Add MemoryRegion and MemoryRegionsBackendConfig to the BackendConfig oneof (field 5) with regenerated Go and Python bindings: selective checkpoint/restore of explicit device memory ranges of a running process, with snapshot_name naming the snapshot slot so multiple snapshots of one process can coexist (job_id fallback; deliberately not the request group, which identifies related jobs for the orchestrator and does not name agent-side storage). Route memory_regions configs to the new BackendMemoryRegions type in getSnapshotBackendType. No implementation registers this backend yet, so a memory_regions request fails cleanly at backend lookup; every other config routes exactly as before. Signed-off-by: Edwinhr716 <edandres249@gmail.com> * snapshot-agent: gate the memory_regions backend behind MemoryRegionsBackend Register a MemoryRegionsBackend feature gate (alpha, default off) and reject memory_regions configs in checkFeatureGates when it is disabled, mirroring DirectMemoryBackend: the backend is driven by GPU-CR and requires the workload to run under the GPU-CR preloader with a shared checkpoint directory, so selecting it must be an explicit per-agent opt-in. The check runs before routing, so a gated config never falls through to another backend. Gate-independence and error-text cases are pinned in the server gate tests. Signed-off-by: Edwinhr716 <edandres249@gmail.com> * tests: include MemoryRegionsBackend in the gate-registry String expectations Signed-off-by: Edwinhr716 <edandres249@gmail.com> --------- Signed-off-by: Edwinhr716 <edandres249@gmail.com>
1 parent d03540a commit a8b5611

8 files changed

Lines changed: 434 additions & 182 deletions

File tree

pkg/client/python/timeslice/snapshot_agent/snapshot_agent_pb2.py

Lines changed: 53 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/snapshot-agent/api/v1alpha1/snapshot_agent.pb.go

Lines changed: 281 additions & 125 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/snapshot-agent/api/v1alpha1/snapshot_agent.proto

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,31 @@ message AppChannelConfig {
121121
repeated string tags = 2;
122122
}
123123

124+
// MemoryRegion identifies one device-memory range owned by a process.
125+
message MemoryRegion {
126+
// PID of the process that owns the region.
127+
int32 pid = 1;
128+
// Device virtual address of the start of the region.
129+
uint64 address = 2;
130+
// Length of the region in bytes.
131+
uint64 size_bytes = 3;
132+
}
133+
134+
// Configuration for the memory-regions backend: selective checkpoint and
135+
// restore of specific device memory ranges of a running process (GPU-CR
136+
// cr_client with a `-s addr:size,...` spec). Regions are required on both
137+
// Snapshot and Restore; the agent performs no discovery for this backend.
138+
message MemoryRegionsBackendConfig {
139+
repeated MemoryRegion regions = 1;
140+
// snapshot_name names the snapshot slot the regions are saved to
141+
// (Snapshot) or loaded from (Restore). Distinct names let multiple
142+
// snapshots of the same process coexist and be swapped on demand.
143+
// If empty, the request's job_id is used. Note this is deliberately NOT
144+
// the request's `group`: group identifies a set of related jobs for the
145+
// orchestrator and does not name agent-side storage.
146+
string snapshot_name = 2;
147+
}
148+
124149
message BackendConfig {
125150
oneof backend {
126151
CudaBackendConfig cuda = 1;
@@ -129,6 +154,7 @@ message BackendConfig {
129154
// Experimental: disabled by default behind the DirectMemoryBackend
130155
// feature gate; see DirectMemoryBackendConfig.
131156
DirectMemoryBackendConfig direct_memory = 4;
157+
MemoryRegionsBackendConfig memory_regions = 5;
132158
}
133159
}
134160

pkg/snapshot-agent/backends/checkpoint.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const (
2020
// BackendAppChannel suspends/resumes application-aware workloads through
2121
// their registered workload channels (see the WorkloadChannel RPC).
2222
BackendAppChannel BackendType = "app-channel"
23+
// BackendMemoryRegions selectively checkpoints/restores explicit device
24+
// memory regions of a running process via the GPU-CR cr_client.
25+
BackendMemoryRegions BackendType = "memory-regions"
2326
)
2427

2528
// Request carries one backend invocation: the job it targets and the

pkg/snapshot-agent/features/features.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,19 @@ type Feature string
1919
// and operational caveats.
2020
const DirectMemoryBackend Feature = "DirectMemoryBackend"
2121

22+
// MemoryRegionsBackend gates the memory_regions backend. Like
23+
// direct_memory it is driven by GPU-CR and additionally requires the
24+
// target workload to run under the GPU-CR preloader with a shared
25+
// checkpoint directory.
26+
const MemoryRegionsBackend Feature = "MemoryRegionsBackend"
27+
2228
// defaults registers every known gate and its default state. Alpha gates
2329
// default to false; flipping a default here is the promotion path
2430
// (alpha → beta → GA), as in Kubernetes. Adding a gate is one const plus
2531
// one entry here.
2632
var defaults = map[Feature]bool{
27-
DirectMemoryBackend: false,
33+
DirectMemoryBackend: false,
34+
MemoryRegionsBackend: false,
2835
}
2936

3037
// Gates holds the explicitly configured gate values. The zero value (nil)

pkg/snapshot-agent/features/features_internal_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,13 @@ func TestGates_String(t *testing.T) {
180180
withGate(t, "AAATestGate", false)
181181

182182
got := Gates{DirectMemoryBackend: true}.String()
183-
want := "AAATestGate=false,DirectMemoryBackend=true"
183+
want := "AAATestGate=false,DirectMemoryBackend=true,MemoryRegionsBackend=false"
184184
if got != want {
185185
t.Errorf("String() = %q, want %q", got, want)
186186
}
187187

188-
if got := Gates(nil).String(); !strings.Contains(got, "DirectMemoryBackend=false") {
188+
if got := Gates(nil).String(); !strings.Contains(got, "DirectMemoryBackend=false") ||
189+
!strings.Contains(got, "MemoryRegionsBackend=false") {
189190
t.Errorf("nil Gates String() should show defaults, got %q", got)
190191
}
191192
}

pkg/snapshot-agent/server/server.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ func (s *Server) checkFeatureGates(config *pb.BackendConfig) error {
6565
"with --feature-gates=%s=true (or the FEATURE_GATES env var) to enable it",
6666
features.DirectMemoryBackend)
6767
}
68+
if config.GetMemoryRegions() != nil && !s.featureGates.Enabled(features.MemoryRegionsBackend) {
69+
return status.Errorf(codes.FailedPrecondition,
70+
"the memory_regions backend is experimental (driven by GPU-CR, which carries deployment "+
71+
"requirements and operational caveats) and is disabled by default; restart the agent "+
72+
"with --feature-gates=%s=true (or the FEATURE_GATES env var) to enable it",
73+
features.MemoryRegionsBackend)
74+
}
6875
return nil
6976
}
7077

@@ -118,9 +125,9 @@ func (s *Server) getSnapshotBackendType(config *pb.BackendConfig) backends.Backe
118125
if config.GetAppChannel() != nil {
119126
return backends.BackendAppChannel
120127
}
121-
// NOTE: direct_memory is not routed yet and falls through to the
122-
// default backend. It is unreachable unless the DirectMemoryBackend
123-
// feature gate is enabled (checkFeatureGates runs before routing).
128+
if config.GetMemoryRegions() != nil {
129+
return backends.BackendMemoryRegions
130+
}
124131
return s.defaultBackend
125132
}
126133

pkg/snapshot-agent/server/server_feature_gates_internal_test.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,24 @@ func directMemoryConfig() *pb.BackendConfig {
2727
}
2828
}
2929

30+
func memoryRegionsGatedConfig() *pb.BackendConfig {
31+
return &pb.BackendConfig{
32+
Backend: &pb.BackendConfig_MemoryRegions{
33+
MemoryRegions: &pb.MemoryRegionsBackendConfig{
34+
Regions: []*pb.MemoryRegion{{Pid: 123, Address: 0x7f0000000000, SizeBytes: 4096}},
35+
},
36+
},
37+
}
38+
}
39+
3040
func TestServer_CheckFeatureGates(t *testing.T) {
3141
tests := []struct {
3242
name string
3343
gates features.Gates
3444
config *pb.BackendConfig
3545
wantCode codes.Code
46+
// wantGate is the gate a FailedPrecondition error must name.
47+
wantGate features.Feature
3648
}{
3749
{
3850
name: "nil config passes with nil gates",
@@ -53,12 +65,48 @@ func TestServer_CheckFeatureGates(t *testing.T) {
5365
gates: nil,
5466
config: directMemoryConfig(),
5567
wantCode: codes.FailedPrecondition,
68+
wantGate: features.DirectMemoryBackend,
5669
},
5770
{
5871
name: "gated config rejected with explicit false",
5972
gates: features.Gates{features.DirectMemoryBackend: false},
6073
config: directMemoryConfig(),
6174
wantCode: codes.FailedPrecondition,
75+
wantGate: features.DirectMemoryBackend,
76+
},
77+
{
78+
name: "memory-regions config rejected by default",
79+
gates: nil,
80+
config: memoryRegionsGatedConfig(),
81+
wantCode: codes.FailedPrecondition,
82+
wantGate: features.MemoryRegionsBackend,
83+
},
84+
{
85+
name: "memory-regions config rejected with explicit false",
86+
gates: features.Gates{features.MemoryRegionsBackend: false},
87+
config: memoryRegionsGatedConfig(),
88+
wantCode: codes.FailedPrecondition,
89+
wantGate: features.MemoryRegionsBackend,
90+
},
91+
{
92+
name: "memory-regions config passes with its gate enabled",
93+
gates: features.Gates{features.MemoryRegionsBackend: true},
94+
config: memoryRegionsGatedConfig(),
95+
wantCode: codes.OK,
96+
},
97+
{
98+
name: "direct-memory gate does not enable memory-regions",
99+
gates: features.Gates{features.DirectMemoryBackend: true},
100+
config: memoryRegionsGatedConfig(),
101+
wantCode: codes.FailedPrecondition,
102+
wantGate: features.MemoryRegionsBackend,
103+
},
104+
{
105+
name: "memory-regions gate does not enable direct-memory",
106+
gates: features.Gates{features.MemoryRegionsBackend: true},
107+
config: directMemoryConfig(),
108+
wantCode: codes.FailedPrecondition,
109+
wantGate: features.DirectMemoryBackend,
62110
},
63111
{
64112
name: "gated config passes with its gate enabled",
@@ -85,8 +133,8 @@ func TestServer_CheckFeatureGates(t *testing.T) {
85133
t.Fatalf("checkFeatureGates() = %v, want code %v", err, tc.wantCode)
86134
}
87135
if tc.wantCode == codes.FailedPrecondition {
88-
if !strings.Contains(err.Error(), string(features.DirectMemoryBackend)) {
89-
t.Errorf("Expected error to name the gate, got: %v", err)
136+
if !strings.Contains(err.Error(), string(tc.wantGate)) {
137+
t.Errorf("Expected error to name gate %s, got: %v", tc.wantGate, err)
90138
}
91139
if !strings.Contains(err.Error(), "--feature-gates") {
92140
t.Errorf("Expected error to name the --feature-gates flag, got: %v", err)

0 commit comments

Comments
 (0)