Skip to content

Commit

Permalink
[New] Added a statistics API for TX, search and services
Browse files Browse the repository at this point in the history
  • Loading branch information
eitch committed Oct 21, 2024
1 parent 3aa46a8 commit 2b28a30
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package li.strolch.agent.api;

import com.google.gson.JsonObject;
import li.strolch.persistence.api.TransactionResult;

import java.time.Duration;

public class AgentStatistics {

private final Statistics transactions;
private final Statistics searches;
private final Statistics services;

public AgentStatistics() {
this.transactions = new Statistics();
this.searches = new Statistics();
this.services = new Statistics();
}

public JsonObject toJson() {
JsonObject json = new JsonObject();
json.add("transactions", this.transactions.toJson());
json.add("searches", this.searches.toJson());
json.add("services", this.services.toJson());
return json;
}

public void recordTransaction(TransactionResult result) {
this.transactions.recordEvent(Duration.ofNanos(result.getTxDuration()));
}

public void recordSearch(long durationNanos) {
this.searches.recordEvent(Duration.ofNanos(durationNanos));
}

public void recordService(long durationNanos) {
this.services.recordEvent(Duration.ofNanos(durationNanos));
}
}
111 changes: 111 additions & 0 deletions strolch-agent/src/main/java/li/strolch/agent/api/Statistics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package li.strolch.agent.api;

import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.stream.Collectors;

import static java.time.temporal.ChronoUnit.*;

