近期立马几乎上在扶持柠檬圈它底APM系统要怎么样收集.Net运行时的各种风波,
这些事件包括线程开始, JIT执行, GC触发等等.
.Net于windows上(NetFramework, CoreCLR)通过ETW(Event Tracing for
Windows), 在linux上(CoreCLR)是经过LTTng跟踪事件.

ETW的API设计既让不少人口骂,
微软推出的类库krabsetw中直指ETW是极度差的API并且将操作ETW的文件命名吧噩梦.hpp.
而且马上篇稿子遇, Casey
Muratori解释了为何ETW是最为差的API, 原坐包括:

  • 事件类应用了个标志(最多只能发出32单), 没有考虑到未来之场面
  • 今非昔比之接口共用一个异常的构造体, 接口的输入和输出不显
  • 给调用者写无论怎么看还是多余的代码
  • 被调用者使用魔法数字(而未是提供一个枚举值)
  • 取名带有误导性
  • 返回值的意思不统一
  • 动过度复杂, 没有先想吓用例
  • 文档中没完整的演示代码, 只能从零碎的代码拼凑

唯独Casey Muratori的稿子针对性自我帮忙特别非常,
我才所以了1上时间便写有了采取ETW收集.Net运行时事件的示范代码.
今后我开看什么用LTTng收集这些事件,
按照自往的经验linux上的类库api通常会比windows的好用, 但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的风波,
在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的代码放在了这里,
调用LTTjsonng的代码放在了这里,
有趣味之得错过参考.

教训

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

Casey
Muratori涉及的教训有:

筹API的第一漫漫及亚修规则: “永远都自编写用例开始”

筹一个API时, 首先要举行的是站于调用者的立场, 想想调用者需要什么,
如何才能够太简易的达这需求.
编写一个略的用例代码永远是规划API中必须的一律步.
永不过多之错过思内部贯彻, 如果内部贯彻机制让API变得复杂,
应该想方去抽象它.

设想到未来底恢弘

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

明明接口的输入和出口

无须为节省代码去受一个接口接收或者返回多余的信息.
在ETW中许多接口都一头用了一个大构造体EVENT_TRACE_PROPERTIES,
调用者很麻烦施明白接口使用了建筑造体里面的焉值, 又影响了什么值.
设计API时应当明白接口的目的, 让接口接收及归必要且最好少的信息.

供整机的以身作则代码

针对调用者来说, 100推行之示范代码通常较1000执行之文档更有意义.
盖接口的设计者和调用者拥有的知识量通常不对等,
调用者在无观看实际的例证之前, 很可能无法掌握设计者编写的文档.

甭采取魔法数字

立刻是众多接口都会犯的荒唐, 例如ETW中决定事件附加的音时, 1代表时间戳,
2表示系统时, 3代表CPU周期计数.
苟您要传递具有某种意义的数字被接口,
请务必以SDK中呢该数字定义枚举类型.

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

写文档

99%底调用者没有看源代码的兴趣或能力,
不写文档没有丁见面懂得什么去调动用而的接口.
本有诸多自动生成文档的工具, 用这些家伙得以抽过多底工作量,
但是你照样当手动去编写一个入门的文档.

甭随意之失创造一个协商

开创一个新的商议表示要编制新的代码去分析其,
而且每个程序语言都要双重编辑一赖.
只有你生有生命力, 可以呢主流的程序语言都提供一个SDK, 否则未引进这样做.
众列都提供了REST API, 这是格外好之取向,
因为几乎每个语言都发出备的类库可以好地调用REST API.

严谨之去定义二进制协议

概念一个吓的二进制协议需大老的功夫, LTTng定义的合计明显考虑的卓绝少.
推荐的做法是强烈区分请求与对, 请求和对还应该来一个含有长度的条,
支持都双工通信.
使您想设计一个二进制协议,
强烈建议参考Cassandra数据库的商事文档,
这个协议无论是设计要文档都是头等的水平.
可是如果您从未针对性传输性能有非常苛刻的渴求, 建议用现成的合计加json或者xml.

无设失去创造一个DSL(Domain-specific language)

这里自己从没写轻易, 如果你出一个数据结构需要代表成文本,
请使用更通用的格式.
LTTng表示初数据经常使用了一个投机创造的DSL,
但里面的情节用json表示也非见面追加多少体积,
也就是说创造一个DSL没有其他好处.
浅析DSL需要团结编排词法分析器,
即使是经验老到的程序员编写一个啊得广大时光(包含单元测试更多),
如果使用json等通用格式那么编写解析的代码只待几分钟.

写于最后

虽然当时篇稿子将LTTng批评了同样海,
但这也许是眼下世界唯一一首涉嫌如何通过代码调用LTTng和收事件数量的文章.
希看罢及时篇稿子的计划API时大都也调用者着想,
你偷懒省下几乎分钟数会招别人浪费几天的时间.

相关文章

网站地图xml地图