dynomite简单分析
[toc]
代码 https://github.com/Netflix/dynomite
[toc]
代码 https://github.com/Netflix/dynomite
ppt地址 ,资料都是整理
macos上安装和演示
#安装
brew tap mongodb/brew
brew install mongodb-community@4.4
#拉起
brew services start mongodb-community@4.4
#停止
brew services stop mongodb-community@4.4
# mongo shell
mongo 127.0.0.1:27017
mongo 和sql对应概念 区分
SQL术语/概念 | MongoDB 术语/概念 |
---|---|
database | database |
table | collection |
row | document 或 BSON document |
column | field |
index | index |
table joins (表联接) | $lookup, embedded documents (嵌入式文档) |
primary key 指定任何唯一的列或者列组合作为主键 | primary key 在 MongoDB 中, 主键自动设置为 _id 字段 |
aggregation (如:group by) | aggregation pipeline (聚合管道) 参考:SQL to Aggregation Mapping Chart |
SELECT INTO NEW_TABLE | $out 参考: SQL to Aggregation Mapping Chart |
MERGE INTO TABLE | $merge (从MongoDB 4.2开始可用) 参考:SQL to Aggregation Mapping Chart |
transactions | transactions |
二进制对应关系
MongoDB | MySQL | |
---|---|---|
数据库服务端 | mongod | mysqld |
数据库客户端 | mongo | mysql |
复制日志 | oplog | binlog |
恢复用日志 | journal | redolog |
最新 oplog 时间戳 | snapshot | 状态 |
---|---|---|
t0 | snapshot0 | committed |
t1 | snapshot1 | uncommitted |
t2 | snapshot2 | uncommitted |
t3 | snapshot3 | uncommitted |
我是随便浏览某个时间队列看到的类似的代码
mutable mutex mu_;
condition_variable cv_;
std::thread timer_thread_;
std::atomic<bool> stop_{false};
std::priority_queue<RCReference<TimerEntry>,
std::vector<RCReference<TimerEntry>>,
TimerEntry::TimerEntryCompare>
timers_ TFRT_GUARDED_BY(mu_);
这个GUARDED_BY让人好奇,简单查证了一番,发现是clang的工具
简单说就是clang编译器带的一个多线程的帮手,线程安全注解,原理是拓展 __attribute__
比如 __attribute__(guarded_by(mutex))
这样指明依赖关系,更能方便定位问题
使用的话编译带上 -Wthread-safety-analysis就可以了
没发现gcc有类似的工具。可惜。
另外这些时间队列的实现用的 std::priority_queue 很有意思,都指定了容器参数(因为不是内建的类型,没有实现operator <)
我看rocksdb的timequeue长这样
// Inheriting from priority_queue, so we can access the internal container
class Queue : public std::priority_queue<WorkItem, std::vector<WorkItem>,
std::greater<WorkItem>> {
public:
std::vector<WorkItem>& getContainer() { return this->c; }
直接把容器参数暴漏出来。挺新颖的。这个数据结构设计保留了c就是为了这样暴露吧。
重点关注最小堆(优先队列) 来维护定时器组,以及时间轮
我发现越攒越多了这东西
https://github.com/YongjunHe/corobase
https://hal.inria.fr/file/index/docid/555588/filename/techreport.pdf
oatpp
https://github.com/oatpp/oatpp#api-controller-and-request-mapping
continuable
https://naios.github.io/continuable/
https://chubaofs.github.io/chubaodb/zh-CN/config.html
https://hammertux.github.io/slab-allocator
coroutine
https://luncliff.github.io/coroutine/articles/combining-coroutines-and-pthread_create/
https://www.jianshu.com/u/bb58761c6c04
分布式
https://cloud.tencent.com/developer/article/1015442
network
https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/#
https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/
crdt
https://techbeacon.com/app-dev-testing/how-simplify-distributed-app-development-crdts
https://redislabs.com/redis-enterprise/technology/active-active-geo-distribution/
mongo
https://mongoing.com/archives/6102
https://blog.csdn.net/baijiwei/article/details/78070355
https://blog.csdn.net/baijiwei/article/details/78303200
https://zhuanlan.zhihu.com/c_1047791597869199360
redis延迟分析
https://github.com/moooofly/MarkSomethingDown/blob/master/Redis/Redis%20%E8%AE%BF%E9%97%AE%E5%BB%B6%E8%BF%9F%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90.md
brpc
https://zhuanlan.zhihu.com/p/113427004
hbase结构介绍
https://niyanchun.com/hbase-introduction-extend.html
我比较关系高可用,负载均衡(range server分裂,怎么做的),以及强一致性的实现
大多是怎么用的文档
bluestore
https://zhuanlan.zhihu.com/p/45084771
https://zhuanlan.zhihu.com/p/46362124
making tcp fast
应该有中文文档
https://netdevconf.info/1.2/papers/bbr-netdev-1.2.new.new.pdf
network 101
https://hpbn.co/building-blocks-of-udp/#null-protocol-services
brendan gregg博客分享
抓tcp
http://www.brendangregg.com/blog/2018-03-22/tcp-tracepoints.html
bpf
http://www.brendangregg.com/bpf-performance-tools-book.html
nginx 延迟高吞吐 分享ppt
https://www.nginx.com/blog/optimizing-web-servers-for-high-throughput-and-low-latency/
大页内存和tlb
https://www.mnstory.net/2016/06/30/qemu-hugepages/
还是透明大页
https://www.percona.com/blog/2019/03/06/settling-the-myth-of-transparent-hugepages-for-databases/
针对写放大的kv sifrdb
https://nan01ab.github.io/2019/02/LSM-Trees(2).html
jungle
btree + lsm https://www.usenix.org/system/files/hotstorage19-paper-ahn.pdf
https://www.usenix.org/sites/default/files/conference/protected-files/hotstorage19_slides_ahn.pdf
写个快的json parser
https://chadaustin.me/2017/05/writing-a-really-really-fast-json-parser/
https://chadaustin.me/2013/01/sajson-why-the-parse-tree-is-big-enough/
smfrpchttps://smfrpc.github.io/smf/
years watching https://nwat.xyz/blog/2018/01/15/systems-research-im-watching-for-in-2018/
linux性能调优指南
https://lihz1990.gitbooks.io/transoflptg/content/01.%E7%90%86%E8%A7%A3Linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/1.1.Linux%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86.html
深入理解iostat
https://bean-li.github.io/dive-into-iostat/
为什么就没有个一图剩千言的模板图呢?
十分钟教会看top
https://juejin.im/post/5d590126f265da03db0776b6
为什么就没有个一图剩千言的模板图呢??
tcpdump
https://danielmiessler.com/study/tcpdump/
为什么就没有个一图剩千言的模板图呢???
boltdb介绍
https://lrita.github.io/2017/05/21/boltdb-overview-0/
常见db比较
https://cloud.tencent.com/developer/article/1067439
SILT – A Memory-Efficient, High-Performance Key-Value Store
https://nan01ab.github.io/2018/04/SILT.html
再议silt
http://blog.foool.net/2012/06/%E5%86%8D%E8%AE%AEsilt-a-memory-efficient-high-performance-key-value-store/
taurus db https://nan01ab.github.io/2020/06/Taurus.html
evendb
热点优化
https://www.jianshu.com/p/bc6a5ee0d3db
https://nan01ab.github.io/2020/06/KV-Store(2).html
这个人博客不错
https://www.jianshu.com/u/bb58761c6c04
https://sekwonlee.github.io/files/nvmw20_splitfs.pdf
goto更像汇编一点。
感觉是老生长谈了,常用的goto场景还算处理错误码退出,还列了论文,高德纳的
goto 在c++:可能那个跳过构造函数,漏掉初始化(编译不过/编译告警),注意(setjmp是不是也这样)
循环中提前退出, goto版本更高效 (why??)
switch里用goto c++里没有对应的应用,类似 duff’s device????? 手动展开循环 别手写,让编译器干这个活
用switch就算用goto了
讨论了其他语言中的使用套路 pattern, 没记录 只记录c++相关的
(确实,多路返回的代码让人痛苦,我也写过那种。。 不清晰)
上面讨论的for循环return 低效,可以把for中间判断限制一下,提前break,起到goto效果
返回值复杂,比如variant 使用overload trick,variant的switch
几种例外,能省构造等等。各取所需
封装 不变量 提前设计 即使你用不到 需要c#那种proprity? class封装和struct那种透明的语义就不一样了。还是哪句话,用不到不要过度设计
可能写成函数声明了,ecpp有一条 某些场景没必要非得初始化 你不用的,不要多付出
还有在函数开头声明,这是c的习惯,可能用不到,白白浪费构造 其他语言也是一样,什么时候用什么时候声明 两部初始化,工厂模式
这个演讲者写了个python to c++的程序 pythran,牛逼阿 这个演讲是pythran生成代码有个bug,抓bug的故事,涉及到llvm
演示了一个简单的add代码,性能差很多,编译器问题(怎么定界的这么快)
首先找最小复现代码片 c的实现是向量化的,但是llvm生成的ir没有向量化
用clang的 -Rpass-missed=loop-vectoreze -Rpass-analysis=loop-vectorize
分析得到没有循环优化
然后看llvm的代码,编译debug版本看打印
PHI node检测有问题
看checker的条件
inttoptr <=> ptrtoint 逻辑有问题?
这里我就跟不太上了,llvm不了解。得看一下llvm相关的东西
作者做了个去掉的patch,验证,结果向量化了
深层问题 SROA 已经提bug修了
回到标题,零成本抽象是牛逼的,但是需要编译器来达成这个优化
编译器有没有保证的最低程度优化?没。所以需要了解这个东西,了解优化程度 作者的建议就是看ir结果,对比,跑omit, analyze,以及了解c 的llvm ir。简单
看看llvm的资料
这第三页ppt介绍的也不能说modern吧。rr确实没用过
gdb -> ptrace ->signal
strace 也是用的ptrace
通过 ptrace(PTRACE_CONT) 传出去 断点和单步,传的信号是SIGTRAP,退出是SIGINT
debug register??头一回知道
DWARF info细节
readelf –debug-dump
info signals能看到所有信号的触发
调试 符号优化没了,用-g3 (有没有性能影响?)
堆栈,堆栈指针的优化,CFA,注意,可以利用来导出堆栈 (好像安全不让用?)
libthreaddb 库,用来调试
没细说
malloc free的实现是有隐藏细节的。导致意外的越界会有问题,这两个工具都是用来抓类似问题的
#cppcheck, coverity
一个coverity公司来做介绍。。这个ppt我见过,以前也有来我们公司的 检查dead code 死循环,越界还算挺有效果的
简单介绍了一下原理? 所有的checker都所定义好的,用调用图来算异常节点?
一个场景,把长度为n的字符数组用0填满 如果用c的话,大家肯定都用memset ,这个文章的主题是c++,咱们用c++来写,是这样的
void fill1(char *p, size_t n) {
std::fill(p, p + n, 0);
}
但是,只添加几个字符,就会快29倍,很容易就写出性能比上面代码片更好的代码来,像这样
void fill2(char *p, size_t n) {
std::fill(p, p + n, '\0');
}
作者用的是O2优化
函数 | Bytes/Cycle |
---|---|
fill1 | 1.0 |
fill2 | 29.1 |
这两种写法有啥区别呢 看汇编
fill1是这样的
fill1(char*, unsigned long):
add rsi, rdi
cmp rsi, rdi
je .L1
.L3:
mov BYTE PTR [rdi], 0 ;rdi存0
add rdi, 1 ;rdi ++
cmp rsi, rdi ;比较rdi 和size大小
jne .L3 ;继续循环L3
.L1:
ret
能看出来这段代码就是按位赋值 根据参考链接2方法论,这段代码主要瓶颈就是每个周期要有一次选择分支和保存值 但是fill2可完全不一样
fill2:
fill2(char*, unsigned long):
test rsi,rdi
jne .L8
ret
.L8:
mov rdx, rsi
xor esi, esi
jmp memset ;尾调用memset
这里就不再分析为啥memset要快了。肯定比手写copy要快,有循环展开,且省掉了很多分支选择
但是为什么第一种写法不会直接调用memset呢 作者一开始以为编译器做了手脚,试了O3优化,结果都优化成memset了
但是真正的原因,在std::fill
的实现上
/*
* ...
*
* This function fills a range with copies of the same value. For char
* types filling contiguous areas of memory, this becomes an inline call
* to @c memset or @c wmemset.
*/
template<typename _ForwardIterator, typename _Tp>
inline void fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value)
{
std::__fill_a(std::__niter_base(__first), std::__niter_base(__last), __value);
}
std::fill根据某些traits做了优化,至于是那种场景呢?看std::__fill_a
template<typename _ForwardIterator, typename _Tp>
inline typename
__gnu_cxx::__enable_if<!__is_scalar<_Tp>::__value, void>::__type
__fill_a(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value)
{
for (; __first != __last; ++__first)
*__first = __value;
}
// Specialization: for char types we can use memset.
template<typename _Tp>
inline typename
__gnu_cxx::__enable_if<__is_byte<_Tp>::__value, void>::__type
__fill_a(_Tp* __first, _Tp* __last, const _Tp& __c)
{
const _Tp __tmp = __c;
if (const size_t __len = __last - __first)
__builtin_memset(__first, static_cast<unsigned char>(__tmp), __len);
}
根据这个SFINAE规则能看到,当T是is_byte的时候,才会触发调用memset fill1的写法,T的类型是整型常量,所以没触发优化成memset的版本 等同于
std::fill<char *, int>(p, p + n, 0);
显式的指定函数模板参数,不用编译器推导,也能触发优化,像下面这个fill3
void fill3(char * p, size_t n) {
std::file<char *, char>(p, p + n, 0);
}
按位复制优化成memset是编译器优化器做的。(优化器怎么做的?idiom recognition) gcc O3/ clang O2
对于第二种写法,不传’\0’,也可以使用 static_cast<char>(0)
后面作者给了个标准库 的修改patch value的类型不必非得和指针类型一致就可以了
template<typename _Tp, typename _Tvalue>
inline typename
__gnu_cxx::__enable_if<__is_byte<_Tp>::__value, void>::__type
__fill_a(_Tp* __first, _Tp* __last, const _Tvalue& __c)
{
const _Tvalue __tmp = __c;
if (const size_t __len = __last - __first)
__builtin_memset(__first, static_cast<unsigned char>(__tmp), __len);
}
但是这种改法,对自定义类型就不行
struct conv_counting_int {
int v_;
mutable size_t count_ = 0;
operator char() const {
count_++;
return (char)v_;
}
};
size_t fill5(char *p, size_t n) {
conv_counting_int zero{0};
std::fill(p, p + n, zero);
return zero.count_;
}
返回值是1而不是n,优化反而让结果不对。这种场景,最好让这种自定义类型不合法 比如
template<typename _Tpointer, typename _Tp>
inline typename
__gnu_cxx::__enable_if<__is_byte<_Tpointer>::__value && __is_scalar<_Tp>::__value, void>::__type
__fill_a( _Tpointer* __first, _Tpointer* __last, const _Tp& __value) {
...
只列重点和延伸
管道 | , FIFO, pipe |
ioctl (io control)
页帧分配(Page frame allocation)
页是物理内存或虚拟内存中一组连续的线性地址,Linux内核以页为单位处理内存,页的大小通常是4KB。当一个进程请求一定量的页面时,如果有可用的页面,内核会直接把这些页面分配给这个进程,否则,内核会从其它进程或者页缓存中拿来一部分给这个进程用。内核知道有多少页可用,也知道它们的位置。
伙伴系统(Buddy system)
Linux内核使用名为伙伴系统(Buddy system)的机制维护空闲页,伙伴系统维护空闲页面,并且尝试给发来页面申请的进程分配页面,它还努力保持内存区域是连续的。如果不考虑到零散的小页面可能会导致内存碎片,而且在要分配一个连续的大内存页时将变得很困难,这就可能导致内存使用效率降低和性能下降。下图说明了伙伴系统如何分配内存页。
如果尝试分配内存页失败,就启动回收机制。可以在/proc/buddyinfo文件看到伙伴系统的信息。
页帧回收
如果在进程请求指定数量的内存页时没有可用的内存页,内核就会尝试释放特定的内存页(以前使用过,现在没有使用,并且基于某些原则仍然被标记为活动状态)给新的请求使用。这个过程叫做内存回收。kswapd内核线程和try_to_free_page()内核函数负责页面回收。
kswapd通常在task interruptible状态下休眠,当一个区域中的空闲页低于阈值的时候,它就会被伙伴系统唤醒。它基于最近最少使用原则(LRU,Least Recently Used)在活动页中寻找可回收的页面。最近最少使用的页面被首先释放。它使用活动列表和非活动列表来维护候选页面。kswapd扫描活动列表,检查页面的近期使用情况,近期没有使用的页面被放入非活动列表中。使用vmstat -a命令可以查看有分别有多少内存被认为是活动和非活动状态。
kswapd还要遵循另外一个原则。页面主要有两种用途:页面缓存(page cahe)和进程地址空间(process address space)。页面缓存是指映射到磁盘文件的页面;进程地址空间的页面(又叫做匿名内存,因为不是任何文件的映射,也没有名字)使用来做堆栈使用的,在回收内存时,kswapd更偏向于回收页面缓存。
Page out和swap out:“page out”和“swap out”很容易混淆。“page out”意思是把一些页面(整个地址空间的一部分)交换到swap;”swap out”意味着把所有的地址空间交换到swap。
如果大部分的页面缓存和进程地址空间来自于内存回收,在某些情况下,可能会影响性能。我们可以通过/proc/sys/vm/swappiness文件来控制这个行为
swap
在发生页面回收时,属于进程地址空间的处于非活动列表的候选页面会发生page out。拥有交换空间本身是很正常的事情。在其它操作系统中,swap无非是保证操作系统可以分配超出物理内存大小的空间,但是Linux使用swap的空间的办法更加高效。如图1-12所示,虚拟内存由物理内存和磁盘子系统或者swap分区组成。在Linux中,如果虚拟内存管理器意识到内存页已经分配了,但是已经很久没有使用,它就把内存页移动到swap空间。
像getty这类守护进程随着开机启动,可是却很少使用到,此时,让它腾出宝贵的物理内存,把内存页移动到swap似乎是很有益的,Linux正是这么做的。所以,即使swap空间使用率到了50%也没必要惊慌。因为swap空间不是用来说明内存出现瓶颈,而是体现了Linux的高效性。
ps -o majflt,minflt -p pid
minor fault 在内核中,缺页中断导致的异常叫做page fault。其中,因为filemap映射导致的缺页,或者swap导致的缺页,叫做major fault;匿名映射导致的page fault叫做minor fault。 作者一般这么区分:需要IO加载的是major fault;minor fault则不需要IO加载
用户和组
密码与密码文件/etc/shadow
/etc/group
跳过了,没啥说的。
进程凭证
讲了一大堆关于用户组,权限之类的东西
系统限制和选项
/proc
cat /proc/pid/status
关注env status cwd fd maps mem mounts task
uname
用户空间缓冲区和内核空间缓冲区之间的数据复制,不会直接发起磁盘访问
系统调用越少越好
刷新stdio fflush
fsync 刷盘,包括元数据更新 fdatafync可能会减少磁盘操作的次数
绕过缓存 直接IO O_DIRECT
utime
监控文件事件
inotify可以和epoll串起来
和内核交互,会耗费内核内存,所以有限制
/proc/sys/fs/inotify
signal handler
kill 发送信号
信号掩码,没用过
可重入要考虑
终止signal handler
系统调用期间遇到的信号 -EINTR, 可能系统调用体检结束失败了。
利用这个特性,可以为阻塞调用设置一个定时器
也可以手动重启调用
#define NO_EINTR(stmt) while((stmt) == -1 && errno = EINTR)
这样循环执行忽略EINTR错误,比较不方便,但我感觉直接屏蔽信号更好一些
高级特性
settitimer alarm
alarm会让阻塞的系统调用产生EINTR,也就是超时结束了
但这东西可能有竞态问题,还是用select /poll超时特性更好,还能整合到轮训框架内
nanosleep
创建
终止
fork和stdio缓冲区的问题
书中的例子printf是有’\n’的
输出到终端是行缓冲会直接刷新到终端,输出到文件是块缓冲,不会立即刷到文件,这就复制了两份缓冲区
如果printf没有‘\n’还是会有缓冲区的问题
标准输出是行缓冲,所以遇到“\n”的时候会刷出缓冲区,但对于磁盘这个块设备来说,“\n”并不会引起缓冲区刷出的动作,那是全缓冲
看参考链接 1 2能加深理解
解决方法,
孤儿进程和僵尸进程
文件描述符与exec
FD_CLOSEXEC
为什么system实现要阻塞SIGCHLD,忽略SIGINT和SIGQUIT信号?
errno一个线程一个
创建线程
终止线程
pthread_cancel
更多细节
互斥量mutex -> futex虽然慢也比fcntl信号量这种系统调用要快
注意死锁
condvar 经典问题,为啥用while守着signal
可重入
一次性初始化 pthread_once std::call_once
一个线程安全的singleton是什么样的
find / 2> /dev/null | wc -l &
find命令和wc命令同进程组
find命令和wc命令和当前bash同会话,bash是会话首进程
一个终端也就只有一个会话
前台进程和后台进程组,前台进程就一个or没有
进程控制 nohup SIGHUP
作业控制 jobs
nice值和分配策略
cpu亲和
使用的资源与资源限制
防范
特权?
能力
永远不会成为会话组长,也就永远不会成为控制终端
关掉不用的0 1 2,不要浪费文件描述符
使用共享库的有用工具
版本与命名规则
管道
FIFO aka命名管道
屏蔽SIGPIPE
阻塞IO 非阻塞IO设置?
消息队列/信号量/共享内存
ipc key:整数 IPR_PRIVATE/ftok
ipcs ipcrm 类似ls rm
ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00001f4f 0 Ruby 666 0 0
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
获取ipc对象列表
/proc/sysvipc
cat /proc/sysvipc/msg
key msqid perms cbytes qnum lspid lrpid uid gid cuid cgid stime rtime ctime
8015 0 666 0 0 0 0 3000 3000 3000 3000 0 0 1598235273
查看限制
ipcs -l
------ Messages Limits --------
max queues system wide = 963
max size of message (bytes) = 8192
default max size of queue (bytes) = 16384
------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 0
max total shared memory (kbytes) = 18014398442373116
min seg size (bytes) = 1
------ Semaphore Limits --------
max number of arrays = 32000
max semaphores per array = 32000
max semaphores system wide = 1024000000
max ops per semop call = 500
semaphore max value = 32767
消息队列
无法结合内核本身的文件描述符系统
标识符引用,键(key)复杂度都省
无连接,涉及到资源管理就会很糟糕
避免使用system v ipc消息队列
信号量
进程同步 尤其是共享内存
共享内存
所处位置
全是偏移
mmap munmap
支持映射文件
虚拟内存操作
mprotect mlock mincore
madvise 内存使用建议
消息队列 信号量 共享内存 用fd管理
信号量和共享内存api和system v的api差不多。都挺复杂
加锁与stdio缓冲问题
flock
fcntl
创建fd(socket) 绑定fd(bind) 监听(listen, backlog含义,系统未accept之前的客户端connect占用的fd最大个数)
接受connect( accept, 返回连接的fd,操作这个fd进行通信)
客户端主动发起connect(失败?)
close没啥说的,得结合时序图才有意思
unix domain socket 本机通信用
tcp/ip
数据链路层隐藏
网络层无连接不可靠
传输层
TCP
UDP 注意分段
网络相关
服务设计
inetd
高级主题
netstat -a –inet
tcpdump抓流量
水平触发和边缘处罚 LT ET
select poll是水平触发,信号驱动IO是边缘触发,epoll都支持
水平触发可以任意时刻查看fd的就绪状态,处理不完继续处理
边缘触发一次就得处理完,采取边缘触发的程序设计规则
self-pipe技术。不多数
CR EOF DISCARD
stty命令
https://www.veaxen.com/fork%E5%AF%B9%E8%A1%8C%E7%BC%93%E5%86%B2%E5%8C%BA%E7%9A%84%E5%BD%B1%E5%93%8D.html
https://coolshell.cn/articles/7965.html
mmap实现cp https://stackoverflow.com/questions/27535033/copying-files-using-memory-map
不打日志必后悔,python设置日志非常简单
import logging
logging.basicConfig(
filename='application.log',
level=logging.WARNING,
format= '[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s',
datefmt='%H:%M:%S'
)
logging.error("Some serious error occurred.")
logging.warning('Function you are using is deprecated.')
有了日志logger能加上配置文件扩展(ini/yaml)就更好了(译者注:really?有点浮夸)
version: 1
disable_existing_loggers: true
formatters:
standard:
format: "[%(asctime)s] (pathname)s:%(lineno)d %(levelname)s - %(message)s"
datefmt: '%H:%M:%S'
handlers:
console: # handler which will log into stdout
class: logging.StreamHandler
level: DEBUG
formatter: standard # Use formatter defined above
stream: ext://sys.stdout
file: # handler which will log into file
class: logging.handlers.RotatingFileHandler
level: WARNING
formatter: standard # Use formatter defined above
filename: /tmp/warnings.log
maxBytes: 10485760 # 10MB
backupCount: 10
encoding: utf8
root: # Loggers are organized in hierarchy - this is the root logger config
level: ERROR
handlers: [console, file] # Attaches both handler defined above
loggers: # Defines descendants of root logger
mymodule: # Logger for "mymodule"
level: INFO
handlers: [file] # Will only use "file" handler defined above
propagate: no # Will not propagate logs to "root" logger
import yaml
from logging import config
with open("config.yaml", 'rt') as f:
config_data = yaml.safe_load(f.read())
config.dictConfig(config_data)
logger不直接支持yaml,但是可以把yaml转成dict,然后就可以用了
有了日志还不够,发现了有问题的代码片,想看内部细节调用,直接上日志装饰器,这样要比直接在日志内加要方便的多 ,具体的细节就调节配置文件中的日志级别就可以了
from functools import wraps, partial
import logging
def attach_wrapper(obj, func=None): # Helper function that attaches function as attribute of an object
if func is None:
return partial(attach_wrapper, obj)
setattr(obj, func.__name__, func)
return func
def log(level, message): # Actual decorator
def decorate(func):
logger = logging.getLogger(func.__module__) # Setup logger
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
log_message = f"{func.__name__} - {message}"
@wraps(func)
def wrapper(*args, **kwargs): # Logs the message and before executing the decorated function
logger.log(level, log_message)
return func(*args, **kwargs)
@attach_wrapper(wrapper) # Attaches "set_level" to "wrapper" as attribute
def set_level(new_level): # Function that allows us to set log level
nonlocal level
level = new_level
@attach_wrapper(wrapper) # Attaches "set_message" to "wrapper" as attribute
def set_message(new_message): # Function that allows us to set message
nonlocal log_message
log_message = f"{func.__name__} - {new_message}"
return wrapper
return decorate
# Example Usage
@log(logging.WARN, "example-param")
def somefunc(args):
return args
somefunc("some args")
somefunc.set_level(logging.CRITICAL) # Change log level by accessing internal decorator function
somefunc.set_message("new-message") # Change log message by accessing internal decorator function
somefunc("some args")
老生常谈了,实现__str__也行
class Circle:
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
def __repr__(self):
return f"Rectangle({self.x}, {self.y}, {self.radius})"
...
c = Circle(100, 80, 30)
repr(c)
# Circle(100, 80, 30)
如果实现了自己的dict(译者注:用到dict作为自己的内部成员,可以封装一层dict) 可以实现__missing__ 如果访问key key不存在,就会触发missing调用,帮助捕捉bug
过快崩溃来不及看日志? -i进入细节调用模式 python -i xx.py
如果这个细节还是不够用,可以调用pdb
# crashing_app.py
SOME_VAR = 42
class SomeError(Exception):
pass
def func():
raise SomeError("Something went wrong...")
func()
~ $ python3 -i crashing_app.py
Traceback (most recent call last):
File "crashing_app.py", line 9, in <module>
func()
File "crashing_app.py", line 7, in func
raise SomeError("Something went wrong...")
__main__.SomeError: Something went wrong...
>>> # We are interactive shell
>>> import pdb
>>> pdb.pm() # start Post-Mortem debugger
> .../crashing_app.py(7)func()
-> raise SomeError("Something went wrong...")
(Pdb) # Now we are in debugger and can poke around and run some commands:
(Pdb) p SOME_VAR # Print value of variable
42
(Pdb) l # List surrounding code we are working with
2
3 class SomeError(Exception):
4 pass
5
6 def func():
7 -> raise SomeError("Something went wrong...")
8
9 func()
[EOF]
(Pdb) # Continue debugging... set breakpoints, step through the code, etc.
import traceback
import sys
def func():
try:
raise SomeError("Something went wrong...")
except:
traceback.print_exc(file=sys.stderr)
>>> import func from module
>>> func()
"This is result..."
# Make some changes to "func"
>>> func()
"This is result..." # Outdated result
>>> from importlib import reload; reload(module) # Reload "module" after changes made to "func"
>>> func()
"New result...
不必陷入编辑临时文件,执行,再改,再执行的循环