1 package org
.argeo
.slc
.systemd
.dbus
;
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
;
8 import java
.io
.IOException
;
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
;
22 import org
.argeo
.cms
.util
.CsvWriter
;
23 import org
.freedesktop
.dbus
.exceptions
.DBusException
;
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
;
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());
34 private Manager manager
;
36 private String unitName
;
37 private Service service
;
39 private long frequency
= 60 * 1000;
43 private Statistics previousStat
= null;
45 private StatisticsThread statisticsThread
;
47 private Path basePath
;
49 private BigInteger maxMemory
= BigInteger
.ZERO
;
50 private BigInteger maxTasks
= BigInteger
.ZERO
;
53 begin
= Instant
.now().toEpochMilli();
54 final long pid
= ProcessHandle
.current().pid();
56 manager
= Systemd
.get().getManager();
58 for (UnitType unitType
: manager
.listUnits()) {
59 if (unitType
.isService()) {
60 Service s
= manager
.getService(unitType
.getUnitName());
61 if (s
.getMainPID() == pid
) {
63 unitName
= unitType
.getUnitName();
64 logger
.log(INFO
, "Systemd service found for pid " + pid
+ ": " + unitName
);
69 } catch (DBusException e
) {
70 logger
.log(WARNING
, "Cannot connect to systemd", e
);
73 if (service
== null) {
74 logger
.log(DEBUG
, () -> "No systemd service found for pid " + pid
+ ", disconnecting from DBus...");
78 // standard systemd property
79 String logsDirectory
= System
.getenv("LOGS_DIRECTORY");
80 if (logsDirectory
== null) {
81 logsDirectory
= System
.getProperty("user.dir");
83 // MainPID,CPUUsageNSec,MemoryCurrent,IPIngressBytes,IPEgressBytes,IOReadBytes,IOWriteBytes,TasksCurrent
84 basePath
= Paths
.get(logsDirectory
);
86 logger
.log(DEBUG
, () -> "Writing statistics for " + unitName
+ " to " + basePath
);
88 statisticsThread
= new StatisticsThread();
89 statisticsThread
.start();
94 if (manager
!= null) {
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
);
103 if (writeHeader
)// header
104 csvWriter
.writeLine("BeginTimeMillis", "EndTimeMillis", "CPUUsageNSec", "MaxMemory",
105 "IPIngressBytes", "IPEgressBytes", "IOReadBytes", "IOWriteBytes", "MaxTasks");
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
);
112 } catch (IOException e
) {
113 logger
.log(ERROR
, "Cannot write accounting to " + accountingPath
, e
);
117 synchronized (this) {
118 Systemd
.disconnect();
121 statisticsThread
.interrupt();
126 protected void collectStatistics() {
128 while (manager
!= null) {
129 synchronized (this) {
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
)
136 Path statisticsPath
= basePath
.resolve("statistics-" + unitName
+ "-" + dateSuffix
+ ".csv");
137 boolean writeHeader
= !Files
.exists(statisticsPath
);
138 if (!writeHeader
&& previousStat
== null)
140 "File " + statisticsPath
+ " exists, but we don't have previous statistics in memory");
142 try (Writer writer
= Files
.newBufferedWriter(statisticsPath
, StandardCharsets
.UTF_8
,
143 StandardOpenOption
.APPEND
, StandardOpenOption
.CREATE
)) {
144 CsvWriter csvWriter
= new CsvWriter(writer
);
146 if (writeHeader
)// header
147 csvWriter
.writeLine("CurrentTimeMillis", "MemoryCurrent", "CPUUsageNSec", "IPIngressBytes",
148 "IPEgressBytes", "IOReadBytes", "IOWriteBytes", "TasksCurrent");
150 Statistics s
= new Statistics(Instant
.now().toEpochMilli(), service
.getMemoryCurrent(),
151 service
.getCPUUsageNSec(), service
.getIPIngressBytes(), service
.getIPEgressBytes(),
152 service
.getIOReadBytes(), service
.getIOWriteBytes(), service
.getTasksCurrent());
154 if (s
.MemoryCurrent().compareTo(maxMemory
) > 0)
155 maxMemory
= s
.MemoryCurrent();
156 if (s
.TasksCurrent().compareTo(maxTasks
) > 0)
157 maxTasks
= s
.TasksCurrent();
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());
167 this.wait(frequency
);
168 } catch (InterruptedException e
) {
169 logger
.log(Level
.TRACE
, () -> "Statistics collection interrupted for " + unitName
);
173 } catch (IOException e
) {
174 throw new IllegalStateException("Cannot collect statistics", e
);
178 class StatisticsThread
extends Thread
{
180 public StatisticsThread() {
181 super("Statistics for " + unitName
);
191 private record
Statistics(long CurrentTimeMillis
, BigInteger MemoryCurrent
, BigInteger CPUUsageNSec
,
192 BigInteger IPIngressBytes
, BigInteger IPEgressBytes
, BigInteger IOReadBytes
, BigInteger IOWriteBytes
,
193 BigInteger TasksCurrent
) {
195 final static Statistics NULL
= new Statistics(0, BigInteger
.ZERO
, BigInteger
.ZERO
, BigInteger
.ZERO
,
196 BigInteger
.ZERO
, BigInteger
.ZERO
, BigInteger
.ZERO
, BigInteger
.ZERO
);
198 public static Statistics
diff(Statistics now
, Statistics previous
) {
199 if (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());