您当前的位置:首页 > 电子 > 嵌入式系统

【经验篇】嵌入式软件如何记录用户行为?

时间:08-02来源:作者:点击数:
CDSY,CDSY.XYZ

记录用户行为的意义?

很多互联网产品都会有数据分析的后台,比如,本公众号的一些数据分析:

通过后台的一些数据分析,我可以知道本公众号读者的一些年龄分布、地域分布、对哪些文章比较感兴趣等信息。

这些数据一定程度上对我之后生产内容有一定的启发。这些数据就是微信公众号把我们的一些用户信息、阅读公众号的一些行为给记录下来,并形成图表等形式展现出来。

特别是To C的消费类电子产品,用户数量较大,用户对设备的使用习惯对产品经理们之后的决策、工程师之后的优化方向很有帮助。

线上的嵌入式设备能记录用户的行为,能够帮助我们深入了解用户的行为模式,进而实现个性化推荐、故障预测、用户体验优化等目标。

比如:

  • 通过分析大量的用户使用功能A的频次最多,那么功能A的bug能修复就尽量修复好,哪怕是一些比较偏门的路径,因为这个功能好用与否可能关乎到用户对于这个产品地评价。
  • 通过监控用户在执行哪些操作时,触发了一些异常,这对于之后地优化起到了指导的方向。
  • 用户可能在夜间的时候没有使用设备的习惯,那对于夜间的一些有声音的预约操作是不是可以通过各种策略提前预防到这种情况,防止打扰用户休息。

具体到各个行业:

  • 智能家居行业。通过智能门锁、智能照明等设备的埋点数据,分析用户的日常行为习惯,以优化家居环境的智能化管理。
  • 工业自动化。通过埋点数据收集生产过程中的关键参数,进行质量控制和数据分析,确保产品质量的稳定性。
  • 医疗健康。通过分析用户的日常健康数据(如步数、心率、睡眠质量等),提供个性化的健康管理建议。
  • 智能交通。在智能交通信号灯中嵌入埋点,根据实时交通流量调整信号灯配时,提高道路通行效率。
  • 物联网。通过对物联网设备收集的海量数据进行分析,预测设备的运行趋势和潜在故障,提前采取措施进行预防和维护。

记录用户的行为,有个专业一点的词,叫做埋点

嵌入式埋点就是在嵌入式设备中预设一些数据采集点(即“埋点”),当特定事件发生时(如用户点击某个按钮、观看某个节目),这些埋点会自动记录并上传相关数据到服务器进行分析。

如何进行数据埋点?

整个数据分析的步骤大致如下:

  • 事件定义与管理:首先,在嵌入式设备中定义和管理数据采集点,即“埋点”。这些埋点可以配置为在用户点击、交互等事件发生时触发数据采集。
  • 数据采集与传输:当事件发生时,嵌入式设备将相关数据存储起来并通过网络传输到数据采集服务器。这里,可以使用HTTP请求、WebSocket、MQTT等协议实现数据的实时传输。
  • 数据处理与分析:在服务器端,使用大数据处理工具对收集到的数据进行实时处理和分析。通过分析用户的点击行为、观看习惯等,可以建立用户行为模型,实现个性化推荐和安全监控等应用。

这里我们着重分享事件定义与管理的例子:

我们基于Linux C,使用POSIX线程(pthread)来创建单独的线程,并使用POSIX消息队列来接收来自其他线程的开机次数及按键埋点事件。同时,我们将使用cJSON库来处理JSON数据,以及标准文件操作来记录数据到tracking.log文件中。

本例子源码可以在本公众号回复关键词:埋点例子,进行获取。

本例子源码可以在本公众号回复关键词:埋点例子,进行获取。

本例子源码可以在本公众号回复关键词:埋点例子,进行获取。

1、相关头文件

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>
#include <pthread.h>  
#include <mqueue.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <time.h>  
#include "cJSON.h"

2、埋点事件数据结构

// 埋点类型
enum track_event_type
{
    TRACK_EVENT_TYPE_BOOT,
    TRACK_EVENT_TYPE_BUTTON,
    TRACK_EVENT_TYPE_MAX,
};

// 公共埋点信息
struct track_event_common_info
{
    char dev_name[32];   // 设备名称
    char serial_num[32]; // 设备序列号
    char timestamp[64];  // 时间戳
};

// 启动事件信息
struct track_event_info_boot
{
    unsigned int cnt;    // 开机次数
};

// 按键事件信息
struct track_event_info_button
{
    unsigned char button_num;    // 按键号
    unsigned char button_type;   // 按键类型,长按 or 短按
};

// 当前的埋点事件信息
union track_event_info
{
    struct track_event_info_boot track_boot;
    struct track_event_info_button track_button;
};

// 埋点事件体
struct tracking_event
{  
    enum track_event_type event_type; 
    union track_event_info event_info;

    struct track_event_common_info *event_common_info;
};  

3、消息队列初始化、清除接口

#define QUEUE_NAME  "/mq0" 
mqd_t g_mqd;  

int init_mq(void) 
{  
    struct mq_attr attr;  

    attr.mq_flags = 0;  
    attr.mq_maxmsg = 10; // 最大消息数  
    attr.mq_msgsize = sizeof(struct tracking_event); // 消息最大大小 
    attr.mq_curmsgs = 0; // 当前队列中的消息数(由系统维护)  
    
    g_mqd = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0777, &attr); 
    if (g_mqd == (mqd_t)-1) 
    {  
        perror("mq_open");  
        exit(EXIT_FAILURE);  
    }  

    return 0;  
}  
  
void cleanup_mq(void) 
{  
    mq_close(g_mqd);  
    mq_unlink(QUEUE_NAME);  
}

4、通过消息队列发送埋点事件

// 微信公众号:城东书院
struct track_event_common_info *get_track_event_common_info(void)
{
    static struct track_event_common_info common_info = {0};
    time_t rawtime;  
    struct tm * timeinfo; 

    strncpy(common_info.dev_name, "board xxx", sizeof(common_info.dev_name) - 1); 
    strncpy(common_info.serial_num, "1234ABCD567", sizeof(common_info.serial_num) - 1); 
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    strftime(common_info.timestamp, sizeof(common_info.timestamp), "%Y-%m-%d %H:%M:%S", timeinfo);  

    return &common_info;
}

void send_event(enum track_event_type event_type, union track_event_info event_info) 
{  

    struct track_event_common_info *common_info = get_track_event_common_info();
    struct tracking_event event = {0};

    event.event_type = event_type;
    event.event_info = event_info;
    event.event_common_info = common_info;
  
    if (mq_send(g_mqd, (const char *)&event, sizeof(event), 1) == -1) 
    {  
        perror("mq_send");  
    }  
}

5、埋点线程实现

// 微信公众号:城东书院
void *tracking_thread(void *arg) 
{ 
    #define STR(x)  #x 

    FILE *fp = fopen("tracking.log", "a");  
    if (!fp) 
    {  
        perror("fopen");  
        return NULL;  
    }  
  
    struct tracking_event event = {0};
    while (1) 
    {  
        if (mq_receive(g_mqd, (char *)&event, sizeof(event), NULL) == -1) 
        {  
            perror("mq_receive");  
            continue;  
        }  
  
        cJSON *root = cJSON_CreateObject();  
        printf("event.event_type = %d\n", event.event_type);
        switch (event.event_type)
        {
            case TRACK_EVENT_TYPE_BOOT:
            {
                cJSON_AddStringToObject(root, "even_type", STR(TRACK_EVENT_TYPE_BOOT));
                cJSON_AddNumberToObject(root, "boot_cnt", event.event_info.track_boot.cnt);
                break;
            }
            case TRACK_EVENT_TYPE_BUTTON:
            {
                cJSON_AddStringToObject(root, "even_type", STR(TRACK_EVENT_TYPE_BUTTON));
                cJSON_AddNumberToObject(root, "button_num", event.event_info.track_button.button_num);
                cJSON_AddNumberToObject(root, "button_type", event.event_info.track_button.button_type);
                break;
            }
        
            default:
                break;
        }

        cJSON_AddStringToObject(root, "dev_name", event.event_common_info->dev_name);   
        cJSON_AddStringToObject(root, "serial_num", event.event_common_info->serial_num);    
        cJSON_AddStringToObject(root, "timestamp", event.event_common_info->timestamp);   
  
        char *json_str = cJSON_Print(root);  
        fprintf(fp, "%s\n", json_str);  
        printf("json_str = %s\n", json_str);
        free(json_str);  
        cJSON_Delete(root);  
  
        fflush(fp);  

        usleep(100 * 1000);
    }  
  
    fclose(fp);  
    pthread_exit(NULL); 
}

6、主函数实现

// 微信公众号:城东书院
int main(void) 
{  
    pthread_t thread_id;  

    init_mq();  

    // 创建跟踪线程  
    if (pthread_create(&thread_id, NULL, tracking_thread, NULL) != 0) 
    {  
        perror("pthread_create");  
        return 1;  
    }  

    while (1)
    {
        int ch = getchar();
        switch (ch)
        {
            case '1':
            {
                // 模拟发送TRACK_EVENT_TYPE_BOOT事件  
                printf("TRACK_EVENT_TYPE_BOOT\n");
                enum track_event_type event_type = {0};
                union track_event_info event_info = {0};

                event_type = TRACK_EVENT_TYPE_BOOT;
                event_info.track_boot.cnt += 1;
                send_event(event_type, event_info);
                break;
            }
            case '2':
            {
                // 模拟发送button_1_pressed事件  
                printf("TRACK_EVENT_TYPE_BUTTON\n");
                enum track_event_type event_type = {0};
                union track_event_info event_info = {0};

                event_type = TRACK_EVENT_TYPE_BUTTON;
                event_info.track_button.button_num = 1;
                event_info.track_button.button_type = 1;
                send_event(event_type, event_info);
                break;
            }
            default:
                break;
        }

        usleep(100);
    }

    // 清理  
    pthread_join(thread_id, NULL); 
    cleanup_mq();  

    return 0;  
}

编译运行:

gcc cJSON.c test.c -o test -lpthread -lrt

埋点文件把相关埋点事件给记录了下来,实际埋点信息可以根据需要进行增删。

埋点注意事项

  • 保护用户隐私:确保收集的数据符合隐私政策,避免泄露敏感信息。
  • 合理控制埋点数量:过多埋点会影响系统性能和用户体验。
  • 数据准确性:确保收集到的数据准确无误,避免误导决策。
  • 是否有必要进行埋点?埋点的缺点:
    • 开发成本高:嵌入式埋点需要在每个监测点都编写单独的事件监测代码,增加了开发工作量。
    • 存在延迟上报和漏报情况:由于网络延迟、设备性能等因素,可能存在数据上报的延迟或漏报情况。这会影响数据的完整性和准确性。进而会影响数据分析。
    • 无历史记录:嵌入式埋点只能采集到植入代码之后的数据,无法回溯到之前的历史数据。这可能导致在分析用户行为或系统变化时缺乏全面的数据支持。
    • 影响系统性能:埋点或多或少会带来一些性能开销。

埋点作为一种数据收集和分析的技术手段,具有其独特的优点和缺点。在实际应用中,需要根据具体需求和场景进行权衡和选择。

CDSY,CDSY.XYZ
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