public class Statistics {

private final Queue<Statistic> events;

public Statistics() {
this.events = new LinkedList<>();
}

public void recordEvent(Duration duration) {
LocalDateTime now = LocalDateTime.now();
this.events.add(new Statistic(now, duration));
removeOldEvents(now);
}

private void removeOldEvents(LocalDateTime now) {
while (!this.events.isEmpty() && DAYS.between(this.events.peek().timestamp, now) >= 1) {
this.events.poll();
}
}

public int getLastMinute() {
return getInTimeFrame(1, MINUTES);
}

public int getLast5Minutes() {
return getInTimeFrame(5, MINUTES);
}

public int getLast15Minutes() {
return getInTimeFrame(15, MINUTES);
}

public int getLastHour() {
return getInTimeFrame(1, HOURS);
}

public int getLastDay() {
return getInTimeFrame(1, DAYS);
}

private int getInTimeFrame(long amount, ChronoUnit unit) {
LocalDateTime now = LocalDateTime.now();
return (int) this.events
.stream()
.filter(tx -> SECONDS.between(tx.timestamp, now) < unit.getDuration().getSeconds() * amount)
.count();
}

public JsonObject toJson() {
List<Duration> durations = this.events.stream().map(e -> e.duration).toList();

long count = durations.size();
long sum = durations.stream().mapToLong(Duration::toMillis).sum();
int avg = count == 0 ? 0 : (int) ((double) sum / count);

Duration min = durations.stream().min(Duration::compareTo).orElse(Duration.ZERO);
Duration max = durations.stream().max(Duration::compareTo).orElse(Duration.ZERO);
Duration median = getMedian(durations);

JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("lastMinute", getLastMinute());
jsonObject.addProperty("last5Minutes", getLast5Minutes());
jsonObject.addProperty("last15Minutes", getLast15Minutes());
jsonObject.addProperty("lastHour", getLastHour());
jsonObject.addProperty("lastDay", getLastDay());
jsonObject.addProperty("minDuration", min.toMillis());
jsonObject.addProperty("maxDuration", max.toMillis());
jsonObject.addProperty("avgDuration", avg);
jsonObject.addProperty("medianDuration", median.toMillis());

return jsonObject;
}

private Duration getMedian(List<Duration> durations) {
if (durations.isEmpty()) {
return Duration.ZERO;
}
List<Duration> sorted = durations.stream().sorted().toList();
int middle = sorted.size() / 2;
if (sorted.size() % 2 == 0) {
return sorted.get(middle - 1).plus(sorted.get(middle)).dividedBy(2);
} else {
return sorted.get(middle);
}
}

private static class Statistic {
LocalDateTime timestamp;
Duration duration;

Statistic(LocalDateTime timestamp, Duration duration) {
this.timestamp = timestamp;
this.duration = duration;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,15 @@ public class StrolchAgent {

private VersionQueryResult versionQueryResult;

private final AgentStatistics agentStatistics;

public StrolchAgent(StrolchVersion appVersion) {
this.appVersion = appVersion;
this.agentStatistics = new AgentStatistics();
}

public AgentStatistics getAgentStatistics() {
return this.agentStatistics;
}

public StrolchConfiguration getStrolchConfiguration() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
Expand Down Expand Up @@ -68,7 +69,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
private final MapOfMaps<String, String, Activity> activityCache;

private TransactionCloseStrategy closeStrategy;
private long globalSilentThreshold;
private final long globalSilentThreshold;
private long silentThreshold;
private boolean suppressUpdates;
private boolean suppressAudits;
Expand Down Expand Up @@ -107,7 +108,7 @@ public AbstractTransaction(ComponentContainer container, StrolchRealm realm, Cer
this.flushedCommands = new ArrayList<>();
this.lockedElements = new HashSet<>();
this.closeStrategy = readOnly ? TransactionCloseStrategy.READ_ONLY : TransactionCloseStrategy.DEFAULT;
this.txResult = new TransactionResult(getRealmName(), System.nanoTime(), new Date());
this.txResult = new TransactionResult(getRealmName(), System.nanoTime(), LocalDateTime.now());
this.txResult.setState(TransactionState.OPEN);

this.objectFilter = new ObjectFilter();
Expand Down Expand Up @@ -1746,6 +1747,9 @@ public void close() throws StrolchTransactionException {
} finally {
releaseElementLocks();
TransactionThreadLocal.removeTx();

// record the event
getAgent().getAgentStatistics().recordTransaction(this.txResult);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import li.strolch.model.activity.Activity;
import li.strolch.utils.collections.DateRange;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

Expand All @@ -26,7 +27,7 @@ public abstract class ArchiveTransaction implements AutoCloseable {
protected ArchiveTransaction(StrolchTransaction tx, DataArchiveHandler archiveHandler) {
this.tx = tx;
this.archiveHandler = archiveHandler;
this.txResult = new TransactionResult(tx.getRealmName(), System.nanoTime(), new Date());
this.txResult = new TransactionResult(tx.getRealmName(), System.nanoTime(), LocalDateTime.now());
}

public StrolchTransaction tx() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <[email protected]>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -17,7 +17,7 @@

import li.strolch.utils.helper.StringHelper;

import java.util.Date;
import java.time.LocalDateTime;

public class TransactionResult {

Expand All @@ -26,7 +26,7 @@ public class TransactionResult {
private Exception failCause;

private final long startNanos;
private Date startTime;
private LocalDateTime startTime;
private long txDuration;
private long closeDuration;

Expand All @@ -35,7 +35,7 @@ public class TransactionResult {
private long updated;
private long deleted;

public TransactionResult(String realm, long startNanos, Date startDate) {
public TransactionResult(String realm, long startNanos, LocalDateTime startDate) {
this.realm = realm;
this.startNanos = startNanos;
this.startTime = startDate;
Expand All @@ -57,8 +57,7 @@ public TransactionState getState() {
}

/**
* @param state
* the state to set
* @param state the state to set
*/
public void setState(TransactionState state) {
this.state = state;
Expand All @@ -72,8 +71,7 @@ public Exception getFailCause() {
}

/**
* @param failCause
* the failCause to set
* @param failCause the failCause to set
*/
public void setFailCause(Exception failCause) {
this.failCause = failCause;
Expand All @@ -89,15 +87,14 @@ public long getStartNanos() {
/**
* @return the startTime
*/
public Date getStartTime() {
public LocalDateTime getStartTime() {
return this.startTime;
}

/**
* @param startTime
* the startTime to set
* @param startTime the startTime to set
*/
public void setStartTime(Date startTime) {
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}

Expand All @@ -109,8 +106,7 @@ public long getTxDuration() {
}

/**
* @param txDuration
* the txDuration to set
* @param txDuration the txDuration to set
*/
public void setTxDuration(long txDuration) {
this.txDuration = txDuration;
Expand All @@ -124,8 +120,7 @@ public long getCloseDuration() {
}

/**
* @param closeDuration
* the closeDuration to set
* @param closeDuration the closeDuration to set
*/
public void setCloseDuration(long closeDuration) {
this.closeDuration = closeDuration;
Expand All @@ -139,8 +134,7 @@ public long getCreated() {
}

/**
* @param created
* the created to set
* @param created the created to set
*/
public void setCreated(long created) {
this.created = created;
Expand All @@ -154,8 +148,7 @@ public long getRead() {
}

/**
* @param read
* the read to set
* @param read the read to set
*/
public void setRead(long read) {
this.read = read;
Expand All @@ -169,8 +162,7 @@ public long getUpdated() {
}

/**
* @param updated
* the updated to set
* @param updated the updated to set
*/
public void setUpdated(long updated) {
this.updated = updated;
Expand All @@ -184,8 +176,7 @@ public long getDeleted() {
}

/**
* @param deleted
* the deleted to set
* @param deleted the deleted to set
*/
public void setDeleted(long deleted) {
this.deleted = deleted;
Expand Down Expand Up @@ -215,15 +206,15 @@ public String getLogMessage() {

StringBuilder sb = new StringBuilder();
switch (this.state) {
case OPEN -> sb.append("TX is still open after ");
case COMMITTED -> sb.append("TX was completed after ");
case ROLLED_BACK -> sb.append("TX was rolled back after ");
case FAILED -> sb.append("TX has failed after ");
default -> {
sb.append("TX is in unhandled state ");
sb.append(this.state);
sb.append(" after ");
}
case OPEN -> sb.append("TX is still open after ");
case COMMITTED -> sb.append("TX was completed after ");
case ROLLED_BACK -> sb.append("TX was rolled back after ");
case FAILED -> sb.append("TX has failed after ");
default -> {
sb.append("TX is in unhandled state ");
sb.append(this.state);
sb.append(" after ");
}
}

sb.append(StringHelper.formatNanoDuration(this.txDuration));
Expand Down
Loading

0 comments on commit 2b28a30

Please sign in to comment.