# Lastprüfung von ASP-.-NET Core Applications mit praktischer Implementierung

<!--category-- Testing, Performance, k6, ASP.NET -->
<datetime class="hidden">2025-12-02T15:00</datetime>

Jetzt, da Sie die Grundlagen des k .6 und Leistungstests verstehen . , . Es ist Zeit, dieses Wissen in die Praxis umzusetzen Dieser Artikel führt Sie durch das Schreiben von echten Tests ?, ? Integrieren sie in CI . / .CD Pipelines .

Das ist **Teil** von einer zwei- oder mehrteiligen Serie auf Lastprüfung mit k- oder

- **[Teil Einleitung](/blog/k6-testing-introduction)**Einführung in die Installation von k-6,, ,, Testtypen und warum k-6,
- **Teil dieses Artikels**Schreibteste zur Integration von CIP-/CDs, zum Profilieren von , und zu realen Beispielen für -

Wenn du nicht gelesen hast Teil 1, starte dort, um die Grundlagen zu verstehen , installation und Testtypen vor dem Eintauchen in die Implementierung

[TOC]

## Stellen Sie Ihre Testumgebung ein

Vor dem Ausführen von k-6-Tests – , – let-let-' – MinimalBlog zum Testen

### Führen Sie MinimalBlog vor Ort

**Verwendung des Demo-Projekts**

```bash
cd Mostlylucid.MinimalBlog.Demo
dotnet run
```

**Oder die Verwendung von Docker-:**

```bash
docker run -d -p 5000:8080 \
  -v $(pwd)/Markdown:/app/Markdown \
  --name minimalblog-test \
  scottgal/minimalblog:latest
```

### Warum Freigabemodus für Leistungsprüfungen wichtig ist

Für genaue Leistungsprüfungen **immer im Release-Modus aufbauen**:

```bash
dotnet run --configuration Release
```

**Debug vs Release-Modus Unterschiede**

|-Aspect-Debugmodus-Release-Modus-Relaunch-Mode-Relate-Relation
|--------|------------|--------------|
| **Optimierungen** Vollständige JIT-Optimierungen aktiviert
| **Einbilden** ,| , minimal ,| , Aggressive Methode inlining , | ,
| **Dead Code** Ausgelöscht Ausgerottet
| **Debug-Symbole** Vollständige PDB.,. Extra-Metadaten.
| **Anordnungen** | `Debug.Assert()` Aktives Ausschöpfen von |
| **Prüfung der Hundehunde** Weitere Sicherheitsüberprüfungen | , optimiert weg wo sicher | ,
| **Typischer Overhead** 2-10x langsamere Ausgangsperformance

**Warum Debug-Modus gibt irreführende Ergebnisse**

1. **Keine JIT-Optimierungen**Der Compiler bewahrt die Codestruktur für Debugging und nicht für Geschwindigkeitsoptimierung.
2. **Zusätzliche Instrumente**Debug-Builds enthalten Code für Breakpoints, variable Inspektion und Stackspuren.
3. **Keine Einschnitte**Methoden sind ,''t inlined , um Anruf Overhead hinzufügen, die won , ''t existieren in der Produktion
4. **Einstellungen werden ausgeführt**: `Debug.Assert()` Aussagen ausführen ,, , das Hinzufügen von Kontrollen, die Produktion won , ' , haben
5. **Verschiedene Speichermuster**Zu den Debug-Zuweisungen gehören Padding und Tracking, die das GC-Verhalten beeinflussen.

**Wann Sie den Debug-Modus zum Testen verwenden können**

- **Fehlerbehebung**Wenn Tests fehlschlagen und Sie detaillierte Stapelspuren benötigen
- **Prüfung spezifischer Anträge**Befestigen Sie einen Debugger, um eine einzelne problematische Anfrage zu verfolgen
- **Protokollierung überprüfen**Vergewissern Sie sich, dass Ihre Protokollierung die richtigen Informationen unter Last erfasst
- **Erste Entwicklung**Beim Schreiben neuer Testskripte und beim Verifizieren treffen sie auf die richtigen Endpunkte

**Empfohlener Workflow**

```bash
# 1. Develop and debug with Debug mode
dotnet run                           # Debug mode (default)

# 2. Validate functionality works
k6 run --vus 1 --duration 10s smoke-test.js

# 3. Switch to Release for actual performance testing
dotnet run -c Release

# 4. Run full load tests
k6 run load-test.js
```

> **Faustregel**Wenn Sie die Messleistung von 're messen, verwenden Sie Release-.. Wenn Sie 're Fehler beheben, benutzen Sie Debug-MSC6.

### Vorbereiten von Testdaten

Erstellen Sie mehrere Markdown-Posts, um mit zu testen

```bash
# Create test posts directory
mkdir -p TestMarkdown

# Create sample posts
for i in {1..10}; do
  cat > TestMarkdown/test-post-$i.md <<EOF
# Test Post $i

<!-- category -- Testing, Performance -->
<datetime class="hidden">2025-12-01T12:00</datetime>

This is test post number $i with some **bold** text and *italic* text.

## Sample Content

- Item 1
- Item 2
- Item 3

\`\`\`csharp
public class Test {
    public int Value { get; set; }
}
\`\`\`
EOF
done
```

### Erstellen Sie das Verzeichnis der Testskripte

```bash
mkdir k6-tests
cd k6-tests
```

Jetzt sind wir bereit, unsere Tests zu schreiben.

## Prüfung des Rauchs

Let.'s beginnen mit einem einfachen Rauchtest, um grundlegende Funktionalitäten zu überprüfen.

**Datei„ :" `smoke-test.js`**

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 1,
  duration: '30s',
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests should be below 500ms
    http_req_failed: ['rate<0.01'],    // Less than 1% errors
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  // Test homepage
  let response = http.get(`${BASE_URL}/`);
  check(response, {
    'homepage status is 200': (r) => r.status === 200,
    'homepage has content': (r) => r.body.length > 0,
    'homepage response time OK': (r) => r.timings.duration < 500,
  });

  sleep(1);

  // Test a single post
  response = http.get(`${BASE_URL}/post/test-post-1`);
  check(response, {
    'post status is 200': (r) => r.status === 200,
    'post has content': (r) => r.body.length > 0,
    'post contains title': (r) => r.body.includes('Test Post 1'),
  });

  sleep(1);

  // Test categories page
  response = http.get(`${BASE_URL}/categories`);
  check(response, {
    'categories status is 200': (r) => r.status === 200,
    'categories has content': (r) => r.body.length > 0,
  });

  sleep(1);

  // Test category filter
  response = http.get(`${BASE_URL}/category/Testing`);
  check(response, {
    'category filter status is 200': (r) => r.status === 200,
    'category has posts': (r) => r.body.includes('Test Post'),
  });
}
```

**Führen Sie den Test aus.**

```bash
k6 run smoke-test.js
```

**Erwartete Ausgabe**

```
     ✓ homepage status is 200
     ✓ homepage has content
     ✓ homepage response time OK
     ✓ post status is 200
     ✓ post has content
     ✓ post contains title
     ✓ categories status is 200
     ✓ categories has content
     ✓ category filter status is 200
     ✓ category has posts

     checks.........................: 100.00% ✓ 300  ✗ 0
     data_received..................: 1.2 MB  40 kB/s
     data_sent......................: 15 kB   500 B/s
     http_req_duration..............: avg=45ms min=12ms med=38ms max=156ms p(95)=98ms
     http_reqs......................: 120     4/s
```

## Prüfung der Validierung des Cache-Tests

Minimale Leistung des Blogs' hängt stark von Caching ab

**Datei„ :" `cache-test.js`**

```javascript
import http from 'k6/http';
import { check, group } from 'k6';

export const options = {
  vus: 1,
  iterations: 10,
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  group('Memory Cache Test - First Request', () => {
    const start = Date.now();
    const response = http.get(`${BASE_URL}/post/test-post-1`);
    const duration = Date.now() - start;

    check(response, {
      'first request successful': (r) => r.status === 200,
    });

    console.log(`First request: ${duration}ms`);
  });

  group('Memory Cache Test - Cached Request', () => {
    const start = Date.now();
    const response = http.get(`${BASE_URL}/post/test-post-1`);
    const duration = Date.now() - start;

    check(response, {
      'cached request successful': (r) => r.status === 200,
      'cached request is faster': (r) => r.timings.duration < 100,
    });

    console.log(`Cached request: ${duration}ms`);
  });

  group('Output Cache Headers', () => {
    const response = http.get(`${BASE_URL}/post/test-post-1`);

    check(response, {
      'has cache headers': (r) => r.headers['Cache-Control'] !== undefined,
      'output cache working': (r) => {
        const age = r.headers['Age'];
        return age !== undefined && parseInt(age) >= 0;
      },
    });

    console.log(`Cache-Control: ${response.headers['Cache-Control']}`);
    console.log(`Age: ${response.headers['Age']}`);
  });
}
```

**Führen Sie den Test aus.**

```bash
k6 run cache-test.js
```

Dieser Test wird zeigen, dass Sie

1. Erste Request-Zeit, wenn der Cache kalt ist.
2. Nachträgliche Request-Zeiten
3. Überprüfung von Cache-Headern

## Prüfung der Last

Jetzt lassen let-' s Test unter realistischer Belastung

**Datei„ :" `load-test.js`**

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');

export const options = {
  stages: [
    { duration: '2m', target: 10 },   // Ramp up to 10 users
    { duration: '5m', target: 10 },   // Stay at 10 users
    { duration: '2m', target: 0 },    // Ramp down to 0
  ],
  thresholds: {
    http_req_duration: ['p(95)<300'],  // 95% under 300ms
    http_req_failed: ['rate<0.01'],     // Less than 1% errors
    errors: ['rate<0.1'],               // Less than 10% errors
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

const scenarios = [
  { weight: 50, path: '/' },                    // 50% homepage
  { weight: 30, path: '/post/test-post-1' },    // 30% specific post
  { weight: 10, path: '/categories' },          // 10% categories
  { weight: 10, path: '/category/Testing' },    // 10% category filter
];

function weightedChoice(scenarios) {
  const total = scenarios.reduce((sum, s) => sum + s.weight, 0);
  let random = Math.random() * total;

  for (const scenario of scenarios) {
    random -= scenario.weight;
    if (random <= 0) return scenario;
  }

  return scenarios[0];
}

export default function() {
  const scenario = weightedChoice(scenarios);
  const response = http.get(`${BASE_URL}${scenario.path}`);

  const success = check(response, {
    'status is 200': (r) => r.status === 200,
    'response time OK': (r) => r.timings.duration < 500,
    'has content': (r) => r.body.length > 0,
  });

  errorRate.add(!success);

  sleep(Math.random() * 2 + 1); // Random sleep 1-3 seconds
}
```

**Führen Sie den Test aus.**

```bash
k6 run load-test.js
```

**Interpretation der Ergebnisse:**

```
scenarios: (100.00%) 1 scenario, 10 max VUs, 9m30s max duration
  default: 2m00s ramp-up, 5m00s plateau, 2m00s ramp-down

     ✓ status is 200
     ✓ response time OK
     ✓ has content

     checks.........................: 100.00% ✓ 3450  ✗ 0
     data_received..................: 12 MB   22 kB/s
     data_sent......................: 132 kB  244 B/s
     errors.........................: 0.00%   ✓ 0     ✗ 1150
     http_req_duration..............: avg=42ms min=8ms med=35ms max=245ms p(95)=86ms p(99)=156ms
     http_reqs......................: 1150    2.12/s
     iteration_duration.............: avg=2.1s min=1.0s med=2.0s max=3.4s
     vus............................: 10      min=0   max=10
```

Schlüssel Metriken zu beobachten

- **Weblinks * Offizielle Website Einzelnachweise**Ansprechzeit des Perzentils sollte unter der Schwelle von ) sein
- **(englisch) Einzelnachweise **Die Fehlerrate sollte in der Nähe von 0%) sein.
- **Diese Daten sind in der Datenbank http:www.cordis.lu/news/index.htm enthalten.**Gesamtdurchsatz

## Test-Stresstest

Lassen Sie uns finden MinimalBlog'Bruchpunkt

**Datei„ :" `stress-test.js`**

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 10 },    // Normal load
    { duration: '2m', target: 50 },    // Increase to 50
    { duration: '2m', target: 100 },   // Stress at 100
    { duration: '2m', target: 200 },   // High stress at 200
    { duration: '3m', target: 0 },     // Recovery
  ],
  thresholds: {
    http_req_duration: ['p(99)<3000'], // 99% under 3s even under stress
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  const responses = http.batch([
    ['GET', `${BASE_URL}/`],
    ['GET', `${BASE_URL}/post/test-post-1`],
    ['GET', `${BASE_URL}/post/test-post-2`],
    ['GET', `${BASE_URL}/categories`],
  ]);

  responses.forEach((response, index) => {
    check(response, {
      [`request ${index} status is 200`]: (r) => r.status === 200,
    });
  });

  sleep(0.5);
}

export function handleSummary(data) {
  return {
    'stress-test-summary.json': JSON.stringify(data),
    stdout: textSummary(data, { indent: ' ', enableColors: true }),
  };
}
```

**Führen Sie den Test aus.**

```bash
k6 run stress-test.js
```

Beobachten Sie nach ,:'

- Wann beginnt die Reaktionszeit zu degradieren?
- Bei welcher VU-Zählung erscheinen Fehler?
- Löst sich das System, nachdem die Last sinkt?

## Prüfmethode für den Spike-Test

Testen Sie plötzliche Verkehrsspitzen wie auf Reddit oder Hacker-Nachrichten vorgestellt

**Datei„ :" `spike-test.js`**

```javascript
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 10 },    // Normal traffic
    { duration: '30s', target: 200 },  // Sudden spike!
    { duration: '3m', target: 200 },   // Sustained spike
    { duration: '1m', target: 10 },    // Back to normal
    { duration: '1m', target: 0 },     // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<1000'], // Allow higher latency during spike
    http_req_failed: ['rate<0.05'],     // Allow 5% errors during spike
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  const response = http.get(`${BASE_URL}/`);

  check(response, {
    'status is 200 or 503': (r) => r.status === 200 || r.status === 503,
  });
}
```

**Führen Sie den Test aus.**

```bash
k6 run spike-test.js
```

Dieser Test prüft die Ergebnisse des Tests.

- Behandelt das System plötzliche Last
- Gibt es geeignete Mechanismen zur Zinsbegrenzung?
- Erlangt es anmutig

## Prüfung der Trockenheit

Lange Prüfung zur Erkennung von Speicherlecks oder Ressourcenerschöpfung

**Datei„ :" `soak-test.js`**

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 20 },    // Ramp up
    { duration: '3h', target: 20 },    // Stay at 20 for 3 hours
    { duration: '2m', target: 0 },     // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  const response = http.get(`${BASE_URL}/post/test-post-${Math.floor(Math.random() * 10) + 1}`);

  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time stable': (r) => r.timings.duration < 500,
  });

  sleep(2);
}
```

**Führen Sie den Test aus.**

```bash
k6 run soak-test.js
```

**Wichtig**Überwachen Sie die Systemressourcen während der Durchsickertests

```bash
# In another terminal
watch -n 5 'dotnet-counters ps | grep -i minimal'

# Or use top/htop
htop -p $(pgrep -f MinimalBlog)
```

Beobachten Sie nach ,:'

- Stufenweise Erhöhung des Gedächtnisses (MSK)
- CPU-Nutzung kriechen nach oben
- Ansprechzeitabbau im Laufe der Zeit

## Testen Sie die realistische Benutzerreise

Simulieren Sie das tatsächliche Benutzerverhalten

**Datei„ :" `user-journey-test.js`**

```javascript
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { htmlReport } from 'https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js';

export const options = {
  vus: 10,
  duration: '5m',
  thresholds: {
    'group_duration{group:::01_homepage}': ['avg<500'],
    'group_duration{group:::02_browse_posts}': ['avg<500'],
    'group_duration{group:::03_categories}': ['avg<500'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  // Simulate a real user journey

  group('01_homepage', () => {
    const response = http.get(`${BASE_URL}/`);
    check(response, {
      'homepage loaded': (r) => r.status === 200,
      'homepage has posts': (r) => r.body.includes('Test Post'),
    });
    sleep(Math.random() * 3 + 2); // Read homepage 2-5 seconds
  });

  group('02_browse_posts', () => {
    // User clicks on a post
    let response = http.get(`${BASE_URL}/post/test-post-1`);
    check(response, {
      'post loaded': (r) => r.status === 200,
      'post has content': (r) => r.body.length > 500,
    });
    sleep(Math.random() * 20 + 10); // Read post 10-30 seconds

    // User clicks another post
    response = http.get(`${BASE_URL}/post/test-post-2`);
    check(response, {
      'second post loaded': (r) => r.status === 200,
    });
    sleep(Math.random() * 15 + 5); // Read second post 5-20 seconds
  });

  group('03_categories', () => {
    // User explores categories
    const response = http.get(`${BASE_URL}/categories`);
    check(response, {
      'categories loaded': (r) => r.status === 200,
    });
    sleep(2);

    // User clicks a category
    const categoryResponse = http.get(`${BASE_URL}/category/Testing`);
    check(categoryResponse, {
      'category posts loaded': (r) => r.status === 200,
      'category has posts': (r) => r.body.includes('Test Post'),
    });
    sleep(Math.random() * 10 + 5); // Browse category 5-15 seconds
  });
}

export function handleSummary(data) {
  return {
    'user-journey-report.html': htmlReport(data),
  };
}
```

**Führen Sie den Test aus.**

```bash
k6 run user-journey-test.js
```

Dies erzeugt einen HTML-Bericht. `user-journey-report.html`

## Erweiterte Tests der MetaWeblog API

Wenn Sie MetaWeblog aktiviert haben, testen Sie es auch.

**Datei„ :" `metaweblog-test.js`**

```javascript
import http from 'k6/http';
import { check } from 'k6';
import encoding from 'k6/encoding';

export const options = {
  vus: 1,
  iterations: 10,
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';
const USERNAME = __ENV.USERNAME || 'admin';
const PASSWORD = __ENV.PASSWORD || 'changeme';

function createXmlRpcRequest(methodName, params) {
  return `<?xml version="1.0"?>
<methodCall>
  <methodName>${methodName}</methodName>
  <params>
    ${params}
  </params>
</methodCall>`;
}

export default function() {
  // Test getRecentPosts
  const recentPostsXml = createXmlRpcRequest('blogger.getRecentPosts', `
    <param><value><string>0</string></value></param>
    <param><value><string>${USERNAME}</string></value></param>
    <param><value><string>${PASSWORD}</string></value></param>
    <param><value><int>10</int></value></param>
  `);

  const response = http.post(`${BASE_URL}/metaweblog`, recentPostsXml, {
    headers: { 'Content-Type': 'text/xml' },
  });

  check(response, {
    'MetaWeblog API responds': (r) => r.status === 200,
    'MetaWeblog returns XML': (r) => r.body.includes('<?xml'),
    'MetaWeblog has methodResponse': (r) => r.body.includes('methodResponse'),
  });
}
```

**Führen Sie den Test aus.**

```bash
k6 run -e USERNAME=admin -e PASSWORD=yourpassword metaweblog-test.js
```

## Laufende Tests in CI-/-CD

### Die Verwendung von k-6® als Qualitätstor

Die leistungsstärkste Verwendung von k-6- in CI-MSC1-CD ist als **Qualitäts-Gate** Fehlerhafte Builds automatisch, wenn die Leistung abgebaut wird... Hier ist es, wie man umfassende Tests in GitHub-Aktionen implementiert.

### GitHub-Aktionen – Basisverifizierung

Let.' s beginnen mit einem grundlegenden Workflow, der auf jeder Pull-Anfrage läuft und Blöcke verschmelzen, wenn Tests fehlschlagen

Erstellen `.github/workflows/k6-pr-check.yml`:

```yaml
name: k6 Performance Check

on:
  pull_request:
    branches: [ main ]

jobs:
  performance-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Setup k6
        run: |
          sudo gpg -k
          sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
            --keyserver hkp://keyserver.ubuntu.com:80 \
            --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | \
            sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update
          sudo apt-get install k6

      - name: Create test data
        run: |
          mkdir -p TestMarkdown
          for i in {1..10}; do
            cat > TestMarkdown/test-post-$i.md <<EOF
          # Test Post $i
          <!-- category -- Testing, Performance -->
          <datetime class="hidden">2025-12-01T12:00</datetime>

          This is test post $i with **bold** and *italic* text.

          \`\`\`csharp
          public class Test { public int Value { get; set; } }
          \`\`\`
          EOF
          done

      - name: Build MinimalBlog
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet build --configuration Release

      - name: Start MinimalBlog
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet run --configuration Release &
          echo $! > app.pid

          # Wait for app to be ready
          timeout 60 bash -c 'until curl -sf http://localhost:5000 > /dev/null; do
            echo "Waiting for app..."
            sleep 2
          done'

          echo "- App is ready!"

      - name: Run Smoke Test
        id: smoke-test
        run: |
          k6 run --out json=smoke-results.json k6-tests/smoke-test.js
          echo "smoke-test-passed=true" >> $GITHUB_OUTPUT

      - name: Run Load Test
        id: load-test
        run: |
          k6 run --out json=load-results.json k6-tests/load-test.js
          echo "load-test-passed=true" >> $GITHUB_OUTPUT

      - name: Run Cache Test
        id: cache-test
        run: |
          k6 run --out json=cache-results.json k6-tests/cache-test.js
          echo "cache-test-passed=true" >> $GITHUB_OUTPUT

      - name: Stop MinimalBlog
        if: always()
        run: |
          if [ -f Mostlylucid.MinimalBlog.Demo/app.pid ]; then
            kill $(cat Mostlylucid.MinimalBlog.Demo/app.pid) || true
          fi

      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: k6-test-results
          path: |
            *-results.json
            *.html
          retention-days: 30

      - name: Check Test Results
        if: always()
        run: |
          echo "## k6 Performance Test Results" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          if [ "${{ steps.smoke-test.outputs.smoke-test-passed }}" == "true" ]; then
            echo "- Smoke Test: PASSED" >> $GITHUB_STEP_SUMMARY
          else
            echo "- Smoke Test: FAILED" >> $GITHUB_STEP_SUMMARY
          fi

          if [ "${{ steps.load-test.outputs.load-test-passed }}" == "true" ]; then
            echo "- Load Test: PASSED" >> $GITHUB_STEP_SUMMARY
          else
            echo "- Load Test: FAILED" >> $GITHUB_STEP_SUMMARY
          fi

          if [ "${{ steps.cache-test.outputs.cache-test-passed }}" == "true" ]; then
            echo "- Cache Test: PASSED" >> $GITHUB_STEP_SUMMARY
          else
            echo "- Cache Test: FAILED" >> $GITHUB_STEP_SUMMARY
          fi
```

**Hauptmerkmale**

- Läuft auf jeder PR
- Erstellt automatisch Testdaten
- Wartet, bis die App vor dem Testen fertig ist
- Funktioniert mehrere Testtypen
- **Fehler beim Bauen, wenn die Schwellenwerte nicht eingehalten werden**
- Hochladen der Ergebnisse als Artefakte
- Zeigt Zusammenfassung in GitHub UI

```mermaid
sequenceDiagram
    participant PR as Pull Request
    participant GHA as GitHub Actions
    participant App as MinimalBlog
    participant k6 as k6 Tests

    PR->>GHA: Trigger workflow
    GHA->>GHA: Setup .NET & k6
    GHA->>GHA: Create test data
    GHA->>App: Build & Start
    App-->>GHA: App ready
    GHA->>k6: Run smoke test
    k6-->>GHA: Results
    GHA->>k6: Run load test
    k6-->>GHA: Results
    GHA->>k6: Run cache test
    k6-->>GHA: Results
    GHA->>App: Stop
    alt Tests Pass
        GHA->>PR: Mark as success
    else Tests Fail
        GHA->>PR: Block merge
    end
```

### GitHub-Aktionen mit PR-Kommentaren

Erstellen `.github/workflows/k6-pr-comment.yml` Ergebnisse als PR-Kommentare posten

```yaml
name: k6 Performance with PR Comment

on:
  pull_request:
    branches: [ main ]

permissions:
  pull-requests: write
  contents: read

jobs:
  performance-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Setup k6
        uses: grafana/setup-k6-action@v1

      - name: Create test data
        run: |
          mkdir -p TestMarkdown
          for i in {1..10}; do
            cat > TestMarkdown/test-post-$i.md <<EOF
          # Test Post $i
          <!-- category -- Testing -->
          <datetime class="hidden">2025-12-01T12:00</datetime>
          Test content for post $i.
          EOF
          done

      - name: Build and Start App
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet build -c Release
          dotnet run -c Release > app.log 2>&1 &
          APP_PID=$!
          echo $APP_PID > app.pid

          timeout 60 bash -c 'until curl -sf http://localhost:5000; do sleep 2; done'

      - name: Run k6 Tests
        id: k6-test
        run: |
          # Run tests and capture output
          k6 run --out json=results.json k6-tests/load-test.js > k6-output.txt 2>&1 || true

          # Parse results
          if grep -q "✓" k6-output.txt; then
            echo "tests-passed=true" >> $GITHUB_OUTPUT
          else
            echo "tests-passed=false" >> $GITHUB_OUTPUT
          fi

          # Extract key metrics
          P95=$(grep "http_req_duration" k6-output.txt | grep -oP 'p\(95\)=\K[0-9.]+' || echo "N/A")
          RPS=$(grep "http_reqs" k6-output.txt | grep -oP '\d+\.\d+/s' || echo "N/A")
          ERRORS=$(grep "http_req_failed" k6-output.txt | grep -oP '\d+\.\d+%' || echo "0%")

          echo "p95=${P95}" >> $GITHUB_OUTPUT
          echo "rps=${RPS}" >> $GITHUB_OUTPUT
          echo "errors=${ERRORS}" >> $GITHUB_OUTPUT

      - name: Comment PR
        uses: actions/github-script@v7
        if: always()
        with:
          script: |
            const fs = require('fs');
            const output = fs.readFileSync('k6-output.txt', 'utf8');

            const testsPassed = '${{ steps.k6-test.outputs.tests-passed }}' === 'true';
            const icon = testsPassed ? 'PASS' : 'FAIL';
            const status = testsPassed ? 'PASSED' : 'FAILED';

            const comment = `## ${icon} k6 Performance Test Results

            **Status:** ${status}

            ### Key Metrics
            | Metric | Value |
            |--------|-------|
            | P95 Response Time | ${{ steps.k6-test.outputs.p95 }}ms |
            | Requests/sec | ${{ steps.k6-test.outputs.rps }} |
            | Error Rate | ${{ steps.k6-test.outputs.errors }} |

            ### Thresholds
            - P95 < 300ms
            - Error rate < 1%

            <details>
            <summary>Full k6 Output</summary>


            \`\`\`
            ${output}
            \`\`\`

            </details>

            [View full test results](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
            `;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: comment
            });

      - name: Stop App
        if: always()
        run: |
          [ -f Mostlylucid.MinimalBlog.Demo/app.pid ] && \
            kill $(cat Mostlylucid.MinimalBlog.Demo/app.pid) || true

      - name: Upload Artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: k6-results
          path: |
            results.json
            k6-output.txt
            Mostlylucid.MinimalBlog.Demo/app.log
```

**Dieser Workflow**

- Posts Ergebnisse direkt an PR
- Zeigt Schlüsselmetriken in einer Tabelle an
- Bietet volle Ausgabe im erweiterbaren Abschnitt
- Links zum vollständigen Testlauf
- Visuelle Statusindikatoren

### GitHub-Aktionen - Leistungsregressionserkennung

Erstellen `.github/workflows/k6-baseline.yml` um Leistungsregressionen zu erkennen

```yaml
name: Performance Regression Check

on:
  pull_request:
    branches: [ main ]

jobs:
  regression-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout PR code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Setup k6
        uses: grafana/setup-k6-action@v1

      - name: Create test data
        run: |
          mkdir -p TestMarkdown
          for i in {1..20}; do
            echo "# Test Post $i" > TestMarkdown/test-$i.md
            echo "<!-- category -- Test -->" >> TestMarkdown/test-$i.md
            echo '<datetime class="hidden">2025-12-01T12:00</datetime>' >> TestMarkdown/test-$i.md
            echo "Test content $i" >> TestMarkdown/test-$i.md
          done

      - name: Test PR Branch
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet build -c Release
          dotnet run -c Release &
          APP_PID=$!

          timeout 60 bash -c 'until curl -sf http://localhost:5000; do sleep 2; done'

          # Run test and save results
          k6 run --summary-export=pr-results.json k6-tests/load-test.js

          kill $APP_PID
          sleep 5

      - name: Checkout main branch
        uses: actions/checkout@v4
        with:
          ref: main
          path: baseline

      - name: Test Baseline (main branch)
        run: |
          cd baseline/Mostlylucid.MinimalBlog.Demo
          dotnet build -c Release
          dotnet run -c Release &
          APP_PID=$!

          timeout 60 bash -c 'until curl -sf http://localhost:5000; do sleep 2; done'

          # Run test and save results
          k6 run --summary-export=baseline-results.json ../k6-tests/load-test.js

          kill $APP_PID

      - name: Compare Results
        run: |
          # Extract P95 from both runs
          PR_P95=$(jq '.metrics.http_req_duration.values["p(95)"]' pr-results.json)
          BASE_P95=$(jq '.metrics.http_req_duration.values["p(95)"]' baseline/baseline-results.json)

          # Calculate percentage change
          CHANGE=$(echo "scale=2; (($PR_P95 - $BASE_P95) / $BASE_P95) * 100" | bc)

          echo "## Performance Comparison" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "| Branch | P95 Response Time |" >> $GITHUB_STEP_SUMMARY
          echo "|--------|-------------------|" >> $GITHUB_STEP_SUMMARY
          echo "| main (baseline) | ${BASE_P95}ms |" >> $GITHUB_STEP_SUMMARY
          echo "| PR | ${PR_P95}ms |" >> $GITHUB_STEP_SUMMARY
          echo "| Change | ${CHANGE}% |" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          # Fail if performance degrades by more than 20%
          if (( $(echo "$CHANGE > 20" | bc -l) )); then
            echo "- Performance degraded by ${CHANGE}%" >> $GITHUB_STEP_SUMMARY
            echo "::error::Performance regression detected: ${CHANGE}% slower than baseline"
            exit 1
          else
            echo "- Performance acceptable" >> $GITHUB_STEP_SUMMARY
          fi
```

**Dieser Workflow**

- Tests sowohl PR als auch Hauptzweig
- Vergleichen von Leistungskennzahlen
- **Fehlt, wenn die Leistung abwertet**
- Verhindert das Zusammenführen von langsamem Code

```mermaid
graph TD
    A[PR Submitted] --> B[Checkout PR Code]
    B --> C[Test PR Branch]
    C --> D[Checkout main Branch]
    D --> E[Test Baseline]
    E --> F{Calculate<br/>Difference}
    F -->|>20% Slower| G[FAIL Build]
    F -->|<20% Change| H[PASS Build]
    G --> I[Block Merge]
    H --> J[Allow Merge]

    style G stroke:#ff6b6b
    style H stroke:#51cf66
```

### Best Practices für die Prüfung

1. **Schnelles, schnelles und frühes Tor starten**
   
   - Führen Sie zuerst Rauchtests aus.
   - Nur vollständige Tests durchführen, wenn Rauch vorüber ist
   - Fehler beim schnellen Speichern von CI-Minuten

2. **Angemessene Schwellen verwenden**
   
   ```javascript
   // Too strict for CI
   thresholds: { http_req_duration: ['p(95)<100'] }
   
   // Good for CI
   thresholds: {
     http_req_duration: ['p(95)<300', 'p(99)<1000'],
     http_req_failed: ['rate<0.01']
   }
   ```

3. **Abhängigkeiten zwischen Zwischenspeichern**
   
   ```yaml
   - name: Cache .NET packages
     uses: actions/cache@v4
     with:
       path: ~/.nuget/packages
       key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
   ```

4. **Protokolle für fehlgeschlagene Tests sammeln**
   
   ```yaml
   - name: Upload App Logs
     if: failure()
     uses: actions/upload-artifact@v4
     with:
       name: app-logs
       path: Mostlylucid.MinimalBlog.Demo/app.log
   ```

5. **Verwendung von Umgebungsschutz-Spezifischen Schwellenwerten**
   
   ```javascript
   const isPR = __ENV.GITHUB_EVENT_NAME === 'pull_request';
   
   export const options = {
     thresholds: {
       http_req_duration: isPR
         ? ['p(95)<500']  // Relaxed for PR
         : ['p(95)<300'], // Strict for main
     },
   };
   ```

## Überwachung von Tests mit Grafana

Für eine fortgeschrittene Überwachung können Sie k streamen.6 metrics to Grafana.

### Einrichtung von Prometheus und Grafana

**docker---kompose-.-yml** für die Überwachung

```yaml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - prometheus
```

### Ausführen mit Prometheus-Ausgabe

```bash
k6 run --out experimental-prometheus-rw load-test.js
```

### Dashboard in Grafana importieren

1. Zugang zu Grafana bei `http://localhost:3000`
2. Prometheus datasource hinzufügen
3. Importieren Sie das Dashboard von k-6.

## Verdolmetschung der Metrics

Schlüsselmetriken zum Verständnis von :

### Ansprechzeit Metrics

- **Schlussfolgerungen des Europäischen Rates von Lissabon vom 22. Juni 2014 (ABl.**Gesamte Anfragezeit
  - `avg`Durchschnittliche Ansprechzeit
  - `min/max`Schnellste Reaktion der /
  - `med`Mittelmäßiger Perzentilwert der )
  - `p(90)`90. Perzentil
  - `p(95)`,: , , 95, Perzentil , ( , gute SLA-Metrik , ) ,
  - `p(99)`Ausreißer der )

**Gute Werte für MinimalBlog-:**

- Für gecachete Inhalte
- P.M.S.K.2ms für die ersten Belastungszeiten

### Durchsatz Metrics

- **Diese Daten sind in der Datenbank http:www.cordis.lu/news/index.htm enthalten.**Anfragen insgesamt
- **Die Prüfchemikalie darf nicht mehr als 10 % des Gesamtgehalts der in Anhang I der Verordnung (EU) Nr. 1308/2013 aufgeführten Stoffe enthalten.**Anträge pro Sekunde
- **iteration-_-Dauer**Zeit für eine vollständige Iteration

### Fehler-Metriken

- **(englisch) Einzelnachweise **Ausgefallene Anfragen
- **Kontrollen**Nicht erfolgsversprechendes Ergebnis

**Fehlerrate des Ziels:**

### Datenübertragung

- **Daten, die empfangen wurden**Download-Daten
- **Datenauswertung**Hochgeladene Daten

## Best Practices for Testing MinimalBlog

### Test in der Isolation

MinimalBlog separat von anderen Diensten testen

```bash
# Stop other services
docker-compose down

# Run only MinimalBlog
dotnet run --project Mostlylucid.MinimalBlog.Demo
```

### Verwenden Sie realistische Daten

Don-Test mit leeren Markdown-Dateien Verwenden Sie realistische Inhalte

- Verschiedene Pfostenlängen
- Verschiedene Markdown-Features (-Codeblöcke,-Bilder,-Listen)-Codeblocks
- Mehrere Kategorien
- Real---Welt nach der Verteilung

### Behavior explizit testen

Minimale Blog-Performance von ' basiert auf Caching-..

```javascript
// Test cold cache
export function setup() {
  // Clear cache by restarting app or waiting for expiration
}

// Test warm cache
export default function() {
  // First request (cache miss)
  http.get(`${BASE_URL}/post/test-1`);

  // Second request (cache hit)
  const cached = http.get(`${BASE_URL}/post/test-1`);
  check(cached, {
    'cache hit is faster': (r) => r.timings.duration < 50,
  });
}
```

### Überwachung der Systemressourcen

Führen Sie diese neben den Tests aus.

```bash
# CPU and memory
htop

# .NET metrics
dotnet-counters monitor -p $(pgrep -f MinimalBlog)

# GC collections
dotnet-counters monitor --counters System.Runtime[gc-heap-size,gen-0-gc-count,gen-1-gc-count,gen-2-gc-count]
```

### Realistische Schwellen setzen

Zu strenge oder zu lockere Schwellen setzen

```javascript
export const options = {
  thresholds: {
    // Too strict (will fail unnecessarily)
    // http_req_duration: ['p(95)<50'],

    // Too loose (won't catch problems)
    // http_req_duration: ['p(95)<5000'],

    // Just right for MinimalBlog
    http_req_duration: ['p(95)<300', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
    checks: ['rate>0.95'],
  },
};
```

### Testen Sie verschiedene Szenarien

```javascript
// Homepage (list view)
http.get(`${BASE_URL}/`);

// Individual posts (detail view)
http.get(`${BASE_URL}/post/getting-started`);

// Categories (filtering)
http.get(`${BASE_URL}/category/Tutorial`);

// Non-existent posts (error handling)
http.get(`${BASE_URL}/post/does-not-exist`);
```

### Version Ihre Tests

Halten Sie Tests in Versionskontrolle neben code

```
Mostlylucid.MinimalBlog/
├── k6-tests/
│   ├── smoke-test.js
│   ├── load-test.js
│   ├── stress-test.js
│   ├── spike-test.js
│   ├── soak-test.js
│   ├── cache-test.js
│   └── user-journey-test.js
├── .github/
│   └── workflows/
│       └── k6-tests.yml
└── README-TESTING.md
```

## Fehlerbehebung bei gemeinsamen Problemen

### Problem-Verbindung versagt

