Files
oci-portal/internal/service/usage.go
T

98 lines
2.7 KiB
Go

package service
import (
"context"
"fmt"
"time"
"oci-portal/internal/oci"
)
// maxUsageWindowDays 限制流量/成本查询窗口,避免拉取超大区间。
const maxUsageWindowDays = 366
// InstanceTraffic 查询实例近 days 天的进出流量(按天聚合)。
func (s *OciConfigService) InstanceTraffic(ctx context.Context, id uint, region, instanceID string, days int) (oci.InstanceTraffic, error) {
if days <= 0 {
days = 30
}
if days > maxUsageWindowDays {
return oci.InstanceTraffic{}, fmt.Errorf("instance traffic: days must be within %d", maxUsageWindowDays)
}
cred, err := s.credentialsByID(ctx, id)
if err != nil {
return oci.InstanceTraffic{}, err
}
end := time.Now().UTC().Truncate(time.Hour).Add(time.Hour)
return s.client.SummarizeInstanceTraffic(ctx, cred, oci.TrafficQuery{
Region: region,
InstanceID: instanceID,
StartTime: end.AddDate(0, 0, -days),
EndTime: end,
})
}
// Costs 汇总租户成本或用量;时间缺省为最近 30 天,起止按粒度对齐。
func (s *OciConfigService) Costs(ctx context.Context, id uint, q oci.CostQuery) ([]oci.CostItem, error) {
applyCostDefaults(&q)
if q.EndTime.Sub(q.StartTime) <= 0 {
return nil, fmt.Errorf("costs: endTime must be after startTime")
}
if q.EndTime.Sub(q.StartTime) > maxUsageWindowDays*24*time.Hour {
return nil, fmt.Errorf("costs: window must be within %d days", maxUsageWindowDays)
}
cred, err := s.credentialsByID(ctx, id)
if err != nil {
return nil, err
}
return s.client.SummarizeCosts(ctx, cred, q)
}
// applyCostDefaults 填充缺省值并把时间对齐到粒度边界(Usage API 要求)。
func applyCostDefaults(q *oci.CostQuery) {
if q.Granularity == "" {
q.Granularity = "DAILY"
}
if q.QueryType == "" {
q.QueryType = "COST"
}
if q.GroupBy == "" {
q.GroupBy = "service"
}
now := time.Now().UTC()
if q.EndTime.IsZero() {
q.EndTime = now
}
if q.StartTime.IsZero() {
q.StartTime = q.EndTime.AddDate(0, 0, -30)
}
q.StartTime = alignDown(q.StartTime, q.Granularity)
// endTime 为开区间,向上对齐到粒度边界以覆盖当天/当月
q.EndTime = alignUp(q.EndTime, q.Granularity)
}
func alignDown(t time.Time, granularity string) time.Time {
t = t.UTC()
switch granularity {
case "MONTHLY":
return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC)
case "HOURLY":
return t.Truncate(time.Hour)
}
return t.Truncate(24 * time.Hour)
}
func alignUp(t time.Time, granularity string) time.Time {
down := alignDown(t, granularity)
if down.Equal(t.UTC()) {
return down
}
switch granularity {
case "MONTHLY":
return down.AddDate(0, 1, 0)
case "HOURLY":
return down.Add(time.Hour)
}
return down.AddDate(0, 0, 1)
}