2022年8月

本教程介绍了如何实时监控 Linux 日志文件(桌面、服务器或应用)以进行诊断和故障排除。

当你在 Linux 桌面、服务器或任何应用中遇到问题时,你首先会查看单独的日志文件。日志文件通常是文本流和来自应用的带有时间戳的消息。它可以帮助你缩小特定问题的范围,并使你能够找到问题的原因。它还可以帮助从网络获得帮助。

一般来说,所有的日志文件都位于 /var/log。此目录包含特定应用和服务的扩展名为 .log 的日志文件,它还包含了其他含有日志的独立目录。

log files in var-log

所以,如果你想监控一堆日志文件或特定的一个,这里有一些方法可以做到。

Linux 实时监控日志文件

使用 tail 命令

tail 命令是实时跟踪日志文件的最基本方式。特别是如果你在只有终端而没有 GUI 的服务器中。这很有帮助。

基本语法如下:

tail /path/to/log/file

用法:

Monitoring multiple log files via tail

可以使用开关 -f 跟踪实时更新的日志文件。例如,如果要关注 syslog,可以使用以下命令。

tail -f /var/log/syslog

你可以使用单个命令监控多个日志文件:

tail -f /var/log/syslog /var/log/dmesg

如果要监视 HTTP 或 sftp 或任何服务器,可以在此命令中使用它们各自的日志文件。

请记住,上述命令需要管理员权限。

使用 lnav(日志文件浏览器)

lnav Running

lnav 是一个出色的程序,你可以用它来用彩色编码的信息以更有条理的方式监控日志文件。在 Linux 系统中,这个工具不是默认安装的。你可以用下面的命令来安装它:

Ubuntu:

sudo apt install lnav

Fedora:

sudo dnf install lnav

lnav 的好处在于,如果你不想安装它,你可以下载其预编译的可执行文件并在任何地方运行它,甚至可以从 U 盘上运行。无需设置,并加载了功能。使用 lnav,你可以通过 SQL 查询日志文件,以及其他很酷的功能,你可以在其官方网站上学习。

安装后,你可以在具有管理员权限的终端上运行 lnav,它会默认显示 /var/log 中的所有日志并开始实时监控。

关于 systemd 的 journalctl 的一个说明

当今所有现代 Linux 发行版都主要使用 systemd。 systemd 提供了运行 Linux 操作系统的基本框架和组件。 systemd 通过 journalctl 提供日志服务,这有助于管理来自所有 systemd 服务的日志。你还可以使用以下命令实时监控各个 systemd 服务和日志。

journalctl -f

以下是一些特定的 journalctl 命令,可用于多种情况。你可以将这些与上面的 -f 选项结合使用以开始实时监控。

对于紧急系统消息,请使用:

journalctl -p 0

显示带有解释的错误:

journalctl -xb -p 3

使用时间控制过滤:

journalctl --since "2022-12-04 06:00:00"
journalctl --since "2022-12-03" --until "2022-12-05 03:00:00"
journalctl --since yesterday
journalctl --since 09:00 --until "1 hour ago"

如果你想了解更多关于 journalctl 的详细信息,我已经在这写了份 指南

结束语

我希望这些命令和技巧可以帮助你找到桌面或服务器中问题/错误的根本原因。有关更多详细信息,你可以随时参考手册页并使用各种选项。如果你对本文有任何意见或想法,请使用下面的评论栏告诉我。

干杯。


via: https://www.debugpoint.com/monitor-log-files-real-time/

作者:Arindam 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

听别的电脑放歌会崩溃的笔记本电脑

微软的一位工程师分享了一个 Windows XP 时代的故事:某些型号的笔记本电脑在播放珍妮·杰克逊的上世纪八十年代的热门音乐视频《节奏国度》时会崩溃。更离奇的是相邻的一台未播放音乐的笔记本电脑也崩溃了。原因是《节奏国度》中的音频与某款笔记本硬盘发生共振,会干扰其运行。该 bug 被赋予了正式的 CVE 编号 CVE-2022-38392。还好这些电脑是 2005 年上市的,基本上已经淘汰。

消息来源:ARS Technica
老王点评:这音乐太劲爆了,笔记本电脑脆弱的小心脏受不了。

骗子利用带有微软徽标的 U 盘来诈骗

一位安全顾问称,接到了一个印刷有微软品牌的欺诈 U 盘。该 U 盘被包装在一个 Office 2021 专业版的纸盒内。当用户将该 U 盘插入他们的电脑之后,就会弹出一条假的警告信息,告知用户他们的系统中存在病毒,并提示受害者拨打技术支持电话,这显然是骗子使用的号码。然后,骗子要求受害者安装一个远程访问程序来接管系统。

消息来源:Sky News
老王点评:这诈骗真是用心了,还专门弄了包装盒和印刷了徽标的 U 盘。

DDoS 攻击新纪录:每秒请求超过 4600 万次

今年 6 月,Cloudflare 报告遭遇了有史以来最大规模的 DDoS 网络攻击,峰值每秒请求高达 2600 万次。这刷新了 2021 年 8 月创下的 1720 万次和 2022 年 4 月创下的 1530 万次。不过谷歌最近披露的一个数据创造了新纪录:峰值达到了每秒 4600 万次。攻击于 6 月 1 日凌晨开始,速度为每秒 1 万次请求,但在 8 分钟后升至每秒 10 万次请求,此时 Cloud Armor 自适应保护启动。两分钟后,每秒请求数增加到 4600 万。攻击在 69 分钟内消失了,可能是因为被 Cloud Armor 挫败而没有达到预期的效果。

消息来源:The Register
老王点评:如果没有云服务商的 DDoS 防护服务,任谁也扛不住这种强度的攻击啊。

1993 年,游戏开发公司 id Software 发行了一款第一人称射击游戏 《 毁灭战士 DOOM 》,游戏一经发行迅速爆火。在今天看来,《毁灭战士》可谓有史以来最具影响力的游戏之一。

《毁灭战士》发行之后的第十年(2003 年),记者 大卫·库什纳 David Kushner 出版了一本关于 id Software 的书,书名为《 Doom 启示录 Masters of Doom 》,后被奉为记录毁灭战士创作史的典范读物。几年前我曾读过这本书,如今内容已记得不太真切了,但是书中有一个关于 id Software 首席程序员 约翰·卡马克 John Carmack 的故事,我印象特别深刻。这里只对故事做粗略描述(具体情节请往下阅读)。实际上,早在《毁灭战士》开发前期,卡马克就发现自己为这款游戏编写的 3D 渲染器在渲染某些关卡时慢得像爬一样。对于《毁灭战士》这一对动感和速度有着相当高要求的射击游戏来说,这是一个非常严重的问题。意识到了这一问题的严重性,卡马克需要一个更加有效的渲染算法,于是他开始阅读相关论文。最后,他实现了一种叫做“ 二叉空间分割 binary space partitioning (BSP)”的技术,极大地提升了《毁灭战士》游戏引擎的运行速度,而这项技术此前从未用于电子游戏当中。

一直以来,我对这个故事的印象十分深刻。卡马克将学术前沿研究运用于电子游戏之中,我觉得这正是他之所以成为传奇人物的原因。无论从哪个角度来看,卡马克都应该是电子游戏行业中人尽皆知的典型的天才程序员,只不过上面这个故事是我最先能够想到的理由。

显而易见,“二叉空间分割”这个术语听起来就是难度相当高的课题,能够自行阅读论文并将其付诸实施实属不易,所以这个故事给我留下了深刻的印象。我一直认为卡马克的做法十分具有创见性,不过由于我既不懂二叉空间分割到底是怎样的一项技术,也不晓得这项技术在当时究竟有多么革新,所以我也并不确定自己的观点是否正确。如果按照从 霍默·辛普森 Homer Simpson (LCTT 译注:《辛普森一家人》中的那个老爹)到 阿尔伯特·爱因斯坦 Albert Einstein 的顺序为天才列出一套级别体系,那么卡马克将二叉空间分割技术运用于《毁灭战士》的做法究竟属于什么级别的天才之举呢?

同时,我也在想,二叉空间分割这个概念最初是从哪儿来的,又是怎样吸引到卡马克的?因此,本篇文章不仅仅会讲述约翰·卡马克和《毁灭战士》的故事,也会探讨二叉空间分割树(BSP 树)数据结构的发展历史。有意思的是,BSP 树和计算机科学领域其他许多技术一样,最初都起源于军事研究领域。

没错,《毁灭战士》的第一关卡 E1M1 就受到了美国空军的启发。

VSD 难题

BSP 树是计算机图形领域最具挑战性难题的解决方案之一。举个例子,为了渲染出三维场景,渲染器必须能够区分在一个特定角度下的可见物体和不可见物体。如果渲染时间比较充足,这一要求也算不上大问题;但是就理想情况来说,实时游戏引擎在 1 秒内至少需要完成 30 次区分任务。

这一问题有时被称为 可见面检测 visible surface determination (VSD)问题。后来与卡马克合作开发《 雷神之锤 Quake 》(id Software 继《毁灭战士》之后开发的游戏)的程序员 迈克尔·亚伯拉什 Michael Abrash ,在自己的著作《 图形程序开发人员指南 Graphics Programming Black Book 》 中写道:

我想探讨一下在我看来 3D 中最棘手的一个问题:可见面检测问题(在每个像素点上绘制合适的表面)以及与之密切相关的隐面消除问题(迅速去除不可见的多边形,用于加快可见表面检测速度)。简略起见,我将在下文采用缩写 VSD 来表示可见面检测和隐面消除。

为什么我会认为 VSD 是 3D 中最棘手的问题呢?尽管纹理映射等光栅化问题更让人感兴趣而且也更重要,但是相对而言,它们是范围相对有限的任务。随着 3D 加速器的出现,它们逐渐被转移到硬件中。同时,它们只随着屏幕分辨率的增加而增加,而分辨率的增加是相对温和的。

相反,VSD 却像是一个无底洞,目前应对方案也有很多。但实际上,在采用简单的方法处理 VSD 时,其性能会直接受到场景复杂程度的影响,而场景的复杂程度通常会以平方级或立方级的形式增大。所以在渲染过程中,VSD 很快就会成为制约因素。 [1]

亚伯拉什是在上个世纪九十年代末写的关于 VSD 问题的这些困难,这是在《毁灭战士》之后数年,这款游戏证明了普通人盼望着能用自家电脑玩很吃图形配置的游戏。九十年代早期,id Software 成立后发行了一些游戏。尽管当时的计算机还只是用来处理文字与表格或者执行其他任务,未尝想过要在上面运行游戏,id Software 必须对发行的游戏进行编程,使其能在计算机上流畅运行。为了实现这一飞跃,尤其是为了能让在《毁灭战士》之前 id Software 发行的少数 3D 游戏在电脑上运行,id Software 必须做出革新。在这些游戏中,所有的关卡在设计时都受到了一定限制,以便更容易解决 VSD 问题。

例如,在《毁灭战士》之前,id Software 发行了《 德军总部 3D 版 Wolfenstein 3D 版 》,该游戏的每一个关卡都是由与坐标轴平齐的墙壁组成。换言之,在《德军总部 3D 版》的游戏画面里,你看到的只有南北方向或者东西方向的墙壁。在游戏中,墙壁与墙壁之间有着固定的间隔,所有过道的宽度或是一个方格,或是两个方格等等,但绝不会出现 2.5 个方格。如此一来,尽管 id Software 团队只能设计出外观十分相似的关卡,但这也让卡马克为 《德军总部 3D 版》 编写渲染器的工作简单了不少。

通过将屏幕上的光线“齐射”入虚拟游戏世界,《德军总部 3D 版》的渲染器解决了 VSD 问题。通常来说,使用光线的渲染器叫做“光线投射”渲染器。这种渲染器的速度一般较慢,因为解决内部的 VSD 问题涉及到在光线和游戏中的物体之间找到第一个交点,这通常需要进行大量的计算。但在 《德军总部 3D 版》,由于所有的墙壁都与网格平齐,所以光线与墙壁相交的位置只能在网格线上。如此一来,渲染器只需逐个检查这些交点即可。如果渲染器先从离玩家视角最近的交点开始检查,接着检查下一个最近的交点,以此类推,最后遇到第一面墙壁时停止检查。这样,VSD 问题便轻而易举地得到了解决。光线从每一个像素点向前投射,与画面物体接触时停止,这一方法是可行的。因为从 CPU 资源来看,投射的成本很低。事实上,由于每面墙壁高度相同,因此针对同列的像素点,投射的光线只需一条。

尽管当时还没有专业的图形显卡,《德军总部 3D 版》凭借这一取巧之法得以在配置较低的个人电脑上正常运行起来。然而,这个办法并不适用于《毁灭战士》。因为 id Software 为这款新游戏增添了许多新元素 —— 倾斜的墙面、楼梯以及高低不一的天花板。光线投射的办法自然也就不好用了,于是卡马克编写出了一个新的渲染器。《德军总部 3D 版》的渲染器关注的是图像,将光线投射到屏幕像素表示的列上,而 《毁灭战士》 关注的则是物体。换句话说,《毁灭战士》 的渲染器会记录游戏场景中的所有物体,继而将其投射到屏幕当中;而非记录屏幕上的像素点,判断每个像素点的颜色。

对于强调物体的渲染器来说,可以使用 Z 缓冲区来解决 VSD 问题,比较简单。每次将物体投射到屏幕上时,需要对每个用于绘制的像素点进行检查。如果你想绘制出的物体的部分和已经绘制在目标像素点上的物体相比更加靠近玩家,可以将其覆盖。否则,必须保持像素不变。尽管办法很简单,但是 Z 缓冲区对内存的要求较高,而且渲染器可能仍然要花费大量的 CPU 资源来投射玩家永远不会看到的水平几何体。

在 20 世纪 90 年代,使用 Z 缓冲区的方法还存在着其他缺陷:IBM 兼容机(PC)搭载的是一种叫 VGA 的显示适配器系统,在这类电脑上,将图像写入帧缓冲区的成本非常之高。因此,在只会以后被覆盖的像素上绘制花费的时间拖慢了渲染器的性能。

考虑到将图像写入帧缓冲区的成本非常之高,理想的渲染器需要首先绘制离玩家最近的物体,接着是比较近的物体,以此类推,直到屏幕上每个像素点都写入了信息。这时,渲染器会停止运行,大幅缩短远处不可见物体的渲染时间。这种由近及远对物体进行排序的方法也可以解决 VSD 问题。那么问题又来了:什么才是玩家可以看到的?

最初,卡马克打算依靠《毁灭战士》的关卡布局来解决 VSD 问题。首先用渲染器绘制出玩家目前所在房间的墙壁,之后玩家冲进隔壁房间,再绘制出隔壁房间的墙壁。由于每个房间互不遮挡,这一办法也能解决 VSD 问题。而互相遮挡的房间可以分割成若干互不遮挡的“区域”。在 YouTube 上的一个 视频 中,Bisqwit 展示了自己制作出来的使用了相同算法的渲染器。可以看到,如果以超慢的速度运行,便能一睹渲染的具体过程。这一算法同样运用到了《毁灭公爵 3D 版》当中,这款游戏在 《毁灭战士》 推出三年之后发行,当时 CPU 的性能也更加强大了。1993 年,尽管在硬件上已经可以运行游戏了,但是使用这一算法的《毁灭战士》渲染器在复杂的层级结构上依旧表现吃力,尤其是在房间分割出来的各部分相互嵌套的情况下。不巧的是,这类层级结构正是构造环形楼梯等物体的唯一办法。沿着环形楼梯走下去,直到走入已经绘制好的区域,由于这其中涉及多次循环下降运动,导致游戏引擎的运行速度大幅降低。

在 id Software 团队意识到《毁灭战士》游戏引擎的速度可能过慢时,公司还面临着其他任务:将《德军总部 3D 版》移植到超级任天堂游戏机(简称“超任”)上。那时,超任的性能比 IBM 兼容机还要差。结果表明,尽管光线投射渲染器非常简单,但是想要在超任上快速运行是不可能的。于是,卡马克着手研究更为高效的算法。事实上,也正是为了顺利将《德军总部》移植到超任,卡马克首次研究了二叉空间分割技术,并将其付诸应用。由于《德军总部 3D 版》的墙壁与坐标轴平齐,所以二叉空间分割技术应用起来也比较简单直接;但是《毁灭战士》的情况则比较复杂。不过,卡马克发现,二叉空间分割树同样可以用来解决《毁灭战士》速度过慢的问题。

二叉空间分割

二叉空间分割 binary space partitioning (BSP)会提前将 3D 场景分割为若干部分,使 VSD 问题易于解决。讲到这里,你需要先了解一下为什么分割场景可以奏效:如果你在场景上画条线(对应三维空间里的一个平面),你就可以指出玩家或者摄像机视角在这条线的哪一侧,在这条线另一侧的物体无法遮挡玩家所在一侧的物体。如果多次重复这一操作,该三维场景最终会被分割为多个区域,这并不是对原始场景的改进,只是现在你知道了更多关于场景的不同部分会如何相互阻挡。

首次阐述上述三维场景分割的是美国空军的研究员,他们曾尝试向美国空军证明计算机图形已经非常先进,可以应用到飞行模拟器领域。1969 年,他们将研究发现发表在一份题为《计算机生成图像在图形仿真中的应用研究》的报告中。该报告的总结部分指出,计算机图形可用于训练飞行员,但也警告说,其实际应用可能会受制于 VSD 问题:

实时图像处理需要解决的一个关键问题就是优先级问题,或称隐藏线问题。在我们平时用眼睛观察外界时,大自然替我们轻易地解决了这一问题:不透明物体上的一个点,掩盖了同一视觉方向上、且距离较远的所有其它物体。但在计算机中,这项任务却非常困难。图像处理需要解决的优先级问题,随着环境复杂程度的增加,计算量会呈指数级增长,随即就会超过绘制物体透视图所需的计算负载。 [2]

他们在报告中提出了一项基于构造“遮挡矩阵”的方案,这一方案据说早些时候曾被应用于 NASA 的项目当中。研究员指出,平面将场景一分为二,可用来解决平面两侧物体之间存在的“任何优先级问题”。通常情况下,可能需要明确将这些平面添加到场景中,但对某些几何体,只需借助你已经拥有的几何体的表面即可。他们举了一个例子,如下图:p 1、p 2 以及 p 3 是三个不同的平面,如果摄像机视角位于其中一个平面的前方,即“正”面,p i 的值就等于 1。这种矩阵展示出基于三个不同平面和摄像机视角位置的三个物体之间的关系 —— 如果物体 a i 遮挡了物体 a j,那么 a ij 在此矩阵中的数值等于 1。

研究人员指出,这种矩阵可以应用到硬件中,对每一帧进行重新评估。该矩阵基本上可以用作大型的开关,或者一种预置的 Z 缓冲区。在绘制给定的物体时,如果在物体所在列上得出数值 1,并且所在行已经在绘制中,那么物体被遮挡的部分就不会绘制出来。

不过,该矩阵方法的主要缺点在于,为了在场景中表示出 n 个物体,你需要一个尺寸为 n 2 的矩阵。于是,研究人员们继续深入,探究将遮挡矩阵表示为“优先级列表”的可行性,该列表的尺寸是 n,可确定物体绘制的顺序。他们随即发现,诸如上图此类场景根本无法确定顺序(因为它存在循环阻塞的现象)。因此,他们花了很多时间来阐明“合适”与“不合适”场景之间的数学区别。最后,他们得出了一个结论:至少对于“合适的”场景下,优先级列表是可以制作出来的;而对场景设计师来说,避免设计出“不合适”的场景也不是一件难事。但是,他们并没有说明如何生成该列表。可以说,这份 1969 年的研究的首要贡献在于提出了:至少,在 理论上,可以采用平面分割的方法,对场景中的物体进行渲染排序。

直到 1980 年,一份题为《基于优先级树结构的可见表面生成》的论文提出了解决该问题的具体算法。在这份论文中,作者 亨利·福克斯 Henry Fuchs 泽维·凯德姆 Zvi Kedem 以及 布鲁斯·内勒 Bruce Naylor 介绍了 BSP 树。他们指出,这种新的数据结构“可以替代十年前首次使用,但由于一些问题未得到广泛发展的方案”(即前文 1969 年美国空军相关研究中的方案)。 [3] BSP 树一经生成,即可用于确定场景中物体的优先级顺序。

三人在论文中对 BSP 树的工作原理给出了相当可读的解释。但在本文,我将尝试使用更加通俗的语言,介绍给大家。

首先,在场景中选定一个多边形,将该多边形所在的平面作为分割平面。同时,该多边形充当树的根节点。场景中剩下的多边形会分散在分割平面的两侧。位于分割表面“前方”或者与分割平面相交后位于“前”半部分的多边形落在了根节点左侧的左子树上;位于分割表面“后方”或者与分割平面相交后位于“后”半部分的多边形落在了右子树上。接着,递归重复这一过程:在左子树和右子树上各选定一个多边形,作为各自空间新的分割平面,继而二分出来更多的子空间和子树。等到全部的多边形均选定之后,二叉空间分割也就结束了。

假设你想由后向前将场景中的几何图形进行渲染。(这就是所谓的“ 画家算法 painter's algorithm ”。因为在绘制时,距离摄像机较远的多边形会被距离摄像机较近的多边形所覆盖,借此正确进行渲染任务。)如果想要实现这一算法,必须按顺序遍历 BSP 树,左右子树的渲染顺序由摄像机视角与节点所在分割平面的位置关系决定的。因此,针对树上的每个节点,首先渲染距离分割平面较“远”一侧的所有多边形,接着是位于平面上的多边形,最后是距离平面较“近”一侧的所有多边形 —— “远”与“近”相对于摄像机视角而言。根据前文,距离分割平面较远一侧的多边形无法遮挡近侧的物体,所以这种方法可以解决 VSD 问题。

下图表示一个简单的二维场景的 BSP 树的构造与遍历过程。在二维中,分割平面变成了分割线,但就基本原理而言,与复杂的三维场景并无二致。

第一步:根分割线落在 D 墙上,将剩下的几何图形分为两组。

第二步:继续分割位于 D 墙两侧的空间。C 墙是其中一侧的唯一一堵墙壁,因此无需再分。另一侧,B 墙形成新的分割平面。因为 A 墙与新的分割平面相交,所以必须将其分割为两堵墙。

第三步:参照右上方视角,由后向前对墙壁进行排序,对执行画家算法很有帮助。这就是树的顺序遍历过程。

福克斯、凯德姆以及内勒多次强调了 BSP 树的优势:它只需构建一次。可能有些难以置信,但实际上无论摄像机视角位于何处,同一棵 BSP 树都可以用来渲染一个场景。只要场景中的多边形没有移动,BSP 树就不会失效。因此,BSP 树在实时渲染任务中非常实用 —— 构建树时的所有艰巨任务都可以在渲染工作开展之前完成。

同时,三人也提到了一项需要进一步深入研究的问题:究竟怎样才能构建出一棵 “高质量的” BSP 树?BSP 树的质量取决于用作分割平面的多边形的选择。我在前文跳过了这一问题,不过如果用作分割平面的多边形与其他多边形相交,那么为了让 BSP 算法发挥作用,必须将相交的多边形一分为二,这样两部分就可以分在不同的空间。但是如果这种现象反复出现,BSP 树的构建势必会大幅增加场景中多边形的数量。

内勒后来在其 1993 年的论文《构建高质量的分割树》中提及这一问题。卡马克的同事,id Software 的共同创始人 约翰·罗梅洛 John Romero 指出,这篇论文是卡马克在《毁灭战士》中引入 BSP 树时读到的论文之一。 [4]

《毁灭战士》中的 BSP 树

别忘了,卡马克首次为《毁灭战士》设计渲染器时,通过让渲染器渲染玩家所在房间之外的临近房间,试图为关卡几何图形建立一套渲染顺序。对此,BSP 树是个不错的选择,因为在玩家进入之前的房间(区域)时,BSP 树能够避免让渲染器重复劳动,从而节省 CPU 资源。

实际上,“将 BSP 树引入《毁灭战士》”意味着将 BSP 树生成器引入《毁灭战士》的关卡编辑器中。当完成一个《毁灭战士》的关卡的制作时,BSP 树就会在关卡几何图形的基础上生成。根据程序员 法比安·桑格勒德 Fabien Sanglard 的说法,在原版《毁灭战士》中,一个关卡的 BSP 树生成时间需要 8 秒,全部关卡合计共需 11 分钟 [5] 。之所以生成时间较长,部分原因在于卡马克所用的 BSP 生成算法,该算法尝试使用各种启发式方法找出 “高质量” BSP 树。在运行时,8 秒的延时可能让人无法接受;但是离线等 8 秒,时间并不算长,尤其是考虑到 BSP 树提升了渲染器的性能。为每个关卡生成的 BSP 树将在游戏启动时作为关卡数据载入。

卡马克对 1980 年论文中提出的 BSP 树算法进行了改造,因为在《毁灭战士》开始运行时,当前关卡的 BSP 树就会读取到内存中,渲染器通过 BSP 树由前向后绘制物体,而非由后向前进行绘制。福克斯三人在那篇论文中演示了 BSP 树可用于执行由后向前的画家算法,但是画家算法会造成许多重复的绘制任务,对于 IBM 兼容机来说负担较大。因此,《毁灭战士》的渲染器换了个方向,首先绘制距离玩家较近的图形,之后再绘制离玩家较远的。这种反向排序很容易通过 BSP 树来实现,因为你可以在树的每个节点都进行反向遍历。为了避免绘制出来的远处图形遮挡到近处的图形,《毁灭战士》的渲染器使用了一种隐式 Z 缓冲区,这种缓冲区不仅具备普通 Z 缓冲区的优势,而且对内存的要求也较低。这种 Z 缓冲区有两组数组,一组记录水平方向的遮挡关系,另两组记录自屏幕顶部和底部的垂直方向的遮挡关系。《毁灭战士》的渲染器就算不使用实际的 Z 缓冲区也无伤大雅,因为从技术上来看它并不是真正的 3D 游戏。BSP 树数据结构的成本虽然不高,但却能够起作用,其原因在于《毁灭战士》不会发生以下问题:水平方向的遮挡数组能够发挥作用,是因为该游戏中没有倾斜的墙体;垂直方向的遮挡数组能够发挥作用,是因为该游戏不存在有着一上一下两扇窗户的墙体。

剩下比较棘手的问题是如何将《毁灭战士》中处于运动中的角色融入到借助 BSP 树绘制的静止的关卡几何图形中。该游戏中的敌人不可能纳入 BSP 树之中,因为他们会移动,而 BSP 树只对静止的几何形状起作用。所以渲染器首先绘制静止的关卡几何图形,同时与另一个内存使用效率较高的数据结构协作,记录屏幕上分割出来用于绘制的区域。之后,渲染器按照由后往前的顺序绘制敌人,并消除被屏幕上的区域遮挡住的敌人。这一过程与使用 BSP 树进行渲染相比,效果稍差一些。但是由于关卡中能看到的敌人的数量少于几何图形的数量,所以速度并不是一个严重的问题。

将 BSP 树应用到《毁灭战士》中可谓一大成功。卡马克能够想到 BSP 树是解决 VSD 问题的最佳方案,无疑非常高明。但是这可以称得上是天才之举吗?

桑格勒德在其关于《毁灭战士》游戏引擎的书中引用了罗梅洛的话:内勒的论文《构建高质量的分割树》主要讲述使用 BSP 树消除 3D 模型的背面。 [6] 根据罗梅洛所言,卡马克认为这种算法对《毁灭战士》依然有效,所以他放手一试,将 BSP 技术应用到了该游戏中。不过这话说得有些奉承的意味 —— 意在暗示卡马克在别人仍然使用 BSP 树渲染静止的场景时,发现该技术可以用于实时游戏领域。在《Doom 启示录》中也有给卡马克戴高帽的故事。该书作者库什纳认为,卡马克在阅读内勒的论文之后,问了自己,“如果使用 BSP 技术创造一整个虚拟世界,而不仅仅是一张 3D 图像,会怎么样呢?” [7]

这些“片面之词”忽视了 BSP 树的发展历史。当美国空军研究人员开始意识到场景分割可能会加快渲染任务的时候,他们就对提升 实时 渲染的速度产生了兴趣,毕竟他们当时试图创建一个飞行模拟器。1980 年,同样的案例再次出现在了福克斯等人的论文中,他们探讨了 BSP 树如何应用于飞行模拟器中,帮助飞行员进行训练:飞行员用它来反复练习将飞机降至同一空港。由于空港的地形不会发生改变,BSP 树只需生成一次,即可一劳永逸。很明显,他们考虑的是实时模拟。在论文的引言部分,福克斯等人还谈到实时图形系统必须在至少 1/30 秒内生成一张图像,由此激励了他们的研究。

因此,卡马克不是第一个想到在实时图形模拟中应用 BSP 树的人。诚然,设想与付诸实践是两码事。但是即使在实施的过程中,卡马克受到的帮助与指导可比人们想象的要多得多。至少是到这篇文章写成之时,BSP 树的 维基百科词条 页面显示,卡马克参考了 1991 年 Chen 戈登 Gordon 的一篇论文,以及 1990 年的一本教材《计算机图形学:原理及实践》。尽管该页面并未提供引用信息,但可信度没什么问题。陈和戈登的论文介绍了运用 BSP 树由前向后的渲染方法,这种方法与《毁灭战士》中用到的方法基本一致,还包括了我称之为“隐式 Z 缓冲区”的数据结构,可用于防止远处的图形在绘制时遮挡近处的图形。《计算机图形学:原理及实践》详细介绍了 BSP 树,以及一些构建并展示 BSP 树的伪代码(非常感谢我大学的图书馆,让我能够一睹这本教材 1990 年的版本)。因为这本书是计算机图形学的经典之作,所以卡马克很可能也有一本。

然而,卡马克发现自己遇到一个新问题:如何让第一人称射击游戏在一台 CPU 甚至都无法进行浮点操作的电脑上运行呢?通过调查研究,他证明了 BSP 树的数据结构非常适用于实时电子游戏渲染。尽管 BSP 树早已提出,而且到了卡马克的时代,相关理论已经非常成熟了,但我始终认为,卡马克的做法可谓惊人之壮举。也许,得到人们称誉的应该是整个《毁灭战士》的游戏引擎,它的确非常精致。我在前文也提及过,但是桑格勒德的《 游戏引擎黑皮书:毁灭战士 Game Engine Black Book: DOOM 》 很好地讲解了这款游戏引擎的非凡之处,以及这些优势相互契合之法。要明白,VSD 问题只是卡马克在编写《毁灭战士》游戏引擎时需要解决的诸多问题之一。不得不说,面对不为大多数程序员所知的复杂的数据结构,卡马克能够查阅相关文献,将其付诸实践,仅此一点就足以说明其技术之精湛、匠心之独到。

如果你喜欢这篇文章,欢迎关注推特 @TwoBitHistory,也可通过 RSS feed 订阅,获取最新文章(每四周更新一篇)。


  1. Michael Abrash, “Michael Abrash’s Graphics Programming Black Book,” James Gregory, accessed November 6, 2019, http://www.jagregory.com/abrash-black-book/#chapter-64-quakes-visible-surface-determination. ↩︎
  2. R. Schumacher, B. Brand, M. Gilliland, W. Sharp, “Study for Applying Computer-Generated Images to Visual Simulation,” Air Force Human Resources Laboratory, December 1969, accessed on November 6, 2019, https://apps.dtic.mil/dtic/tr/fulltext/u2/700375.pdf. ↩︎
  3. Henry Fuchs, Zvi Kedem, Bruce Naylor, “On Visible Surface Generation By A Priori Tree Structures,” ACM SIGGRAPH Computer Graphics, July 1980. ↩︎
  4. Fabien Sanglard, Game Engine Black Book: DOOM (CreateSpace Independent Publishing Platform, 2018), 200. ↩︎
  5. Sanglard, 206. ↩︎
  6. Sanglard, 200. ↩︎
  7. David Kushner, Masters of Doom (Random House Trade Paperbacks, 2004), 142. ↩︎

via: https://twobithistory.org/2019/11/06/doom-bsp.html

作者:Two-Bit History 选题:lujun9972 译者:aREversez 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

单元测试可能令人生畏,但是这些 Python 模块会使你的生活变得更容易。

在这个教程中,你将学到如何对执行 HTTP 请求代码的进行单元测试。也就是说,你将看到用 Python 对 API 进行单元测试的艺术。

单元测试是指对单个行为的测试。在测试中,一个众所周知的经验法则就是隔离那些需要外部依赖的代码。

比如,当测试一段执行 HTTP 请求的代码时,建议在测试过程中,把真正的调用替换成一个假的的调用。这种情况下,每次运行测试的时候,就可以对它进行单元测试,而不需要执行一个真正的 HTTP 请求。

问题就是,怎样才能隔离这些代码?

这就是我希望在这篇博文中回答的问题!我不仅会向你展示如果去做,而且也会权衡不同方法之间的优点和缺点。

要求:

使用一个天气状况 REST API 的演示程序

为了更好的解决这个问题,假设你正在创建一个天气状况的应用。这个应用使用第三方天气状况 REST API 来检索一个城市的天气信息。其中一个需求是生成一个简单的 HTML 页面,像下面这个图片:

web page displaying London weather

伦敦的天气,OpenWeatherMap。图片是作者自己制作的。

为了获得天气的信息,必须得去某个地方找。幸运的是,通过 OpenWeatherMap 的 REST API 服务,可以获得一切需要的信息。

好的,很棒,但是我该怎么用呢?

通过发送一个 GET 请求到:https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={api_key}&units=metric,就可以获得你所需要的所有东西。在这个教程中,我会把城市名字设置成一个参数,并确定使用公制单位。

检索数据

使用 requests 模块来检索天气数据。你可以创建一个接收城市名字作为参数的函数,然后返回一个 JSON。JSON 包含温度、天气状况的描述、日出和日落时间等数据。

下面的例子演示了这样一个函数:

def find_weather_for(city: str) -> dict:
    """Queries the weather API and returns the weather data for a particular city."""
    url = API.format(city_name=city, api_key=API_KEY)
    resp = requests.get(url)
    return resp.json()

这个 URL 是由两个全局变量构成:

BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
API = BASE_URL + "?q={city_name}&appid={api_key}&units=metric"

API 以这个格式返回了一个 JSON:

{
  "coord": {
    "lon": -0.13,
    "lat": 51.51
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "clear sky",
      "icon": "01d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 16.53,
    "feels_like": 15.52,
    "temp_min": 15,
    "temp_max": 17.78,
    "pressure": 1023,
    "humidity": 72
  },
  "visibility": 10000,
  "wind": {
    "speed": 2.1,
    "deg": 40
  },
  "clouds": {
    "all": 0
  },
  "dt": 1600420164,
  "sys": {
    "type": 1,
    "id": 1414,
    "country": "GB",
    "sunrise": 1600407646,
    "sunset": 1600452509
  },
  "timezone": 3600,
  "id": 2643743,
  "name": "London",
  "cod": 200

当调用 resp.json() 的时候,数据是以 Python 字典的形式返回的。为了封装所有细节,可以用 dataclass 来表示它们。这个类有一个工厂方法,可以获得这个字典并且返回一个 WeatherInfo 实例。

这种办法很好,因为可以保持这种表示方法的稳定。比如,如果 API 改变了 JSON 的结构,就可以在同一个地方(from_dict 方法中)修改逻辑。其他代码不会受影响。你也可以从不同的源获得信息,然后把它们都整合到 from_dict 方法中。

@dataclass
class WeatherInfo:
    temp: float
    sunset: str
    sunrise: str
    temp_min: float
    temp_max: float
    desc: str

    @classmethod
    def from_dict(cls, data: dict) -> "WeatherInfo":
        return cls(
            temp=data["main"]["temp"],
            temp_min=data["main"]["temp_min"],
            temp_max=data["main"]["temp_max"],
            desc=data["weather"][0]["main"],
            sunset=format_date(data["sys"]["sunset"]),
            sunrise=format_date(data["sys"]["sunrise"]),
        )

现在来创建一个叫做 retrieve_weather 的函数。使用这个函数调用 API,然后返回一个 WeatherInfo,这样就可创建你自己的 HTML 页面。

def retrieve_weather(city: str) -> WeatherInfo:
    """Finds the weather for a city and returns a WeatherInfo instance."""
    data = find_weather_for(city)
    return WeatherInfo.from_dict(data)

很好,我们的 app 现在有一些基础了。在继续之前,对这些函数进行单元测试。

1、使用 mock 测试 API

根据维基百科 模拟对象 mock object 是通过模仿真实对象来模拟它行为的一个对象。在 Python 中,你可以使用 unittest.mock 库来 模拟 mock 任何对象,这个库是标准库中的一部分。为了测试 retrieve_weather 函数,可以模拟 requests.get,然后返回静态数据。

pytest-mock

在这个教程中,会使用 pytest 作为测试框架。通过插件,pytest 库是非常具有扩展性的。为了完成我们的模拟目标,要用 pytest-mock。这个插件抽象化了大量 unittest.mock 中的设置,也会让你的代码更简洁。如果你感兴趣的话,我在 另一篇博文中 会有更多的讨论。

好的,言归正传,现在看代码。

下面是一个 retrieve_weather 函数的完整测试用例。这个测试使用了两个 fixture:一个是由 pytest-mock 插件提供的 mocker fixture, 还有一个是我们自己的。就是从之前请求中保存的静态数据。

@pytest.fixture()
def fake_weather_info():
    """Fixture that returns a static weather data."""
    with open("tests/resources/weather.json") as f:
        return json.load(f)
def test_retrieve_weather_using_mocks(mocker, fake_weather_info):
    """Given a city name, test that a HTML report about the weather is generated
    correctly."""
    # Creates a fake requests response object
    fake_resp = mocker.Mock()
    # Mock the json method to return the static weather data
    fake_resp.json = mocker.Mock(return_value=fake_weather_info)
    # Mock the status code
    fake_resp.status_code = HTTPStatus.OK

    mocker.patch("weather_app.requests.get", return_value=fake_resp)

    weather_info = retrieve_weather(city="London")
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

如果运行这个测试,会获得下面的输出:

============================= test session starts ==============================
...[omitted]...
tests/test_weather_app.py::test_retrieve_weather_using_mocks PASSED      [100%]
============================== 1 passed in 0.20s ===============================
Process finished with exit code 0

很好,测试通过了!但是...生活并非一帆风顺。这个测试有优点,也有缺点。现在来看一下。

优点

好的,有一个之前讨论过的优点就是,通过模拟 API 的返回值,测试变得简单了。将通信和 API 隔离,这样测试就可以预测了。这样总会返回你需要的东西。

缺点

对于缺点,问题就是,如果不再想用 requests 了,并且决定回到标准库的 urllib,怎么办。每次改变 find_weather_for 的代码,都得去适配测试。好的测试是,当你修改代码实现的时候,测试时不需要改变的。所以,通过模拟,你最终把测试和实现耦合在了一起。

而且,另一个不好的方面是你需要在调用函数之前进行大量设置——至少是三行代码。

...
    # Creates a fake requests response object
    fake_resp = mocker.Mock()
    # Mock the json method to return the static weather data
    fake_resp.json = mocker.Mock(return_value=fake_weather_info)
    # Mock the status code
    fake_resp.status_code = HTTPStatus.OK
...

我可以做的更好吗?

是的,请继续看。我现在看看怎么改进一点。

使用 responses

mocker 功能模拟 requests 有点问题,就是有很多设置。避免这个问题的一个好办法就是使用一个库,可以拦截 requests 调用并且给它们 打补丁 patch 。有不止一个库可以做这件事,但是对我来说最简单的是 responses。我们来看一下怎么用,并且替换 mock

@responses.activate
def test_retrieve_weather_using_responses(fake_weather_info):
    """Given a city name, test that a HTML report about the weather is generated
    correctly."""
    api_uri = API.format(city_name="London", api_key=API_KEY)
    responses.add(responses.GET, api_uri, json=fake_weather_info, status=HTTPStatus.OK)

    weather_info = retrieve_weather(city="London")
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

这个函数再次使用了我们的 fake_weather_info fixture。

然后运行测试:

============================= test session starts ==============================
...
tests/test_weather_app.py::test_retrieve_weather_using_responses PASSED  [100%]
============================== 1 passed in 0.19s ===============================

非常好!测试也通过了。但是...并不是那么棒。

优点

使用诸如 responses 这样的库,好的方面就是不需要再给 requests 打补丁 patch 。通过将这层抽象交给库,可以减少一些设置。然而,如果你没注意到的话,还是有一些问题。

缺点

unittest.mock 很像,测试和实现再一次耦合了。如果替换 requests,测试就不能用了。

2、使用适配器测试 API

如果用模拟让测试耦合了,我能做什么?

设想下面的场景:假如说你不能再用 requests 了,而且必须要用 urllib 替换,因为这是 Python 自带的。不仅仅是这样,你了解了不要把测试代码和实现耦合,并且你想今后都避免这种情况。你想替换 urllib,也不想重写测试了。

事实证明,你可以抽象出执行 GET 请求的代码。

真的吗?怎么做?

可以使用 适配器 adapter 来抽象它。适配器是一种用来封装其他类的接口,并作为新接口暴露出来的一种设计模式。用这种方式,就可以修改适配器而不需要修改代码了。比如,在 find_weather_for 函数中,封装关于 requests 的所有细节,然后把这部分暴露给只接受 URL 的函数。

所以,这个:

def find_weather_for(city: str) -> dict:
    """Queries the weather API and returns the weather data for a particular city."""
    url = API.format(city_name=city, api_key=API_KEY)
    resp = requests.get(url)
    return resp.json()

变成这样:

def find_weather_for(city: str) -> dict:
    """Queries the weather API and returns the weather data for a particular city."""
    url = API.format(city_name=city, api_key=API_KEY)
    return adapter(url)

然后适配器变成这样:

def requests_adapter(url: str) -> dict:
    resp = requests.get(url)
    return resp.json()

现在到了重构 retrieve_weather 函数的时候:

def retrieve_weather(city: str) -> WeatherInfo:
    """Finds the weather for a city and returns a WeatherInfo instance."""
    data = find_weather_for(city, adapter=requests_adapter)
    return WeatherInfo.from_dict(data)

所以,如果你决定改为使用 urllib 的实现,只要换一下适配器:

def urllib_adapter(url: str) -> dict:
    """An adapter that encapsulates urllib.urlopen"""
    with urllib.request.urlopen(url) as response:
        resp = response.read()
    return json.loads(resp)
def retrieve_weather(city: str) -> WeatherInfo:
    """Finds the weather for a city and returns a WeatherInfo instance."""
    data = find_weather_for(city, adapter=urllib_adapter)
    return WeatherInfo.from_dict(data)

好的,那测试怎么做?

为了测试 retrieve_weather, 只要创建一个在测试过程中使用的假的适配器:

@responses.activate
def test_retrieve_weather_using_adapter(
    fake_weather_info,
):
    def fake_adapter(url: str):
        return fake_weather_info

    weather_info = retrieve_weather(city="London", adapter=fake_adapter)
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

如果运行测试,会获得:

============================= test session starts ==============================
tests/test_weather_app.py::test_retrieve_weather_using_adapter PASSED    [100%]
============================== 1 passed in 0.22s ===============================

优点

这个方法的优点是可以成功将测试和实现解耦。使用 依赖注入 dependency injection 在测试期间注入一个假的适配器。你也可以在任何时候更换适配器,包括在运行时。这些事情都不会改变任何行为。

缺点

缺点就是,因为你在测试中用了假的适配器,如果在实现中往适配器中引入了一个 bug,测试的时候就不会发现。比如说,往 requests 传入了一个有问题的参数,像这样:

def requests_adapter(url: str) -> dict:
    resp = requests.get(url, headers=<some broken headers>)
    return resp.json()

在生产环境中,适配器会有问题,而且单元测试没办法发现。但是事实是,之前的方法也会有同样的问题。这就是为什么不仅要单元测试,并且总是要集成测试。也就是说,要考虑另一个选项。

3、使用 VCR.py 测试 API

现在终于到了讨论我们最后一个选项了。诚实地说,我也是最近才发现这个。我用 模拟 mock 也很长时间了,而且总是有一些问题。VCR.py 是一个库,它可以简化很多 HTTP 请求的测试。

它的工作原理是将第一次运行测试的 HTTP 交互记录为一个 YAML 文件,叫做 cassette。请求和响应都会被序列化。当第二次运行测试的时候,VCT.py 将拦截对请求的调用,并且返回一个响应。

现在看一下下面如何使用 VCR.py 测试 retrieve_weather

@vcr.use_cassette()
def test_retrieve_weather_using_vcr(fake_weather_info):
    weather_info = retrieve_weather(city="London")
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

天呐,就这样?没有设置?@vcr.use_cassette() 是什么?

是的,就这样!没有设置,只要一个 pytest 标注告诉 VCR 去拦截调用,然后保存 cassette 文件。

cassette 文件是什么样?

好问题。这个文件里有很多东西。这是因为 VCR 保存了交互中的所有细节。

interactions:
- request:
    body: null
    headers:
      Accept:
      - '*/*'
      Accept-Encoding:
      - gzip, deflate
      Connection:
      - keep-alive
      User-Agent:
      - python-requests/2.24.0
    method: GET
    uri: https://api.openweathermap.org/data/2.5/weather?q=London&appid=<YOUR API KEY HERE>&units=metric
  response:
    body:
      string: '{"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":800,"main":"Clear","description":"clearsky","icon":"01d"}],"base":"stations","main":{"temp":16.53,"feels_like":15.52,"temp_min":15,"temp_max":17.78,"pressure":1023,"humidity":72},"visibility":10000,"wind":{"speed":2.1,"deg":40},"clouds":{"all":0},"dt":1600420164,"sys":{"type":1,"id":1414,"country":"GB","sunrise":1600407646,"sunset":1600452509},"timezone":3600,"id":2643743,"name":"London","cod":200}'
    headers:
      Access-Control-Allow-Credentials:
      - 'true'
      Access-Control-Allow-Methods:
      - GET, POST
      Access-Control-Allow-Origin:
      - '*'
      Connection:
      - keep-alive
      Content-Length:
      - '454'
      Content-Type:
      - application/json; charset=utf-8
      Date:
      - Fri, 18 Sep 2020 10:53:25 GMT
      Server:
      - openresty
      X-Cache-Key:
      - /data/2.5/weather?q=london&amp;units=metric
    status:
      code: 200
      message: OK
version: 1

确实很多!

真的!好的方面就是你不需要留意它。VCR.py 会为你安排好一切。

优点

现在看一下优点,我可以至少列出五个:

  • 没有设置代码。
  • 测试仍然是分离的,所以很快。
  • 测试是确定的。
  • 如果你改了请求,比如说用了错误的 header,测试会失败。
  • 没有与代码实现耦合,所以你可以换适配器,而且测试会通过。唯一有关系的东西就是请求必须是一样的。

缺点

再与模拟相比较,除了避免了错误,还是有一些问题。

如果 API 提供者出于某种原因修改了数据格式,测试仍然会通过。幸运的是,这种情况并不经常发生,而且在这种重大改变之前,API 提供者通常会给他们的 API 提供不同版本。

另一个需要考虑的事情是 就地 in place 端到端 end-to-end 测试。每次服务器运行的时候,这些测试都会调用。顾名思义,这是一个范围更广、更慢的测试。它们会比单元测试覆盖更多。事实上,并不是每个项目都需要使用它们。所以,就我看来,VCR.py 对于大多数人的需求来说都绰绰有余。

总结

就这么多了。我希望今天你了解了一些有用的东西。测试 API 客户端应用可能会有点吓人。然而,当武装了合适的工具和知识,你就可以驯服这个野兽。

我的 Github 上可以找到这个完整的应用。

这篇文章最早发表在 作者的个人博客,授权转载


via: https://opensource.com/article/21/9/unit-test-python

作者:Miguel Brito 选题:lujun9972 译者:Yufei-Yan 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

使用我最喜欢的工具在 Linux 上截屏,可以节省时间。

在写开源软件时,我更喜欢展示一些截图来帮助演示我在说什么。古语有云,一图胜千言。如果你能展示一件事,那通常比试图用言语描述它要好。

有几种方法可以在 Linux 中截图。以下是我在 Linux 上用于捕获截图的三种方法:

1、GNOME

GNOME 有一个很棒的内置截图工具。只需按下键盘上的 PrtScr 键,GNOME 就会显示一个截图对话框:

Image of GNOME screenshot tool

默认操作是抓取区域的截图。这是一种在你制作截图时裁剪截图的非常有用的方法。只需将高亮显示框移动到你需要的位置,然后使用“抓取”角来更改大小。或选择其他图标之一以截取整个屏幕或系统上的单个窗口。点击“圆圈”图标进行截图,类似于手机上的“拍照”按钮。 GNOME 截图工具将截图保存在图片文件夹内的截图文件夹中。

2、GIMP

如果你需要更多截图选项,你可以使用流行的图像编辑器 GIMP 截图。要进行截图,请选择“ 文件 File ”并选择“ 创建 Create ”子菜单,然后选择“ 截图 Screenshot ”。

Image of the GIMP screenshot menu

该对话框允许你截取单个窗口、整个屏幕或仅一个区域的屏幕截图。我喜欢这个工具可以让你设置一个延迟:选择窗口后多长时间,按下截图后多长时间。当我想截取菜单操作的截图时,我经常使用此功能,因此我有足够的时间去窗口打开菜单。

GIMP 将截图作为新图像打开,你可以对其进行编辑并保存到你喜欢的位置。

3、Firefox

如果你需要截取网站的截图,请尝试使用 Firefox 的内置截图程序。右键单击网页正文中的任意位置,然后从菜单中选择“ 截图 Take Screenshot ”:

Image of screenshot utility

Firefox 切换到模态显示,并提示你单击或拖动页面以选择区域,或使用其中一个图标保存整个页面的副本,或仅在浏览器中可见的内容:

Image of Firefox modal display

当你在屏幕上移动鼠标时,你可能会注意到 Firefox 会高亮显示某些区域。这些是页面上的块元素,例如 <div> 或其他块元素。单击该元素以对其进行截图。 Firefox 将截图保存到你的下载文件夹,或你设置为“下载”位置的任何位置。

如果你尝试记录流程,那么截图可以为你节省大量时间。

尝试使用其中一种方法在 Linux 上截图。

(图片来源:Jim Hall,CC BY-SA 40)


via: https://opensource.com/article/22/8/screenshots-linux

作者:Jim Hall 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

好奇号将通过软件更新提速 50%

已经登陆火星达十年之久的好奇号火星车,依靠视觉测程法测量行驶距离,它通常每行驶约 1 米就要停下来检查周围环境的照片,以计算究竟走了多远。这种谨慎的做法使得好奇号的时速只有 45 米,而它本身的硬件能支持每小时 120 米的速度。更新后的软件可以让好奇号在静止时拍摄周围环境照片,而在行驶时检查位置并修正错误。测试显示,好奇号的时速能增加到 83.2 米,提速 50%。

消息来源:新科学家
老王点评:一样是“电动车”,要是电动汽车得到这种提升 50% 的升级补丁,怎么也得收个升级费或订阅费啊。

VisionFive RISC-V 单板机正式得到 Ubuntu 支持

Canonical 正式发布了适用于 VisionFive RISC-V 单板机的 Ubuntu 22.04.1 LTS。作为一款售价 179 美元的单板机,国内厂商赛昉科技的 VisionFive 旨在支撑成熟的 RISC-V Linux 发行版运行。过去几个月,Canonical 工程师一直在努力向后移植各种补丁,以确保 Ubuntu 22.04 LTS 也可在该单板机上良好运行。

消息来源:Phoronix
老王点评:说起来,下周二赛昉科技会在线上举办发布会,会发布全球首款高性能、低成本的开源 RISC-V 单板计算机。

Linus 破例为 NTFS3 内核驱动更新网开一面

Linux 6.0 的合并窗口已经关闭,正常情况下除了修复 bug,不会接受代码清理、功能添加等提交。而在两周的合并窗口期间,开源的 NTFS3 并未带来新功能代码和其它严重 bug 修复。不过,维护者在 rc1 发布后,提交了一批 NTFS3 代码重构和 bug 修复。Linus Torvalds 认为“似乎大多仅为重构和清理 —— 这些内容本该在上一个合并窗口期内出现。不过鉴于这些只涉及 NTFS3 本身,我们认为相关实验还是相当可靠的。”因而破例接纳了这些提交。

消息来源:Phoronix
老王点评:毕竟 NTFS3 是 Linus 钟爱的部分。