forked from Archives/Athou_commafeed
add metrics page
This commit is contained in:
@@ -14,6 +14,7 @@ import { Header } from "components/header/Header"
|
||||
import { Tree } from "components/sidebar/Tree"
|
||||
import { useI18n } from "i18n"
|
||||
import { AdminUsersPage } from "pages/admin/AdminUsersPage"
|
||||
import { MetricsPage } from "pages/admin/MetricsPage"
|
||||
import { AddPage } from "pages/app/AddPage"
|
||||
import { CategoryDetailsPage } from "pages/app/CategoryDetailsPage"
|
||||
import { FeedDetailsPage } from "pages/app/FeedDetailsPage"
|
||||
@@ -78,6 +79,7 @@ function AppRoutes() {
|
||||
<Route path="settings" element={<SettingsPage />} />
|
||||
<Route path="admin">
|
||||
<Route path="users" element={<AdminUsersPage />} />
|
||||
<Route path="metrics" element={<MetricsPage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
IDRequest,
|
||||
LoginRequest,
|
||||
MarkRequest,
|
||||
Metrics,
|
||||
PasswordResetRequest,
|
||||
ProfileModificationRequest,
|
||||
RegistrationRequest,
|
||||
@@ -79,6 +80,7 @@ export const client = {
|
||||
getAllUsers: () => axiosInstance.get<UserModel[]>("admin/user/getAll"),
|
||||
saveUser: (req: UserModel) => axiosInstance.post("admin/user/save", req),
|
||||
deleteUser: (req: IDRequest) => axiosInstance.post("admin/user/delete", req),
|
||||
getMetrics: () => axiosInstance.get<Metrics>("admin/metrics"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ export const redirectToSettings = createAsyncThunk("redirect/settings", (_, thun
|
||||
export const redirectToAdminUsers = createAsyncThunk("redirect/admin/users", (_, thunkApi) =>
|
||||
thunkApi.dispatch(redirectTo("/app/admin/users"))
|
||||
)
|
||||
export const redirectToMetrics = createAsyncThunk("redirect/admin/metrics", (_, thunkApi) =>
|
||||
thunkApi.dispatch(redirectTo("/app/admin/metrics"))
|
||||
)
|
||||
|
||||
export const redirectSlice = createSlice({
|
||||
name: "redirect",
|
||||
|
||||
@@ -115,6 +115,22 @@ export interface FeedModificationRequest {
|
||||
filter?: string
|
||||
}
|
||||
|
||||
export interface GetEntriesRequest {
|
||||
id: string
|
||||
readType?: ReadingMode
|
||||
newerThan?: number
|
||||
order?: ReadingOrder
|
||||
keywords?: string
|
||||
onlyIds?: boolean
|
||||
excludedSubscriptionIds?: string
|
||||
tag?: string
|
||||
}
|
||||
|
||||
export interface GetEntriesPaginatedRequest extends GetEntriesRequest {
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
|
||||
export interface IDRequest {
|
||||
id: number
|
||||
}
|
||||
@@ -132,6 +148,50 @@ export interface MarkRequest {
|
||||
excludedSubscriptions?: number[]
|
||||
}
|
||||
|
||||
export interface MetricCounter {
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface MetricGauge {
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface MetricMeter {
|
||||
count: number
|
||||
m15_rate: number
|
||||
m1_rate: number
|
||||
m5_rate: number
|
||||
mean_rate: number
|
||||
units: string
|
||||
}
|
||||
|
||||
export type MetricTimer = {
|
||||
count: number
|
||||
max: number
|
||||
mean: number
|
||||
min: number
|
||||
p50: number
|
||||
p75: number
|
||||
p95: number
|
||||
p98: number
|
||||
p99: number
|
||||
p999: number
|
||||
stddev: number
|
||||
m15_rate: number
|
||||
m1_rate: number
|
||||
m5_rate: number
|
||||
mean_rate: number
|
||||
duration_units: string
|
||||
rate_units: string
|
||||
}
|
||||
|
||||
export interface Metrics {
|
||||
counters: { [key: string]: MetricCounter }
|
||||
gauges: { [key: string]: MetricGauge }
|
||||
meters: { [key: string]: MetricMeter }
|
||||
timers: { [key: string]: MetricTimer }
|
||||
}
|
||||
|
||||
export interface MultipleMarkRequest {
|
||||
requests: MarkRequest[]
|
||||
}
|
||||
@@ -219,22 +279,6 @@ export interface TagRequest {
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface GetEntriesRequest {
|
||||
id: string
|
||||
readType?: ReadingMode
|
||||
newerThan?: number
|
||||
order?: ReadingOrder
|
||||
keywords?: string
|
||||
onlyIds?: boolean
|
||||
excludedSubscriptionIds?: string
|
||||
tag?: string
|
||||
}
|
||||
|
||||
export interface GetEntriesPaginatedRequest extends GetEntriesRequest {
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
|
||||
export interface UnreadCount {
|
||||
feedId?: number
|
||||
unreadCount?: number
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from "@lingui/macro"
|
||||
import { Divider, Menu, useMantineColorScheme } from "@mantine/core"
|
||||
import { redirectToAdminUsers, redirectToSettings } from "app/slices/redirect"
|
||||
import { redirectToAdminUsers, redirectToMetrics, redirectToSettings } from "app/slices/redirect"
|
||||
import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { useState } from "react"
|
||||
import { TbMoon, TbPower, TbSettings, TbSun, TbUsers } from "react-icons/tb"
|
||||
import { TbChartLine, TbMoon, TbPower, TbSettings, TbSun, TbUsers } from "react-icons/tb"
|
||||
|
||||
interface ProfileMenuProps {
|
||||
control: React.ReactElement
|
||||
@@ -52,6 +52,15 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
||||
>
|
||||
<Trans>Manage users</Trans>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
icon={<TbChartLine />}
|
||||
onClick={() => {
|
||||
dispatch(redirectToMetrics())
|
||||
setOpened(false)
|
||||
}}
|
||||
>
|
||||
<Trans>Metrics</Trans>
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
9
commafeed-client/src/components/metrics/Gauge.tsx
Normal file
9
commafeed-client/src/components/metrics/Gauge.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { MetricGauge } from "app/types"
|
||||
|
||||
interface MeterProps {
|
||||
gauge: MetricGauge
|
||||
}
|
||||
|
||||
export function Gauge(props: MeterProps) {
|
||||
return <span>{props.gauge.value}</span>
|
||||
}
|
||||
19
commafeed-client/src/components/metrics/Meter.tsx
Normal file
19
commafeed-client/src/components/metrics/Meter.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Box } from "@mantine/core"
|
||||
import { MetricMeter } from "app/types"
|
||||
|
||||
interface MeterProps {
|
||||
meter: MetricMeter
|
||||
}
|
||||
|
||||
export function Meter(props: MeterProps) {
|
||||
return (
|
||||
<Box>
|
||||
<Box>Mean: {props.meter.mean_rate.toFixed(2)}</Box>
|
||||
<Box>Last minute: {props.meter.m1_rate.toFixed(2)}</Box>
|
||||
<Box>Last 5 minutes: {props.meter.m5_rate.toFixed(2)}</Box>
|
||||
<Box>Last 15 minutes: {props.meter.m15_rate.toFixed(2)}</Box>
|
||||
<Box>Units: {props.meter.units}</Box>
|
||||
<Box>Total: {props.meter.count}</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Accordion, Box, Group } from "@mantine/core"
|
||||
|
||||
interface MetricAccordionItemProps {
|
||||
metricKey: string
|
||||
name: string
|
||||
headerValue: number
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function MetricAccordionItem({ metricKey, name, headerValue, children }: MetricAccordionItemProps) {
|
||||
return (
|
||||
<Accordion.Item value={metricKey} key={metricKey}>
|
||||
<Accordion.Control>
|
||||
<Group position="apart">
|
||||
<Box>{name}</Box>
|
||||
<Box>{headerValue}</Box>
|
||||
</Group>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>{children}</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
)
|
||||
}
|
||||
19
commafeed-client/src/components/metrics/Timer.tsx
Normal file
19
commafeed-client/src/components/metrics/Timer.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Box } from "@mantine/core"
|
||||
import { MetricTimer } from "app/types"
|
||||
|
||||
interface MetricTimerProps {
|
||||
timer: MetricTimer
|
||||
}
|
||||
|
||||
export function Timer(props: MetricTimerProps) {
|
||||
return (
|
||||
<Box>
|
||||
<Box>Mean: {props.timer.mean_rate.toFixed(2)}</Box>
|
||||
<Box>Last minute: {props.timer.m1_rate.toFixed(2)}</Box>
|
||||
<Box>Last 5 minutes: {props.timer.m5_rate.toFixed(2)}</Box>
|
||||
<Box>Last 15 minutes: {props.timer.m15_rate.toFixed(2)}</Box>
|
||||
<Box>Units: {props.timer.rate_units}</Box>
|
||||
<Box>Total: {props.timer.count}</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
75
commafeed-client/src/pages/admin/MetricsPage.tsx
Normal file
75
commafeed-client/src/pages/admin/MetricsPage.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Accordion, Box, Tabs } from "@mantine/core"
|
||||
import { client } from "app/client"
|
||||
import { Loader } from "components/Loader"
|
||||
import { Gauge } from "components/metrics/Gauge"
|
||||
import { Meter } from "components/metrics/Meter"
|
||||
import { MetricAccordionItem } from "components/metrics/MetricAccordionItem"
|
||||
import { Timer } from "components/metrics/Timer"
|
||||
import { useAsync } from "react-async-hook"
|
||||
import { TbChartAreaLine, TbClock } from "react-icons/tb"
|
||||
|
||||
const shownMeters: { [key: string]: string } = {
|
||||
"com.commafeed.backend.feed.FeedQueues.refill": "Refresh queue refill rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshTaskGiver.feedRefreshed": "Feed refreshed",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed updated",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss",
|
||||
}
|
||||
|
||||
const shownGauges: { [key: string]: string } = {
|
||||
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.active": "Feed Updater active",
|
||||
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.pending": "Feed Updater queued",
|
||||
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.active": "Feed Worker active",
|
||||
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.pending": "Feed Worker queued",
|
||||
"com.commafeed.backend.feed.FeedQueues.addQueue": "Task Giver Add Queue",
|
||||
"com.commafeed.backend.feed.FeedQueues.takeQueue": "Task Giver Take Queue",
|
||||
"com.commafeed.backend.feed.FeedQueues.giveBackQueue": "Task Giver Give Back Queue",
|
||||
}
|
||||
|
||||
export function MetricsPage() {
|
||||
const query = useAsync(() => client.admin.getMetrics(), [])
|
||||
|
||||
if (!query.result) return <Loader />
|
||||
const { meters, gauges, timers } = query.result.data
|
||||
return (
|
||||
<Tabs defaultValue="stats">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="stats" icon={<TbChartAreaLine size={14} />}>
|
||||
Stats
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="timers" icon={<TbClock size={14} />}>
|
||||
Timers
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="stats" pt="xs">
|
||||
<Accordion variant="contained" chevronPosition="left">
|
||||
{Object.keys(shownMeters).map(m => (
|
||||
<MetricAccordionItem key={m} metricKey={m} name={shownMeters[m]} headerValue={meters[m].count}>
|
||||
<Meter meter={meters[m]} />
|
||||
</MetricAccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
|
||||
<Box pt="xs">
|
||||
{Object.keys(shownGauges).map(g => (
|
||||
<Box key={g}>
|
||||
<span>{shownGauges[g]} </span>
|
||||
<Gauge gauge={gauges[g]} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="timers" pt="xs">
|
||||
<Accordion variant="contained" chevronPosition="left">
|
||||
{Object.keys(timers).map(key => (
|
||||
<MetricAccordionItem key={key} metricKey={key} name={key} headerValue={timers[key].count}>
|
||||
<Timer timer={timers[key]} />
|
||||
</MetricAccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user