这里只是简单的使用Java获取到Prometheus监控数据的资源监控,并不做深入解析,实际上是我也不会,只是记录一下怎么使用。本篇文章局限性很高!!!
/**
* 容器资源监控
*/
@Data
@ApiModel("容器资源监控")
public class DockerResourceMonitorVo {
/**
* 实例编码
*/
@NotNull(message = "容器实例编码不能为空")
@ApiModelProperty("容器实例编码")
private String code;
/**
* 开始时间
*/
@ApiModelProperty("开始时间")
private String startDate;
/**
* 结束时间
*/
@ApiModelProperty("结束时间")
private String endDate;
/**
* 手工输入时间
*/
@ApiModelProperty("手工输入时间")
private Integer handWriteTime;
}
public Result<JSONObject> getAllResourceMonitor(DockerResourceMonitorVo body) {
try {
//获取开始/结束时间的毫秒数
String startDate = "";
String endDate = "";
//时间为空,则使用默认时间
if (StringUtils.isEmpty(body.getStartDate()) || StringUtils.isEmpty(body.getEndDate())) {
//则默认将当前时间往前推15分钟作为开始时间
Integer timeCycle = 15;
if (null != body.getHandWriteTime()) { //用户手动输入时间,由前端转成分钟传入
timeCycle = body.getHandWriteTime();
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MINUTE, -timeCycle);
//设置开始时间毫秒数
startDate = String.valueOf(calendar.getTimeInMillis());
//结束时间为空,则使用当前时间
endDate = String.valueOf(new Date().getTime());
} else {
startDate = String.valueOf(DateUtils.yyyyMMddHHmmssWithWhiffletreeToDate(body.getStartDate()).getTime());
endDate = String.valueOf(DateUtils.yyyyMMddHHmmssWithWhiffletreeToDate(body.getEndDate()).getTime());
}
//计算结束时间-开始时间的差值
long minute = (Long.parseLong(endDate) - Long.parseLong(startDate)) / 1000 / 60;
//获取步长/采集周期
String step = getStep(minute);
//设置请求参数开始时间/结束时间的格式
// String startTime = StringUtils.substring(startDate, 0, startDate.length() - 3) + ".006";
// String endTime = StringUtils.substring(endDate, 0, endDate.length() - 3) + ".006";
String startTime = startDate.substring(0, startDate.length() - 3) + ".006";
String endTime = endDate.substring(0, endDate.length() - 3) + ".006";
JSONObject resourceMonitorData = new JSONObject(true);
//GPU利用率
JSONObject gpuRate = getGpuRate(body.getCode(), startTime, endTime, step);
resourceMonitorData.put("gpuRate", gpuRate);
//GPU显存使用量
JSONObject gpuMemoryRate = getGpuMemoryRate(body.getCode(), startTime, endTime, step);
resourceMonitorData.put("gpuMemoryRate", gpuMemoryRate);
//获取CPU利用率
JSONObject cpuRate = getCpuRate(body.getCode(), startTime, endTime, step);
resourceMonitorData.put("cpuRate", cpuRate);
//内存使用量
JSONObject memoryRate = getMemoryRate(body.getCode(), startTime, endTime, step);
resourceMonitorData.put("memoryRate", memoryRate);
return Result.ok(resourceMonitorData);
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException("获取资源监控失败!");
}
}
/**
* 根据时间差获取步长/采集周期
*
* @param minute
* @return
*/
private String getStep(Long minute) {
if (minute <= 5) {//5分钟以内
return "1";
} else if (minute > 5 && minute <= 60) {//大于5分钟小于1小时
return "1";
} else if (minute > 60 && minute <= (60 * 24)) {//1440-->大于1小时小于24小时
return "30";
} else if (minute > (60 * 24) && minute <= (60 * 24 * 7)) {//10080-->大于24小时小于一周
return "300";
} else if (minute > (60 * 24 * 7) && minute <= (60 * 24 * 7 * 4)) {//40320-->大于1周小于4周
return "1800";
} else {//大于4周
return "43200";
}
}
/**
* 获取cpu利用率 单位:%
*
* @param name 容器实例名
* @param startTime 开始时间字符串
* @param endTime 结束时间字符串
* @param step 步长/采集周期
*/
public JSONObject getCpuRate(String name, String startTime, String endTime, String step) {
//拼接url monitorUrl 是我在配置文件中定义的==>http://ip:9090/api/v1/query_range
String url = monitorUrl + "?query={query}&start={startTime}&end={endTime}&step={step}";
//设置参数
Map<String, String> param = new HashMap();
String query = "sum(rate(container_cpu_user_seconds_total{name='" + name + "'}[5m])) by (name) * 100";
param.put("query", query);
param.put("startTime", startTime);
param.put("endTime", endTime);
param.put("step", step);
//发起请求
JSONObject resultJson = sendResourceMonitor(url, param);
//解析结果集
return analyticResult("CPU利用率", resultJson, step, startTime);
}
/**
* 获取内存使用量 单位:GB
*
* @param name 容器实例名
* @param startTime 开始时间字符串
* @param endTime 结束时间字符串
* @param step 步长/采集周期
*/
public JSONObject getMemoryRate(String name, String startTime, String endTime, String step) {
//拼接url monitorUrl 是我在配置文件中定义的==>http://ip:9090/api/v1/query_range
String url = monitorUrl + "?query={query}&start={startTime}&end={endTime}&step={step}";
//设置参数
Map<String, String> param = new HashMap();
String query = "sum(container_memory_rss{name='" + name + "'}) by (name) /1024/1024/1024";
param.put("query", query);
param.put("startTime", startTime);
param.put("endTime", endTime);
param.put("step", step + "");
//发起请求
JSONObject resultJson = sendResourceMonitor(url, param);
//解析结果集
return analyticResult("内存使用量", resultJson, step, startTime);
}
/**
* 获取GPU利用率 单位:%
*
* @param name 容器实例名
* @param startTime 开始时间字符串
* @param endTime 结束时间字符串
* @param step 步长/采集周期
*/
public JSONObject getGpuRate(String name, String startTime, String endTime, String step) {
//拼接url monitorUrl 是我在配置文件中定义的==>http://ip:9090/api/v1/query_range
String url = monitorUrl + "?query={query}&start={startTime}&end={endTime}&step={step}";
//设置参数
Map<String, String> param = new HashMap();
String query = "sum(container_accelerator_duty_cycle{name='" + name + "'}) by(name, acc_id)";
param.put("query", query);
param.put("startTime", startTime);
param.put("endTime", endTime);
param.put("step", step);
//发起请求
JSONObject resultJson = sendResourceMonitor(url, param);
//解析结果集
return analyticResult("GPU利用率", resultJson, step, startTime);
}
/**
* 获取GPU显存使用量 单位:GB
*
* @param name 容器实例名
* @param startTime 开始时间字符串
* @param endTime 结束时间字符串
* @param step 步长/采集周期
*/
public JSONObject getGpuMemoryRate(String name, String startTime, String endTime, String step) {
//拼接url monitorUrl 是我在配置文件中定义的==>http://ip:9090/api/v1/query_range
String url = monitorUrl + "?query={query}&start={startTime}&end={endTime}&step={step}";
//设置参数
Map<String, String> param = new HashMap();
String query = "sum(container_accelerator_memory_used_bytes{name= '" + name + "'}) by (name, acc_id) /1024/1024/1024";
param.put("query", query);
param.put("startTime", startTime);
param.put("endTime", endTime);
param.put("step", step);
//发起请求
JSONObject resultJson = sendResourceMonitor(url, param);
//解析结果集
return analyticResult("GPU显存使用量", resultJson, step, startTime);
}
/**
* 发送HTTP请求Prometheus
*
* @param url
* @param param
* @return
*/
public JSONObject sendResourceMonitor(String url, Map<String, String> param) {
try {
//发起请求
Map<String, Object> resultMap = restTemplate.getForObject(url, Map.class, param);
//将map转为json
//JSONObject resultJson = JsonUtils.parseObject(JsonUtils.toJSONString(resultMap), JSONObject.class);
JSONObject resultJson = JSON.parseObject(JSONObject.toJSONString(resultMap), JSONObject.class);
String status = resultJson.getString("status");
if (!"success".equals(status)) {
log.error("资源监控请求失败! url:{}, param:{},", url, param.toString());
return null;
}
return resultJson;
} catch (Exception e) {
e.printStackTrace();
log.error("资源监控请求失败! url:{}, param:{},", url, param.toString());
return null;
}
}
/**
* 解析Prometheus返回结果
*
* @param title 标题
* @param result
* @return
*/
private JSONObject analyticResult(String title, JSONObject result, String step, String startTime) {
JSONObject returnJson = new JSONObject();
JSONArray returnArray = new JSONArray();
List<String> dateList = new ArrayList<>();//获取时间轴
if (null == result) {
return null;
}
try {
//获取result结果集
JSONArray resultArray = result.getJSONObject("data").getJSONArray("result");
if (null == resultArray || resultArray.isEmpty()) {
return null;
}
//获取请求的开始时间毫秒数
long startMillisecond = Long.parseLong(startTime.replace(".", ""));
for (int i = 0; i < resultArray.size(); i++) {
JSONObject resultJson = resultArray.getJSONObject(i);
//获取数据集合
List<String> valueList = new ArrayList<>();
JSONObject jsonObject = new JSONObject();
//获取实例名
String name = title.contains("GPU") ? "卡" + (i + 1) : title;
jsonObject.put("name", name);
//获取values
JSONArray valuesArray = resultJson.getJSONArray("values");
long upMillisecond = 0l;
for (int j = 0; j < valuesArray.size(); j++) {
try {
JSONArray json = valuesArray.getJSONArray(j);
long currentMillisecond = Long.parseLong(json.getString(0).replace(".", ""));
if (j == 0) {//flag:true-->如果查询范围为5天,而容器却在昨天购买,前3天的日期轴没有,通过对比查询时间与数据返回时间,补齐时间轴
verifyCompleteTimeline(startMillisecond, currentMillisecond, step, (i == 0), true, dateList, valueList);
}
//flag:false-->如果查询范围为3天,机器第1,3天开启,在第2天关机,则Prometheus不会返回第二天的数据,时间轴只有第一天和第三天的数据,直接跳过第二天
if (verifyCompleteTimeline(upMillisecond, currentMillisecond, step, (i == 0), false, dateList, valueList)) {
upMillisecond = DateUtils.yyyyMMddHHmmssWithWhiffletreeToDate(dateList.get(dateList.size() - 1)).getTime();
continue;
}
if (i == 0) {
dateList.add(DateUtils.yyyyMMddHHmmssWithWhiffletree(new Date(currentMillisecond)));
}
upMillisecond = currentMillisecond;
double value = Double.valueOf(json.getString(1));
// 如果是CPU或GPU利用率 则保留两位小数
if (title.contains("CPU利用率") || title.contains("GPU利用率")) {
valueList.add(String.format("%.2f", value));
} else {//保留一位小数
valueList.add(String.format("%.1f", value));
}
} catch (Exception e) {
//转换失败,将上一次的结果赋值本次或跳过
e.printStackTrace();
if (dateList.size() == 0) {
continue;
}
if (i == 0 && dateList.size() == valueList.size()) {
//根据上一个日期毫秒,和步长,设置当前时间轴,确保时间轴的连续性
dateList.add(DateUtils.yyyyMMddHHmmssWithWhiffletree(new Date(upMillisecond + (Long.parseLong(step) * 1000))));
}
valueList.add("0");//转换失败,值设置为0。
continue;
}
}
jsonObject.put("value", valueList);
returnArray.add(jsonObject);
}
returnJson.put("title", title);
returnJson.put("date", dateList);
returnJson.put("values", returnArray);
} catch (Exception e) {
e.printStackTrace();
log.error(title + ": 解析结果集错误:{}", e.getMessage());
return null;
}
return returnJson;
}
/**
* 校验时间轴及数据
* 问题1:如果查询范围为3天,机器第1,3天开启,在第2天关机,则Prometheus不会返回第二天的数据,时间轴只有第一天和第三天的数据,直接跳过第二天
* 解决1:根据上一次时间加步长赋值时间轴,确保时间轴连续性,值为"";
* 问题2: 如果查询近七天的数据,容器是在昨天购买,时间轴只有昨天到现在的,前五天的没有
* 解决2: 根据查询开始时间和数据返回的第一条作对比,补齐时间轴
* @param upMillisecond 上一次时间毫秒值
* @param currentMillisecond 本次时间毫秒值
* @param step 步长/采集周期
* @param isDate 是否对日期轴进行添加 true:是 false:否
* @param flag true:补齐购买容器之前的日期 false:补齐关机期间的日期
* @param dateList 日期集合
* @param valueList 数据集合
* @return true: 有时间差距,需补充时间轴 false:无时间差距
*/
private boolean verifyCompleteTimeline(Long upMillisecond, Long currentMillisecond, String step, boolean isDate, boolean flag, List<String> dateList, List<String> valueList) {
if (upMillisecond == 0 && !flag) {
return false;
}
long count = (currentMillisecond - upMillisecond) / (Long.parseLong(step) * 1000);
if (count == 1) {
return false;
}
// log.error("资源监控-->日期时间缺失,开始补充:{}", dateList.get((dateList.size() - 1)));
for (int i = 0; i < count; i++) {
if (isDate) {
//补充时间差距,确保日期轴的完整性
long dateMillisecond = upMillisecond + Long.parseLong(step) * 1000 * (i + 1);
dateList.add(DateUtils.yyyyMMddHHmmssWithWhiffletree(new Date(dateMillisecond)));
}
valueList.add("");
}
// log.error("资源监控-->日期时间缺失,结束补充:{}", dateList.get((dateList.size() - 1)));
return true;
}
DateUtils
import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtils {
public static String yyyyMMddHHmmssWithWhiffletree(Date date) {
if(date == null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
public static Date yyyyMMddHHmmssWithWhiffletreeToDate(String dateStr) throws ParseException {
if(!StringUtils.hasText(dateStr)) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(dateStr);
}
}