Skip to content

Commit

Permalink
Add online and offline sessions metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
OleksandrMishchuk committed Nov 11, 2024
1 parent 0331363 commit 198ebe6
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 76 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,25 @@ This counter counts the number of response errors (responses where the http stat
keycloak_response_errors{code="500",method="GET",} 1
```
##### keycloak_online_sessions
This gauge indicates the number of online sessions.
```c
# HELP keycloak_online_sessions Total online sessions
# TYPE keycloak_online_sessions gauge
keycloak_online_sessions{realm="test",client_id="application1",} 1.0
```

##### keycloak_offline_sessions
This gauge indicates the number of offline sessions.

```c
# HELP keycloak_offline_sessions Total offline sessions
# TYPE keycloak_offline_sessions gauge
keycloak_offline_sessions{realm="test",client_id="application1",} 1.0
```
#### Metrics URI
The URI can be added to the metrics by setting the environment variable ```URI_METRICS_ENABLED``` to `true`.
This will output a consolidated realm URI value to the metrics. The realm value is replaced with a generic `{realm}` value
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
<build>
<resources>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,64 +4,70 @@
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.KeycloakSession;

public class MetricsEventListener implements EventListenerProvider {

public final static String ID = "metrics-listener";

private final static Logger logger = Logger.getLogger(MetricsEventListener.class);
private final RealmProvider realmProvider;
private final KeycloakSession keycloakSession;

public MetricsEventListener(RealmProvider realmProvider) {
this.realmProvider = realmProvider;
public MetricsEventListener(KeycloakSession keycloakSession) {
this.keycloakSession = keycloakSession;
}


@Override
public void onEvent(Event event) {
logEventDetails(event);

switch (event.getType()) {
case LOGIN:
PrometheusExporter.instance().recordLogin(event, realmProvider);
PrometheusExporter.instance().recordLogin(event, keycloakSession.realms());
PrometheusExporter.instance().recordSessions(event, keycloakSession);
break;
case CLIENT_LOGIN:
PrometheusExporter.instance().recordClientLogin(event, realmProvider);
PrometheusExporter.instance().recordClientLogin(event, keycloakSession.realms());
PrometheusExporter.instance().recordSessions(event, keycloakSession);
break;
case LOGOUT:
PrometheusExporter.instance().recordSessions(event, keycloakSession);
break;
case REGISTER:
PrometheusExporter.instance().recordRegistration(event, realmProvider);
PrometheusExporter.instance().recordRegistration(event, keycloakSession.realms());
break;
case REFRESH_TOKEN:
PrometheusExporter.instance().recordRefreshToken(event, realmProvider);
PrometheusExporter.instance().recordRefreshToken(event, keycloakSession.realms());
break;
case CODE_TO_TOKEN:
PrometheusExporter.instance().recordCodeToToken(event, realmProvider);
PrometheusExporter.instance().recordCodeToToken(event, keycloakSession.realms());
break;
case REGISTER_ERROR:
PrometheusExporter.instance().recordRegistrationError(event, realmProvider);
PrometheusExporter.instance().recordRegistrationError(event, keycloakSession.realms());
break;
case LOGIN_ERROR:
PrometheusExporter.instance().recordLoginError(event, realmProvider);
PrometheusExporter.instance().recordLoginError(event, keycloakSession.realms());
break;
case CLIENT_LOGIN_ERROR:
PrometheusExporter.instance().recordClientLoginError(event, realmProvider);
PrometheusExporter.instance().recordClientLoginError(event, keycloakSession.realms());
break;
case REFRESH_TOKEN_ERROR:
PrometheusExporter.instance().recordRefreshTokenError(event, realmProvider);
PrometheusExporter.instance().recordRefreshTokenError(event, keycloakSession.realms());
break;
case CODE_TO_TOKEN_ERROR:
PrometheusExporter.instance().recordCodeToTokenError(event, realmProvider);
PrometheusExporter.instance().recordCodeToTokenError(event, keycloakSession.realms());
break;
default:
PrometheusExporter.instance().recordGenericEvent(event, realmProvider);
PrometheusExporter.instance().recordGenericEvent(event, keycloakSession.realms());
}
}

@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
logAdminEventDetails(event);

PrometheusExporter.instance().recordGenericAdminEvent(event, realmProvider);
PrometheusExporter.instance().recordGenericAdminEvent(event, keycloakSession.realms());
}

private void logEventDetails(Event event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class MetricsEventListenerFactory implements EventListenerProviderFactory

@Override
public EventListenerProvider create(KeycloakSession session) {
return new MetricsEventListener(session.realms());
return new MetricsEventListener(session);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
import io.prometheus.client.Gauge;
import io.prometheus.client.Histogram;
import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory;
import io.prometheus.client.exporter.PushGateway;
import io.prometheus.client.exporter.common.TextFormat;
import io.prometheus.client.hotspot.DefaultExports;
import org.apache.commons.collections4.MapUtils;
import org.jboss.logging.Logger;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;

Expand Down Expand Up @@ -63,6 +67,8 @@ public final class PrometheusExporter {
final Counter responseTotal;
final Counter responseErrors;
final Histogram requestDuration;
final Gauge totalOnlineSessions;
final Gauge totalOfflineSessions;
final PushGateway PUSH_GATEWAY;

private PrometheusExporter() {
Expand Down Expand Up @@ -152,6 +158,18 @@ private PrometheusExporter() {
.labelNames("realm", "provider", "error", "client_id")
.register();

totalOnlineSessions = Gauge.build()
.name("keycloak_online_sessions")
.help("Total online sessions")
.labelNames("realm", "provider", "client_id")
.register();

totalOfflineSessions = Gauge.build()
.name("keycloak_offline_sessions")
.help("Total offline sessions")
.labelNames("realm", "provider", "client_id")
.register();

final boolean URI_METRICS_ENABLED = Boolean.parseBoolean(System.getenv("URI_METRICS_ENABLED"));
if (URI_METRICS_ENABLED){
responseTotal = Counter.build()
Expand Down Expand Up @@ -280,6 +298,25 @@ public void recordLogin(final Event event, RealmProvider realmProvider) {
pushAsync();
}

public void recordSessions(final Event event, final KeycloakSession keycloakSession) {
final String provider = getIdentityProvider(event);
final RealmModel realmModel = keycloakSession.realms().getRealm(event.getRealmId());
final ClientModel clientModel = keycloakSession.clients().getClientByClientId(realmModel, event.getClientId());

if(clientModel != null) {
final Map<String, Long> onlineClientSessionStats = keycloakSession.sessions().getActiveClientSessionStats(realmModel, false);
final Optional<Long> onlineSessionCount = Optional.ofNullable(MapUtils.emptyIfNull(onlineClientSessionStats).get(clientModel.getId()));

final Map<String, Long> offlineClientSessionStats = keycloakSession.sessions().getActiveClientSessionStats(realmModel, true);
final Optional<Long> offlineSessionCount = Optional.ofNullable(MapUtils.emptyIfNull(offlineClientSessionStats).get(clientModel.getId()));

totalOnlineSessions.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getClientId())).set(onlineSessionCount.orElse(0L));
totalOfflineSessions.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getClientId())).set(offlineSessionCount.orElse(0L));

pushAsync();
}
}

/**
* Increase the number registered users
*
Expand Down
Loading

0 comments on commit 198ebe6

Please sign in to comment.