Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
9 changes: 6 additions & 3 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ name: Pylint

on: [push]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -21,4 +24,4 @@ jobs:
pip install -r requirements.txt
- name: Analysing the code with pylint
run: |
# pylint $(git ls-files '*.py')
pylint $(git ls-files '*.py')
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Tests

on: [push, pull_request]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Test package can be imported
run: |
python -c "import gpttrace; print('Import successful')"
- name: Run unit tests (non-interactive)
run: |
# Note: Full tests require user input and are skipped in CI
# Run only import and basic syntax checks
python -c "from gpttrace.bpftrace import TestRunBpftrace; print('Test class imported successfully')"

8 changes: 4 additions & 4 deletions gpttrace/GPTtrace.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
"""GPTtrace main entry point module."""
#! /bin/env python
import argparse
import os
import pathlib

from gpttrace.cmd import cmd
from gpttrace.execute import execute
from gpttrace.utils.common import pretty_print


def main() -> None:
Expand All @@ -27,13 +26,14 @@ def main() -> None:
action="store_true")
parser.add_argument(
"-k", "--key",
help="Openai api key, see `https://platform.openai.com/docs/quickstart/add-your-api-key` or passed through `OPENAI_API_KEY`",
help="OpenAI API key, see https://platform.openai.com or passed through OPENAI_API_KEY",
metavar="OPENAI_API_KEY")
parser.add_argument('input_string', type=str, help='Your question or request for a bpf program')
args = parser.parse_args()

if os.getenv('OPENAI_API_KEY', args.key) is None:
print(f"Either provide your access token through `-k` or through environment variable {OPENAI_API_KEY}")
print("Either provide your access token through `-k` or through "
"environment variable OPENAI_API_KEY")
return
if args.cmd is not None:
cmd(args.cmd[0], args.cmd[1], args.verbose)
Expand Down
1 change: 1 addition & 0 deletions gpttrace/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
"""GPTtrace package initialization."""
from gpttrace.GPTtrace import main
__version__ = "0.1.2"
6 changes: 5 additions & 1 deletion gpttrace/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# This file allows the construction of executable file. If there is no such file, only the package is generated.
"""GPTtrace main execution entry point.

This file allows the construction of executable file.
If there is no such file, only the package is generated.
"""
from gpttrace.GPTtrace import main

main()
46 changes: 31 additions & 15 deletions gpttrace/bpftrace.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""BPFtrace integration module for GPTtrace."""
#!/bin/python
import subprocess
import openai
import json
import unittest
import subprocess
import sys
import threading
import unittest
from typing import List, TypedDict

import openai

functions = [
{
"name": "bpftrace",
Expand Down Expand Up @@ -97,6 +100,7 @@
]

class CommandResult(TypedDict):
"""Type definition for command execution results."""
command: str
stdout: str
stderr: str
Expand All @@ -111,12 +115,14 @@ def run_command_with_timeout(command: List[str], timeout: int) -> CommandResult:
user_input = input("Enter 'y' to proceed: ")
if user_input.lower() != 'y':
print("Aborting...")
exit()
sys.exit(1)
# Start the process
with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as process:
with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True) as process:
timer = threading.Timer(timeout, process.kill)
stdout = ""
stderr = ""
returncode = None
try:
# Set a timer to kill the process if it doesn't finish within the timeout
timer.start()
Expand All @@ -130,6 +136,7 @@ def run_command_with_timeout(command: List[str], timeout: int) -> CommandResult:
last_stdout, last_stderr = process.communicate()
stdout += last_stdout
stderr += last_stderr
returncode = process.returncode
except Exception as e:
print("Exception: " + str(e))
finally:
Expand All @@ -141,12 +148,15 @@ def run_command_with_timeout(command: List[str], timeout: int) -> CommandResult:
if process.poll() is None and process.stderr.readable():
stderr += process.stderr.read()
print(stderr)
return {
"command": ' '.join(command),
"stdout": stdout,
"stderr": stderr,
"returncode": process.returncode
}
# Ensure returncode is set even if there was an exception
if returncode is None and process.poll() is not None:
returncode = process.returncode
return {
"command": ' '.join(command),
"stdout": stdout,
"stderr": stderr,
"returncode": returncode
}


def construct_command(operation: dict) -> list:
Expand All @@ -167,8 +177,8 @@ def construct_command(operation: dict) -> list:
if "program" in operation:
cmd += ["-e", operation["program"]]
if "includeDir" in operation:
for dir in operation["includeDir"]:
cmd += ["-I", dir]
for include_dir in operation["includeDir"]:
cmd += ["-I", include_dir]
if "usdtFileActivation" in operation and operation["usdtFileActivation"]:
cmd += ["--usdt-file-activation"]
if "unsafe" in operation and operation["unsafe"]:
Expand Down Expand Up @@ -230,7 +240,7 @@ def run_bpftrace(prompt: str, verbose: bool = False) -> CommandResult:
filename = args["filename"]
print("Save to file: " + filename)
print(args["content"])
with open(filename, 'w') as file:
with open(filename, 'w', encoding='utf-8') as file:
file.write(args["content"])
res = {
"command": "SaveFile",
Expand All @@ -250,12 +260,16 @@ def run_bpftrace(prompt: str, verbose: bool = False) -> CommandResult:


class TestRunBpftrace(unittest.TestCase):
"""Test cases for bpftrace integration."""

def test_summary(self):
"""Test running bpftrace with a summary request."""
res = run_bpftrace("tracing with Count page faults by process for 3s")
print(res)
print(res["stderr"])

def test_construct_command(self):
"""Test construction of bpftrace commands."""
operation_json = """
{
"bufferingMode": "full",
Expand All @@ -271,6 +285,7 @@ def test_construct_command(self):
print(command)

def test_construct_complex_command(self):
"""Test construction of complex bpftrace commands."""
operation_json = """
{
"bufferingMode": "full",
Expand All @@ -292,10 +307,11 @@ def test_construct_complex_command(self):
print(command)

def test_run_command_with_timeout_short_live(self):
"""Test running a short-lived command with timeout."""
command = ["ls", "-l"]
timeout = 5
result = run_command_with_timeout(command, timeout)
print(result)
self.assert_(result["stdout"] != "")
self.assertNotEqual(result["stdout"], "")
self.assertEqual(result["command"], "ls -l")
self.assertEqual(result["returncode"], 0)
13 changes: 8 additions & 5 deletions gpttrace/cmd.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import subprocess
import openai
"""BCC tool command generation and execution module."""
import json
import subprocess

import openai
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_community.chat_models import ChatOpenAI
from langchain.chains import ConversationChain
from gpttrace.prompt import func_call_prompt

from gpttrace.config import cfg
from gpttrace.prompt import func_call_prompt

def cmd(cmd_name: str, query: str, verbose=False) -> None:
"""
Expand Down Expand Up @@ -43,7 +45,8 @@ def cmd(cmd_name: str, query: str, verbose=False) -> None:
print("LLM does not call any bcc tools.")


def exec_cmd(cmd_name: str, args: str, func_descript: json) -> None:
def exec_cmd(cmd_name: str, args: str,
func_descript: json) -> None:
"""
Execute the command

Expand Down
1 change: 1 addition & 0 deletions gpttrace/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Configuration management for GPTtrace."""
import os

from getpass import getpass
Expand Down
18 changes: 14 additions & 4 deletions gpttrace/examples.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Example eBPF programs and vector database utilities."""
import os
from langchain_community.document_loaders import JSONLoader
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
# from langchain.embeddings.openai import OpenAIEmbeddings
# from langchain.vectorstores import DocArrayInMemorySearch
# from langchain.document_loaders import DirectoryLoader
Expand Down Expand Up @@ -55,6 +56,7 @@
"""

def get_bpftrace_basic_examples(query: str) -> str:
"""Get basic bpftrace examples from vector database."""
loader = JSONLoader(
file_path='./tools/examples.json',
jq_schema='.data[].content',
Expand All @@ -64,23 +66,31 @@ def get_bpftrace_basic_examples(query: str) -> str:
embeddings = OpenAIEmbeddings()

# Check if the vector database files exist
if not (os.path.exists("./data_save/vector_db.faiss") and os.path.exists("./data_save/vector_db.pkl")):
if not (os.path.exists("./data_save/vector_db.faiss") and
os.path.exists("./data_save/vector_db.pkl")):
db = FAISS.from_documents(documents, embeddings)
db.save_local("./data_save", index_name="vector_db")
else:
# Load an existing FAISS vector store
db = FAISS.load_local("./data_save", index_name="vector_db", embeddings=embeddings)
db = FAISS.load_local(
"./data_save",
index_name="vector_db",
embeddings=embeddings,
allow_dangerous_deserialization=True,
)

results = db.search(query, search_type='similarity')
results = [result.page_content for result in results]
return "\n".join(results[:2])

def construct_bpftrace_examples(text: str) -> str:
"""Construct bpftrace examples for a given query."""
examples = get_bpftrace_basic_examples(text)
# docs = db.similarity_search(text)
# examples += "\n The following is a more complex example: \n"
# examples += docs[0].page_content
return examples

if __name__ == "__main__":
get_bpftrace_basic_examples("Trace allocations and display each individual allocator function call")
get_bpftrace_basic_examples(
"Trace allocations and display each individual allocator function call")
23 changes: 13 additions & 10 deletions gpttrace/execute.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import os
"""eBPF program execution module."""
import json
import shutil
import tempfile

import openai

from gpttrace.utils.common import get_doc_content_for_query, init_conversation
from gpttrace.prompt import construct_running_prompt, construct_prompt_on_error, construct_prompt_for_explain
from gpttrace.bpftrace import run_bpftrace
from gpttrace.prompt import (
construct_prompt_for_explain,
construct_prompt_on_error,
construct_running_prompt
)

# litellm is optional and only used if call_litellm is called
try:
Expand All @@ -33,17 +35,19 @@ def call_litellm(prompt: str) -> str:
OpenAI, Azure, Cohere, Anthropic, Replicate models supported
"""
if not HAS_LITELLM:
raise ImportError("litellm is not installed. Install it with: pip install litellm")
raise ImportError(
"litellm is not installed. Install it with: pip install litellm")
messages = [{"role": "user", "content": prompt}]
# see supported models here:
# see supported models here:
# https://litellm.readthedocs.io/en/latest/supported/
response = completion(
model="claude-instant-1",
messages=messages,
)
return response["choices"][0]["message"]["content"]

def execute(user_input: str, verbose: bool = False, retry: int = 5, previous_prompt: str = None, output: str = None) -> None:
def execute(user_input: str, verbose: bool = False, retry: int = 5,
previous_prompt: str = None, output: str = None) -> None:
"""
Convert the user request into a BPF command and execute it.

Expand All @@ -67,7 +71,7 @@ def execute(user_input: str, verbose: bool = False, retry: int = 5, previous_pro
print("output: " + json.dumps(res))
print("retry time " + str(retry) + "...")
# retry
res = execute(user_input, verbose, retry - 1, prompt, json.dumps(res))
return execute(user_input, verbose, retry - 1, prompt, json.dumps(res))
else:
# success
print("AI explanation:")
Expand All @@ -76,4 +80,3 @@ def execute(user_input: str, verbose: bool = False, retry: int = 5, previous_pro
print("Prompt: " + prompt)
explain = call_gpt_api(prompt)
print(explain)

Loading
Loading