mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-26 03:04:13 +00:00
fix(usage.tsx): show top users and top keys driving spend
This commit is contained in:
parent
6710c2ee5d
commit
4678c6b936
3 changed files with 129 additions and 42 deletions
|
@ -3185,7 +3185,7 @@ async def view_spend_logs(
|
||||||
|
|
||||||
# SQL query
|
# SQL query
|
||||||
response = await prisma_client.db.litellm_spendlogs.group_by(
|
response = await prisma_client.db.litellm_spendlogs.group_by(
|
||||||
by=["api_key", "startTime"],
|
by=["api_key", "user", "model", "startTime"],
|
||||||
where=filter_query, # type: ignore
|
where=filter_query, # type: ignore
|
||||||
sum={
|
sum={
|
||||||
"spend": True,
|
"spend": True,
|
||||||
|
@ -3204,14 +3204,22 @@ async def view_spend_logs(
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
date = dt_object.date()
|
date = dt_object.date()
|
||||||
if date not in result:
|
if date not in result:
|
||||||
result[date] = {}
|
result[date] = {"users": {}, "models": {}}
|
||||||
api_key = record["api_key"]
|
api_key = record["api_key"]
|
||||||
|
user_id = record["user"]
|
||||||
|
model = record["model"]
|
||||||
result[date]["spend"] = (
|
result[date]["spend"] = (
|
||||||
result[date].get("spend", 0) + record["_sum"]["spend"]
|
result[date].get("spend", 0) + record["_sum"]["spend"]
|
||||||
)
|
)
|
||||||
result[date][api_key] = (
|
result[date][api_key] = (
|
||||||
result[date].get(api_key, 0) + record["_sum"]["spend"]
|
result[date].get(api_key, 0) + record["_sum"]["spend"]
|
||||||
)
|
)
|
||||||
|
result[date]["users"][user_id] = (
|
||||||
|
result[date]["users"].get(user_id, 0) + record["_sum"]["spend"]
|
||||||
|
)
|
||||||
|
result[date]["models"][model] = (
|
||||||
|
result[date]["models"].get(model, 0) + record["_sum"]["spend"]
|
||||||
|
)
|
||||||
return_list = []
|
return_list = []
|
||||||
final_date = None
|
final_date = None
|
||||||
for k, v in sorted(result.items()):
|
for k, v in sorted(result.items()):
|
||||||
|
@ -3224,7 +3232,12 @@ async def view_spend_logs(
|
||||||
while current_date <= end_date_date:
|
while current_date <= end_date_date:
|
||||||
# Represent current_date as string because original response has it this way
|
# Represent current_date as string because original response has it this way
|
||||||
return_list.append(
|
return_list.append(
|
||||||
{"startTime": current_date, "spend": 0}
|
{
|
||||||
|
"startTime": current_date,
|
||||||
|
"spend": 0,
|
||||||
|
"users": {},
|
||||||
|
"models": {},
|
||||||
|
}
|
||||||
) # If no data, will stay as zero
|
) # If no data, will stay as zero
|
||||||
current_date += timedelta(days=1) # Move on to the next day
|
current_date += timedelta(days=1) # Move on to the next day
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,13 @@ const customTooltip = (props: CustomTooltipTypeBar) => {
|
||||||
|
|
||||||
// Convert the object into an array of key-value pairs
|
// Convert the object into an array of key-value pairs
|
||||||
const entries: [string, number][] = Object.entries(value)
|
const entries: [string, number][] = Object.entries(value)
|
||||||
.filter(([key]) => key !== "spend" && key !== "startTime")
|
.filter(
|
||||||
|
([key]) =>
|
||||||
|
key !== "spend" &&
|
||||||
|
key !== "startTime" &&
|
||||||
|
key !== "models" &&
|
||||||
|
key !== "users"
|
||||||
|
)
|
||||||
.map(([key, value]) => [key, value as number]); // Type assertion to specify the value as number
|
.map(([key, value]) => [key, value as number]); // Type assertion to specify the value as number
|
||||||
|
|
||||||
// Sort the array based on the float value in descending order
|
// Sort the array based on the float value in descending order
|
||||||
|
@ -53,26 +59,68 @@ const customTooltip = (props: CustomTooltipTypeBar) => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div className="w-56 rounded-tremor-default border border-tremor-border bg-tremor-background p-2 text-tremor-default shadow-tremor-dropdown">
|
|
||||||
// {payload.map((category: any, idx: number) => {
|
|
||||||
// <div key={idx} className="flex flex-1 space-x-2.5">
|
|
||||||
// <div
|
|
||||||
// className={`flex w-1 flex-col bg-${category.color}-500 rounded`}
|
|
||||||
// />
|
|
||||||
// <div className="space-y-1">
|
|
||||||
// <p className="text-tremor-content">{category.dataKey}</p>
|
|
||||||
// <p className="font-medium text-tremor-content-emphasis">
|
|
||||||
// {category.value} bpm
|
|
||||||
// </p>
|
|
||||||
// </div>
|
|
||||||
// </div>;
|
|
||||||
// })}
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getTopKeys(data: Array<{ [key: string]: unknown }>): any[] {
|
||||||
|
const spendKeys: { key: string; spend: unknown }[] = [];
|
||||||
|
|
||||||
|
data.forEach((dict) => {
|
||||||
|
Object.entries(dict).forEach(([key, value]) => {
|
||||||
|
if (
|
||||||
|
key !== "spend" &&
|
||||||
|
key !== "startTime" &&
|
||||||
|
key !== "models" &&
|
||||||
|
key !== "users"
|
||||||
|
) {
|
||||||
|
spendKeys.push({ key, spend: value });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
spendKeys.sort((a, b) => Number(b.spend) - Number(a.spend));
|
||||||
|
|
||||||
|
const topKeys = spendKeys.slice(0, 5);
|
||||||
|
console.log(`topKeys: ${Object.keys(topKeys[0])}`);
|
||||||
|
return topKeys;
|
||||||
|
}
|
||||||
|
type DataDict = { [key: string]: unknown };
|
||||||
|
type UserData = { user_id: string; spend: number };
|
||||||
|
function getTopUsers(data: Array<DataDict>): UserData[] {
|
||||||
|
const userSpend: { [key: string]: number } = {};
|
||||||
|
|
||||||
|
data.forEach((dict) => {
|
||||||
|
const payload: DataDict = dict["users"] as DataDict;
|
||||||
|
Object.entries(payload).forEach(([user_id, value]) => {
|
||||||
|
if (
|
||||||
|
user_id === "" ||
|
||||||
|
user_id === undefined ||
|
||||||
|
user_id === null ||
|
||||||
|
user_id == "None"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userSpend[user_id]) {
|
||||||
|
userSpend[user_id] = 0;
|
||||||
|
}
|
||||||
|
userSpend[user_id] += value as number;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const spendUsers: UserData[] = Object.entries(userSpend).map(
|
||||||
|
([user_id, spend]) => ({
|
||||||
|
user_id,
|
||||||
|
spend,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
spendUsers.sort((a, b) => b.spend - a.spend);
|
||||||
|
|
||||||
|
const topKeys = spendUsers.slice(0, 5);
|
||||||
|
console.log(`topKeys: ${Object.values(topKeys[0])}`);
|
||||||
|
return topKeys;
|
||||||
|
}
|
||||||
|
|
||||||
const UsagePage: React.FC<UsagePageProps> = ({
|
const UsagePage: React.FC<UsagePageProps> = ({
|
||||||
accessToken,
|
accessToken,
|
||||||
token,
|
token,
|
||||||
|
@ -81,7 +129,8 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const [keySpendData, setKeySpendData] = useState<any[]>([]);
|
const [keySpendData, setKeySpendData] = useState<any[]>([]);
|
||||||
const [keyCategories, setKeyCategories] = useState<string[]>([]);
|
const [topKeys, setTopKeys] = useState<any[]>([]);
|
||||||
|
const [topUsers, setTopUsers] = useState<any[]>([]);
|
||||||
|
|
||||||
const firstDay = new Date(
|
const firstDay = new Date(
|
||||||
currentDate.getFullYear(),
|
currentDate.getFullYear(),
|
||||||
|
@ -128,23 +177,9 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||||
endTime
|
endTime
|
||||||
);
|
);
|
||||||
|
|
||||||
const uniqueKeys: Set<string> = new Set();
|
setTopKeys(getTopKeys(response));
|
||||||
|
setTopUsers(getTopUsers(response));
|
||||||
response.forEach((item: any) => {
|
|
||||||
Object.keys(item).forEach((key) => {
|
|
||||||
if (key !== "spend" && key !== "startTime") {
|
|
||||||
uniqueKeys.add(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
let uniqueKeysList = Array.from(uniqueKeys);
|
|
||||||
setKeyCategories(uniqueKeysList);
|
|
||||||
setKeySpendData(response);
|
setKeySpendData(response);
|
||||||
// localStorage.setItem("keySpendData", JSON.stringify(response));
|
|
||||||
// localStorage.setItem(
|
|
||||||
// "keyCategories",
|
|
||||||
// JSON.stringify(uniqueKeysList)
|
|
||||||
// );
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("There was an error fetching the data", error);
|
console.error("There was an error fetching the data", error);
|
||||||
// Optionally, update your UI to reflect the error state here as well
|
// Optionally, update your UI to reflect the error state here as well
|
||||||
|
@ -153,10 +188,16 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
}, [accessToken, token, userRole, userID]);
|
}, [accessToken, token, userRole, userID]);
|
||||||
|
|
||||||
|
topUsers.forEach((obj) => {
|
||||||
|
Object.values(obj).forEach((value) => {
|
||||||
|
console.log(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Grid numItems={1} className="gap-0 p-10 h-[75vh] w-full">
|
<Grid numItems={2} className="gap-2 p-10 h-[75vh] w-full">
|
||||||
<Col numColSpan={1}>
|
<Col numColSpan={2}>
|
||||||
<Card>
|
<Card>
|
||||||
<Title>Monthly Spend</Title>
|
<Title>Monthly Spend</Title>
|
||||||
<BarChart
|
<BarChart
|
||||||
|
@ -171,6 +212,39 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col numColSpan={1}>
|
||||||
|
<Card>
|
||||||
|
<Title>Top API Keys</Title>
|
||||||
|
<BarChart
|
||||||
|
className="mt-4 h-40"
|
||||||
|
data={topKeys}
|
||||||
|
index="key"
|
||||||
|
categories={["spend"]}
|
||||||
|
colors={["blue"]}
|
||||||
|
yAxisWidth={200}
|
||||||
|
tickGap={5}
|
||||||
|
layout="vertical"
|
||||||
|
showXAxis={false}
|
||||||
|
showLegend={false}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col numColSpan={1}>
|
||||||
|
<Card>
|
||||||
|
<Title>Top Users</Title>
|
||||||
|
<BarChart
|
||||||
|
className="mt-4 h-40"
|
||||||
|
data={topUsers}
|
||||||
|
index="user_id"
|
||||||
|
categories={["spend"]}
|
||||||
|
colors={["blue"]}
|
||||||
|
yAxisWidth={200}
|
||||||
|
layout="vertical"
|
||||||
|
showXAxis={false}
|
||||||
|
showLegend={false}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -89,7 +89,7 @@ module.exports = {
|
||||||
"tremor-full": "9999px",
|
"tremor-full": "9999px",
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
"tremor-label": ["0.75rem", { lineHeight: "1rem" }],
|
"tremor-label": ["0.75rem", { lineHeight: "0.4rem" }],
|
||||||
"tremor-default": ["0.875rem", { lineHeight: "1.25rem" }],
|
"tremor-default": ["0.875rem", { lineHeight: "1.25rem" }],
|
||||||
"tremor-title": ["1.125rem", { lineHeight: "1.75rem" }],
|
"tremor-title": ["1.125rem", { lineHeight: "1.75rem" }],
|
||||||
"tremor-metric": ["1.875rem", { lineHeight: "2.25rem" }],
|
"tremor-metric": ["1.875rem", { lineHeight: "2.25rem" }],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue