Skip to main content

各时间段调用统计插件

tip

使用场景推荐

  • 线上
  • 性能分析

介绍

TimeRangeInOut 是各时间段调用统计插件,主要的关注点是每天各时间段 action 的调用次数。

  • 可统计全天 24 小时,也可指定统计某个时间段
  • 可统计每个小时内每分钟,也可指定某些分钟时间段(如,只统计 0 ~ 30 分钟的,30 ~ 60 分钟的不做统计)

开发者可以通过这些数据来分析各时间段的调用频率,从而知道游戏在那个时间阶段玩的人较多。

有了这些数据,你将知道你的游戏在各个时间段访问的数据。 通过这些统计数据,在高峰时段来临前可以提前准备更多的机器来负载请求,高峰过后又可以减少机器。

默认配置下的会统计每小时的调用次数,打印如下

2023-11-29 action 调用次数 共 [10000] 次
0:00 共 431 次;
1:00 共 416 次;
2:00 共 421 次;
3:00 共 414 次;
4:00 共 441 次;
5:00 共 423 次;
6:00 共 407 次;
7:00 共 395 次;
8:00 共 410 次;
9:00 共 413 次;
10:00 共 378 次;
11:00 共 411 次;
12:00 共 380 次;
13:00 共 413 次;
14:00 共 417 次;
15:00 共 416 次;
16:00 共 400 次;
17:00 共 430 次;
18:00 共 471 次;
19:00 共 440 次;
20:00 共 405 次;
21:00 共 430 次;
22:00 共 414 次;
23:00 共 424 次;
tip

默认的配置已经能够满足大多数情况了

如何使用

@Override
public BarSkeleton createBarSkeleton() {
BarSkeletonBuilder builder = ...;
builder.addInOut(new TimeRangeInOut());
...
}

更多配置示例

精准到具体小时

下面的示例中,我们重写了 createListenerTimeRangeHourList 方法, 只监听 12、19、20 这几个时间 点的调用统计,其他时间点则不关心。

private void setListener(TimeRangeInOut inOut) {
inOut.setListener(new TimeRangeInOut.ChangeListener() {
@Override
public List<TimeRangeInOut.TimeRangeHour> createListenerTimeRangeHourList() {
return List.of(
this.createListenerTimeRangeHour(12),
this.createListenerTimeRangeHour(19),
this.createListenerTimeRangeHour(20)
);
}
});
}

由于我们只监听了 12、19、20 这三个时间点,此时的打印如下

  • 全天的调用统计
  • 三个时间点的明细
2023-11-29 action 调用次数 共 [10000] 次
12:00 共 442 次;
19:00 共 410 次;
20:00 共 422 次;

精准到具体分钟段

下面的示例中,我们重写了 createListenerTimeRangeMinuteList 方法, 表示在小时内的一个细分统计,以分钟范围来细分。

private void setListener(TimeRangeInOut inOut) {
inOut.setListener(new TimeRangeInOut.ChangeListener() {
@Override
public List<TimeRangeInOut.TimeRangeHour> createListenerTimeRangeHourList() {
return List.of(
this.createListenerTimeRangeHour(12),
this.createListenerTimeRangeHour(19),
this.createListenerTimeRangeHour(20)
);
}

@Override
public List<TimeRangeInOut.TimeRangeMinute> createListenerTimeRangeMinuteList() {
return List.of(
TimeRangeInOut.TimeRangeMinute.create(0, 29),
TimeRangeInOut.TimeRangeMinute.create(30, 59)
);
}

});
}

由于我们监听了 0~29、30~59 分钟这些范围,插件分别给出了

  • 全天的调用统计
  • 三个时间点的明细
  • 每小时内的分钟范围统计
2023-11-29  action 调用次数 共 [10000] 次
12:00 共 449 次; - [0~29分钟 236 次] - [30~59分钟 213 次]
19:00 共 435 次; - [0~29分钟 227 次] - [30~59分钟 208 次]
20:00 共 385 次; - [0~29分钟 175 次] - [30~59分钟 210 次]


tip

分钟统计默认的实现是两边包含,如下示例只统计 0、1、59 分钟这 3 个时间点。

private void setListener(TimeRangeInOut inOut) {
inOut.setListener(new TimeRangeInOut.ChangeListener() {
@Override
public List<TimeRangeInOut.TimeRangeMinute> createListenerTimeRangeMinuteList() {
return List.of(
TimeRangeInOut.TimeRangeMinute.create(0, 0),
TimeRangeInOut.TimeRangeMinute.create(1, 1),
TimeRangeInOut.TimeRangeMinute.create(59, 59)
);
}
});
}

每天 0:00 回调

TimeRangeDay 是一天的调用统计对象,对象内包括了当天各时间段的统计数据。

插件会在每天的 0:00 触发 callbackYesterday 方法,并将昨日的 TimeRangeDay 对象传入方法中。 开发者可以在这个方法中将数据同步到日志、或 DB、或其他地方中。

  • code 5,昨日的全天统计数据对象
private void setListener(TimeRangeInOut inOut) {
inOut.setListener(new TimeRangeInOut.ChangeListener() {
@Override
public void callbackYesterday(TimeRangeInOut.TimeRangeDay timeRangeYesterday) {
System.out.println(timeRangeYesterday);
}

});
}

优化

每次调用 action 业务方法,插件会通过 listener 对象得到当前的日期和时间, 而 nowLocalDate 、nowLocalTime 默认实现是每次通过对应的 now 来得到新的对象。

如果每秒调用 1 万次 action,那么将会生成至少 2 万个对象。 理论上来说,这点消耗对 ZGC 来说算不上什么。

  • code 3,将当前日期和时间保存到 flowContext 中
public final class TimeRangeInOut implements ActionMethodInOut {
...
public static final FlowOption<LocalDate> localDate = FlowOption.valueOf("TimeRangeInOut-LocalDate");
public static final FlowOption<LocalTime> localTime = FlowOption.valueOf("TimeRangeInOut-LocalTime");

ChangeListener listener = new ChangeListener() {
};

@Override
public void fuckIn(FlowContext flowContext) {
flowContext.option(localDate, listener.nowLocalDate());
flowContext.option(localTime, listener.nowLocalTime());
}

public interface ChangeListener {
...
default LocalDate nowLocalDate() {
return LocalDate.now();
}

default LocalTime nowLocalTime() {
return LocalTime.now();
}
}
}

优化实现

默认的实现在每次调用 action 时,都会产生新的日期和时间对象。 如果你能接受一些时间上的误差,可以考虑重写nowLocalDate 、nowLocalTime 方法的实现。


在下面代码中,每分钟会在 onUpdate 方法内更新日期和时间对象。 这样,即使每秒中调用 1 万次 action,也不会产生新的日期和时间对象,从而减少对象的创建。

MyChangeListener 实现了两个接口

  • TimeRangeInOut.ChangeListener
  • IntervalTaskListener

代码说明

  • code 2,自定义 MyChangeListener。
  • code 24,将 MyChangeListener 对象设置到 TimeRangeInOut 中。
  • code 26,每分钟会调用一次 MyChangeListener.onUpdate 方法。
private void setListener(TimeRangeInOut inOut) {
class MyChangeListener implements TimeRangeInOut.ChangeListener, IntervalTaskListener {
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();

@Override
public void onUpdate() {
this.localDate = LocalDate.now();
this.localTime = LocalTime.now();
}

@Override
public LocalDate nowLocalDate() {
return localDate;
}

@Override
public LocalTime nowLocalTime() {
return localTime;
}
}

MyChangeListener myChangeListener = new MyChangeListener();
inOut.setListener(new MyChangeListener());

TaskKit.runIntervalMinute(myChangeListener, 1);
}