日前这几天在帮柠檬看她的APM系统要什么样收集.Net运行时的各个风波,
这一个事件包括线程起先, JIT执行, GC触发等等.
.Net在windows上(NetFramework, CoreCLR)通过ETW(伊芙(Eve)nt 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发送事件过来.
json,于是乎我写了这么的代码:

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的第一条和第二条规则: “永远都从编写用例初步”

计划一个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定义的合计分明考虑的太少.
推荐的做法是扎眼区分请求和应对, 请求和应对都应当有一个含有长度的头,
帮忙全双工通信.
只要您想设计一个二进制协议,
强烈指出参考卡桑德拉(Sandra)(Cassandra)数据库的共谋文档,
这几个协议无论是设计仍旧文档都是顶尖的水平.
只是一旦你从未对传输性能有很严格的渴求, 提议使用现成的商议加json或者xml.

不要去成立一个DSL(Domain-specific language)

此处自己没有写轻易, 假若你有一个数据结构需要代表成文本,
请使用更通用的格式.
LTTng表示元数据时采用了一个协调创设的DSL,
但里面的始末用json表示也不会追加多少体积,
也就是说创建一个DSL没有其余好处.
浅析DSL需要团结编写词法分析器,
即便是经验老到的程序员编写一个也急需多多时日(包含单元测试更多),
如果使用json等通用格式那么编写解析的代码只需要几分钟.

写在结尾

即使这篇作品把LTTng批评了一番,
但那可能是现阶段海内外唯一一篇涉嫌怎样通过代码调用LTTng和吸纳事件数量的著作.
可望看过这篇随笔的设计API时多为调用者着想,
你偷懒省下几分钟往往会促成旁人浪费几天的时间.

相关文章

网站地图xml地图