让我详细分析Grafana源码中计算step的完整逻辑,它确实比简单的intervalMs/1000
复杂得多。
完整的Step计算流程
1. 入口点:[models.Parse](file://F:\JavaProject\grafana-release-11.2.0\pkg\promlib\models\query.go#L190-L274)函数
在pkg/promlib/models/query.go
中:
func Parse(span trace.Span, query backend.DataQuery, dsScrapeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool, enableScope bool) (*Query, error) {
model := &internalQueryModel{}
if err := json.Unmarshal(query.JSON, model); err != nil {
return nil, err
}
// Final step value for prometheus
calculatedStep, err := calculatePrometheusInterval(model.Interval, dsScrapeInterval, int64(model.IntervalMS), model.IntervalFactor, query, intervalCalculator)
if err != nil {
return nil, err
}
// ...
return &Query{
Expr: expr,
Step: calculatedStep, // 这就是最终的step值
// ...
}, nil
}
2. 核心计算函数:calculatePrometheusInterval
func calculatePrometheusInterval(
queryInterval, dsScrapeInterval string,
intervalMs, intervalFactor int64,
query backend.DataQuery,
intervalCalculator intervalv2.Calculator,
) (time.Duration, error) {
// 保存原始queryInterval用于后续比较
originalQueryInterval := queryInterval
// 如果使用变量(如$__interval),则清空queryInterval
if isVariableInterval(queryInterval) {
queryInterval = ""
}
// 1. 获取最小间隔
minInterval, err := gtime.GetIntervalFrom(dsScrapeInterval, queryInterval, intervalMs, 15*time.Second)
if err != nil {
return time.Duration(0), err
}
// 2. 使用intervalCalculator计算间隔
calculatedInterval := intervalCalculator.Calculate(query.TimeRange, minInterval, query.MaxDataPoints)
// 3. 计算安全间隔
safeInterval := intervalCalculator.CalculateSafeInterval(query.TimeRange, int64(safeResolution))
// 4. 选择较大的间隔值
adjustedInterval := safeInterval.Value
if calculatedInterval.Value > safeInterval.Value {
adjustedInterval = calculatedInterval.Value
}
// 5. 特殊处理$__rate_interval情况
if originalQueryInterval == varRateInterval || originalQueryInterval == varRateIntervalAlt {
// Rate interval有特殊计算逻辑
return calculateRateInterval(adjustedInterval, dsScrapeInterval), nil
} else {
// 6. 应用intervalFactor
queryIntervalFactor := intervalFactor
if queryIntervalFactor == 0 {
queryIntervalFactor = 1
}
return time.Duration(int64(adjustedInterval) * queryIntervalFactor), nil
}
}
3. intervalCalculator的实现
在pkg/promlib/intervalv2/intervalv2.go
中:
func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval {
to := timerange.To.UnixNano()
from := timerange.From.UnixNano()
resolution := maxDataPoints
if resolution == 0 {
resolution = DefaultRes // 默认1500
}
// 核心计算:(时间范围) / (最大数据点数)
calculatedInterval := time.Duration((to - from) / resolution)
// 如果计算出的间隔小于最小间隔,则使用最小间隔
if calculatedInterval < minInterval {
return Interval{Text: gtime.FormatInterval(minInterval), Value: minInterval}
}
// 对计算出的间隔进行四舍五入调整
rounded := gtime.RoundInterval(calculatedInterval)
return Interval{Text: gtime.FormatInterval(rounded), Value: rounded}
}
4. gtime.RoundInterval的实现
这个函数在SDK中,用于将计算出的间隔四舍五入到标准值:
// 这是简化版的逻辑,实际更复杂
func RoundInterval(interval time.Duration) time.Duration {
// 根据不同的间隔范围,四舍五入到标准值
switch {
case interval < 15*time.Second:
return 15 * time.Second
case interval < 30*time.Second:
return 30 * time.Second
case interval < 1*time.Minute:
return 1 * time.Minute
// ... 更多情况
default:
return interval
}
}
5. 特殊情况:Rate Interval计算
func calculateRateInterval(
queryInterval time.Duration,
requestedMinStep string,
) time.Duration {
scrape := requestedMinStep
if scrape == "" {
scrape = "15s"
}
scrapeIntervalDuration, err := gtime.ParseIntervalStringToTimeDuration(scrape)
if err != nil {
return time.Duration(0)
}
// Rate interval = max(queryInterval + scrapeInterval, 4 * scrapeInterval)
rateInterval := time.Duration(int64(math.Max(
float64(queryInterval+scrapeIntervalDuration),
float64(4)*float64(scrapeIntervalDuration))))
return rateInterval
}
实际计算示例
假设:
- 时间范围:1小时 (3,600,000毫秒)
- maxDataPoints:1147
- intervalMs:从面板宽度计算得出
- dsScrapeInterval:“15s”
计算过程:
calculatedInterval = 3,600,000ms / 1147 ≈ 3139ms
RoundInterval(3139ms) → 5000ms
(四舍五入到标准值)safeInterval = 3,600,000ms / 11000 ≈ 327ms
(安全间隔)adjustedInterval = max(5000ms, 327ms) = 5000ms
- 如果没有intervalFactor,则最终step = 5000ms = 5s
与简单intervalMs/1000的区别
- 四舍五入:计算结果会被四舍五入到标准间隔值
- 最小间隔限制:不会小于数据源的scrape间隔
- 安全间隔:会考虑一个安全的上限
- 特殊变量处理:对
$__rate_interval
等变量有特殊处理 - intervalFactor:可以放大间隔
- 显式step优先:如果查询中设置了step字段,会直接使用
Java实现建议
基于这个复杂的逻辑,您的Java实现应该包括:
public class PrometheusStepCalculator {
public static long calculateStep(
Long explicitStep, // 显式设置的step(秒)
String interval, // 查询中的interval字段
long intervalMs, // 前端计算的intervalMs
long intervalFactor, // intervalFactor
String dsScrapeInterval, // 数据源scrape间隔(如"15s")
long timeRangeMs, // 查询时间范围(毫秒)
long maxDataPoints // maxDataPoints
) {
// 1. 显式step优先
if (explicitStep != null) {
return explicitStep;
}
// 2. 处理变量情况
if (isVariableInterval(interval)) {
interval = "";
}
// 3. 获取最小间隔
long minInterval = getMinInterval(dsScrapeInterval, interval, intervalMs);
// 4. 计算基本间隔
long calculatedInterval = calculateBaseInterval(timeRangeMs, maxDataPoints);
// 5. 四舍五入到标准值
long roundedInterval = roundInterval(calculatedInterval);
// 6. 计算安全间隔
long safeInterval = calculateSafeInterval(timeRangeMs);
// 7. 选择较大值
long adjustedInterval = Math.max(roundedInterval, safeInterval);
// 8. 应用最小间隔限制
adjustedInterval = Math.max(adjustedInterval, minInterval);
// 9. 应用intervalFactor
if (intervalFactor <= 0) {
intervalFactor = 1;
}
long finalInterval = adjustedInterval * intervalFactor;
// 10. 转换为秒
return finalInterval / 1000;
}
// 实现各个辅助方法...
}
这解释了为什么您的观察数据与简单的intervalMs/1000
不一致,因为Grafana进行了复杂的调整和优化。