Initial commit

This commit is contained in:
Daniel Sy 2025-03-25 17:53:35 +01:00
commit 600e641560
Signed by untrusted user who does not match committer: Daniel.Sy
GPG key ID: 1F39A8BBCD2EE3D3
11 changed files with 394 additions and 0 deletions

108
pkg/loic/loic.go Normal file
View file

@ -0,0 +1,108 @@
package loic
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"log"
"net/http"
"sync"
"time"
)
var meter = otel.Meter("loic")
var requestsSent metric.Int64Counter
var currentTest *Test
var isRunning bool
func init() {
var err error
requestsSent, err = meter.Int64Counter("requests_sent",
metric.WithDescription("Number of requests sent"),
metric.WithUnit("1"),
)
if err != nil {
log.Fatalf("failed to create counter: %v", err)
}
}
type Test struct {
TargetURL string
Concurrency int
Duration time.Duration
RampUpTime time.Duration
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
func StartTest(targetURL string, concurrency int, duration time.Duration, rampUp time.Duration) {
ctx, cancel := context.WithCancel(context.Background())
currentTest := &Test{
TargetURL: targetURL,
Concurrency: concurrency,
Duration: duration,
RampUpTime: rampUp,
ctx: ctx,
cancel: cancel,
}
go currentTest.run()
}
func StopTest() {
if currentTest != nil && isRunning {
currentTest.cancel()
currentTest.wg.Wait()
currentTest = nil
isRunning = false
log.Println("Test stopped")
}
}
func IsTestRunning() bool {
return isRunning
}
func (t *Test) run() {
defer func() { isRunning = false }()
ticker := time.NewTicker(t.RampUpTime / time.Duration(t.Concurrency))
defer ticker.Stop()
currentConcurrency := 1
for i := 0; i < t.Concurrency; i++ {
select {
case <-ticker.C:
if currentConcurrency < t.Concurrency {
currentConcurrency++
t.startWorker()
}
case <-time.After(t.Duration):
t.cancel()
case <-t.ctx.Done():
return
}
}
t.wg.Wait()
}
func (t *Test) startWorker() {
t.wg.Add(1)
go func() {
defer t.wg.Done()
for {
select {
case <-t.ctx.Done():
return
default:
resp, err := http.Get(t.TargetURL)
if err != nil {
log.Println("Error:", err)
} else {
resp.Body.Close()
requestsSent.Add(t.ctx, 1)
}
time.Sleep(time.Second / time.Duration(t.Concurrency))
}
}
}()
}

View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LOIC Web UI</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">LOIC Control Panel</h1>
<div class="card">
<div class="card-body">
<h5 class="card-title">Start Test</h5>
<form action="/start" method="post">
<div class="mb-3">
<label for="target_url" class="form-label">Target URL</label>
<input type="text" class="form-control" id="target_url" name="target_url" placeholder="http://example.com">
</div>
<div class="mb-3">
<label for="concurrency" class="form-label">Concurrency</label>
<input type="number" class="form-control" id="concurrency" name="concurrency" value="10">
</div>
<div class="mb-3">
<label for="duration" class="form-label">Duration (e.g., 1m, 30s)</label>
<input type="text" class="form-control" id="duration" name="duration" value="1m">
</div>
<div class="mb-3">
<label for="ramp_up" class="form-label">Ramp-Up Time (e.g., 30s)</label>
<input type="text" class="form-control" id="ramp_up" name="ramp_up" value="30s">
</div>
<button type="submit" class="btn btn-primary">Start Test</button>
</form>
</div>
</div>
<div class="card mt-3">
<div class="card-body">
<h5 class="card-title">Stop Test</h5>
<form action="/stop" method="post">
<button type="submit" class="btn btn-danger">Stop Test</button>
</form>
</div>
</div>
<div class="card mt-3">
<div class="card-body">
<h5 class="card-title">Status</h5>
<p>Current Status: {{ .Status }}</p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

41
pkg/web/web.go Normal file
View file

@ -0,0 +1,41 @@
package web
import (
"forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/Daniel.Sy/loic-go/pkg/loic"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"time"
)
func Start() {
r := gin.Default()
r.LoadHTMLGlob("pkg/web/templates/*")
r.GET("/", func(c *gin.Context) {
status := "stopped"
if loic.IsTestRunning() {
status = "running"
}
c.HTML(http.StatusOK, "index.html", gin.H{
"Status": status,
})
})
r.POST("/start", func(c *gin.Context) {
targetURL := c.PostForm("target_url")
concurrency, _ := strconv.Atoi(c.PostForm("concurrency"))
duration, _ := time.ParseDuration(c.PostForm("duration"))
rampUp, _ := time.ParseDuration(c.PostForm("ramp_up"))
go loic.StartTest(targetURL, concurrency, duration, rampUp)
c.Redirect(http.StatusSeeOther, "/")
})
r.POST("/stop", func(c *gin.Context) {
loic.StopTest()
c.Redirect(http.StatusSeeOther, "/")
})
r.Run(":8080")
}