opensumi/core

ASoC 2022: Implement a Chrome DevTools for viewing OpenSumi communication messages

yantze opened this issue · 24 comments

Implement a Chrome DevTools for viewing OpenSumi communication messages

Background

This is an advance subject of ASoC 2022 and #1103 .

The current architecture of OpenSumi is a front-end and back-end separation, where the front-end of the desktop has to communicate with multiple processes, or the web-end has to communicate with the back-end services. The current communication methods are ipc, rpc, etc., but it is difficult to see or debug the communication problems visually.

Hope to provide a visual display of communication data Chrome plug-in, easy to see the communication traffic.

Target

  • Output: Chrome DevTools plugin
  • Communication list already with details of each message, filtering capabilities
  • Communication information visualization, support for current latency, traffic (xKB/s)
  • Advanced
    • Control communication speed
    • Provide some debugging aids on communication

Difficulty

Advance

Mentor

@yantze yutian.yz#alibaba-inc#com

Output Requirements

  • CDP
  • Chrome Extensions Devtools
  • Electron experience
  • OpenSumi module: ide-connection

实现一个用于查看 OpenSumi 通信消息的 Chrome 插件

背景

这是一个阿里巴巴编程之夏 2022 的基础课题 #1098 .

目前 OpenSumi 的架构是前后端分离的,桌面端前端要和有多个进程进行通信,或者 Web 端要和后端服务通信。目前通信的方式有 ipc、rpc 等,但通信的问题会比较难以直观的看到或者调试。

希望提供一个可视化的显示通信数据的 Chrome 插件,方便的查看通信流量。

目标

为了在 Web 和 桌面端都可以通用,期望实现的最终产物是一个 Chrome Extension,通信数据展示在 Devtools 上,具有可视化显示每一条通信数据。

  • 产出:Chrome DevTools 插件
  • 通信列表已经每条信息的详情,过滤能力
  • 通信信息可视化,支持当前延迟、流量(xKB/s)
  • 进阶
    • 控制通信速度
    • 提供一些通信上的调试辅助工具

难度

进阶

导师

@yantze yutian.yz#alibaba-inc#com

需要的技能

  • CDP
  • Chrome Extensions 插件开发
  • Electron 相关的能力
  • OpenSumi ide-connection 模块

老师们好~我有开发Chrome Extension的经验,目前运营着插件项目Hypercrx,该插件着重于ContentScripts和Options的开发。但我对Background(service worker)和Devtools页面的开发也非常感兴趣!

由于Google在逐步淘汰Manifest v2版本的插件,之前我对Hypercrx进行了升级,所以对Manifest v3有一定了解。也捣鼓出了支持ContentScripts“伪Hot-Reload”的插件模板

之前OSPP活动晚了一步,这次ASOC希望能参与进来,和大家一起学习一起have fun!

这个可能要过几天再确认是谁,因为现在不止一个同学申请这个题目。

评价标准其实是看之前有给 OpenSumi 贡献过一些代码或者有一些研发能力的的同学(因为这个项目可能涉及到的东西还是有一些的)。

没问题~

一个可能行得通的思路:
image

将“可能行得通”划掉是因为Electron端的事情没想象中简单。

【6.20小记】外挂式通信消息捕获是否可行?

Hi, OpenSumi community! 这两天我在探索学习中,主要是想确定捕获通信消息的可行方案。标题中所谓的“外挂式通信消息捕获”指的就是诸如:

  • 用DevTools的Network查看WebSocket消息
  • DevTron查看Electron的Main进程和renderer进程之间的IPC消息

因为宿主应用对它们是没有感知的,所以是“外挂式”的。就我目前的认知看来,这种比较理想的方式似乎不大可行

DevTron捕获IPC消息的原理

