perf: 数据看板支持选择时间粒度

This commit is contained in:
CaIon 2024-01-13 00:33:52 +08:00
parent d30b9321b2
commit 00306aa142
7 changed files with 132 additions and 65 deletions

View File

@ -26,7 +26,8 @@ var DisplayInCurrencyEnabled = true
var DisplayTokenStatEnabled = true
var DrawingEnabled = true
var DataExportEnabled = true
var DataExportInterval = 5 // unit: minute
var DataExportInterval = 5 // unit: minute
var DataExportDefaultTime = "hour" // unit: minute
// Any options with "Secret", "Token" in its key won't be return by GetOptions

View File

@ -16,26 +16,27 @@ func GetStatus(c *gin.Context) {
"success": true,
"message": "",
"data": gin.H{
"start_time": common.StartTime,
"email_verification": common.EmailVerificationEnabled,
"github_oauth": common.GitHubOAuthEnabled,
"github_client_id": common.GitHubClientId,
"system_name": common.SystemName,
"logo": common.Logo,
"footer_html": common.Footer,
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled,
"server_address": common.ServerAddress,
"price": common.Price,
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"chat_link": common.ChatLink,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,
"enable_drawing": common.DrawingEnabled,
"enable_data_export": common.DataExportEnabled,
"start_time": common.StartTime,
"email_verification": common.EmailVerificationEnabled,
"github_oauth": common.GitHubOAuthEnabled,
"github_client_id": common.GitHubClientId,
"system_name": common.SystemName,
"logo": common.Logo,
"footer_html": common.Footer,
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled,
"server_address": common.ServerAddress,
"price": common.Price,
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"chat_link": common.ChatLink,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,
"enable_drawing": common.DrawingEnabled,
"enable_data_export": common.DataExportEnabled,
"data_export_default_time": common.DataExportDefaultTime,
},
})
return

View File

@ -79,6 +79,7 @@ func InitOptionMap() {
common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval)
common.OptionMap["DataExportDefaultTime"] = common.DataExportDefaultTime
common.OptionMapRWMutex.Unlock()
loadOptionsFromDatabase()
@ -228,6 +229,8 @@ func updateOptionMap(key string, value string) (err error) {
common.RetryTimes, _ = strconv.Atoi(value)
case "DataExportInterval":
common.DataExportInterval, _ = strconv.Atoi(value)
case "DataExportDefaultTime":
common.DataExportDefaultTime = value
case "ModelRatio":
err = common.UpdateModelRatioByJSONString(value)
case "GroupRatio":

View File

@ -51,6 +51,7 @@ function App() {
localStorage.setItem('display_in_currency', data.display_in_currency);
localStorage.setItem('enable_drawing', data.enable_drawing);
localStorage.setItem('enable_data_export', data.enable_data_export);
localStorage.setItem('data_export_default_time', data.data_export_default_time);
if (data.chat_link) {
localStorage.setItem('chat_link', data.chat_link);
} else {

View File

@ -23,13 +23,19 @@ const OperationSetting = () => {
DisplayTokenStatEnabled: '',
DrawingEnabled: '',
DataExportEnabled: '',
DataExportDefaultTime: 'hour',
DataExportInterval: 5,
RetryTimes: 0
});
const [originInputs, setOriginInputs] = useState({});
let [loading, setLoading] = useState(false);
let [historyTimestamp, setHistoryTimestamp] = useState(timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600)); // a month ago
// 精确时间选项(小时,天,周)
const timeOptions = [
{key: 'hour', text: '小时', value: 'hour'},
{key: 'day', text: '天', value: 'day'},
{key: 'week', text: '周', value: 'week'}
];
const getOptions = async () => {
const res = await API.get('/api/option/');
const {success, message, data} = res.data;
@ -71,7 +77,10 @@ const OperationSetting = () => {
};
const handleInputChange = async (e, {name, value}) => {
if (name.endsWith('Enabled') || name === 'DataExportInterval') {
if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime') {
if (name === 'DataExportDefaultTime') {
localStorage.setItem('data_export_default_time', value);
}
await updateOption(name, value);
} else {
setInputs((inputs) => ({...inputs, [name]: value}));
@ -234,15 +243,28 @@ const OperationSetting = () => {
name='LogConsumeEnabled'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group inline>
<Form.Checkbox
checked={inputs.DataExportEnabled === 'true'}
label='启用数据看板(实验性)'
name='DataExportEnabled'
onChange={handleInputChange}
/>
<Form.Group widths={4}>
<Form.Input label='目标时间' value={historyTimestamp} type='datetime-local'
name='history_timestamp'
onChange={(e, {name, value}) => {
setHistoryTimestamp(value);
}}/>
</Form.Group>
<Form.Button onClick={() => {
deleteHistoryLogs().then();
}}>清理历史日志</Form.Button>
<Divider/>
<Header as='h3'>
数据看板
</Header>
<Form.Checkbox
checked={inputs.DataExportEnabled === 'true'}
label='启用数据看板(实验性)'
name='DataExportEnabled'
onChange={handleInputChange}
/>
<Form.Group>
<Form.Input
label='数据看板更新间隔(分钟,设置过短会影响数据库性能)'
name='DataExportInterval'
@ -254,19 +276,17 @@ const OperationSetting = () => {
value={inputs.DataExportInterval}
placeholder='数据看板更新间隔(分钟,设置过短会影响数据库性能)'
/>
<Form.Select
label='数据看板默认时间粒度(仅修改展示粒度,统计精确到小时)'
options={timeOptions}
name='DataExportDefaultTime'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.DataExportDefaultTime}
placeholder='数据看板默认时间粒度'
/>
</Form.Group>
<Divider/>
<Form.Group widths={4}>
<Form.Input label='目标时间' value={historyTimestamp} type='datetime-local'
name='history_timestamp'
onChange={(e, {name, value}) => {
setHistoryTimestamp(value);
}}/>
</Form.Group>
<Form.Button onClick={() => {
deleteHistoryLogs().then();
}}>清理历史日志</Form.Button>
<Divider/>
<Header as='h3'>
监控设置
</Header>

View File

@ -171,7 +171,7 @@ export function timestamp2string(timestamp) {
);
}
export function timestamp2string1(timestamp) {
export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
let date = new Date(timestamp * 1000);
// let year = date.getFullYear().toString();
let month = (date.getMonth() + 1).toString();
@ -186,15 +186,22 @@ export function timestamp2string1(timestamp) {
if (hour.length === 1) {
hour = '0' + hour;
}
return (
// year +
// '-' +
month +
'-' +
day +
' ' +
hour + ":00"
);
let str = month + '-' + day
if (dataExportDefaultTime === 'hour') {
str += ' ' + hour + ":00"
} else if (dataExportDefaultTime === 'week') {
let nextWeek = new Date(timestamp * 1000 + 6 * 24 * 60 * 60 * 1000);
let nextMonth = (nextWeek.getMonth() + 1).toString();
let nextDay = nextWeek.getDate().toString();
if (nextMonth.length === 1) {
nextMonth = '0' + nextMonth;
}
if (nextDay.length === 1) {
nextDay = '0' + nextDay;
}
str += ' - ' + nextMonth + '-' + nextDay
}
return str;
}
export function downloadTextAsFile(text, filename) {

View File

@ -12,17 +12,18 @@ import {
} from "../../helpers/render";
const Detail = (props) => {
const formRef = useRef();
let now = new Date();
const [inputs, setInputs] = useState({
username: '',
token_name: '',
model_name: '',
start_timestamp: timestamp2string(now.getTime() / 1000 - 86400),
start_timestamp: localStorage.getItem('data_export_default_time') === 'hour' ? timestamp2string(now.getTime() / 1000 - 86400) : (localStorage.getItem('data_export_default_time') === 'week' ? timestamp2string(now.getTime() / 1000 - 86400 * 30) : timestamp2string(now.getTime() / 1000 - 86400 * 7)),
end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
channel: ''
channel: '',
data_export_default_time: ''
});
const {username, token_name, model_name, start_timestamp, end_timestamp, channel} = inputs;
const {username, model_name, start_timestamp, end_timestamp, channel} = inputs;
const isAdminUser = isAdmin();
const initialized = useRef(false)
const [modelDataChart, setModelDataChart] = useState(null);
@ -31,8 +32,13 @@ const Detail = (props) => {
const [quotaData, setQuotaData] = useState([]);
const [consumeQuota, setConsumeQuota] = useState(0);
const [times, setTimes] = useState(0);
const [dataExportDefaultTime, setDataExportDefaultTime] = useState(localStorage.getItem('data_export_default_time') || 'hour');
const handleInputChange = (value, name) => {
if (name === 'data_export_default_time') {
setDataExportDefaultTime(value);
return
}
setInputs((inputs) => ({...inputs, [name]: value}));
};
@ -41,8 +47,7 @@ const Detail = (props) => {
data: [
{
id: 'barData',
values: [
]
values: []
}
],
xField: 'Time',
@ -54,7 +59,7 @@ const Detail = (props) => {
},
title: {
visible: true,
text: '模型消耗分布(小时)',
text: '模型消耗分布',
subtext: '0'
},
bar: {
@ -104,7 +109,7 @@ const Detail = (props) => {
{
id: 'id0',
values: [
{ type: 'null', value: '0' },
{type: 'null', value: '0'},
]
}
],
@ -163,9 +168,9 @@ const Detail = (props) => {
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
let localEndTimestamp = Date.parse(end_timestamp) / 1000;
if (isAdminUser) {
url = `/api/data/?username=${username}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
url = `/api/data/?username=${username}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&default_time=${dataExportDefaultTime}`;
} else {
url = `/api/data/self/?start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
url = `/api/data/self/?start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&default_time=${dataExportDefaultTime}`;
}
const res = await API.get(url);
const {success, message, data} = res.data;
@ -179,6 +184,16 @@ const Detail = (props) => {
'created_at': now.getTime() / 1000
})
}
// 根据dataExportDefaultTime重制时间粒度
let timeGranularity = 3600;
if (dataExportDefaultTime === 'day') {
timeGranularity = 86400;
} else if (dataExportDefaultTime === 'week') {
timeGranularity = 604800;
}
data.forEach(item => {
item['created_at'] = Math.floor(item['created_at'] / timeGranularity) * timeGranularity;
});
updateChart(lineChart, pieChart, data);
} else {
showError(message);
@ -190,7 +205,7 @@ const Detail = (props) => {
await loadQuotaData(modelDataChart, modelDataPieChart);
};
const initChart = async () => {
const initChart = async () => {
let lineChart = modelDataChart
if (!modelDataChart) {
lineChart = new VChart(spec_line, {dom: 'model_data'});
@ -231,7 +246,7 @@ const Detail = (props) => {
}
// 合并created_at和model_name 为 lineData, created_at 数据类型是小时的时间戳
// 转换日期格式
let createTime = timestamp2string1(item.created_at);
let createTime = timestamp2string1(item.created_at, dataExportDefaultTime);
let lineItem = lineData.find(it => it.Time === createTime && it.Model === item.model_name);
if (lineItem) {
lineItem.Usage += parseFloat(getQuotaWithUnit(item.quota));
@ -263,6 +278,13 @@ const Detail = (props) => {
}
useEffect(() => {
// setDataExportDefaultTime(localStorage.getItem('data_export_default_time'));
// if (dataExportDefaultTime === 'day') {
// // 设置开始时间为7天前
// let st = timestamp2string(now.getTime() / 1000 - 86400 * 7)
// inputs.start_timestamp = st;
// formRef.current.formApi.setValue('start_timestamp', st);
// }
if (!initialized.current) {
initialized.current = true;
initChart();
@ -276,7 +298,7 @@ const Detail = (props) => {
<h3>数据看板</h3>
</Layout.Header>
<Layout.Content>
<Form layout='horizontal' style={{marginTop: 10}}>
<Form ref={formRef} layout='horizontal' style={{marginTop: 10}}>
<>
<Form.DatePicker field="start_timestamp" label='起始时间' style={{width: 272}}
initValue={start_timestamp}
@ -288,6 +310,18 @@ const Detail = (props) => {
value={end_timestamp} type='dateTime'
name='end_timestamp'
onChange={value => handleInputChange(value, 'end_timestamp')}/>
<Form.Select field="data_export_default_time" label='时间粒度' style={{width: 176}}
initValue={dataExportDefaultTime}
placeholder={'时间粒度'} name='data_export_default_time'
optionList={
[
{label: '小时', value: 'hour'},
{label: '天', value: 'day'},
{label: '周', value: 'week'}
]
}
onChange={value => handleInputChange(value, 'data_export_default_time')}>
</Form.Select>
{
isAdminUser && <>
<Form.Input field="username" label='用户名称' style={{width: 176}} value={username}