add job queue and code submission tests; remove unused main executable

This commit is contained in:
2025-03-25 23:05:26 +05:30
parent 67b39f3275
commit 574f754940
10 changed files with 1434 additions and 275 deletions

487
backend/Readme.md Normal file
View File

@@ -0,0 +1,487 @@
# Monaco Backend - Code Execution Service
## Table of Contents
1. Introduction
2. Architecture
3. Installation
4. API Reference
5. Code Execution
6. Job Queue System
7. Language Support
8. Security Considerations
9. Configuration
10. Testing
11. Performance Tuning
12. Troubleshooting
## Introduction
Monaco is a secure, containerized code execution backend service designed to run user-submitted code in multiple programming languages. It features a job queue system to manage execution resources, containerized execution environments for security, and a RESTful API for submission and monitoring.
**Key Features:**
- Multi-language support (Python, Java, C, C++)
- Secure containerized execution using Docker
- Resource limiting to prevent abuse
- Job queuing for managing concurrent executions
- Detailed execution statistics and monitoring
- Support for user input via stdin
- CORS support for browser-based clients
## Architecture
### Component Overview
Monaco follows a layered architecture with the following key components:
1. **HTTP Handlers** (handler package) - Processes incoming HTTP requests
2. **Execution Service** (service package) - Manages code execution in containers
3. **Job Queue** (queue package) - Controls concurrent execution
4. **Data Models** (model package) - Defines data structures
### Request Flow
1. Client submits code via `/submit` endpoint
2. Request is validated and assigned a unique ID
3. Submission is added to the job queue
4. Worker picks job from queue when available
5. Code is executed in appropriate Docker container
6. Results are stored and available via `/result` endpoint
### Dependency Diagram
```
Client Request → HTTP Handlers → Execution Service → Job Queue → Docker Containers
Data Models
```
## Installation
### Prerequisites
- Go 1.22+
- Docker Engine
- Docker images for supported languages:
- `python:3.9`
- `eclipse-temurin:11-jdk-alpine`
- `gcc:latest`
### Setup Instructions
1. Clone the repository:
```bash
git clone https://github.com/arnab-afk/monaco.git
cd monaco/backend
```
2. Install Go dependencies:
```bash
go mod download
```
3. Build the application:
```bash
go build -o monaco main.go
```
4. Run the service:
```bash
./monaco
```
The service will start on port 8080 by default.
## API Reference
### Endpoints
#### `POST /submit`
Submits code for execution.
**Request Body:**
```json
{
"language": "python", // Required: "python", "java", "c", or "cpp"
"code": "print('Hello, World!')", // Required: source code to execute
"input": "optional input string" // Optional: input to stdin
}
```
**Response:**
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1" // Unique ID for this submission
}
```
**Status Codes:**
- 202 Accepted - Code accepted for execution
- 400 Bad Request - Invalid request (e.g., unsupported language)
#### `GET /status?id={submissionId}`
Checks the status of a submission.
**Response:**
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed", // "pending", "queued", "running", "completed", "failed"
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z", // Only present if status is "running", "completed", or "failed"
"completedAt": "2025-03-25T14:30:02Z", // Only present if status is "completed" or "failed"
"executionTime": 1000 // Execution time in milliseconds (only if completed)
}
```
**Status Codes:**
- 200 OK - Status retrieved successfully
- 400 Bad Request - Missing ID parameter
- 404 Not Found - Submission with given ID not found
#### `GET /result?id={submissionId}`
Gets the execution result of a submission.
**Response:**
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed",
"language": "python",
"output": "Hello, World!",
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z",
"completedAt": "2025-03-25T14:30:02Z",
"executionTime": 1000,
"executionTimeFormatted": "1.0s",
"totalTime": 2000,
"totalTimeFormatted": "2.0s"
}
```
**Status Codes:**
- 200 OK - Result retrieved successfully
- 400 Bad Request - Missing ID parameter
- 404 Not Found - Submission with given ID not found
#### `GET /queue-stats`
Gets statistics about the job queue.
**Response:**
```json
{
"queue_stats": {
"queue_length": 5,
"max_workers": 3,
"running_jobs": 3
},
"submissions": 42
}
```
## Code Execution
### Execution Process
1. **Language Detection**: The system identifies the programming language of the submission.
2. **Environment Setup**: A temporary directory is created for compiled languages.
3. **Container Setup**: Docker containers are configured with resource limits.
4. **Compilation**: For compiled languages (Java, C, C++), code is compiled first.
5. **Execution**: The program is executed with the provided input.
6. **Resource Monitoring**: Memory and CPU usage are limited during execution.
7. **Result Collection**: Output and errors are captured and stored.
### Language-Specific Processing
#### Python
- Directly executes Python code using the `-c` flag
- Uses `python:3.9` Docker image
- Resource limits: 100MB memory, 10% CPU
#### Java
- Detects class name using regex pattern matching
- Compiles with `javac` and runs with optimized JVM settings
- Uses `eclipse-temurin:11-jdk-alpine` Docker image
- Resource limits: 400MB memory, 50% CPU
- JVM flags: `-XX:+TieredCompilation`, `-XX:TieredStopAtLevel=1`, `-Xverify:none`
#### C/C++
- Saves code to a file in a temporary directory
- Compiles with `gcc`/`g++` and runs the executable
- Uses `gcc:latest` Docker image
- Resource limits: 100MB memory, 10% CPU
### Timeout Handling
All executions have a timeout limit:
- Python: 10 seconds
- Java: 15 seconds
- C/C++: 10 seconds
If execution exceeds this limit, the process is killed and an error is returned.
## Job Queue System
### Worker Pool
Monaco uses a worker pool to manage concurrent code executions:
- Default pool size: 20 workers (configurable)
- Maximum queue capacity: 100 jobs
- FIFO (First-In-First-Out) processing order
### Job Lifecycle
1. **Creation**: Job created when code is submitted
2. **Queuing**: Job added to queue with `queued` status
3. **Execution**: Worker picks job from queue and changes status to `running`
4. **Completion**: Job finishes with either `completed` or `failed` status
### Performance Metrics
The queue tracks and reports:
- Current queue length
- Number of running jobs
- Maximum worker count
- Total number of submissions
## Language Support
### Python
- **Version**: Python 3.9
- **Input Handling**: Direct stdin piping
- **Limitations**: No file I/O, no package imports outside standard library
### Java
- **Version**: Java 11 (Eclipse Temurin)
- **Class Detection**: Extracts class name from code using regex
- **Memory Settings**: 64MB min heap, 256MB max heap
- **Best Practices**: Use `public class` with the main method
### C
- **Version**: Latest GCC
- **Compilation Flags**: Default GCC settings
- **Execution**: Compiled binary
### C++
- **Version**: Latest G++
- **Standard**: C++17
- **Execution**: Compiled binary
## Security Considerations
### Containerization
All code execution happens within isolated Docker containers with:
- No network access (`--network=none`)
- Limited CPU and memory resources
- Limited file system access
- No persistent storage
### Resource Limiting
- **Memory Limits**: 100-400MB depending on language
- **CPU Limits**: 10-50% of CPU depending on language
- **File Descriptors**: Limited to 64 for Python
- **Execution Time**: Enforced timeouts (10-15 seconds)
### Known Limitations
- Container escape vulnerabilities
- Docker daemon security depends on host configuration
- Resource limits can be circumvented with certain techniques
## Configuration
The service can be configured through environment variables:
- `PORT`: HTTP port (default: 8080)
- `MAX_WORKERS`: Maximum concurrent executions (default: 3)
- `QUEUE_SIZE`: Maximum queue size (default: 100)
- `DEFAULT_LANGUAGE`: Default language if none specified (default: "python")
## Testing
### Unit Tests
Run unit tests with:
```bash
go test ./...
```
# Monaco Backend Test Plan
## Overview
This test plan outlines the testing approach for the Monaco code execution backend service.
## Test Environment
- Development: Local workstations with Docker and Go
- Testing: Dedicated test server with Docker
- Production-like: Staging environment with similar resources to production
## Test Types
### Unit Tests
- **Purpose**: Verify individual components work as expected
- **Components to Test**:
- Handler package
- Queue package
- Execution service
- Models
- **Tools**: Go testing framework
### Integration Tests
- **Purpose**: Verify components work together correctly
- **Focus Areas**:
- API endpoints
- End-to-end code execution flow
- Error handling
- **Tools**: Go testing framework, HTTP test utilities
### Load Tests
- **Purpose**: Verify system performance under load
- **Scenarios**:
- Concurrent submissions
- Mixed language workloads
- Queue saturation
- **Metrics**:
- Request throughput
- Response times
- Success rates
- Resource utilization
- **Tools**: Custom Python test scripts
## Test Data
- Simple programs in each language
- Programs with input requirements
- Programs with compile errors
- Programs with runtime errors
- Programs with timeouts
## Test Execution
1. Run unit tests on every code change
2. Run integration tests before merging to main branch
3. Run load tests weekly and before major releases
## Success Criteria
- All unit tests pass
- Integration tests complete successfully
- Load tests show acceptable performance metrics:
- 95% of requests complete successfully
- 95th percentile response time < 5 seconds
- System can handle 20 concurrent users
## Reporting
- Test results stored in CI/CD pipeline
- Performance metrics graphed over time
- Issues logged in GitHub issues
### Load Testing
A Python script (`test.py`) is included for load testing:
```bash
python test.py
```
This script sends 500 requests concurrently and reports performance metrics.
### Manual Testing with Curl
#### Python Example
```bash
curl -X POST http://localhost:8080/submit \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"code": "print(\"Hello, World!\")\nfor i in range(5):\n print(f\"Number: {i}\")",
"input": ""
}'
```
#### Java Example
```bash
curl -X POST http://localhost:8080/submit \
-H "Content-Type: application/json" \
-d '{
"language": "java",
"code": "public class Solution {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n for (int i = 0; i < 5; i++) {\n System.out.println(\"Number: \" + i);\n }\n }\n}",
"input": ""
}'
```
## Performance Tuning
### Optimizing Worker Count
The optimal worker count depends on:
- CPU cores available
- Memory available
- Docker container startup time
For most single-server deployments, 3-5 workers is optimal.
### Memory Considerations
Each language has different memory requirements:
- Python: ~50-100MB per instance
- Java: ~200-400MB per instance
- C/C++: ~50-100MB per instance
Calculate total memory needs as: `(Python instances × 100MB) + (Java instances × 400MB) + (C/C++ instances × 100MB)`
### Disk Space Management
Temporary files are cleaned up after execution, but with high request volumes, ensure adequate disk space for concurrent operations (approximately 1-5MB per request for compiled languages).
## Troubleshooting
### Common Issues
#### Docker Connection Errors
```
Error: Cannot connect to the Docker daemon
```
**Solution**: Ensure Docker daemon is running with `systemctl start docker` or `docker --version`
#### Permissions Issues
```
Error: Permission denied while trying to connect to the Docker daemon socket
```
**Solution**: Add user to docker group: `sudo usermod -aG docker $USER`
#### Container Resource Limits
```
Error: Container killed due to memory limit
```
**Solution**: Increase memory limits in execution service or optimize submitted code
#### File Not Found Errors
```
Error: Failed to write Java file
```
**Solution**: Check temporary directory permissions and disk space
### Logs
The service provides structured logs with prefixes for easier filtering:
- `[HTTP]` - API requests
- `[QUEUE]` - Queue operations
- `[WORKER-n]` - Worker activities
- `[EXEC-id]` - Execution details
- `[PYTHON/JAVA/C/CPP-id]` - Language-specific logs
- `[TIMEOUT-id]` - Timeout events
- `[RESULT-id]` - Execution results
---
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.

View File

@@ -2,75 +2,13 @@ module github.com/arnab-afk/monaco
go 1.22.3
require github.com/stretchr/testify v1.9.0
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v26.1.5+incompatible // indirect
github.com/docker/docker v26.1.5+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/expr-lang/expr v1.16.5 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/knadh/koanf/parsers/toml v0.1.0 // indirect
github.com/knadh/koanf/providers/env v0.1.0 // indirect
github.com/knadh/koanf/providers/file v0.1.0 // indirect
github.com/knadh/koanf/v2 v2.1.1 // indirect
github.com/labstack/echo/v4 v4.12.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rabbitmq/amqp091-go v1.9.0 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/runabol/code-execution-demo v0.0.0-20231001172254-4b23df61090b // indirect
github.com/runabol/tork v0.1.98 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.3 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/urfave/cli/v2 v2.27.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,214 +1,23 @@
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v26.1.5+incompatible h1:NxXGSdz2N+Ibdaw330TDO3d/6/f7MvHuiMbuFaIQDTk=
github.com/docker/cli v26.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g=
github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/expr-lang/expr v1.16.5 h1:m2hvtguFeVaVNTHj8L7BoAyt7O0PAIBaSVbjdHgRXMs=
github.com/expr-lang/expr v1.16.5/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/runabol/code-execution-demo v0.0.0-20231001172254-4b23df61090b h1:hSYt/eRkKwJ3C40aHMJJLlJg+ysSq7FvnTYYFymouhY=
github.com/runabol/code-execution-demo v0.0.0-20231001172254-4b23df61090b/go.mod h1:FRPaDaWfFOIYN5QjmQH4aH/F9Jc2CXtbzwB8c51sg4s=
github.com/runabol/tork v0.1.97 h1:XZo39ZXruiDipN3xdgQpKrLjiyDTHL8vjEtHHA8r5+U=
github.com/runabol/tork v0.1.97/go.mod h1:EuJuNGc5raepZ5fFE9meC4QbUVaWHedF5P8KEYFHBX4=
github.com/runabol/tork v0.1.98 h1:R/QkBTSF4sQLnW8nq5vlIPwtHuwllDBYjJMNlQ4vTkc=
github.com/runabol/tork v0.1.98/go.mod h1:EuJuNGc5raepZ5fFE9meC4QbUVaWHedF5P8KEYFHBX4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,154 @@
package handler
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/arnab-afk/monaco/model"
"github.com/stretchr/testify/assert"
)
func TestSubmitHandler(t *testing.T) {
h := NewHandler()
// Test valid Python submission
body := map[string]string{
"language": "python",
"code": "print('Hello, World!')",
}
bodyBytes, _ := json.Marshal(body)
req := httptest.NewRequest("POST", "/submit", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
h.SubmitHandler(w, req)
assert.Equal(t, http.StatusAccepted, w.Code)
var response map[string]string
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.NotEmpty(t, response["id"])
// Test invalid language
body["language"] = "invalid"
bodyBytes, _ = json.Marshal(body)
req = httptest.NewRequest("POST", "/submit", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
h.SubmitHandler(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "Unsupported language")
}
func TestStatusHandler(t *testing.T) {
h := NewHandler()
// Create a test submission
submission := &model.CodeSubmission{
ID: "test-id",
Language: "python",
Code: "print('Hello')",
Status: "completed",
QueuedAt: time.Now().Add(-2 * time.Second),
StartedAt: time.Now().Add(-1 * time.Second),
CompletedAt: time.Now(),
Output: "Hello",
}
h.submissions["test-id"] = submission
// Test valid status request
req := httptest.NewRequest("GET", "/status?id=test-id", nil)
w := httptest.NewRecorder()
h.StatusHandler(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "test-id", response["id"])
assert.Equal(t, "completed", response["status"])
// Test missing ID
req = httptest.NewRequest("GET", "/status", nil)
w = httptest.NewRecorder()
h.StatusHandler(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "ID is required")
// Test non-existent ID
req = httptest.NewRequest("GET", "/status?id=nonexistent", nil)
w = httptest.NewRecorder()
h.StatusHandler(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
assert.Contains(t, w.Body.String(), "Submission not found")
}
func TestResultHandler(t *testing.T) {
h := NewHandler()
// Create a test submission
submission := &model.CodeSubmission{
ID: "test-id",
Language: "python",
Code: "print('Hello')",
Status: "completed",
QueuedAt: time.Now().Add(-2 * time.Second),
StartedAt: time.Now().Add(-1 * time.Second),
CompletedAt: time.Now(),
Output: "Hello",
}
h.submissions["test-id"] = submission
// Test valid result request
req := httptest.NewRequest("GET", "/result?id=test-id", nil)
w := httptest.NewRecorder()
h.ResultHandler(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "test-id", response["id"])
assert.Equal(t, "completed", response["status"])
assert.Equal(t, "Hello", response["output"])
}
func TestQueueStatsHandler(t *testing.T) {
h := NewHandler()
// Add some test submissions
h.submissions["test-id1"] = &model.CodeSubmission{ID: "test-id1"}
h.submissions["test-id2"] = &model.CodeSubmission{ID: "test-id2"}
req := httptest.NewRequest("GET", "/queue-stats", nil)
w := httptest.NewRecorder()
h.QueueStatsHandler(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
stats, ok := response["queue_stats"].(map[string]interface{})
assert.True(t, ok)
assert.Contains(t, stats, "queue_length")
assert.Contains(t, stats, "max_workers")
assert.Contains(t, stats, "running_jobs")
assert.Equal(t, float64(2), response["submissions"])
}

View File

@@ -0,0 +1,71 @@
package model
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCodeSubmissionSerialization(t *testing.T) {
// Create a submission
now := time.Now()
submission := CodeSubmission{
ID: "test-id",
Code: "print('Hello, World!')",
Language: "python",
Input: "test input",
Status: "completed",
QueuedAt: now.Add(-2 * time.Second),
StartedAt: now.Add(-1 * time.Second),
CompletedAt: now,
Output: "Hello, World!",
}
// Serialize to JSON
jsonBytes, err := json.Marshal(submission)
assert.NoError(t, err)
assert.NotNil(t, jsonBytes)
// Deserialize back
var decoded CodeSubmission
err = json.Unmarshal(jsonBytes, &decoded)
assert.NoError(t, err)
// Verify fields match
assert.Equal(t, submission.ID, decoded.ID)
assert.Equal(t, submission.Code, decoded.Code)
assert.Equal(t, submission.Language, decoded.Language)
assert.Equal(t, submission.Input, decoded.Input)
assert.Equal(t, submission.Status, decoded.Status)
assert.Equal(t, submission.Output, decoded.Output)
// Time fields need special handling due to JSON serialization
assert.Equal(t, submission.QueuedAt.Format(time.RFC3339), decoded.QueuedAt.Format(time.RFC3339))
assert.Equal(t, submission.StartedAt.Format(time.RFC3339), decoded.StartedAt.Format(time.RFC3339))
assert.Equal(t, submission.CompletedAt.Format(time.RFC3339), decoded.CompletedAt.Format(time.RFC3339))
}
func TestCodeSubmissionDefaults(t *testing.T) {
// Test that zero time values work correctly
submission := CodeSubmission{
ID: "test-id",
Code: "print('Hello')",
Language: "python",
Status: "pending",
}
assert.True(t, submission.QueuedAt.IsZero())
assert.True(t, submission.StartedAt.IsZero())
assert.True(t, submission.CompletedAt.IsZero())
// Test JSON marshaling with zero time values
jsonBytes, err := json.Marshal(submission)
assert.NoError(t, err)
// The zero time values should still be included in the JSON
jsonStr := string(jsonBytes)
assert.Contains(t, jsonStr, `"id":"test-id"`)
assert.Contains(t, jsonStr, `"status":"pending"`)
}

112
backend/queue/queue_test.go Normal file
View File

@@ -0,0 +1,112 @@
package queue
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// Mock job for testing
type MockJob struct {
executed bool
executeTime time.Duration
mu sync.Mutex
}
func (j *MockJob) Execute() {
j.mu.Lock()
defer j.mu.Unlock()
time.Sleep(j.executeTime)
j.executed = true
}
func (j *MockJob) IsExecuted() bool {
j.mu.Lock()
defer j.mu.Unlock()
return j.executed
}
func TestJobQueueCreation(t *testing.T) {
// Test with different numbers of workers
jq := NewJobQueue(5)
assert.NotNil(t, jq)
assert.Equal(t, 5, jq.maxWorkers)
stats := jq.QueueStats()
assert.Equal(t, 0, stats["queue_length"])
assert.Equal(t, 5, stats["max_workers"])
assert.Equal(t, 0, stats["running_jobs"])
}
func TestJobExecution(t *testing.T) {
jq := NewJobQueue(2)
// Create test jobs
job1 := &MockJob{executeTime: 10 * time.Millisecond}
job2 := &MockJob{executeTime: 10 * time.Millisecond}
// Enqueue jobs
jq.Enqueue(job1)
jq.Enqueue(job2)
// Wait for execution
time.Sleep(50 * time.Millisecond)
// Verify both jobs executed
assert.True(t, job1.IsExecuted())
assert.True(t, job2.IsExecuted())
}
func TestConcurrentJobsExecution(t *testing.T) {
// Test that only maxWorkers jobs run concurrently
jq := NewJobQueue(2)
var mu sync.Mutex
runningCount := 0
maxObservedRunning := 0
wg := sync.WaitGroup{}
// Create long running jobs to test concurrency
for i := 0; i < 5; i++ {
wg.Add(1)
job := &MockJob{
executeTime: 100 * time.Millisecond,
}
// Wrap the job to monitor concurrency
wrappedJob := JobFunc(func() {
mu.Lock()
runningCount++
if runningCount > maxObservedRunning {
maxObservedRunning = runningCount
}
mu.Unlock()
job.Execute()
mu.Lock()
runningCount--
mu.Unlock()
wg.Done()
})
jq.Enqueue(wrappedJob)
}
wg.Wait()
jq.Stop()
// Verify max concurrent jobs is respected
assert.LessOrEqual(t, maxObservedRunning, 2)
}
// Define JobFunc type for easier job creation in tests
type JobFunc func()
func (f JobFunc) Execute() {
f()
}

View File

@@ -0,0 +1,115 @@
package service
import (
"os"
"testing"
"time"
"github.com/arnab-afk/monaco/model"
"github.com/stretchr/testify/assert"
)
// TestExecutionServiceCreation tests that the service is created properly
func TestExecutionServiceCreation(t *testing.T) {
service := NewExecutionService()
assert.NotNil(t, service)
assert.NotNil(t, service.queue)
}
// TestExtractClassName tests the class name extraction for Java code
func TestExtractClassName(t *testing.T) {
tests := []struct {
name string
code string
expected string
}{
{
name: "Public class",
code: "public class MyClass { public static void main(String[] args) {} }",
expected: "MyClass",
},
{
name: "Regular class",
code: "class RegularClass { public static void main(String[] args) {} }",
expected: "RegularClass",
},
{
name: "Multiple classes",
code: "class Class1 {} public class MainClass {} class Class2 {}",
expected: "MainClass",
},
{
name: "No class",
code: "// Just a comment",
expected: "Solution", // Default class name
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractClassName(tt.code)
assert.Equal(t, tt.expected, result)
})
}
}
// MockDockerExec is a function that can be used to mock Docker exec commands
type MockDockerExec func(cmd string, args ...string) ([]byte, error)
// TestUpdateSubmissionResult tests the submission result update logic
func TestUpdateSubmissionResult(t *testing.T) {
service := NewExecutionService()
// Test successful execution
submission := &model.CodeSubmission{
ID: "test-id",
Status: "running",
StartedAt: time.Now().Add(-500 * time.Millisecond),
QueuedAt: time.Now().Add(-1 * time.Second),
}
output := []byte("Hello, World!")
service.updateSubmissionResult(submission, output, nil)
assert.Equal(t, "completed", submission.Status)
assert.Equal(t, "Hello, World!", submission.Output)
assert.False(t, submission.CompletedAt.IsZero())
// Test failed execution
submission = &model.CodeSubmission{
ID: "test-id-2",
Status: "running",
StartedAt: time.Now().Add(-500 * time.Millisecond),
QueuedAt: time.Now().Add(-1 * time.Second),
}
output = []byte("Compilation error")
err := os.ErrInvalid // Any error will do for testing
service.updateSubmissionResult(submission, output, err)
assert.Equal(t, "failed", submission.Status)
assert.Contains(t, submission.Output, "Compilation error")
assert.Contains(t, submission.Output, err.Error())
assert.False(t, submission.CompletedAt.IsZero())
}
// TestCodeExecutionJob tests the job execution logic
func TestCodeExecutionJob(t *testing.T) {
service := NewExecutionService()
submission := &model.CodeSubmission{
ID: "test-id",
Language: "python",
Code: "print('test')",
Status: "queued",
QueuedAt: time.Now(),
}
job := NewCodeExecutionJob(service, submission)
assert.NotNil(t, job)
assert.Equal(t, submission, job.submission)
assert.Equal(t, service, job.service)
// We can't easily test the actual execution because it depends on Docker
// In a real test environment, you would mock the Docker calls
}

View File

@@ -0,0 +1,195 @@
package tests
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/arnab-afk/monaco/handler"
"github.com/stretchr/testify/assert"
)
func setupTestServer() *httptest.Server {
h := handler.NewHandler()
mux := http.NewServeMux()
mux.HandleFunc("/submit", h.SubmitHandler)
mux.HandleFunc("/status", h.StatusHandler)
mux.HandleFunc("/result", h.ResultHandler)
mux.HandleFunc("/queue-stats", h.QueueStatsHandler)
return httptest.NewServer(mux)
}
func TestAPIIntegration(t *testing.T) {
server := setupTestServer()
defer server.Close()
// Test: Submit code, check status, and get results
// 1. Submit a Python job
submitURL := server.URL + "/submit"
body := map[string]string{
"language": "python",
"code": "print('Hello, Integration Test!')",
}
bodyBytes, _ := json.Marshal(body)
resp, err := http.Post(submitURL, "application/json", bytes.NewReader(bodyBytes))
assert.NoError(t, err)
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
// Get the job ID
var submitResp map[string]string
json.NewDecoder(resp.Body).Decode(&submitResp)
resp.Body.Close()
jobID := submitResp["id"]
assert.NotEmpty(t, jobID)
// 2. Check status
statusURL := server.URL + "/status?id=" + jobID
// Wait for job to complete (try multiple times)
var statusResp map[string]interface{}
maxRetries := 10
for i := 0; i < maxRetries; i++ {
resp, err = http.Get(statusURL)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
json.NewDecoder(resp.Body).Decode(&statusResp)
resp.Body.Close()
// If job completed or failed, break
status, _ := statusResp["status"].(string)
if status == "completed" || status == "failed" {
break
}
// Wait before next retry
time.Sleep(200 * time.Millisecond)
}
// 3. Get results
resultURL := server.URL + "/result?id=" + jobID
resp, err = http.Get(resultURL)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
var resultResp map[string]interface{}
json.NewDecoder(resp.Body).Decode(&resultResp)
resp.Body.Close()
assert.Equal(t, jobID, resultResp["id"])
assert.Contains(t, resultResp["output"], "Hello, Integration Test!")
// 4. Check queue stats
statsURL := server.URL + "/queue-stats"
resp, err = http.Get(statsURL)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
var statsResp map[string]interface{}
json.NewDecoder(resp.Body).Decode(&statsResp)
resp.Body.Close()
assert.Contains(t, statsResp, "queue_stats")
assert.Contains(t, statsResp, "submissions")
}
func TestMultipleLanguageSubmissions(t *testing.T) {
server := setupTestServer()
defer server.Close()
// Test submissions for different languages
languages := []string{"python", "java", "c", "cpp"}
codes := map[string]string{
"python": "print('Hello from Python')",
"java": "public class Solution { public static void main(String[] args) { System.out.println(\"Hello from Java\"); } }",
"c": "#include <stdio.h>\nint main() { printf(\"Hello from C\\n\"); return 0; }",
"cpp": "#include <iostream>\nint main() { std::cout << \"Hello from C++\" << std::endl; return 0; }",
}
submitURL := server.URL + "/submit"
for _, lang := range languages {
body := map[string]string{
"language": lang,
"code": codes[lang],
}
bodyBytes, _ := json.Marshal(body)
resp, err := http.Post(submitURL, "application/json", bytes.NewReader(bodyBytes))
assert.NoError(t, err)
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
var submitResp map[string]string
json.NewDecoder(resp.Body).Decode(&submitResp)
resp.Body.Close()
jobID := submitResp["id"]
assert.NotEmpty(t, jobID)
// We don't wait for completion in this test
// This is just to verify submission acceptance for all languages
}
}
func TestInputHandling(t *testing.T) {
server := setupTestServer()
defer server.Close()
// Test code submission with input
submitURL := server.URL + "/submit"
body := map[string]string{
"language": "python",
"code": "name = input('Enter name: ')\nprint('Hello, ' + name + '!')",
"input": "Integration Test",
}
bodyBytes, _ := json.Marshal(body)
resp, err := http.Post(submitURL, "application/json", bytes.NewReader(bodyBytes))
assert.NoError(t, err)
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
var submitResp map[string]string
json.NewDecoder(resp.Body).Decode(&submitResp)
resp.Body.Close()
jobID := submitResp["id"]
assert.NotEmpty(t, jobID)
// Wait for job to complete and check result
resultURL := server.URL + "/result?id=" + jobID
// Poll for results
var resultResp map[string]interface{}
maxRetries := 10
for i := 0; i < maxRetries; i++ {
time.Sleep(300 * time.Millisecond)
resp, err = http.Get(resultURL)
assert.NoError(t, err)
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
continue
}
json.NewDecoder(resp.Body).Decode(&resultResp)
resp.Body.Close()
status, _ := resultResp["status"].(string)
if status == "completed" || status == "failed" {
break
}
}
// Verify output contains the greeting with input
assert.Contains(t, resultResp["output"], "Hello, Integration Test!")
}

278
backend/tests/load_test.py Normal file
View File

@@ -0,0 +1,278 @@
import requests
import concurrent.futures
import time
import statistics
import matplotlib.pyplot as plt
import numpy as np
# Define the endpoint URLs
POST_URL = "http://localhost:8080/submit"
GET_URL_STATUS = "http://localhost:8080/status?id={}"
GET_URL_RESULT = "http://localhost:8080/result?id={}"
GET_URL_STATS = "http://localhost:8080/queue-stats"
# Test payloads for different languages
PAYLOADS = {
"python": {
"language": "python",
"code": "print('Hello, Load Test!')",
},
"java": {
"language": "java",
"code": "public class Solution { public static void main(String[] args) { System.out.println(\"Hello, Load Test!\"); } }",
},
"c": {
"language": "c",
"code": "#include <stdio.h>\nint main() { printf(\"Hello, Load Test!\\n\"); return 0; }",
},
"cpp": {
"language": "cpp",
"code": "#include <iostream>\nint main() { std::cout << \"Hello, Load Test!\" << std::endl; return 0; }",
}
}
def send_request(language, index):
"""Sends a POST request and returns (task_id, time_taken)."""
payload = PAYLOADS[language]
start_time = time.time()
try:
response = requests.post(POST_URL, json=payload, timeout=10)
end_time = time.time()
if response.status_code == 202:
return response.json().get("id"), end_time - start_time
else:
print(f"Request {index} failed with status {response.status_code}")
return None, end_time - start_time
except requests.exceptions.RequestException as e:
end_time = time.time()
print(f"Request {index} error: {e}")
return None, end_time - start_time
def wait_for_result(task_id, index):
"""Waits for a result and returns (result, time_taken)."""
if not task_id:
return None, 0
start_time = time.time()
max_retries = 30
retry_interval = 0.5 # seconds
for _ in range(max_retries):
try:
response = requests.get(GET_URL_RESULT.format(task_id), timeout=10)
if response.status_code == 200:
result = response.json()
if result.get("status") in ["completed", "failed"]:
end_time = time.time()
return result, end_time - start_time
time.sleep(retry_interval)
except requests.exceptions.RequestException as e:
print(f"Error checking result for task {index}: {e}")
end_time = time.time()
print(f"Timed out waiting for result of task {index}")
return None, end_time - start_time
def run_test(concurrency, requests_per_language):
"""Runs a load test with the specified parameters."""
languages = list(PAYLOADS.keys())
all_results = {lang: [] for lang in languages}
submit_times = {lang: [] for lang in languages}
wait_times = {lang: [] for lang in languages}
success_rates = {lang: 0 for lang in languages}
# Keep track of all submissions for each language
total_per_language = {lang: 0 for lang in languages}
successful_per_language = {lang: 0 for lang in languages}
start_time = time.time()
# Create a list of tasks
tasks = []
for lang in languages:
for i in range(requests_per_language):
tasks.append((lang, i))
print(f"Running load test with {concurrency} concurrent connections")
print(f"Sending {requests_per_language} requests per language ({len(languages)} languages)")
# Submit all tasks
task_ids = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
future_to_task = {executor.submit(send_request, lang, i): (lang, i) for lang, i in tasks}
for future in concurrent.futures.as_completed(future_to_task):
lang, i = future_to_task[future]
total_per_language[lang] += 1
try:
task_id, submit_time = future.result()
if task_id:
task_ids[(lang, i)] = task_id
submit_times[lang].append(submit_time)
except Exception as e:
print(f"Error submitting {lang} task {i}: {e}")
print(f"Submitted {len(task_ids)} tasks successfully")
# Wait for all results
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
future_to_task = {executor.submit(wait_for_result, task_ids.get((lang, i)), i): (lang, i)
for lang, i in tasks if (lang, i) in task_ids}
for future in concurrent.futures.as_completed(future_to_task):
lang, i = future_to_task[future]
try:
result, wait_time = future.result()
if result and result.get("status") == "completed":
successful_per_language[lang] += 1
all_results[lang].append(result)
wait_times[lang].append(wait_time)
except Exception as e:
print(f"Error waiting for {lang} task {i}: {e}")
end_time = time.time()
total_time = end_time - start_time
# Calculate success rates
for lang in languages:
if total_per_language[lang] > 0:
success_rates[lang] = (successful_per_language[lang] / total_per_language[lang]) * 100
else:
success_rates[lang] = 0
# Calculate statistics
stats = {
"total_time": total_time,
"requests_per_second": len(task_ids) / total_time if total_time > 0 else 0,
"success_rate": sum(success_rates.values()) / len(success_rates) if success_rates else 0,
"submit_times": {
lang: {
"avg": statistics.mean(times) if times else 0,
"min": min(times) if times else 0,
"max": max(times) if times else 0,
"p95": np.percentile(times, 95) if times else 0
} for lang, times in submit_times.items()
},
"wait_times": {
lang: {
"avg": statistics.mean(times) if times else 0,
"min": min(times) if times else 0,
"max": max(times) if times else 0,
"p95": np.percentile(times, 95) if times else 0
} for lang, times in wait_times.items()
},
"success_rates": success_rates
}
return stats, all_results
def print_stats(stats):
"""Prints test statistics."""
print("\n=== Load Test Results ===")
print(f"Total time: {stats['total_time']:.2f}s")
print(f"Requests per second: {stats['requests_per_second']:.2f}")
print(f"Overall success rate: {stats['success_rate']:.2f}%")
print("\n== Submit Times (seconds) ==")
for lang, times in stats["submit_times"].items():
print(f"{lang:<6}: avg={times['avg']:.4f}, min={times['min']:.4f}, max={times['max']:.4f}, p95={times['p95']:.4f}")
print("\n== Wait Times (seconds) ==")
for lang, times in stats["wait_times"].items():
print(f"{lang:<6}: avg={times['avg']:.4f}, min={times['min']:.4f}, max={times['max']:.4f}, p95={times['p95']:.4f}")
print("\n== Success Rates ==")
for lang, rate in stats["success_rates"].items():
print(f"{lang:<6}: {rate:.2f}%")
def plot_results(stats):
"""Creates visualizations of test results."""
languages = list(stats["submit_times"].keys())
# Plot submit times
plt.figure(figsize=(12, 10))
plt.subplot(2, 2, 1)
plt.title("Average Submit Time by Language")
avg_times = [stats["submit_times"][lang]["avg"] for lang in languages]
plt.bar(languages, avg_times)
plt.ylabel("Time (seconds)")
plt.subplot(2, 2, 2)
plt.title("Average Wait Time by Language")
avg_wait_times = [stats["wait_times"][lang]["avg"] for lang in languages]
plt.bar(languages, avg_wait_times)
plt.ylabel("Time (seconds)")
plt.subplot(2, 2, 3)
plt.title("Success Rate by Language")
success_rates = [stats["success_rates"][lang] for lang in languages]
plt.bar(languages, success_rates)
plt.ylabel("Success Rate (%)")
plt.ylim(0, 100)
plt.subplot(2, 2, 4)
plt.title("95th Percentile Wait Time by Language")
p95_times = [stats["wait_times"][lang]["p95"] for lang in languages]
plt.bar(languages, p95_times)
plt.ylabel("Time (seconds)")
plt.tight_layout()
plt.savefig("load_test_results.png")
print("Results saved to load_test_results.png")
def main():
# Run tests with different concurrency levels
concurrency_levels = [10, 20, 30]
requests_per_language = 10
all_stats = []
for concurrency in concurrency_levels:
stats, results = run_test(concurrency, requests_per_language)
all_stats.append((concurrency, stats))
print_stats(stats)
# Create comparison visualization
plt.figure(figsize=(12, 8))
plt.subplot(2, 2, 1)
plt.title("Requests per Second vs Concurrency")
plt.plot([s[0] for s in all_stats], [s[1]["requests_per_second"] for s in all_stats], "o-")
plt.xlabel("Concurrency Level")
plt.ylabel("Requests per Second")
plt.subplot(2, 2, 2)
plt.title("Success Rate vs Concurrency")
plt.plot([s[0] for s in all_stats], [s[1]["success_rate"] for s in all_stats], "o-")
plt.xlabel("Concurrency Level")
plt.ylabel("Success Rate (%)")
plt.ylim(0, 100)
plt.subplot(2, 2, 3)
plt.title("Average Submit Time vs Concurrency")
for lang in PAYLOADS.keys():
plt.plot([s[0] for s in all_stats],
[s[1]["submit_times"][lang]["avg"] for s in all_stats],
"o-", label=lang)
plt.xlabel("Concurrency Level")
plt.ylabel("Average Submit Time (s)")
plt.legend()
plt.subplot(2, 2, 4)
plt.title("Average Wait Time vs Concurrency")
for lang in PAYLOADS.keys():
plt.plot([s[0] for s in all_stats],
[s[1]["wait_times"][lang]["avg"] for s in all_stats],
"o-", label=lang)
plt.xlabel("Concurrency Level")
plt.ylabel("Average Wait Time (s)")
plt.legend()
plt.tight_layout()
plt.savefig("concurrency_comparison.png")
print("Concurrency comparison saved to concurrency_comparison.png")
# Plot detailed results for the highest concurrency test
plot_results(all_stats[-1][1])
if __name__ == "__main__":
main()

Binary file not shown.