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
Load Testing ASP.NET Core Applications with k6: Practical Implementation
Tuesday, 02 December 2025
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: 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.
É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:
cd Mostlylucid.MinimalBlog.Demo
dotnet run
Ou en utilisant Docker:
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:
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:
- 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
- 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
- Aucune mise en relief: Les méthodes ne sont pas 't inlined, adding call overhead that wonM SK3t exist in production
- Exécution des affirmations:
Debug.Assert()les énoncés exécutent, en ajoutant des vérifications que la production won' n’a pas - 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é:
# 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:
# 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
3. Créer un répertoire des scripts d'essai
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
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:
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
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:
k6 run cache-test.js
Ce test vous montrera:
- Premier temps de demande (lorsque le cache est froid)
- Temps de demande subséquents (cache warmM SK1
- Vérification des titres de cache
Essai 3: Essai de chargement
Maintenant, let's test under realistic load:
Dossier: 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
}
Faire l’essai:
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
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:
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
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:
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
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:
k6 run soak-test.js
Importance: Surveiller les ressources du système au cours des essais de souche
# 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
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:
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
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:
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:
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
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
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:
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
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:
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
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
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
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
Utiliser des seuils appropriés
// 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'] }Dépendences de cache
- name: Cache .NET packages uses: actions/cache@v4 with: path: ~/.nuget/packages key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}Recueillir les registres des tests qui ont échoué
- name: Upload App Logs if: failure() uses: actions/upload-artifact@v4 with: name: app-logs path: Mostlylucid.MinimalBlog.Demo/app.logUtiliser l'environnement-Crédits seuils
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:
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
k6 run --out experimental-prometheus-rw load-test.js
3. Importer kM SK1 tableau de bord à Grafana
- Accès à Grafana :
http://localhost:3000 - Ajouter la source de données Prometheus
- 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 moyenmin/max:La réponse la plus rapideM SK1la réponse la moins lentemed: Median (50th percentileM SK2p(90): 90th percentilep(95): 95deuxième percentile (moyenne métrique de l'ALSM SK3p(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:
# 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
// 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:
# 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:
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
// 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:
# 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:
- Vérifier les registres de l'application pour des erreurs
- Vérifier que les données d'essai existent (files de marquage)
- Réduire le nombre de VU (peut être surchargéM SK1
- 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:
- Construire en mode de sortie:
dotnet run -c Release - Vérifier que le caching fonctionne
- Vérifier le disque I/O M SK1les lectures de fichier lentes)
- Profil avec dotnet-trace:
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:
- Surveillance avec:
dotnet-counters monitor --counters System.Runtime[gc-heap-size,gc-committed,gc-allocated]
- Collecte d’une décharge de mémoire:
dotnet-dump collect -p $(pgrep -f MinimalBlog)
dotnet-dump analyze memory.dump
- 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:
#!/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:
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):
# 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
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
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é:
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:
# This generates load while profiler captures data
k6 run --duration 60s --vus 20 k6-tests/load-test.js
3. Analyser le profil
# 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)
# 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:
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
# 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
# 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:
# 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)
# 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
#!/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:
chmod +x profile-under-load.sh
./profile-under-load.sh
Pratiques exemplaires: k6 M SK2 Profilage
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
Prendre des photos multiples
- Avant la charge (baselineM SK1
- Au cours de la charge (activeM SK1
- Après charge (vérification de nettoyage)
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
Mesure avant et après
- Suivre toujours les essais de k6 après l'optimisation
- Vérifier l'amélioration avec le profileur
Surveiller les régressions
- Garder les profils de base dans le répertoire
- Comparer les profils dans CI/CD
Ébauches de diffusion des profils
- Les systèmes de débugging ont des caractéristiques de performance différentes
- Utiliser toujours
-c Releasepour 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
- Commencer par les essais de fumée - Vérifier la fonctionnalité de base avant les essais plus approfondis
- Tester explicitement le caching - MinimalBlogThe performance of ' depends on it
- Utiliser des données réalistes - Les fichiers vides ne représentent pas l’utilisation réelle
- Surveillance des ressources du système - CPUM SK1 mémoire , GC au cours des essais
- Établir des seuils significatifs - Sur la base des besoins réels
- Automatiser dans CI/CD - Régressions précoces de capture
- É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
- ReliableTaux 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:
- Créer un profil de performance de base pour votre configuration
- Mettre en place des essais automatisés dans votre pipeline CI/CD
- Faire des essais d'évacuation avant les principales mises à jour
- Surveiller les mesures de production et comparer celles des résultats d'essai
- 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 - Documentation complète k6
- k6 API JavaScript - Tous les APIs disponibles k6
- k6 exemples - Exemple de scripts d'essai
- k6 extensions - Extend k6 functionality
- k6 métriques - Comprendre les mesures
- k6 seuils - Pass de configurationM SK1 Critères d'échec
- k6 scénarios - Systèmes d'essai avancés
Actions de GitHub
- GitHub Actions Docs - Documentation complète des actions
- Synthèse du flux de travail - Référence syntaxique YAML
- Utilisation d'artefacts - Résultats des essais de stockage
- Établissement des actions - kM SK1 action de mise en place
Outils de profilage
- dotnet-trace - Profilage du rendement
- Coordonnateurs - Échéance réelleM SK1 Mesures de temps
- dotnet-dump - Débuis de mémoire
- JetBrains dotTrace - Profileur commercial
- JetBrains dotMemory - Profileur de mémoire
- Spécialisation - Visualisateur de graphique Flame
Surveillance & Visualisation
- k6 Nuage - CloudM SK1based k6 testing
- k6 Rapporteur - Rapports HTML
- Grafana k6 tableau de bord - kM SK1 tableau de bord des mesures
- Prometheus - Collection de métriques
- Grafana - Visualisation métrique
MinimalBlog
- MinimalBlog Introduction - Pourquoi MinimalBlog existe-t-il
- Code de source - Source complète
- NuGet - Installer via NuGet
- Projet de démonstration - Exemple de travail
ASP.Performance NET
- Performance de base ASP.NET - Pratiques exemplaires
- Cache dans le noyau ASP.NET - Cache de réponse
- Gestion de la mémoire - .NET GC
- Outils de diagnostic - .Diagnostic du réseau
Tests heureux!