DevTron是 @yantze 老师推荐看看的,于是我首先学习了它的原理。与IPC消息捕获相关的代码在这儿,可以用移花接木或偷梁换柱形容。具体是怎么做的呢?Electron的Main进程和renderer进程之间以IPC的方式进行通信,具体到代码就是用ipcMain和ipcRenderer这两个API通信。DevTron对ipcRenderer做了手脚,在其中加入捕获通信消息的逻辑,使得宿主应用执行ipcRenderer的方法时实际上调用的是改造过后的ipcRenderer,于是就达到了“外挂式通信消息捕获”的目的。

为什么我觉得“外挂式”不大可行

我一度认为DevTron所采用的方法就是本课题的方案了,直到意识到此课题要求捕获的消息是前端进程和后端进程之间的消息,而不是Electron的Main进程和renderer进程之间的消息。前者在Electron端(isRemote为false时)是靠Nodejs的Socket来通信的,这意味着跟ipcRenderer没什么关系。这也解释了为什么当我在ide-electron中运行DevTron后,IPC消息列表几乎没什么消息——因为这个列表中的消息只包含ipcRenderer收发的消息。

我不想轻易放弃:既然Chrome能捕获websocket的消息,那有没有方法可以直接捕获socket消息呢?这样的话宿主应用照样是对此无感的。遗憾的是,目前我还没找到。

其他

以上只是我目前的认知,如果有错误的地方请 @yantze 和其他maintainers一定要纠正我。

通过修改 OpenSumi 的前端代码其实是很容易做到这件事的,Chrome 插件向 window 上注入一个 __OPENSUMI_DEVTOOL_EVENT_SOURCE_TOKEN__,然后在 core 的代码中检测这个变量是否存在,比如说:

const __OPENSUMI_DEVTOOL_EVENT_SOURCE_TOKEN__ = { traffic: { send(xxx){}, recieve(xxx){} } }

然后代码中这么用,最后在前端收发的过程中将自己发出去或者接收到的内容发给 Chrome 插件。

let trafficInspector;
if (typeof __OPENSUMI_DEVTOOL_EVENT_SOURCE_TOKEN__ !== "undefined") {
  trafficInspector = __OPENSUMI_DEVTOOL_EVENT_SOURCE_TOKEN__.traffic;
}

class WSChannel {
  sendMessage(xxx) {
    trafficInspector?.send(xxx)
    this.connection.send(xxxx)
  }
  onMessage(xxx) {
    trafficInspector?.recieve(xxx)
  }
}

以上都是随手写的代码,仅供参考。

谢谢 @bytemain ,你提供的snippet非常具有启发性!

之前和 @yantze 老师交流过,如果“外挂式”方案行不通的话,就改动connection模块的代码。WSChannel是针对websocket的,我刚刚发现RPCProtocol._protocol看起来是个更好的切入点:

private readonly _protocol: IMessagePassingProtocol;

因为this._protocol是一个connection,而connection是统一了websocekt和socket的,所以这个send和onMessage对websocket和socket的msg都适用。大家怎么看?


HELP WANTED:

以connection这个模块为例,我发现common/的一些模块只在web端用到;node/的一些模块是针对Electron的前端进程的,还有一些是针对OpenSumi的后端进程的。好吧,我也说不大清我的感受,也许我想问node/connect.ts为什么不放在common/里,因为我目前认为node/只放后端代码。

大家如果愿意分享一下“如何读懂connection”相关的经验的话,那就太好了!急需这方面的建议。

有关这几个为什么..只能说 connection 这部分写的太耦合,以至于后面想改已经改不动了。

有关 connection 的一些 pr:
改造开始的起点:

然后发现 electron 下有问题:

然后发现 web 端又有问题:

至此,connection 改造失败

之前的一些尝试:

  1. 之前尝试过直接读 sumi-ipc-main_threadyw-q6BbDmwr6WVDMb6Uh_.sock 文件,通过 unix socket 捕获到对应的信息,但感觉这条路太底层,有点吃力不讨好的感觉

