]> git.argeo.org Git - gpl/argeo-slc.git/blob - lib/linux/org.argeo.slc.systemd/src/org/argeo/slc/systemd/dbus/ServiceStatistics.java
Merge remote-tracking branch 'origin/unstable' into testing
[gpl/argeo-slc.git] / lib / linux / org.argeo.slc.systemd / src / org / argeo / slc / systemd / dbus / ServiceStatistics.java
1 package org.argeo.slc.systemd.dbus;
2
3 import static java.lang.System.Logger.Level.DEBUG;
4 import static java.lang.System.Logger.Level.ERROR;
5 import static java.lang.System.Logger.Level.INFO;
6 import static java.lang.System.Logger.Level.WARNING;
7
8 import java.io.IOException;
9 import java.io.Writer;
10 import java.lang.System.Logger;
11 import java.lang.System.Logger.Level;
12 import java.math.BigInteger;
13 import java.nio.charset.StandardCharsets;
14 import java.nio.file.Files;
15 import java.nio.file.Path;
16 import java.nio.file.Paths;
17 import java.nio.file.StandardOpenOption;
18 import java.time.Instant;
19 import java.time.ZoneOffset;
20 import java.time.format.DateTimeFormatter;
21
22 import org.argeo.cms.util.CsvWriter;
23 import org.freedesktop.dbus.exceptions.DBusException;
24
25 import de.thjom.java.systemd.Manager;
26 import de.thjom.java.systemd.Service;
27 import de.thjom.java.systemd.Systemd;
28 import de.thjom.java.systemd.types.UnitType;
29
30 /** Gathers statistics about the runnign process, if it is a systemd unit. */
31 public class ServiceStatistics {
32 private final static Logger logger = System.getLogger(ServiceStatistics.class.getName());
33
34 private Manager manager;
35
36 private String unitName;
37 private Service service;
38
39 private long frequency = 60 * 1000;
40
41 private long begin;
42
43 private Statistics previousStat = null;
44
45 private StatisticsThread statisticsThread;
46
47 private Path basePath;
48
49 private BigInteger maxMemory = BigInteger.ZERO;
50 private BigInteger maxTasks = BigInteger.ZERO;
51
52 public void start() {
53 begin = Instant.now().toEpochMilli();
54 final long pid = ProcessHandle.current().pid();
55 try {
56 manager = Systemd.get().getManager();
57 // find own service
58 for (UnitType unitType : manager.listUnits()) {
59 if (unitType.isService()) {
60 Service s = manager.getService(unitType.getUnitName());
61 if (s.getMainPID() == pid) {
62 service = s;
63 unitName = unitType.getUnitName();
64 logger.log(INFO, "Systemd service found for pid " + pid + ": " + unitName);
65 }
66 }
67 }
68
69 } catch (DBusException e) {
70 logger.log(WARNING, "Cannot connect to systemd", e);
71 }
72
73 if (service == null) {
74 logger.log(DEBUG, () -> "No systemd service found for pid " + pid + ", disconnecting from DBus...");
75 manager = null;
76 Systemd.disconnect();
77 } else {
78 // standard systemd property
79 String logsDirectory = System.getenv("LOGS_DIRECTORY");
80 if (logsDirectory == null) {
81 logsDirectory = System.getProperty("user.dir");
82 }
83 // MainPID,CPUUsageNSec,MemoryCurrent,IPIngressBytes,IPEgressBytes,IOReadBytes,IOWriteBytes,TasksCurrent
84 basePath = Paths.get(logsDirectory);
85
86 logger.log(DEBUG, () -> "Writing statistics for " + unitName + " to " + basePath);
87 // start collecting
88 statisticsThread = new StatisticsThread();
89 statisticsThread.start();
90 }
91 }
92
93 public void stop() {
94 if (manager != null) {
95 // write accounting
96 Path accountingPath = basePath.resolve("accounting-" + unitName + ".csv");
97 logger.log(INFO, () -> "Writing accounting for " + unitName + " to " + accountingPath);
98 boolean writeHeader = !Files.exists(accountingPath);
99 try (Writer writer = Files.newBufferedWriter(accountingPath, StandardCharsets.UTF_8,
100 StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
101 CsvWriter csvWriter = new CsvWriter(writer);
102
103 if (writeHeader)// header
104 csvWriter.writeLine("BeginTimeMillis", "EndTimeMillis", "CPUUsageNSec", "MaxMemory",
105 "IPIngressBytes", "IPEgressBytes", "IOReadBytes", "IOWriteBytes", "MaxTasks");
106
107 // TODO better synchronise with stop
108 csvWriter.writeLine(begin, System.currentTimeMillis(), service.getCPUUsageNSec(), maxMemory,
109 service.getIPIngressBytes(), service.getIPEgressBytes(), service.getIOReadBytes(),
110 service.getIOWriteBytes(), maxTasks);
111 writer.flush();
112 } catch (IOException e) {
113 logger.log(ERROR, "Cannot write accounting to " + accountingPath, e);
114 }
115
116 // disconnect
117 synchronized (this) {
118 Systemd.disconnect();
119 manager = null;
120 notifyAll();
121 statisticsThread.interrupt();
122 }
123 }
124 }
125
126 protected void collectStatistics() {
127 try {
128 while (manager != null) {
129 synchronized (this) {
130
131 // We change the prefix in order to have a file per day
132 // but keep the begin timestamp in order to identify restarts
133 String dateSuffix = Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)
134 + "-" + begin;
135
136 Path statisticsPath = basePath.resolve("statistics-" + unitName + "-" + dateSuffix + ".csv");
137 boolean writeHeader = !Files.exists(statisticsPath);
138 if (!writeHeader && previousStat == null)
139 logger.log(ERROR,
140 "File " + statisticsPath + " exists, but we don't have previous statistics in memory");
141
142 try (Writer writer = Files.newBufferedWriter(statisticsPath, StandardCharsets.UTF_8,
143 StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
144 CsvWriter csvWriter = new CsvWriter(writer);
145
146 if (writeHeader)// header
147 csvWriter.writeLine("CurrentTimeMillis", "MemoryCurrent", "CPUUsageNSec", "IPIngressBytes",
148 "IPEgressBytes", "IOReadBytes", "IOWriteBytes", "TasksCurrent");
149
150 Statistics s = new Statistics(Instant.now().toEpochMilli(), service.getMemoryCurrent(),
151 service.getCPUUsageNSec(), service.getIPIngressBytes(), service.getIPEgressBytes(),
152 service.getIOReadBytes(), service.getIOWriteBytes(), service.getTasksCurrent());
153
154 if (s.MemoryCurrent().compareTo(maxMemory) > 0)
155 maxMemory = s.MemoryCurrent();
156 if (s.TasksCurrent().compareTo(maxTasks) > 0)
157 maxTasks = s.TasksCurrent();
158
159 Statistics diff = Statistics.diff(s, previousStat);
160 // TODO better synchronise with stop
161 csvWriter.writeLine(diff.CurrentTimeMillis(), diff.MemoryCurrent(), diff.CPUUsageNSec(),
162 diff.IPIngressBytes(), diff.IPEgressBytes(), diff.IOReadBytes(), diff.IOWriteBytes(),
163 diff.TasksCurrent());
164 previousStat = s;
165 }
166 try {
167 this.wait(frequency);
168 } catch (InterruptedException e) {
169 logger.log(Level.TRACE, () -> "Statistics collection interrupted for " + unitName);
170 }
171 }
172 }
173 } catch (IOException e) {
174 throw new IllegalStateException("Cannot collect statistics", e);
175 }
176 }
177
178 class StatisticsThread extends Thread {
179
180 public StatisticsThread() {
181 super("Statistics for " + unitName);
182 }
183
184 @Override
185 public void run() {
186 collectStatistics();
187 }
188
189 }
190
191 private record Statistics(long CurrentTimeMillis, BigInteger MemoryCurrent, BigInteger CPUUsageNSec,
192 BigInteger IPIngressBytes, BigInteger IPEgressBytes, BigInteger IOReadBytes, BigInteger IOWriteBytes,
193 BigInteger TasksCurrent) {
194
195 final static Statistics NULL = new Statistics(0, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO,
196 BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO);
197
198 public static Statistics diff(Statistics now, Statistics previous) {
199 if (previous == null)
200 previous = NULL;
201 return new Statistics(now.CurrentTimeMillis(), now.MemoryCurrent(),
202 now.CPUUsageNSec().subtract(previous.CPUUsageNSec()),
203 now.IPIngressBytes().subtract(previous.IPIngressBytes()),
204 now.IPEgressBytes().subtract(previous.IPEgressBytes()),
205 now.IOReadBytes().subtract(previous.IOReadBytes()),
206 now.IOWriteBytes().subtract(previous.IOWriteBytes()), now.TasksCurrent());
207 }
208 }
209 }