Java 服务监控
效果

pom 添加依赖:
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>5.7.5</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.8.0</version>
</dependency>
oshi-core API
对象 | 方法 | 描述 |
SystemInfo | getOperatingSystem() | 获取操作系统信息,OperatingSystem |
getHardware() | 获取硬件信息,HardwareAbstractionLayer |
OperatingSystem | getFamily() | 获取操作系统名称,例如 Windows |
getManufacturer() | 获取供货商,例如 Microsoft |
getVersion() | 获取操作系统版本信息 |
getFileSystem() | 获取系统硬盘信息,FileSystem |
getProcesses(int pId, OperatingSystem.ProcessSort var2) | 通过 进程id 获取进程信息,并设置排序方式,OSProcess[] |
getProcess(int pId) | 通过 进程id 获取一个进程信息,OSProcess |
getProcesses(Collection<Integer> pId) | 获取多个进程信息,List<OSProcess> |
getChildProcesses(int var1, int var2, OperatingSystem.ProcessSort var3) | 获取子进程,OSProcess[] |
getProcessId() | 获取进程id |
getProcessCount() | 获取进程数量 |
getThreadCount() | 获取线程数 |
getNetworkParams() | 获取网络相关参数,NetworkParams |
HardwareAbstractionLayer | getComputerSystem() | 获取计算机系统信息,ComputerSystem |
getProcessor() | 获取处理器信息,CentralProcessor |
getMemory() | 获取内存信息,GlobalMemory |
getPowerSources() | 获取电源信息,PowerSource |
getDiskStores() | 获取硬件磁盘信息,HWDiskStore |
getNetworkIFs() | 获取网络信息,NetworkIF |
getDisplays() | 获取显示信息,Display |
getSensors() | 获取传感器信息,Sensors |
getUsbDevices(boolean var1) | 获取USB设备信息,UsbDevice |
OperatingSystemVersion | getBuildNumber() | 获取内部编号 |
getCodeName() | 代码名称 |
getVersion() | 获取版本 |
FileSystem | getMaxFileDescriptors() | 获取最大文件描述符 |
getOpenFileDescriptors() | 获取打开文件描述符 |
getFileStores() | 获取盘符相关信息 |
OSProcess | getName() | 进程程序名称 |
getPath() | 进程程序所在位置 |
getCommandLine() | 获取命令行 |
getCurrentWorkingDirectory() | 获取当前工作目录 |
getUser() | 获取用户信息 |
getUserID() | 获取用户id |
getGroup() | 获取组信息 |
getGroupID() | 获取组id |
getState() | 状态 |
getProcessID() | 获取进程id |
getParentProcessID() | 获取父进程id |
getThreadCount() | 获取线程数 |
getPriority() | 优先级 |
getVirtualSize() | 虚拟大小 |
getResidentSetSize() | 实际使用物理内存 |
getKernelTime() | 内核时间 |
getUserTime() | 用户时间 |
getUpTime() | 正常运行时间 |
getStartTime() | 开始时间 |
getBytesRead() | 读取字节 |
getBytesWritten() | 写入字节 |
getOpenFiles() | 打开文件数量 |
NetworkParams | getDnsServers() | 获取域名地址 |
getHostName() | 获取主机名 |
getDomainName() | 获取域名 |
getIpv4DefaultGateway() | 获取默认Ipv4 |
getIpv6DefaultGateway() | 获取默认Ipv6 |
OSFileStore | getName() | 磁盘名称 |
getVolume() | 文件集 |
getLogicalVolume() | 合理的文件集 |
getMount() | 盘符 |
getDescription() | 描述 |
getType() | 类型 |
getUUID() | 磁盘UUID |
getUsableSpace() | 可用空间 |
getTotalSpace() | 总空间 |
ComputerSystem | getManufacturer() | 获取制造商 |
getModel() | 获取型号 |
getSerialNumber() | 获取序列号 |
getFirmware() | 获取固件信息,Firmware |
getBaseboard() | 获取外壳信息,Baseboard |
Firmware | getManufacturer() | 获取制造商信息 |
getName() | 获取名称 |
getDescription() | 获取描述信息 |
getVersion() | 获取版本 |
getReleaseDate() | 获取发布时间 |
Baseboard | getManufacturer() | 获取制造商信息 |
getModel() | 获取型号 |
getVersion() | 获取版本信息 |
getSerialNumber() | 获取序列号 |
CentralProcessor | getVendor() | 获取供应商 |
getName() | 获取cpu名称 |
getVendorFreq() | 获取供应商频率 |
getProcessorID() | 获取处理器id |
getIdentifier() | 获取标识符 |
isCpu64bit() | 判断cpu是否为64位的 |
getStepping() | 获取cpu步进 |
getModel() | 获取型号 |
getFamily() | 获取家族 |
getSystemCpuLoadBetweenTicks() | 获取cpu负载间隔刻度 |
getSystemCpuLoadTicks() | 获取cpu负载刻度 |
getSystemCpuLoad() | 获取cpu负载 |
getSystemLoadAverage() | 获取cpu平均负载 |
getSystemLoadAverage(int var1) | 获取cpu平均负载 |
getProcessorCpuLoadBetweenTicks() | 获取处理器cpu负载间隔刻度 |
getProcessorCpuLoadTicks() | 获取处理器cpu负载刻度 |
getSystemUptime() | 获取正常运行时间 |
getLogicalProcessorCount() | 获取逻辑处理器数量 |
getPhysicalProcessorCount() | 获取物理处理器数量 |
getPhysicalPackageCount() | 获取物理包装数量 |
getContextSwitches() | 获取上下文切换数量 |
getInterrupts() | 获取中断 |
GlobalMemory | getTotal() | 获取总内存 |
getAvailable() | 获取可用系统运行内存 |
getSwapTotal() | 获取可用虚拟总内存 |
getSwapUsed() | 获取已用虚拟总内存 |
PowerSource | getName() | 获取名称 |
getRemainingCapacity() | 获取剩余容量 |
getTimeRemaining() | 获取剩余时间 |
HWDiskStore | getName() | 获取名称 |
getModel() | 获取型号 |
getSerial() | 获取序列号 |
getSize() | 获取大小 |
getReads() | (读长)是高通量测序中一个反应获得的测序序列 |
getReadBytes() | 读取字节 |
getWrites() | 写长 |
getWriteBytes() | 写入字节 |
getTransferTime() | 获取转移时间 |
getPartitions() | 获取分区,HWPartition |
getTimeStamp() | 获取时间戳 |
NetworkIF | getName() | 获取名称 |
getDisplayName() | 获取显示名称 |
getMTU() | 获取最大传输单元 |
getMacaddr() | 获取MAC地址 |
getIPv4addr() | 获取IPv4 |
getIPv6addr() | 获取IPv6 |
getBytesRecv() | 获取接收字节数 |
getBytesSent() | 获取发送字节数 |
getPacketsRecv() | 获取接收数据包 |
getPacketsSent() | 获取发送数据包 |
getInErrors() | 是否可达,正常 0 |
getOutErrors() | 响应错误,无错误 0 |
getSpeed() | 获取速率 |
getTimeStamp() | 获取时间错 |
Display | getEdid() | 中文名称扩展显示器识别数据 |
Sensors | getCpuTemperature() | 获取CPU温度 |
getFanSpeeds() | 获取风扇速度 |
getCpuVoltage() | 获取CPU电压 |
UsbDevice | getName() | 获取名称 |
getVendor() | 获取供应商 |
getVendorId() | 获取供应商id |
getProductId() | 获取商品id |
getSerialNumber() | 获取序列号 |
getConnectedDevices() | 获取连接设备 |
CPU 相关
package com.example.demo.module.domain.entity;
import com.example.demo.common.utils.Arith;
public class Cpu
{
private int cpuNum;
private double total;
private double sys;
private double used;
private double wait;
private double free;
public int getCpuNum()
{
return cpuNum;
}
public void setCpuNum(int cpuNum)
{
this.cpuNum = cpuNum;
}
public double getTotal()
{
return Arith.round(Arith.mul(total, 100), 2);
}
public void setTotal(double total)
{
this.total = total;
}
public double getSys()
{
return Arith.round(Arith.mul(sys / total, 100), 2);
}
public void setSys(double sys)
{
this.sys = sys;
}
public double getUsed()
{
return Arith.round(Arith.mul(used / total, 100), 2);
}
public void setUsed(double used)
{
this.used = used;
}
public double getWait()
{
return Arith.round(Arith.mul(wait / total, 100), 2);
}
public void setWait(double wait)
{
this.wait = wait;
}
public double getFree()
{
return Arith.round(Arith.mul(free / total, 100), 2);
}
public void setFree(double free)
{
this.free = free;
}
}
JVM 相关
package com.example.demo.module.domain.entity;
import com.example.demo.common.utils.Arith;
import com.example.demo.common.utils.DateUtils;
import java.lang.management.ManagementFactory;
public class Jvm
{
private double total;
private double max;
private double free;
private String version;
private String home;
public double getTotal()
{
return Arith.div(total, (1024 * 1024), 2);
}
public void setTotal(double total)
{
this.total = total;
}
public double getMax()
{
return Arith.div(max, (1024 * 1024), 2);
}
public void setMax(double max)
{
this.max = max;
}
public double getFree()
{
return Arith.div(free, (1024 * 1024), 2);
}
public void setFree(double free)
{
this.free = free;
}
public double getUsed()
{
return Arith.div(total - free, (1024 * 1024), 2);
}
public double getUsage()
{
return Arith.mul(Arith.div(total - free, total, 4), 100);
}
public String getName()
{
return ManagementFactory.getRuntimeMXBean().getVmName();
}
public String getVersion()
{
return version;
}
public void setVersion(String version)
{
this.version = version;
}
public String getHome()
{
return home;
}
public void setHome(String home)
{
this.home = home;
}
public String getStartTime()
{
return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate());
}
public String getRunTime()
{
return DateUtils.getDatePoor(DateUtils.getNowDate(), DateUtils.getServerStartDate());
}
}
内存相关 MEN
package com.example.demo.module.domain.entity;
import com.example.demo.common.utils.Arith;
public class Mem
{
private double total;
private double used;
private double free;
public double getTotal()
{
return Arith.div(total, (1024 * 1024 * 1024), 2);
}
public void setTotal(long total)
{
this.total = total;
}
public double getUsed()
{
return Arith.div(used, (1024 * 1024 * 1024), 2);
}
public void setUsed(long used)
{
this.used = used;
}
public double getFree()
{
return Arith.div(free, (1024 * 1024 * 1024), 2);
}
public void setFree(long free)
{
this.free = free;
}
public double getUsage()
{
return Arith.mul(Arith.div(used, total, 4), 100);
}
}
系统相关
package com.example.demo.module.domain.entity;
public class Sys
{
private String computerName;
private String computerIp;
private String userDir;
private String osName;
private String osArch;
public String getComputerName()
{
return computerName;
}
public void setComputerName(String computerName)
{
this.computerName = computerName;
}
public String getComputerIp()
{
return computerIp;
}
public void setComputerIp(String computerIp)
{
this.computerIp = computerIp;
}
public String getUserDir()
{
return userDir;
}
public void setUserDir(String userDir)
{
this.userDir = userDir;
}
public String getOsName()
{
return osName;
}
public void setOsName(String osName)
{
this.osName = osName;
}
public String getOsArch()
{
return osArch;
}
public void setOsArch(String osArch)
{
this.osArch = osArch;
}
}
系统文件相关
package com.example.demo.module.domain.entity;
public class SysFile
{
private String dirName;
private String sysTypeName;
private String typeName;
private String total;
private String free;
private String used;
private double usage;
public String getDirName()
{
return dirName;
}
public void setDirName(String dirName)
{
this.dirName = dirName;
}
public String getSysTypeName()
{
return sysTypeName;
}
public void setSysTypeName(String sysTypeName)
{
this.sysTypeName = sysTypeName;
}
public String getTypeName()
{
return typeName;
}
public void setTypeName(String typeName)
{
this.typeName = typeName;
}
public String getTotal()
{
return total;
}
public void setTotal(String total)
{
this.total = total;
}
public String getFree()
{
return free;
}
public void setFree(String free)
{
this.free = free;
}
public String getUsed()
{
return used;
}
public void setUsed(String used)
{
this.used = used;
}
public double getUsage()
{
return usage;
}
public void setUsage(double usage)
{
this.usage = usage;
}
}
服务器相关
package com.example.demo.module.domain.entity;
import com.example.demo.common.utils.Arith;
import com.example.demo.common.utils.IpUtils;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.CentralProcessor.TickType;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
public class Server
{
private static final int OSHI_WAIT_SECOND = 1000;
private Cpu cpu = new Cpu();
private Mem mem = new Mem();
private Jvm jvm = new Jvm();
private Sys sys = new Sys();
private List<SysFile> sysFiles = new LinkedList<SysFile>();
public Cpu getCpu()
{
return cpu;
}
public void setCpu(Cpu cpu)
{
this.cpu = cpu;
}
public Mem getMem()
{
return mem;
}
public void setMem(Mem mem)
{
this.mem = mem;
}
public Jvm getJvm()
{
return jvm;
}
public void setJvm(Jvm jvm)
{
this.jvm = jvm;
}
public Sys getSys()
{
return sys;
}
public void setSys(Sys sys)
{
this.sys = sys;
}
public List<SysFile> getSysFiles()
{
return sysFiles;
}
public void setSysFiles(List<SysFile> sysFiles)
{
this.sysFiles = sysFiles;
}
public void copyTo() throws Exception
{
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
setCpuInfo(hal.getProcessor());
setMemInfo(hal.getMemory());
setSysInfo();
setJvmInfo();
setSysFiles(si.getOperatingSystem());
}
private void setCpuInfo(CentralProcessor processor)
{
long[] prevTicks = processor.getSystemCpuLoadTicks();
Util.sleep(OSHI_WAIT_SECOND);
long[] ticks = processor.getSystemCpuLoadTicks();
long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
cpu.setCpuNum(processor.getLogicalProcessorCount());
cpu.setTotal(totalCpu);
cpu.setSys(cSys);
cpu.setUsed(user);
cpu.setWait(iowait);
cpu.setFree(idle);
}
private void setMemInfo(GlobalMemory memory)
{
mem.setTotal(memory.getTotal());
mem.setUsed(memory.getTotal() - memory.getAvailable());
mem.setFree(memory.getAvailable());
}
private void setSysInfo()
{
Properties props = System.getProperties();
sys.setComputerName(IpUtils.getHostName());
sys.setComputerIp(IpUtils.getHostIp());
sys.setOsName(props.getProperty("os.name"));
sys.setOsArch(props.getProperty("os.arch"));
sys.setUserDir(props.getProperty("user.dir"));
}
private void setJvmInfo() throws UnknownHostException
{
Properties props = System.getProperties();
jvm.setTotal(Runtime.getRuntime().totalMemory());
jvm.setMax(Runtime.getRuntime().maxMemory());
jvm.setFree(Runtime.getRuntime().freeMemory());
jvm.setVersion(props.getProperty("java.version"));
jvm.setHome(props.getProperty("java.home"));
}
private void setSysFiles(OperatingSystem os)
{
FileSystem fileSystem = os.getFileSystem();
List<OSFileStore> fsArray = fileSystem.getFileStores();
for (OSFileStore fs : fsArray)
{
long free = fs.getUsableSpace();
long total = fs.getTotalSpace();
long used = total - free;
SysFile sysFile = new SysFile();
sysFile.setDirName(fs.getMount());
sysFile.setSysTypeName(fs.getType());
sysFile.setTypeName(fs.getName());
sysFile.setTotal(convertFileSize(total));
sysFile.setFree(convertFileSize(free));
sysFile.setUsed(convertFileSize(used));
sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100));
sysFiles.add(sysFile);
}
}
public String convertFileSize(long size)
{
long kb = 1024;
long mb = kb * 1024;
long gb = mb * 1024;
if (size >= gb)
{
return String.format("%.1f GB", (float) size / gb);
}
else if (size >= mb)
{
float f = (float) size / mb;
return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
}
else if (size >= kb)
{
float f = (float) size / kb;
return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
}
else
{
return String.format("%d B", size);
}
}
}
浮点数运算工具类
package com.example.demo.common.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Arith
{
private static final int DEF_DIV_SCALE = 10;
private Arith()
{
}
public static double add(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
public static double sub(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
public static double mul(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
public static double div(double v1, double v2)
{
return div(v1, v2, DEF_DIV_SCALE);
}
public static double div(double v1, double v2, int scale)
{
if (scale < 0)
{
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
if (b1.compareTo(BigDecimal.ZERO) == 0)
{
return BigDecimal.ZERO.doubleValue();
}
return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
public static double round(double v, int scale)
{
if (scale < 0)
{
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = BigDecimal.ONE;
return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
}
controller
package com.example.demo.module.domain.controller;
import com.example.demo.module.domain.entity.Server;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping(value = "/domain")
public class DomainController {
@GetMapping(value = "data")
public ModelAndView server() throws Exception {
ModelAndView mv = new ModelAndView("server");
Server server = new Server();
server.copyTo();
mv.addObject("server",server);
return mv;
}
}
html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>server</title>
<link th:href="@{/static/layui/css/layui.css}" rel="stylesheet"/>
</head>
<body style="padding: 20px 20px 20px 20px">
<p>cpu</p>
<table class="layui-table">
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>核心数</td>
<td th:text="${server.cpu.cpuNum}">0个</td>
</tr>
<tr>
<td>用户使用率</td>
<td th:text="${server.cpu.used + '%'}">0%</td>
</tr>
<tr>
<td>系统使用率</td>
<td th:text="${server.cpu.sys + '%'}">0%</td>
</tr>
<tr>
<td>当前空闲率</td>
<td th:text="${server.cpu.free + '%'}">0%</td>
</tr>
</tbody>
</table>
<br/>
<p>内存</p>
<table class="layui-table">
<thead>
<tr>
<th>属性</th>
<th>内存</th>
<th>JVM</th>
</tr>
</thead>
<tbody>
<tr>
<td>总内存</td>
<td th:text="${server.mem.total + 'G'}">0GB</td>
<td th:text="${server.jvm.total + 'M'}">0MB</td>
</tr>
<tr>
<td>已用内存</td>
<td th:text="${server.mem.used + 'G'}">0GB</td>
<td th:text="${server.jvm.used + 'M'}">0MB</td>
</tr>
<tr>
<td>剩余内存</td>
<td th:text="${server.mem.free + 'G'}">0GB</td>
<td th:text="${server.jvm.free + 'M'}">0MB</td>
</tr>
<tr>
<td>使用率</td>
<td th:class="${server.mem.usage gt 80} ? 'text-danger'">[[${server.mem.usage}]]%</td>
<td th:class="${server.jvm.usage gt 80} ? 'text-danger'">[[${server.jvm.usage}]]%</td>
</tr>
</tbody>
</table>
<br/>
<p>服务器信息</p>
<table class="layui-table">
<tbody>
<tr>
<td>服务器名称</td>
<td th:text="${server.sys.computerName}">xxx</td>
<td>操作系统</td>
<td th:text="${server.sys.osName}">Linux</td>
</tr>
<tr>
<td>服务器IP</td>
<td th:text="${server.sys.computerIp}">127.0.0.1</td>
<td>系统架构</td>
<td th:text="${server.sys.osArch}">amd64</td>
</tr>
</tbody>
</table>
<br/>
<p>Java虚拟机信息</p>
<table class="layui-table">
<tbody>
<tr>
<td>Java名称</td>
<td th:text="${server.jvm.name}">Java</td>
<td>Java版本</td>
<td th:text="${server.jvm.version}">1.8.0</td>
</tr>
<tr>
<td>启动时间</td>
<td th:text="${server.jvm.startTime}">2018-12-31 00:00:00</td>
<td>运行时长</td>
<td th:text="${server.jvm.runTime}">0天0时0分0秒</td>
</tr>
<tr>
<td colspan="1">安装路径</td>
<td colspan="3" th:text="${server.jvm.home}"></td>
</tr>
<tr>
<td colspan="1">项目路径</td>
<td colspan="3" th:text="${server.sys.userDir}"></td>
</tr>
</tbody>
</table>
<br/>
<p>磁盘状态</p>
<table class="layui-table">
<thead>
<tr>
<th>盘符路径</th>
<th>文件系统</th>
<th>盘符类型</th>
<th>总大小</th>
<th>可用大小</th>
<th>已用大小</th>
<th>已用百分比</th>
</tr>
</thead>
<tbody>
<tr th:each="sysFile : ${server.sysFiles}">
<td th:text="${sysFile.dirName}">C:\</td>
<td th:text="${sysFile.sysTypeName}">NTFS</td>
<td th:text="${sysFile.typeName}">local</td>
<td th:text="${sysFile.total}">0GB</td>
<td th:text="${sysFile.free}">0GB</td>
<td th:text="${sysFile.used}">0GB</td>
<td th:class="${sysFile.usage gt 80} ? 'text-danger'">[[${sysFile.usage}]]%</td>
</tr>
</tbody>
</table>
</body>
</html>