#954 生活感悟
个人 2023-10-30有时候真的是秀才遇到兵,有理说不清。
可能有些人就是坚持为自己的利益斗争的思想,一言不合就要掀桌子。
可能有些人就是喜欢信口开河,胡搅蛮缠,歇斯底里。
我总是有一种感觉,就好像我正在吃饭,有人一边往我的碗里面丢沙子,我却只能继续默默吃下去。
coding in a complicated world
有时候真的是秀才遇到兵,有理说不清。
可能有些人就是坚持为自己的利益斗争的思想,一言不合就要掀桌子。
可能有些人就是喜欢信口开河,胡搅蛮缠,歇斯底里。
我总是有一种感觉,就好像我正在吃饭,有人一边往我的碗里面丢沙子,我却只能继续默默吃下去。
Select latest sample for series with a given metric name:
node_cpu_seconds_total
Select 5-minute range of samples for series with a given metric name:
node_cpu_seconds_total[5m]
Only series with given label values:
node_cpu_seconds_total{cpu="0",mode="idle"}
Complex label matchers (=: equality, !=: non-equality, =~: regex match, !~: negative regex match):
node_cpu_seconds_total{cpu!="0",mode=~"user|system"}
Select data from one day ago and shift it to the current time:
process_resident_memory_bytes offset 1d
Per-second rate of increase, averaged over last 5 minutes:
rate(demo_api_request_duration_seconds_count[5m])
Per-second rate of increase, calculated over last two samples in a 1-minute time window:
irate(demo_api_request_duration_seconds_count[1m])
Absolute increase over last hour:
increase(demo_api_request_duration_seconds_count[1h])
Sum over all series:
sum(node_filesystem_size_bytes)
Preserve the instance and job label dimensions:
sum by(job, instance) (node_filesystem_size_bytes)
Aggregate away the instance and job label dimensions:
sum without(instance, job) (node_filesystem_size_bytes)
Available aggregation operators: sum()
,min()
,max()
,avg()
,stddev()
,stdvar()
,count()
,count_values()
,group()
,bottomk()
,topk()
,quantile()
Add all equally-labelled series from both sides:
node_memory_MemFree_bytes + node_memory_Cached_bytes
Add series, matching only on the instance and job labels:
node_memory_MemFree_bytes + on(instance, job) node_memory_Cached_bytes
Add series, ignoring the instance and job labels for matching:
node_memory_MemFree_bytes + ignoring(instance, job) node_memory_Cached_bytes
Explicitly allow many-to-one matching:
rate(demo_cpu_usage_seconds_total[1m]) / on(instance, job) group_left demo_num_cpus
Include the version label from "one" (right) side in the result:
node_filesystem_avail_bytes * on(instance, job) group_left(version) node_exporter_build_info
Available arithmetic operators: +, -, \*, /, %, ^
Only keep series with a sample value greater than a given number:
node_filesystem_avail_bytes > 10*1024*1024
Only keep series from the left-hand side whose sample values are larger than their right-hand-side matches:
go_goroutines > go_threads
Instead of filtering, return 0 or 1 for each compared series:
go_goroutines > bool go_threads
Match only on specific labels:
go_goroutines > bool on(job, instance) go_threads
Available comparison operators: ==, !=, >, <, >=, <=
Include any label sets that are either on the left or right side:
up{job="prometheus"} or up{job="node"}
Include any label sets that are present both on the left and right side:
node_network_mtu_bytes and (node_network_address_assign_type == 0)
Include any label sets from the left side that are not present in the right side:
node_network_mtu_bytes unless (node_network_address_assign_type == 1)
Match only on specific labels:
node_network_mtu_bytes and on(device) (node_network_address_assign_type == 0)
90th percentile request latency over last 5 minutes, for every label dimension:
histogram_quantile(0.9, rate(demo_api_request_duration_seconds_bucket[5m]))
...for only the path and method dimensions:
histogram_quantile(
0.9,
sum by(le, path, method) (
rate(demo_api_request_duration_seconds_bucket[5m])
)
)
Per-second derivative using linear regression:
deriv(demo_disk_usage_bytes[1h])
Absolute change in value over last hour:
delta(demo_disk_usage_bytes[1h])
Predict value in 1 hour, based on last 4 hours:
predict_linear(demo_disk_usage_bytes[4h], 3600)
Average within each series over a 5-minute period:
avg_over_time(go_goroutines[5m])
Get the maximum for each series over a one-day period:
max_over_time(process_resident_memory_bytes[1d])
Count the number of samples for each series over a 5-minute period:
count_over_time(process_resident_memory_bytes[5m])
See all available xxx_over_time() aggregation functions.
Get the Unix time in seconds at each resolution step:
time()
Get the age of the last successful batch job run:
time() - demo_batch_last_success_timestamp_seconds
Find batch jobs which haven't succeeded in an hour:
time() - demo_batch_last_success_timestamp_seconds > 3600
Create one output series when the input vector is empty:
absent(up{job="some-job"})
Create one output series when the input range vector is empty for 5 minutes:
absent_over_time(up{job="some-job"}[5m])
Join the values of two labels with a - separator into a new endpoint label:
label_join(rate(demo_api_request_duration_seconds_count[5m]), "endpoint", " ", "method", "path")
Extract part of a label and store it in a new label:
label_replace(up, "hostname", "$1", "instance", "(.+):(\\d+)")
Calculate the 5-minute-averaged rate over a 1-hour period, at the default subquery resolution (= global rule evaluation interval):
rate(demo_api_request_duration_seconds_count[5m])[1h:]
Calculate the 5-minute-averaged rate over a 1-hour period, at a 15-second subquery resolution:
rate(demo_api_request_duration_seconds_count[5m])[1h:15s]
Using the subquery result to get the maximum rate over a 1-hour period:
max_over_time(
rate(
demo_api_request_duration_seconds_count[5m]
)[1h:]
)
两个知名的商业面板(属于同一家公司):
国际:
国内:
readline 是 GNU 的库,实现命令行编辑功能,bash、ftp、python、zsh、mysql 等程序的命令行界面都是使用 readline 实现的,具体实现有 ctrl-r
(查找历史命令)、ctrl-p
(上一历史命令)、ctrl-a
(跳到行首)等。
最重要的还有本次我们所需的 alt+.(dot)
、esc+.
、meta+.
得到上一命令的最后一个参数。还有更多快捷键可以参考 readline shortcuts。
mac 上没有 alt 和 meta 键,所以我一般使用 esc+.
来获取上一条最后的参数。
用 !$
获取上一命令的最后一个参数。
历史展开由命令、参数、操作符三部分构成,分别表示展开哪一条命令、该命令的哪个参数、对命令的操作。命令展开完成之后才会进入 .bash_history
中,即执行 history 命令不会看到用于历史展开的参数。
本节的所有命令都假设当前 bash 已经有如下的历史:
$ history
1 echo 1 2 3 4 5
2 ls 6 7 8 9 10
3 echo 11 12 13 14 15
4 cat 16 17 18 19 20
5 echo 21 22 23 24 25
$ !n # 表示第n条命令,如!2表示执行ls 6 7 8 9 10
$ !-n # 表示倒数第n条命令,如!-3表示执行echo 11 12 13 14 15
$ !! # 表示上一条命令,是!-1的快捷方式
$ !string # 表示以string开始的最近的一条命令,如!echo表示echo 21 22 23 24 25
$ !?string? # 表示含有string的最近的一条命令,如!?6?表示cat 16 17 18 19 20
$ ^string1^string2^ # 表示执行上一条命令,并将其中的第一个string1替换为string2,如果string1不存在则替换失败,不会执行命令。
$ !# # 表示当前命令现在已经输入的部分,如echo 1 2 !#会执行echo 1 2 echo 1 2
$ !!:0 # 表示上一命令的第0个参数,即命令本身,得到的是echo
$ !2:n # 表示第2个命令的第n个参数,如!2:2得到的是7
$ !!:^ # 表示上一命令第1个参数,可进一步简写为!^,与!!:1同义,得到的是21
$ !!:$ # 表示上一命令的最后一个参数,可进一步简写为!$,得到的是25
$ !!:x-y # 表示第x到第y个参数,-y意为0-y,如!-2:3-4得到的是18 19
$ !!:* # 表示上一命令的参数部分,可进一步简写为!*,如!!:*得到的是21 22 23 24 25
$ !!:n* # 跟!!:n-$同义
$ !!:n- # 意为!!:n-$-1,从第n个参数到倒数第二个参数,如!-2:3-得到的是18 19
通过 bash 历史展开实现创建并 cd 到目录的方式为:
$ mkdir somewhere/dir && cd !#:1 # 其中!#表示本行所有命令"mkdir somewhere/dir && cd”,:1取第一个参数就是目录名
c. 操作符(Modifiers),在可选的参数部分之后,用一个或多个 : 操作符加特定字符。
h 去除最后的一个文件路径部分,
echo /tmp/123/456/
,则 cd !:1:h:h
意为 cd /tmp/123
;echo /tmp/123/456
,则 cd !:1:h:h
意为 cd /tmp
echo /tmp/123/456/
,则 cd !:1:t
意为 cd
;echo /tmp/123/456
,则 cd !:1:t
意为 cd 456
echo /tmp/bbs.c
,则 echo !:1:r
意为 echo /tmp/bbs
echo /tmp/bbs.c
,则 echo !:1:e
意为 echo .c
[g]s/string1/sting2/
将命令的 string1 替换为 string2,g 意为全局替换,echo 1 2 1
,则 !:gs/1/3/
意为 echo 3 2 3
。^string1^string2^
相当于 !!:s/string1/string2/
[g]&
意为执行上次替换,g 意为全局替换。echo 123451
,则 !:&
意为 echo 323451
$
($_
)在 shell/bash 里 $
符号表示当前是普通用户(#
是 root),在 sh 脚本或命令行里,$
开头的表示变量。
# root 用户
[root@localhost iotl] # echo root
# 普通用户
[root@localhost iotl] $ echo hello
以下是一些特殊变量:
$_
代表上一个命令的最后一个参数$#
参数个数。$0
当前 shell 名称(zsh or bash)或脚本的名字。$1
传递给该 shell 脚本的第一个参数。$2
传递给该 shell 脚本的第二个参数。$@
表示所有的独立的参数,将各个参数分别加双引号返回。$*
以一对双引号给出参数列表。$$
脚本运行的当前进程 ID 号或所在命令的 PID。$?
显示最后命令的退出状态,0 表示执行成功,其他表示失败。$!
代表最后执行的后台命令的 PIDesc + .
!xxx
^^^
(例如 ^/^/etc^
将 ls /
改为 ls /etc
)!#
!!:3
、^
开始、&
结束(可以缩写为 !^
和 !&
,注意不是 !#
)!#:1
ctrl + t
和 esc + t
,比如交换前后单词(word)有 alt + t
(别和 alt + dot
搞混了)周一被问到这个问题,我真的是一身汗,因为我发现自己真的很长一段时间没有思考过这个问题了。
每天按照固定的步骤处理各种问题,时间久了,工作就成为了一种惯性。
或许很多人都是这样,但我认为这不是一种积极的,上进的人生态度。
生活中充满了变化,我们应该能够适应不断变化的生活,而不是在稳定中消磨掉自己的斗志。
运气好的话,一直稳定下去,运气不好,今后要摔大跟头的。
我刚开始工作时,经理告诉我们,大家在这里相聚,应该要有一点基础的认知,大家都不是杰出人才,只有资历深浅而已,没有谁高人一等。
我认为说的非常实际,现实就是这样,大多数人都是普通人。我也是一个一般水平的开发者。
这么说并不是要打击个人的积极性,这完全不耽误我们大家在工作学习中力争上游。只是说,我们要清楚认知到我们力争的这个“上游”在社会上的一个真实位置。
如果我们没有这个认知,可能对职业生涯的规划产生错误的影响。
好,现在开始说说职业生涯规划的事情。
TODO: 未完待续...
https://lp.jetbrains.com/zh-cn/python-developers-survey-2022/
59% 是全职员工,其中 65% 是程序员,年龄主要是 20~40 岁,大部分人在 10 人以下团队工作。
PS:Linux 相较去年下降了 4%
Python 3 的比例达到 95% 左右,其中:
Name | Percent | Remark |
---|---|---|
venv | 43% | |
virtualenv | 37% | |
conda | 21% | environment.yml |
poetry | 16% | poetry |
pipenv | 14% | pipfile |
virtualenvwrapper | 6% | |
hatch | 3% |
设:
那么:
第 1 期剩余金额:$Q_1 = A(1+R)-X$
第 2 期剩余金额:$Q_2 = Q_1(1+R)-X$
。。。
第 n 期剩余金额:$Q_n = Q_{n-1}(1+R)-X$
推导一下:
$$
\begin{aligned}
Q_2 &= Q_1(1+R)-X \
&= (A(1+R)-X)(1+R)-X \
&= A(1+R)^2-X(1+R)-X \
&= A(1+R)^2-X(1+(1+R))
\end{aligned}
$$
$$
\begin{aligned}
Q_3 &= Q_2(1+R)-X \
&= (A(1+R)^2-X(1+(1+R)))(1+R)-X \
&= A(1+R)^3-X(1+(1+R))(1+R)-X \
&= A(1+R)^3-X(1+R)^2-X(1+R)-X \
&= A(1+R)^3-X(1+(1+R)+(1+R)^2)
\end{aligned}
$$
$$
\begin{aligned}
Q_k &= A(1+R)^k-X(1+(1+R)+ \cdots +(1+R)^{k-1})
\end{aligned}
$$
根据等比数列公式 $ S_n=\frac {a_1(1-q^n)} {1-q} $(q 不等于 1):
$$
\begin{aligned}
1+(1+R)+ \cdots +(1+R)^{k-1} &= \frac {1 \times (1-(1+R)^k)} {1-(1+R)} \
&= \frac {1-(1+R)^k} {1-(1+R)}
\end{aligned}
$$
代入上面的推导方程中:
$$
\begin{aligned}
Q_k &= A(1+R)^k-X \cdot (1+(1+R)+ \cdots +(1+R)^{k-1}) \
&= A(1+R)^k - X \cdot \frac {1-(1+R)^k} {1-(1+R)}
\end{aligned}
$$
k = N 的时候:
$$
\begin{aligned}
Q_n &= A(1+R)^N - X \cdot \frac {1-(1+R)^N} {1-(1+R)} = 0 \
A(1+R)^N &= X \cdot \frac {1-(1+R)^N} {1-(1+R)} \
X &= \frac {A(1+R)^N} {\frac {1-(1+R)^N} {1-(1+R)}} \
&= \frac {A(1+R)^N} {\frac {(1+R)^N-1} {R}} \
&= \frac {AR(1+R)^N} {(1+R)^N-1}
\end{aligned}
$$
也就是:
$$
\begin{aligned}
每期还款金额 = 贷款金额 \times 月利率 \times \frac {(1+月利率)^{还款期数}} {(1+月利率)^{还款期数}-1}
\end{aligned}
$$
设:
每月还款计算:
$$
\begin{aligned}
X_1 &= {\frac A N} + A \cdot R \
X_2 &= {\frac A N} + (A - ({\frac A N})) \cdot R \
X_3 &= {\frac A N} + (A - ({\frac A N}) \cdot 2) \cdot R \
X_N &= {\frac A N} + (A - ({\frac A N}) \cdot (N - 1)) \cdot R
\end{aligned}
$$
也可以视作:
$$
\begin{aligned}
X_1 &= {\frac A N} + {\frac N N} \cdot A \cdot R \
X_2 &= {\frac A N} + {\frac {N - 1} N} \cdot A \cdot R \
X_3 &= {\frac A N} + {\frac {N - 2} N} \cdot A \cdot R \
X_{N-1} &= {\frac A N} + {\frac 2 N} \cdot A \cdot R \
X_N &= {\frac A N} + {\frac 1 N} \cdot A \cdot R
\end{aligned}
$$
总还款金额:
$$
\begin{aligned}
T &= A + A \cdot R \cdot {\frac {(1 + 2 + \cdots + N)} N} & 总还款金额\
&= A + A \cdot R \cdot ({\frac {N + 1} {2}}) \
I &= A \cdot R \cdot ({\frac {N + 1} {2}}) & 总利息 \
\end{aligned}
$$
使用 WSL 的时候 git 用得简直令人难受,每次切换环境(Windows 和 WSL),都需要 Refresh Index,一次就得二十几秒。
最后只好搞一个别名:
# 5.15.90.1-microsoft-standard-WSL2
if [[ "`uname -r`" == *WSL2 ]]; then
SHMODE=wsl
fi
if [[ $SHMODE == "wsl" ]]; then
alias git=git.exe
fi
这是我的本地环境中截出来得一点,其实还做了一些其他得适配,总算能够接受了。
暂时没有读到相关资料。
WSL 已经出来很久了,可是这些性能问题微软还是没有解决,可能是他们不想解决。
程序中有些时候无法避免需要执行系统命令完成一些任务。
比如,我们系统有一个内部的小功能以来 rsync 来做文件同步。
最近发现如果数据量稍微大一点,rsync 就会卡住,不知道为什么。
经过排查之后,发现是 Popen 模块的使用错误导致。
mkdir /tmp/aa /tmp/bb
# Create 10K ~ 1000k Files
for i in {00001..10000}; do
file_size=$((1 + $RANDOM % 100))
dd if=/dev/urandom of=/tmp/aa/file$i.txt bs=10K count=$file_size
done
# du -sh /tmp/aa/
# 4.9G /tmp/aa/
from subprocess import PIPE, STDOUT, Popen
src_dir = '/tmp/aa/'
tgt_dir = '/tmp/bb/'
# --remove-source-files
command = 'rsync -av %s %s' % (src_dir, tgt_dir)
p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, shell=True)
p.wait()
if p.returncode == 0:
LOG.info('rsync success')
else:
LOG.warning('rsync error %d', p.returncode)
数据传输卡在 file0670.txt 了,总传输数据 2.3G。
经过排查之后,确认是我们的编码问题。
代码捕获了标准输出和标准错误,但是我们没有去读这个数据,最后把管道缓冲区占满了,程序就无法继续运行。
Popen 初始化有一个参数 pipesize,如果设置了,则会调用 fcntl.fcntl(p2cwrite, fcntl.F_SETPIPE_SZ, self.pipesize)
设置缓冲区大小。
在 man fcntl 中了解到:
Changing the capacity of a pipe
F_SETPIPE_SZ (int; since Linux 2.6.35)
Change the capacity of the pipe referred to by fd to be at least arg bytes. An unprivileged process can adjust the pipe capacity to any value between the system page size and the limit defined in /proc/sys/fs/pipe-max-size (see proc(5)). Attempts to set the pipe capacity below the page size are silently rounded up to the page size. Attempts by an unprivileged process to set the pipe capacity above the limit in /proc/sys/fs/pipe-max-size yield the error EPERM; a privileged process (CAP_SYS_RESOURCE) can override the limit.
When allocating the buffer for the pipe, the kernel may use a capacity larger than arg, if that is convenient for the implementation. (In the current implementation, the allocation is the next higher power-of-two page-size multiple of the requested size.) The actual capacity (in bytes) that is set is returned as the function result.
Attempting to set the pipe capacity smaller than the amount of buffer space currently used to store data produces the error EBUSY.
Note that because of the way the pages of the pipe buffer are employed when data is written to the pipe, the number of bytes that can be written may be less than the nominal size, depending on the size of the writes.
F_GETPIPE_SZ (void; since Linux 2.6.35)
Return (as the function result) the capacity of the pipe referred to by fd.
又在 man 7 pipe | grep size -C10
中了解到:
Pipe capacity
A pipe has a limited capacity. If the pipe is full, then a write(2) will block or fail, depending on whether the O_NONBLOCK flag is set (see below). Different implementations have different limits for the pipe capacity.
Applications should not rely on a particular capacity: an application should be designed so that a reading process consumes data as soon as it is available, so that a writing process does not remain blocked.
In Linux versions before 2.6.11, the capacity of a pipe was the same as the system page size (e.g., 4096 bytes on i386). Since Linux 2.6.11, the pipe capacity is 16 pages (i.e., 65,536 bytes in a system with a page size of 4096 bytes). Since Linux 2.6.35, the default pipe capacity is 16 pages, but the capacity can be queried and set using the fcntl(2) F_GETPIPE_SZ and F_SETPIPE_SZ operations. See fcntl(2) for more information.
The following ioctl(2) operation, which can be applied to a file descriptor that refers to either end of a pipe, places a count of the number of unread bytes in the pipe in the int buffer pointed to by the final argument of the call:
ioctl(fd, FIONREAD, &nbytes);
The FIONREAD operation is not specified in any standard, but is provided on many implementations.
也就是说:
/proc/sys/fs/pipe-max-size
O_NONBLOCK
)getconf PAGE_SIZE
4096
getconf PAGESIZE
4096
系统的缓冲区大小应该是 16 x 4K = 64K
import subprocess
cmd = ['python', '-c', 'print("a" * 1024 * 64, end=".")']
# cmd = ['python', '-c', 'import time; time.sleep(10);']
print(1)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
print(2)
# stdout, stderr = p.communicate()
# print(repr([stdout, stderr]))
print(3)
p.wait()
子进程执行 python 命令,输出 64KB 不会卡住,增加 1B 就会卡在 wait 那个地方。
解除 communicate 那一行的注释,程序能正常运行。
Popen(command, shell=True)
Popen(command, stdout=subprocess.DEVNULL, shell=True)
# python2 不支持 DEVNULL
devnull = os.open(os.devnull, os.O_RDWR)
Popen(command, stdout=devnull, shell=True)
devnull.close()
p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
# 程序阻塞着,不停从子进程标准输出读数据
p.communicate()
result = subprocess.run(command, shell=True)
print(result.returncode)
print(result.stdout)
print(result.stderr)
今天的科技爱好者周刊上分享了美国风险投资家纳瓦尔在访谈中说的一些话:
"新人企业家的常见错误,就是认为结果是可预测的。如果我长期努力工作,就应该会得到某种成果。"
"实际上,你的成果是不可预测的。你工作多么努力并不重要,重要的是你在做什么、与谁一起工作、以及在哪里工作。 "
"你每天都会看到,那些赚最多钱的人,工作时间根本不长。而淘金者和商铺老板,努力工作一天,赚不到什么钱。 "
"重要的不在于你的努力程度,而在于仔细选择工作、人员和项目。"
"真正有效的工作方式,不是铁人三项或马拉松,比拼谁坚持的时间长,而是短跑,当机会来临的时候冲刺,平时注意健康和休息。"
"你要像狮子一样,看到猎物一跃而起,而不要牛一样,从早到晚劳作。"
给我们的启示:
现实世界是非线性的,也就是说耕耘不一定可以换来收获,我们需要摆脱线性的思维。
这期科技爱好者后面的另一个言论其实也有这样的意思,对开发者的针对性更强:
不要在疲劳的时候写代码。敬业和专业精神,更多地体现在你的纪律性,而不是体现在投入的时间。
-- Robert C. Martin(鲍勃大叔),《Clean Coder》的作者