另外:

  1. ide-connection 是用于Node.js 进程、插件进程和浏览器之间的通讯模块,使用 _protocol 是可以作为一种检阅 (inspect) 通讯的方式。
  2. 另外「外挂」其实也可以用,集成方(也就是使用 OpenSumi 集成为自己产品的开发者)是可能用到 ipc 通讯的。

这两种都可以作为插件查看通讯的方式。可以针对 ide-connection 的传送信息,优化显示,比如能通过 connection 模块,查看 请求的信息和返回的信息。而 ipc 的方式可以作为一个通用的面板展示即可,后期有必要再进行优化。

【6.28小记】修改ide-connection实现对MessageConnection消息的捕获

非常感谢 @bytemain@yantze 的回复!前几天仔细阅读了回复内容后,我明确了一下目标,并进行了实验。

明确目标

  • 优先实现“侵入式”捕获消息,即通过改造ide-connection捕获前端这测的收/发消息。
  • 其次(之后)再实现简单的“外挂式”捕获消息,对于浏览器可以基于CDP;对于Electron(非remote)可以直接基于socket?

(这里想确认一下:@yantze 老师说的ipc特指socket通信吗?)

实验:改造ide-connection

改造哪里?

因为this._protocol是一个connection,而connection是统一了websocekt和socket的,所以这个send和onMessage对websocket和socket的msg都适用。大家怎么看?

首先,我放弃了将_protocol作为消息拦截对象,因为在查看了class RPCProtocol的所有引用后,我发现只有extension模块使用了它。但前后端的通信不只发生在extension模块。

通过阅读源码、调试后,我发现MessageConnection才是合适的消息拦截对象。下图是我在寻找websocket和socket的统一层的时候画的“connection建立过程”流程图:

image

怎么改造?

目前的改动部分我已经提了一个PR到自己仓库里,大家可以直接前往看changes。代码改动的地方不多,一看就能明白(虽然我花了很久)。我在这里稍微描述一下我的改造旅程。

首先,我非常希望能改造得松耦合一点,尽量能不影响原来的代码逻辑,所以一开始我还是希望能像DevTron那样“偷梁换柱”,于是我计划用Proxy代理createMessageConnection()得到的messageConnection,并在handler中对get做手脚,使得messageConnection实例调用sendRequestsendNotificationonRequestonNotification这4个方法的时候会被拦截到,这样我就可以插入捕获消息的逻辑了。

这里有个细节,那就是createWebSocketConnectioncreateSocketConnection不仅在前端被调用,还会在后端进程启动的时候被调用,这是个公共的函数,所以要考虑window对象是否存在——使用window对象是因为我根据 @bytemain 提供的思路在“opensumi-devtools”插件(暂且这么称呼它)启动的时候利用chrome.devtools.inspectedWindow.eval(xxx)向前端的window对象插入了一个TOKEN。

上面4个被拦截的方法中,sendRequestsendNotification看上去成功了,但onRequestonNotification似乎没有被拦截到。后来我意识到,拦截代码得放到调用这两个方法时注册的handler里面去——onXXX只是一开始的时候被调用一次,不像sendXXX每次发送消息都会调用。

这么一来,理想中的松耦合改造落空了,我又去handler里加了捕获逻辑。此外,sendRequest是有返回值的,我还得在requestResult这个Promise的then中加捕获逻辑。于是,就有了目前PR里的内容。

改造效果

现在只做到了插件向window注入东西,还没做到前端发信息给插件,截图中console里的内容是利用window.eval打印的。

image

经过自己的测试和体验,像方向键移动、获取文件buffer、terminal“画面”的原始字符串、插件列表信息等都能在捕获的信息里看到。但是,onRequest的handler从来没有被触发到,即我试了很久也没有试出来后端向前端sendRequest的情况,这是正常的吗?

其他

码的字有点多,希望大家看看目前的改造思路可以吗?如果思路可以,有没有方法能更加的松耦合、优雅一点?

后端向前端发送消息的场景我知道的有一个:

把 "terminal.type"这个配置项 设置为一个随便的字符串,然后启动一下终端,因为这个终端是不存在的,后端就会通知前端这个 terminal 启动失败了。

我猜这个 onRequest 和 onNotification 的区别貌似是只有文字上的区别。它们的实现和底层的东西貌似是一样的

Hi, @bytemain, 请问“terminal.type”是去一个文件里设置吗?我在界面上设置只能选择,不能输入。

image

我猜这个 onRequest 和 onNotification 的区别貌似是只有文字上的区别。它们的实现和底层的东西貌似是一样的

我目前的理解是,对前端来说,onRequest是来响应后端的sendRequest,onNotification是接受后端的单向通知,后者在消息中很高频的,前者目前我还没遇到过。

「ipc特指socket通信」这里的 ipc 特指 ipcMain 和 ipcRenderer 之间的通信

CleanShot 2022-06-28 at 16 00 11@2x

点击这个图标可以编辑 json

触发到了!谢谢你!

image

Hi,大家好!

我已经准备好了一个仓库:https://github.com/tyn1998/opensumi-devtools

计划下一步是先把捕获到的消息传给devtools page,相关issue:tyn1998/opensumi-devtools#4

tyn1998/opensumi-devtools#4 中的方案对electron不适用,故放弃。不过,做都做了,我还是在web端跑通了,大家可以在 tyn1998/opensumi-devtools#6 看效果~

启用Plan B,即模仿DevTron的方式,请见:tyn1998/opensumi-devtools#7

敬请期待~

Hi,大家好!

已经有一个(非常朴素的)原型了:tyn1998/opensumi-devtools#10

image

一个升级版demo,支持:

  • 自动跟随
  • 组合过滤(例子中只有一列过滤)

2022-07-18 23 37 00

2022-07-18 23 27 23

相关PR:

消息呈现方式

大家好!之前已经确认用data grid的形式来呈现通信消息,但是具体的columns没有明确过。这次构想了一下需要的列:

  • id:data grid中的行号,如果清空消息,那么重新编号
  • time:消息捕获时的时间
  • type:↑ sendNotification、↑↓ sendRequest+requestResult、↓ onNotification/onRequest
  • service:消息所属的服务名称
  • method:消息所属的服务的方法名称(把service.method拆开来了,这样过滤的时候更方便)
  • send:如果send列有东西,那内容就是method的args
  • receive:可能是args、requestResult.data、requestResult.error、“The requested method is not registered”中的一种

(突然发现自己一直没考虑onRequest后再return给后端data的消息,如果考虑进来,那么可能会用↓↑表示onRequest+return data,于是上面的columns还要改进)

演示视频

有了最基本的功能,录了个视频大概演示了一下:

7-26-opensumi-devtools-demo.mov

相关PR:

下一步计划

  1. 把其他columns的filter header都补上,像service和method这种就应该用dropdown而不是input的形式
  2. JSON Parsed View,用于查看send和receive的具体消息

sendRequest+requestResult, onRequest+return 看起来是某个一侧的内容?只要加上是哪一侧发起的就好了吧。

【8.4】Changelogs

大家好,与上次比变化如下:

2022-08-04.17.17.00.mov

下一步计划:

  • 目前功能还行,质感不行,所以要统一样式,处理好一些细节
  • 流量统计方面的功能

【8.16】更有质感的UI & 流量统计功能 & 更好的响应式布局

视频演示:

2022-08-16.23.50.57.mov

相关PR:

下一步计划:

  • 延迟统计功能
  • 删去无用代码

【8.21】基本功能都完成 🎉

更新内容:

  • 新的网络延迟功能,这个功能需要opensumi/core的支持
  • 处理了在捕获状态时关闭devtools的情况
  • 支持了只有在当前页面检测到opensumi/core暴露的全局HOOK才会加载devtools

视频演示(网络延迟功能):

2022-08-18.19.13.15.mov

相关PR:

计划安排: