PranavKK1201 commited on
Commit
4b5c463
·
1 Parent(s): 3a871a0

added VIP nodes, which have a higher weightage

Browse files
Files changed (5) hide show
  1. client.py +4 -0
  2. models.py +12 -0
  3. server/AntiAtropos_environment.py +24 -7
  4. simulator.py +15 -1
  5. stability.py +6 -1
client.py CHANGED
@@ -79,10 +79,12 @@ class AntiAtroposEnv(
79
  NodeObservation(
80
  node_id=n.get("node_id", ""),
81
  status=NodeStatus(n.get("status", NodeStatus.HEALTHY)),
 
82
  queue_depth=n.get("queue_depth", 0),
83
  latency_ms=n.get("latency_ms", 0.0),
84
  incoming_request_rate=n.get("incoming_request_rate", 0.0),
85
  cpu_utilization=n.get("cpu_utilization", 0.0),
 
86
  done=n.get("done", False),
87
  reward=n.get("reward", 0.0),
88
  )
@@ -102,6 +104,8 @@ class AntiAtroposEnv(
102
  step=obs_data.get("step", 0),
103
  max_steps=obs_data.get("max_steps", 100),
104
  sla_violations=obs_data.get("sla_violations", 0),
 
 
105
  done=payload.get("done", False),
106
  reward=payload.get("reward", 0.0),
107
  )
 
79
  NodeObservation(
80
  node_id=n.get("node_id", ""),
81
  status=NodeStatus(n.get("status", NodeStatus.HEALTHY)),
82
+ is_vip=n.get("is_vip", False),
83
  queue_depth=n.get("queue_depth", 0),
84
  latency_ms=n.get("latency_ms", 0.0),
85
  incoming_request_rate=n.get("incoming_request_rate", 0.0),
86
  cpu_utilization=n.get("cpu_utilization", 0.0),
87
+ importance_weight=n.get("importance_weight", 1.0),
88
  done=n.get("done", False),
89
  reward=n.get("reward", 0.0),
90
  )
 
104
  step=obs_data.get("step", 0),
105
  max_steps=obs_data.get("max_steps", 100),
106
  sla_violations=obs_data.get("sla_violations", 0),
107
+ invalid_action_count=obs_data.get("invalid_action_count", 0),
108
+ vip_failure_count=obs_data.get("vip_failure_count", 0),
109
  done=payload.get("done", False),
110
  reward=payload.get("reward", 0.0),
111
  )
models.py CHANGED
@@ -40,6 +40,7 @@ class NodeObservation(BaseModel):
40
  """Telemetry for a single service instance (node)."""
41
  node_id: str
42
  status: NodeStatus
 
43
 
44
  # All numerical telemetry is normalized to [0, 1] for RL stability.
45
  queue_depth: float = Field(
@@ -72,6 +73,12 @@ class NodeObservation(BaseModel):
72
  description="Estimated CPU load [0.0, 1.0].",
73
  )
74
 
 
 
 
 
 
 
75
  # Episode interaction fields (handled by framework)
76
  done: bool = False
77
  reward: float = 0.0
@@ -129,6 +136,11 @@ class ClusterObservation(BaseModel):
129
  description="Number of forbidden actions (e.g. SHED_LOAD on critical nodes).",
130
  )
131
 
 
 
 
 
 
132
  nodes: list[NodeObservation]
133
 
134
  # Episode interaction fields (handled by framework)
 
40
  """Telemetry for a single service instance (node)."""
41
  node_id: str
42
  status: NodeStatus
43
+ is_vip: bool = False
44
 
45
  # All numerical telemetry is normalized to [0, 1] for RL stability.
46
  queue_depth: float = Field(
 
73
  description="Estimated CPU load [0.0, 1.0].",
74
  )
75
 
76
+ importance_weight: float = Field(
77
+ default=1.0,
78
+ ge=0.0,
79
+ description="Business criticality weight. VIP nodes have higher impact on scoring.",
80
+ )
81
+
82
  # Episode interaction fields (handled by framework)
83
  done: bool = False
84
  reward: float = 0.0
 
136
  description="Number of forbidden actions (e.g. SHED_LOAD on critical nodes).",
137
  )
138
 
139
+ vip_failure_count: int = Field(
140
+ default=0,
141
+ description="Number of failed VIP nodes in the current observation.",
142
+ )
143
+
144
  nodes: list[NodeObservation]
145
 
146
  # Episode interaction fields (handled by framework)
server/AntiAtropos_environment.py CHANGED
@@ -156,24 +156,38 @@ class AntiAtroposEnvironment(Environment):
156
  return total_capacity_units * COST_PER_CAPACITY_UNIT_PER_HOUR
157
 
158
  def _avg_latency(self, nodes: list[dict]) -> float:
159
- """Computes mean latency across all non-failed nodes."""
160
- active = [n for n in nodes if n["status"] != NodeStatus.FAILED]
161
- if not active:
162
  return float("inf")
163
- return sum(n["latency_ms"] for n in active) / len(active)
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  def _error_rate(self, nodes: list[dict]) -> float:
166
- """Calculates the cluster-wide fraction of dropped or lost requests."""
167
- total_incoming = sum(n.get("incoming_request_rate", 0.0) for n in nodes)
168
  if total_incoming <= 0:
169
  return 0.0
170
 
171
  # dropped_requests already includes both:
172
  # - explicit shedding (SHED_LOAD)
173
  # - traffic sent to FAILED nodes in simulator._update_queues
174
- total_drops = sum(n.get("dropped_requests", 0.0) for n in nodes)
175
  return min(1.0, total_drops / total_incoming)
176
 
 
 
 
 
177
  def _build_observation(self) -> ClusterObservation:
178
  """Assembles the ClusterObservation from the current observed simulator state."""
179
  node_obs = [
@@ -184,6 +198,8 @@ class AntiAtroposEnvironment(Environment):
184
  latency_ms=min(1.0, max(0.0, float(n["latency_ms"]) / MAX_LATENCY_NORM)),
185
  incoming_request_rate=min(1.0, max(0.0, float(n["incoming_request_rate"]) / MAX_REQUEST_RATE_NORM)),
186
  cpu_utilization=min(1.0, max(0.0, float(n["cpu_utilization"]))),
 
 
187
  done=False,
188
  reward=0.0,
189
  )
@@ -207,6 +223,7 @@ class AntiAtroposEnvironment(Environment):
207
  max_steps=MAX_STEPS,
208
  sla_violations=self._sla_violations,
209
  invalid_action_count=self._sim.invalid_action_count,
 
210
  done=False,
211
  reward=0.0,
212
  )
 
156
  return total_capacity_units * COST_PER_CAPACITY_UNIT_PER_HOUR
157
 
158
  def _avg_latency(self, nodes: list[dict]) -> float:
159
+ """Computes importance-weighted mean latency across the cluster."""
160
+ if not nodes:
 
161
  return float("inf")
162
+
163
+ weighted_latency = 0.0
164
+ total_weight = 0.0
165
+ for n in nodes:
166
+ weight = float(n.get("importance_weight", 1.0))
167
+ latency = MAX_LATENCY_NORM if n["status"] == NodeStatus.FAILED else float(n["latency_ms"])
168
+ weighted_latency += weight * latency
169
+ total_weight += weight
170
+
171
+ if total_weight <= 0:
172
+ return float("inf")
173
+ return weighted_latency / total_weight
174
 
175
  def _error_rate(self, nodes: list[dict]) -> float:
176
+ """Calculates an importance-weighted fraction of dropped or lost requests."""
177
+ total_incoming = sum(float(n.get("incoming_request_rate", 0.0)) * float(n.get("importance_weight", 1.0)) for n in nodes)
178
  if total_incoming <= 0:
179
  return 0.0
180
 
181
  # dropped_requests already includes both:
182
  # - explicit shedding (SHED_LOAD)
183
  # - traffic sent to FAILED nodes in simulator._update_queues
184
+ total_drops = sum(float(n.get("dropped_requests", 0.0)) * float(n.get("importance_weight", 1.0)) for n in nodes)
185
  return min(1.0, total_drops / total_incoming)
186
 
187
+ def _vip_failure_count(self, nodes: list[dict]) -> int:
188
+ """Counts failed VIP nodes for reporting and diagnostics."""
189
+ return sum(1 for n in nodes if n.get("is_vip") and n["status"] == NodeStatus.FAILED)
190
+
191
  def _build_observation(self) -> ClusterObservation:
192
  """Assembles the ClusterObservation from the current observed simulator state."""
193
  node_obs = [
 
198
  latency_ms=min(1.0, max(0.0, float(n["latency_ms"]) / MAX_LATENCY_NORM)),
199
  incoming_request_rate=min(1.0, max(0.0, float(n["incoming_request_rate"]) / MAX_REQUEST_RATE_NORM)),
200
  cpu_utilization=min(1.0, max(0.0, float(n["cpu_utilization"]))),
201
+ is_vip=bool(n.get("is_vip", False)),
202
+ importance_weight=float(n.get("importance_weight", 1.0)),
203
  done=False,
204
  reward=0.0,
205
  )
 
