You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Kibana和ElasticSearch生成设备每日在线离线时间报表?

嘿,这个需求我之前帮好几个做IoT设备监控的朋友解决过——核心痛点就是我们只有在线时的ping记录,得靠这些数据反向推导离线时段,还要处理不同时区的问题对吧?下面是我整理的完整落地方案,从ES查询到数据处理再到报表生成一步到位:

核心思路

因为设备在线时每5分钟发一次ping,离线时完全不发,所以:

  • 连续的ping记录(间隔≤5分钟)属于同一个在线时段
  • 两个ping间隔>5分钟的中间段,以及当天首尾无ping的时段,就是离线时段
  • 必须先把所有时间转换为设备所在时区的本地时间,再做时段计算,不然跨时区的设备报表会完全不准
步骤1:从ElasticSearch查询目标设备的当日ping数据

首先要按设备ID和设备时区的当日范围过滤数据,然后按时间升序排序。这里给你一个DSL示例,注意把时区和设备ID换成实际值:

GET /ping_index/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"deviceId": 34567}},
        {"range": {
          "timestamp": {
            "gte": "2018-02-02T00:00:00+08:00", // 设备所在时区的当日0点
            "lte": "2018-02-02T23:59:59+08:00"  // 设备所在时区的当日23:59
          }
        }}
      ]
    }
  },
  "sort": [{"timestamp": "asc"}],
  "size": 1000 // 一天最多288条记录(每5分钟一条),这个值足够用
}
步骤2:数据处理(以Python为例)

拿到排序后的ping时间戳后,我们需要把它们转换成设备时区的本地时间,然后合并在线时段、计算离线时长。下面是可直接复用的逻辑:

from datetime import datetime, timedelta
import pytz

# 1. 配置参数
device_id = 34567
target_date = datetime(2018, 2, 2)
device_tz = pytz.timezone("Asia/Shanghai")  # 替换为设备实际时区
# 模拟从ES获取的ping数据(已转成UTC时间戳)
ping_timestamps_utc = [1517529600, 1517530200, 1517559300, 1517564700, 1517568900]

# 2. 转换为设备时区的本地时间并排序
ping_times_local = sorted(
    [datetime.utcfromtimestamp(ts).replace(tzinfo=pytz.utc).astimezone(device_tz) 
     for ts in ping_timestamps_utc]
)

# 3. 定义当日的起始和结束时间(设备时区)
day_start = device_tz.localize(datetime(target_date.year, target_date.month, target_date.day, 0, 0))
day_end = device_tz.localize(datetime(target_date.year, target_date.month, target_date.day, 23, 59, 59))

# 4. 计算在线/离线时段
segments = []

if not ping_times_local:
    # 当日无ping记录,全天离线
    segments.append({
        "type": "离线",
        "start": day_start,
        "end": day_end,
        "duration": int((day_end - day_start).total_seconds() // 60)
    })
else:
    # 处理当日开始到第一个ping的离线时段
    first_ping = ping_times_local[0]
    if first_ping > day_start:
        duration = int((first_ping - day_start).total_seconds() // 60)
        segments.append({
            "type": "离线",
            "start": day_start,
            "end": first_ping,
            "duration": duration
        })
    
    # 遍历处理连续在线/离线时段
    current_online_start = ping_times_local[0]
    for i in range(1, len(ping_times_local)):
        prev_time = ping_times_local[i-1]
        curr_time = ping_times_local[i]
        interval_minutes = int((curr_time - prev_time).total_seconds() // 60)
        
        if interval_minutes <= 5:
            # 属于同一个在线时段,继续
            continue
        else:
            # 结束当前在线时段
            segments.append({
                "type": "在线",
                "start": current_online_start,
                "end": prev_time
            })
            # 添加离线时段
            duration = interval_minutes
            segments.append({
                "type": "离线",
                "start": prev_time,
                "end": curr_time,
                "duration": duration
            })
            # 开启新的在线时段
            current_online_start = curr_time
    
    # 处理最后一个在线时段
    segments.append({
        "type": "在线",
        "start": current_online_start,
        "end": ping_times_local[-1]
    })
    
    # 处理最后一个ping到当日结束的离线时段
    last_ping = ping_times_local[-1]
    if last_ping < day_end:
        duration = int((day_end - last_ping).total_seconds() // 60)
        segments.append({
            "type": "离线",
            "start": last_ping,
            "end": day_end,
            "duration": duration
        })

# 5. 生成用户需要的报表格式
print(f"日期:{target_date.strftime('%Y.%m.%d')} 设备:{device_id}")
for seg in segments:
    start_str = seg["start"].strftime("%H:%M")
    end_str = seg["end"].strftime("%H:%M")
    if seg["type"] == "在线":
        print(f"{start_str}-{end_str} 在线")
    else:
        print(f"{start_str}-{end_str} 离线时长({seg['duration']}分钟)")
步骤3:时区处理注意事项
  • 永远不要用UTC时间直接做当日范围过滤,必须转换为设备所在时区的0点到23:59
  • 如果你的ES里存储的是本地时间戳(不是UTC),那查询时直接用设备时区的当日范围即可,但更推荐存储UTC时间戳,然后在应用层转换时区,避免时区混乱
  • 可以把设备的时区信息存在设备元数据索引里,查询时关联获取,不用硬编码
可选:用Kibana生成可视化图表

如果需要做可视化报表,用Kibana的Timelion或者自定义可视化很方便:

  • Timelion表达式示例:
.es(index=ping_index, q='deviceId:34567', timefield='timestamp', metric=count).if(gt(0), 1, 0).fill(0)

这个表达式会生成一个折线图,1代表设备在线,0代表离线,鼠标hover就能查看具体时段的状态。

内容的提问来源于stack exchange,提问作者Sfisioza

火山引擎 最新活动