测试 ASP.NET 核心应用的装入测试 (中文 (Chinese Simplified))

测试 ASP.NET 核心应用的装入测试

Tuesday, 02 December 2025

//

30 minute read

现在,您可以理解 k6 和性能测试的基本原理... ... ,... ... it'... ...时间将知识付诸实践...

这是 部分#2 以 k6: 来进行负载测试时的 2 -\ part 序列

  • 第一部分:1:_ 导言@ :_ 介绍 k6,_ 安裝@ MS K2} 測試型態 @ MPK3} 以及為什麼 k6}
  • 本篇文章=):写测试@,CI/CD整合 @,剖析_,和真实的#-世界示例

如果您在跳入执行前已经读到 1, 部分, 请先从那里开始理解 k6 基本内容 @ MS K3 安装@ , @ 和测试类型\ .}

设置您的测试环境

在运行 k{6} 测试前@ ,} < let\ MS K2> > 设置用于测试的 MiminimalBlog_ :}

1. 运行最小 Blog 本地

使用演示工程@:

cd Mostlylucid.MinimalBlog.Demo
dotnet run

或使用 Docker:

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

为什么释放模式 用于绩效测试的事项

准确性能测试 总是以释放模式构建:

dotnet run --configuration Release

调试与释放模式差异@: @%

QAspect @ ZD调试模式@ }释放模式 @MS K3
优化 MPK0残废 全面JIT优化启用 @
内衬 -=YTET -伊甸园字幕组=- 翻译:
死代码 -=YTET -伊甸园字幕组=- 翻译:
除调符号 完整 PDB,额外的元数据 @ 最小或没有@
发售 Debug.Assert() 正在活动的 @ @ 编译@ MS K1}
检查伤口 +额外的安全检查+ #优化到安全的地方
典型间接费用 @ @ @ 2-10#x减速@ _BARBAR_基准性能 @MS K3}

为何调试模式给出误导结果@: @

  1. 无 JIT 优化: 编译器保存调试代码结构, 而不是优化速度
  2. 额外仪器仪表:Debugbuilding 包含断点代码@,变量检查,和堆叠痕迹
  3. 无内衬“: 方法”是“'” 内嵌“,”,加上在生产中不存在的调用费“Won@'”
  4. 发售运行: Debug.Assert() 执行 profile Won_'} t
  5. 不同的内存模式: 调试分配包括会影响GC行为的铺垫和跟踪

测试时何时使用调试模式@ : @

  • 排除问题失败: 测试失败时, 您需要详细的堆叠痕迹
  • 调查具体请求: 附加调试器追踪单一有问题的请求
  • 正在核查日志: 确保您的日志记录捕捉正确信息在装入中
  • 初始发展: 在写入新的测试脚本并校验它们击中右端点时

推荐的工作流程@: @%

# 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

拇指规则:如果您在测量性能@',请使用释放 @, 如果您在修补 bugs,使用调试.

准备测试数据

创建数个标记降下站点, 用 @ : 来测试@ title

# 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. 创建测试脚本目录

mkdir k6-tests
cd k6-tests

现在我们准备写我们的测试了 !

测试1:烟雾测试

让我们从简单的烟雾测试开始 来验证基本功能=:}

檔案@: 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'),
  });
}

运行测试@: @%

k6 run smoke-test.js

预期产出:*

     ✓ 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

测试 @2:_快取校验试验

最小型的Blog@' @%s 性能主要取决于 caching}.}( Let'})

檔案@: 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']}`);
  });
}

运行测试@: @%

k6 run cache-test.js

此测试将显示您@ : @ @ action

  1. 第一次请求时@ ( @ 缓存是冷的 @ MS K1 @ *
  2. 随后的求援时间@(cache warm @)
  3. 缓存信头校验

测试 *#3: 加载测试

现在让我们在现实的负载下测试 :

檔案@: 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
}

运行测试@: @%

k6 run load-test.js

解释结果 @:

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

监视“:”的关键度量

  • https:__req__dernation p{(95)}<http:www.(应该处于阈值以下
  • 校对:Soup@:_ 错误率 @ (} 应该接近@0%){
  • @_reqs*:总吞吐量

测试4:压力测试

Let''s find MinimalBlog's break point\ :

檔案@: 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 }),
  };
}

运行测试@: @%

k6 run stress-test.js

监视@: @ @

  • 回应时间何时开始降低 S?@ @
  • VU 计数时出现错误@? @%
  • 系统在负载下降后是否恢复@? @%

测试5:Spike试验

测试性突发交通峰值 @(}就像在Reddit或Hacker News@):上出现的那样

檔案@: 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,
  });
}

运行测试@: @%

k6 run spike-test.js

此测试校验@ : @

  • 系统是否处理突如其来的负载@? @%
  • 是否有适当的税率限制机制?
  • 它是否优雅地恢复了 ?

测试 *#6:_

用于检测内存泄漏或资源耗竭的LongM- 运行测试@ : @

檔案@: 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);
}

运行测试@: @%

k6 run soak-test.js

重要@:在浸泡测试期间的监测系统资源@:

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

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

监视@: @ @

  • 渐变内存增加 @ ( @ momory 漏出@ MS K1
  • CPU使用量逐渐增加
  • 反应时间随时间变化的降解

测试“7:”和现实用户旅程

模拟实际用户行为@: @%

檔案@: 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),
  };
}

运行测试@: @%

k6 run user-journey-test.js

这将生成一个 HTML 报告@ : @ user-journey-report.html

超前 : 测试MeteWeblog API

如果您已经启用了 MetaWeblog , @ , @ 测试它也% MS K1 @

檔案@: 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'),
  });
}

运行测试@: @%

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

在 CI/}CD 中运行测试

使用 k6作为质量门

在 CI/中, K6Q 最有力的使用 质量门 @ -_ 自动失灵, 如果性能降低@ MS K1} 这里 @ ' 如何在 GitHub Action=. 中实施全面的 k6 测试 ?

GitHub 行动=:}基本核查

Let's 以一个基本工作流程为起点, 该流程运行在每次拉动请求上, 如果测试失败, 块将合并@ . @

创建创建 .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

密钥功能@: @% 1

  • 在每个公关上运行
  • 自动创建测试数据
  • 等待测试前应用程序准备就绪
  • 运行多个测试类型
  • 如果未达到阈值,建设失败
  • 上传结果作为工艺品
  • 在 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 行动=:高级,附有公关评论

创建创建 .github/workflows/k6-pr-comment.yml 将结果作为 PR 注释@: @%

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

此工作流程@ :

  • 员额结果直接由PRP产生
  • 在表格中显示关键度
  • 提供可扩展部分的全部产出
  • 与完整测试运行的链接
  • 视觉状况指标

GitHub 动作@: @ 性能倒退检测

创建创建 .github/workflows/k6-baseline.yml 检测性能回归=:

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

此工作流程@ :

  • PR和主要分支的测试
  • 比较业绩计量
  • 如果性能退化, 失灵 >20%
  • 防止将慢速代码合并
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

CI/}CD kMPK1测试的最佳做法

  1. 提前启动快速关口@, @% 1

    • 首次测试烟雾,
    • 只有在烟雾通过时才进行全面测试
    • 无法快速保存 CI 分钟
  2. 使用适当的阈值

    // 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. 缓存依赖

    - name: Cache .NET packages
      uses: actions/cache@v4
      with:
        path: ~/.nuget/packages
        key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
    
  4. 收集失败测试的日志

    - name: Upload App Logs
      if: failure()
      uses: actions/upload-artifact@v4
      with:
        name: app-logs
        path: Mostlylucid.MinimalBlog.Demo/app.log
    
  5. 使用环境@-}特定阈值

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

用格拉法纳监测 k6 测试

用于高级监控“,”您可以向格拉法纳(Grafana)传送 k6度量计@:

建立普罗米修斯和格拉法纳

多克 - comption * MS K1 _ _ yml 用于监控的:

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

使用 prometheus 输出, 2. 运行 k6

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

在格拉法纳进口 k6 挂板

  1. 格拉法纳 http://localhost:3000
  2. 添加 Prometheus 数据源
  3. 导入 k6 仪表板 @ (ID}: MS K3

解释 k6 度量

理解“:”的关键度量

反应时间

  • https:req auturation请求总时间
    • avg平均反应时间
    • min/max@ : @ fastest @ / @ slow 响应最慢
    • med: median @(50}%iile_)
    • p(90): NSK1% 1 百分位
    • p(95): MS K1 百分位 ( 良好SLA mit)
    • p(99)( 渔获物与众不同

最小数值的好值@ : @

  • 用于缓存内容的 300
  • 第一次--时间装载时的 musicName

投入量

  • @_reqs请求总数
  • http:reqrate*:* 请求每秒
  • 延长期: 一个完整迭代的时间

误差计数

  • 校对:Soup-=YTET -伊甸园字幕组=- 翻译:
  • 支票检查:_Assertion pass *% 1_BARBAR_失败率

*目标@: @ @ @ < @ @ 1%} 错误率

数据传输

  • 数据=_收悉:下载数据
  • 数据@_ @%sign: 已上传数据

测试最微小Blog的最佳做法

1. 孤立测试

与其它服务分别测试 MinimalBlog@: @% 1

# Stop other services
docker-compose down

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

2. 使用现实数据

使用符合实际的内容@: @%

  • 不同的文章长度 @( @short@,}介质 @ , long_)
  • 不同的标记下标功能 @ (_ code blocks MS K1} 图像@ , 列表 @ MPK3
  • 多个类别
  • 真实-世界日志分布

3.測缓存行为

最小型的Blog@ ' @%s 性能取决于 caching @ MS K1} 校验@ MPK2 *

// 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. 监视系统资源

在 K6 测试中运行这些\ :\

# 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. 设定现实的阈值

Donó't 设定的阈值太严格或过于松懈@:

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. 测试不同的情景

// 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. 版本您的测试

将测试保留在代码@ : 的版本控制中

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

解决共同问题

拒绝连接

问题M:

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

解答@: @

# 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

高误差率

问题M:

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

解答@: @

  1. 检查错误的应用程序日志
  2. 校验测试数据已存在 @ ( markdown 文件@ MS K1
  3. 降低 VU 计数@ ( @ 可能是超载@ I) @
  4. 增加斜坡@ - @ up 时间

迟缓反应时报

问题M:

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

解答@: @

  1. 以释放模式构建@: @% dotnet run -c Release
  2. 查证缓存工作正常
  3. 检查磁盘 I/ @O (}#slow 文件读取@)}
  4. 带有dotnet@- @ trace @ :}的配置文件
dotnet-trace collect -p $(pgrep -f MinimalBlog) --format speedscope

烟雾测试中的内存渗漏

问题M: 内存用量不断增加

解答@: @

  1. 使用 @:}监视器
dotnet-counters monitor --counters System.Runtime[gc-heap-size,gc-committed,gc-allocated]
  1. 收集内存倾弃@: @%
dotnet-dump collect -p $(pgrep -f MinimalBlog)
dotnet-dump analyze memory.dump
  1. 检查@: @%
    • 缓存未过期结束
    • 文件处理不关闭
    • 未取消订阅的事件处理器

释放前测试

这里“ '” 在发布最小Blog@ MS K1 之前, 这是完整的测试工作流程 。 @ info

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

运行它:

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

用点跟踪和点记忆分析

k6 告诉你 什么什么是 是缓慢的,}但配置人告诉你 为什么. 组合 K6 載荷測試與 JetBrains 設定器 (dotTrace 的性能@,DotMemory 用于 memory @) 讓您辨明 MinimalBlog 編碼中确切的瓶颈Q.

为何在下装入配置文件@ ? @%

没有负载的剖析通常会错失真实问题@- @world matters @:#

  • 冷开始于暖热缓存不同的代码路径
  • 同时进入-=YTET -伊甸园字幕组=- 翻译:
  • 内存压力“:GC 负载行为”
  • 资源耗用尽:连接库 @,文件控件

k6 创造现实的负载, 而配置程序捕捉到您的 App.中发生的事情

安装喷射控制工具

选择“1:”喷气冲刺点轨迹“MSSK1”多位Memory “(”商业=)

下载自 喷气冲:

# 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

选项 @2: @ dotnet@ -# trace @ MS K2_ free}, 正在建造 -in @ {

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

在此指南中, , @ we'}将显示两种方法_. @

工作流程分析

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

步骤 @1:无负载基线 步骤 @ 2: @ 配置文件在 k6} load 比较并找出瓶颈 修补和重新修补#-}测试

使用 dotTtrace 分析性能

选择方案@:}寻找缓慢的标记分解

“1. ” 启动最小图表,随附剖析图“: ”

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. @ 在另一个终端@ MS K1} 运行 k6 @ load test * :*

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

3. 分析配置:

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

报告中该寻找什么?

  • 热路最耗用CPU时间的方法
  • 调用树@: 时间花在调用层次上
  • 自自时间: 在方法上的时间, 排除调用其他方法

最小Blog:的范例调查结果

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

在此显示 @ : @ {

  • Markdown.Parse() 是“#1”瓶颈 = “(250”=%s 总计=+)}
  • Regex 匹配为 @ #2 @\ MS K1}%s 总计@)
  • 多数时间在第一集 @10 @ 调用@ (Qcache miss *),} 然后缓存

使用dotnet@- @trace @% (# 无替代选项 @ MS K2

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

速度镜可视化@: @%

  • 火焰图形显示呼叫堆栈
  • 命令时间视图显示随时间执行的时长
  • 左重视突出显示慢操作

用 dot Memory 描述内存

缓存中检测内存泄漏的假想@:

1. 启动最小映像仪并附加内存剖析档

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

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

*2.}运行 k6}负载测试 :}

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

@3.在测试期间及之后拍下照片@:

# 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. 分析记忆增长 :

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

寻找什么 :

  • 增加收藏: lists @,字典从未缩放过
  • 事件处理器: 未指定活动( 有参考文件)
  • 缓存浮点*: 缓存超过预期规模
  • 字符串分配: 超绳接龙

实例调查结果:*

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

在此显示 @ : @ {

  • 字符串由 @ 10 MB *% MS K1} 潜在字符串泄漏或缓存问题=)
  • BlogPost 缓存是稳定的 @( @good@) @
  • 由 GC @(#good@)}清理的 MarkdownDocument

使用dotnet@- @ dump @ (# 无选择@ MS K2 @

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

组合 k6 @ @ + 分析脚本

这里“ '” 是一个完整脚本, 它运行 k6 和 profication@ MS K2\

檔案@: 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 ""

运行它:

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

最佳做法: : @ k6 @ @ MS K2_ 图表

  1. 总是在现实负载下配置文件

    • 使用 k{6} 来模拟生产流量模式
    • 配置文件空闲应用程序告诉您任何关于实际性能的信息
  2. 抓取多个快照

    • 在装入“% (” 之前
    • 装载期间 @ (_ 激活@ )} @
    • 卸载后 @(_ check cleanup@)}检查清理后 @ MSK 1
  3. 首先关注热路

    • 先解决最大的瓶颈
    • 代码的80/20Q规则:Q#20%Q需要时间
  4. 前后的措施

    • 优化后总是重新@ - @ run k6}测试
    • 使用配置程序校验改进
  5. 观察回归

    • 将基线概况保留在回书中
    • 比较 CI/CD 中的配置
  6. 配置文件发布基础

    • Debug 构造有不同的性能特点
    • 总是使用 -c Release 特征分析

工具摘要

-=YTET -伊甸园字幕组=- 翻译: |------|---------|------|----------| | k6 @|load test {|} 免費|} 尋找最慢的}|} | 多跟踪 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}寻找为什么它缓慢的 | * | dot 记忆 |记忆分析 |已支付 <|内存泄漏>*,GC问题| | 双网@ - @ 跟踪 -=YTET -伊甸园字幕组 | 顶点@ - @ ocer @|Real-}时间标准#|_免费 |现场监控@|{ | 点网@- @%dump -=YTET -伊甸园字幕组=- 翻译: | 《观察展望》 @| 高级剖析=@| 免费| 深视窗剖析|#

通过测试建立信任

使用 k 6} 加载测试让您相信 MinimalBlog 可以处理真实@ MS K1 world breaty @ . _ 2+% 1 part violation@ MPK4} 您应该能够处理 @ OMK5}

  • 在任何平台上安裝 k6@ (Windows @ MS K2 Mac,Linux @)#
  • 编写和运行不同类型的性能测试
  • 校验缓存行为如预期一样有效
  • 找出生产前的绩效瓶颈
  • 将负载测试集成到 CI/}CD 管道
  • 监测并解释 k6}度量

密钥外出

  1. 从烟雾测试开始 -在更深的测试前验证基本功能
  2. 明确测试缓存 - MinimalBlog' 性能取决于它
  3. 使用现实数据 - 空文件@ MS K1 t表示实际使用量
  4. 监测系统资源 测试期间的 - CPU, memory,GC
  5. 设定有意义的阈值 根据实际需要 , 无法猜测
  6. 在 CI/}CD 中自动挂载 - 早期捕捉回归
  7. 测试不同的假设情景 @-}主页, possible\ ,}分类}=,}错误

我们所学到的关于最小Blog

通过 k6 @ 测试 @ ,} 我们可以校验 MinimalBlog@ MS K2 @ squest @ MPK3}

  • 快速快:+p(95)+<+#300+有温暖的缓存
  • 高效微小的GC压力
  • 可缩放:Handles100+同时使用小硬件
  • 可靠@ : @ @ I< @ *0.1%} 正常载荷下的错误率
  • 缓存-}有效@ : @ @ I10#x 用热缓存快速响应

下一步步骤

现在,你知道如何用 k 6: 测试 ASP.NET吗?

  1. 为您设置创建基线性能配置
  2. 在您的 CI/}CD 管道中设置自动测试
  3. 在重大释放前进行渗漏测试
  4. 监测生产指标并与测试结果进行比较
  5. 校对:Portnoy

记住: 性能测试为 @ ' @ title a one @ MPK1}时间活动. @% 1} 在您加入内容时, 正在修改編碼@ MS K2 @ 或更改主机提供者@ I, @ re-{ 运行这些测试以确保您的应用程序保持快速和可靠@ MPK5}

资源资源资源 资源和资源资源资源

k6文档

吉特胡布行动组织

分析工具

监控: @&视觉化

最小浏览量

ASP.NET 性能

测试快乐 !

Finding related posts...
logo

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