manbetx官方网站

LTTng,谈什么勾勒一个吓的接口

十二月 17th, 2018  |  manbetx官方网站

近年眼看几上在帮柠檬在押她底APM系统而哪些收集.Net运行时之各类风波,
那么些事件包括线程先河, JIT执行, GC触发等等.
.Net于windows上(NetFramework, CoreCLR)通过ETW(伊芙(Eve)nt Tracing for
Windows), 在linux上(CoreCLR)是经过LTTng跟踪事件.

不久前就几乎天在帮柠檬扣押其底APM系统要什么收集.Net运行时的各样风波,
这几个事件包括线程初步, JIT执行, GC触发等等.
.Net于windows上(NetFramework, CoreCLR)通过ETW(伊夫nt Tracing for
Windows), 在linux上(CoreCLR)是透过LTTng跟踪事件.

ETW的API设计已经为很多口诟病,
微软推出的类库krabsetw中直指ETW是无比差之API而将操作ETW的文书命名为噩梦.hpp.
而且登时首稿子惨遭, Casey
Muratori解释了怎么ETW是极致差之API, 原盖包括:

ETW的API设计已为两人喝斥,
微软推出的类库krabsetw中直指ETW是极其差的API而把操作ETW的文件命名吧噩梦.hpp.
而且即时篇作品吃, Casey
Muratori解释了胡ETW是无与伦比差的API, 原盖包括:

  • 事件类应用了个标志(最两只好发出32单), 没有考虑到未来的状态
  • 今非昔比之接口共用一个挺的构造体, 接口的输入和输出不显眼
  • 让调用者写无论怎么看都是多余的代码
  • 于调用者使用魔法数字(而休是提供一个朵举值)
  • 命名带有误导性
  • 重临值的意思不统一
  • 动用过度复杂, 没有先行想吓用例
  • 文档中没有整的示范代码, 只好从零碎的代码拼凑
  • 事件类应用了个标志(最四只可以发出32单), 没有考虑到以后的图景
  • 今非昔比之接口共用一个不行的构造体, 接口的输入和出口不明朗
  • 深受调用者写无论怎么看都是多余的代码
  • 让调用者使用魔法数字(而未是提供一个枚举值)
  • 取名带有误导性
  • 再次来到值的义不合并
  • 使用过度复杂, 没有优先想吓用例
  • 文档中没有完整的以身作则代码, 只可以打零碎的代码拼凑

不过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那序提供了多参数,
假若一个但是想跟用户事件之主次启动了这一个服务并点名了忽略内核事件之参数,
然后此外一个跟踪内核事件的程序用未可以健康运作.
是的做法是行使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汇合回去成功了, 不过同时发现了初的题材,
创办的对话是由于一个独自的服务管理之, 即便当前进程退出会话也相会是,
第二次等创的时段会回到一个曾在的错误.
这么些题材和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,
看源代码就足以想见怎么着调用.

博事件

opebet体育app,在告诉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凡是一个养字段,
也不怕是他俩无仔细的想过api的换代问题.
没错的做法应该是再次回到数据吧应有提供一个数据头,
然后允客户端忽略多出的数据.


圈了协议下, 我以想既使用了第二上前制协议,
应该为会供一个sdk来减解析的工作量吧?
通过一番搜找到了一个匹文件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未遭含有精晓析这些文本格式的代码,
不过没有开丢弃何解析其的接口,
也就是是一旦你想自己分析不得不写一个词法分析器.
这么些格式其实可以使json表示, 体积不会合追加多少,
可是立局就是发明了一个初的格式扩大使用者的负担.
形容一个词法分析器需要1上时间跟1000行代码, 这里我虽先行跳了了.


搭下去获取跟踪数据,
使用的是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会合落到剩余的多少依旧重临ERR).
事实上再次回到的跟数据如下:

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通讯 + 元数据词法分析器 +
跟踪数据解析器全体加以起来估量用2000行代码,
而这总体应用ETW只所以了100差不多行代码.
不佳之统筹, 复杂的选择, 落后的文档, 各类各种的自定义协议以及多少格式,
不提供SDK把LTTng打招了一个比ETW更难以用之跟系统.
眼前于github上LTTng只出100大抵星而babeltrace只发20大抵,
也表达了从未稍微人在用它们.
我不知道为啥CoreCLR要为此LTTng, 但欣慰之是CoreCLR
2.1会见暴发新的跟踪机制EventPipe,
到早晚可以又简明的落实跨平台捕获CoreCLR跟踪事件.

本人当下写的调用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举凡一个雁过拔毛字段,
也即是他们没有仔细的想过api的翻新问题.
然的做法应该是回来数据为应该提供一个数据头,
然后许客户端忽略多出去的数据.


看罢协议下, 我于回想既用了次前进制协议,
应该为会供一个sdk来减解析的工作量吧?
透过一番摸索找到了一个峰文件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备受含有了分析这一个文本格式的代码,
但是没有放弃何解析其的接口,
也即是假若你想自己分析唯其如此写一个词法分析器.
这些格式其实可以使用json表示, 体积不会面扩大多少,
不过登事势就是发明了一个初的格式扩张使用者的负担.
形容一个词法分析器需要1上时间及1000行代码, 那里我不怕先行跳了了.


紧接下获取跟踪数据,
使用的凡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会见获拿到剩余的数目或者再次来到ERR).
实际上重返的跟数据如下:

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通讯 + 元数据词法分析器 +
跟踪数据解析器全部加起推断需要2000行代码,
而下一周使ETW只所以了100基本上行代码.
不好的规划, 复杂的接纳, 落后的文档, 各样各个的自定义协议以及数码格式,
不提供SDK把LTTng打招了一个比ETW更难以用之跟系统.
即以github上LTTng只出100大抵星而babeltrace只出20大抵,
也表明了从未稍微人当为此它们.
自我不亮堂为何CoreCLR要用LTTng, 但欣慰之是CoreCLR
2.1碰面出新的跟踪机制EventPipe,
到时刻能够另行简便易行的兑现跨越平台捕获CoreCLR跟踪事件.

本身手上勾勒的调用ETW的代码放在了这里,
调用LTTng的代码放在了这里,
有趣味的足去参考.

教训

极差的API(ETW)和还不比之API(LTTng)都扣留了了, 那么该怎么样避免他们之缪,
编写一个吓的API呢?

Casey
Muratori
关系的训诫有:

教训

最为差之API(ETW)和还不比的API(LTTng)都扣留了了, 那么该怎样防止他们之一无是处,
编写一个吓的API呢?

Casey
Muratori
涉及的训有:

筹API的首先长长的及次长长的规则: “永远都由编写用例开端”

筹一个API时, 首先要举办的凡立于调用者的立足点, 想想调用者需要什么,
怎么着才可以顶简便易行的达标这需求.
编排一个粗略的用例代码永远是计划性API中必须的同步.
绝不了多之去想内部贯彻, 假诺内部贯彻机制让API变得复杂,
应该想方法去抽象它.

筹API的第一长条跟第二长规则: “永远都起编写用例初阶”

规划一个API时, 首先使做的凡立在调用者的立场, 想想调用者需要什么,
咋样才会最好简便易行的高达这个需求.
修一个简单的用例代码永远是设计API中务必的一致步.
决可是多的失想内部贯彻, 假设内部贯彻机制让API变得复杂,
应该想方去抽象它.

设想到以后的恢宏

坐需求会不断变更, 设计API的早晚该吗前途的别预留空间,
保证为后相当性.
例如ETW中监听的轩然大波类.aspx)使用了个标记,
也尽管是参数是32位时最三只好有32种植事件,
考虑到未来起又一次多事件应该拿事件类型定义为连日来的数值并提供额外的API启用事件.
如今时有发生无数接口在计划时会面设想到本, 例如用v1和v2区分,
这是一个不行好的策略.

设想到将来底壮大

坐需求会不断变动, 设计API的上该也未来的更动预留空间,
保证为后很是性.
例如ETW中监听的轩然大波类.aspx)使用了个标记,
也就是参数是32各项时最四只好有32种植事件,
考虑到将来发重多事件应该拿事件类型定义为连日来的数值并提供额外的API启用事件.
当今生为数不少接口在设计时谋面设想到本, 例如用v1和v2区分,
那是一个百般好之策略.

显明接口的输入和出口

不用为省代码去叫一个接口接收或者重返多余的信息.
以ETW中诸多接口都同用了一个大构造体EVENT_TRACE_PROPERTIES,
调用者很麻烦施懂接口使用了盘造体里面的咋样值, 又影响了什么样值.
计划API时应醒目接口的目标, 让接口接收和归必要且最好少之音信.

显而易见接口的输入和出口

绝不为了省去代码去吃一个接口接收或者重回多余的音信.
在ETW中很多接口都一起用了一个大构造体EVENT_TRACE_PROPERTIES,
调用者很不便将精晓接口使用了修建造体里面的什么值, 又影响了争值.
筹API时该明了接口的目标, 让接口接收及归必要且最少的音讯.

供整机的言传身教代码

对调用者来说, 100实施的示范代码日常较1000实践之文档更有意义.
盖接口的设计者和调用者拥有的知识量平日不对等,
调用者在无观察实际的例证从前, 很可能不可能清楚设计者编写的文档.

提供全部的以身作则代码

针对调用者来说, 100实践之演示代码平常较1000履的文档更有意义.
坐接口的设计者和调用者拥有的知识量平常不对等,
调用者在没看实际的例子从前, 很可能无法知道设计者编写的文档.

毫不使魔法数字

随即是无数接口都会犯的一无是处, 例如ETW中决定事件附加的消息时, 1代表时间戳,
2表示系统时, 3代表CPU周期计数.
假定您得传递具有某种意义的数字为接口,
请务必以SDK中也该数字定义枚举类型.

自我打LTTng中吸收及之教训有:

不要动魔法数字

立即是广大接口都会犯的左, 例如ETW中控制事件附加的音平日, 1表示时间戳,
2代表系统时, 3代表CPU周期计数.
使您得传递具有某种意义的数字被接口,
请务必以SDK中呢该数字定义枚举类型.

自我起LTTng中收取到之教训有:

写文档

99%底调用者没有看源代码的兴味或能力,
不写文档没有丁会面分晓怎么错过调整用你的接口.
现暴发成百上千自动生成文档的家伙, 用这么些家伙得以减小过多的工作量,
然而若仍然当手动去编写一个入门的文档.

写文档

99%之调用者没有看源代码的兴味或能力,
不写文档没有丁会了然如何去调动用你的接口.
兹暴发诸多自动生成文档的工具, 用那些工具得以抽过多底工作量,
然而若依旧当手动去编写一个入门的文档.

决不随便之去创制一个商

始建一个新的协商表示需要编制新的代码去分析其,
而且每个程序语言都设双重编排一软.
只有你很有生命力, 可以吗主流的程序语言都提供一个SDK, 否则无引进这样做.
重重类都提供了REST API, 那是颇好之矛头,
因为几每个语言都发生现成的类库可以方便地调用REST API.

毫不擅自的失去创建一个商议

创建一个新的商表示要编制新的代码去分析其,
而且每个程序语言都如双重编排一软.
唯有您死有生气, 可以为主流的程序语言都提供一个SDK, 否则不引进这样做.
洋洋型还提供了REST API, 这是丰硕好之自由化,
因为几乎每个语言都暴发现成的类库可以一本万利地调用REST API.

当心之去定义二进制协议

概念一个好之二进制协议要充足挺的功力, LTTng定义之说道分明考虑的最好少.
推荐的做法是明确区分请求与回复, 请求和回复都应有来一个带有长度的峰,
辅助全双工通信.
倘您想设计一个二进制协议,
强烈提出参考卡Sandra(Cassandra)(Cassandra)数据库的共谋文档,
那个协议无论是设计仍旧文档都是五星级的水平.
而是只要你莫对传输性能有充裕严峻的求, 提出利用现成的商议加json或者xml.

严厉之去定义二进制协议

概念一个吓的二进制协议需卓殊挺的造诣, LTTng定义之商事显明考虑的绝少.
推介的做法是肯定区分请求与报, 请求和报还当出一个饱含长度的腔,
补助都双工通信.
设您想设计一个二进制协议,
强烈提出参考卡Sandra(Cassandra)数据库的合计文档,
这多少个协议无论是设计要文档都是一品的水平.
不过假诺你无针对传输性能有甚严酷的渴求, 提出用现成的合计加json或者xml.

无苟失去创建一个DSL(Domain-specific language)

此地我尚未写轻易, 假若你来一个数据结构需要代表成文本,
请使用还通用的格式.
LTTng表示正数据时选用了一个温馨创办的DSL,
但里面的始末用json表示为不汇合追加多少体积,
也就是说成立一个DSL没有任何好处.
分析DSL需要协调编辑词法分析器,
即便是更老到的程序员编写一个吗要多多时空(包含单元测试更多),
假设使用json等通用格式那么编写解析的代码只需要几分钟.

免若去创造一个DSL(Domain-specific language)

这里我尚未写轻易, 假使你生出一个数据结构需要代表成文本,
请使用更通用的格式.
LTTng表示正数据平日使用了一个和好成立的DSL,
但里面的情节用json表示为无碰面多多少体积,
也就是说成立一个DSL没有此外好处.
浅析DSL需要团结修词法分析器,
尽管是经历老到的程序员编写一个呢得多岁月(包含单元测试更多),
倘若使用json等通用格式那么编写解析的代码只需要几分钟.

描绘在末

虽当时篇稿子将LTTng批评了一样旗,
但这或者是当下全世界唯一一篇涉嫌怎么着通过代码调用LTTng和接事件数量的著作.
期看罢及时篇著作的筹划API时大都为调用者着想,
你偷懒省下几分钟数会招别人浪费几龙之时间.

形容在末

即使如此这篇稿子将LTTng批评了同等外来,
但这或是眼下全世界唯一一首涉嫌如何通过代码调用LTTng和接收事件数量的作品.
指望看了及时首著作的筹划API时大都呢调用者着想,
你偷懒省下几乎分钟数会招致别人浪费几上之时间.

相关文章

Your Comments

近期评论

    功能


    网站地图xml地图