Skip to content

Commit

Permalink
feat(server): support query instant metrics (#1502)
Browse files Browse the repository at this point in the history
* feat(server): support query instant metrics

* chore(sever): don't return 404 when avatar not found

* test(server): monitor test

* Delete laf.code-workspace
  • Loading branch information
0fatal authored Sep 1, 2023
1 parent c444288 commit b12527d
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 79 deletions.
41 changes: 40 additions & 1 deletion e2e/2-monitor/00-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('query monitor metrics normally', () => {
})

test(
'query monitor metrics',
'query monitor metrics range',
async () => {
const metrics = [
'cpuUsage',
Expand All @@ -23,6 +23,7 @@ describe('query monitor metrics normally', () => {
const query = {
q: metrics,
step: 300,
type: 'range',
}

const res = await api.get(`/v1/monitor/${appid}/metrics`, {
Expand All @@ -47,6 +48,44 @@ describe('query monitor metrics normally', () => {
},
10 * 1000,
)

test(
'query monitor metrics instant',
async () => {
const metrics = [
'cpuUsage',
'memoryUsage',
'storageUsage',
'databaseUsage',
]

const query = {
q: metrics,
step: 300,
type: 'instant',
}

const res = await api.get(`/v1/monitor/${appid}/metrics`, {
params: query,
headers: { Authorization: `Bearer ${token}` },
})

expect(res.status).toBe(200)

const data = res.data?.data
expect(Object.keys(data)).toEqual(expect.arrayContaining(metrics))

Object.values(data).forEach((v: []) => {
expect(Array.isArray(v)).toBeTruthy()

expect([
['values', 'metric'],
['value', 'metric'],
]).toContainEqual(expect.arrayContaining(Object.keys(v)))
})
},
10 * 1000,
)
})

describe('query monitor metrics with invalid inputs', () => {
Expand Down
18 changes: 17 additions & 1 deletion server/src/monitor/dto/query-metrics.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsArray, IsEnum, IsNumber, Max, Min } from 'class-validator'
import {
IsArray,
IsEnum,
IsIn,
IsNumber,
IsString,
Max,
Min,
} from 'class-validator'
import { MonitorMetric } from '../monitor.service'
import { Transform } from 'class-transformer'

Expand All @@ -19,4 +27,12 @@ export class QueryMetricsDto {
@Max(3600)
@Transform(({ value }) => Number(value))
step: number

@ApiProperty({
description: 'Query type',
enum: ['range', 'instant'],
})
@IsString()
@IsIn(['range', 'instant'])
type: 'range' | 'instant'
}
14 changes: 10 additions & 4 deletions server/src/monitor/monitor.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ export class MonitorController {
@UseGuards(JwtAuthGuard, ApplicationAuthGuard)
@Get(':appid/metrics')
async getData(@Param('appid') appid: string, @Query() dto: QueryMetricsDto) {
const { q: metrics, step } = dto
const { q: metrics, step, type } = dto
const isRange = type === 'range'

const res = await this.monitorService.getData(appid, metrics, {
step,
})
const res = await this.monitorService.getData(
appid,
metrics,
{
step,
},
isRange,
)

return ResponseUtil.ok(res)
}
Expand Down
113 changes: 45 additions & 68 deletions server/src/monitor/monitor.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable prettier/prettier */
import { HttpService } from '@nestjs/axios'
import { Injectable, Logger } from '@nestjs/common'
import { ApplicationService } from 'src/application/application.service'
Expand All @@ -14,72 +13,48 @@ const requestConfig = {

export const getQuery =
({ rateAccuracy }: { rateAccuracy: string }) =>
(opts: Record<string, unknown>, metric: MonitorMetric) => {
switch (metric) {
case MonitorMetric.cpuUsage:
return {
instant: false,
query: `sum(rate(container_cpu_usage_seconds_total{image!="",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`,
}
// case MonitorMetric.cpuRequests:
// return {
// instant: false,
// query: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
// }
// case MonitorMetric.cpuLimits:
// return {
// instant: false,
// query: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
// }
case MonitorMetric.memoryUsage:
return {
instant: false,
query: `sum(container_memory_working_set_bytes{image!="",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
}
// case MonitorMetric.memoryRequests:
// return {
// instant: false,
// query: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
// }
// case MonitorMetric.memoryLimits:
// return {
// instant: false,
// query: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
// }
// case MonitorMetric.networkReceive:
// return {
// instant: false,
// query: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`,
// }
// case MonitorMetric.networkTransmit:
// return {
// instant: false,
// query: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`,
// }
case MonitorMetric.databaseUsage:
return {
instant: true,
query: `sum(mongodb_dbstats_dataSize{database="${opts.appid}"})`,
}
case MonitorMetric.storageUsage:
return {
instant: true,
query: `sum(minio_bucket_usage_total_bytes{bucket=~"${opts.appid}.+"})`,
}
}
(opts: Record<string, unknown>, metric: MonitorMetric) => {
switch (metric) {
case MonitorMetric.cpuUsage:
return {
instant: false,
query: `sum(rate(container_cpu_usage_seconds_total{image!="",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`,
}
case MonitorMetric.memoryUsage:
return {
instant: false,
query: `sum(container_memory_working_set_bytes{image!="",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
}
case MonitorMetric.networkReceive:
return {
instant: false,
query: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`,
}
case MonitorMetric.networkTransmit:
return {
instant: false,
query: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`,
}
case MonitorMetric.databaseUsage:
return {
instant: true,
query: `sum(mongodb_dbstats_dataSize{database="${opts.appid}"})`,
}
case MonitorMetric.storageUsage:
return {
instant: true,
query: `sum(minio_bucket_usage_total_bytes{bucket=~"${opts.appid}.+"})`,
}
}
}

export enum MonitorMetric {
cpuUsage = 'cpuUsage', //
// cpuRequests = 'cpuRequests',
// cpuLimits = 'cpuLimits',
memoryUsage = 'memoryUsage', //
// memoryRequests = 'memoryRequests',
// memoryLimits = 'memoryLimits',
// networkReceive = 'networkReceive', //
// networkTransmit = 'networkTransmit', //
databaseUsage = 'databaseUsage', //
storageUsage = 'storageUsage', //
cpuUsage = 'cpuUsage',
memoryUsage = 'memoryUsage',
networkReceive = 'networkReceive',
networkTransmit = 'networkTransmit',
databaseUsage = 'databaseUsage',
storageUsage = 'storageUsage',
}

@Injectable()
Expand All @@ -88,13 +63,14 @@ export class MonitorService {
private readonly httpService: HttpService,
private readonly applicationService: ApplicationService,
private readonly regionService: RegionService,
) { }
) {}
private readonly logger = new Logger(MonitorService.name)

async getData(
appid: string,
metrics: MonitorMetric[],
queryParams: Record<string, number | string>,
isRange: boolean,
) {
const region = await this.regionService.findByAppId(appid)
const conf = region?.prometheusConf
Expand All @@ -115,9 +91,10 @@ export class MonitorService {
rateAccuracy: requestConfig.rateAccuracy,
})(opts, metric)

data[metric] = instant
? await this.query(conf, query)
: await this.queryRange(conf, query, queryParams)
data[metric] =
instant || !isRange
? await this.query(conf, query)
: await this.queryRange(conf, query, queryParams)
})

await Promise.all(res)
Expand Down
5 changes: 0 additions & 5 deletions server/src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Param,
Post,
Req,
Expand Down Expand Up @@ -98,9 +96,6 @@ export class UserController {
}

const avatar = await this.userService.getAvatarData(new ObjectId(uid))
if (!avatar) {
throw new HttpException('avatar not found', HttpStatus.NOT_FOUND)
}

res.set('Content-Type', 'image/webp')
res.send(avatar)
Expand Down

0 comments on commit b12527d

Please sign in to comment.