Files
orbit/sdk/go/cmd/orbit-exec-load/report.go

122 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"errors"
"math"
"runtime"
"sort"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
// distribution 只计算明确纳入的样本;空集合的百分位返回null,不能冒充零延迟。
type distribution struct {
Count int `json:"count"` // Count 样本数;完成阶段仅包含通过持久化核验的成功任务。
P50 *float64 `json:"p50_ms"` // P50 最近秩中位数,毫秒。
P95 *float64 `json:"p95_ms"` // P95 最近秩95百分位,毫秒。
P99 *float64 `json:"p99_ms"` // P99 最近秩99百分位,毫秒。
Max *float64 `json:"max_ms"` // Max 样本最大耗时,毫秒。
}
// executionReport 是单次有界测量结果,不是容量承诺或持久化审计。
type executionReport struct {
RunID string `json:"run_id"` // RunID 固定提交键和处理器身份的组成部分。
GoVersion string `json:"go_version"` // GoVersion 实际编译工具链版本,不从仓库要求推断。
GOOS string `json:"goos"` // GOOS 负载进程操作系统。
GOARCH string `json:"goarch"` // GOARCH 负载进程架构。
StartedAt time.Time `json:"started_at"` // StartedAt 本机开始时间,包含预检。
AdmissionFinishedAt time.Time `json:"admission_finished_at"` // AdmissionFinishedAt 本机受理组结束时间;预检失败时为零值。
FinishedAt time.Time `json:"finished_at"` // FinishedAt 核验及Worker收尾结束时间。
Requests int `json:"requests"` // Requests 计划独立任务数。
Concurrency int `json:"concurrency"` // Concurrency 提交及核验各自最大并发。
Workers int `json:"workers"` // Workers 同一负载进程内独立WSS运行器数。
Capacity int `json:"capacity_per_worker"` // Capacity 每个运行器物理处理器并发上限。
DurationsMS []int64 `json:"durations_ms"` // DurationsMS 按序号循环选取的模拟等待时长。
RunTimeoutMS int64 `json:"run_timeout_ms"` // RunTimeoutMS 整体期限,收尾最多另加5秒。
PollIntervalMS int64 `json:"poll_interval_ms"` // PollIntervalMS 每个活跃核验器两次读取之间的间隔。
HTTPTimeoutMS int64 `json:"http_timeout_ms"` // HTTPTimeoutMS 单次HTTP请求时限,不是任务时限。
Queue *orbit.Queue `json:"queue,omitempty"` // Queue 预检时队列额度快照;工具不会修改配置。
Attempted int `json:"attempted"` // Attempted 实际发起的提交调用数,不自动重试。
NotSubmitted int `json:"not_submitted"` // NotSubmitted 截止/错误前未发起的项数。
Accepted int `json:"accepted"` // Accepted 收到并核验唯一回执的项数。
AdmissionErrors int `json:"admission_errors"` // AdmissionErrors 明确拒绝或提交结果无法确认的项数,不能当作全部未落库。
VerifiedSuccess int `json:"verified_success"` // VerifiedSuccess 最终任务、尝试及结果均通过核验的任务数。
Unverified int `json:"unverified"` // Unverified 已受理但未获核验成功的数量,包括失败/核查/仍待执行。
HandlerCalls int `json:"handler_calls"` // HandlerCalls 本进程有效输入处理器调用数。
DuplicateInvocations int `json:"duplicate_invocations"` // DuplicateInvocations 同一序号超过一次的调用数。
UnconfirmedHandlerCalls int `json:"unconfirmed_handler_calls"` // UnconfirmedHandlerCalls 没有已确认受理回执的处理器调用数。
WorkerStarted uint64 `json:"worker_started"` // WorkerStarted SDK本进程统计,不替代持久化成功数。
Reconnects uint64 `json:"worker_reconnects"` // Reconnects SDK重连计数。
Renewals uint64 `json:"worker_renewals_confirmed"` // Renewals SDK续租确认数。
Admission distribution `json:"admission"` // Admission 全部提交HTTP交换的本机单调时钟耗时,含失败。
QueueWait distribution `json:"queue_wait"` // QueueWait 数据库available_at至authorized_at,含领取及开始授权。
AuthorizedExecution distribution `json:"authorized_execution"` // AuthorizedExecution 数据库authorized_at至ended_at,止于结束事务取时点,不包含其后提交确认。
TaskCompletion distribution `json:"task_completion"` // TaskCompletion 数据库created_at至completed_at,不受客户端轮询顺序影响。
Errors map[string]int `json:"errors"` // Errors 固定分类,不保存内部异常、SQL、凭据或请求原文。
Complete bool `json:"complete"` // Complete 仅所有计划任务和本地调用逐项核验且无错误时为true。
}
func newReport(cfg config, runID string) executionReport {
r := executionReport{RunID: runID, GoVersion: runtime.Version(), GOOS: runtime.GOOS, GOARCH: runtime.GOARCH, StartedAt: time.Now().UTC(), Requests: cfg.Requests, NotSubmitted: cfg.Requests, Concurrency: cfg.Concurrency, Workers: cfg.Workers, Capacity: cfg.Capacity, RunTimeoutMS: cfg.RunTimeout.Milliseconds(), PollIntervalMS: cfg.PollInterval.Milliseconds(), HTTPTimeoutMS: cfg.Timeout.Milliseconds(), Errors: map[string]int{}}
for _, d := range cfg.Durations {
r.DurationsMS = append(r.DurationsMS, d.Milliseconds())
}
return r
}
func (r executionReport) finish() executionReport {
for code, count := range r.Errors {
if count == 0 {
delete(r.Errors, code)
}
}
r.FinishedAt = time.Now().UTC()
r.Unverified = r.Accepted - r.VerifiedSuccess
r.Complete = r.Attempted == r.Requests && r.NotSubmitted == 0 && r.AdmissionErrors == 0 && r.Accepted == r.Requests && r.VerifiedSuccess == r.Requests && r.HandlerCalls == r.Requests && r.WorkerStarted == uint64(r.Requests) && r.DuplicateInvocations == 0 && r.UnconfirmedHandlerCalls == 0 && len(r.Errors) == 0
return r
}
func distributionOf(values []time.Duration) distribution {
d := distribution{Count: len(values)}
if len(values) == 0 {
return d
}
sort.Slice(values, func(i, j int) bool { return values[i] < values[j] })
percentile := func(q float64) *float64 {
v := float64(values[int(math.Ceil(float64(len(values))*q))-1]) / float64(time.Millisecond)
return &v
}
d.P50 = percentile(.5)
d.P95 = percentile(.95)
d.P99 = percentile(.99)
d.Max = percentile(1)
return d
}
func classify(err error) string {
if errors.Is(err, context.DeadlineExceeded) {
return "TIMEOUT"
}
if errors.Is(err, context.Canceled) {
return "CANCELLED"
}
var api *orbit.APIError
if errors.As(err, &api) {
switch api.StatusCode {
case 401, 403:
return "AUTH"
case 409:
return "CONFLICT"
case 429:
return "BACKPRESSURE"
case 400, 422:
return "INVALID_REQUEST"
}
return "HTTP"
}
var h *orbit.HTTPError
if errors.As(err, &h) {
return "HTTP"
}
return "TRANSPORT"
}