热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

observer_cli基础使用扩展指南

observer_cli基础使用扩展指南简介安装基础用法主界面介绍自带插件功能扩展自定义插件使用简介observer_cli是一个针对erlang虚拟机,基于rec

observer_cli 基础使用扩展指南

  • 简介
  • 安装
  • 基础用法
  • 主界面介绍
  • 自带插件功能扩展
  • 自定义插件使用


简介

observer_cli是一个针对erlang虚拟机,基于recon实现的模仿werl的observer功能的工具。因为生产环境一般都是linux系统,而observer是不支持linux系统,有了observer_cli就可以在linux环境下更直观的观察环境,及早发现问题。以下是作者自己介绍:

Visualize Erlang/Elixir Nodes On The Command Line base on recon.
Provide a high-performance tool usable both in development and production settings.
Focus on important and detailed information about real-time running system.
Keep minimal consumption.

本文只会简单的介绍基础用法,着重点在如果编写一个自己的扩展插件,安装和使用部分参看作者写的内容更详尽

Git库地址

安装

observer_cli支持rebar构建,如果你的项目支持rebar,只需要将observer加入到你的deps目录中

{deps, [{observer_cli, ".*",{git, https://github.com/zhongwencool/observer_cli.git, {branch, "master"}}}
]}.

作者用的rebar3,好像不需要填写项目地址这些?
如果用的rebar而不是rebar3,而且你的项目之前没有使用recon,那么还需要修改下项目本身的rebar.config

{deps, [{recon, ".*", {git, https://github.com/ferd/recon.git, {branch, "master"}}}
]}.

如果使用了recon那么只需要保证get-deps的时候recon在observer_cli check前get到即可

除了rebar构建,也支持erlang.mk的构建
另外Elixir也可以使用
这两种构建方式直接参照项目的ReadMe即可

基础用法

使用

observer_cli:start().

来启动本地的observer_cli程序,可以填入一个时间作为刷新间隔,单位为毫秒,默认为1000

observer_cli不仅可以查看本地节点,也可以查看远程节点

observer_cli:start('test@127.0.0.1')
observer_cli:start('test@127.0.0.1', COOKIE).
observer_cli:start('test@127.0.0.1', [{COOKIE,COOKIE},{interval, 1000}]).

以上三种方式都可以启动,如果没有指定COOKIE的时候就需要自己保证当前node和目标node的COOKIE一致,当然远程调用的时候还是可以指定刷新间隔

本地节点和远程节点都必须将observer_cli的库加进来,自定义插件(后面会介绍)部分的环境变量设置部分必须在查看的节点上确保,而查看的目标节点必须能确实能完成自定义插件配置的功能,目标节点自定义插件的环境变量的设置不是必须的

主界面介绍

主界面
observer_cli提供的功能和observer非常像,而且依靠recon还完成了更细致的内存分配数据,例如内存的利用率等
标题依次为

  • 首页:提供概览信息,内存分配,进程数量,虚拟机基础配置,cpu利用率,还有模仿etop的进程列表,可以使用关键字完成排序
  • 网络:提供当前虚拟机已经打开的端口综述,会列出当前已经打开的端口,同样可以使用关键字完成排序,这部分内容大多依赖recon完成
  • 系统:提供当前系统的概览,这里可以看到当前虚拟机内存分配的详细情况,R18(需要考证)以后的observer也支持了具体的内存分配查看
  • ETS:提供ETS表的查看,和observer功能类似,不过不能直接查看内容,也没必要。
  • Mnesia:Mnesia数据表的查看
  • App:查看当前虚拟机已经启动的application,不过不能像observer那种直接以结构树的形式展示,毕竟是命令行下,如果熟悉架构,这种平铺列表的内容已经可以满足解决问题的目的
  • Doc:帮助文档,提供基础命令的说明
  • Plugin:最新版本新增的支持自定义插件界面

关于内存和网络端口详细数据介绍可以查看recon,当然observer_cli也贴心的做了介绍observer_cli文档
最新版本的界面和上图有一丢丢的差距,最新的版本加入了自定义插件部分

主界面上展示的进程可以进入二级菜单针对单个进行进行详细的查看
在这里插入图片描述
在Home页面输入当前进程的编号(最左边的数字)即可,进程较多时可以使用pu/pd进程翻页,刷新较快时使用p暂停页面操作

自带插件功能扩展

这里以Process界面举例,加入一个dump当前字典内容的命令,并在界面打印出dump文件所在目录,通过这个扩展也可以理解observer_cli实现的一些基础逻辑

Process界面主要由observer_cli_process模块进行管理,先看一下observer_cli_process模块的内容

start(Pid, Opts) ->#view_opts{process = #process{interval = RefreshMs}} = Opts,RenderPid = spawn_link(fun() ->?output(?CLEAR),render_worker(info, RefreshMs, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE)end),manager(RenderPid, Opts).manager(RenderPid, #view_opts{process = ProcOpts} = Opts) ->case parse_cmd(Opts, RenderPid) ofquit ->erlang:send(RenderPid, quit);{new_interval, NewInterval} ->erlang:send(RenderPid, {new_interval, NewInterval}),manager(RenderPid,Opts#view_opts{process = ProcOpts#process{interval = NewInterval}});ViewAction ->erlang:send(RenderPid, ViewAction),manager(RenderPid, Opts)end.render_worker(state, Interval, Pid, TimeRef, RedQ, MemQ) ->case render_state(Pid, Interval) ofok ->next_draw_view(state, TimeRef, Interval, Pid, RedQ, MemQ);error ->next_draw_view_2(state, TimeRef, Interval, Pid, RedQ, MemQ)end.

observer_cli_process本身是我们在选中了查看某一进程详细信息的时候触发并开始走创建流程,除了自己本身外会额外spawn一个进程用来管理控制台的显示
进程自己本身负责从命令行中读取命令解读后抓发给显示进程,同是还维护定时刷新逻辑和退出逻辑
负责绘制的进程根据当前绘制的选中的状态,会get数据然后输入到界面并进入下次循环,逻辑并不复杂

next_draw_view(Status, TimeRef, Interval, Pid, NewRedQ, NewMemQ) ->NewTimeRef = observer_cli_lib:next_redraw(TimeRef, Interval),next_draw_view_2(Status, NewTimeRef, Interval, Pid, NewRedQ, NewMemQ).next_draw_view_2(Status, TimeRef, Interval, Pid, NewRedQ, NewMemQ) ->receivequit ->quit;{new_interval, NewInterval} ->?output(?CLEAR),render_worker(Status, NewInterval, Pid, TimeRef, NewRedQ, NewMemQ);info_view ->?output(?CLEAR),render_worker(info, Interval, Pid, TimeRef, NewRedQ, NewMemQ);message_view ->?output(?CLEAR),render_worker(message, Interval, Pid, TimeRef, NewRedQ, NewMemQ);dict_view ->?output(?CLEAR),render_worker(dict, Interval, Pid, TimeRef, NewRedQ, NewMemQ);stack_view ->?output(?CLEAR),render_worker(stack, Interval, Pid, TimeRef, NewRedQ, NewMemQ);state_view ->?output(?CLEAR),render_worker(state, Interval, Pid, TimeRef, NewRedQ, NewMemQ);_Msg ->render_worker(Status, Interval, Pid, TimeRef, NewRedQ, NewMemQ)end.

next_draw_view和next_draw_view2的区别只有next_draw_view会额外根据当前的界面刷新时间给自己发送一个定时刷新消息而已,所以state的处理中,如果get失败了就直接进入next_draw_view2没必要再次循环刷新了

observer_cli_lib:next_redraw生成的消息最后会有_Msg分支catch住根据当前状态,完成当前界面的刷新
new_interval则是给全局的设置刷新间隔的API的handle,每个模块都会带
其他分支则就是具体的功能模块

现在我们开始给Process模块加入dump命令

  1. 首先我们给尾行加入我们新加的命令描述

render_last_line() ->observer_cli_lib:render_last_line("q(quit) dump(dump dict into file dict.txt)").

  1. 修改parse_cmd命令加入我们新加的dump命令

parse_cmd(ViewOpts, Pid) ->case observer_cli_lib:to_list(io:get_line("")) of"q\n" ->quit;"Q\n" ->quit;"P\n" ->info_view;"M\n" ->message_view;"D\n" ->dict_view;"C\n" ->stack_view;"S\n" ->state_view;"H\n" ->erlang:exit(Pid, stop),observer_cli:start(ViewOpts);"dump\n" ->{dump_dict,false}; //新加部分Number ->observer_cli_lib:parse_integer(Number)end.

这里我们在进入刷新逻辑的时候额外加了一个bool变量,因为dump是一个一次性的命令,不需要刷新,但是我们又要不能破坏刷新逻辑的同时,确保dump只执行一次,且能保持显示dump文件所在的目录

  1. 增加dump命令的handle逻辑

next_draw_view_2(Status, TimeRef, Interval, Pid, NewRedQ, NewMemQ) ->receivequit ->quit;{new_interval, NewInterval} ->?output(?CLEAR),render_worker(Status, NewInterval, Pid, TimeRef, NewRedQ, NewMemQ);info_view ->?output(?CLEAR),render_worker(info, Interval, Pid, TimeRef, NewRedQ, NewMemQ);message_view ->?output(?CLEAR),render_worker(message, Interval, Pid, TimeRef, NewRedQ, NewMemQ);dict_view ->?output(?CLEAR),render_worker(dict, Interval, Pid, TimeRef, NewRedQ, NewMemQ);stack_view ->?output(?CLEAR),render_worker(stack, Interval, Pid, TimeRef, NewRedQ, NewMemQ);state_view ->?output(?CLEAR),render_worker(state, Interval, Pid, TimeRef, NewRedQ, NewMemQ);{dump_dict, Done} ->?output(?CLEAR), //新加部分render_worker({dump_dict, Done}, Interval, Pid, TimeRef, NewRedQ, NewMemQ);_Msg ->render_worker(Status, Interval, Pid, TimeRef, NewRedQ, NewMemQ)end.render_worker({dump_dict, Done}, Interval, Pid, TimeRef, RedQ, MemQ) ->Menu = render_menu(dict, Interval),Line1 = "file:write_file(FileName,io_lib:format(p,[List]),[write]), ~n",LastLine = render_last_line(),{Done2, Line2} = case Done oftrue ->{ok, Pwd} = file:get_cwd(),{Done, io_lib:format("Dict write in ~s/~s ~n", [Pwd, "dict.txt"])};_ ->case dump_dict(Pid) ofundefined ->{undefined, "dict empty! ~n"};FileName ->{ok, Pwd} = file:get_cwd(),{true, io_lib:format("Dict write in ~s/~s ~n", [Pwd, FileName])}endend,?output([?CURSOR_TOP, Menu, Line1, Line2, LastLine]),next_draw_view({dump_dict, Done2}, TimeRef, Interval, Pid, RedQ, MemQ);dump_dict(Pid) ->case erlang:process_info(Pid, dictionary) of{dictionary, List} ->FileName = "dict.txt",file:write_file(FileName, io_lib:format("~p", [List]), [write]),FileName;undefined ->undefinedend.

render_worker 的内容并不算复杂,根据dump_dict命令带入的参数决定是否进行dump操作,如果是由dump_dict命令来完成指定Pid的dump操作,如果dump成功就返回true状态,如果已经是true就保持原状态
dump_dict写的很粗糙,如果要进生产环境记得包装
render_worker的结束我们直接进入下次dump_dict的循环,确保界面上显示的dump文件所在目录不会被定时刷新逻辑刷新成其他
现在启动来看下命令执行结果
主界面
主界面

执行后
执行后

执行结果
执行结果

ok,看来成功了,至此我们就完成了对Process的一个简单的扩展
如果要做刷新逻辑那就更简单了只需要编写自己的render函数,确保每次刷新时get到最新的数据并返回即可

这些新的扩展功能必须要保证要查看的目标节点拥有这些逻辑,如果是用的远程节点查看,远程节点只需要有基础版本的observer_cli库即可

自定义插件使用

这里先摘抄一段作者的使用介绍,原文已经介绍的很详尽了

  1. Configure observer_cli,tell observer_cli how to find your plugin.
  2. Write observer_cli_plugin behaviour.

主要内容在需要完成的3个回调,回调格式如下:

-callback kv_label() -> [Rows] whenRows :: #{key => string(), key_width => pos_integer(),value => string()|integer()|{byte, pos_integer()}, value_width => pos_integer()}.

要注意的原文这里还是用的name,作者的文档还没更新,使用key即可
这里定义的是类似System界面的那种键值对结构,如果不想使用可以直接返回一个空字符串,一般返回一个界面的总览

-callback sheet_header() -> [SheetHeader] whenSheetHeader :: #{title => string(), width => pos_integer(), shortcut => string()}.

这部分返回表头其中Shortcut指的是如果表格需要排序,指定快捷键后就可以根据当前列排序,还需要注意的是,关键词也会加入到title显示中,所有width设置的时候也需要考虑进去,当然还要考虑下面的数据的具体长度

-callback sheet_body() -> [SheetBody] whenSheetBody :: list().

表内容,主要注意和表头一一对应即可,value支持绝大多数的erlang数据类型,
如果要显示内存大小之类的字节大小,可以使用observer_cli_lib:to_byte函数,不过用了后排序就不准了。。。
要修复也可以实现,新包装一个数据结构,然后在observer_cli_plugin:render_sheet_body函数中将

DataSet = lists:map(fun(I) ->{0, lists:nth(SortRow, I), I} end,Module:sheet_body()),

在这里对I进行检查和特殊处理,如果是需要转变的数据直接转变

is_memory_type({memory,_}) -> true;
is_memory_type(_) -> false.render_sheet_body(Module, CurPage, Rows, SortRow, Widths) ->DataSet &#61; lists:map(fun(I) ->Value &#61; lists:nth(SortRow, I),I1 &#61; lists:map(fun(V) ->case is_memory_type(V) oftrue ->observer_cli_lib:to_byte(element(2, V));_ ->Vendend, I),case is_memory_type(Value) oftrue ->{0, element(2, Value), I1};_ ->{0, Value, I1}endend,Module:sheet_body()),SortData &#61; observer_cli_lib:sublist(DataSet, Rows, CurPage),[beginList &#61; mix_value_width(Item, Widths, []),?render(List)end || {_, _, Item} <- SortData].

这里可以细致点&#xff0c;包装一个observer_cli_memory结构然后include进来&#xff0c;然后配合宏使用

也就是目标节点主要提供这三个回调函数&#xff0c;然后配置到observer_cli 的application env
下就可以在插件界面使用&#xff0c;环境变量配置格式如下

%% module - Specific module implements plugin behavior. It&#39;s mandatory.
%% title - Menu title. It&#39;s mandatory.
%% shortcut - Switch plugin by shortcut. It&#39;s mandatory.
%% interval - Refresh interval ms. It&#39;s optional. default is 1500ms.
%% sort_column - Sort the sheet by this index. It&#39;s optional default is 2.{plugins,[#{module &#61;> observer_cli_plug_behaviour1, title &#61;> "XPlug",interval &#61;> 1500, shortcut &#61;> "X", sort_column &#61;> 3},#{module &#61;> observer_cli_plug_behaviour2, title &#61;> "YPlug",interval &#61;> 1600, shortcut &#61;> "Y", sort_column &#61;> 3}]
}

一般来说我们不会直接修改deps目录的内容&#xff0c;所以可以在你的项目启动的时候&#xff0c;额外配置一个appenv文件&#xff0c;针对observer_cli的application增加plugins变量即可&#xff0c;最好不要直接修改deps下的app文件&#xff0c;需要注意的是这个设置部分是调用发起的节点必须有&#xff0c;也就是界面显示所在的节点&#xff0c;也就是说如果用的远程节点查看&#xff0c;则查看用的这个节点必须有这些环境变量才行&#xff0c;而布标节点必提供配置中模块指定的回调

下面我们加入一个查看当前服务器玩家进程的自定义插件为例介绍这个功能
在项目内建立observer_cli_role模块&#xff0c;名字可以随意指定

kv_label() ->[[#{key &#61;> "online_role", key_width &#61;> 20,value &#61;> user_default:n(), value_width &#61;> 10}]].sheet_header() ->[#{title &#61;> "RoleID", width &#61;> 15},#{title &#61;> "RoleName", width &#61;> 20},#{title &#61;> "level", width &#61;> 10, shortcut &#61;> "Lv"},#{title &#61;> "VipLv", width &#61;> 10, shortcut &#61;> "VLv"},#{title &#61;> "FID", width &#61;> 15},#{title &#61;> "FamilyName", width &#61;> 20},#{title &#61;> "Pid", width &#61;> 15},#{title &#61;> "Memory", width &#61;> 20, shortcut &#61;> "Me"},#{title &#61;> "Reductions", width &#61;> 24, shortcut &#61;> "Re"},#{title &#61;> "MsgQL", width &#61;> 10, shortcut &#61;> "Mq"}].
sheet_body() ->[beginPid &#61; user_default:rs_pid(RoleID),RolePublic &#61; user_default:get_role_public(RoleID),[RoleID,maps:get(rolename, RolePublic),maps:get(rolelv, RolePublic),maps:get(viplv, RolePublic),maps:get(familyid, RolePublic),maps:get(familyname, RolePublic),Pid,{memory,(element(2, erlang:process_info(Pid, memory)))},element(2, erlang:process_info(Pid, reductions)),element(2, erlang:process_info(Pid, message_queue_len))]end || RoleID <- user_default:onlines()].

这里面用了一些user_default模块假设存在的功能。这些功能不是非要在user_default模块提供&#xff0c;调用自己项目的内容即可&#xff0c;注意内存那格&#xff0c;使用了前面说的{memory, Memory}格式&#xff0c;如果没有修改过排序显示部分最好直接显示数字本身&#xff0c;否则会报错&#xff0c;启动后切换到自定义插件下&#xff0c;效果如图
在这里插入图片描述

设定了快捷键的列就可以使用快捷键进行排序&#xff0c;修改后&#xff0c;内存显示修改后的方式&#xff0c;排序也不会有问题。

ok&#xff0c;现在可以模仿写一个你自己的插件了&#xff0c;如果自写插件部分有错误&#xff0c;会在目标节点那边抛出异常&#xff0c;会由项目本身的日志逻辑catch住方便调试。


推荐阅读
  • Vagrant虚拟化工具的安装和使用教程
    本文介绍了Vagrant虚拟化工具的安装和使用教程。首先介绍了安装virtualBox和Vagrant的步骤。然后详细说明了Vagrant的安装和使用方法,包括如何检查安装是否成功。最后介绍了下载虚拟机镜像的步骤,以及Vagrant镜像网站的相关信息。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • FIN7后门工具伪装成白帽工具进行传播
    fin7,后门,工具,伪装,成,白, ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 解决github访问慢的问题的方法集锦
    本文总结了国内用户在访问github网站时可能遇到的加载慢的问题,并提供了解决方法,其中包括修改hosts文件来加速访问。 ... [详细]
  • svnWebUI:一款现代化的svn服务端管理软件
    svnWebUI是一款图形化管理服务端Subversion的配置工具,适用于非程序员使用。它解决了svn用户和权限配置繁琐且不便的问题,提供了现代化的web界面,让svn服务端管理变得轻松。演示地址:http://svn.nginxwebui.cn:6060。 ... [详细]
  • Carve库在Visual Studio2015中的编译方法及注意事项
    本文介绍了在Visual Studio2015中编译Carve库的方法及注意事项。首先下载Carve库,并使用Visual Studio2015打开,生成后在bin目录下会生成.lib文件。同时,本文还指出了之前在Visual Studio2017中编译的问题,并提醒需要根据对应的平台进行编译,否则会出现报错。详细的步骤和注意事项请参考原文链接。 ... [详细]
  • Introduction(简介)Forbeingapowerfulobject-orientedprogramminglanguage,Cisuseda ... [详细]
  • 完成字符串和时间对象的转化(DateFormat)、(以及日历Calendar用法)
    DateFormat 和SimpleDateFormat示例(时间格式的书写)packagecn.date;importjava.text.DateFormat;importjav ... [详细]
author-avatar
奋肀斗小族
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有