diff --git a/server/package-lock.json b/server/package-lock.json index 1a9e9f7748..97d086bf24 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -46,6 +46,7 @@ "nodemailer": "^6.9.4", "npm-package-arg": "^10.1.0", "passport-jwt": "^4.0.0", + "prometheus-query": "^3.3.2", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", @@ -6830,6 +6831,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -12894,6 +12903,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/prometheus-query": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prometheus-query/-/prometheus-query-3.3.2.tgz", + "integrity": "sha512-xNgDjDdueiTkA3sY9CJPLa4OgGGoH1ug+TPq3aYY6hnhN7nq1ykP9UmciWnMTYCc178eQ3yesG4HFsRg72CgOg==", + "dependencies": { + "axios": "^0.26.1" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -21214,6 +21231,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, "b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -25836,6 +25861,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "prometheus-query": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prometheus-query/-/prometheus-query-3.3.2.tgz", + "integrity": "sha512-xNgDjDdueiTkA3sY9CJPLa4OgGGoH1ug+TPq3aYY6hnhN7nq1ykP9UmciWnMTYCc178eQ3yesG4HFsRg72CgOg==", + "requires": { + "axios": "^0.26.1" + } + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/server/package.json b/server/package.json index 6f0edd29b0..1aa689b9cb 100644 --- a/server/package.json +++ b/server/package.json @@ -61,6 +61,7 @@ "nodemailer": "^6.9.4", "npm-package-arg": "^10.1.0", "passport-jwt": "^4.0.0", + "prometheus-query": "^3.3.2", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", diff --git a/server/src/billing/billing-creation-task.service.ts b/server/src/billing/billing-creation-task.service.ts index 5bd0dd6ef5..e7143f3367 100644 --- a/server/src/billing/billing-creation-task.service.ts +++ b/server/src/billing/billing-creation-task.service.ts @@ -120,16 +120,7 @@ export class BillingCreationTaskService { return } - // lookup metering data - const meteringData = await MeteringDatabase.db - .collection('metering') - .find({ category: appid, time: nextMeteringTime }, { sort: { time: 1 } }) - .toArray() - - if (meteringData.length === 0) { - this.logger.warn(`No metering data found for application: ${appid}`) - return - } + const meteringData = await this.billing.getMeteringData(app) // get application bundle const bundle = await this.bundleService.findOne(appid) @@ -195,21 +186,14 @@ export class BillingCreationTaskService { private buildCalculatePriceInput( app: Application, - meteringData: any[], + meteringData: any, bundle: ApplicationBundle, ) { const dto = new CalculatePriceDto() dto.regionId = app.regionId.toString() - dto.cpu = 0 - dto.memory = 0 - dto.storageCapacity = 0 - dto.databaseCapacity = 0 - - for (const item of meteringData) { - if (item.property === 'cpu') dto.cpu = item.value - if (item.property === 'memory') dto.memory = item.value - } + dto.cpu = meteringData.cpu + dto.memory = meteringData.memory dto.storageCapacity = bundle.resource.storageCapacity dto.databaseCapacity = bundle.resource.databaseCapacity diff --git a/server/src/billing/billing.service.ts b/server/src/billing/billing.service.ts index 45f57689d3..41336337b2 100644 --- a/server/src/billing/billing.service.ts +++ b/server/src/billing/billing.service.ts @@ -8,12 +8,18 @@ import * as assert from 'assert' import { ApplicationBilling } from './entities/application-billing' import { CalculatePriceDto } from './dto/calculate-price.dto' import { BillingQuery } from './interface/billing-query.interface' +import { PrometheusDriver } from 'prometheus-query' +import { Application } from 'src/application/entities/application' +import { RegionService } from 'src/region/region.service' @Injectable() export class BillingService { private readonly db = SystemDatabase.db - constructor(private readonly resource: ResourceService) {} + constructor( + private readonly resource: ResourceService, + private readonly region: RegionService, + ) {} async query(userId: ObjectId, condition?: BillingQuery) { const query = { createdBy: userId } @@ -213,4 +219,33 @@ export class BillingService { total: totalPrice.toNumber(), } } + + async getMeteringData(app: Application) { + const region = await this.region.findOne(app.regionId) + + const prom = new PrometheusDriver({ + endpoint: region.prometheusConf.apiUrl, + }) + + const cpuTask = prom + .instantQuery( + `sum(max_over_time(laf_runtime_cpu_limit{container!="",appid="${app.appid}"}[1h])) by (appid)`, + ) + .then((res) => res.result[0]) + .then((res) => Number(res.value.value)) + + const memoryTask = prom + .instantQuery( + `sum(max_over_time(laf_runtime_memory_limit{container!="",appid="${app.appid}"}[1h])) by (appid)`, + ) + .then((res) => res.result[0]) + .then((res) => Number(res.value.value)) + + const [cpu, memory] = await Promise.all([cpuTask, memoryTask]) + + return { + cpu, + memory, + } + } } diff --git a/server/src/monitor/monitor.service.ts b/server/src/monitor/monitor.service.ts index e46582e074..38fc5a784e 100644 --- a/server/src/monitor/monitor.service.ts +++ b/server/src/monitor/monitor.service.ts @@ -1,6 +1,5 @@ import { HttpService } from '@nestjs/axios' import { Injectable, Logger } from '@nestjs/common' -import { ApplicationService } from 'src/application/application.service' import { PrometheusConf } from 'src/region/entities/region' import { RegionService } from 'src/region/region.service' import { GetApplicationNamespace } from 'src/utils/getter' @@ -18,12 +17,12 @@ export const getQuery = 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})`, + query: `sum(laf_runtime_cpu{container!="",appid="${opts.appid}"}) 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})`, + query: `sum(laf_runtime_memory{container!="",appid="${opts.appid}"}) by (${opts.selector})`, } case MonitorMetric.networkReceive: return { @@ -61,7 +60,6 @@ export enum MonitorMetric { export class MonitorService { constructor( private readonly httpService: HttpService, - private readonly applicationService: ApplicationService, private readonly regionService: RegionService, ) {} private readonly logger = new Logger(MonitorService.name)