**Problemstellung**

```
WARN[0000] Request Failed error="Get \"http://localhost:5000/\": dial tcp [::1]:5000: connect: connection refused"
```

**Lösung**

```bash
# Verify MinimalBlog is running
curl http://localhost:5000

# Check correct port
netstat -an | grep 5000

# Use correct URL
k6 run -e BASE_URL=http://localhost:5173 smoke-test.js
```

### Ausgabe Hohe Fehlerrate

**Problemstellung**

```
✗ http_req_failed: 45.23% (threshold is < 1%)
```

**Lösung**

1. Prüfen Sie Anwendungsprotokolle auf Fehler
2. Prüfen Sie die Testdaten vorhanden ist.
3. Reduzieren Sie die VU-Anzahl.( kann überladen sein.
4. Rampe erhöhen --Up Zeit

### Ausgabe - Langsame Reaktionszeiten

**Problemstellung**

```
http_req_duration..............: avg=2.5s p(95)=5.2s
```

**Lösung**

1. Im Release-Modus aufbauen `dotnet run -c Release`
2. Caching wird überprüft
3. Festplatte überprüfen I/-O Datei lesen (-slow
4. Profil mit dotnet---Trace-:-

```bash
dotnet-trace collect -p $(pgrep -f MinimalBlog) --format speedscope
```

### Ausgabe 4:-Speicherlecks bei Soak-Tests

**Problemstellung** Speichernutzung wächst kontinuierlich

**Lösung**

1. Überwachen mit ,:'

```bash
dotnet-counters monitor --counters System.Runtime[gc-heap-size,gc-committed,gc-allocated]
```

2. Sammle einen Speicher Dump.:.

```bash
dotnet-dump collect -p $(pgrep -f MinimalBlog)
dotnet-dump analyze memory.dump
```

3. Prüfen nach:
   - Cache, der nicht ausläuft
   - Datei-Handles schließen nicht
   - Event-Handler nicht abbestellen

## Reale Tests vor der Freigabe

Hier ist ein vollständiger Test-Workflow vor der Freigabe von MinimalBlog.:

```bash
#!/bin/bash
# test-release.sh

set -e

echo "Building: Building MinimalBlog in Release mode..."
dotnet build -c Release Mostlylucid.MinimalBlog/Mostlylucid.MinimalBlog.csproj

echo "Starting: Starting MinimalBlog Demo..."
dotnet run -c Release --project Mostlylucid.MinimalBlog.Demo &
APP_PID=$!

# Wait for app to start
echo "Waiting: Waiting for app to start..."
timeout 30 bash -c 'until curl -s http://localhost:5000 > /dev/null; do sleep 1; done'

echo "- App started successfully"

# Run test suite
echo "Running: Running smoke tests..."
k6 run k6-tests/smoke-test.js

echo "Running: Running cache validation..."
k6 run k6-tests/cache-test.js

echo "Running: Running load tests..."
k6 run k6-tests/load-test.js

echo "Running: Running stress tests..."
k6 run k6-tests/stress-test.js

echo "Running: Running spike tests..."
k6 run k6-tests/spike-test.js

echo "Generating: Generating combined report..."
# Process and combine results

echo "Stopping: Stopping app..."
kill $APP_PID

echo "- All tests completed successfully!"
```

**Führen Sie es aus.**

```bash
chmod +x test-release.sh
./test-release.sh
```

## Profilierung mit dotTrace und dotMemory

Das sagt dir K.6. **Was ist** ist langsam, aber Profiler sagen dir: **warum**Mit der Kombination von k-6-Lasttests mit JetBrains Profilern können Sie exakte Engpässe im MinimalBlog-Code erkennen.

### Warum Profil unter Last?

Profilieren ohne Last oft verpasst real---Welt-Probleme

- **Kaltstart vs. warmer Cache**Verschiedene Code-Pfads
- **Gleichzeitiger Zugriff**Zögern Sie nicht, das Rennen zu beenden.
- **Gedächtnisdruck**GC-Verhalten unter Last
- **Erschöpfung der Ressourcen**Datei-Handles für die Verbindung pools

k-6 Erstellt realistische Last, während Profiler erfassen, was in Ihrer App geschieht.

### Installation von JetBrains-Werkzeugen

**Option: JetBrains dotTrace-/ dotMemory - Kommerziell - )**

Download von [JetBrains](https://www.jetbrains.com/profiler/):

```bash
# Linux/Mac
wget https://download.jetbrains.com/resharper/dotUltimate.2024.3/JetBrains.dotTrace.GlobalTools.2024.3.nupkg

# Install as global tool
dotnet tool install --global JetBrains.dotTrace.GlobalTools
dotnet tool install --global JetBrains.dotMemory.Console
```

**Option 2:-Punktnetz--Trace-Anschluss**

```bash
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-dump
```

Für diesen Leitfaden zeigen wir beide Ansätze an

### Profilierung des Arbeitsablaufs

```mermaid
flowchart LR
    A[Start App] --> B[Baseline<br/>Snapshot]
    B --> C[Start Profiler]
    C --> D[Run k6 Load Test]
    D --> E[Capture Metrics]
    E --> F[Stop Profiler]
    F --> G[Final Snapshot]
    G --> H{Analyze Results}
    H --> I[Identify<br/>Hotspots]
    I --> J[Optimize Code]
    J --> K[Re-test with k6]
    K --> L{Better?}
    L -->|Yes| M[Document & Deploy]
    L -->|No| I

    style M stroke:#51cf66
    style I stroke:#FFD700
```

**Schritt 1:-Basis ohne Last**
**Schritt-Profil unter der Last**
**Schritt - Vergleich und Identifizierung von Engpässen**
**Schritt Befestigen und wiederholen**

### Performance Profiling mit dotTrace

#### Szenario: Das langsame Markdown Parsing finden

**Starten Sie MinimalBlog mit Profilierung angebracht**

```bash
cd Mostlylucid.MinimalBlog.Demo

# Using dotTrace
dottrace attach <PID> --save-to=profile-baseline.dtt --timeout=60s

# Or build and run with profiling
dotnet build -c Release
dotnet run -c Release &
APP_PID=$!

# Attach profiler
dottrace attach $APP_PID --save-to=profile-load.dtt --timeout=120s
```

**In einem anderen Terminal führen Sie die Belastungsprüfung durch.**

```bash
# This generates load while profiler captures data
k6 run --duration 60s --vus 20 k6-tests/load-test.js
```

**Analysieren Sie das Profil**

```bash
# Convert to speedscope format for web viewing
dottrace report profile-load.dtt --output=report.html
```

**Was im Bericht zu suchen ist**

- **Heiße Pfade**Methoden, die die meiste CPU-Zeit verbrauchen
- **Aufrufbaum**Wo die Zeit in der Call-Hierarchie verbracht wird
- **Selbständigkeit**Zeit in der Methode ohne Aufrufe zu anderen Methoden

**Beispiele für Ergebnisse in MinimalBlog-:**

```
Method                                    Total Time    Self Time    Calls
-----------------------------------------------------------------------
 MarkdownBlogService.GetAllPosts()         450ms        5ms          100
  ├─ LoadAllPosts()                       420ms        10ms         10
  │   ├─ ParseFile()                      400ms        20ms         100
  │   │   ├─ Markdown.Parse()             250ms        250ms        100   HOTSPOT
  │   │   └─ Regex.Match() (categories)   80ms         80ms         100   HOTSPOT
  │   └─ File.ReadAllText()               10ms         10ms         100
  └─ cache.GetOrCreate()                  25ms         25ms         90
```

Dies zeigt:

- `Markdown.Parse()` ist der Engpass aus dem Flaschenhals aus dem (250.
- Regex-Matching ist insgesamt
- Die meiste Zeit in den ersten Anrufen von 10, die dann zwischengespeichert werden

#### Verwendung von Dotnet---Trace-Methoden

```bash
# Start tracing
dotnet-trace collect -p $APP_PID --format speedscope -o trace.json &
TRACE_PID=$!

# Run k6 test
k6 run --duration 30s --vus 10 k6-tests/load-test.js

# Stop tracing
kill -SIGINT $TRACE_PID

# View in browser
# Upload trace.json to https://www.speedscope.app/
```

**Geschwindigkeitsmesser-Visualisierung**

- Flammendiagramm zeigt Rufstapel
- Zeitreihenansicht zeigt Ausführung im Laufe der Zeit
- Links starke Ansicht zeigt langsame Operationen

### Speicherprofilierung mit PunktSpeicher

#### Szenario: Speicherlecks im Cache erkennen

**Starten Sie MinimalBlog und befestigen Sie Memory-Profiler**

```bash
cd Mostlylucid.MinimalBlog.Demo
dotnet run -c Release &
APP_PID=$!

# Take initial snapshot
dotmemory get-snapshot $APP_PID --save-to=snapshot-start.dmw
```

**Ausführen der Belastungsprüfung**

```bash
# Run for 5 minutes with constant load
k6 run --duration 5m --vus 20 k6-tests/load-test.js
```

**Nehmen Sie Snapshots während und nach test.**

```bash
# During load (after 2 minutes)
sleep 120
dotmemory get-snapshot $APP_PID --save-to=snapshot-load.dmw

# After load (cool down)
# Wait for k6 to finish, then wait 2 more minutes
sleep 120
dotmemory get-snapshot $APP_PID --save-to=snapshot-after.dmw
```

**Analysieren des Speicherwachstums**

```bash
# Compare snapshots
dotmemory compare snapshot-start.dmw snapshot-after.dmw --save-to=comparison.html
```

**Was zu suchen ist**

- **Wachsende Sammlungen**Wörterbücher, die nie schrumpfen
- **Ereignisbetreuer**Nicht abonnierte Ereignisse mit Referenzen
- **Cache-Aufblähung**Der Cache wächst über die erwartete Größe hinaus
- **String-Zuweisungen**Übermäßige String-Verkettung

**Beispiele für Ergebnisse**

```
Object Type                      Start    Load     After    Growth
-------------------------------------------------------------------
System.String                    5 MB     45 MB    15 MB    +10 MB
BlogPost[]                       2 MB     2 MB     2 MB     0 MB    PASS
MarkdownDocument                 0 MB     8 MB     0 MB     0 MB    - (GC'd)
Dictionary<string, BlogPost>     1 MB     1 MB     1 MB     0 MB    PASS
```

Dies zeigt:

- Strings wuchsen um 10, MB Potenzielles String-Leak oder Cache-Problem
- BlogPost-Cache ist stabil
- MarkdownDokument von GC gereinigt

#### Verwendung von dotnet---dump - Kostenlose Alternative

```bash
# Capture memory dump during load
dotnet-dump collect -p $APP_PID -o dump-load.dmp

# After test
dotnet-dump collect -p $APP_PID -o dump-after.dmp

# Analyze dump
dotnet-dump analyze dump-load.dmp

> dumpheap -stat
> dumpheap -mt <MethodTable> -min 1000
> gcroot <address>
```

### Kombiniertes Zeichen für das Profiling

Hier ist ein komplettes Skript, das während des Profilings läuft.

**Datei„ :" `profile-under-load.sh`**

```bash
#!/bin/bash
set -e

echo "Profiling: Profiling MinimalBlog under k6 load"

# Configuration
DURATION="120s"
VUS="20"
OUTPUT_DIR="profiling-results"

mkdir -p $OUTPUT_DIR

# Build and start app
echo "Building: Building MinimalBlog..."
cd Mostlylucid.MinimalBlog.Demo
dotnet build -c Release > /dev/null

echo "Starting: Starting MinimalBlog..."
dotnet run -c Release > $OUTPUT_DIR/app.log 2>&1 &
APP_PID=$!
echo $APP_PID > $OUTPUT_DIR/app.pid

# Wait for startup
timeout 60 bash -c "until curl -sf http://localhost:5000 > /dev/null; do sleep 2; done"
echo "- App started (PID: $APP_PID)"

# Baseline metrics
echo "Generating: Collecting baseline metrics..."
dotnet-counters collect -p $APP_PID --format json -o $OUTPUT_DIR/baseline-metrics.json &
COUNTER_PID=$!
sleep 10
kill -SIGINT $COUNTER_PID

# Take initial memory snapshot
echo "Snapshot: Initial memory snapshot..."
if command -v dotmemory &> /dev/null; then
    dotmemory get-snapshot $APP_PID --save-to=$OUTPUT_DIR/snapshot-start.dmw
else
    dotnet-dump collect -p $APP_PID -o $OUTPUT_DIR/dump-start.dmp
fi

# Start performance profiling
echo "Starting: Starting performance profiler..."
if command -v dottrace &> /dev/null; then
    dottrace attach $APP_PID --save-to=$OUTPUT_DIR/performance-profile.dtt --timeout=150s &
    PROFILER_PID=$!
else
    dotnet-trace collect -p $APP_PID --format speedscope -o $OUTPUT_DIR/trace.json &
    PROFILER_PID=$!
fi

sleep 5

# Run k6 load test
echo "Running: Running k6 load test ($DURATION, $VUS VUs)..."
cd ..
k6 run --duration $DURATION --vus $VUS \
    --out json=$OUTPUT_DIR/k6-results.json \
    k6-tests/load-test.js | tee $OUTPUT_DIR/k6-output.txt

echo "- k6 test completed"

# Wait for profiler to finish
wait $PROFILER_PID
echo "- Performance profile captured"

# Take final memory snapshot
echo "Snapshot: Final memory snapshot..."
if command -v dotmemory &> /dev/null; then
    dotmemory get-snapshot $APP_PID --save-to=$OUTPUT_DIR/snapshot-end.dmw

    echo "Comparing: Comparing memory snapshots..."
    dotmemory compare $OUTPUT_DIR/snapshot-start.dmw $OUTPUT_DIR/snapshot-end.dmw \
        --save-to=$OUTPUT_DIR/memory-comparison.html
else
    dotnet-dump collect -p $APP_PID -o $OUTPUT_DIR/dump-end.dmp
fi

# Stop app
echo "Stopping: Stopping MinimalBlog..."
kill $APP_PID

# Generate reports
echo "Generating: Generating reports..."

if command -v dottrace &> /dev/null; then
    dottrace report $OUTPUT_DIR/performance-profile.dtt \
        --output=$OUTPUT_DIR/performance-report.html
fi

# Summary
echo ""
echo "========================================="
echo "- Profiling Complete!"
echo "========================================="
echo ""
echo "Results in: $OUTPUT_DIR/"
echo ""
echo "Files generated:"
ls -lh $OUTPUT_DIR/
echo ""
echo "Generating: View results:"
echo "  - k6 output: cat $OUTPUT_DIR/k6-output.txt"
echo "  - Performance profile: open $OUTPUT_DIR/performance-report.html"
echo "  - Memory comparison: open $OUTPUT_DIR/memory-comparison.html"
echo "  - Speedscope trace: https://www.speedscope.app/ (upload trace.json)"
echo ""
```

**Führen Sie es aus.**

```bash
chmod +x profile-under-load.sh
./profile-under-load.sh
```

### Best Practices ,: , k , 6 , Profiling

1. **Immer Profil unter realistischer Belastung**
   
   - Verwenden Sie k-6 , um Produktionsverkehrsmuster zu simulieren
   - Profil Leerlauf-Apps erzählt Ihnen nichts über echte Leistung

2. **Nehmen Sie mehrere Schnappschüsse**
   
   - Vor der Belastung
   - Während der Belastung
   - Nach dem Beladen prüfen Sie die Reinigung.

3. **Konzentriere dich zuerst auf heiße Pfade**
   
   - Die größten Engpässe zuerst beheben
   - Die Regel des Codes nimmt die Zeit ein.

4. **Maßnahme vor und nach**
   
   - Immer neue Tests nach der Optimierung durchführen
   - Verbesserung mit Profiler überprüfen

5. **Achten Sie auf Regressionen**
   
   - Baseline-Profile in Repo aufbewahren
   - Vergleich von Profilen in CI-/-CD

6. **Profilfreigabe erstellt**
   
   - Debug-Builds haben unterschiedliche Leistungsmerkmale
   - Immer verwenden `-c Release` für Profiling

### Zusammenfassung der Werkzeuge

Werkzeug für die Verwendung von | Zweck Kosten am besten für die Verarbeitung
|------|---------|------|----------|
| **K.M.S.K.** Beladungstests Kostenlos Belastbar finden Was langsam ist
| **dotTrace** Leistungsprofilierung Bezahltes Profilieren von |® WARUM es zu finden' slow
| **dotMemory** Speicherprofilierung Bezahlte Speicher-Leaks,, GC-Probleme
| **dotnet---Trace** Performance-Profiling Kostenlos Kostenloses CPU-Profilierungsprofil
| **dotnet---Zähler** Echtzeit-Metriken von | kostenlos Live-Monitoring
| **dotnet---dump** Memory Dumps Kostenlos Nach der --Mortem-Analyse
| **PerfView** Fortgeschrittenes Profiling Kostenlos Kostenloses Profiling für tiefe Windows-Profiling

## Schlussbemerkung: Das Vertrauen durch Testen stärken

Belastungstests mit k-6 , gibt Ihnen Vertrauen, dass MinimalBlog kann real umgehen.- , Weltverkehr , . , Bis zum Ende dieser zwei ,- ,Teil Führer , , , sollten Sie in der Lage sein zu sein , ,: ,

- Installieren Sie k-6 auf einer beliebigen Plattform
- Schreiben und führen Sie verschiedene Arten von Leistungstests aus
- Caching-Verhalten wird wie erwartet überprüft
- Leistungsengpässe finden, bevor sie die Produktion treffen
- Integrieren von Lasttests in Pipelines
- Überwachen und interpretieren Sie die Kennwerte von k-6.

### Schlüssel zum Mitnehmen

1. **Beginnen Sie mit Rauchtests** Verifizieren Sie die grundlegende Funktionalität vor dem tieferen Testen
2. **Test caching explizit** Die Leistung von minimalen Blogs ist davon abhängig.
3. **Realistische Daten verwenden** Geleerte Dateien stellen den realen Gebrauch dar.
4. **Überwachung der Systemressourcen** CPU-Speicher für ,C während Tests
5. **Bedeutungsvolle Schwellen setzen** Basierend auf den tatsächlichen Anforderungen, die nicht erraten werden
6. **Automatisieren Sie in CI-/-CD** Regressionen frühzeitig einfangen
7. **Testen Sie verschiedene Szenarien** Startseite,.Posts,.Kategorien,.Fehler

### Was wir über MinimalBlog gelernt haben

Durch die Prüfung von k-, können wir MinimalBlog-MSC2s-Forderungen überprüfen

- **Schnell**:-P-(95)-M<-300-M mit warmem Cache
- **Effizient**Niedriger Speicherverbrauch – minimaler GC-Druck
- **Skalierbar**Behandelt gleichzeitige Benutzer auf bescheidener Hardware
- **Zuverlässig**Fehlerrate unter normaler Last
- **Cache®--effektiv**Schnellere Reaktionen mit warmem Cache

### Nächste Schritte

Jetzt, da Sie wissen, wie man ein ASP-.-NET mit k.6: testet

1. Erstellen Sie ein Leistungsprofil für Ihr Setup
2. Automatisches Testen in Ihrer CI-/-CD Pipeline einrichten
3. Durchsickertests vor den wichtigsten Freisetzungen durchführen
4. Überwachung der Produktionsmetriken und Vergleich mit den Testergebnissen
5. Iterieren Sie erneut den MSK-Test.

Merken Sie sich: **Leistungsprüfung ist eine einmalige Aktivität**Wenn Sie Inhalte hinzufügen, ändern Sie den Code, , oder wechseln Sie die Hosting-Provider.,, führen Sie diese Tests durch, um sicherzustellen, dass Ihre App schnell und zuverlässig bleibt.

## Aufkommen

### Dokumentation

- [Amtliche Dokumente](https://k6.io/docs/) Gesamte Dokumentation
- [JavaScript API](https://k6.io/docs/javascript-api/) Alle verfügbaren APIs
- [Beispiele](https://github.com/grafana/k6-learn) Beispiel für Testskripte
- [Verlängerungen von k-6](https://k6.io/docs/extensions/) Verlängern Sie die Funktionalität von k-6
- [K.M.SK0-Metriken](https://k6.io/docs/using-k6/metrics/) Metriken verstehen
- [Schwellenwerte](https://k6.io/docs/using-k6/thresholds/) Einstellen von Pass-Kriterien
- [Szenarios](https://k6.io/docs/using-k6/scenarios/) Fortgeschrittene Testszenarien

### GitHub-Maßnahmen

- [GitHub Aktionen Docs](https://docs.github.com/en/actions) Dokumentation für vollständige Aktionen
- [Syntax des Workflows](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions) YAML-Syntaxreferenz
- [Verwendung von Artefakten](https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts) Speichern von Testergebnissen
- [Maßnahmen einrichten](https://github.com/grafana/setup-k6-action) Einstellungsaktion

### Profilierwerkzeuge

- [dotnet---Trace](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) Leistungsprofilierung
- [dotnet---Zähler](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) Metriken der Realzeit
- [dotnet---dump](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump) Speicher-Dumps
- [JetBrains dotTrace](https://www.jetbrains.com/profiler/) Technischer Profiler
- [JetBrains dot Memory](https://www.jetbrains.com/dotmemory/) Speicherprofiler
- [Geschwindigkeitsmesser](https://www.speedscope.app/) Flammendiagramm-Betrachter

### Überwachung der Visualisierung

- [k-6-Wolke](https://k6.io/cloud/) Prüfung auf Basis von k-6
- [Bericht erstattende Person](https://github.com/benc-uk/k6-reporter) HTML-Berichte
- [Grafana k-6-Dashboard](https://grafana.com/grafana/dashboards/18030) Das Armaturenbrett für die Metriken
- [Prometheus](https://prometheus.io/docs/introduction/overview/) Sammlung von Metrics
- [Grafana](https://grafana.com/docs/) Visualisierung von Metrics

### MinimalBlog

- [MinimalBlog Einführung](/blog/minimalblog-introduction) Warum MinimalBlog existiert
- [Quellencode](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.MinimalBlog) Gesamte Quelle
- [NuGet-Paket](https://www.nuget.org/packages/Mostlylucid.MinimalBlog) Installation über NuGet
- [Demonstrationsprojekt](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.MinimalBlog.Demo) Beispiel für die Arbeit

### Leistung des ASP-.-NET

- [Kernleistung des ASP-.-NET](https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices) Bewährte Verfahren
- [Caching im ASP-.-NET-Kern](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/response) Ansprech-Caching
- [Speicherverwaltung](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/) ]-- ®.-NET GC
- [Diagnosewerkzeuge](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/) --Diagnostik

Viel Spaß beim Testen