All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graylog2.periodical.ThrottleStateUpdaterThread Maven / Gradle / Ivy

There is a newer version: 6.1.4
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.periodical;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.RatioGauge;
import com.github.joschi.jadconfig.util.Size;
import com.google.common.eventbus.EventBus;
import org.graylog2.notifications.Notification;
import org.graylog2.notifications.NotificationService;
import org.graylog2.plugin.GlobalMetricNames;
import org.graylog2.plugin.ServerStatus;
import org.graylog2.plugin.ThrottleState;
import org.graylog2.plugin.periodical.Periodical;
import org.graylog2.shared.buffers.ProcessBuffer;
import org.graylog2.shared.journal.Journal;
import org.graylog2.shared.journal.LocalKafkaJournal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;

import static org.graylog2.shared.metrics.MetricUtils.safelyRegister;

/**
 * The ThrottleStateUpdater publishes the current state buffer state of the journal to other interested parties,
 * chiefly the ThrottleableTransports.
 * 

*

* It only includes the necessary information to make a decision about whether to throttle parts of the system, * but does not send "throttle" commands. This allows for a flexible approach in picking a throttling strategy. *

*

* The implementation expects to be called once per second to have a rough estimate about the events per second, * over the last second. *

*/ public class ThrottleStateUpdaterThread extends Periodical { private static final Logger log = LoggerFactory.getLogger(ThrottleStateUpdaterThread.class); private final LocalKafkaJournal journal; private final ProcessBuffer processBuffer; private final EventBus eventBus; private final Size retentionSize; private final NotificationService notificationService; private final ServerStatus serverStatus; private boolean firstRun = true; private long logEndOffset; private long currentReadOffset; private long currentTs; private ThrottleState throttleState; @Inject public ThrottleStateUpdaterThread(final Journal journal, ProcessBuffer processBuffer, EventBus eventBus, NotificationService notificationService, ServerStatus serverStatus, MetricRegistry metricRegistry, @Named("message_journal_max_size") Size retentionSize) { this.processBuffer = processBuffer; this.eventBus = eventBus; this.retentionSize = retentionSize; this.notificationService = notificationService; this.serverStatus = serverStatus; // leave this.journal null, we'll say "don't start" in that case, see startOnThisNode() below. if (journal instanceof LocalKafkaJournal) { this.journal = (LocalKafkaJournal) journal; } else { this.journal = null; } throttleState = new ThrottleState(); safelyRegister(metricRegistry, GlobalMetricNames.JOURNAL_APPEND_RATE, new Gauge() { @Override public Long getValue() { return throttleState.appendEventsPerSec; } }); safelyRegister(metricRegistry, GlobalMetricNames.JOURNAL_READ_RATE, new Gauge() { @Override public Long getValue() { return throttleState.readEventsPerSec; } }); safelyRegister(metricRegistry, GlobalMetricNames.JOURNAL_SEGMENTS, new Gauge() { @Override public Integer getValue() { if (ThrottleStateUpdaterThread.this.journal == null) { return 0; } return ThrottleStateUpdaterThread.this.journal.numberOfSegments(); } }); safelyRegister(metricRegistry, GlobalMetricNames.JOURNAL_UNCOMMITTED_ENTRIES, new Gauge() { @Override public Long getValue() { return throttleState.uncommittedJournalEntries; } }); final Gauge sizeGauge = safelyRegister(metricRegistry, GlobalMetricNames.JOURNAL_SIZE, new Gauge() { @Override public Long getValue() { return throttleState.journalSize; } }); final Gauge sizeLimitGauge = safelyRegister(metricRegistry, GlobalMetricNames.JOURNAL_SIZE_LIMIT, new Gauge() { @Override public Long getValue() { return throttleState.journalSizeLimit; } }); safelyRegister(metricRegistry, GlobalMetricNames.JOURNAL_UTILIZATION_RATIO, new RatioGauge() { @Override protected Ratio getRatio() { return Ratio.of(sizeGauge.getValue(), sizeLimitGauge.getValue()); } }); } @Override public boolean runsForever() { return false; } @Override public boolean stopOnGracefulShutdown() { return true; } @Override public boolean leaderOnly() { return false; } @Override public boolean startOnThisNode() { // don't start if we don't have the KafkaJournal return journal != null; } @Override public boolean isDaemon() { return true; } @Override public int getInitialDelaySeconds() { return 1; } @Override public int getPeriodSeconds() { return 1; } @Override protected Logger getLogger() { return log; } @Override public void doRun() { throttleState = new ThrottleState(throttleState); final long committedOffset = journal.getCommittedOffset(); // TODO there's a lot of duplication around this class. Probably should be refactored a bit. // also update metrics for each of the values, so clients can get to it cheaply long prevTs = currentTs; currentTs = System.nanoTime(); long previousLogEndOffset = logEndOffset; long previousReadOffset = currentReadOffset; long logStartOffset = journal.getLogStartOffset(); logEndOffset = journal.getLogEndOffset() - 1; // -1 because getLogEndOffset is the next offset that gets assigned currentReadOffset = journal.getNextReadOffset() - 1; // just to make it clear which field we read // for the first run, don't send an update, there's no previous data available to calc rates if (firstRun) { firstRun = false; return; } throttleState.appendEventsPerSec = (long) Math.floor((logEndOffset - previousLogEndOffset) / ((currentTs - prevTs) / 1.0E09)); throttleState.readEventsPerSec = (long) Math.floor((currentReadOffset - previousReadOffset) / ((currentTs - prevTs) / 1.0E09)); throttleState.journalSize = journal.size(); throttleState.journalSizeLimit = retentionSize.toBytes(); throttleState.processBufferCapacity = processBuffer.getRemainingCapacity(); if (committedOffset == LocalKafkaJournal.DEFAULT_COMMITTED_OFFSET) { // nothing committed at all, the entire log is uncommitted, or completely empty. throttleState.uncommittedJournalEntries = journal.size() == 0 ? 0 : logEndOffset - logStartOffset; } else { throttleState.uncommittedJournalEntries = logEndOffset - committedOffset; } log.debug("ThrottleState: {}", throttleState); // the journal needs this to provide information to rest clients journal.setThrottleState(throttleState); // publish to interested parties eventBus.post(throttleState); // Abusing the current thread to send notifications from KafkaJournal in the graylog2-shared module final double journalUtilizationPercentage = throttleState.journalSizeLimit > 0 ? (throttleState.journalSize * 100) / throttleState.journalSizeLimit : 0.0; if (journalUtilizationPercentage > LocalKafkaJournal.NOTIFY_ON_UTILIZATION_PERCENTAGE) { Notification notification = notificationService.buildNow() .addNode(serverStatus.getNodeId().toString()) .addType(Notification.Type.JOURNAL_UTILIZATION_TOO_HIGH) .addSeverity(Notification.Severity.URGENT) .addDetail("journal_utilization_percentage", journalUtilizationPercentage); notificationService.publishIfFirst(notification); } if (journal.getPurgedSegmentsInLastRetention() > 0) { Notification notification = notificationService.buildNow() .addNode(serverStatus.getNodeId().toString()) .addType(Notification.Type.JOURNAL_UNCOMMITTED_MESSAGES_DELETED) .addSeverity(Notification.Severity.URGENT); notificationService.publishIfFirst(notification); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy