mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-08-28 11:46:02 +08:00
Adds per-job timing metrics (#605)
This commit is contained in:
parent
1c262945f3
commit
a5a82c8473
7 changed files with 70 additions and 30 deletions
|
@ -41,7 +41,7 @@ export class Stats {
|
|||
|
||||
const results = (await multi.exec()) ?? []
|
||||
return results.map(([_, value]: any, index) => ({
|
||||
date: new Date(keys[index] * 1000),
|
||||
date: keys[index] * 1000,
|
||||
count: parseInt(value ?? 0),
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -35,16 +35,36 @@ router.get('/performance/jobs', async ctx => {
|
|||
|
||||
router.get('/performance/jobs/:job', async ctx => {
|
||||
const jobName = ctx.params.job
|
||||
ctx.body = [
|
||||
{
|
||||
label: 'added',
|
||||
data: await App.main.stats.list(jobName),
|
||||
},
|
||||
{
|
||||
label: 'completed',
|
||||
data: await App.main.stats.list(`${jobName}:completed`),
|
||||
},
|
||||
]
|
||||
|
||||
const added = await App.main.stats.list(jobName)
|
||||
const completed = await App.main.stats.list(`${jobName}:completed`)
|
||||
const duration = await App.main.stats.list(`${jobName}:duration`)
|
||||
const average = duration.map((item, index) => {
|
||||
const count = completed[index]?.count
|
||||
return {
|
||||
date: item.date,
|
||||
count: count ? item.count / count : 0,
|
||||
}
|
||||
})
|
||||
|
||||
ctx.body = {
|
||||
throughput: [
|
||||
{
|
||||
label: 'added',
|
||||
data: added,
|
||||
},
|
||||
{
|
||||
label: 'completed',
|
||||
data: completed,
|
||||
},
|
||||
],
|
||||
timing: [
|
||||
{
|
||||
label: 'average',
|
||||
data: average,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/performance/failed', async ctx => {
|
||||
|
|
|
@ -36,9 +36,10 @@ export default class Queue {
|
|||
App.main.error.notify(new Error(`No handler found for job: ${job.name}`))
|
||||
}
|
||||
|
||||
const start = Date.now()
|
||||
await this.started(job)
|
||||
await handler(job.data, job)
|
||||
await this.completed(job)
|
||||
await this.completed(job, Date.now() - start)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -80,9 +81,11 @@ export default class Queue {
|
|||
App.main.error.notify(error, job)
|
||||
}
|
||||
|
||||
async completed(job: EncodedJob) {
|
||||
async completed(job: EncodedJob, duration: number) {
|
||||
logger.trace(job, 'queue:job:completed')
|
||||
|
||||
await App.main.stats.increment(`${job.name}:completed`)
|
||||
await App.main.stats.increment(`${job.name}:duration`, duration)
|
||||
}
|
||||
|
||||
async start() {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"api_keys": "API Keys",
|
||||
"api_triggered": "API Triggered",
|
||||
"archive": "Archive",
|
||||
"average": "Average",
|
||||
"back": "Back",
|
||||
"balancer": "Balancer",
|
||||
"balancer_desc": "Randomly split users across paths and rate limit traffic.",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"api_keys": "Claves API",
|
||||
"api_triggered": "Activado por API",
|
||||
"archive": "Archivar",
|
||||
"average": "Promedio",
|
||||
"back": "Atrás",
|
||||
"balancer": "Equilibrador",
|
||||
"balancer_desc": "Dividir aleatoriamente a los usuarios en rutas y limitar la velocidad del tráfico.",
|
||||
|
|
|
@ -334,7 +334,7 @@ const api = {
|
|||
.get<string[]>('/admin/organizations/performance/jobs')
|
||||
.then(r => r.data),
|
||||
jobPerformance: async (job: string) => await client
|
||||
.get<Series[]>(`/admin/organizations/performance/jobs/${job}`)
|
||||
.get<{ throughput: Series[], timing: Series[] }>(`/admin/organizations/performance/jobs/${job}`)
|
||||
.then(r => r.data),
|
||||
failed: async () => await client
|
||||
.get<any>('/admin/organizations/performance/failed')
|
||||
|
|
|
@ -14,7 +14,11 @@ import { PreferencesContext } from '../../ui/PreferencesContext'
|
|||
import { formatDate } from '../../utils'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const Chart = ({ series }: { series: Series[] }) => {
|
||||
interface ChartProps {
|
||||
series: Series[]
|
||||
formatter?: (value: number) => string
|
||||
}
|
||||
const Chart = ({ series, formatter = (value) => value.toLocaleString() }: ChartProps) => {
|
||||
const strokes = ['#3C82F6', '#12B981']
|
||||
const [preferences] = useContext(PreferencesContext)
|
||||
return (
|
||||
|
@ -34,7 +38,7 @@ const Chart = ({ series }: { series: Series[] }) => {
|
|||
tick={{ fontSize: 12 }}
|
||||
tickCount={15}
|
||||
tickMargin={8} />
|
||||
<YAxis tick={{ fontSize: 12 }} tickFormatter={count => count.toLocaleString() } />
|
||||
<YAxis tick={{ fontSize: 12 }} tickFormatter={count => formatter(count) } />
|
||||
<CartesianGrid vertical={false} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
|
@ -42,7 +46,7 @@ const Chart = ({ series }: { series: Series[] }) => {
|
|||
border: '1px solid var(--color-grey)',
|
||||
}}
|
||||
labelFormatter={(date: number) => formatDate(preferences, date)}
|
||||
formatter={(value, name) => [`${name}: ${value.toLocaleString()}`]}
|
||||
formatter={(value: any, name) => [`${name}: ${formatter(value)}`]}
|
||||
/>
|
||||
<Legend />
|
||||
{series.map((s, index) => (
|
||||
|
@ -61,6 +65,11 @@ const Chart = ({ series }: { series: Series[] }) => {
|
|||
)
|
||||
}
|
||||
|
||||
interface JobPerformance {
|
||||
throughput: Series[]
|
||||
timing: Series[]
|
||||
}
|
||||
|
||||
export default function Performance() {
|
||||
const { t } = useTranslation()
|
||||
const [waiting, setWaiting] = useState(0)
|
||||
|
@ -72,7 +81,7 @@ export default function Performance() {
|
|||
|
||||
const [jobs, setJobs] = useState<string[]>([])
|
||||
const [currentJob, setCurrentJob] = useState<string | undefined>(job)
|
||||
const [jobMetrics, setJobMetrics] = useState<Series[] | undefined>()
|
||||
const [jobMetrics, setJobMetrics] = useState<JobPerformance | undefined>()
|
||||
|
||||
const [failed, setFailed] = useState<Array<Record<string, any>>>([])
|
||||
const [selectedFailed, setSelectedFailed] = useState<Record<string, any> | undefined>()
|
||||
|
@ -109,15 +118,10 @@ export default function Performance() {
|
|||
useEffect(() => {
|
||||
currentJob && api.organizations.jobPerformance(currentJob)
|
||||
.then((series) => {
|
||||
setJobMetrics(
|
||||
series.map(({ data, label }) => ({
|
||||
label: t(label),
|
||||
data: data.map(item => ({
|
||||
date: Date.parse(item.date as string),
|
||||
count: item.count,
|
||||
})),
|
||||
})),
|
||||
)
|
||||
setJobMetrics({
|
||||
throughput: series.throughput.map(({ data, label }) => ({ data, label: t(label) })),
|
||||
timing: series.timing.map(({ data, label }) => ({ data, label: t(label) })),
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [currentJob])
|
||||
|
@ -135,7 +139,8 @@ export default function Performance() {
|
|||
title="Performance"
|
||||
desc="View queue throughput for your project."
|
||||
>
|
||||
<Heading size="h4" title="Queue Throughput" />
|
||||
<Heading size="h3" title="Queue" />
|
||||
<Heading size="h4" title="Throughput" />
|
||||
{metrics && <div>
|
||||
<TileGrid numColumns={4}>
|
||||
<Tile title={waiting.toLocaleString()} size="large">In Queue</Tile>
|
||||
|
@ -143,7 +148,7 @@ export default function Performance() {
|
|||
<Chart series={metrics} />
|
||||
</div>}
|
||||
<br /><br />
|
||||
<Heading size="h4" title="Per Job Metrics" actions={
|
||||
<Heading size="h3" title="Individual Job" actions={
|
||||
<SingleSelect
|
||||
size="small"
|
||||
options={jobs}
|
||||
|
@ -151,7 +156,17 @@ export default function Performance() {
|
|||
onChange={handleChangeJob}
|
||||
/>
|
||||
} />
|
||||
{jobMetrics && <Chart series={jobMetrics} />}
|
||||
{jobMetrics && (
|
||||
<>
|
||||
<Heading size="h4" title="Throughput" />
|
||||
<Chart series={jobMetrics.throughput} />
|
||||
<Heading size="h4" title="Timing" />
|
||||
<Chart
|
||||
series={jobMetrics.timing}
|
||||
formatter={(value) => `${value.toLocaleString()}ms`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{failed.length && <>
|
||||
<Heading size="h4" title="Failed Jobs" />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue