netstat -s统计输出的所有字段详细解释

今天工作上碰到一个问题需要知道udp的丢包数据。实际上我不相信能简单地得到udp的丢包精确数据。理由是,网卡负载太高时有些包连网卡都没收到,根本不可能来得及汇报给内核。另外,如果是路由器把udp丢了,那udp的目的端机器当然更不可能感知到有丢包了。

这时,同事说netstat -us (–statistic)可以看到udp的丢包。这里的u选项指的是只展示udp相关的统计,s选项自然表示的是统计了。如果不用u选项,则出显示所有统计数据。下面是我的机器上的输出。

Ip:
    203440255187 total packets received
    0 forwarded
    0 incoming packets discarded
    201612429535 incoming packets delivered
    1064529177 requests sent out
    15 fragments dropped after timeout
    3058122492 reassemblies required
    1230296840 packets reassembled ok
    15 packet reassembles failed
Icmp:
    14869220 ICMP messages received
    3965512 input ICMP message failed.
    ICMP input histogram:
        destination unreachable: 6054246
        timeout in transit: 687
        echo requests: 8570532
        echo replies: 243755
    12913011 ICMP messages sent
    0 ICMP messages failed
    ICMP output histogram:
        destination unreachable: 4097869
        time exceeded: 5
        echo request: 244605
        echo replies: 8570532
IcmpMsg:
        InType0: 243755
        InType3: 6054246
        InType8: 8570532
        InType11: 687
        OutType0: 8570532
        OutType3: 4097869
        OutType8: 244605
        OutType11: 5
Tcp:
    111681768 active connections openings
    4186820 passive connection openings
    24951865 failed connection attempts
    55064041 connection resets received
    275 connections established
    1033901799 segments received
    1776166765 segments send out
    12156205 segments retransmited
    6705 bad segments received.
    106348033 resets sent
Udp:
    198894689917 packets received
    472986510 packets to unknown port received.
    1146976531 packet receive errors
    116750744 packets sent
    110301286 receive buffer errors
    0 send buffer errors
UdpLite:
TcpExt:
    423 invalid SYN cookies received
    693 packets pruned from receive queue because of socket buffer overrun
    19 packets pruned from receive queue
    11309370 TCP sockets finished time wait in fast timer
    106 packets rejects in established connections because of timestamp
    10210477 delayed acks sent
    20811 delayed acks further delayed because of locked socket
    Quick ack mode was activated 8856 times
    17118697 packets directly queued to recvmsg prequeue.
    301717551 bytes directly in process context from backlog
    152118951904 bytes directly received in process context from prequeue
    104771733 packet headers predicted
    15179703 packets header predicted and directly queued to user
    218747377 acknowledgments not containing data payload received
    102637644 predicted acknowledgments
    7293 times recovered from packet loss by selective acknowledgements
    Detected reordering 40 times using FACK
    Detected reordering 27 times using SACK
    Detected reordering 1088 times using time stamp
    476 congestion windows fully recovered without slow start
    5287 congestion windows partially recovered using Hoe heuristic
    236 congestion windows recovered without slow start by DSACK
    151673 congestion windows recovered without slow start after partial ack
    1 timeouts after reno fast retransmit
    4 timeouts after SACK recovery
    10540 timeouts in loss state
    7232 fast retransmits
    649 forward retransmits
    1871 retransmits in slow start
    11612658 other TCP timeouts
    TCPLossProbes: 93185
    TCPLossProbeRecovery: 14667
    2431 packets collapsed in receive queue due to low socket buffer
    8814 DSACKs sent for old packets
    3350 DSACKs received
    1 DSACKs for out of order packets received
    90851 connections reset due to unexpected data
    214 connections reset due to early user close
    352 connections aborted due to timeout
    TCPDSACKIgnoredNoUndo: 1571
    TCPSpuriousRTOs: 7
    TCPSackShifted: 94
    TCPSackMerged: 131
    TCPSackShiftFallback: 21183
    TCPTimeWaitOverflow: 1876775
    TCPRcvCoalesce: 15711184
    TCPOFOQueue: 3194
    TCPChallengeACK: 2337394
    TCPSYNChallenge: 13608
    TCPSpuriousRtxHostQueues: 1982796
IpExt:
    InBcastPkts: 46443933
    InOctets: 44312451521655
    OutOctets: 1915626725817
    InBcastOctets: 6827280595

喂,要是转载文章。麻烦贴一下出处 blog.ykyi.net 采集爬虫把链接也抓走

这里面确实有两个疑似表示udp的丢包数的数据:

Udp:
    1146976531 packet receive errors
    110301286 receive buffer errors

于是,当然首先是看linux man page。结果netstat的man手册里居然没有这些字段的介绍。
跟住,问google。没想到,答案就是netstat -s的输出并没有准确的文档(pooly documented)。
这里有个贴子问了相同的问题 https://www.reddit.com/r/linux/comments/706wsa/detailed_explanation_of_all_netstat_statistics/
简单地说,回贴人告诉他,“别用netstat,而是用nstat和ip tools”“这是个不可能的任务,除非看完成吨源代码”。
blablabla …
事实上,看了google到的一些贴子后,还是大概知道了真相。

    1146976531 packet receive errors

这一句对应关于UDP的一个RFC标准的文档 中定义的字段 udpInErrors。

“The number of received UDP datagrams that could not be
delivered for reasons other than the lack of an application
at the destination port.”
udpInErrors表示操作系统收到的不能被投递的UDP包,不能投递的原因除了没有应用程序开启了对应的端口。

而这一行

    110301286 receive buffer errors

这一行对应 nstat -a -z (下文会再提到nstat)输出中的 UdpRcvbufErrors 字段。我没有找到RFC关于UdpRcvbufErrors字段的定义。
IBM官网上有个网页简单介绍了UdpRcvbufErrors: Number of UDP buffer receive errors. (UDP的缓冲收到错误的次数)。
再结合这篇文章: 为何udp会被丢弃Why do UDP packets get dropped。我非常有信心的认为 UdpRcvbufErrors 表示的是操作系统的内核tcp栈给udp socket分配的缓冲出错(缓冲满)的次数。至于网卡自己的缓冲,和操作系统的缓冲是两回事。网卡的缓冲出错不会被计入这个计数。udp经过的路由的丢包数当然只能够查看对应的路由器的统计数据了。

另外,因为netstat已经被废弃,不建议使用。而是用 nstat 和 ss 这两个新命令代替。
nstat的输出相当于netstat -s的输出。但nstat会输出比netstat -s更多的字段信息,且绝大多数字段名对应到RFC标准中用的字段名。

可任意转载本文,但需要注明出处!!!谢谢

Why do UDP packets get dropped: https://jvns.ca/blog/2016/08/24/find-out-where-youre-dropping-packets/
1: https://tools.ietf.org/html/rfc4113
2: https://www.ibm.com/support/knowledgecenter/STXNRM_3.13.4/coss.doc/deviceapi_response_2.html

Linux系统级/进程级最多打开文件数,FD文件描述符数

如何增加Linux系统最大文件打开数目呢?

查看系统最大可打开文件数

以下命令查看操作系统级最多可打开文件数,fd数目

$ cat /proc/sys/fs/file-max

我用ubuntu 16输出:

573738

怎么调整系统最大可打开文件数

# sysctl -w fs.file-max=1000000

需要用root用户执行以上命令,设最大为一百万。
但在命令行上修改了这个配置,会在下一次操作系统重启后重置为以前的值。要一劳永逸的改变系统最大打开文件数,需要修改 /etc/sysctl.conf 文件。

fs.file-max = 1000000

在/etc/sysctl.conf文件中增加上述一行。

ulimit命令查看/调整进程级最大文件打开数

$ ulimit -Sn
1024

这个命令查看一个ulimit的软极限值(soft limit),本用户起的进程的最大文件打开数的限制。我的ubuntu 16显示进程最多开1024个fd。如果要提高每个进程可同时打开的文件数,需要更改这个值。

$ ulimit -Sn 2048
bash: ulimit: open files: cannot modify limit: Invalid argument

但我想把每个进程可同时打开的文件数增加到以前的两倍时,报错了。这是因为,软极限值(soft limit)不能够越过(Hard Limit)。我看了一下Hard limit,也是1024

$ ulimit -Hn
1024

那没办法罗,需要以root用户登录更改Hard Limit。

怎么修改普通用户的硬极限hard Limit

用root账户登录后,编辑 /etc/security/limits.conf文件,假如普通用户名是kamuszhou。

kamuszhou hard nofile 10000
kamuszhou soft nofile 5000

以上配置修改kamuszhou普通用户的最多打开文件数的hard limit为10000,soft limit为5000。
对于ubuntu 16,如果使用图形桌面,还需要修改 /etc/systemd/system.conf 和 /etc/systemd/user.conf。
加上这么一行:

DefaultLimitNOFILE=10000

如何定制一个python的logging Handler

gevent貌似和logging冲害,如何定制一个日志File Handler

上个月用python的gevent协程库写了一个tcp服务。日志库使用python标准日志库logging。一个月后,发现一个偶发的bug。这个bug发生时,用python的标准日志库自带的FileHandler写的日志会发往socket占用的文件描述符fd。结果就是,客户端收到了本要打印到磁盘上的日志。花了不少时间定位排查这个bug,仍然没有结果。我开始怀疑是gevent协程库和python的标准日志库logging有冲突。协程库会错误的把logging打开的文件描述符fd关闭并分配给新创建的socket,于是日志就打印到socket占用的fd了。

后来,写了一个检测脚本用来监控这个情况发生的概率。该脚本每分钟会检查进程占用的所有fd,一但发现用来打印本地日志的文件fd不见了,就重启服务进程。核心代码如下:

lsof -p $pid | grep -q ${log_file_name}
if [ $? != 0 ] 
then
    # 报警代码 ...
fi

发现bug发生的频率大概是一个星期一次。但这毕竟根本上解决不了问题啊,又找不到bug的原因,怎么办?

那就自定义一个File Handler吧!

决定自定义一个File Handler,这个File Handler工作在另外一个单独的进程,这样无论如何日志用的fd都不会跟主进程的各种socket用的fd冲突了吧。
代码如下,主要用到的技术进程通讯和用python的__getattribute__魔法把截获类实例的方法调用。这样,只需要把旧的代码中的File Handler(我用了TimedRotatingFileHandler)换成自定义的Handler Class,所有其他旧代码都无需改动。

#!/usr/bin/env python3

'''
created by kamuszhou*AT*tencent.com, zausiu*AT*gmail.com http://blog.ykyi.net
Nov 20, 2018
'''

import logging
import logging.handlers
from functools import partial
from multiprocessing import Process, Queue


class MySpecialHandler(logging.StreamHandler):
    def __init__(self, *args, **kargs):
        self._q = Queue()
        self._p = Process(target=MySpecialHandler.__run, args=(self._q,))
        self._p.start()
        self._q.put(('__init__', args, kargs))

    def join(self):
        self._p.join()

    def __run(q):
        handler = None
        while True:
            # op, params = q.get()
            method, args, kwargs = q.get()
            if method == '__init__':
                handler = logging.handlers.TimedRotatingFileHandler(*args, **kwargs)
            else:  # 主进程的日志调用实际上被转到这里
                getattr(handler, method)(*args, **kwargs)

    def __proxy(self, name, *args, **kwargs):
        # 把调用的方法名和方法参数通过Queue传到专门的日志进程。
        self._q.put((name, args, kwargs))
        fun = logging.StreamHandler.__getattribute__(self, name)
        # print('call method: ', name, args, kwargs)
        # 如果是setLevel函数,再调用一次父类的方法
        if name in {'setLevel'}: 
            return fun(*args, **kwargs)

    def __getattribute__(self, name):
        '''
        Hook大法!截获所有方法
        '''
        attr = logging.StreamHandler.__getattribute__(self, name)
        if hasattr(attr, '__call__') and name not in {'join', 'emit'}:
            return partial(MySpecialHandler.__proxy, self, name)
        else:
            return attr


if __name__ == '__main__':
    handler = MySpecialHandler('/data/tmp/ttt.log', when='D', interval=1, backupCount=90)
    handler.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s: %(levelname)s %(message)s')
    handler.setFormatter(formatter)
    logger = logging.getLogger(__name__)
    logger.propagate = False  # Don't propagate the logging to ROOT
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    logger.debug('debug testtttttttt')
    logger.info('info testtttttttttt')
    logger.warn('warn testtttttttttt')
    logger.error('error testtttttttt')
    logger.critical('critical testttttt')
    handler.join()

这里自定义的日志进程类只是一个很粗糙的实现,一但跑起来,只能手动杀进程。反正我的使用场景是一个服务。所以,我也懒得加‘优雅的退出代码’。

另外,这里创建自定义日志Handler的父类是StreamHandler,它还有一个重要的函数是emit。如果想定制这么一个Handler,把日志发给kafka而不需要起进程。则子类需要重写父类的emit方法。比如:

    def emit(self, record):
        msg = self.format(record)  # 日志会以record的形式传入该函数,用format把它格式化
        self.kafka_broker.send(msg, self.topic)

伯努利(Binomial)分布和伯努利试验的区别,负伯努利分布及几何分布,超几何分布

我又重新把概率,统计书拿起来了。这次是真的,要把机器学习(统计学习)学好。显然,学好机器学习并不是一件容易的事情。需要要扎实的数学基础。好在我的数学储备还勉强可以,但远算不上“基础扎实”。所以,我有了一个长期远景规划,重新开始学习吧!

今天看了伯努力分布,负伯努利分布,超几何分布,几何分布这几个知识点。下面我一边写回顾今天学到的,一边写文章。

伯努利分布和伯努利试验的区别

伯努利试验(Binomial Experiment)指的仅仅是”一次试验”,或者成功,或者失败。伯奴利分布是一个常用分布,这是完全不同的两个概念。
每次伯努利试验的成功概率记为p,又把重复很多次伯努利试验称为伯努利过程(Binomial Process)。一个重复了n次伯努利试验的伯努利过程中,成功的试验次数记为X,这个X则是伯努利随机变量。X的概率分布被称为伯努利分布。
伯努利分布:

$$b(x;n,p) = {n \choose x} p^k (1-p)^{ n-k}$$

其中,n表示试验次数,p表示每次独立试验成功的概率,x表示成功次数。

什么是负伯努利分布(Negative Binomial Distribution)

伯努利分布和负伯努利分布的大多数设定都是一样的,都不断重复伯努利试验,要么成功要么失败。
不同点是:伯努利分布的随机变量X,表示的是在前n次伯努利试验中成功了x次;而负伯努利的随机变量x,表示的是要达成k次成功试验,需要试验x次(即第x次试验成功,前x-1次试验成功了k-1次)。伯努利分布限定了试验次数记为n,负伯努利限定了成功次数记为k。
负伯努利分布:

$$b^*(x;k, p) = {{x-1} \choose {k-1}}p^kq^x-k$$

几何分布和超几何分布

再以伯努利分布为讨论的基础,仅更改一处,即把伯努利过程中的放回抽样(with replacement)改成不放回抽样(without replacement),即可得到超几何分布。
以负伯努利分布为讨论的基础,仅更改一处,即k设为1,表示成功一次。即不断重复伯努利试验,直到第一次成功就结束,得到了几何分布。

伯努利分布,负伯努利分布的命名

  • 为什么叫伯努利分布
    因为伯努利展开式$$(q+p)^n$$的n+1个项对应伯努利分布取各个值时的情况。
  • 为什么叫负伯努利分布
    因为展开式$$p^k(1-p)^(-k)$$中的每个项对应$$b^*(x;k,p)$$取x=k, k+1, k+2, …时的情况。

安装MathJax-LaTeX插件,让wordpress显示数学公式

安装MathJax-LaTeX插件,让wordpress显示数学公式

最近看统计学,涉及很多数学公式。于是,想找到一款wordpress的插件可以显示数学公司。

当然,于是就找到了 MathJax-LaTex这个插件。虽然插件名字中有LaTex,但实际上不仅仅可以用LaTex语法描述数学公式,亦可用AsciiMath,以及MathML。
本以为像其它所有wordpress插件一样,安装很简单,很容易就能上手使用。没想到还是花了一些时间。

MathJax-LaTex插件依赖MathJax这个javascript开源工程。原因貌似是MathJax默认用的CDN在大陆被墙了,还是因为浏览器的同源策略被浏览器禁了??我懒得重现排查真正原因了。总之,我用MathJax-LaTex默认配置没有成功,于是我自己手动下载并安装到自己的服务器,运行OK。

  1. 首先到 https://github.com/mathjax/MathJax 下载源代码。熟悉git的同学可以用git下载,亦可用右边的 Clone or Dowload 按钮下载zip包。
  2. 把zip包上传到自己的服务器,找一个目录解压缩。
  3. 打开MathJax-LaTex的配置web页面,如下图如示,去掉Use MathJax CDN Service 的勾,不要用MathJax自己的CDN;然后,在Custom MathJax location选项中填上自己的路径。假如刚才zip包MathJax解压缩到的目录是/tools,那么就填上 /tools/MathJax-2.7.5/MathJax.js
    MathJax-LaTex-Conf
  4. 然后创建一篇新博文,现在可以录入数学公式啦。装逼必备!比如下面的伯努利分布的公式:

$$b(x;n,p) = {n \choose x} p^k (1-p)^{ n-x}$$

要输入上述方程式,在文章开头输入[mathJax] ,这个标志让MathJax-LaTex插件加载显示数学公式相关的javascript代码。然后敲两个美元号 $$,敲入公式 b(x;n,p) = {n \choose k} p^k (1-p)^{ n-k},再敲入两个美元号结尾。即用双美元号$$把公式围起来。亦可用[latex][/latex]这对标签把公式围起来。这对标签这里的公式描述用了 ansciiMath语法. n choose k即一个组合。在网上搜索相关语法参考。

在这个页面可以看到更多用MathJax显示的数学公式的例子,用浏览器的查看源代码功能(ctrl-u)查看使用的语法:
使用LaTex语法: http://blog.ykyi.net/zausiu/MathJax-2.7.5/test/sample.html
使用AsciiMath语法: http://blog.ykyi.net/zausiu/MathJax-2.7.5/test/sample-asciimath.html
使用MathML语法:http://blog.ykyi.net/zausiu/MathJax-2.7.5/test/sample-mml.html

C/C++用万恶的libcurl透过HTTPS发POST+JSON请求

相比python, nodejs这样的脚本开发语言已有非常好用的RESTful开发库,很多C/C++程序在调用RESTful接口时,还在使用非常难使用的libcurl纯C接口。下面是用libcurl透过HTTPS发POST请求的代码代段,http body是json字符串:

    #include <curl/curl.h>

    CURL* curl;
    struct curl_slist* headers;
    curl = curl_easy_init();
    headers_ = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
    curl_easy_setopt(curl, CURLOPT_SSLCERT, cert_file_path.c_str());
    curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
    curl_easy_setopt(curl, CURLOPT_SSLKEY, key_file_path.c_str());
    curl_easy_setopt(curl, CURLOPT_CAINFO, ca_file_path.c_str());
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1L);

    // jv_text存了json字符串
    curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, jv_text.c_str());
    curl_easy_setopt(curl_, CURLOPT_URL, path.c_str());

    CURLcode res = curl_easy_perform(curl_);
    if (res != CURLE_OK)   // 没有成功
    {
        LG_ERR("curl_easy_perform() failed: %s", curl_easy_strerror(res));
    }
    else  // 成功了
    {
    }
    curl_easy_cleanup(curl);