Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b977e52
properly escape quotes so commands with json will work.
HacksonClark Mar 6, 2025
a39de4d
This error message isn't for actual errors, so I'm removing it
HacksonClark Mar 6, 2025
fb585e6
Added code for onboarding task evaluation
HacksonClark Mar 6, 2025
1952aa5
Add console logs since we no longer use env
HacksonClark Mar 6, 2025
2d3d79a
parse results before creating json
HacksonClark Mar 6, 2025
8bcdfaa
Get name for results file
HacksonClark Mar 6, 2025
e536280
typo fix
HacksonClark Mar 6, 2025
ce23d78
Add task message for assessment
HacksonClark Mar 6, 2025
ccb2620
Add hint to task message
HacksonClark Mar 6, 2025
8a53f07
rename onboarding related files
yinfangchen Mar 19, 2025
c588825
Merge pull request #31 from xlab-uiuc/assessment
HacksonClark Mar 19, 2025
7cb7ff3
Handle errors from get_traces&get_metrics
daklqw Mar 19, 2025
fdbf62c
Merge pull request #33 from xlab-uiuc/ask_env-fix
HacksonClark Mar 20, 2025
9c0ef3c
fix the issue of getting log of the workload pod
yinfangchen Mar 20, 2025
17916ed
Merge pull request #34 from xlab-uiuc/default-namespace-handle
yinfangchen Mar 20, 2025
c7c44a3
Add helm as a requirement to README
HacksonClark Mar 20, 2025
32bbda8
Fixed link
HacksonClark Mar 21, 2025
12db7bc
Update README.md
yinfangchen Mar 21, 2025
ad0d5a5
Merge pull request #36 from xlab-uiuc/JacksonArthurClark-patch-1
yinfangchen Mar 21, 2025
cab9e0a
fix return value of read operations
yinfangchen Mar 27, 2025
45b0044
Merge pull request #39 from xlab-uiuc/fix-return-value-get
HacksonClark Mar 27, 2025
e984ed0
Update prometheus interface to use PVC everywhere
HacksonClark Mar 28, 2025
a858f6b
teardown openebs and prometheus during cleanup
HacksonClark Mar 28, 2025
c3a2ccf
Fix function name
HacksonClark Mar 28, 2025
ccc9c0d
fix function name
HacksonClark Mar 28, 2025
504f4d9
Delete pvc instead of pv
HacksonClark Mar 28, 2025
02a3e69
update parameter name
HacksonClark Mar 28, 2025
3509ac1
Fix issue 41 (Fix key error with TTA)
ChuanweiQu Apr 10, 2025
9711138
Issue 42 (Cleanup before exit)
ChuanweiQu Apr 10, 2025
30f4a42
This file is not closed
daklqw Apr 10, 2025
2cac6fc
Fix assign-non-existent-node evaluation issue
daklqw Apr 11, 2025
6d0fc72
Merge pull request #43 from xlab-uiuc/fix-TTA-key-error
HacksonClark Apr 14, 2025
5ac50aa
Merge pull request #45 from xlab-uiuc/patch-Apr-10
HacksonClark Apr 14, 2025
40029bb
Enhances trace processing with error and response tracking
daklqw Apr 14, 2025
59d923d
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 14, 2025
4407c81
Merge pull request #40 from xlab-uiuc/teardown
yinfangchen Apr 14, 2025
529aefd
Merge pull request #47 from xlab-uiuc/trace-improve
HacksonClark Apr 14, 2025
4cacda9
fix service name issue
daklqw Apr 15, 2025
2751063
Merge remote-tracking branch 'origin/main' into trace-improve
daklqw Apr 15, 2025
2448117
Task prompts
daklqw Apr 15, 2025
3ac8379
Update README.md
HacksonClark Apr 16, 2025
8f2eb4d
Merge pull request #49 from xlab-uiuc/HacksonClark-patch-1
yinfangchen Apr 16, 2025
b86a686
Polish prompt
daklqw Apr 16, 2025
7b9e52c
Merge pull request #50 from xlab-uiuc/trace-improve
yinfangchen Apr 16, 2025
0dca58a
Recover fault before users catch exceptions
ChuanweiQu Apr 17, 2025
868f504
Merge branch 'main' into fault-recovery-before-exit
ChuanweiQu Apr 17, 2025
8026265
Merge pull request #51 from xlab-uiuc/fault-recovery-before-exit
HacksonClark Apr 17, 2025
0741e0c
Add remote chart parameter
HacksonClark Apr 18, 2025
82ed33f
Switch to remote chart
HacksonClark Apr 18, 2025
37ad626
nit: no need to print every loop iteration.
HacksonClark Apr 18, 2025
4dc7c8e
Remove local path
HacksonClark Apr 18, 2025
ec69911
Only use TARGET_MICROSERVICES for local charts
HacksonClark Apr 18, 2025
a380b1f
Fix configmap name
HacksonClark Apr 18, 2025
d89a947
Fix ad service failure name from configmap.
HacksonClark Apr 18, 2025
943ed0b
Restart so changes take effect
HacksonClark Apr 18, 2025
578d951
Update name.
HacksonClark Apr 18, 2025
740a4ae
Fix name to match configmap
HacksonClark Apr 18, 2025
487f522
Fix cm name
HacksonClark Apr 18, 2025
a2d65b5
change to cartFailure
HacksonClark Apr 18, 2025
a01abad
update cm name
HacksonClark Apr 18, 2025
35c9236
Fix names
HacksonClark Apr 18, 2025
f8f8374
Add proxy configuration tips for AIOpsLab users
Flemington8 Apr 20, 2025
67a0e74
Refine task descriptions and instructions for smaller models to ensur…
Flemington8 Apr 21, 2025
e14d413
Update README and llm.py to support .env file for API keys management
Flemington8 Apr 21, 2025
c52b816
Add Weights & Biases integration for session logging and orchestrator…
Flemington8 Apr 21, 2025
a862d39
Refactor W&B integration to use environment variable for configuratio…
Flemington8 Apr 21, 2025
a3d7403
Fix typo in W&B initialization comment for clarity
Flemington8 Apr 21, 2025
cad460d
Load environment variables from .env file to check the presence of "U…
Flemington8 Apr 21, 2025
5d19677
Merge pull request #52 from xlab-uiuc/astronomy-shop-fixes
yinfangchen Apr 21, 2025
d9e6aa4
Refactor Orchestrator initialization to use environment variable for …
Flemington8 Apr 22, 2025
fe63d75
Merge branch 'xlab-uiuc:main' into main
Flemington8 Apr 22, 2025
d887517
Merge pull request #53 from Flemington8/main
HacksonClark Apr 22, 2025
a1e0234
Add new clients and update README with usage instructions
Flemington8 Apr 22, 2025
ec64ea4
Add wandb dependency
HacksonClark Apr 22, 2025
c7edeae
Merge pull request #57 from xlab-uiuc/wandb-dep
HacksonClark Apr 22, 2025
30e1480
Merge remote-tracking branch 'origin/main' into flemington8-main
HacksonClark Apr 23, 2025
2b41afd
Regenerated lock file
HacksonClark Apr 23, 2025
d21f015
Merge pull request #56 from Flemington8/main
HacksonClark Apr 23, 2025
d820fa8
update faulty service to match actual pod names
HacksonClark Apr 23, 2025
aa0d4e0
Merge pull request #60 from xlab-uiuc/astro-localization
HacksonClark Apr 23, 2025
d3bfc37
Add helper function to get CPU architecture of k8s cluster
HacksonClark Apr 24, 2025
317ffd9
Update arch check to check on node and not local.
HacksonClark Apr 24, 2025
5e8d648
Merge pull request #62 from xlab-uiuc/remote-arch
HacksonClark Apr 24, 2025
93846c6
fix frontend service name
HacksonClark Apr 24, 2025
3c32fbf
Add noop problem for astronomy shop
HacksonClark Apr 24, 2025
7f0edd8
Merge pull request #64 from xlab-uiuc/noop-astro
HacksonClark Apr 24, 2025
e77d1ce
Remove operator problems, they are not mature enough for eval
HacksonClark Apr 24, 2025
704f462
Fix trace api in astronomy shop
HacksonClark Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,6 +327,9 @@ cython_debug/
# Visual Studio Code
.vscode/

# Weight & Biases
wandb/

# Project specific
cache_dir
demos
Expand Down
36 changes: 34 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,7 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to

### Requirements
- Python >= 3.11
- [Helm](https://helm.sh/)

Recommended installation:
```bash
Expand DownExpand Up@@ -64,10 +65,23 @@ kind create cluster --config kind/kind-config-arm.yaml

If you're running into issues, consider building a Docker image for your machine by following this [README](kind/README.md). Please also open an issue.

### [Tips]
If you are running AIOpsLab using a proxy, beware of exporting the HTTP proxy as `172.17.0.1`. When creating the kind cluster, all the nodes in the cluster will inherit the proxy setting from the host environment and the Docker container.

The `172.17.0.1` address is used to communicate with the host machine. For more details, refer to the official guide: [Configure Kind to Use a Proxy](https://kind.sigs.k8s.io/docs/user/quick-start/#configure-kind-to-use-a-proxy).

Additionally, Docker doesn't support SOCKS5 proxy directly. If you're using a SOCKS5 protocol to proxy, you may need to use [Privoxy](https://www.privoxy.org) to forward SOCKS5 to HTTP.

If you're running VLLM and the LLM agent locally, Privoxy will by default proxy `localhost`, which will cause errors. To avoid this issue, you should set the following environment variable:

```bash
export no_proxy=localhost
```

After finishing cluster creation, proceed to the next "Update `config.yml`" step.

### b) Remote cluster
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks we have to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.
AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step.

### Update `config.yml`
```bash
Expand All@@ -89,12 +103,30 @@ python3 cli.py
Run GPT-4 baseline agent:

```bash
export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
# Create a .env file in the project root (if not exists)
echo "OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>" > .env
# Add more API keys as needed:
# echo "QWEN_API_KEY=<YOUR_QWEN_API_KEY>" >> .env
# echo "DEEPSEEK_API_KEY=<YOUR_DEEPSEEK_API_KEY>" >> .env

python3 clients/gpt.py # you can also change the problem to solve in the main() function
```

The clients will automatically load API keys from your .env file.

You can check the running status of the cluster using [k9s](https://k9scli.io/) or other cluster monitoring tools conveniently.

To browse your logged `session_id` values in the W&B app as a table:

1. Make sure you have W&B installed and configured.
2. Set the USE_WANDB environment variable:
```bash
# Add to your .env file
echo "USE_WANDB=true" >> .env
```
3. In the W&B web UI, open any run and click Tables → Add Query Panel.
4. In the key field, type `runs.summary` and click `Run`, then you will see the results displayed in a table format.

<h2 id="⚙️usage">⚙️ Usage</h2>

AIOpsLab can be used in the following ways:
Expand Down
1 change: 0 additions & 1 deletion aiopslab/generators/fault/base.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ def _recover(
self._invoke_method("recover", fault_type, microservices)
elif fault_type:
self._invoke_method("recover", fault_type)
time.sleep(6)

def _invoke_method(self, action_prefix, *args):
"""helper: injects/recovers faults based on name"""
Expand Down
11 changes: 10 additions & 1 deletion aiopslab/generators/fault/inject_otel.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ class OtelFaultInjector(FaultInjector):
def __init__(self, namespace: str):
self.namespace = namespace
self.kubectl = KubeCtl()
self.configmap_name = f"{namespace}-flagd-config"
self.configmap_name = "flagd-config"

def inject_fault(self, feature_flag: str):
command = (
Expand DownExpand Up@@ -39,6 +39,11 @@ def inject_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)

print(f"Fault injected: Feature flag '{feature_flag}' set to 'on'.")

def recover_fault(self, feature_flag: str):
Expand DownExpand Up@@ -70,6 +75,10 @@ def recover_fault(self, feature_flag: str):
self.kubectl.create_or_update_configmap(
self.configmap_name, self.namespace, updated_data
)

self.kubectl.exec_command(
f"kubectl rollout restart deployment flagd -n {self.namespace}"
)
print(f"Fault recovered: Feature flag '{feature_flag}' set to 'off'.")


Expand Down
3 changes: 2 additions & 1 deletion aiopslab/observer/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,8 @@
root_path = pathlib.Path(__file__).parent
sys.path.append(root_path)
# read the configuration file
monitor_config = full_load(open(root_path / "monitor_config.yaml", "r"))
with open(root_path / "monitor_config.yaml", "r") as f:
monitor_config = full_load(f)


# root_config = full_load(open(root_path / "config.yaml", "r"))
Expand Down
110 changes: 76 additions & 34 deletions aiopslab/observer/trace_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,13 +22,18 @@ def __init__(self, namespace: str):
self.namespace = namespace
self.stop_event = threading.Event()

# NOTE: it may not be jaeger-out for other apps
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
if self.namespace == "astronomy-shop":
# No NodePort in astronomy shop
self.base_url = "http://localhost:16686/jaeger/ui"
self.start_port_forward()
else:
# Other namespaces may expose a NodePort
node_port = self.get_nodeport("jaeger", namespace)
if node_port:
self.base_url = f"http://localhost:{node_port}"
else:
self.base_url = "http://localhost:16686"
self.start_port_forward()

def get_nodeport(self, service_name, namespace):
"""Fetch the NodePort for the given service."""
Expand DownExpand Up@@ -74,19 +79,38 @@ def print_output(self, stream):
def is_port_in_use(self, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0

def get_jaeger_pod_name(self):
try:
result = subprocess.check_output(
["kubectl", "get", "pods", "-n", self.namespace,
"-l", "app.kubernetes.io/name=jaeger",
"-o", "jsonpath={.items[0].metadata.name}"],
text=True
)
return result.strip()
except subprocess.CalledProcessError as e:
print("Error getting Jaeger pod name:", e)
raise

def start_port_forward(self):
"""Starts kubectl port-forward command to access Jaeger service."""
"""Starts kubectl port-forward command to access Jaeger service or pod."""
for attempt in range(3):
if self.is_port_in_use(16686):
print(
f"Port 16686 is already in use. Attempt {attempt + 1} of {3}. Retrying in {3} seconds..."
f"Port 16686 is already in use. Attempt {attempt + 1} of 3. Retrying in 3 seconds..."
)
time.sleep(3)
continue

# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"
# Use pod port-forwarding for astronomy-shop only
if self.namespace == "astronomy-shop":
pod_name = self.get_jaeger_pod_name()
command = f"kubectl port-forward pod/{pod_name} 16686:16686 -n {self.namespace}"
else:
command = f"kubectl port-forward svc/jaeger 16686:16686 -n {self.namespace}"

print("Starting port-forward with command:", command)
self.port_forward_process = subprocess.Popen(
command,
shell=True,
Expand All@@ -103,17 +127,17 @@ def start_port_forward(self):
)
thread_out.start()
thread_err.start()
time.sleep(3) # Wait a bit for the port-forward to establish

if (
self.port_forward_process.poll() is None
): # Check if the process is still running
time.sleep(3) # Let port-forward initialize

if self.port_forward_process.poll() is None:
print("Port forwarding established successfully.")
break
else:
print("Port forwarding failed. Retrying...")
else:
print("Failed to establish port forwarding after multiple attempts.")
print("Failed to establish port forwarding after 3 attempts.")

# TODO: modify this command for other microservices
# command = "kubectl port-forward svc/jaeger 16686:16686 -n hotel-reservation"
# self.port_forward_process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
Expand DownExpand Up@@ -151,13 +175,14 @@ def cleanup(self):
def get_services(self) -> list:
"""Fetch a list of services from the tracing API."""
url = f"{self.base_url}/api/services"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# print(f"data: {response}")
return data.get("data", [])
else:
print(f"Failed to get services: {response.status_code}")
headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except Exception as e:
print(f"Failed to get services: {e}")
return []

def get_traces(
Expand All@@ -171,15 +196,14 @@ def get_traces(
Fetch traces for a specific service between start_time and end_time.
If limit is not specified, all available traces are fetched.
"""
# Calculate the lookback in milliseconds.
lookback = int((datetime.now() - start_time).total_seconds())

url = f"{self.base_url}/api/traces?service={service_name}&lookback={lookback}s"
if limit is not None:
url += f"&limit={limit}"

headers = {"Accept": "application/json"} if self.namespace == "astronomy-shop" else {}
try:
response = requests.get(url)
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
except requests.RequestException as e:
Expand DownExpand Up@@ -211,9 +235,9 @@ def extract_traces(
)
for trace in traces:
for span in trace["spans"]:
span[
span["serviceName"] = trace["processes"][span["processID"]][
"serviceName"
] = service # Directly associate service name with each span
]
all_traces.append(trace) # Collect the trace with service name included
self.cleanup()
print("Cleanup completed.")
Expand All@@ -223,22 +247,20 @@ def extract_traces(
def process_traces(self, traces) -> pd.DataFrame:
"""Process raw traces data into a structured DataFrame."""
trace_id_list = []
span_id_list = []
service_name_list = []
operation_name_list = []
start_time_list = []
duration_list = []
parent_span_list = []
error_list = []
response_list = []

for trace in traces:
trace_id = trace["traceID"]
for span in trace["spans"]:
trace_id_list.append(trace_id)
service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])
span_id_list.append(span["spanID"])
parent_span = "ROOT"
if "references" in span:
for ref in span["references"]:
Expand All@@ -247,14 +269,34 @@ def process_traces(self, traces) -> pd.DataFrame:
break
parent_span_list.append(parent_span)

service_name_list.append(
span["serviceName"]
) # Use the correct service name from the span
operation_name_list.append(span["operationName"])
start_time_list.append(span["startTime"])
duration_list.append(span["duration"])

has_error = False
response = "Unknown"
for tag in span.get("tags", []):
if tag["key"] == "error" and tag["value"] == True:
has_error = True
if tag["key"] == "http.status_code" or tag["key"] == "response_class":
response = tag["value"]
error_list.append(has_error)
response_list.append(response)

df = pd.DataFrame(
{
"trace_id": trace_id_list,
"span_id": span_id_list,
"parent_span": parent_span_list,
"service_name": service_name_list,
"operation_name": operation_name_list,
"start_time": start_time_list,
"duration": duration_list,
"parent_span": parent_span_list,
"has_error": error_list,
"response": response_list,
}
)
return df
Expand Down
Loading