# Load Testing ASP.NET Core Applications with k6: Practical Implementation

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

Maintenant que vous comprenez les fondements de k6 et d’essais de performance ,, il est temps de mettre ces connaissances en pratique M SK3 Cet article vous guide à l’élaboration de tests réels , leur intégration dans des pipelines de CI/CDM SK6 et l’utilisation d’outils de profilage pour identifier et corriger les lacunes du rendement MSC7

Il s'agit de **Partie 2** d'une série de deux pièces - sur l'essai de charge avec kM SK1

- **[Partie 1: Introduction](/blog/k6-testing-introduction)**: Introduction à l’installation de kM SK1 Types d’essai de , , et pourquoi k6
- **Partie 2**: Essais de rédactionM SK1 CI/ Intégration à la CD , profilageMNK4 et exemples réelsMRK5world

Si vous n’avez pas lu la partie ', commencez par y comprendre les bases de k6 l’installationM SK3 , et les types d’essai avant de s’engager dans la mise en œuvre.

[TOC]

## Établissement de votre environnement d'essai

Avant de lancer k6 testsM SK1 laissez's mettre en place MinimalBlog pour les essais :

### 1. Faire fonctionner MinimalBlog localement

**Utiliser le projet de démonstration:**

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

**Ou en utilisant Docker:**

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

### Pourquoi le mode de mise à jour est important pour l'essai du rendement

Pour des essais de performance exacts, **bâtir toujours en mode de mise à jour**:

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

**Différences entre débugging et mode de diffusion:**

| Aspect | Modèle de débugging
|--------|------------|--------------|
| **Optimisations** | désactivé | Optimisations JIT complètes activées |
| **Insérer** | Minimale | Méthode agressive d'affichage de ≥|
| **Code mort** | Préservé | Éliminé |
| **Symboles de débug** | Données métadonnées supplémentaires à la base de données PDB entière, | Minimale ou aucune ≥|
| **Affirmations** | `Debug.Assert()` Activité | Compilé à l'extérieur |
| **Contrôle des limites** | Contrôles de sécurité supplémentaires | Optimisé à l’extérieur où il est sécuritaire
| **Coûts généraux** | 2-10x plus lentes MSC2 Performance de base MSC3

**Pourquoi le mode de débug donne des résultats trompeurs:**

1. **Aucune optimisation de JIT**: Le compiler préserve la structure du code pour le débugging plutôt que pour l'optimisation de la vitesse
2. **Instrumentation supplémentaire**: Les constructions de débugging comprennent le code pour les points de rupture , l'inspection des variables M SK2 et les traces du cheminement
3. **Aucune mise en relief**: Les méthodes ne sont pas 't inlined, adding call overhead that wonM SK3t exist in production
4. **Exécution des affirmations**: `Debug.Assert()` les énoncés exécutent, en ajoutant des vérifications que la production won' n’a pas
5. **Modèles de mémoire différents**: Les allocations de débugging comprennent le padding et le suivi qui affectent le comportement du GC

**Quand utiliser le mode de débug pour tester:**

- **Défaillances dans la résolution des problèmes**: Lorsque les essais échouent et que vous avez besoin de traces détaillées du cheminement
- **Enquête sur des demandes spécifiques**: Attacher un débugger pour tracer une seule demande problématique
- **Vérifier l'enregistrement**: Assurez-vous que votre enregistrement saisit l'information appropriée sous charge
- **Développement initial**: Lorsque vous rédigez de nouveaux scripts d'essai et que vous vérifiez qu'ils atteignent les points finals appropriés

**Flux de travail recommandé:**

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

> **Règle de la poignée**: Si vous ' mesurez le rendement , utilisez la version de mise à jour

### 2. Préparer les données d'essai

Créer plusieurs postes de démarrage pour tester avec:

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

### 3. Créer un répertoire des scripts d'essai

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

Nous sommes maintenant prêts à rédiger nos tests.

## Essai 1: Essai de fumée

Let's commencer par un simple test de fumée pour vérifier la fonctionnalité de base:

**Dossier: `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'),
  });
}
```

**Faire l’essai:**

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

**Résultats escomptés:**

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

## Essai de validation du cache 2:

MinimalBlog's performance dépend fortement du caching. LetM SK2s verify it works :

**Dossier: `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']}`);
  });
}
```

**Faire l’essai:**

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

Ce test vous montrera:

1. Premier temps de demande (lorsque le cache est froid)
2. Temps de demande subséquents (cache warmM SK1
3. Vérification des titres de cache

## Essai 3: Essai de chargement

Maintenant, let's test under realistic load:

**Dossier: `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
}
```

**Faire l’essai:**

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

**Interprétation des résultats:**

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

Mesures clés à suivre:

- **http_reqM SK1duration p(95)**: 95premier temps de réponse pour le percentile.
- **http_req_faille**: Taux d'erreur
- **http_reqs**: Débit total

## Test 4: Essai de stress

Let's trouver MinimalBlog's point de ruptureM SK2

**Dossier: `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 }),
  };
}
```

**Faire l’essai:**

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

Vérification de:

- Quand le temps de réponse commence à dégrader?
- À quel compte VU apparaissent les erreurs?
- Le système se rétablit-il après une diminution de la charge?

## Essai 5: Essai d'étalonnage

Testez les pics de trafic soudainement (comme ceux qui sont affichés sur Reddit ou Hacker News):

**Dossier: `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,
  });
}
```

**Faire l’essai:**

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

Cet essai vérifie:

- Le système gère-t-il une charge soudaine?
- Existe-t-il des mécanismes appropriés de limitation du taux?
- Reprend-il gracieusement?

## Essai 6: Soak Test

Long-essai en cours pour détecter les fuites de mémoire ou l'épuisement des ressources:

**Dossier: `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);
}
```

**Faire l’essai:**

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

**Importance**: Surveiller les ressources du système au cours des essais de souche

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

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

Vérification de:

- Augmentation graduelle de la mémoire (déversement de mémoire)
- Utilisation du CPU s'accroît
- Dégradation du temps de réponse au fil du temps

## Essai 7: Voyage réaliste des utilisateurs

Simulation du comportement réel de l'utilisateur:

**Dossier: `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),
  };
}
```

**Faire l’essai:**

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

Cela produit un rapport HTML: `user-journey-report.html`

## Advanced: Testing MetaWeblog API

Si vous avez activé MetaWeblog, testez aussi:

**Dossier: `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'),
  });
}
```

**Faire l’essai:**

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

## Essais en cours dans CI/CD

### Utilisation de k6 comme passerelle de qualité

L'utilisation la plus puissante de k6 dans CI/CD est une **entrée de qualité** - faillite automatique des constructions si la performance décroît . Voici' comment mettre en œuvre un test exhaustif kM SK3 dans les actions de GitHubMSC4

### GitHub Actions: Verification de base

Let's commencer par un flux de travail de base qui fonctionne à chaque demande de retrait et bloque la fusion si les essais échouent

Créer `.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
```

**Faits clés:**

- Sur toutes les relations publiques
- Créer les données d'essai automatiquement
- Attende que l'application soit prête avant d'être testée
- Exécute plusieurs types d'essai
- **Défaut de construire si les seuils ne sont pas atteints**
- Déposer les résultats en tant qu'artefacts
- Indique le résumé dans l'interface de GitHub

```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 Actions: Amélioré avec les commentaires de la PR

Créer `.github/workflows/k6-pr-comment.yml` Poster les résultats comme commentaires publics:

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

**Ce flux de travail:**

- Postes résultant directement à la PR
- Indique les mesures clés dans une table
- Fournit une sortie complète dans la section élargible
- Liens à l'essai complet
- Indicateurs de situation visuelle

### Actions de GitHub: Détection de la régression du rendement

Créer `.github/workflows/k6-baseline.yml` pour détecter les régressions de rendement:

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

**Ce flux de travail:**

- Évaluer les relations avec la clientèle et le secteur principal
- Comparaison des mesures de rendement
- **Défaut si la performance dégrade >20%**
- Prévention de la fusion du code lent

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

### Pratiques exemplaires pour l'essai de CI/CD k6

1. **Démarrer rapidement, Porte tôt**
   
   - Faire des essais de fumée d'abord (30sM SK1
   - Faire des essais complets seulement si la fumée passe
   - Défaut de sauvegarder rapidement les minutes CI

2. **Utiliser des seuils appropriés**
   
   ```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. **Dépendences de cache**
   
   ```yaml
   - name: Cache .NET packages
     uses: actions/cache@v4
     with:
       path: ~/.nuget/packages
       key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
   ```

4. **Recueillir les registres des tests qui ont échoué**
   
   ```yaml
   - name: Upload App Logs
     if: failure()
     uses: actions/upload-artifact@v4
     with:
       name: app-logs
       path: Mostlylucid.MinimalBlog.Demo/app.log
   ```

5. **Utiliser l'environnement-Crédits seuils**
   
   ```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
     },
   };
   ```

## Surveillance des essais k6 avec Grafana

Pour une surveillance plus poussée, vous pouvez transférer les métriques kM SK1 à Grafana:

### 1. Établissement Prometheus et Grafana

**docker-composition.yml** pour la surveillance:

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

### 2. Exécuter k6 avec la sortie Prometheus

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

### 3. Importer kM SK1 tableau de bord à Grafana

1. Accès à Grafana : `http://localhost:3000`
2. Ajouter la source de données Prometheus
3. Importer k6 tableau de bord M SK1ID: 18030)

## Interprétation de la métrique k6

Mesures clés à comprendre:

### Mesures de temps d'intervention

- **http_req_duration**: Durée totale de la demande
  - `avg`: Temps de réponse moyen
  - `min/max`:La réponse la plus rapideM SK1la réponse la moins lente
  - `med`: Median (50th percentileM SK2
  - `p(90)`:  90th percentile
  - `p(95)`: 95deuxième percentile (moyenne métrique de l'ALSM SK3
  - `p(99)`:  99septième percentile

**Bonnes valeurs pour MinimalBlog:**

- p(95) M SK1 ≥300ms pour le contenu caché
- p(95) M SK1 ≥500ms pour les premières charges de temps -

### Metriques de débit

- **http_reqs**: Total des demandes
- **http_reqM SK1rate**: Demandes par seconde
- **iteration_duration**: Temps pour une itération complète

### Mesures d'erreur

- **http_req_faille**: Demandes défectueuses (4xx,
- **Les vérifications**: Passe d'affirmation / Taux de défaillance

**Taux d’erreur cible: M SK1 | 1%**

### Transfert de données

- **données_reçues**: Données téléchargées
- **données_sent**: Données mises à jour

## Les meilleures pratiques pour tester MinimalBlog

### 1. Essai en isolation

Testez MinimalBlog séparément des autres services:

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

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

### 2. Utiliser des données réalistes

Don' ne testez pas avec des fichiers de marquage videsM SK1 Utilisez un contenu réaliste:

- Différentes longueurs de poste ( courte , moyenneM SK2 longue)
- Différentes fonctions de marquage (blocs de code,imagesM SK2listes )
- Plusieurs catégories
- Réalisation de la distribution mondiale des postes

### 3. Modifier explicitement le comportement du cache d'essai

MinimalBlog's performance relies à la cache

```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,
  });
}
```

### 4. Ressources du système de contrôle

Suivre ces essais à côté de k6 tests:

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

### 5. Fixer des seuils réalistes

Ne pas fixer de seuils trop stricts ou trop souples:

```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'],
  },
};
```

### 6. Évaluer différents scénarios

```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`);
```

### 7. Version Vos tests

Garder les essais sous contrôle de version à côté du 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
```

## Troubleshooting Common Issues

### Numéro 1: Connection rejetée

**Problème:**

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

**Solution:**

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

### Numéro 2: Taux d'erreur élevé

**Problème:**

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

**Solution:**

1. Vérifier les registres de l'application pour des erreurs
2. Vérifier que les données d'essai existent (files de marquage)
3. Réduire le nombre de VU (peut être surchargéM SK1
4. Accroître le temps de rampe-up

### Numéro 3: Temps de réponse lente

**Problème:**

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

**Solution:**

1. Construire en mode de sortie: `dotnet run -c Release`
2. Vérifier que le caching fonctionne
3. Vérifier le disque I/O M SK1les lectures de fichier lentes)
4. Profil avec dotnet-trace:

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

### Numéro 4: fuites de mémoire dans les essais d'écoulement

**Problème:** L'utilisation de la mémoire augmente constamment

**Solution:**

1. Surveillance avec:

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

2. Collecte d’une décharge de mémoire:

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

3. Vérifier pour:
   - Cache n'expire pas
   - Les handles du fichier ne s'arrêtent pas
   - Les gestionnaires d'événements ne s'en souscrivent pas

## Examen réel-Exemple mondiale: Essais avant diffusion

Voici'un flux de travail d’essai complet avant la sortie du 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!"
```

**Run it:**

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

## Profilage avec dotTrace et dotMemory

k6 vous dit **Ce qui suit :** est lente, mais les profileurs vous disent **pourquoi**. Combiner kM SK1 essais de charge avec les profileurs JetBrains (dotTrace for performance, dotMemory for memoryMSC4 vous permet d’identifier des goulets d’étranglement exacts dans le code MinimalBlog

### Pourquoi le profil doit-il être chargé?

Le profilage sans charge manque souvent les problèmes réels-world issues:

- **Démarrage froid vs cache chaud**: Voies de code différentes
- **Accès concurrent**: Lock contentionM SK1 conditions de la course
- **Pression de mémoire**: Le comportement du GC sous charge
- **Épuisement des ressources**: Bases de connexionManages de fichier ,

k6 crée une charge réaliste alors que les profileurs captent ce qui se passe à l’intérieur de votre app.

### Établissement: Installation des outils JetBrains

**Option 1: JetBrains dotTraceM SK1dotMemory (Commercial)**

Télécharger de [JetBrains (en anglais seulement)](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: dotnetM SK1trace (FreeMSC3 built-inMST5**

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

Pour ce guide, nous montrons les deux approches :

### Profilage des flux de travail

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

**Étape 1: Ligne de base sans charge**
**Étape 2: Profil sous la charge k6**
**Étape 3: Comparer et identifier les lacunes**
**Étape 4: Fixer et réexaminer**

### Profilage du rendement avec dotTrace

#### Scénario: Trouver une analyse du ralentissement de la marquage

**1. Démarrer MinimalBlog avec le profilage attaché:**

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

**2. Dans un autre terminalM SK1 exécuter k 6 essai de chargement:**

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

**3. Analyser le profil**

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

**Ce qu’il faut rechercher dans le rapport:**

- **Routes chaudes**: Méthodes consommant la plus grande partie du temps CPU
- **L'arbre d'appel**: Où se passe le temps dans la hiérarchie des appels
- **Auto-temps**: Temps dans la méthode à l'exclusion des appels aux autres méthodes

**Exemples de constatations dans 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
```

Cela montre:

- `Markdown.Parse()` est le créneau d’étanchéité #1
- La correspondance avec le Regex est #2 (80ms totauxM SK2
- La plupart des premières 10 appels, ( manquant de cache, puis en cachée

#### Utilisation du réseau de pointes-trace M SK1Alternative gratuite)

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

**Visualisation du spectre de vitesse:**

- Le graphique Flame montre les piles d'appel
- La vue ordonnée du temps montre l'exécution au fil du temps
- La vue lourde de gauche met en évidence les opérations lentes

### Profilage de la mémoire avec dotMemory

#### Scénario: Détection des fuites de mémoire dans la cache

**1. Démarrer MinimalBlog et attacher le profileur de mémoire:**

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

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

**2. Faire l'essai de chargement kM SK1**

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

**3. Faire des prises de vue pendant et après l'essai**

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

**4. Analyser la croissance de la mémoire:**

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

**Que chercher:**

- **Collections en croissance**: Listes, Dictionaries qui ne diminuent jamais
- **Gestionnaire des événements**: Références relatives aux événements non abonnés
- **bloat de cache**: Le cache augmente au-delà de la taille prévue
- **Répartition des chaînes**: Concatenation de chaînes excessive

**Exemple de constatations:**

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

Cela montre:

- Les chaînes ont augmenté de 10 MB ( fuite potentielle des chaînes ou issue du cache)
- Cache de BlogPost est stable (good)
- MarkdownDocument nettoyé par GC (goodM SK1

#### Utilisation du réseau de pointe-dump M SK1Alternative gratuite)

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

### Script de profilage combiné k6 M SK1

Voici' est un script complet qui utilise k6 pendant le profilageM SK2

**Dossier: `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 ""
```

**Run it:**

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

### Pratiques exemplaires: k6 M SK2 Profilage

1. **Présenter toujours le profil sous une charge réaliste**
   
   - Utiliser k6 pour simuler les patrons de trafic de production
   - Les applications inactives du profil ne vous disent rien sur la performance réelle

2. **Prendre des photos multiples**
   
   - Avant la charge (baselineM SK1
   - Au cours de la charge (activeM SK1
   - Après charge (vérification de nettoyage)

3. **Mettre l'accent sur les pistes chaudes d'abord**
   
   - Fixer les plus grands obstacles d'abord
   - 80/20 RègleM SK1 20% du code prend 80% de temps

4. **Mesure avant et après**
   
   - Suivre toujours les essais de k6 après l'optimisation
   - Vérifier l'amélioration avec le profileur

5. **Surveiller les régressions**
   
   - Garder les profils de base dans le répertoire
   - Comparer les profils dans CI/CD

6. **Ébauches de diffusion des profils**
   
   - Les systèmes de débugging ont des caractéristiques de performance différentes
   - Utiliser toujours `-c Release` pour le profilage

### Résumé des outils

| Outil MSC1 But MSC2 Coût SMC3 Le meilleur pour SMC4
|------|---------|------|----------|
| **k6** | Essais de chargement | gratuits MSC2 Trouver ce qui est lente MSC3
| **La tracée des points** | Profilage du rendement | Paiements MSC2 Détermination de la raison pour laquelle il est lente SMC3
| **Données de mémoire** | Profilage de la mémoire | Paiements | fuites de mémoire, Questions de GC MSC4
| **dotnet-trace** | Profilage de la performance | Libre | Profilation de base du CPU ≥|
| **Coordonnateurs** | Réel- Mesures de temps | Sans frais MSC3 Surveillance en direct MSC4
| **dotnet-dump** | Détournements de mémoire | gratuits
| **PerfView** | Préparation de profils avancés | Services gratuits

## Conclusion: Building Confidence Through Testing

Les essais de chargement avec k6 vous donnent la confiance que MinimalBlog peut gérer le trafic réel.

- Installer k6 sur n'importe quelle plate-forme (Windows, MacM SK3 LinuxMSC4
- Rédiger et exécuter différents types d'essais de performance
- Vérifier que le comportement de cache fonctionne comme prévu
- Trouver des lacunes en matière de performance avant qu'elles ne se produisent
- Intégrer les essais de charge dans les pipelines CI/CD
- Surveiller et interpréter les métriques k6

### Key Takeaways

1. **Commencer par les essais de fumée** - Vérifier la fonctionnalité de base avant les essais plus approfondis
2. **Tester explicitement le caching** - MinimalBlogThe performance of ' depends on it
3. **Utiliser des données réalistes** - Les fichiers vides ne représentent pas l’utilisation réelle
4. **Surveillance des ressources du système** - CPUM SK1 mémoire , GC au cours des essais
5. **Établir des seuils significatifs** - Sur la base des besoins réels
6. **Automatiser dans CI/CD** - Régressions précoces de capture
7. **Étudier différents scénarios** - Page d'accueil

### Ce que nous avons appris sur MinimalBlog

Par le biais de k6 essais, nous pouvons vérifier les revendications du MinimalBlogM SK2

- **Soutien rapide**: pM SK1 < ≥300ms avec cache chaud
- **Efficacité**: Utilisation faible de la mémoire , pression minimale du GC
- **Étalonnabilité**: Appareils 100+ utilisateurs concurrents sur un matériel modeste
- **Reliable**Taux d'erreur : < ≥0.1% sous charge normale
- **Cache- efficace**: 10x réponses plus rapides avec cache chaud

### Prochaines étapes

Maintenant que vous savez comment tester un ASP.NET avec k6:

1. Créer un profil de performance de base pour votre configuration
2. Mettre en place des essais automatisés dans votre pipeline CI/CD
3. Faire des essais d'évacuation avant les principales mises à jour
4. Surveiller les mesures de production et comparer celles des résultats d'essai
5. Renouveler le test :, optimiser de nouveau le test

Remarque: **Le test de performance n’est pas une activité ponctuelle.**. Au fur et à mesure que vous ajoutez du contenuM SK1 modifiez le code , ou changez les fournisseurs d’hébergement, reMSC4suivez ces tests pour faire en sorte que votre application demeure rapide et fiable

## Ressources

### k6 Documentation

- [Documents officiels k6](https://k6.io/docs/) - Documentation complète k6
- [k6 API JavaScript](https://k6.io/docs/javascript-api/) - Tous les APIs disponibles k6
- [k6 exemples](https://github.com/grafana/k6-learn) - Exemple de scripts d'essai
- [k6 extensions](https://k6.io/docs/extensions/) - Extend k6 functionality
- [k6 métriques](https://k6.io/docs/using-k6/metrics/) - Comprendre les mesures
- [k6 seuils](https://k6.io/docs/using-k6/thresholds/) - Pass de configurationM SK1 Critères d'échec
- [k6 scénarios](https://k6.io/docs/using-k6/scenarios/) - Systèmes d'essai avancés

### Actions de GitHub

- [GitHub Actions Docs](https://docs.github.com/en/actions) - Documentation complète des actions
- [Synthèse du flux de travail](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions) - Référence syntaxique YAML
- [Utilisation d'artefacts](https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts) - Résultats des essais de stockage
- [Établissement des actions](https://github.com/grafana/setup-k6-action) - kM SK1 action de mise en place

### Outils de profilage

- [dotnet-trace](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) - Profilage du rendement
- [Coordonnateurs](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) - Échéance réelleM SK1 Mesures de temps
- [dotnet-dump](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump) - Débuis de mémoire
- [JetBrains dotTrace](https://www.jetbrains.com/profiler/) - Profileur commercial
- [JetBrains dotMemory](https://www.jetbrains.com/dotmemory/) - Profileur de mémoire
- [Spécialisation](https://www.speedscope.app/) - Visualisateur de graphique Flame

### Surveillance & Visualisation

- [k6 Nuage](https://k6.io/cloud/) - CloudM SK1based k6 testing
- [k6 Rapporteur](https://github.com/benc-uk/k6-reporter) - Rapports HTML
- [Grafana k6 tableau de bord](https://grafana.com/grafana/dashboards/18030) - kM SK1 tableau de bord des mesures
- [Prometheus](https://prometheus.io/docs/introduction/overview/) - Collection de métriques
- [Grafana](https://grafana.com/docs/) - Visualisation métrique

### MinimalBlog

- [MinimalBlog Introduction](/blog/minimalblog-introduction) - Pourquoi MinimalBlog existe-t-il
- [Code de source](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.MinimalBlog) - Source complète
- [NuGet](https://www.nuget.org/packages/Mostlylucid.MinimalBlog) - Installer via NuGet
- [Projet de démonstration](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.MinimalBlog.Demo) - Exemple de travail

### ASP.Performance NET

- [Performance de base ASP.NET](https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices) - Pratiques exemplaires
- [Cache dans le noyau ASP.NET](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/response) - Cache de réponse
- [Gestion de la mémoire](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/) -  .NET GC
- [Outils de diagnostic](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/) - .Diagnostic du réseau

Tests heureux!