///|
priv struct ProviderLimitConfig {
daily_limit_usd : String?
monthly_limit_usd : String?
}
///|
fn ProviderLimitConfig::new(
daily_limit_usd? : String? = None,
monthly_limit_usd? : String? = None,
) -> ProviderLimitConfig {
{ daily_limit_usd, monthly_limit_usd }
}
///|
fn TrafficStats::set_provider_limits(
self : TrafficStats,
provider_id : String,
app_type : String,
daily_limit_usd? : String? = None,
monthly_limit_usd? : String? = None,
) -> Unit {
self.provider_limits.set(
provider_limit_key(provider_id, app_type),
ProviderLimitConfig::new(daily_limit_usd~, monthly_limit_usd~),
)
}
///|
fn TrafficStats::provider_limit_status_json(
self : TrafficStats,
provider_id : String,
app_type : String,
daily_limit_usd? : String? = None,
monthly_limit_usd? : String? = None,
now_ts? : Int? = None,
) -> Json {
let configured = self.provider_limits.get(
provider_limit_key(provider_id, app_type),
)
let daily_limit = match daily_limit_usd {
Some(limit) => Some(limit)
None =>
match configured {
Some(config) => config.daily_limit_usd
None => None
}
}
let monthly_limit = match monthly_limit_usd {
Some(limit) => Some(limit)
None =>
match configured {
Some(config) => config.monthly_limit_usd
None => None
}
}
let now_ts = now_ts.unwrap_or(trend_now_seconds())
let daily_usage_nano = self.provider_daily_usage_nano(
provider_id, app_type, now_ts,
)
let monthly_usage_nano = self.provider_monthly_usage_nano(
provider_id, app_type, now_ts,
)
let daily_limit_nano = daily_limit.map(fn(limit) {
parse_decimal_scaled(limit, NANO_USD_SCALE)
})
let monthly_limit_nano = monthly_limit.map(fn(limit) {
parse_decimal_scaled(limit, NANO_USD_SCALE)
})
{
"providerId": Json::string(provider_id),
"dailyUsage": Json::string(format_nano_usd_fixed_6(daily_usage_nano)),
"dailyLimit": optional_limit_json(daily_limit_nano),
"dailyExceeded": Json::boolean(
limit_exceeded(daily_usage_nano, daily_limit_nano),
),
"monthlyUsage": Json::string(format_nano_usd_fixed_6(monthly_usage_nano)),
"monthlyLimit": optional_limit_json(monthly_limit_nano),
"monthlyExceeded": Json::boolean(
limit_exceeded(monthly_usage_nano, monthly_limit_nano),
),
}.to_json()
}
///|
fn TrafficStats::provider_limit_exceeded(
self : TrafficStats,
provider_id : String,
app_type : String,
now_ts? : Int = trend_now_seconds(),
) -> Bool {
guard self.provider_limits.get(provider_limit_key(provider_id, app_type))
is Some(config) else {
return false
}
let daily_limit = config.daily_limit_usd.map(fn(limit) {
parse_decimal_scaled(limit, NANO_USD_SCALE)
})
let monthly_limit = config.monthly_limit_usd.map(fn(limit) {
parse_decimal_scaled(limit, NANO_USD_SCALE)
})
limit_exceeded(
self.provider_daily_usage_nano(provider_id, app_type, now_ts),
daily_limit,
) ||
limit_exceeded(
self.provider_monthly_usage_nano(provider_id, app_type, now_ts),
monthly_limit,
)
}
///|
fn TrafficStats::provider_limit_monitor_counts(
self : TrafficStats,
now_ts? : Int = trend_now_seconds(),
) -> (Int, Int) {
let mut configured_count = 0
let mut exceeded_count = 0
for key, config in self.provider_limits {
if config.daily_limit_usd is None && config.monthly_limit_usd is None {
continue
}
configured_count += 1
let parts = key.split("|").to_array()
if parts.length() != 2 {
continue
}
if self.provider_limit_exceeded(
parts[0].to_owned(),
parts[1].to_owned(),
now_ts~,
) {
exceeded_count += 1
}
}
(configured_count, exceeded_count)
}
///|
fn TrafficStats::provider_daily_usage_nano(
self : TrafficStats,
provider_id : String,
app_type : String,
now_ts : Int,
) -> Int64 {
let today = trend_local_day_key(now_ts)
let mut total = 0L
for log in self.request_logs {
if log.provider_id == provider_id &&
log.app_type == app_type &&
trend_local_day_key(log.created_at) == today {
total += request_log_total_cost_nano(log)
}
}
total
}
///|
fn TrafficStats::provider_monthly_usage_nano(
self : TrafficStats,
provider_id : String,
app_type : String,
now_ts : Int,
) -> Int64 {
let month = trend_local_month_key(now_ts)
let mut total = 0L
for log in self.request_logs {
if log.provider_id == provider_id &&
log.app_type == app_type &&
trend_local_month_key(log.created_at) == month {
total += request_log_total_cost_nano(log)
}
}
total
}
///|
fn provider_limit_key(provider_id : String, app_type : String) -> String {
"\{provider_id}|\{app_type}"
}
///|
fn trend_local_month_key(timestamp : Int) -> String {
@os.local_month_key(timestamp.to_int64())
}
///|
fn optional_limit_json(limit : Int64?) -> Json {
match limit {
Some(value) => Json::string(format_nano_usd_fixed_2(value))
None => Json::null()
}
}
///|
fn limit_exceeded(usage : Int64, limit : Int64?) -> Bool {
match limit {
Some(value) => usage >= value
None => false
}
}
///|
fn format_nano_usd_fixed_2(nano : Int64) -> String {
let negative = nano < 0L
let absolute = if negative { 0L - nano } else { nano }
let cents = divide_rounded(absolute, 10_000_000L)
let whole = cents / 100L
let fraction = cents % 100L
let sign = if negative { "-" } else { "" }
"\{sign}\{whole}.\{left_pad_zeroes(fraction.to_string(), 2)}"
}