diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..535c157 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +target/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..639dcff --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3' + +services: + keycloak: + image: quay.io/keycloak/keycloak:21.0.2 + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_HTTP_RELATIVE_PATH: /auth + KC_HEALTH_ENABLED: true + KC_METRICS_ENABLED: true + ports: + - 8080:8080 + volumes: + - ./target/keycloak-event-metrics-0.2.1-SNAPSHOT.jar:/opt/keycloak/providers/keycloak-event-metrics.jar + command: [ "start-dev" ] diff --git a/src/main/java/io/kokuwa/keycloak/metrics/MicrometerEventListenerFactory.java b/src/main/java/io/kokuwa/keycloak/metrics/MicrometerEventListenerFactory.java index 8553df6..298ab6d 100644 --- a/src/main/java/io/kokuwa/keycloak/metrics/MicrometerEventListenerFactory.java +++ b/src/main/java/io/kokuwa/keycloak/metrics/MicrometerEventListenerFactory.java @@ -1,15 +1,18 @@ package io.kokuwa.keycloak.metrics; +import com.google.common.collect.ImmutableList; +import io.micrometer.core.instrument.ImmutableTag; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; import javax.enterprise.inject.spi.CDI; - import org.jboss.logging.Logger; import org.keycloak.Config.Scope; import org.keycloak.events.EventListenerProvider; import org.keycloak.events.EventListenerProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; - -import io.micrometer.core.instrument.MeterRegistry; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.timer.TimerProvider; /** * Factory for {@link MicrometerEventListener}, uses {@link MeterRegistry} from CDI. @@ -18,13 +21,15 @@ import io.micrometer.core.instrument.MeterRegistry; */ public class MicrometerEventListenerFactory implements EventListenerProviderFactory { + private static final String PROVIDER_ID = "metrics-listener"; + private static final int INTERVAL = 60 * 1000; //1 MINUTE private static final Logger log = Logger.getLogger(MicrometerEventListener.class); private MeterRegistry registry; private boolean replace; @Override public String getId() { - return "metrics-listener"; + return PROVIDER_ID; } @Override @@ -36,6 +41,17 @@ public class MicrometerEventListenerFactory implements EventListenerProviderFact @Override public void postInit(KeycloakSessionFactory factory) { registry = CDI.current().select(MeterRegistry.class).get(); + + KeycloakModelUtils.runJobInTransaction(factory, s1 -> { + TimerProvider timer = s1.getProvider(TimerProvider.class); + log.info("Registering gauge update job TimerProvider"); + timer.schedule(() -> { + KeycloakModelUtils.runJobInTransaction(s1.getKeycloakSessionFactory(), s2 -> { + log.info("Updating gauges"); + updateGauges(s2); + }); + }, INTERVAL, PROVIDER_ID); + }); } @Override @@ -45,4 +61,49 @@ public class MicrometerEventListenerFactory implements EventListenerProviderFact @Override public void close() {} + + + private Iterable tags(String... tags) { + if (tags.length % 2 != 0) throw new IllegalStateException("Tag name value pairs must be even"); + ImmutableList.Builder builder = new ImmutableList.Builder(); + for (int i = 0;i < tags.length;i+=2) { + builder.add(new ImmutableTag(tags[i], tags[i+1])); + } + return builder.build(); + } + + private void updateGauges(KeycloakSession session) { + session.realms().getRealmsStream().forEach(realm -> { + // keycloak_users = total number of users + registry.gauge("keycloak_users", + tags("realm", replace ? realm.getName() : realm.getId()), + session.users().getUsersCount(realm)); + + // keycloak_clients = total number of clients + registry.gauge("keycloak_clients", + tags("realm", replace ? realm.getName() : realm.getId()), + realm.getClientsCount()); + + // sessions - by client + + realm.getClientsStream().forEach(client -> { + // keycloak_active_user_sessions + registry.gauge("keycloak_active_user_sessions", + tags("realm", replace ? realm.getName() : realm.getId(), + "client", replace ? client.getClientId() : client.getId()), + session.sessions().getActiveUserSessions(realm, client)); + // keycloak_active_client_sessions + registry.gauge("keycloak_active_client_sessions", + tags("realm", replace ? realm.getName() : realm.getId(), + "client", replace ? client.getClientId() : client.getId()), + session.sessions().getActiveClientSessionStats(realm,false).get(client.getId())); + + // keycloak_offline_sessions + registry.gauge("keycloak_offline_sessions", + tags("realm", replace ? realm.getName() : realm.getId(), + "client", replace ? client.getClientId() : client.getId()), + session.sessions().getOfflineSessionsCount(realm, client)); + }); + }); + } }