Back to "Lastprüfung von ASP-.-NET Core Applications mit praktischer Implementierung"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET k6 Performance Testing

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

Tuesday, 02 December 2025

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**Einführung in die Installation von k-6,, ,, Testtypen und warum k-6,
  • Teil dieses ArtikelsSchreibteste 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

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

cd Mostlylucid.MinimalBlog.Demo
dotnet run

Oder die Verwendung von Docker-:

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:

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-OptimierungenDer Compiler bewahrt die Codestruktur für Debugging und nicht für Geschwindigkeitsoptimierung.
  2. Zusätzliche InstrumenteDebug-Builds enthalten Code für Breakpoints, variable Inspektion und Stackspuren.
  3. Keine EinschnitteMethoden 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 SpeichermusterZu den Debug-Zuweisungen gehören Padding und Tracking, die das GC-Verhalten beeinflussen.

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

  • FehlerbehebungWenn Tests fehlschlagen und Sie detaillierte Stapelspuren benötigen
  • Prüfung spezifischer AnträgeBefestigen Sie einen Debugger, um eine einzelne problematische Anfrage zu verfolgen
  • Protokollierung überprüfenVergewissern Sie sich, dass Ihre Protokollierung die richtigen Informationen unter Last erfasst
  • Erste EntwicklungBeim Schreiben neuer Testskripte und beim Verifizieren treffen sie auf die richtigen Endpunkte

Empfohlener Workflow

# 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

FaustregelWenn 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

# 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




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

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

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.

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

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.

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

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.

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 EinzelnachweiseAnsprechzeit 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

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.

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

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.

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

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.

k6 run soak-test.js

WichtigÜberwachen Sie die Systemressourcen während der Durchsickertests

# 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

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.

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

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.

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:

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
          
          

          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
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

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
          
          
          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

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 "" >> TestMarkdown/test-$i.md
            echo '' >> 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
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

    // 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

    - 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

    - 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

    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

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

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
    • avgDurchschnittliche Ansprechzeit
    • min/maxSchnellste Reaktion der /
    • medMittelmäß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-_-DauerZeit für eine vollständige Iteration

Fehler-Metriken

  • **(englisch) Einzelnachweise **Ausgefallene Anfragen
  • KontrollenNicht erfolgsversprechendes Ergebnis

Fehlerrate des Ziels:

Datenübertragung

  • Daten, die empfangen wurdenDownload-Daten
  • DatenauswertungHochgeladene Daten

Best Practices for Testing MinimalBlog

Test in der Isolation

MinimalBlog separat von anderen Diensten testen

# 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-..

// 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.

# 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

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

// 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

# 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-:-
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 ,:'
dotnet-counters monitor --counters System.Runtime[gc-heap-size,gc-committed,gc-allocated]
  1. Sammle einen Speicher Dump.:.
dotnet-dump collect -p $(pgrep -f MinimalBlog)
dotnet-dump analyze memory.dump
  1. 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.:

#!/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.

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: warumMit 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 CacheVerschiedene Code-Pfads
  • Gleichzeitiger ZugriffZögern Sie nicht, das Rennen zu beenden.
  • GedächtnisdruckGC-Verhalten unter Last
  • Erschöpfung der RessourcenDatei-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:

# 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

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

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

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.

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

Analysieren Sie das Profil

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

Was im Bericht zu suchen ist

  • Heiße PfadeMethoden, die die meiste CPU-Zeit verbrauchen
  • AufrufbaumWo die Zeit in der Call-Hierarchie verbracht wird
  • SelbständigkeitZeit 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

# 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

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

# 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.

# 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

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

Was zu suchen ist

  • Wachsende SammlungenWörterbücher, die nie schrumpfen
  • EreignisbetreuerNicht abonnierte Ereignisse mit Referenzen
  • Cache-AufblähungDer 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

# 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

#!/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.

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
  • EffizientNiedriger Speicherverbrauch – minimaler GC-Druck
  • SkalierbarBehandelt gleichzeitige Benutzer auf bescheidener Hardware
  • ZuverlässigFehlerrate unter normaler Last
  • Cache®--effektivSchnellere 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ätWenn 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

GitHub-Maßnahmen

Profilierwerkzeuge

Überwachung der Visualisierung

MinimalBlog

Leistung des ASP-.-NET

Viel Spaß beim Testen

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.