class DefaultMetricsManager {
    private static final Logger logger = LoggerFactory.getLogger(DefaultMetricsManager.class);

    protected final CopyOnWriteArrayList<DefaultCacheMonitor> monitorList = new CopyOnWriteArrayList();

    private ScheduledFuture<?> future;

    private final int resetTime;
    private final TimeUnit resetTimeUnit;
    private final Consumer<StatInfo> metricsCallback;
    private final ReentrantLock reentrantLock = new ReentrantLock();

    public DefaultMetricsManager(int resetTime, TimeUnit resetTimeUnit, Consumer<StatInfo> metricsCallback) {
        this.resetTime = resetTime;
        this.resetTimeUnit = resetTimeUnit;
        this.metricsCallback = metricsCallback;
    }

    public DefaultMetricsManager(int resetTime, TimeUnit resetTimeUnit) {
        this(resetTime, resetTimeUnit, false);
    }

    public DefaultMetricsManager(int resetTime, TimeUnit resetTimeUnit, boolean verboseLog) {
        this.resetTime = resetTime;
        this.resetTimeUnit = resetTimeUnit;
        this.metricsCallback = new StatInfoLogger(verboseLog);
    }

    final Runnable cmd = new Runnable() {
        private long time = System.currentTimeMillis();

        @Override
        public void run() {
            try {
                List<CacheStat> stats = monitorList.stream().map((m) -> {
                    CacheStat stat = m.getCacheStat();
                    m.resetStat();
                    return stat;
                }).collect(Collectors.toList());

                long endTime = System.currentTimeMillis();
                StatInfo statInfo = new StatInfo();
                statInfo.setStartTime(time);
                statInfo.setEndTime(endTime);
                statInfo.setStats(stats);
                time = endTime;

                metricsCallback.accept(statInfo);
            } catch (Exception e) {
                logger.error("jetcache DefaultMetricsManager error", e);
            }
        }
    };


    public void start() {
        reentrantLock.lock();
        try {
            if (future != null) {
                return;
            }
            long delay = firstDelay(resetTime, resetTimeUnit);
            future = JetCacheExecutor.defaultExecutor().scheduleAtFixedRate(
                    cmd, delay, resetTimeUnit.toMillis(resetTime), TimeUnit.MILLISECONDS);
            logger.info("cache stat period at " + resetTime + " " + resetTimeUnit);
        }finally {
            reentrantLock.unlock();
        }
    }

    public void stop() {
        reentrantLock.lock();
        try {
            future.cancel(false);
            logger.info("cache stat canceled");
            future = null;
        }finally {
            reentrantLock.unlock();
        }
    }

    public void add(DefaultCacheMonitor... monitors) {
        monitorList.addAll(Arrays.asList(monitors));
    }

    public void clear() {
        monitorList.clear();
    }

    protected static long firstDelay(int resetTime, TimeUnit resetTimeUnit) {<FILL_FUNCTION_BODY>}

    protected static LocalDateTime computeFirstResetTime(LocalDateTime baseTime, int time, TimeUnit unit) {
        if (unit != TimeUnit.SECONDS && unit != TimeUnit.MINUTES && unit != TimeUnit.HOURS && unit != TimeUnit.DAYS) {
            throw new IllegalArgumentException();
        }
        LocalDateTime t = baseTime;
        switch (unit) {
            case DAYS:
                t = t.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
                break;
            case HOURS:
                if (24 % time == 0) {
                    t = t.plusHours(time - t.getHour() % time);
                } else {
                    t = t.plusHours(1);
                }
                t = t.withMinute(0).withSecond(0).withNano(0);
                break;
            case MINUTES:
                if (60 % time == 0) {
                    t = t.plusMinutes(time - t.getMinute() % time);
                } else {
                    t = t.plusMinutes(1);
                }
                t = t.withSecond(0).withNano(0);
                break;
            case SECONDS:
                if (60 % time == 0) {
                    t = t.plusSeconds(time - t.getSecond() % time);
                } else {
                    t = t.plusSeconds(1);
                }
                t = t.withNano(0);
                break;
        }
        return t;
    }


}

        LocalDateTime firstResetTime = computeFirstResetTime(LocalDateTime.now(), resetTime, resetTimeUnit);
        return firstResetTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - System.currentTimeMillis();
    
<no_super_class>