看她的APM系统要怎么样收集.Net运转时的各类风浪,在linux上(CoreCL瑞虎)是透过LTTng跟踪事件.

近年来这几天在帮柠檬看她的APM系统要如何收集.Net运维时的种种风云,
这么些事件包涵线程先河, JIT执行, GC触发等等.
.Net在windows上(NetFramework, CoreCLEnclave)通过ETW(Event Tracing for
Windows), 在linux上(CoreCLEnclave)是由此LTTng跟踪事件.

方今这几天在帮柠檬看她的APM系统要什么收集.Net运转时的各个风浪,
这么些事件包罗线程初始, JIT执行, GC触发等等.
.Net在windows上(NetFramework, CoreCL奇骏)通过ETW(Event Tracing for
Windows), 在linux上(CoreCL奥迪Q7)是通过LTTng跟踪事件.

ETW的API设计已经被不少人非议,
微软推出的类库krabsetw中直指ETW是最差的API再者把操作ETW的文本命名为噩梦.hpp.
而且那篇小说中, Casey
Muratori解释了为啥ETW是最差的API, 原因包含:

ETW的API设计已经被不少人诟病,
微软推出的类库krabsetw中直指ETW是最差的API同时把操作ETW的文本命名为噩梦.hpp.
而且这篇小说中, Casey
Muratori解释了为什么ETW是最差的API, 原因包含:

  • 事件类型应用了位标志(最八只好有33个), 没有考虑到今后的图景
  • 不等的接口共用贰个大的构造体, 接口的输入和出口不强烈
  • 让调用者写无论怎么看都以剩下的代码
  • 让调用者使用魔法数字(而不是提供三个枚举值)
  • 取名带有误导性
  • 再次回到值的意义不合并
  • 选用过度复杂, 没有先想好用例
  • 文书档案里面没有完好的言传身教代码, 只好从零星的代码拼凑
  • 事件类型应用了位标志(最六只可以有叁十二个), 没有设想到未来的动静
  • 不等的接口共用二个大的构造体, 接口的输入和出口不肯定
  • 让调用者写无论怎么看都以剩下的代码
  • 让调用者使用魔法数字(而不是提供三个枚举值)
  • 取名带有误导性
  • 再次来到值的含义不联合
  • 采取过度复杂, 没有先想好用例
  • 文书档案里面没有完好的言传身教代码, 只可以从零星的代码拼凑

但是Casey Muratori的小说对自个儿协助非常大,
笔者只用了1天时间就写出了采纳ETW收集.Net运维时事件的演示代码.
自此作者起先看怎么样选用LTTng收集这几个事件,
遵照小编过去的经验linux上的类库api经常会比windows的好用, 但LTTng是个例外.

唯独Casey Muratori的篇章对小编支持极大,
小编只用了1天时间就写出了应用ETW收集.Net运营时事件的以身作则代码.
而后作者起来看什么行使LTTng收集这个事件,
依据作者过去的经验linux上的类库api日常会比windows的好用, 但LTTng是个例外.

本身第二件做的事务是去摸索怎么样在c程序里面LTTng的接口,
我打开了他们的文档然后初阶浏览.
快快作者意识了她们的文书档案只谈了怎样行使代码出殡事件,
却从未任何申明怎么着用代码收受事件, 作者发觉到自家应当去看源代码.

本人先是件做的作业是去找寻如何在c程序里面LTTng的接口,
我打开了她们的文档下一场开首浏览.
敏捷作者发觉了她们的文书档案只谈了什么利用代码发送事件,
却绝非任何表明怎样用代码接过事件, 作者意识到小编应该去看源代码.

初始化LTTng

采取LTTng跟踪事件首先需求创建一个对话, 启用事件和添加上下文参数,
然后启用跟踪, 在命令行里面是如此的调用:

lttng create --live
lttng enable-event --userspace --tracepoint DotNETRuntime:GCStart_V2
lttng add-context --userspace --type vpid
lttng add-context --userspace --type vtid
lttng start

lttng那些命令的源代码在github上,
通过几分钟的搜寻本身发觉lttng的一一命令的兑现都以保存在本条文件夹下的.
打开create.c后又发现了创制会话调用的是lttng_create_session函数,
lttng_create_session函数能够由此引用lttng.h调用.
再过了几分钟笔者写出了第③行代码

int ret = lttng_create_session_live("example-session", "net://127.0.0.1", 1000000);

运维后立时就报错了, 错误是”No session daemon is available”.
原因是lttng-sessiond本条程序没有运转,
lttng是通过叁个单身服务来治本会话的, 而那一个服务须要手动运转.

应用独立服务本身并未错, 但是lttng-sessiond本条顺序提供了成百上千参数,
比方2个只想跟踪用户事件的先后运行了那些服务并钦命了忽略内核事件的参数,
然后此外叁个跟踪内核事件的次序将不能够健康运作.
是的的做法是利用systemd来运行这么些服务, 让系统一管理理员决定用什么样参数,
而不是让调用者去运营它.

缓解这么些题材只需求简单残忍的两行, 运行时借使已经运行过新历程会破产,
没有其它影响:

system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));

现在lttng_create_session_live会重临成功了, 不过又发现了新的题材,
创建的对话是由一个独自的服务管理的, 尽管当前经过退出会话也会存在,
第四回成立的时候会回到三个已存在的错误.
以此题材和ETW的标题一模一样, 化解格局也一律,
在创设会话前关闭它就足以了.

于是乎代码变成了那般:

system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));
lttng_destroy_session(SessionName);
int ret = lttng_create_session_live("example-session", "net://127.0.0.1", 1000000);

经过一段时间后, 小编用代码实现了和命令行一样的功用:

// start processes, won't replace exists
system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));

