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

34
.gitignore vendored Normal file
View file

@ -0,0 +1,34 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Go workspace file
go.work
# Go modules
go.mod
go.sum
# Dependency directories
vendor/
# IDE and editor files
.idea/
.vscode/
*.swp
# Logs
*.log
# OS generated files
.DS_Store
Thumbs.db
# Custom files
loic

0
README.md Normal file
View file

20
cmd/root.go Normal file
View file

@ -0,0 +1,20 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var rootCmd = &cobra.Command{
Use: "loic",
Short: "LOIC-Anwendung für Lasttests",
Long: `Eine Anwendung für skalierbare Lasttests im Kubernetes-Cluster.`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

29
cmd/start.go Normal file
View file

@ -0,0 +1,29 @@
package cmd
import (
"fmt"
"forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/Daniel.Sy/loic-go/pkg/loic"
"github.com/spf13/cobra"
"time"
)
var startCmd = &cobra.Command{
Use: "start",
Short: "Startet einen Lasttest",
Run: func(cmd *cobra.Command, args []string) {
targetURL, _ := cmd.Flags().GetString("target")
concurrency, _ := cmd.Flags().GetInt("concurrency")
duration, _ := cmd.Flags().GetDuration("duration")
rampUp, _ := cmd.Flags().GetDuration("ramp-up")
fmt.Printf("Starting test with target %s, concurrency %d, duration %v, ramp-up %v\n", targetURL, concurrency, duration, rampUp)
loic.StartTest(targetURL, concurrency, duration, rampUp)
},
}
func init() {
startCmd.Flags().StringP("target", "t", "", "Ziel-URL für den Test (z. B. http://service.cluster.local oder http://example.com)")
startCmd.Flags().IntP("concurrency", "c", 10, "Maximale Anzahl paralleler Anfragen")
startCmd.Flags().DurationP("duration", "d", time.Minute, "Dauer des Tests")
startCmd.Flags().DurationP("ramp-up", "r", 30*time.Second, "Zeit für schrittweise Erhöhung der Last")
rootCmd.AddCommand(startCmd)
}

25
cmd/status.go Normal file
View file

@ -0,0 +1,25 @@
package cmd
import (
"fmt"
"forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/Daniel.Sy/loic-go/pkg/loic"
"github.com/spf13/cobra"
)
var statusCmd = &cobra.Command{
Use: "status",
Short: "Displays test status",
Long: "Display test status of the application",
Run: func(cmd *cobra.Command, args []string) {
if loic.IsTestRunning() {
fmt.Println("Status: running")
return
} else {
fmt.Println("Status: stopped")
}
},
}
func init() {
rootCmd.AddCommand(statusCmd)
}

18
cmd/stop.go Normal file
View file

@ -0,0 +1,18 @@
package cmd
import (
"forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/Daniel.Sy/loic-go/pkg/loic"
"github.com/spf13/cobra"
)
var stopCmd = &cobra.Command{
Use: "stop",
Short: "Stoppt den aktuellen Lasttest",
Run: func(cmd *cobra.Command, args []string) {
loic.StopTest()
},
}
func init() {
rootCmd.AddCommand(stopCmd)
}

19
cmd/web.go Normal file
View file

@ -0,0 +1,19 @@
package cmd
import (
"forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/Daniel.Sy/loic-go/pkg/web"
"github.com/spf13/cobra"
)
var webCmd = &cobra.Command{
Use: "web",
Short: "Startet den Web-Server",
Long: "Startet den Web-Server für myapp",
Run: func(cmd *cobra.Command, args []string) {
web.Start()
},
}
func init() {
rootCmd.AddCommand(webCmd)
}

48
main.go Normal file
View file

@ -0,0 +1,48 @@
package main
import (
"context"
"forgejo.edf-bootstrap.cx.fg1.ffm.osc.live/Daniel.Sy/loic-go/cmd"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"log"
"time"
)
func main() {
// Ressource definieren
res, err := resource.New(context.Background(),
resource.WithAttributes(
semconv.ServiceNameKey.String("loic"),
semconv.ServiceVersionKey.String("0.1.0"),
),
)
if err != nil {
log.Fatalf("failed to create resource: %v", err)
}
// Exporter erstellen (stdout für Tests)
exporter, err := stdoutmetric.New()
if err != nil {
log.Fatalf("failed to create stdout exporter: %v", err)
}
// Periodic Reader erstellen
reader := metric.NewPeriodicReader(exporter, metric.WithInterval(10*time.Second))
// MeterProvider initialisieren
meterProvider := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(reader),
)
defer meterProvider.Shutdown(context.Background())
// Globalen MeterProvider setzen
otel.SetMeterProvider(meterProvider)
// CLI ausführen
cmd.Execute()
}

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")
}