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

com.weibo.rill.flow.service.util.ProfileUtil Maven / Gradle / Ivy

There is a newer version: 0.1.18
Show newest version
/*
 *  Copyright 2021-2023 Weibo, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.weibo.rill.flow.service.util;

import com.weibo.rill.flow.common.constant.AccessStatisticResult;
import com.weibo.rill.flow.common.constant.ProfileConstants;
import com.weibo.rill.flow.common.model.ProfileType;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DecimalFormat;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 监控统计类, 为了解除对api-commons的依赖, borrow code from
 * api-commons#com.weibo.rill.flow.commons.profile#ProfileUtil
 *
 * @author liyan23
 */
public class ProfileUtil {
    private static final Logger log = LoggerFactory.getLogger(ProfileUtil.class);
    private static final Logger profileLogger = LoggerFactory.getLogger("profile");

    private static final String SEPARATE = "\\|";

    private static ScheduledExecutorService executorService;
    private static ConcurrentMap accessStatistics = new ConcurrentHashMap<>();

    static {
        startLogging();
        Runtime.getRuntime().addShutdownHook(new Thread(ProfileUtil::stopLogging));
    }

    public static void startLogging() {
        stopLogging();
        executorService = Executors.newSingleThreadScheduledExecutor();
        // access statistic
        executorService.scheduleAtFixedRate(() -> logAccessStatistic(false), ProfileConstants.STATISTIC_PERIOD,
                ProfileConstants.STATISTIC_PERIOD, TimeUnit.SECONDS);
    }

    public static void stopLogging() {
        Optional.ofNullable(executorService).ifPresent(ExecutorService::shutdown);
        executorService = null;
        logAccessStatistic(true); // the last log
    }

    public static void accessStatistic(ProfileType profileType, String name, long currentTimeMillis, long costTimeMillis) {
        String key = profileType.getType() + "|" + name;

        try {
            AccessStatisticItem item = getStatisticItem(key, profileType, currentTimeMillis);
            item.statistic(currentTimeMillis, costTimeMillis);
        } catch (Exception e) {
            log.error("ProfileUtil accessStatistic error, type=" + profileType.getType() + ", name=" + name, e);
        }
    }

    private static AccessStatisticItem getStatisticItem(String key, ProfileType profileType, long currentTime) {
        AccessStatisticItem item = accessStatistics.get(key);

        if (item == null) {
            accessStatistics.putIfAbsent(key, new CostStatistic(profileType, currentTime, ProfileConstants.STATISTIC_PERIOD * 2));
            item = accessStatistics.get(key);
        }

        return item;
    }

    public static void count(ProfileType profileType, String name, long currentTimeMillis, int count) {
        String key = profileType.getType() + "|" + name;

        try {
            AccessStatisticItem item = getCounter(key, profileType, currentTimeMillis);
            item.statistic(currentTimeMillis, count);
        } catch (Exception e) {
            log.error("ProfileUtil count error, type=" + profileType.getType() + ", name=" + name, e);
        }

    }

    private static AccessStatisticItem getCounter(final String key, final ProfileType profileType, final long currentTimeMillis) {
        AccessStatisticItem item = accessStatistics.get(key);
        if (item == null) {
            accessStatistics.putIfAbsent(key, new Counter(profileType, currentTimeMillis, ProfileConstants.STATISTIC_PERIOD * 2));
            item = accessStatistics.get(key);
        }
        return item;
    }

    private static void logAccessStatistic(final boolean all) {
        DecimalFormat mbFormat = new DecimalFormat("#0.00");
        long currentTimeMillis = System.currentTimeMillis();

        for (Map.Entry entry : accessStatistics.entrySet()) {
            AccessStatisticItem item = entry.getValue();

            AccessStatisticResult result = all
                    ? item.getStatisticResult(currentTimeMillis + 1000, ProfileConstants.STATISTIC_PERIOD + 1)
                    : item.getStatisticResult(currentTimeMillis, ProfileConstants.STATISTIC_PERIOD);

            item.clearStatistic(currentTimeMillis, ProfileConstants.STATISTIC_PERIOD);

            String key = entry.getKey();
            String[] keys = key.split(SEPARATE);
            if (keys.length != 2) {
                continue;
            }
            String type = keys[0];
            String name = keys[1];

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("type", type);
            jsonObject.put("name", name);
            jsonObject.put("slowThreshold", result.slowThreshold);
            if (result.totalCount == 0) {
                jsonObject.put("total_count", 0);
                jsonObject.put("slow_count", 0);
                jsonObject.put("avg_time", "0.00");
                jsonObject.put("interval1", 0);
                jsonObject.put("interval2", 0);
                jsonObject.put("interval3", 0);
                jsonObject.put("interval4", 0);
                jsonObject.put("interval5", 0);

            } else {
                jsonObject.put("total_count", result.totalCount);
                jsonObject.put("slow_count", result.slowCount);
                jsonObject.put("avg_time", mbFormat.format(result.costTime / result.totalCount));
                jsonObject.put("interval1", result.intervalCounts[0]);
                jsonObject.put("interval2", result.intervalCounts[1]);
                jsonObject.put("interval3", result.intervalCounts[2]);
                jsonObject.put("interval4", result.intervalCounts[3]);
                jsonObject.put("interval5", result.intervalCounts[4]);
            }
            String monitorInfo = jsonObject.toString();
            profileLogger.info(monitorInfo);
        }
    }

    public static abstract class AccessStatisticItem {
        volatile int currentIndex;
        AtomicInteger[] costTimes = null;
        AtomicInteger[] totalCounter = null;
        AtomicInteger[] slowCounter = null;
        int length;
        AtomicInteger[] interval1 = null;
        AtomicInteger[] interval2 = null;
        AtomicInteger[] interval3 = null;
        AtomicInteger[] interval4 = null;
        AtomicInteger[] interval5 = null;
        ProfileType profileType = null;

        public AccessStatisticItem(ProfileType profileType, long currentTimeMillis) {
            this(profileType, currentTimeMillis, ProfileConstants.STATISTIC_PERIOD * 2);
        }

        public AccessStatisticItem(ProfileType profileType, long currentTimeMillis, int length) {
            this.costTimes = initAtomicIntegerArr(length);
            this.totalCounter = initAtomicIntegerArr(length);
            this.slowCounter = initAtomicIntegerArr(length);
            this.length = length;
            this.interval1 = initAtomicIntegerArr(length);
            this.interval2 = initAtomicIntegerArr(length);
            this.interval3 = initAtomicIntegerArr(length);
            this.interval4 = initAtomicIntegerArr(length);
            this.interval5 = initAtomicIntegerArr(length);
            this.currentIndex = getIndex(currentTimeMillis, length);
            this.profileType = profileType;
        }

        private AtomicInteger[] initAtomicIntegerArr(int size) {
            AtomicInteger[] arrs = new AtomicInteger[size];
            for (int i = 0; i < arrs.length; i++) {
                arrs[i] = new AtomicInteger(0);
            }

            return arrs;
        }

        /**
         *
         * @param currentTimeMillis 此刻记录的时间 (ms)
         * @param value 这次操作的耗时 (ms)
         */
        void statistic(long currentTimeMillis, long value) {
            int currentIndex = getIndex(currentTimeMillis, length);

            ensureInitSlot(currentIndex);

            doStatistic(currentIndex, (int) value);
        }

        protected abstract void doStatistic(final int currentIndex, final int value);

        private void ensureInitSlot(final int tempIndex) {
            if (currentIndex != tempIndex) {
                synchronized (this) {
                    // 这一秒的第一条统计,把对应的存储位的数据置0
                    if (currentIndex != tempIndex) {
                        reset(tempIndex);
                        currentIndex = tempIndex;
                    }
                }
            }
        }

        private int getIndex(long currentTimeMillis, int periodSecond) {
            return (int) ((currentTimeMillis / 1000) % periodSecond);
        }

        private void reset(int index) {
            costTimes[index].set(0);
            totalCounter[index].set(0);
            slowCounter[index].set(0);
            interval1[index].set(0);
            interval2[index].set(0);
            interval3[index].set(0);
            interval4[index].set(0);
            interval5[index].set(0);
        }

        AccessStatisticResult getStatisticResult(long currentTimeMillis, int peroidSecond) {
            long currentTimeSecond = currentTimeMillis / 1000;
            currentTimeSecond--; // 当前这秒还没完全结束,因此数据不全,统计从上一秒开始,往前推移peroidSecond

            int startIndex = getIndex(currentTimeSecond * 1000, length);

            AccessStatisticResult result = new AccessStatisticResult();

            result.slowThreshold = profileType.getSlowThreshold();
            for (int i = 0; i < peroidSecond; i++) {
                int currentIndex = (startIndex - i + length) % length;

                result.costTime += costTimes[currentIndex].get();
                result.totalCount += totalCounter[currentIndex].get();
                result.slowCount += slowCounter[currentIndex].get();
                result.intervalCounts[0] += interval1[currentIndex].get();
                result.intervalCounts[1] += interval2[currentIndex].get();
                result.intervalCounts[2] += interval3[currentIndex].get();
                result.intervalCounts[3] += interval4[currentIndex].get();
                result.intervalCounts[4] += interval5[currentIndex].get();
                if (totalCounter[currentIndex].get() > result.maxCount) {
                    result.maxCount = totalCounter[currentIndex].get();
                } else if (totalCounter[currentIndex].get() < result.minCount || result.minCount == -1) {
                    result.minCount = totalCounter[currentIndex].get();
                }
            }

            return result;
        }

        void clearStatistic(long currentTimeMillis, int periodSecond) {
            long currentTimeSecond = currentTimeMillis / 1000;
            currentTimeSecond--; // 当前这秒还没完全结束,因此数据不全,统计从上一秒开始,往前推移peroidSecond

            int startIndex = getIndex(currentTimeSecond * 1000, length);

            for (int i = 0; i < periodSecond; i++) {
                int currentIndex = (startIndex - i + length) % length;

                reset(currentIndex);
            }
        }
    }

    static class CostStatistic extends AccessStatisticItem {

        public CostStatistic(final ProfileType profileType, final long currentTimeMillis) {
            super(profileType, currentTimeMillis);
        }

        public CostStatistic(final ProfileType profileType, final long currentTimeMillis, final int length) {
            super(profileType, currentTimeMillis, length);
        }

        @Override
        protected void doStatistic(final int currentIndex, final int value) {
            costTimes[currentIndex].addAndGet(value);
            totalCounter[currentIndex].incrementAndGet();

            if (value >= profileType.getSlowThreshold()) {
                slowCounter[currentIndex].incrementAndGet();
            }
            if (value < profileType.getInterval1()) {
                interval1[currentIndex].incrementAndGet();
            } else if (value < profileType.getInterval2()) {
                interval2[currentIndex].incrementAndGet();
            } else if (value < profileType.getInterval3()) {
                interval3[currentIndex].incrementAndGet();
            } else if (value < profileType.getInterval4()) {
                interval4[currentIndex].incrementAndGet();
            } else {
                interval5[currentIndex].incrementAndGet();
            }
        }
    }

    static class Counter extends AccessStatisticItem {

        public Counter(final ProfileType profileType, final long currentTimeMillis) {
            super(profileType, currentTimeMillis);
        }

        public Counter(final ProfileType profileType, final long currentTimeMillis, final int length) {
            super(profileType, currentTimeMillis, length);
        }

        @Override
        protected void doStatistic(final int currentIndex, final int value) {
            totalCounter[currentIndex].addAndGet(value);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy