Code review comment: I think what you are proposing makes sense and the option (1) would be cleaner and in the spirit of unix style cpu usage reporting (where everything sums up to 100%). But this would most likely require _some_ Web UI changes:  1. Ideally we would need to find a good way to present the 4th task usage time metric. It might be easy, but maybe we will struggle to find a space to do it easily. 2. To limit Web UI changes, at the very least we could have a separate metric for network and state backend based backpressure, but somewhere we could add up those two values. For example either on the REST API level, or in the Web UI itself. (probably in WebUI would be a better option).

Code under review:
protected void processInput(MailboxDefaultAction.Controller controller) throws Exception {
        DataInputStatus status = inputProcessor.processInput();
        switch (status) {
            case MORE_AVAILABLE:
                if (recordWriter.isAvailable()
                        && changelogWriterAvailabilityProvider.isApproximatelyAvailable()) {
                    return;
                }
                break;
            case NOTHING_AVAILABLE:
                break;
            case END_OF_RECOVERY:
                throw new IllegalStateException("We should not receive this event here.");
            case END_OF_DATA:
                endData();
                return;
            case END_OF_INPUT:
                
                
                
                
                controller.suspendDefaultAction();
                mailboxProcessor.suspend();
                return;
        }

        TaskIOMetricGroup ioMetrics = getEnvironment().getMetricGroup().getIOMetricGroup();
        PeriodTimer timer;
        CompletableFuture<?> resumeFuture;
        if (!recordWriter.isAvailable()) {
            timer = new GaugePeriodTimer(ioMetrics.getBackPressuredTimePerSecond());
            resumeFuture = recordWriter.getAvailableFuture();
        } else if (!inputProcessor.isAvailable()) {
            timer =
                    new ThroughputPeriodTimer(
                            ioMetrics.getIdleTimeMsPerSecond(), throughputCalculator);
            resumeFuture = inputProcessor.getAvailableFuture();
        } else {
            
            timer = new GaugePeriodTimer(ioMetrics.getBackPressuredTimePerSecond());
            resumeFuture = changelogWriterAvailabilityProvider.getAvailableFuture();
        }
        assertNoException(
                resumeFuture.thenRun(
                        new ResumeWrapper(controller.suspendDefaultAction(timer), timer)));
    }


Revised code:
protected void processInput(MailboxDefaultAction.Controller controller) throws Exception {
        DataInputStatus status = inputProcessor.processInput();
        switch (status) {
            case MORE_AVAILABLE:
                if (recordWriter.isAvailable()
                        && (changelogWriterAvailabilityProvider == null
                                || changelogWriterAvailabilityProvider.isAvailable())) {
                    return;
                }
                break;
            case NOTHING_AVAILABLE:
                break;
            case END_OF_RECOVERY:
                throw new IllegalStateException("We should not receive this event here.");
            case END_OF_DATA:
                endData();
                return;
            case END_OF_INPUT:
                
                
                
                
                controller.suspendDefaultAction();
                mailboxProcessor.suspend();
                return;
        }

        TaskIOMetricGroup ioMetrics = getEnvironment().getMetricGroup().getIOMetricGroup();
        PeriodTimer timer;
        CompletableFuture<?> resumeFuture;
        if (!recordWriter.isAvailable()) {
            timer = new GaugePeriodTimer(ioMetrics.getBackPressuredTimePerSecond());
            resumeFuture = recordWriter.getAvailableFuture();
        } else if (!inputProcessor.isAvailable()) {
            timer = new GaugePeriodTimer(ioMetrics.getIdleTimeMsPerSecond());
            resumeFuture = inputProcessor.getAvailableFuture();
        } else {
            
            
            timer = null;
            resumeFuture = changelogWriterAvailabilityProvider.getAvailableFuture();
        }
        assertNoException(
                resumeFuture.thenRun(
                        new ResumeWrapper(controller.suspendDefaultAction(timer), timer)));
    }