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 String dateSuffix
= Instant
.ofEpochMilli(begin
).atOffset(ZoneOffset
.UTC
)
132 .format(DateTimeFormatter
.ISO_LOCAL_DATE
) + "-" + begin
;
134 Path statisticsPath
= basePath
.resolve("statistics-" + unitName
+ "-" + dateSuffix
+ ".csv");
135 boolean writeHeader
= !Files
.exists(statisticsPath
);
136 if (!writeHeader
&& previousStat
== null)
138 "File " + statisticsPath
+ " exists, but we don't have previous statistics in memory");
140 try (Writer writer
= Files
.newBufferedWriter(statisticsPath
, StandardCharsets
.UTF_8
,
141 StandardOpenOption
.APPEND
, StandardOpenOption
.CREATE
)) {
142 CsvWriter csvWriter
= new CsvWriter(writer
);
144 if (writeHeader
)// header
145 csvWriter
.writeLine("CurrentTimeMillis", "CPUUsageNSec", "MemoryCurrent", "IPIngressBytes",
146 "IPEgressBytes", "IOReadBytes", "IOWriteBytes", "TasksCurrent");
148 Statistics s
= new Statistics(Instant
.now().toEpochMilli(), service
.getCPUUsageNSec(),
149 service
.getMemoryCurrent(), service
.getIPIngressBytes(), service
.getIPEgressBytes(),
150 service
.getIOReadBytes(), service
.getIOWriteBytes(), service
.getTasksCurrent());
152 if (s
.MemoryCurrent().compareTo(maxMemory
) > 0)
153 maxMemory
= s
.MemoryCurrent();
154 if (s
.TasksCurrent().compareTo(maxTasks
) > 0)
155 maxTasks
= s
.TasksCurrent();
157 Statistics diff
= Statistics
.diff(s
, previousStat
);
158 // TODO better synchronise with stop
159 csvWriter
.writeLine(diff
.CurrentTimeMillis(), diff
.CPUUsageNSec(), diff
.MemoryCurrent(),
160 diff
.IPIngressBytes(), diff
.IPEgressBytes(), diff
.IOReadBytes(), diff
.IOWriteBytes(),
161 diff
.TasksCurrent());
165 this.wait(frequency
);
166 } catch (InterruptedException e
) {
167 logger
.log(Level
.TRACE
, () -> "Statistics collection interrupted for " + unitName
);
171 } catch (IOException e
) {
172 throw new IllegalStateException("Cannot collect statistics", e
);
176 class StatisticsThread
extends Thread
{
178 public StatisticsThread() {
179 super("Statistics for " + unitName
);
189 private record
Statistics(long CurrentTimeMillis
, BigInteger CPUUsageNSec
, BigInteger MemoryCurrent
,
190 BigInteger IPIngressBytes
, BigInteger IPEgressBytes
, BigInteger IOReadBytes
, BigInteger IOWriteBytes
,
191 BigInteger TasksCurrent
) {
193 final static Statistics NULL
= new Statistics(0, BigInteger
.ZERO
, BigInteger
.ZERO
, BigInteger
.ZERO
,
194 BigInteger
.ZERO
, BigInteger
.ZERO
, BigInteger
.ZERO
, BigInteger
.ZERO
);
196 public static Statistics
diff(Statistics now
, Statistics previous
) {
197 if (previous
== null)
199 return new Statistics(now
.CurrentTimeMillis(), now
.CPUUsageNSec().subtract(previous
.CPUUsageNSec()),
200 now
.MemoryCurrent(), now
.IPIngressBytes().subtract(previous
.IPIngressBytes()),
201 now
.IPEgressBytes().subtract(previous
.IPEgressBytes()),
202 now
.IOReadBytes().subtract(previous
.IOReadBytes()),
203 now
.IOWriteBytes().subtract(previous
.IOWriteBytes()), now
.TasksCurrent());
207 public static void main(String
[] args
) throws Exception
{
209 Systemd systemd
= Systemd
.get();
210 Service service
= systemd
.getManager().getService("ipsec.service");
211 System
.out
.println(service
.getCPUUsageNSec());
213 for (UnitType unitType
: systemd
.getManager().listUnits()) {
214 if (unitType
.isService()) {
215 System
.out
.println(unitType
.getUnitName());
220 Systemd
.disconnect();