Skip to content

Commit

Permalink
Adds per-job timing metrics (#605)
Browse files Browse the repository at this point in the history
  • Loading branch information
pushchris authored Jan 5, 2025
1 parent 1c26294 commit a5a82c8
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 30 deletions.
2 changes: 1 addition & 1 deletion apps/platform/src/config/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}))
}
Expand Down
40 changes: 30 additions & 10 deletions apps/platform/src/organizations/OrganizationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
7 changes: 5 additions & 2 deletions apps/platform/src/queue/Queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions apps/ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
1 change: 1 addition & 0 deletions apps/ui/public/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
47 changes: 31 additions & 16 deletions apps/ui/src/views/organization/Performance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -34,15 +38,15 @@ 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={{
backgroundColor: 'var(--color-background)',
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) => (
Expand All @@ -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)
Expand All @@ -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>()
Expand Down Expand Up @@ -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])
Expand All @@ -135,23 +139,34 @@ 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>
</TileGrid>
<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}
value={currentJob}
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" />
Expand Down

0 comments on commit a5a82c8

Please sign in to comment.