// create new session
lttng_destroy_session(SessionName);
int ret = lttng_create_session_live(SessionName, SessionUrl, LiveSessionInterval);
if (ret != 0) {
    std::cerr << "lttng_create_session: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// create handle from session
lttng_domain domain = {};
domain.type = LTTNG_DOMAIN_UST;
lttng_handle* handle = lttng_create_handle(SessionName, &domain);
if (handle == nullptr) {
    std::cerr << "lttng_create_handle: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// enable event
lttng_event event = {};
event.type = LTTNG_EVENT_TRACEPOINT;
memcpy(event.name, EventName.c_str(), EventName.size());
event.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL;
event.loglevel = -1;
ret = lttng_enable_event_with_exclusions(handle, &event, nullptr, nullptr, 0, nullptr);
if (ret < 0) {
    std::cerr << "lttng_enable_event_with_exclusions: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// add context
lttng_event_context contextPid = {};
contextPid.ctx = LTTNG_EVENT_CONTEXT_VPID;
ret = lttng_add_context(handle, &contextPid, nullptr, nullptr);
if (ret < 0) {
    std::cerr << "lttng_add_context: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// start tracing
ret = lttng_start_tracing(SessionName);
if (ret < 0) {
    std::cerr << "lttng_start_tracing: " << lttng_strerror(ret) << std::endl;
    return -1;
}

到那边截止是还是不是相当粗略? 就算没有文书档案, 可是这一个api都以万分简单的api,
看源代码就足以推论怎么着调用.

初始化LTTng

选择LTTng跟踪事件首先供给成立多个会话, 启用事件和添加上下文参数,
然后启用跟踪, 在命令行里面是如此的调用:

lttng create --live
lttng enable-event --userspace --tracepoint DotNETRuntime:GCStart_V2
lttng add-context --userspace --type vpid
lttng add-context --userspace --type vtid
lttng start

lttng那么些命令的源代码在github上,
通过几分钟的摸索自身意识lttng的逐一命令的完成都是保存在本条文件夹下的.
打开create.c后又发现了创办会话调用的是lttng_create_session函数,
lttng_create_session函数能够由此引用lttng.h调用.
再过了几分钟笔者写出了第二行代码

int ret = lttng_create_session_live("example-session", "net://127.0.0.1", 1000000);

运作后立时就报错了, 错误是”No session daemon is available”.
原因是lttng-sessiond本条程序没有运维,
lttng是通过三个单身服务来管理会话的, 而这么些服务供给手动运维.

应用独立服务本人没有错, 不过lttng-sessiond这么些程序提供了好多参数,
倘若二个只想跟踪用户事件的次序运营了这些服务并内定了忽略内核事件的参数,
然后别的多个跟踪内核事件的程序将无法健康运作.
正确的做法是行使systemd来运转这几个服务, 让系统一管理理员决定用如何参数,
而不是让调用者去运维它.

缓解那些题材只需求不难凶横的两行, 运行时如若已经运转过新进程会破产,
没有别的影响:

system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));

现在lttng_create_session_live会回来成功了, 不过又发现了新的题材,
创造的对话是由三个独自的劳动管理的, 就算当前经过退出会话也会存在,
第3回创造的时候会回到一个已存在的错误.
本条题材和ETW的题材一模一样, 消除方法也如出一辙,
在成立会话前关闭它就足以了.

于是代码变成了那样:

system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));
lttng_destroy_session(SessionName);
int ret = lttng_create_session_live("example-session", "net://127.0.0.1", 1000000);

透过一段时间后, 小编用代码实现了和命令行一样的效应:

// start processes, won't replace exists
system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));

// create new session
lttng_destroy_session(SessionName);
int ret = lttng_create_session_live(SessionName, SessionUrl, LiveSessionInterval);
if (ret != 0) {
    std::cerr << "lttng_create_session: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// create handle from session
lttng_domain domain = {};
domain.type = LTTNG_DOMAIN_UST;
lttng_handle* handle = lttng_create_handle(SessionName, &domain);
if (handle == nullptr) {
    std::cerr << "lttng_create_handle: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// enable event
lttng_event event = {};
event.type = LTTNG_EVENT_TRACEPOINT;
memcpy(event.name, EventName.c_str(), EventName.size());
event.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL;
event.loglevel = -1;
ret = lttng_enable_event_with_exclusions(handle, &event, nullptr, nullptr, 0, nullptr);
if (ret < 0) {
    std::cerr << "lttng_enable_event_with_exclusions: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// add context
lttng_event_context contextPid = {};
contextPid.ctx = LTTNG_EVENT_CONTEXT_VPID;
ret = lttng_add_context(handle, &contextPid, nullptr, nullptr);
if (ret < 0) {
    std::cerr << "lttng_add_context: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// start tracing
ret = lttng_start_tracing(SessionName);
if (ret < 0) {
    std::cerr << "lttng_start_tracing: " << lttng_strerror(ret) << std::endl;
    return -1;
}

到此地截至是还是不是很粗大略? 即便尚未文书档案, 可是这么些api都是格外简单的api,
看源代码就能够估测计算怎么着调用.

得到事件

在告诉LTTng启用跟踪后, 作者还索要获得发送到LTTng的轩然大波,
在ETW中获取事件是通过注册回调获取的:

EVENT_TRACE_LOGFILE trace = { };
trace.LoggerName = (char*)mySessionName.c_str();
trace.EventRecordCallback = (PEVENT_RECORD_CALLBACK)(StaticRecordEventCallback);
trace.BufferCallback = (PEVENT_TRACE_BUFFER_CALLBACK)(StaticBufferEventCallback);
trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME;
TRACEHANDLE sessionHandle = ::OpenTrace(&trace);
if (sessionHandle == INVALID_PROCESSTRACE_HANDLE) {
    // ...
}
ULONG processStatus = ::ProcessTrace(&sessionHandle, 1, nullptr, nullptr);

自个儿寻思lttng有没有那样的机制,
首先本身看到的是lttng.h中的lttng_register_consumer函数,
那些函数的笺注如下:

This call registers an "outside consumer" for a session and an lttng domain.
No consumer will be spawned and all fds/commands will go through the socket path given (socket_path).

翻译出来正是给会话注册3个外部的消费者, 听上去和自身的须要很像吗?
本条函数的第三个参数是三个字符串, 小编想来是unix socket, lttng会通过unix
socket发送事件过来.
于是乎作者写了那样的代码:

ret = lttng_register_consumer(handle, "/tmp/custom-consumer");

一实施及时报错, 错误是Command undefined, 也正是命令未定义,
服务端不帮助这一个命令.
通过查找发现lttng的源代码中绝非其它调用这些函数的地点,
也正是说这么些函数是个装饰.
看起来那个法子行不通.


透过一番物色,
小编意识了live-reading-howto其一文书档案,
里面包车型客车剧情分外少可是能够见到使用lttng-relayd那几个服务能够读取事件.
读取事件最近只扶助TCP, 使用TCP传输事件数量不仅复杂而且成效极低,
相对ETW直接通过内部存款和储蓄器传递数据那如实是个愚笨的办法.
固然古板不过依旧要三番五次写, 笔者起来看那TCP传输用的是何等协议.

对传输协议的演讲文档在live-reading-protocol.txt,
那篇文书档案写的很不佳, 但总比没有好.
lttng-relayd进展互动使用的是三个lttng自个儿成立的半双工二进制协议,
设计如下:

客户端发送命令给lttng-relayd亟需遵守以下的格式

[data_size: unsigned 64 bit big endian int, 命令体大小]
[cmd: unsigned 32 bit big endian int, 命令类型]
[cmd_version: unsigned 32 bit big endian int, 命令版本]
[命令体, 大小是data_size]

发送命令的统一筹划没有毛病, 半数以上二进制协议都以这么设计的,
难题在于接收命令的设计.
收到命令的格式完全依赖于发送命令的花色,
例如LTTNG_VIEWER_CONNECT这几个命令发送过去会吸收以下的多寡:

[viewer_session_id: unsigned 64 bit big endian int, 服务端指定的会话ID]
[major: unsigned 32 bit big endian int, 大版本]
[minor: unsigned 32 bit big endian int, 中版本]
[type: 客户端的类型]

能够阅览接收的数量没有数据头, 没有数据头怎样控制接受多少数量吧?
那就需求客户端定义的回应大小必须和服务端完全一致, 贰个字段都无法漏.
服务端在后来的立异中无法给重回数据随意添加字段,
重回多少字段需求取决于发送过来的cmd_version,
保持api的包容性将会这一个的麻烦.
目前在lttng中cmd_version是三个留下字段,
也正是她们并未仔细的想过api的立异难题.
没错的做法应该是回去数据也应有提供贰个数据头,
然后同意客户端忽略多出去的数据.


看完协议未来, 我在想既然使用了二进制协议,
应该也会提供3个sdk来压缩解析的工作量吗?
由此一番搜索找到了2个头文件lttng-viewer-abi.h,
包蕴了和lttng-relayd相互使用的数据结构体定义.
本条头文件在源代码里面有, 不过却不在LTTng揭橥的软件包中,
那象征使用它要求复制它到品种里面.
复制外人的源代码到品种里面不可能那么不论,
看了一晃LTTng的开源协议,
include/lttng/*src/lib/lttng-ctl/*下的文本是LGPL,
别的文件是GPL,
也等于地方设若把那一个头文件复制到自个儿的花色里面,
自个儿的花色必须采用GPL协议开源
,
不想用GPL的话只好把内部的始末本人一行行重新写, 还不能写的太像.

既然是测试就随便如此多了, 把这几个头文件的代码复制过来就从头持续写,
首先是连接受lttng-relayd:

int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
    perror("socket");
    return -1;
}
sockaddr_in address = {};
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_family = AF_INET;
address.sin_port = htons(5344);
ret = connect(fd, (sockaddr*)&address, sizeof(address));
if (ret < 0) {
    perror("connect");
    return -1;
}

连接成功现在的彼此流程在读书方面包车型大巴商议文书档案现在能够整理如下:

初始化
    客户端发送命令 LTTNG_VIEWER_CLIENT_COMMAND + 构造体 lttng_viewer_connect
    服务端返回构造体 lttng_viewer_connect
    客户端发送命令 LTTNG_VIEWER_CREATE_SESSION + 构造体 lttng_viewer_create_session_response
    服务端返回构造体 lttng_viewer_create_session_response
列出会话
    客户端发送命令 LTTNG_VIEWER_LIST_SESSIONS, 不带构造体
    服务端返回构造体 lttng_viewer_list_sessions + 指定长度的 lttng_viewer_session
附加到会话
    客户端发送命令 LTTNG_VIEWER_ATTACH_SESSION + 构造体 lttng_viewer_attach_session_request
    服务端返回构造体 lttng_viewer_attach_session_response + 指定长度的 lttng_viewer_stream
循环 {
    如果需要获取新的流 {
        客户端发送命令 LTTNG_VIEWER_GET_NEW_STREAMS + 构造体 lttng_viewer_new_streams_request
        服务端返回构造体 lttng_viewer_new_streams_response + 指定长度的 lttng_viewer_stream
    }
    如果需要获取新的元数据(metadata) {
        枚举现存的metadata流列表 {
            客户端发送命令 LTTNG_VIEWER_GET_METADATA + 构造体 lttng_viewer_get_metadata
            服务端返回构造体 lttng_viewer_metadata_packet + 指定长度的payload
        }
    }
    枚举现存的trace流列表 {
        客户端发送命令 LTTNG_VIEWER_GET_NEXT_INDEX + 构造体 lttng_viewer_get_next_index
        服务端返回构造体 lttng_viewer_index
        检查返回的 index.flags, 如果服务端出现了新的流或者元数据, 需要先获取新的流和元数据才可以继续
        客户端发送命令 LTTNG_VIEWER_GET_PACKET + 构造体 lttng_viewer_trace_packet
        服务端返回构造体 lttng_viewer_trace_packet + 指定长度的payload
        根据metadata packet和trace packet分析事件的内容然后记录事件
    }
}

是还是不是认为很复杂?
因为协议决定了服务端发给客户端的数目没有数据头,
所以服务端无法积极推送数据到客户端, 客户端必须主动的去开始展览轮询.
一旦您放在心上到构造体的名目,
会发现部分构造体前边有request和response而部分没有,
假若不看上下文只看构造体的称号很难猜到它们的功能.
不错的做法是全数请求和再次回到的社团体名称末尾都添加request和response,
不要去大约这一个假名而浪费思考的时间.


为了发送命令和接收构造体笔者写了有的声援函数, 它们并不复杂,
使用TCP交互的顺序都会有像样的代码:

int sendall(int fd, const void* buf, std::size_t size) {
    std::size_t pos = 0;
    while (pos < size) {
        auto ret = send(fd,
            reinterpret_cast<const char*>(buf) + pos, size - pos, 0);
        if (ret <= 0) {
            return -1;
        }
        pos += static_cast<std::size_t>(ret);
    }
    return 0;
}

int recvall(int fd, void* buf, std::size_t size) {
    std::size_t pos = 0;
    while (pos < size) {
        auto ret = recv(fd,
            reinterpret_cast<char*>(buf) + pos, size - pos, 0);
        if (ret <= 0) {
            return -1;
        }
        pos += static_cast<std::size_t>(ret);
    }
    return 0;
}

template <class T>
int sendcmd(int fd, std::uint32_t type, const T& body) {
    lttng_viewer_cmd cmd = {};
    cmd.data_size = htobe64(sizeof(T));
    cmd.cmd = htobe32(type);
    if (sendall(fd, &cmd, sizeof(cmd)) < 0) {
        return -1;
    }
    if (sendall(fd, &body, sizeof(body)) < 0) {
        return -1;
    }
    return 0;
}

开头化连接的代码如下:

lttng_viewer_connect body = {};
body.major = htobe32(2);
body.minor = htobe32(9);
body.type = htobe32(LTTNG_VIEWER_CLIENT_COMMAND);
if (sendcmd(fd, LTTNG_VIEWER_CONNECT, body) < 0) {
    return -1;
}
if (recvall(fd, &body, sizeof(body)) < 0) {
    return -1;
}
viewer_session_id = be64toh(body.viewer_session_id);

末端的代码相比较单调作者就简单了,
想看完整代码的能够看这里.


跻身循环后会从lttng-relayd获得三种有效的数额:

  • 元数据(metadata), 定义了跟踪数据的格式
  • 跟踪数据(trace), 包涵了风浪音信例如GC起先和了结等

获取元数据应用的是LTTNG_VIEWER_GET_METADATA命令,
获取到的元数据内容如下:

Wu@"Jtf@oe/* CTF 1.8 */

typealias integer { size = 8; align = 8; signed = false; } := uint8_t;
typealias integer { size = 16; align = 8; signed = false; } := uint16_t;
typealias integer { size = 32; align = 8; signed = false; } := uint32_t;
typealias integer { size = 64; align = 8; signed = false; } := uint64_t;
typealias integer { size = 64; align = 8; signed = false; } := unsigned long;
typealias integer { size = 5; align = 1; signed = false; } := uint5_t;
typealias integer { size = 27; align = 1; signed = false; } := uint27_t;

trace {
    major = 1;
    minor = 8;
    uuid = "a3df4090-0722-4a74-97a4-81e066406f03";
    byte_order = le;
    packet.header := struct {
        uint32_t magic;
        uint8_t  uuid[16];
        uint32_t stream_id;
        uint64_t stream_instance_id;
    };
};

env {
    hostname = "ubuntu-virtual-machine";
    domain = "ust";
    tracer_name = "lttng-ust";
    tracer_major = 2;
    tracer_minor = 9;
};

clock {
    name = "monotonic";
    uuid = "f397e532-4837-402b-8cc9-700ed92a339d";
    description = "Monotonic Clock";
    freq = 1000000000; /* Frequency, in Hz */
    /* clock value offset from Epoch is: offset * (1/freq) */
    offset = 1514336042565610080;
};

typealias integer {
    size = 27; align = 1; signed = false;
    map = clock.monotonic.value;
} := uint27_clock_monotonic_t;

typealias integer {
    size = 32; align = 8; signed = false;
    map = clock.monotonic.value;
} := uint32_clock_monotonic_t;

typealias integer {
    size = 64; align = 8; signed = false;
    map = clock.monotonic.value;
} := uint64_clock_monotonic_t;

struct packet_context {
    uint64_clock_monotonic_t timestamp_begin;
    uint64_clock_monotonic_t timestamp_end;
    uint64_t content_size;
    uint64_t packet_size;
    uint64_t packet_seq_num;
    unsigned long events_discarded;
    uint32_t cpu_id;
};

struct event_header_compact {
    enum : uint5_t { compact = 0 ... 30, extended = 31 } id;
    variant <id> {
        struct {
            uint27_clock_monotonic_t timestamp;
        } compact;
        struct {
            uint32_t id;
            uint64_clock_monotonic_t timestamp;
        } extended;
    } v;
} align(8);

struct event_header_large {
    enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;
    variant <id> {
        struct {
            uint32_clock_monotonic_t timestamp;
        } compact;
        struct {
            uint32_t id;
            uint64_clock_monotonic_t timestamp;
        } extended;
    } v;
} align(8);

stream {
    id = 0;
    event.header := struct event_header_compact;
    packet.context := struct packet_context;
    event.context := struct {
        integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _vpid;
        integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _vtid;
    };
};

event {
    name = "DotNETRuntime:GCStart_V2";
    id = 0;
    stream_id = 0;
    loglevel = 13;
    fields := struct {
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Count;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Depth;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Reason;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Type;
        integer { size = 16; align = 8; signed = 0; encoding = none; base = 10; } _ClrInstanceID;
        integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } _ClientSequenceNumber;
    };
};

其一元数据的格式是CTF Metadata,
那些格式看上去像json不过并不是, 是LTTng的合作社温馨成立的一个文本格式.
babeltrace中包蕴了剖析那一个文本格式的代码,
可是没有开放任何解析它的接口,
也正是一旦你想协调分析唯其如此写2个词法分析器.
这么些格式其实能够使用json表示, 体量不会增添多少,
可是那集团便是发明了多少个新的格式扩充使用者的负担.
写2个词法分析器供给1天时间和一千行代码, 那里自身就先跳过了.


接下去获取跟踪数据,
使用的是LTTNG_VIEWER_GET_NEXT_INDEX和LTTNG_VIEWER_GET_PACKET命令.
LTTNG_VIEWER_GET_NEXT_INDEX再次回到了目前流的offset和可取得的content_size,
这里的content_size单位是位(bit),
也便是内需除以8才能够算出能够取得多少字节,
关于content_size的单位LTTng中并未其余文档和注释表达它是位,
唯有一个测试代码里面包车型大巴某行写了/ CHAR_BIT.
使用LTTNG_VIEWER_GET_PACKET命令,
传入offset和content_size/8能够取得跟踪数据(若是不/8会得到到剩余的数目恐怕重临E中华VEscort).
实际上再次回到的跟踪数据如下:

000000: c1 1f fc c1 29 82 6b fe 24 10 4c 6b 97 91 4d c3  ....).k.$.Lk..M.
000010: ed d4 41 8f 00 00 00 00 03 00 00 00 00 00 00 00  ..A.............
000020: 92 91 49 96 08 0a 00 00 07 a0 58 b9 08 0a 00 00  ..I.......X.....
000030: 50 05 00 00 00 00 00 00 00 80 00 00 00 00 00 00  P...............
000040: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000050: 03 00 00 00 1f 00 00 00 00 92 91 49 96 08 0a 00  ...........I....
000060: 00 e1 1b 00 00 03 00 00 00 02 00 00 00 01 00 00  ................
000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1f  ................
000080: 00 00 00 00 4d ae a7 af 08 0a 00 00 e1 1b 00 00  ....M...........
000090: 04 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00  ................
0000a0: 00 00 00 00 00 00 00 00 00 00                    ..........

亿万先生官方网站:,跟踪数据的格式是CTF Stream
Packet
,
也是一个自定义的二进制格式, 须求般配元数据解析.
babeltrace中平等没有开放解析它的接口(有python
binding不过从未解析数据的函数), 也正是索要协调写二进制数据解析器.

操作LTTng + 和relayd通信 + 元数据词法分析器 +
跟踪数据解析器全体加起来臆度须要两千行代码,
而那整个应用ETW只用了100多行代码.
糟糕的设计, 复杂的应用, 落后的文书档案, 各样各种的自定义协议和多少格式,
不提供SDK把LTTng创设成了一个比ETW更难用的跟踪系统.
此时此刻在github上LTTng唯有100多星而babeltrace唯有20多,
也表达了没有几人在用它们.
自家不晓得为何CoreCL冠道要用LTTng, 但欣慰的是CoreCL奥迪Q52.1会有新的跟踪机制EventPipe,
到时候能够更简明的落成跨平台捕获CoreCL奥迪Q3跟踪事件.

自家当下写的调用ETW的代码放在了这里,
调用LTTng的代码放在了这里,
有趣味的能够去参考.

获得事件

在告知LTTng启用跟踪后, 小编还亟需获得发送到LTTng的事件,
在ETW中收获事件是因而挂号回调获取的:

EVENT_TRACE_LOGFILE trace = { };
trace.LoggerName = (char*)mySessionName.c_str();
trace.EventRecordCallback = (PEVENT_RECORD_CALLBACK)(StaticRecordEventCallback);
trace.BufferCallback = (PEVENT_TRACE_BUFFER_CALLBACK)(StaticBufferEventCallback);
trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME;
TRACEHANDLE sessionHandle = ::OpenTrace(&trace);
if (sessionHandle == INVALID_PROCESSTRACE_HANDLE) {
    // ...
}
ULONG processStatus = ::ProcessTrace(&sessionHandle, 1, nullptr, nullptr);

自家寻思lttng有没有那样的编写制定,
首先本身看出的是lttng.h中的lttng_register_consumer函数,
那个函数的诠释如下:

This call registers an "outside consumer" for a session and an lttng domain.
No consumer will be spawned and all fds/commands will go through the socket path given (socket_path).

翻译出来便是给会话注册二个外表的顾客, 听上去和本人的渴求很像啊?
那么些函数的第②个参数是三个字符串, 笔者想见是unix socket, lttng会通过unix
socket发送事件过来.
于是自个儿写了这么的代码:

ret = lttng_register_consumer(handle, "/tmp/custom-consumer");

一履行及时报错, 错误是Command undefined, 也等于命令未定义,
服务端不帮忙那几个命令.
通过查找发现lttng的源代码中尚无另向外调拨运输用那个函数的地方,
也便是说这几个函数是个装饰.
看起来那几个措施行不通.


透过一番招来,
我发觉了live-reading-howto其一文档,
里面包车型地铁始末相当少但是能够见见使用lttng-relayd那一个服务能够读取事件.
读取事件近期只协理TCP, 使用TCP传输事件数量不仅复杂而且功效相当低,
相对ETW间接通过内部存款和储蓄器传递数据那的确是个鸠拙的办法.
虽说鲁钝不过如故要持续写, 笔者初阶看那TCP传输用的是哪些协议.

对传输协议的分解文书档案在live-reading-protocol.txt,
那篇文书档案写的很倒霉, 但总比没有好.
lttng-relayd开始展览相互使用的是二个lttng自身创设的半双工二进制协议,
设计如下:

客户端发送命令给lttng-relayd亟待服从以下的格式

[data_size: unsigned 64 bit big endian int, 命令体大小]
[cmd: unsigned 32 bit big endian int, 命令类型]
[cmd_version: unsigned 32 bit big endian int, 命令版本]
[命令体, 大小是data_size]

出殡命令的安顿性没反常, 大多数二进制协议都以那样设计的,
难题在于接收命令的设计.
接收命令的格式完全依靠于发送命令的种类,
例如LTTNG_VIEWER_CONNECT以此命令发送过去会接到以下的数目:

[viewer_session_id: unsigned 64 bit big endian int, 服务端指定的会话ID]
[major: unsigned 32 bit big endian int, 大版本]
[minor: unsigned 32 bit big endian int, 中版本]
[type: 客户端的类型]

能够看到接收的数额从未有过数据头, 没有数据头怎么样控制收取多少数量吧?
那就供给客户端定义的回应大小必须和服务端完全一致, 二个字段都不能够漏.
服务端在其后的更新中不可能给重临数据随意添加字段,
重回多少字段供给取决于发送过来的cmd_version,
保持api的包容性将会卓殊的麻烦.
目前在lttng中cmd_version是1个留给字段,
也正是她们一贯不仔细的想过api的更新难点.
不错的做法应该是再次回到数据也应有提供3个数据头,
然后同意客户端忽略多出去的数据.


看完协议未来, 笔者在想既然使用了二进制协议,
应该也会提供3个sdk来压缩解析的工作量吗?
透过一番搜索找到了3个头文件lttng-viewer-abi.h,
包括了和lttng-relayd互动使用的数据结构体定义.
以此头文件在源代码里面有, 可是却不在LTTng发表的软件包中,
那意味着使用它须求复制它到花色里面.
复制旁人的源代码到花色里面不可能那么不论是,
看了须臾间LTTng的开源协议,
include/lttng/*src/lib/lttng-ctl/*下的文件是LGPL,
其他文件是GPL,
也正是上边假定把这一个头文件复制到自身的类型里面,
自己的类型必须接纳GPL协议开源
,
不想用GPL的话只可以把里面包车型大巴始末本人一行行重新写, 还无法写的太像.

既是是测试就随便如此多了, 把那个头文件的代码复制过来就开端继续写,
首先是连接受lttng-relayd:

int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
    perror("socket");
    return -1;
}
sockaddr_in address = {};
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_family = AF_INET;
address.sin_port = htons(5344);
ret = connect(fd, (sockaddr*)&address, sizeof(address));
if (ret < 0) {
    perror("connect");
    return -1;
}

总是成功之后的交互流程在翻阅方面包车型客车情商文书档案今后能够整理如下:

初始化
    客户端发送命令 LTTNG_VIEWER_CLIENT_COMMAND + 构造体 lttng_viewer_connect
    服务端返回构造体 lttng_viewer_connect
    客户端发送命令 LTTNG_VIEWER_CREATE_SESSION + 构造体 lttng_viewer_create_session_response
    服务端返回构造体 lttng_viewer_create_session_response
列出会话
    客户端发送命令 LTTNG_VIEWER_LIST_SESSIONS, 不带构造体
    服务端返回构造体 lttng_viewer_list_sessions + 指定长度的 lttng_viewer_session
附加到会话
    客户端发送命令 LTTNG_VIEWER_ATTACH_SESSION + 构造体 lttng_viewer_attach_session_request
    服务端返回构造体 lttng_viewer_attach_session_response + 指定长度的 lttng_viewer_stream
循环 {
    如果需要获取新的流 {
        客户端发送命令 LTTNG_VIEWER_GET_NEW_STREAMS + 构造体 lttng_viewer_new_streams_request
        服务端返回构造体 lttng_viewer_new_streams_response + 指定长度的 lttng_viewer_stream
    }
    如果需要获取新的元数据(metadata) {
        枚举现存的metadata流列表 {
            客户端发送命令 LTTNG_VIEWER_GET_METADATA + 构造体 lttng_viewer_get_metadata
            服务端返回构造体 lttng_viewer_metadata_packet + 指定长度的payload
        }
    }
    枚举现存的trace流列表 {
        客户端发送命令 LTTNG_VIEWER_GET_NEXT_INDEX + 构造体 lttng_viewer_get_next_index
        服务端返回构造体 lttng_viewer_index
        检查返回的 index.flags, 如果服务端出现了新的流或者元数据, 需要先获取新的流和元数据才可以继续
        客户端发送命令 LTTNG_VIEWER_GET_PACKET + 构造体 lttng_viewer_trace_packet
        服务端返回构造体 lttng_viewer_trace_packet + 指定长度的payload
        根据metadata packet和trace packet分析事件的内容然后记录事件
    }
}

是或不是认为很复杂?
因为协议决定了服务端发给客户端的多少没有数据头,
所以服务端无法积极推送数据到客户端, 客户端必须主动的去开始展览轮询.
即使您放在心上到构造体的称呼,
会发现部分构造体前面有request和response而某些没有,
如若不看上下文只看构造体的名称很难猜到它们的成效.
是的的做法是兼备请求和再次来到的布局体名称末尾都添加request和response,
不要去大致这一个字母而浪费思考的时间.


为了发送命令和接收构造体作者写了部分帮忙函数, 它们并不复杂,
使用TCP交互的次序都会有接近的代码:

int sendall(int fd, const void* buf, std::size_t size) {
    std::size_t pos = 0;
    while (pos < size) {
        auto ret = send(fd,
            reinterpret_cast<const char*>(buf) + pos, size - pos, 0);
        if (ret <= 0) {
            return -1;
        }
        pos += static_cast<std::size_t>(ret);
    }
    return 0;
}

int recvall(int fd, void* buf, std::size_t size) {
    std::size_t pos = 0;
    while (pos < size) {
        auto ret = recv(fd,
            reinterpret_cast<char*>(buf) + pos, size - pos, 0);
        if (ret <= 0) {
            return -1;
        }
        pos += static_cast<std::size_t>(ret);
    }
    return 0;
}

template <class T>
int sendcmd(int fd, std::uint32_t type, const T& body) {
    lttng_viewer_cmd cmd = {};
    cmd.data_size = htobe64(sizeof(T));
    cmd.cmd = htobe32(type);
    if (sendall(fd, &cmd, sizeof(cmd)) < 0) {
        return -1;
    }
    if (sendall(fd, &body, sizeof(body)) < 0) {
        return -1;
    }
    return 0;
}

初步化连接的代码如下:

lttng_viewer_connect body = {};
body.major = htobe32(2);
body.minor = htobe32(9);
body.type = htobe32(LTTNG_VIEWER_CLIENT_COMMAND);
if (sendcmd(fd, LTTNG_VIEWER_CONNECT, body) < 0) {
    return -1;
}
if (recvall(fd, &body, sizeof(body)) < 0) {
    return -1;
}
viewer_session_id = be64toh(body.viewer_session_id);

前边的代码比较干燥小编就简单了,
想看完整代码的能够看这里.


进入循环后会从lttng-relayd收获二种有效的数量:

  • 元数据(metadata), 定义了跟踪数据的格式
  • 跟踪数据(trace), 包蕴了轩然大波信息例如GC初叶和完工等

获得元数据采纳的是LTTNG_VIEWER_GET_METADATA命令,
获取到的元数据内容如下:

Wu@"Jtf@oe/* CTF 1.8 */

typealias integer { size = 8; align = 8; signed = false; } := uint8_t;
typealias integer { size = 16; align = 8; signed = false; } := uint16_t;
typealias integer { size = 32; align = 8; signed = false; } := uint32_t;
typealias integer { size = 64; align = 8; signed = false; } := uint64_t;
typealias integer { size = 64; align = 8; signed = false; } := unsigned long;
typealias integer { size = 5; align = 1; signed = false; } := uint5_t;
typealias integer { size = 27; align = 1; signed = false; } := uint27_t;

trace {
    major = 1;
    minor = 8;
    uuid = "a3df4090-0722-4a74-97a4-81e066406f03";
    byte_order = le;
    packet.header := struct {
        uint32_t magic;
        uint8_t  uuid[16];
        uint32_t stream_id;
        uint64_t stream_instance_id;
    };
};

env {
    hostname = "ubuntu-virtual-machine";
    domain = "ust";
    tracer_name = "lttng-ust";
    tracer_major = 2;
    tracer_minor = 9;
};

clock {
    name = "monotonic";
    uuid = "f397e532-4837-402b-8cc9-700ed92a339d";
    description = "Monotonic Clock";
    freq = 1000000000; /* Frequency, in Hz */
    /* clock value offset from Epoch is: offset * (1/freq) */
    offset = 1514336042565610080;
};

typealias integer {
    size = 27; align = 1; signed = false;
    map = clock.monotonic.value;
} := uint27_clock_monotonic_t;

typealias integer {
    size = 32; align = 8; signed = false;
    map = clock.monotonic.value;
} := uint32_clock_monotonic_t;

typealias integer {
    size = 64; align = 8; signed = false;
    map = clock.monotonic.value;
} := uint64_clock_monotonic_t;

struct packet_context {
    uint64_clock_monotonic_t timestamp_begin;
    uint64_clock_monotonic_t timestamp_end;
    uint64_t content_size;
    uint64_t packet_size;
    uint64_t packet_seq_num;
    unsigned long events_discarded;
    uint32_t cpu_id;
};

struct event_header_compact {
    enum : uint5_t { compact = 0 ... 30, extended = 31 } id;
    variant <id> {
        struct {
            uint27_clock_monotonic_t timestamp;
        } compact;
        struct {
            uint32_t id;
            uint64_clock_monotonic_t timestamp;
        } extended;
    } v;
} align(8);

struct event_header_large {
    enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;
    variant <id> {
        struct {
            uint32_clock_monotonic_t timestamp;
        } compact;
        struct {
            uint32_t id;
            uint64_clock_monotonic_t timestamp;
        } extended;
    } v;
} align(8);

stream {
    id = 0;
    event.header := struct event_header_compact;
    packet.context := struct packet_context;
    event.context := struct {
        integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _vpid;
        integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _vtid;
    };
};

event {
    name = "DotNETRuntime:GCStart_V2";
    id = 0;
    stream_id = 0;
    loglevel = 13;
    fields := struct {
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Count;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Depth;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Reason;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Type;
        integer { size = 16; align = 8; signed = 0; encoding = none; base = 10; } _ClrInstanceID;
        integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } _ClientSequenceNumber;
    };
};

其一元数据的格式是CTF Metadata,
这些格式看上去像json可是并不是, 是LTTng的商号温馨制造的3个文本格式.
babeltrace中包涵了分析那几个文本格式的代码,
不过没有开放任何解析它的接口,
也正是一旦您想自身分析唯其如此写3个词法分析器.
这个格式其实能够应用json表示, 体积不会大增多少,
不过那公司正是发明了三个新的格式扩展使用者的负担.
写三个词法分析器要求1天时间和一千行代码, 这里自身就先跳过了.


接下去获取跟踪数据,
使用的是LTTNG_VIEWER_GET_NEXT_INDEX和LTTNG_VIEWER_GET_PACKET命令.
LTTNG_VIEWER_GET_NEXT_INDEX重回了脚下流的offset和可获得的content_size,
这里的content_size单位是位(bit),
约等于急需除以8才方可算出能够拿走多少字节,
关于content_size的单位LTTng中没有其他文书档案和注释表明它是位,
只有四个测试代码里面包车型大巴某行写了/ CHAR_BIT.
使用LTTNG_VIEWER_GET_PACKET命令,
传入offset和content_size/8能够取得跟踪数据(假如不/8会获得到剩余的数量可能再次回到E奥迪Q7Rubicon).
实际上再次回到的跟踪数据如下:

000000: c1 1f fc c1 29 82 6b fe 24 10 4c 6b 97 91 4d c3  ....).k.$.Lk..M.
000010: ed d4 41 8f 00 00 00 00 03 00 00 00 00 00 00 00  ..A.............
000020: 92 91 49 96 08 0a 00 00 07 a0 58 b9 08 0a 00 00  ..I.......X.....
000030: 50 05 00 00 00 00 00 00 00 80 00 00 00 00 00 00  P...............
000040: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000050: 03 00 00 00 1f 00 00 00 00 92 91 49 96 08 0a 00  ...........I....
000060: 00 e1 1b 00 00 03 00 00 00 02 00 00 00 01 00 00  ................
000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1f  ................
000080: 00 00 00 00 4d ae a7 af 08 0a 00 00 e1 1b 00 00  ....M...........
000090: 04 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00  ................
0000a0: 00 00 00 00 00 00 00 00 00 00                    ..........

盯住数据的格式是CTF Stream
Packet
,
也是贰个自定义的二进制格式, 要求般配元数据解析.
babeltrace中一致没有开放解析它的接口(有python
binding不过尚未解析数据的函数), 也正是急需协调写二进制数据解析器.

操作LTTng + 和relayd通信 + 元数据词法分析器 +
跟踪数据解析器全体加起来估摸须求三千行代码,
而那总体应用ETW只用了100多行代码.
倒霉的设计, 复杂的施用, 落后的文书档案, 各类各个的自定义协议和数目格式,
不提供SDK把LTTng营造成了三个比ETW更难用的跟踪系统.
时下在github上LTTng只有100多星而babeltrace唯有20多,
也认证了从未有过稍微人在用它们.
本身不精通怎么CoreCL陆风X8要用LTTng, 但欣慰的是CoreCLCRUISER2.1会有新的跟踪机制EventPipe,
到时候能够更简明的达成跨平台捕获CoreCL君越跟踪事件.

自个儿当下写的调用ETW的代码放在了这里,
调用LTTng的代码放在了这里,
有趣味的能够去参考.

教训

最差的API(ETW)和更差的API(LTTng)都看过了, 那么相应什么防止他们的谬误,
编写二个好的API呢?

Casey
Muratori
波及的训诫有:

教训

最差的API(ETW)和更差的API(LTTng)都看过了, 那么相应如何幸免他们的一无可取,
编写七个好的API呢?

Casey
Muratori
波及的教训有:

统一筹划API的第壹条和第一条规则: “永远都从编写用例开首”

统一筹划1个API时, 首先要做的是站在调用者的立场, 想想调用者要求什么样,
如何才能最简便的达到那一个须要.
编写制定五个简练的用例代码永远是设计API中务必的一步.
毫无过多的去想内部贯彻, 假使内部贯彻机制让API变得复杂,
应该想方法去抽象它.

安顿API的首先条和第三条规则: “永远都从编写用例开头”

规划2个API时, 首先要做的是站在调用者的立场, 想想调用者需求哪些,
怎么着才能最简单易行的直达这一个须要.
编纂三个大致的用例代码永远是统筹API中必须的一步.
决不过多的去想内部贯彻, 假如内部贯彻机制让API变得复杂,
应该想办法去抽象它.

设想到未来的壮大

因为须求会不断变更, 设计API的时候应该为前途的转变预留空间,
保证向后非凡性.
例如ETW中监听的事件类型.aspx)使用了位标记,
也正是参数是37位时最四只好有32种事件,
考虑到今后有更加多事件应该把事件类型定义为总是的数值并提供额外的API启用事件.
当今有无数接口在安排时会考虑到版本, 例如用v1和v2区分,
那是叁个很好的策略.

设想到未来的扩大

因为须求会不断变更, 设计API的时候理应为前途的成形预留空间,
保障向后相当性.
例如ETW中监听的轩然大波类型.aspx)使用了位标记,
也即是参数是叁拾几个人时最多只可以有32种事件,
考虑到以往有愈来愈多事件应该把事件类型定义为三番五次的数值并提供额外的API启用事件.
今昔有好多接口在设计时会考虑到版本, 例如用v1和v2区分,
那是1个很好的策略.

精晓接口的输入和出口

决不为了省去代码去让多个接口接收大概重临多余的音讯.
在ETW中过多接口都共用了叁个大构造体EVENT_TRACE_PROPERTIES,
调用者很难搞理解接口使用了构造体里面包车型大巴什么值, 又影响了如何值.
统一筹划API时应该显明接口的指标, 让接口接收和重回须要且最少的音讯.

明明接口的输入和输出

绝不为了节省代码去让一个接口接收大概重回多余的音讯.
在ETW中有的是接口都共用了1个大构造体EVENT_TRACE_PROPERTIES,
调用者很难搞明白接口使用了构造体里面包车型大巴什么值, 又影响了怎么值.
设计API时应有分明接口的指标, 让接口接收和重返供给且最少的消息.

提供完整的演示代码

对调用者来说, 100行的言传身教代码日常比1000行的文书档案更有意义.
因为接口的设计者和调用者拥有的知识量常常不对等,
调用者在尚未见到实际的例证在此以前, 很或者不能领悟设计者编写的文书档案.

提供完整的演示代码

对调用者来说, 100行的言传身教代码平日比1000行的文书档案更有意义.
因为接口的设计者和调用者拥有的知识量平日不对等,
调用者在一贯不看到实际的例证在此以前, 很只怕不能驾驭设计者编写的文书档案.

不用使用魔法数字

那是很多接口都会犯的一无可取, 例如ETW中控制事件附加的音信时, 1表示时间戳,
2表示系统时间, 3代表CPU周期计数.
比方你必要传递具有某种意义的数字给接口,
请务必在SDK中为该数字定义枚举类型.

本人从LTTng中吸收到的训诫有:

永不采纳魔法数字

那是成百上千接口都会犯的失实, 例如ETW中央控制制事件附加的信息时, 1代表时间戳,
2表示系统时间, 3代表CPU周期计数.
若是你要求传递具有某种意义的数字给接口,
请务必在SDK中为该数字定义枚举类型.

自作者从LTTng中吸纳到的教训有:

写文档

99%的调用者没有看源代码的兴趣可能能力,
不写文书档案没有人会分晓怎么去调用你的接口.
方今有成都百货上千自动生成文书档案的工具, 用那些工具得以削减过多的工作量,
不过你照旧应该手动去编写2个入门的文书档案.

写文档

99%的调用者没有看源代码的趣味可能能力,
不写文书档案没有人会领会如何去调用你的接口.
近日有不少自动生成文书档案的工具, 用那一个工具得以减掉过多的工作量,
可是你照样应该手动去编写三个入门的文档.

不用随便的去创立1个商议

创立2个新的商议表示供给编写制定新的代码去分析它,
而且各种程序语言都要重复编排3遍.
只有你很有活力, 能够为主流的程序语言都提供2个SDK, 不然不引进那样做.
许多品类都提供了REST API, 这是很好的矛头,
因为大概各样语言都有现成的类库能够便宜地调用REST API.

不用任意的去创设一个商业事务

创造二个新的协议表示需求编制新的代码去分析它,
而且每一种程序语言都要重复编写2回.
唯有您很有生命力, 能够为主流的程序语言都提供三个SDK, 不然不引进那样做.
有的是品种都提供了REST API, 那是很好的趋向,
因为大致各样语言都有现成的类库能够一本万利地调用REST API.

谨慎的去定义二进制协议

概念贰个好的二进制协议必要很深的素养, LTTng定义的合计显著考虑的太少.
推荐的做法是明显区分请求和应对, 请求和应对都应当有三个含有长度的头,
支持全双工通讯.
若是你想设计1个二进制协议,
强烈提出参考Cassandra数据库的磋商文书档案,
这一个体协会议无论是设计依然文书档案都以一等的水平.
唯独尽管您没有对传输品质有很苛刻的要求, 提议采纳现成的商业事务加json恐怕xml.

严刻的去定义二进制协议

概念1个好的二进制协议必要很深的功力, LTTng定义的协议分明考虑的太少.
推荐的做法是肯定区分请求和回答, 请求和回答都应当有一个含有长度的头,
补助全双工通讯.
一经你想设计四个二进制协议,
强烈提议参考Cassandra数据库的切磋文书档案,
这么些体协会议无论是设计还是文书档案都以头等的水平.
可是一旦您从未对传输品质有很苛刻的必要, 提出使用现成的说道加json恐怕xml.

不要去创立1个DSL(Domain-specific language)

那里小编从未写轻易, 假诺你有多个数据结构须要代表成文本,
请使用更通用的格式.
LTTng表示元数据时利用了一个体协会调成立的DSL,
但里面包车型大巴情节用json表示也不会追加多少年体育积,
也便是说创建一个DSL没有任何好处.
分析DSL须要本身编排词法分析器,
即便是经历老到的程序员编写2个也需求过多时刻(包罗单元测试越多),
要是使用json等通用格式那么编写解析的代码只须求几分钟.

不要去创建1个DSL(Domain-specific language)

此间本人从不写轻易, 假若你有2个数据结构需求代表成文本,
请使用更通用的格式.
LTTng表示元数据时使用了一个和谐创造的DSL,
但里面包车型客车剧情用json表示也不会追加多少年体育积,
也正是说创制二个DSL没有任何好处.
解析DSL供给协调编辑词法分析器,
即便是经验老到的程序员编写一个也急需广大日子(包涵单元测试越多),
假若使用json等通用格式那么编写解析的代码只须求几秒钟.

写在终极

尽管那篇小说把LTTng批评了一番,
但这也许是目前些天下唯一一篇涉嫌如何通过代码调用LTTng和接收事件数量的作品.
梦想看过那篇小说的统一筹划API时多为调用者着想,
你偷懒省下几分钟往往会促成别人浪费几天的时间.

写在结尾

就算那篇小说把LTTng批评了一番,
但那只怕是眼前海内外唯一一篇涉嫌如何通过代码调用LTTng和收取事件数量的文章.
瞩望看过那篇文章的陈设性API时多为调用者着想,
你偷懒省下几分钟往往会促成别人浪费几天的时间.

相关文章

网站地图xml地图