Compare commits

14 Commits

Author SHA1 Message Date
523edffc4e Merge pull request 'v1.0.2' (#6) from dev into master
Reviewed-on: #6
2026-04-13 10:28:11 +00:00
Timi
3cc371c53e v1.0.2
All checks were successful
CI / build-deploy (pull_request) Successful in 27s
CI / notify-on-failure (pull_request) Has been skipped
2026-04-13 18:27:29 +08:00
c7ddb1a8b0 Merge pull request 'v1.0.1' (#5) from dev into master
Reviewed-on: #5
2026-04-12 16:17:02 +00:00
Timi
b46e9079d5 v1.0.1
All checks were successful
CI / build-deploy (pull_request) Successful in 26s
CI / notify-on-failure (pull_request) Has been skipped
2026-04-13 00:09:55 +08:00
Timi
dc20070bf8 update status api 2026-04-13 00:09:48 +08:00
45c9fc814a Merge pull request 'v1.0.0' (#4) from dev into master
Reviewed-on: #4
2026-04-09 05:16:04 +00:00
Timi
78163441dd v1.0.0
All checks were successful
CI / build-deploy (pull_request) Successful in 16s
CI / notify-on-failure (pull_request) Has been skipped
2026-04-09 13:14:20 +08:00
407dc13ac4 Merge pull request 'v1.0.0' (#3) from dev into master
Reviewed-on: #3
2026-04-09 04:09:16 +00:00
Timi
9762be1244 v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 51s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-09 12:08:24 +08:00
0c06bf16c2 Merge pull request 'v1.0.0' (#2) from dev into master
Reviewed-on: #2
2026-04-09 04:03:43 +00:00
Timi
971cad7365 v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 5s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-09 12:03:15 +08:00
1db39e77d3 Merge pull request 'v1.0.0' (#1) from dev into master
Reviewed-on: #1
2026-04-08 08:48:12 +00:00
Timi
b5e9da0e9b v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 3s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-08 16:30:10 +08:00
Timi
34e1ac6264 remove gson 2026-04-08 12:00:52 +08:00
13 changed files with 459 additions and 58 deletions

333
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,333 @@
name: CI
on:
pull_request:
branches:
- master
types:
- closed
jobs:
build-deploy:
runs-on: act_runner_java
if: ${{ github.event.pull_request.merged == true }}
outputs:
deployment_status: ${{ steps.set_status.outputs.status }}
env:
JAVA_HOME: /usr/lib/jvm/java-21-openjdk
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up environment
run: |
echo "PR #${{ github.event.number }} merged into master"
echo "Source branch: ${{ github.event.pull_request.head.ref }}"
echo "Target branch: ${{ github.event.pull_request.base.ref }}"
- name: Run tests
run: |
echo "Running test suite..."
- name: Setup Maven settings
run: |
if [ -z "${{ vars.TIMI_NEXUS_USERNAME }}" ] || [ -z "${{ vars.TIMI_NEXUS_PASSWORD }}" ]; then
echo "Missing vars.TIMI_NEXUS_USERNAME or vars.TIMI_NEXUS_PASSWORD"
exit 1
fi
mkdir -p ~/.m2
cat > ~/.m2/settings.xml <<EOF
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>timi_nexus</id>
<username>${{ vars.TIMI_NEXUS_USERNAME }}</username>
<password>${{ vars.TIMI_NEXUS_PASSWORD }}</password>
</server>
</servers>
</settings>
EOF
- name: Build project
run: |
mvn -B -DskipTests clean package -P prod-linux
- name: Deploy service
if: success()
env:
CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
CONTAINER_TARGET_PATH: ${{ vars.CONTAINER_TARGET_PATH }}
MAX_RETRIES: 3
RETRY_DELAY: 10
run: |
if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_TARGET_PATH" ]; then
echo "Missing production environment variables"
echo "Required: CONTAINER_NAME, CONTAINER_TARGET_PATH"
exit 1
fi
retry_command() {
local cmd="$1"
local desc="$2"
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "[$desc] Attempt $attempt/$MAX_RETRIES..."
if eval "$cmd"; then
echo "OK: $desc succeeded"
return 0
fi
echo "FAIL: $desc failed (attempt $attempt/$MAX_RETRIES)"
if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
attempt=$((attempt + 1))
done
echo "FAIL: $desc failed after $MAX_RETRIES attempts"
return 1
}
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar"
if [ ! -f "$jar_file" ]; then
echo "Build artifact not found: $jar_file"
exit 1
fi
if ! command -v docker >/dev/null 2>&1; then
echo "docker command not found in runner environment"
exit 1
fi
if ! docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
echo "Docker container not found: $CONTAINER_NAME"
exit 1
fi
target_jar="${artifact_id}.jar"
container_target="${CONTAINER_TARGET_PATH%/}/$target_jar"
echo "Deploying $jar_file to container $CONTAINER_NAME:$container_target"
if ! retry_command "docker cp \"$jar_file\" \"$CONTAINER_NAME:$container_target\"" "Docker copy"; then
exit 1
fi
echo "Restarting Docker container: $CONTAINER_NAME"
if ! retry_command "docker restart \"$CONTAINER_NAME\"" "Docker restart"; then
exit 1
fi
echo "Deployment completed successfully"
- name: Create release
if: ${{ success() && startsWith(github.event.pull_request.title, 'v') }}
env:
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
GITEA_SERVER_URL: ${{ github.server_url }}
GITEA_INTERNAL_URL: ${{ vars.TIMI_GITEA_INTERNAL_URL }}
GITEA_REPOSITORY: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.pull_request.title }}
RELEASE_TARGET: ${{ github.sha }}
MAX_RETRIES: 3
RETRY_DELAY: 10
run: |
if [ -z "$GITEA_TOKEN" ]; then
echo "Missing secrets.RUNNER_TOKEN"
exit 1
fi
if [ -n "$GITEA_INTERNAL_URL" ]; then
api_base_url="$GITEA_INTERNAL_URL"
echo "Using internal Gitea URL: $api_base_url"
else
api_base_url="$GITEA_SERVER_URL"
echo "Using public Gitea URL: $api_base_url"
fi
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar"
if [ ! -f "$jar_file" ]; then
echo "Build artifact not found: $jar_file"
exit 1
fi
file_size=$(stat -c%s "$jar_file" 2>/dev/null || stat -f%z "$jar_file" 2>/dev/null || echo "unknown")
echo "Found fat jar: $jar_file (size: $file_size bytes)"
api_url="$api_base_url/api/v1/repos/$GITEA_REPOSITORY/releases"
payload=$(cat <<EOF
{
"tag_name": "$RELEASE_TAG",
"name": "$RELEASE_TAG",
"target_commitish": "$RELEASE_TARGET",
"draft": false,
"prerelease": false
}
EOF
)
echo "Creating release with tag: $RELEASE_TAG"
echo "API URL: $api_url"
echo "Target commit: $RELEASE_TARGET"
release_response_file=$(mktemp /tmp/release_response_XXXXXX.json)
trap "rm -f $release_response_file" EXIT
release_id=""
attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do
echo "[Create release] Attempt $attempt/$MAX_RETRIES..."
> "$release_response_file"
http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
--connect-timeout 30 \
--max-time 60 \
-d "$payload" 2>/dev/null) || http_code="000"
response=$(cat "$release_response_file" 2>/dev/null || echo "{}")
echo "HTTP Status: $http_code"
if [ "$http_code" = "201" ]; then
if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$response" | jq -r '.id' 2>/dev/null)
else
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi
echo "OK: Release created: id=$release_id"
elif [ "$http_code" = "409" ]; then
echo "Release already exists (HTTP 409), fetching existing release..."
existing=$(curl -sS "$api_url" -H "Authorization: token $GITEA_TOKEN" --connect-timeout 30 2>/dev/null || echo "[]")
if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$existing" | jq -r ".[] | select(.tag_name==\"$RELEASE_TAG\") | .id" 2>/dev/null | head -1)
else
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi
if [ -n "$release_id" ]; then
echo "OK: Found existing release: id=$release_id"
else
echo "FAIL: Could not find existing release id"
fi
else
echo "FAIL: Create release failed (HTTP $http_code)"
if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
fi
attempt=$((attempt + 1))
done
if [ -z "$release_id" ]; then
echo "FAIL: Failed to create or find release after $MAX_RETRIES attempts"
exit 1
fi
asset_name=$(basename "$jar_file")
echo "Uploading asset: $asset_name (size: $file_size bytes)"
upload_url="$api_url/$release_id/assets?name=$asset_name"
echo "Upload URL: $upload_url"
asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json)
trap "rm -f $release_response_file $asset_response_file" EXIT
upload_success=false
attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do
echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..."
> "$asset_response_file"
http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \
-H "Authorization: token $GITEA_TOKEN" \
--connect-timeout 30 \
--max-time 300 \
-F "attachment=@$jar_file" 2>/dev/null) || http_code="000"
if [ "$http_code" = "201" ]; then
upload_success=true
echo "OK: Successfully uploaded: $asset_name"
else
echo "FAIL: Upload failed (HTTP $http_code)"
cat "$asset_response_file" 2>/dev/null || true
fi
if [ "$upload_success" = "false" ] && [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
attempt=$((attempt + 1))
done
if [ "$upload_success" = "false" ]; then
echo "FAIL: Failed to upload asset after $MAX_RETRIES attempts"
exit 1
fi
- name: Mark deployment success
id: set_status
if: always()
run: |
echo "status=success" >> $GITHUB_OUTPUT
notify-on-failure:
runs-on: act_runner_java
needs: build-deploy
if: ${{ always() && github.event.pull_request.merged == true && needs.build-deploy.result == 'failure' }}
steps:
- name: Notify CI failure
env:
PR_NUMBER: ${{ github.event.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
SOURCE_BRANCH: ${{ github.event.pull_request.head.ref }}
AUTHOR: ${{ github.event.pull_request.user.login }}
COMMIT_SHA: ${{ github.sha }}
REPO: ${{ github.repository }}
SERVER_URL: ${{ github.server_url }}
WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }}
run: |
echo "========================================="
echo "CI Pipeline Failed - Manual Review Required"
echo "========================================="
echo ""
echo "PR: #$PR_NUMBER - $PR_TITLE"
echo "Branch: $SOURCE_BRANCH"
echo "Author: $AUTHOR"
echo "Commit: $COMMIT_SHA"
echo ""
echo "Actions:"
echo " 1. Re-run CI: $SERVER_URL/$REPO/actions"
echo " 2. Revert PR: $PR_URL (click 'Revert' button)"
echo ""
echo "========================================="
if [ -n "$WEBHOOK_URL" ]; then
message="CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n- 重试 CI\n- 回滚合并"
payload=$(cat <<EOF
{
"msgtype": "text",
"text": {
"content": "$message"
}
}
EOF
)
curl -sS -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$payload" || echo "Warning: Failed to send notification"
echo "OK: Notification sent"
else
echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications"
fi

29
pom.xml
View File

@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId> <groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId> <artifactId>TimiServerAPI</artifactId>
<version>1.0.0</version> <version>1.0.1</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -23,13 +23,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<repositories>
<repository>
<id>apache-maven</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>
<profiles> <profiles>
<profile> <profile>
<id>dev-windows</id> <id>dev-windows</id>
@@ -112,17 +105,33 @@
<configuration> <configuration>
<excludeDevtools>true</excludeDevtools> <excludeDevtools>true</excludeDevtools>
<mainClass>com.imyeyu.api.TimiServerAPI</mainClass> <mainClass>com.imyeyu.api.TimiServerAPI</mainClass>
<finalName>${project.artifactId}</finalName>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<repositories>
<repository>
<id>apache-maven</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
<repository>
<id>timi_nexus</id>
<url>https://nexus.imyeyu.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.imyeyu.spring</groupId> <groupId>com.imyeyu.spring</groupId>
<artifactId>timi-spring</artifactId> <artifactId>timi-spring</artifactId>
<version>0.0.9</version> <version>0.0.10</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.network</groupId> <groupId>com.imyeyu.network</groupId>

View File

@@ -348,10 +348,10 @@ public class ServerStatus implements TimiJava {
private String mountPoint; private String mountPoint;
/** 分区总空间 */ /** 分区总空间 */
private long totalBytes; private long total;
/** 分区已用空间 */ /** 分区已用空间 */
private Long usedBytes; private Long used;
/** 磁盘传输耗时 */ /** 磁盘传输耗时 */
private long transferTimeMs; private long transferTimeMs;

View File

@@ -26,17 +26,17 @@ public class DockerController {
private final DockerService dockerService; private final DockerService dockerService;
@GetMapping("/containers") @GetMapping("/container")
public List<DockerContainerSummaryView> listContainers() { public List<DockerContainerSummaryView> listContainers() {
return dockerService.listContainers(); return dockerService.listContainers();
} }
@GetMapping("/containers/{containerId}/status") @GetMapping("/container/{containerId}/status")
public DockerContainerStatusView getContainerStatus(@PathVariable String containerId) { public DockerContainerStatusView getContainerStatus(@PathVariable String containerId) {
return dockerService.getContainerStatus(containerId); return dockerService.getContainerStatus(containerId);
} }
@GetMapping("/containers/{containerId}/history") @GetMapping("/container/{containerId}/history")
public DockerContainerHistoryView getContainerHistory(@PathVariable String containerId, @RequestParam(required = false) String window) { public DockerContainerHistoryView getContainerHistory(@PathVariable String containerId, @RequestParam(required = false) String window) {
return dockerService.getContainerHistory(containerId, window); return dockerService.getContainerHistory(containerId, window);
} }

View File

@@ -72,7 +72,6 @@ public class StatusServiceImplement implements StatusService {
SystemStatusDataView.OS os = new SystemStatusDataView.OS(); SystemStatusDataView.OS os = new SystemStatusDataView.OS();
os.setName(serverStatus.getOs().getName()); os.setName(serverStatus.getOs().getName());
os.setBootAt(serverStatus.getOs().getBootAt()); os.setBootAt(serverStatus.getOs().getBootAt());
os.setUptimeMs(Math.max(0, serverTime - serverStatus.getOs().getBootAt()));
snapshot.setOs(os); snapshot.setOs(os);
} }
if (selectedMetrics.contains(Metric.CPU)) { if (selectedMetrics.contains(Metric.CPU)) {
@@ -80,8 +79,8 @@ public class StatusServiceImplement implements StatusService {
cpu.setModel(serverStatus.getCpu().getName()); cpu.setModel(serverStatus.getCpu().getName());
cpu.setPhysicalCores(serverStatus.getCpu().getCoreCount()); cpu.setPhysicalCores(serverStatus.getCpu().getCoreCount());
cpu.setLogicalCores(serverStatus.getCpu().getLogicalCount()); cpu.setLogicalCores(serverStatus.getCpu().getLogicalCount());
cpu.setUsagePercent(lastDouble(serverStatus.getCpu().getUsed())); cpu.setUsageTotal(lastDouble(serverStatus.getCpu().getUsed()));
cpu.setSystemPercent(lastDouble(serverStatus.getCpu().getSystem())); cpu.setUsageSystem(lastDouble(serverStatus.getCpu().getSystem()));
cpu.setTemperatureCelsius(serverStatus.getCpu().getTemperature()); cpu.setTemperatureCelsius(serverStatus.getCpu().getTemperature());
snapshot.setCpu(cpu); snapshot.setCpu(cpu);
} }
@@ -91,7 +90,6 @@ public class StatusServiceImplement implements StatusService {
Long swapUsedBytes = lastLong(serverStatus.getMemory().getSwapUsed()); Long swapUsedBytes = lastLong(serverStatus.getMemory().getSwapUsed());
memory.setTotalBytes(serverStatus.getMemory().getSize()); memory.setTotalBytes(serverStatus.getMemory().getSize());
memory.setUsedBytes(usedBytes); memory.setUsedBytes(usedBytes);
memory.setUsagePercent(toPercent(usedBytes, serverStatus.getMemory().getSize()));
memory.setSwapTotalBytes(serverStatus.getMemory().getSwapSize()); memory.setSwapTotalBytes(serverStatus.getMemory().getSwapSize());
memory.setSwapUsedBytes(swapUsedBytes); memory.setSwapUsedBytes(swapUsedBytes);
snapshot.setMemory(memory); snapshot.setMemory(memory);
@@ -160,9 +158,9 @@ public class StatusServiceImplement implements StatusService {
item.setPartitionType(partition.getPartitionType()); item.setPartitionType(partition.getPartitionType());
item.setUuid(partition.getUuid()); item.setUuid(partition.getUuid());
item.setMountPoint(partition.getMountPoint()); item.setMountPoint(partition.getMountPoint());
item.setTotalBytes(partition.getTotalBytes()); item.setTotal(partition.getTotal());
item.setUsedBytes(partition.getUsedBytes()); item.setUsed(partition.getUsed());
item.setUsagePercent(toPercent(partition.getUsedBytes(), partition.getTotalBytes())); item.setUsagePercent(toPercent(partition.getUsed(), partition.getTotal()));
item.setTransferTimeMs(partition.getTransferTimeMs()); item.setTransferTimeMs(partition.getTransferTimeMs());
storagePartitions.add(item); storagePartitions.add(item);
} }

View File

@@ -23,7 +23,7 @@ public class UpsServiceImplement implements UpsService {
private final UpsStatusTask upsStatusTask; private final UpsStatusTask upsStatusTask;
private final UpsStatusStore upsStatusStore; private final UpsStatusStore upsStatusStore;
@Value("${ups.collect-rate-ms:60000}") @Value("${ups.collect-rate-ms:3000}")
private long collectRateMs; private long collectRateMs;
@Override @Override

View File

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.imyeyu.api.modules.system.bean.DockerStatusStore; import com.imyeyu.api.modules.system.bean.DockerStatusStore;
import com.imyeyu.api.modules.system.util.DockerEngineClient; import com.imyeyu.api.modules.system.util.DockerEngineClient;
import com.imyeyu.java.TimiJava;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -15,12 +16,13 @@ import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Docker 鐘舵€侀噰闆嗕换鍔? * Docker 容器状态采集任务
* *
* @author Codex * @author Codex
* @since 2026-04-06 * @since 2026-04-06
@@ -56,19 +58,27 @@ public class DockerStatusTask implements SchedulingConfigurer {
try { try {
ArrayNode containers = (ArrayNode) dockerEngineClient.getJson("/containers/json", DockerEngineClient.query("all", "true")); ArrayNode containers = (ArrayNode) dockerEngineClient.getJson("/containers/json", DockerEngineClient.query("all", "true"));
long now = Time.now(); long now = Time.now();
synchronized (dockerStatusStore) { Map<String, DockerStatusStore.Container> collectedContainers = new LinkedHashMap<>();
Set<String> activeIds = new HashSet<>(); for (JsonNode summary : containers) {
for (JsonNode summary : containers) { try {
try { String containerId = getAsString(summary, "Id");
String containerId = getAsString(summary, "Id"); DockerStatusStore.Container container = new DockerStatusStore.Container();
activeIds.add(containerId); if (TimiJava.isEmpty(container)) {
DockerStatusStore.Container container = dockerStatusStore.getContainers().computeIfAbsent(containerId, key -> new DockerStatusStore.Container()); continue;
updateContainerSummary(container, summary);
updateContainerInspect(containerId, container);
updateContainerStats(containerId, container, now);
} catch (Exception e) {
log.error("collect docker container item error", e);
} }
updateContainerSummary(container, summary);
updateContainerInspect(containerId, container);
updateContainerStats(containerId, container, now);
collectedContainers.put(container.getId(), container);
} catch (Exception e) {
log.error("collect docker container item error", e);
}
}
synchronized (dockerStatusStore) {
Set<String> activeIds = new HashSet<>(collectedContainers.keySet());
for (Map.Entry<String, DockerStatusStore.Container> item : collectedContainers.entrySet()) {
DockerStatusStore.Container container = dockerStatusStore.getContainers().computeIfAbsent(item.getKey(), key -> new DockerStatusStore.Container());
applyCollectedContainer(container, item.getValue());
} }
dockerStatusStore.getContainers().entrySet().removeIf(item -> !activeIds.contains(item.getKey())); dockerStatusStore.getContainers().entrySet().removeIf(item -> !activeIds.contains(item.getKey()));
} }
@@ -77,6 +87,39 @@ public class DockerStatusTask implements SchedulingConfigurer {
} }
} }
private void applyCollectedContainer(DockerStatusStore.Container target, DockerStatusStore.Container source) {
target.setId(source.getId());
target.setName(source.getName());
target.setImage(source.getImage());
target.setImageId(source.getImageId());
target.setCreatedAt(source.getCreatedAt());
target.setState(source.getState());
target.setStatus(source.getStatus());
target.setHealthStatus(source.getHealthStatus());
target.setStartedAt(source.getStartedAt());
target.setFinishedAt(source.getFinishedAt());
target.setExitCode(source.getExitCode());
target.setRestartCount(source.getRestartCount());
target.setOomKilled(source.isOomKilled());
target.setCpuPercent(source.getCpuPercent());
target.setMemoryUsageBytes(source.getMemoryUsageBytes());
target.setMemoryLimitBytes(source.getMemoryLimitBytes());
target.setMemoryPercent(source.getMemoryPercent());
target.setNetworkRxBytes(source.getNetworkRxBytes());
target.setNetworkTxBytes(source.getNetworkTxBytes());
target.setBlockReadBytes(source.getBlockReadBytes());
target.setBlockWriteBytes(source.getBlockWriteBytes());
target.setPids(source.getPids());
target.setUpdatedAt(source.getUpdatedAt());
DockerStatusStore.Point point = source.getHistory().peekLast();
if (point != null) {
target.getHistory().addLast(point);
while (historyLimit < target.getHistory().size()) {
target.getHistory().pollFirst();
}
}
}
private void updateContainerSummary(DockerStatusStore.Container container, JsonNode summary) { private void updateContainerSummary(DockerStatusStore.Container container, JsonNode summary) {
container.setId(getAsString(summary, "Id")); container.setId(getAsString(summary, "Id"));
container.setName(trimContainerName(readFirstArrayText(summary, "Names"))); container.setName(trimContainerName(readFirstArrayText(summary, "Names")));
@@ -108,8 +151,8 @@ public class DockerStatusTask implements SchedulingConfigurer {
if (memoryUsageBytes != null && memoryLimitBytes != null && 0 < memoryLimitBytes) { if (memoryUsageBytes != null && memoryLimitBytes != null && 0 < memoryLimitBytes) {
memoryPercent = memoryUsageBytes * 100D / memoryLimitBytes; memoryPercent = memoryUsageBytes * 100D / memoryLimitBytes;
} }
Long networkRxBytes = 0L; long networkRxBytes = 0L;
Long networkTxBytes = 0L; long networkTxBytes = 0L;
JsonNode networks = getAsObject(stats, "networks"); JsonNode networks = getAsObject(stats, "networks");
if (networks != null) { if (networks != null) {
for (Map.Entry<String, JsonNode> item : (Iterable<Map.Entry<String, JsonNode>>) networks::fields) { for (Map.Entry<String, JsonNode> item : (Iterable<Map.Entry<String, JsonNode>>) networks::fields) {
@@ -118,8 +161,8 @@ public class DockerStatusTask implements SchedulingConfigurer {
networkTxBytes += getAsLong(network, "tx_bytes", 0L); networkTxBytes += getAsLong(network, "tx_bytes", 0L);
} }
} }
Long blockReadBytes = 0L; long blockReadBytes = 0L;
Long blockWriteBytes = 0L; long blockWriteBytes = 0L;
JsonNode blkioStats = getAsObject(stats, "blkio_stats"); JsonNode blkioStats = getAsObject(stats, "blkio_stats");
ArrayNode ioServiceBytes = blkioStats == null ? null : getAsArray(blkioStats, "io_service_bytes_recursive"); ArrayNode ioServiceBytes = blkioStats == null ? null : getAsArray(blkioStats, "io_service_bytes_recursive");
if (ioServiceBytes != null) { if (ioServiceBytes != null) {
@@ -134,7 +177,6 @@ public class DockerStatusTask implements SchedulingConfigurer {
} }
} }
Integer pids = getNestedInteger(stats, "pids_stats", "current"); Integer pids = getNestedInteger(stats, "pids_stats", "current");
container.setCpuPercent(cpuPercent); container.setCpuPercent(cpuPercent);
container.setMemoryUsageBytes(memoryUsageBytes); container.setMemoryUsageBytes(memoryUsageBytes);
container.setMemoryLimitBytes(memoryLimitBytes); container.setMemoryLimitBytes(memoryLimitBytes);

View File

@@ -38,10 +38,10 @@ public class CpuStatusCollector extends AbstractDequeStatusCollector {
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IRQ.getIndex()]; long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.IRQ.getIndex()];
long softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; long softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - lastCpuTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCpuTicks[CentralProcessor.TickType.STEAL.getIndex()]; long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - lastCpuTicks[CentralProcessor.TickType.STEAL.getIndex()];
long total = user + nice + sys + idle + ioWait + irq + softIrq + steal; double total = user + nice + sys + idle + ioWait + irq + softIrq + steal;
if (0 < total) { if (0 < total) {
putDeque(context, context.getStatus().getCpu().getSystem(), 100D * sys / total); putDeque(context, context.getStatus().getCpu().getSystem(), sys / total);
putDeque(context, context.getStatus().getCpu().getUsed(), 100 - 100D * idle / total); putDeque(context, context.getStatus().getCpu().getUsed(), 1 - idle / total);
} }
} }
lastCpuTicks = ticks; lastCpuTicks = ticks;

View File

@@ -38,13 +38,13 @@ public class StorageStatusCollector implements StatusCollector {
item.setPartitionType(partition.getType()); item.setPartitionType(partition.getType());
item.setUuid(partition.getUuid()); item.setUuid(partition.getUuid());
item.setMountPoint(partition.getMountPoint()); item.setMountPoint(partition.getMountPoint());
item.setTotalBytes(partition.getSize()); item.setTotal(partition.getSize());
item.setTransferTimeMs(diskStore.getTransferTime()); item.setTransferTimeMs(diskStore.getTransferTime());
OSFileStore fileStore = matchFileStore(partition, fileStoreMap); OSFileStore fileStore = matchFileStore(partition, fileStoreMap);
if (fileStore != null) { if (fileStore != null) {
fileStore.updateAttributes(); fileStore.updateAttributes();
item.setUsedBytes(fileStore.getTotalSpace() - fileStore.getUsableSpace()); item.setUsed(fileStore.getTotalSpace() - fileStore.getUsableSpace());
} }
context.getStatus().getStoragePartitions().add(item); context.getStatus().getStoragePartitions().add(item);
} }

View File

@@ -18,6 +18,7 @@ import java.net.http.HttpResponse;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -73,6 +74,28 @@ public class DockerEngineClient {
} }
} }
/**
* 判断 Docker Engine 当前是否可访问。
*
* @return 可访问返回 true不可访问返回 false
*/
public boolean isAvailable() {
if (host.startsWith("unix://")) {
String socketPath = host.substring("unix://".length());
return Files.exists(Path.of(socketPath));
}
return true;
}
/**
* 获取 Docker Engine 主机配置。
*
* @return 主机配置字符串
*/
public String getHost() {
return host;
}
private String buildRequestPath(String path, Map<String, String> queryParams) { private String buildRequestPath(String path, Map<String, String> queryParams) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("/"); builder.append("/");

View File

@@ -101,9 +101,6 @@ public class SystemStatusDataView {
/** 启动时间 */ /** 启动时间 */
private long bootAt; private long bootAt;
/** 运行时长 */
private long uptimeMs;
} }
/** /**
@@ -125,10 +122,10 @@ public class SystemStatusDataView {
private int logicalCores; private int logicalCores;
/** 总占用 */ /** 总占用 */
private Double usagePercent; private Double usageTotal;
/** 系统占用 */ /** 系统占用 */
private Double systemPercent; private Double usageSystem;
/** 温度 */ /** 温度 */
private double temperatureCelsius; private double temperatureCelsius;
@@ -149,9 +146,6 @@ public class SystemStatusDataView {
/** 已用内存 */ /** 已用内存 */
private Long usedBytes; private Long usedBytes;
/** 使用率 */
private Double usagePercent;
/** 交换分区总量 */ /** 交换分区总量 */
private long swapTotalBytes; private long swapTotalBytes;
@@ -364,10 +358,10 @@ public class SystemStatusDataView {
private String mountPoint; private String mountPoint;
/** 分区总空间 */ /** 分区总空间 */
private long totalBytes; private long total;
/** 已用空间 */ /** 已用空间 */
private Long usedBytes; private Long used;
/** 使用率 */ /** 使用率 */
private Double usagePercent; private Double usagePercent;

View File

@@ -1,5 +1,9 @@
server: server:
shutdown: graceful shutdown: graceful
# 压缩
compression:
enable: true
min-response-size: 10KB
# 开发环境语言,激活开发配置时,多语言系统始终使用此语言环境 # 开发环境语言,激活开发配置时,多语言系统始终使用此语言环境
dev: dev:

View File

@@ -1,6 +1,5 @@
package test; package test;
import com.google.gson.Gson;
import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.blog.entity.Article; import com.imyeyu.api.modules.blog.entity.Article;
import com.imyeyu.api.modules.blog.mapper.ArticleMapper; import com.imyeyu.api.modules.blog.mapper.ArticleMapper;
@@ -118,6 +117,5 @@ public class SpringTest {
for (int i = 0; i < icon.size(); i++) { for (int i = 0; i < icon.size(); i++) {
map.put(icon.get(i).getName(), icon.get(i).getSvg()); map.put(icon.get(i).getName(), icon.get(i).getSvg());
} }
System.out.println(new Gson().toJson(map));
} }
} }