223
  max_steps=MAX_STEPS,
224
  sla_violations=self._sla_violations,
225
  invalid_action_count=self._sim.invalid_action_count,
226
+ vip_failure_count=self._vip_failure_count(self._nodes_true),
227
  done=False,
228
  reward=0.0,
229
  )
simulator.py CHANGED
@@ -59,6 +59,12 @@ T3_SURGE_MAGNITUDE: float = 70.0 # Extra req/tick added to node-1 and node-2
59
  # In Task 3, these receive the surge. Forcing the agent to SCALE.
60
  CRITICAL_NODES: list[str] = ["node-0", "node-1", "node-2"]
61
 
 
 
 
 
 
 
62
 
63
  class NodeStatus(str, Enum):
64
  HEALTHY = "HEALTHY"
@@ -70,9 +76,11 @@ class NodeStatus(str, Enum):
70
  class NodeState:
71
  node_id: str
72
  status: NodeStatus = NodeStatus.HEALTHY
 
73
 
74
  # Physics parameters
75
  capacity: float = DEFAULT_CAPACITY
 
76
  queue_depth: float = 0.0
77
  latency_ms: float = BASE_LATENCY_MS
78
  incoming_request_rate: float = 0.0
@@ -95,7 +103,9 @@ class NodeState:
95
  return {
96
  "node_id": self.node_id,
97
  "status": self.status,
 
98
  "capacity": self.capacity,
 
99
  "queue_depth": int(self.queue_depth),
100
  "latency_ms": round(self.latency_ms, 2),
101
  "incoming_request_rate": round(self.incoming_request_rate, 2),
@@ -161,7 +171,11 @@ class ClusterSimulator:
161
 
162
  def _reset_nodes(self) -> None:
163
  self._nodes = [
164
- NodeState(node_id=f"node-{i}")
 
 
 
 
165
  for i in range(self._n_nodes)
166
  ]
167
 
 
59
  # In Task 3, these receive the surge. Forcing the agent to SCALE.
60
  CRITICAL_NODES: list[str] = ["node-0", "node-1", "node-2"]
61
 
62
+ # VIP / business-critical node weights.
63
+ # node-0 is the payment portal, so its queue growth or failure matters more.
64
+ VIP_NODE_WEIGHTS: dict[str, float] = {
65
+ "node-0": 4.0,
66
+ }
67
+
68
 
69
  class NodeStatus(str, Enum):
70
  HEALTHY = "HEALTHY"
 
76
  class NodeState:
77
  node_id: str
78
  status: NodeStatus = NodeStatus.HEALTHY
79
+ is_vip: bool = False
80
 
81
  # Physics parameters
82
  capacity: float = DEFAULT_CAPACITY
83
+ importance_weight: float = 1.0
84
  queue_depth: float = 0.0
85
  latency_ms: float = BASE_LATENCY_MS
86
  incoming_request_rate: float = 0.0
 
103
  return {
104
  "node_id": self.node_id,
105
  "status": self.status,
106
+ "is_vip": self.is_vip,
107
  "capacity": self.capacity,
108
+ "importance_weight": self.importance_weight,
109
  "queue_depth": int(self.queue_depth),
110
  "latency_ms": round(self.latency_ms, 2),
111
  "incoming_request_rate": round(self.incoming_request_rate, 2),
 
171
 
172
  def _reset_nodes(self) -> None:
173
  self._nodes = [
174
+ NodeState(
175
+ node_id=f"node-{i}",
176
+ is_vip=f"node-{i}" in VIP_NODE_WEIGHTS,
177
+ importance_weight=VIP_NODE_WEIGHTS.get(f"node-{i}", 1.0),
178
+ )
179
  for i in range(self._n_nodes)
180
  ]
181
 
stability.py CHANGED
@@ -75,7 +75,12 @@ def compute_lyapunov(nodes: list[dict]) -> float:
75
  Returns:
76
  Scalar Lyapunov energy ≥ 0.
77
  """
78
- return float(sum(n["queue_depth"] ** 2 for n in nodes))
 
 
 
 
 
79
 
80
 
81
  def compute_drift(v_prev: float, v_curr: float) -> float:
 
75
  Returns:
76
  Scalar Lyapunov energy ≥ 0.
77
  """
78
+ return float(
79
+ sum(
80
+ float(n.get("importance_weight", 1.0)) * (n["queue_depth"] ** 2)
81
+ for n in nodes
82
+ )
83
+ )
84
 
85
 
86
  def compute_drift(v_prev: float, v_curr: float) -> float: