很多互联网产品都会有数据分析的后台,比如,本公众号的一些数据分析:
通过后台的一些数据分析,我可以知道本公众号读者的一些年龄分布、地域分布、对哪些文章比较感兴趣等信息。
这些数据一定程度上对我之后生产内容有一定的启发。这些数据就是微信公众号把我们的一些用户信息、阅读公众号的一些行为给记录下来,并形成图表等形式展现出来。
特别是To C的消费类电子产品,用户数量较大,用户对设备的使用习惯对产品经理们之后的决策、工程师之后的优化方向很有帮助。
线上的嵌入式设备能记录用户的行为,能够帮助我们深入了解用户的行为模式,进而实现个性化推荐、故障预测、用户体验优化等目标。
比如:
具体到各个行业:
记录用户的行为,有个专业一点的词,叫做埋点。
嵌入式埋点就是在嵌入式设备中预设一些数据采集点(即“埋点”),当特定事件发生时(如用户点击某个按钮、观看某个节目),这些埋点会自动记录并上传相关数据到服务器进行分析。
整个数据分析的步骤大致如下:
这里我们着重分享事件定义与管理的例子:
我们基于Linux C,使用POSIX线程(pthread)来创建单独的线程,并使用POSIX消息队列来接收来自其他线程的开机次数及按键埋点事件。同时,我们将使用cJSON库来处理JSON数据,以及标准文件操作来记录数据到tracking.log文件中。
本例子源码可以在本公众号回复关键词:埋点例子,进行获取。
本例子源码可以在本公众号回复关键词:埋点例子,进行获取。
本例子源码可以在本公众号回复关键词:埋点例子,进行获取。
#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"
// 埋点类型
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;
};
#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);
}
// 微信公众号:城东书院
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");
}
}
// 微信公众号:城东书院
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);
}
// 微信公众号:城东书院
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
埋点文件把相关埋点事件给记录了下来,实际埋点信息可以根据需要进行增删。
埋点作为一种数据收集和分析的技术手段,具有其独特的优点和缺点。在实际应用中,需要根据具体需求和场景进行权衡和选择。