日志透传工具-channel

在一些嵌入式系统中,如果需要采集长时间的全量日志或者系统性能数据,内存将成为一个瓶颈。是否可以将嵌入式系统中的数据透传到外部机器上,这样内存和性能都没有限制。通过此想法实现了channel这个工具,主要作用就是消息透传。

项目地址:https://github.com/caibingcheng/channel.git

使用

本地使用

最基本的使用方法,在服务端运行:

1
2
$ echo "Hello, World!" | channel -s
Hello, World!

那么同一ip下的客户端将接收到:

1
2
$ channel
Hello, World!

透传日志

如果是将嵌入式系统中的信息传递到本地主机,那么在嵌入式系统中:

1
2
3
4
5
6
7
8
$ echo "Hello, World!" | channel -s
Hello, World!

# or transmit top command output
$ top -n 1000 -d 1 -b | channel -s

# or transmit application output
$ <your app> 2>&1 | channel -s

本地主机可以得到:

1
2
3
4
5
# just receive and write to file, because the host disk space is sufficient
$ channel -i <embedded system ip> | tee log.txt

# or receive and process by another command, such as python script
$ channel -i <embedded system ip> | <another command>

分享实时日志

引申出的一个应用场景是可以与其他工程师共同分析日志,比如在你的机器上:

1
2
3
4
5
$ echo "Hello, World!" | channel -s
Hello, World!

# or transmit log from embedded system
$ channel -i <embedded system ip> | channel -s

在其他工程师机器上就可以实时获取你分享的内容:

1
2
$ channel -i <your machine ip>
Hello, World!

安装

该项目使用了一些C++17特性,因此需要至少支持C++17的GCC。

1
2
3
$ cd channel
$ make
$ sudo make install

或者交叉编译为arm版本:

1
2
3
$ cd channel
$ make CROSS_COMPILE=aarch64-linux-gnu-
$ sudo make install

原理

实现该工具的基本想法就是管道 + TCP

通过管道可以实现下面这样的用法,不会破坏原有程序运行的结构。

1
<your app> | channel -s

使用TCP则是考虑透传数据完整性是第一保证,其次是性能。

Linux中的管道可以将前一个输出变为下一个的输入。在channel中使用该特性的代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void send_message(bool is_drop) {
  std::string message = "";
  auto &bstream = std::cin;
  while (std::getline(bstream, message) && !stop_) {
    if (!message.empty()) {
      message += "\n";
      Log::debug("Message %lu Bytes", message.size());
      Log::raw("%s", message.c_str());
      Message msg;
      msg.body.length = message.size();
      msg.body.generate_timestamp = Message::timestamp_us();
      message = std::string(reinterpret_cast<char *>(&msg), sizeof(Message)) + message;
      send_message_(message, is_drop);
    }
  }

  Log::debug("Waiting for sender to stop");
  {
    std::unique_lock<std::mutex> lock(messages_mutex_);
    messages_condition_.wait(lock, [this] { return messages_.empty() || stop_; });
  }
  Log::debug("Sender stopped");
}

目前实现相当于直接从std::cin中读取内容,可能存在一些兼容性问题。不过实现channel是考虑的主要应用场景就是日志透传,基本都是ASCII字符,因此一般也不会出现兼容性问题。

小结

这个工具的基本想法是很朴素的。最开始的来源是,我做了一个性能统计和分析的工具,需要采集雷达系统上的top命令的输出,有些场景可能需要采集几十个小时,这时候数据放雷达中是不显示的,内存受限。因此可以通过sshpass + ssh工具的组合,在本地机器上采集。但是这样需要密码认证,自动化脚本时则需要明文密码,不太适合,因此才考虑了直接通过无授权的日志透传,来将雷达中的信息传递到本地电脑上。

开始实现的版本中,我考虑了“既然实现了字符的透传,不如实现一下文件透传”,而后还是删除了文件透传的功能,文件透传必要性不高,而且一般都是用scp、rsync工具,对其使用方法已经比较熟悉了。而字符透传的工具比较少,通过ssh协议似乎也可行,但我不太熟悉,且需要用户密码,不适合和其他同事的电脑分享。因此索性专一channel的功能,只做日志透传,不需要授权认证。