diff --git a/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/api/IServiceResourceStatController.kt b/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/api/IServiceResourceStatController.kt new file mode 100644 index 000000000..8fd7ed732 --- /dev/null +++ b/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/api/IServiceResourceStatController.kt @@ -0,0 +1,38 @@ +package com.tencent.devops.turbo.api + +import com.tencent.devops.api.pojo.Response +import com.tencent.devops.common.api.annotation.ServiceInterface +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.turbo.vo.ProjectResourceUsageVO +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam + +@Api(tags = ["SERVER_RESOURCES"], description = "资源统计查询接口") +@RequestMapping("/service/resources") +@FeignClient(name = "turbo", contextId = "IServiceResourceStatController") +@ServiceInterface("turbo-new") +interface IServiceResourceStatController { + + @ApiOperation("获取项目使用服务器资源统计") + @GetMapping("/getSummary", produces = [MediaType.APPLICATION_JSON_VALUE]) + fun getSummary( + @ApiParam("起始统计日期") + @RequestParam("startDate") + startDate: String?, + @ApiParam("截止统计日期") + @RequestParam("endDate") + endDate: String?, + @ApiParam(value = "页数") + @RequestParam(value = "pageNum") + pageNum: Int?, + @ApiParam(value = "每页多少条") + @RequestParam("pageSize") + pageSize: Int?, + ): Response> +} diff --git a/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/api/IServiceTurboController.kt b/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/api/IServiceTurboController.kt index c68a30ab6..5e1d99141 100644 --- a/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/api/IServiceTurboController.kt +++ b/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/api/IServiceTurboController.kt @@ -3,7 +3,9 @@ package com.tencent.devops.turbo.api import com.tencent.devops.api.pojo.Response import com.tencent.devops.common.api.annotation.ServiceInterface import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.turbo.pojo.TurboPlanModel import com.tencent.devops.turbo.pojo.TurboRecordModel +import com.tencent.devops.turbo.validate.TurboPlanGroup import com.tencent.devops.turbo.validate.TurboRecordGroup import com.tencent.devops.turbo.vo.TurboPlanDetailVO import com.tencent.devops.turbo.vo.TurboPlanStatRowVO @@ -107,5 +109,24 @@ interface IServiceTurboController { @ApiParam(value = "用户信息", required = true) @PathVariable("userId") userId: String - ): Response + ): Response + + @ApiOperation("新增加速方案") + @PostMapping( + "/projectId/{projectId}/{userId}/addTurboPlan", + consumes = [MediaType.APPLICATION_JSON_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + fun addNewTurboPlan( + @ApiParam(value = "蓝盾项目id", required = true) + @PathVariable("projectId") + projectId: String, + @ApiParam(value = "用户信息", required = true) + @PathVariable("userId") + userId: String, + @ApiParam(value = "新增加速方案请求数据信息", required = true) + @RequestBody + @Validated(TurboPlanGroup.Create::class) + turboPlanModel: TurboPlanModel + ): Response } diff --git a/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/vo/ProjectResourceUsageVO.kt b/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/vo/ProjectResourceUsageVO.kt new file mode 100644 index 000000000..3f95899c6 --- /dev/null +++ b/src/backend/turbo/api-turbo/src/main/kotlin/com/tencent/devops/turbo/vo/ProjectResourceUsageVO.kt @@ -0,0 +1,41 @@ +package com.tencent.devops.turbo.vo + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("机器资源使用统计") +data class ProjectResourceUsageVO( + + @ApiModelProperty("项目id") + val projectId: String, + + @ApiModelProperty("项目名称") + val projectName: String?, + + @ApiModelProperty("加速模式") + var engineCode: String?, + + @ApiModelProperty("加速时长*cpu核数(单位分钟*核)") + var totalTimeWithCpu: Double?, + + @ApiModelProperty("项目所属运营产品id") + var productId: Int?, + + @ApiModelProperty("BG名称") + var bgName: String?, + + @ApiModelProperty("BG id") + var bgId: Int?, + + @ApiModelProperty("部门名称") + var deptName: String?, + + @ApiModelProperty("部门id") + var deptId: Int?, + + @ApiModelProperty("中心名称") + var centerName: String?, + + @ApiModelProperty("中心id") + var centerId: Int?, +) diff --git a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/controller/ServiceResourceStatController.kt b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/controller/ServiceResourceStatController.kt new file mode 100644 index 000000000..38774fcfd --- /dev/null +++ b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/controller/ServiceResourceStatController.kt @@ -0,0 +1,24 @@ +package com.tencent.devops.turbo.controller + +import com.tencent.devops.api.pojo.Response +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.turbo.api.IServiceResourceStatController +import com.tencent.devops.turbo.service.ProjectResourcesService +import com.tencent.devops.turbo.vo.ProjectResourceUsageVO +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.web.bind.annotation.RestController + +@RestController +class ServiceResourceStatController @Autowired constructor( + private val projectResourcesService: ProjectResourcesService +) : IServiceResourceStatController { + + override fun getSummary( + startDate: String?, + endDate: String?, + pageNum: Int?, + pageSize: Int? + ): Response> { + return Response.success(projectResourcesService.querySummary(startDate, endDate, pageNum, pageSize)) + } +} diff --git a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/controller/ServiceTurboController.kt b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/controller/ServiceTurboController.kt index cc02e1dfc..4d11827e3 100644 --- a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/controller/ServiceTurboController.kt +++ b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/controller/ServiceTurboController.kt @@ -6,6 +6,7 @@ import com.tencent.devops.common.api.exception.code.IS_NOT_ADMIN_MEMBER import com.tencent.devops.common.api.pojo.Page import com.tencent.devops.common.util.constants.NO_ADMIN_MEMBER_MESSAGE import com.tencent.devops.turbo.api.IServiceTurboController +import com.tencent.devops.turbo.pojo.TurboPlanModel import com.tencent.devops.turbo.pojo.TurboRecordModel import com.tencent.devops.turbo.service.TurboAuthService import com.tencent.devops.turbo.service.TurboPlanService @@ -68,4 +69,12 @@ class ServiceTurboController @Autowired constructor( } return Response.success(turboPlanService.getTurboPlanDetailByPlanId(planId)) } + + override fun addNewTurboPlan(projectId: String, userId: String, turboPlanModel: TurboPlanModel): Response { + // 判断是否是管理员 + if (!turboAuthService.getAuthResult(projectId, userId)) { + throw TurboException(errorCode = IS_NOT_ADMIN_MEMBER, errorMessage = NO_ADMIN_MEMBER_MESSAGE) + } + return Response.success(turboPlanService.addNewTurboPlan(turboPlanModel, userId)) + } } diff --git a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/dao/mongotemplate/TbsDaySummaryDao.kt b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/dao/mongotemplate/TbsDaySummaryDao.kt new file mode 100644 index 000000000..54ca8fd5a --- /dev/null +++ b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/dao/mongotemplate/TbsDaySummaryDao.kt @@ -0,0 +1,63 @@ +package com.tencent.devops.turbo.dao.mongotemplate + +import com.tencent.devops.turbo.model.TTbsDaySummaryEntity +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.domain.Sort +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.aggregation.Aggregation +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.stereotype.Repository + +@Repository +class TbsDaySummaryDao @Autowired constructor( + private val mongoTemplate: MongoTemplate +) { + companion object { + private val logger = LoggerFactory.getLogger(this::class.java) + private const val COLLECTION_NAME = "t_tbs_day_summary_entity" + } + + /** + * 根据日期查询机器资源统计 + */ + fun findByDay( + startDate: String, + endDate: String, + filterPlanIdNin: Set, + filterProjectIdNin: Set, + pageNum: Int, + pageSize: Int + ): List { + logger.info("findByDay startDate: $startDate, endDate: $endDate, filterPlanIdNin: $filterPlanIdNin") + + val criteria = Criteria.where("day").gte(startDate).lte(endDate) + .and("user").`is`(null) + + // 过滤方案id + filterPlanIdNin.takeIf { it.isNotEmpty() }.let { criteria.and("plan_id").nin(filterPlanIdNin) } + // 过滤项目id + filterProjectIdNin.takeIf { it.isNotEmpty() }.let { criteria.and("project_id").nin(filterPlanIdNin) } + + val match = Aggregation.match(criteria) + val sort = Aggregation.sort(Sort.Direction.DESC, "day") + val group = Aggregation.group("project_id", "engine_code") + .sum("total_time_with_cpu").`as`("total_time_with_cpu") + .first("project_id").`as`("project_id") + .first("project_name").`as`("project_name") + .first("engine_code").`as`("engine_code") + .first("product_id").`as`("product_id") + .first("bg_name").`as`("bg_name") + .first("dept_name").`as`("dept_name") + .first("center_name").`as`("center_name") + .first("bg_id").`as`("bg_id") + .first("dept_id").`as`("dept_id") + .first("center_id").`as`("center_id") + + val skip = Aggregation.skip((pageNum * pageSize).toLong()) + val limit = Aggregation.limit(pageSize.toLong()) + + val aggregation = Aggregation.newAggregation(match, sort, group, skip, limit) + return mongoTemplate.aggregate(aggregation, COLLECTION_NAME, TTbsDaySummaryEntity::class.java).mappedResults + } +} diff --git a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/dao/repository/BaseDataRepository.kt b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/dao/repository/BaseDataRepository.kt new file mode 100644 index 000000000..017efc303 --- /dev/null +++ b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/dao/repository/BaseDataRepository.kt @@ -0,0 +1,14 @@ +package com.tencent.devops.turbo.dao.repository + +import com.tencent.devops.turbo.model.BaseDataEntity +import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.stereotype.Repository + +@Repository +interface BaseDataRepository : MongoRepository { + + /** + * 根据参数标识代码查询 + */ + fun findFirstByParamCode(paramCode: String): BaseDataEntity? +} diff --git a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/job/TBSDaySummaryJob.kt b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/job/TBSDaySummaryJob.kt index 98f65bb2f..907e5cae6 100644 --- a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/job/TBSDaySummaryJob.kt +++ b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/job/TBSDaySummaryJob.kt @@ -147,6 +147,7 @@ class TBSDaySummaryJob @Autowired constructor( day = summary.day, engineCode = engineCode, planId = planId, + // user字段没有值即为disttask的统计数据,有值的是ue的用户数据 user = if (engineCode == "disttask-ue4") summary.user else null, totalTime = summary.totalTime, totalTimeWithCpu = summary.totalTimeWithCpu, diff --git a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/sdk/TBSSdkApi.kt b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/sdk/TBSSdkApi.kt index 658fa08e6..7e504acb2 100644 --- a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/sdk/TBSSdkApi.kt +++ b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/sdk/TBSSdkApi.kt @@ -223,6 +223,8 @@ object TBSSdkApi { engineCode = "disttask", resourceName = "summary", queryParam = queryParam, + // disttask的统计数据涵盖cc和ue4,因此disttask+distcc就是全量统计 + // 该groupbyuser接口的统计信息不包含cc,只是为了方便查看ue的用户维度数据,是disttask的子集 customPath = if (engineCode.contains("ue4"))"/groupbyuser/scene/ue4" else null ) } else { diff --git a/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/service/ProjectResourcesService.kt b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/service/ProjectResourcesService.kt new file mode 100644 index 000000000..b293e3a0e --- /dev/null +++ b/src/backend/turbo/biz-turbo/src/main/kotlin/com/tencent/devops/turbo/service/ProjectResourcesService.kt @@ -0,0 +1,77 @@ +package com.tencent.devops.turbo.service + +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.common.util.MathUtil +import com.tencent.devops.common.util.constants.BASE_EXCLUDED_PLAN_ID_LIST +import com.tencent.devops.common.util.constants.BASE_EXCLUDED_PROJECT_ID_LIST +import com.tencent.devops.turbo.dao.mongotemplate.TbsDaySummaryDao +import com.tencent.devops.turbo.dao.repository.BaseDataRepository +import com.tencent.devops.turbo.vo.ProjectResourceUsageVO +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service +import java.time.LocalDate + +@Service +class ProjectResourcesService @Autowired constructor( + private val tbsDaySummaryDao: TbsDaySummaryDao, + private val baseDataRepository: BaseDataRepository +) { + + companion object { + private val logger = LoggerFactory.getLogger(this::class.java) + } + + /** + * 不传日期时默认统计上个月所有天数的数据 + */ + fun querySummary( + startDate: String?, + endDate: String?, + pageNum: Int?, + pageSize: Int? + ): Page { + val page = pageNum?.takeIf { it > 0 }?.let { it - 1 } ?: 0 + val pageSizeNum = pageSize?.coerceAtMost(10000) ?: 100 + + // 获取需要过滤掉的方案id集合 + val baseDataEntity = baseDataRepository.findFirstByParamCode(BASE_EXCLUDED_PLAN_ID_LIST) + val filterPlanIds = baseDataEntity?.paramValue?.split(",")?.toSet() ?: emptySet() + + // 获取需要过滤掉的项目id集合 + val projectExcludedEntity = baseDataRepository.findFirstByParamCode(BASE_EXCLUDED_PROJECT_ID_LIST) + val filterProjectIds = projectExcludedEntity?.paramValue?.split(",")?.toSet() ?: emptySet() + + val today = LocalDate.now() + + val summaryEntityList = tbsDaySummaryDao.findByDay( + startDate = startDate ?: today.minusMonths(1).withDayOfMonth(1).toString(), + endDate = endDate ?: today.withDayOfMonth(1).minusDays(1).toString(), + filterPlanIdNin = filterPlanIds, + filterProjectIdNin = filterProjectIds, + pageNum = page, + pageSize = pageSizeNum + ) + logger.info("summaryEntityList size: ${summaryEntityList.size}") + + val resultList = summaryEntityList.map { + with(it) { + ProjectResourceUsageVO( + projectId = projectId!!, + projectName = projectName, + engineCode = engineCode, + // 秒转分钟 + totalTimeWithCpu = MathUtil.secondsToMinutes(totalTimeWithCpu!!).toDouble(), + productId = productId, + bgName = bgName, + bgId = bgId, + deptName = deptName, + deptId = deptId, + centerName = centerName, + centerId = centerId + ) + } + } + return Page(page + 1, pageSizeNum, 0, resultList) + } +} diff --git a/src/backend/turbo/common-turbo/common-turbo-api/src/main/kotlin/com/tencent/devops/common/api/exception/code/TurboMessageCode.kt b/src/backend/turbo/common-turbo/common-turbo-api/src/main/kotlin/com/tencent/devops/common/api/exception/code/TurboMessageCode.kt index ce18933c7..9a536f985 100644 --- a/src/backend/turbo/common-turbo/common-turbo-api/src/main/kotlin/com/tencent/devops/common/api/exception/code/TurboMessageCode.kt +++ b/src/backend/turbo/common-turbo/common-turbo-api/src/main/kotlin/com/tencent/devops/common/api/exception/code/TurboMessageCode.kt @@ -6,3 +6,4 @@ const val TURBO_NO_DATA_FOUND = "2121003" // 没有查询到对应数据 const val TURBO_PARAM_INVALID = "2121004" // 参数异常 const val IS_NOT_ADMIN_MEMBER = "2300017" // 非管理员,操作失败 const val SUB_CLASS_CHECK_ERROR = "2121006" // 子类检查错误 +const val PERMISSION_DENIED = "2121007" // {0}无权限 diff --git a/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/MathUtil.kt b/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/MathUtil.kt index e654d24ab..f1a3f4bf8 100644 --- a/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/MathUtil.kt +++ b/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/MathUtil.kt @@ -1,8 +1,19 @@ package com.tencent.devops.common.util +import java.text.DecimalFormat + object MathUtil { fun roundToTwoDigits(input: Double): String { return String.format("%.2f", input) } + + /** + * 秒转分钟 + */ + fun secondsToMinutes(seconds: Double): String { + val minutes = seconds.toDouble() / 60 + val df = DecimalFormat("#.##") + return df.format(minutes) + } } diff --git a/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/constants/TurboConstants.kt b/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/constants/TurboConstants.kt index f76cae07d..77f7db81c 100644 --- a/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/constants/TurboConstants.kt +++ b/src/backend/turbo/common-turbo/common-turbo-util/src/main/kotlin/com/tencent/devops/common/util/constants/TurboConstants.kt @@ -9,3 +9,13 @@ const val SYSTEM_ADMIN = "Turbo" * 无权限错误提示信息 */ const val NO_ADMIN_MEMBER_MESSAGE = "无权限,请先加入项目!" + +/** + * 统计数据需排除的plan id + */ +const val BASE_EXCLUDED_PLAN_ID_LIST = "EXCLUDED_PLAN_ID_LIST" + +/** + * 统计数据需排除的project id + */ +const val BASE_EXCLUDED_PROJECT_ID_LIST = "EXCLUDED_PROJECT_ID_LIST" diff --git a/src/backend/turbo/model-turbo/src/main/kotlin/com/tencent/devops/turbo/model/BaseDataEntity.kt b/src/backend/turbo/model-turbo/src/main/kotlin/com/tencent/devops/turbo/model/BaseDataEntity.kt new file mode 100644 index 000000000..62b5504ad --- /dev/null +++ b/src/backend/turbo/model-turbo/src/main/kotlin/com/tencent/devops/turbo/model/BaseDataEntity.kt @@ -0,0 +1,44 @@ +package com.tencent.devops.turbo.model + +import org.springframework.data.mongodb.core.index.Indexed +import org.springframework.data.mongodb.core.mapping.Document +import org.springframework.data.mongodb.core.mapping.Field +import java.time.LocalDateTime + + +@Document(collection = "t_base_data") +data class BaseDataEntity( + /** + * 唯一标识 + */ + @Indexed + @Field("param_code") + val paramCode: String, + + /** + * 参数名称、描述等信息 + */ + @Field("param_name") + val paramName: String, + + /** + * 参数值 + */ + @Field("param_value") + val paramValue: String, + + @Field("param_type") + val paramType: String? = null, + + @Field("param_status") + val paramStatus: String? = null, + + @Field("updated_by") + var updatedBy: String, + @Field("updated_date") + var updatedDate: LocalDateTime, + @Field("created_by") + var createdBy: String, + @Field("created_date") + var createdDate: LocalDateTime +) diff --git a/src/backend/turbo/model-turbo/src/main/kotlin/com/tencent/devops/turbo/model/TTbsDaySummaryEntity.kt b/src/backend/turbo/model-turbo/src/main/kotlin/com/tencent/devops/turbo/model/TTbsDaySummaryEntity.kt index 55f263739..73acb6948 100644 --- a/src/backend/turbo/model-turbo/src/main/kotlin/com/tencent/devops/turbo/model/TTbsDaySummaryEntity.kt +++ b/src/backend/turbo/model-turbo/src/main/kotlin/com/tencent/devops/turbo/model/TTbsDaySummaryEntity.kt @@ -121,5 +121,5 @@ data class TTbsDaySummaryEntity( * 本entity创建时间 */ @Field("created_date") - var createdDate: LocalDateTime + var createdDate: LocalDateTime? )