阅读视图

发现新文章,点击刷新页面。
☑️ ☆

如何优雅地寻找 Tesla ?

近期,Tesla APP 发布了 4.24.0 版本,在 iOS 的“快捷指令”中可以直接调用部分 Tesla 命令了!借助快捷指令,你能很优雅地找到你的 Tesla !

有时候我们将车停在了地库中的一个不常去的位置,用车时往往就找不到车。之前我经常使用 Tesla APP 上的“鸣笛”功能来加速自己找车的速度,而在近期,Tesla APP 发布了 4.24.0 版本,在 iOS 的“快捷指令”中可以直接调用部分 Tesla 命令了!这是一个非常令人兴奋的功能!因为这意味着,我们完全可以设定一个 iOS 快捷指令,直接呼唤 Siri 来寻找我们的 Tesla 。

安装「我的 Tesla 在哪里?」快捷指令

使用方法:

  • 对 iPhone 说出「嘿 Siri ,我的 Tesla 在哪里?」来触发快捷指令,运行后根据提示操作即可。

Q: 为什么快捷指令安装后无法使用?

A: 请确保 iPhone 上的 Tesla APP 版本在 4.24.0 及以上。

Q: 可以在 Android 设备上使用吗?

A: 此快捷指令依赖 iOS,无法在 Android 设备上使用。

🔲 ☆

chaosblade 生成指定 CPU 利用率负载的原理

最近笔者在做的一个降级功能,与机器资源情况密切相关。然而在测试时发现控制 CPU 利用率来构造测试条件,并不是一个容易的事情。借助时间片的思想,笔者用一个非常简单的 shell 一定程度上解决了这个问题。但转念一想,对于混沌测试的软件,这应该是个必备能力。查找了一下 chaosblade 的相关资料,果然支持生成指定 CPU 利用率的负载。故读了读其这部分源码,看看它是怎么实现的。

使用 chaosblade 来构造指定 CPU 利用率的负载非常简单:

1
blade create cpu load --cpu-percent 80

即能够生成使 CPU 利用率到达 80% 的负载。

burncpu 的核心逻辑位于这里: https://github.com/chaosblade-io/chaosblade-exec-os/blob/318c52d83a851bc75012abc7d880d4f440f1f972/exec/bin/burncpu/burncpu.go#L140-L168

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
func burnCpu() {

	runtime.GOMAXPROCS(cpuCount)

	var totalCpuPercent []float64
	var curProcess *process.Process
	var curCpuPercent float64
	var err error

	totalCpuPercent, err = cpu.Percent(time.Second, false)  // 获取当前所有 CPU 一秒内平均利用率
	// ...
	curProcess, err = process.NewProcess(int32(os.Getpid()))
	// ...
	curCpuPercent, err = curProcess.CPUPercent()  // 获取当前进程的 CPU 利用率
	// ...
	otherCpuPercent := (100.0 - (totalCpuPercent[0] - curCpuPercent)) / 100.0  // 除去已有进程,可操作的 CPU 利用率。值的范围为 [0, 1]
	go func() {
		t := time.NewTicker(3 * time.Second)
		for {
			select {
			// timer 3s
			case <-t.C:
				totalCpuPercent, err = cpu.Percent(time.Second, false)
				// ...
				curCpuPercent, err = curProcess.CPUPercent()
				// ...
				otherCpuPercent = (100.0 - (totalCpuPercent[0] - curCpuPercent)) / 100.0
			}
		}
	}()  // 每 3s 更新一次 totalCpuPercent, curCpuPercent 和 otherCpuPercent

	if climbTime == 0 {  // 不需要爬坡时间
		slopePercent = float64(cpuPercent)  // 爬坡值与目标值一致
	} else {
		// ...
	}

    // cpuCount 是由 runtime.NumCPU() 得来,获取的是当前 CPU 的逻辑核数量
	for i := 0; i < cpuCount; i++ {
		go func() {
			busy := int64(0)
			idle := int64(0)
			all := int64(10000000)    // 设定 10ms 为一个周期
			dx := 0.0
			ds := time.Duration(0)
			for i := 0; ; i = (i + 1) % 1000 {  // 死循环
				startTime := time.Now().UnixNano()
				if i == 0 {  // 每 1000 次进入
                    // 这个赋值语句是整个 burncpu 的灵魂。
                    // 我们最终希望获得的是 slopePercent% 的 CPU 利用率
                    // 应该生成的 CPU 压力即为 (slopePercent - totalCpuPercent[0])%
                    // 理想条件下,我们只需对 (slopePercent - totalCpuPercent[0]) 个 0.1ms 时间片设置为 busy 状态即可
                    // 但由于系统中存在其他进程,burncpu 无法真正获得到 (slopePercent - totalCpuPercent[0]) 个 0.1ms 时间片
                    // 因此需要按比例放大时间片的个数,而这个比例则是当时 burncpu 实际可用的 CPU 利用率
                    // 将这个赋值语句转换为如下方程,则更好理解了:
                    //   slopePercent  = totalCpuPercent + dx * otherCpuPercent
                    //       ^                 ^           ^                 ^
                    // 最终获得的CPU利用率 当前CPU利用率 一个周期内busy的时间片个数 burnCpu真正可操作的CPU比例
					dx = (slopePercent - totalCpuPercent[0]) / otherCpuPercent
					busy = busy + int64(dx*100000)  // 有 dx 个 0.1ms 需要为 busy 状态
					if busy < 0 {
						busy = 0
					}
					idle = all - busy
					if idle < 0 {
						idle = 0
					}
					ds, _ = time.ParseDuration(strconv.FormatInt(idle, 10) + "ns")
				}
				for time.Now().UnixNano()-startTime < busy {
				}  // 阻塞 CPU,使 CPU 位于 busy 状态,直至设定的时间片结束
				time.Sleep(ds) // 空闲 CPU,使 CPU 处于 idle 状态,直至设定的时间片结束
				runtime.Gosched()
			}
		}()
	}
	select {}  // 阻塞 burnCpu 函数,保活 goroutines
}
☑️ ☆

Lexer & Parser Toolchain

推荐一个非常好的编译器工具链入门教程: https://pandolia.net/tinyc/index.html

Lexer - flex

flex 文件格式:

https://pandolia.net/tinyc/ch8_flex.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
%{
Declarations
%}

Definitions

%%
Rules
%%

User subroutines
  • Declarations:声明,会被原样复制入 lex.yy.c 。一般用于声明全局变量和函数。

  • Definitions:定义,可以定义正则表达式的名字,用于 Rules 中使用,通过名字直接使用预定义的正则表达式。

  • Rules:规则,每一行都是一条规则,由匹配模式 pattern (正则表达式)和事件 action (C 代码)组成。

  • User subroutines:用户定义过程,会被原样复制到 lex.yy.c 的最末尾。

  • yywrap() 用于把多个输入文件打包成一个输入,当 yylex 将一个文件读入到结尾 EOF 时,会向 yywrap 询问是否继续。若需连续解析多个文件,需要在 yywrap 中打开文件,并返回 0。返回 1 则表示后面没有文件可以读取了,使得 yylex 函数结束。

  • yytext:刚刚匹配到的字符串的值。

  • yyleng:刚刚匹配到的字符串的长度。

如一个简单的计算器实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
%{
#include "y.tab.h"
%}

%%
[0-9]+          { yylval = atoi(yytext); return T_NUM; }
[-/+*()\n]      { return yytext[0]; }
.               { return 0; /* end when meet everything else */ }
%%

int yywrap(void) { 
    return 1;
}

Parser - Yacc/Bison

bison 可以认为是 yacc 的开源实现。

Bison 文件的格式:

https://pandolia.net/tinyc/ch13_bison.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
%{
Declarations
%}

Definitions

%%
Productions
%%

User subroutines
  • Declarations:声明,会被原样复制入 y.tab.c 。一般用于声明全局变量和函数。

  • Definitions:定义,可以定义 bison 专有的变量。

    • %token:单字符 token (token type 值与字符的 ASCII 码相同)不需要使用 %token 进行预定义,其他类型的 token 都需要使用 %token 进行预定义。bison 会自动为 token 分配一个编号,并写入 y.tab.h 中,因此在 flex 文件中是可以直接调用的。
    • %left%right:表示符号是左(左向右)、右(右向左)结合的。
    • %nonassoc :表示符号是不可结合的,如 x op y op z 是非法的。
    • %prec:上下文依赖的优先级,如「负号」就是一个很典型的例子,见 Context-Dependent Precedence
    • 更多定义可见 bison Declarations
  • Productions:

    • : 代表 ->,或 EBNF 式中的 =

    • | 用于分隔同一个非终结符的不同产生式。

    • /* empty */ ,若产生式右边为 $\epsilon$ 时,不需要写任何符号,可写为注释 /* empty */

    • ; 表示结束一个非终结符的产生式。

    • 每个产生式后面花括号内,都是一段 C 代码,可在产生式被应用时执行。

    • 例:

      1
      2
      3
      
      s : S E '\n'       { printf("ans = %d\n", $2); }
        | /* empty */    { /* empty */}
        ;
      
  • User subroutines:用户定义过程,会被原样复制到 y.tab.c 的最末尾。

bison 会将语法产生式以及符号优先级转换成一个 C 语言的 LALR(1) 动作表,输出到 y.tab.c 中。并会将这个动作表转换为可读形式输出至 y.output 中。

bison 会根据自定义语法文件在 y.tab.c 中生成一个函数 int yyparse(void) 。这个函数按照 LR(1) 解析流程,对词法分析中得到的 token 流进行解析。每当读取下一个符号时,就会执行一次 x = yylex() 。每当要执行一个折叠动作(reduce)时,相应的产生式后的 C 代码将被执行,执行完后将相应的状态出栈。

若 token 流不合法,yyparse 会在第一次出错的地方终止,并调用 yyerror 函数,最后返回 1。

在 reduce 动作时,可用 $1, $2$n 来引用属性栈的属性(可以认为是产生式中的第 n 个属性内容,并在最后将这个状态下的属性出栈。其中,$$ 代表产生式左侧的终结符,可在 reduce 动作设置 $$ 的值,最后将 $$ 入栈。

如一个简单的计算器实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
%{
#include <stdio.h>
void yyerror(const char* msg) {}
int yylex();
%}

%token T_NUM

%left '+' '-'
%left '*' '/'

%%

S   :   S E '\n'        { printf("ans = %d\n", $2); }
    |   /* empty */     { /* empty */ }
    ;

E   :   E '+' E         { $$ = $1 + $3; }
    |   E '-' E         { $$ = $1 - $3; }
    |   E '*' E         { $$ = $1 * $3; }
    |   E '/' E         { $$ = $1 / $3; }
    |   T_NUM           { $$ = $1; }
    |   '(' E ')'       { $$ = $2; }
    ;

%%

int main() {
    return yyparse();
}
☑️ ☆

grub rescue>, Oops!

教你如何修复丢失的 Linux GRUB 启动引导。

起因

为了再将神船 Z7 用起来,笔者决定扩展其仅有 200GB 的游戏分区空间。然而由于可用空间的位置并不连续,Windows 自带磁盘工具无法处理这种情况,且笔者并不希望将分区转为动态分区,因此使用了一个第三方软件 EaseUS Partition Master Free 来完成磁盘数据的迁移和分区重分配工作。就在设置好分区,重启了之后,屏幕上赫然显示着一个命令行界面:

1
2
3
error: unknown filesystem.
Entering rescue mode...
grub rescue> 

Oops!难道重新分个区就把系统搞挂了吗?!grub rescue> 这个命令行界面在我心中一直是噩梦般的存在,之前遇到都只能选择计算机三大法宝之一:重装来解决问题。

既然双系统里的 Linux 进不去了,那我试试直接进 Windows?于是重启了一下机器,通过 BIOS 直接进入了系统盘位于 SSD 中的 Windows。好家伙,这能正常启动,那问题就不大了。

Windows 磁盘分区的重分配工作得到了验证,看上去非常完美。笔者用于存储游戏的专用分区已经扩容至 ~450GB ,那接下来还是修复下 Linux 启动引导的问题吧。

经过一番简单的搜索,笔者摸清了其中大概的原因:在进行 Windows 磁盘操作时,对整个 HDD 做了分区、合并等处理,导致 GPT 元数据发生了改变,Linux 的引导程序 GRUB 自然就无法找到之前的 /boot 启动分区了,这也是报出 error: unknown filesystem 的原因。

在 grub rescue 中修复启动项

由于是硬盘元数据发生了改变导致 GRUB 失效,那我们的思路就是更新 GRUB 中的配置。

首先要找到目前 Linux 的 /boot 启动分区的位置,在 grub rescue 中,可以用 ls 来寻找:

1
2
grub rescue> ls
(hd0) (hd0,gpt7) (hd0,gpt6) (hd0,gpt5) (hd0,gpt4) (hd0,gpt3) (hd0,gpt2) (hd0,gpt1) (hd1) (hd1,gpt3) (hd1,gpt2) (hd1,gpt1)

直接输入 ls ,即可得到所有的分区位置。但是启动分区到底是哪个呢?我们可以继续用 ls 来查找,先试试 (hd0,gpt1) 这个分区吧。

此处要注意的是,我们要查找的是 /boot/grub 这个文件夹目录存在的分区。由于笔者的 /boot 启动分区是单独挂载的,所以查找的路径是 <partition>/grub 。若安装的 Linux 并没有单独挂载 /boot 启动分区,则查找路径应为 <partition>/boot/grub

1
2
grub rescue> ls (hd0,gpt1)/grub
error: unknown filesystem.

输出为 error: unknown filesystem. ,说明这个分区并不是我们想要的启动分区,那再试试下一个分区。

1
2
grub rescue> ls (hd0,gpt2)/grub
./ ../ x86_64-efi/ grubenv themes/ fonts/ grub.cfg

Bingo!这就是我们要找的东西丫!好,记下这个分区 (hd0,gpt2),它就是我们宝贵的启动分区。

接下来就是给 GRUB 修改启动配置,只要改两个参数即可:

1
2
grub rescue> set root=(hd0,gpt2)
grub rescue> set prefix=(hd0,gpt2)/grub
  • root 设置为启动分区。
  • prefix 设置为 grub 安装文件夹。若 /boot 未挂载单独分区,也可能为 <partition>/boot/grub

配置也修改好了之后,重新使 GRUB 进入普通模式就可以找回我们的启动项了!

1
2
grub rescue> insmod normal
grub rescue> normal

执行完后,应该能看到我们熟悉的 GRUB 引导界面了。

持久化

事实上,这样的修复只是暂时的,我们还需要对 GRUB 配置进行持久化的更新,否则每次开机都要这样操作一遍。

进入 Linux,打开终端,输入如下命令,GRUB 就会自动更新配置。

1
2
$ sudo update-grub
$ sudo grub-install /dev/sda

/dev/sda 为 Linux 的安装磁盘。

可以再重启一次计算机,验证 Linux 启动引导是否修复成功。

References

🔲 ☆

浅谈 beancount 借款还款交易记录方法

本文基于的假设是:友人 A 需要购买产品 B,但需要你来代他购买。探讨以下几种事件发生顺序的记账方法。

除了自己已有的 Assets:Bank:Z 外,需要额外建立这些账户:

  • Assets:Receivables:A

先转账,再交易

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
; 转账
0000-00-00 * "A transfer money"
  Assets:Receivables:A -1000.00 CNY
  Assets:Bank:Z         1000.00 CNY
  
; 此时 Assets:Bank:Z 账户起着代持资金的作用。

; 交易
0000-00-01 * "Buy B for A"
  Assets:Bank:Z         -1000.00 CNY  
  Assets:Receivables:A   1000.00 CNY
  
; 整个过程结束后各账户余额:
; Assets:Bank:Z             0.00 CNY
; Assets:Receivables:A      0.00 CNY

先交易,再转账(垫付)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
; 交易(垫付)
0000-00-00 * "Buy B for A"
  Assets:Receivables:A  1000.00 CNY
  Assets:Bank:Z        -1000.00 CNY
  
; 此时 Assets:Bank:Z 账户起着垫付资金的作用,
; Assets:Receivables:A 用于应收来自 A 的款项。

; 转账(还款)
0000-00-01 * "A transfer money"
  Assets:Bank:Z          1000.00 CNY
  Assets:Receivables:A  -1000.00 CNY
  
; 交易结束后各账户余额:
; Assets:Bank:Z             0.00 CNY
; Assets:Receivables:A      0.00 CNY

转账和交易同步

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
; 转账+交易
0000-00-00 * "A transfer money to buy B"
  Assets:Receivables:A -1000.00 CNY
  Assets:Bank:Z         1000.00 CNY
  Assets:Bank:Z        -1000.00 CNY
  Assets:Receivables:A  1000.00 CNY

; 交易结束后各账户余额:
; Assets:Bank:Z              0.00 CNY
; Assets:Receivables:A       0.00 CNY
🔲 ☆

多种支付账户的账单导出及查看方法汇总

第三方电子支付

微信支付

  1. 打开“微信”,点击“我的”,点击“支付”,进入如下界面:

  2. 点击“钱包”,点击右上角“账单”,点击右上角的“常见问题”。

  3. 点击“下载账单”。

  4. 选择“用于个人对账”。

  5. 自行选择需要导出的日期范围,填写自己的邮箱即可。

支付宝

打开「支付宝」APP,在菜单栏中点击「我的」。

进入「账单」,点击右上角「…」。

选择「开具交易流水证明」。

在「选择申请用途」中选择「用于个人对账」。

选择需要的交易时间范围。

填写电子邮箱后,即可获取账单。

余额宝

打开余额宝网站,选择日期,点击“下载”即可。

银行

借记卡

中国工商银行

登录中国工商银行个人网上银行,在「我的卡包」中选择某个银行卡,点击「明细」按钮,进入「明细查询」。

macOS 需要使用 Safari 并安装工行插件才可正常登录。

选择需要的时间区间,在「下载明细格式」选择 「EXCEL 格式(.csv)」,点击「下载」后,提示“明细正在下载中,请稍后回到本交易页面查看下载结果”。稍等片刻,页面提示“您的历史明细下载已经处理成功,请您再次进入明细查询页面进行下载!”。此时重新点击「查询」,在「下载」按钮旁边会出现可下载结果的链接,点击即可下载真正的 csv 账单文件(不要忘记重新将下载格式改为 csv)。

交通银行

手机「交通银行 APP」->查找“交易明细”->最下面“是否需要开立交易明细证明?”点击「立即申请」->「交易明细清单」->「电子版」-> 选择日期->填写电子邮箱->「确认开立」->输入银行卡密码->开立成功

PDF 为加密格式,加密密码为身份证后六位。

如何开立 CSV 格式的账单?

中国银行

手机「中国银行 APP」->「首页」->「更多」->「助手」->「交易流水打印」->「立即申请」-> 选择日期 -> 选择发送邮箱 -> 填写电子邮箱 -> 点击「申请」-> 申请成功

PDF 为加密格式,加密密码在「交易流水打印」的「申请记录」中可以找到。

网上银行: 需要安装安全输入插件,用 Chrome 登录中国银行网上银行

找不到登录账号了。

信用卡

招商银行

“电邮账单”获取方式:

掌上生活->金融->查账单->右上角…->账单服务->账单补寄->(寄送方式改为「电邮账单」)->申请补寄

zsxsoft/my-beancount-scripts

如何获取 CSV 账单?

登录网银大众版专业版后,点【信用卡→账户管理→未出账单查询/已出账单查询】查看/下载/打印一年内的账单,带有业务受理专用章。

已出账单:点击【账单明细→点击下载财务明细】打印/下载,格式为【PDF】。 未出账单:点击页面右侧的下载或打印,格式为【Excel】。

社保(深圳)

深圳五险缴费明细查询:

登录深圳社保服务网站,进入“查询服务”,选择“缴费信息查询”,点击“五险缴费明细查询”即可。

深圳医保、养老个人账户余额查询:

登录深圳社保服务网站,进入“查询服务”,选择“参保信息查询”,点击“基本医疗保险个人账户查询”即可。

公积金(深圳)

「i 深圳 APP」,在首页点击「公积金」,进入「深圳市公积金」页面,即可看到余额。点击「账户明细」即可看到缴费情况。

加密货币

火币 Global

登录火币 Global 网站,进入币币订单的成交明细页面,选择合适的时间区间后,点击成交明细右上角的导出按钮即可。

☑️ ☆

如何从 Mendeley 迁移到 Zotero

同步 Mendeley

打开 Mendeley,先同步所有更改。

建议遵循 保存附件 ,将所有 PDF 保存到指定目录,防止内容丢失。

保存附件

在菜单栏中点击 Mendeley Desktop 👉🏻 Preferences...

在弹出框中,找到 File Organizer 的标签,并勾选 Organize my files

备份 Mendeley

查看 Mendeley 版本,若大于 1.18,则先备份 Mendeley 数据库(可用于失败回滚)。

版本号小于等于 1.18 可直接阅读 使用 Mendeley 1.18 版同步数据库

在菜单栏中点击 Mendeley Desktop 👉🏻 About 可查看版本号。

备份 Mendeley:点击菜单栏中的 Help 👉🏻 Create Backup...

Save As 中填入备份文件名,点击 Save 即可。

备份完毕之后,关闭 Mendeley。

这里找你所在平台的 1.18 版本 Mendeley 并下载。

使用 Mendeley 1.18 版同步数据库

打开 Mendeley 1.18 后,登录你的账号,并点击刷新,等待刷新结束。

1.18 版本的 Mendeley 无法自动下载所有附件,需要手动逐一点击下载 将上步中 保存附件 的内容放入 Mendeley 也可行,但命名方式一定要一致!笔者把库给搞乱了,没法实践这个方法了 :(

打开 Zotero,在菜单栏中选择 文件 👉🏻 导入 👉🏻 Mendeley

选择有 Mendeley 账号的那条 Mendeley 数据库导入即可。

Zotero 同步方式

Zotero 提供了两种同步方式,分别是使用 Zotero 提供的云端存储(免费 300MB)以及使用自定义 WebDAV 同步文件。

笔者是坚果云的重度用户,且坚果云也提供了 WebDAV 服务,自然使用坚果云的 WebDAV 来同步 Zotero 数据。

在坚果云上新建一个 WebDAV 应用非常简单,按照官方文档的指引即可。

最后在 Zotero 菜单栏中点击 Zotero 👉🏻 首选项 👉🏻 同步

在文件同步中选择 WebDAV,将坚果云中的信息填入即可。

References

  1. How do I import a Mendeley library into Zotero? https://www.zotero.org/support/kb/mendeley_import#mendeley_database_encryption
  2. How do I create or restore a backup of my Mendeley database? https://service.elsevier.com/app/answers/detail/a_id/18153/
❌