业务服务监控详解

业务服务监控是运维体系中最重要的环节,是保证业务服务质量的关键手段。如何更有效地实现业务服务,是每个运维人员应该思考的问题,不同业务场景需要定制不同的监控策略。Python在监控方面提供了大量的第三方工具,可以帮助我们快速、有效地开发企业级服务监控平台,为我们的业务保驾护航。

一. 文件内容差异对比方法

difflib是一个Python标准库模块,主要功能是实现文件内容的差异对比,且支持生成可读性强的HTML文档,与Linux中的diff命令相似。可以用来比较代码、配置文件等的差异,在版本控制方面是非常有用。

  1. #!/usr/bin/python3
  2. import difflib
  3. txt1 = '''
  4. txt1:
  5. Someone like you
  6. author:Adele
  7. I heard,that your settled down.
  8. That you, found a girl and your married now.
  9. I heard that your dreams came true.
  10. Guess she gave you things, I didn't give to you.
  11. '''
  12. txt2 = '''
  13. txt2:
  14. Someone like you
  15. author:Adele
  16. That you, found a girl and your married now.
  17. Guess she gave you things, I didn't give to you.
  18. I heard that your dreams came true.
  19. '''
  20. txt1s = txt1.splitlines() ##以行进行分割,以便进行对比
  21. txt2s = txt2.splitlines()
  22. D = difflib.Differ() ##创建Differ()对象
  23. diff = D.compare(txt1s,txt2s) ##采用compare方法对字符串进行比较
  24. print('\n'.join(list(diff)))

运行结果:

  1. [root@localhost difflib-test]# ./simple1.py
  2. - txt1:
  3. ? ^
  4. + txt2:
  5. ? ^
  6. - Someone like you
  7. + Someone like you
  8. ? +++++
  9. author:Adele
  10. - I heard,that your settled down.
  11. That you, found a girl and your married now.
  12. + Guess she gave you things, I didn't give to you.
  13. I heard that your dreams came true.
  14. - Guess she gave you things, I didn't give to you.

符号含义说明:

符号 含义
‘-’ 包含在第一个序列行中,但不包含在第二个序列行中
‘+’ 包含在第二个序列行中,但不包含在第一个序列行中
’ ’ 两个序列行一致
‘?’ 标志两个序列行存在增量差异
‘^’ 标志出两个序列行存在的差异字符

生成美观的对比HTML格式文档:

上例不便阅读,可以生成更易读的html文档,方法如下:

  1. #!/usr/bin/python3
  2. import difflib
  3. txt1 = '''
  4. txt1:
  5. Someone like you
  6. author:Adele
  7. I heard,that your settled down.
  8. That you, found a girl and your married now.
  9. I heard that your dreams came true.
  10. Guess she gave you things, I didn't give to you.
  11. '''
  12. txt2 = '''
  13. txt2:
  14. Someone like you
  15. author:Adele
  16. That you, found a girl and your married now.
  17. Guess she gave you things, I didn't give to you.
  18. I heard that your dreams came true.
  19. '''
  20. txt1s = txt1.splitlines()
  21. txt2s = txt2.splitlines()
  22. D = difflib.HtmlDiff()
  23. diff = D.make_file(txt1s,txt2s) ##将比较结果输出为HTML格式
  24. HTML = 'ret.html'
  25. with open(HTML,'w') as f:
  26. f.write(diff)

运行结果后生成ret.html文件,使用浏览器打开可以看到结果:

2. 业务服务监控详解 - 图1

上例仅展示了比较文本变量之间的差异,如果要比较文件,可以将两个文件以readlines()的方法将两个文件读成列表,然后使用同样的方法进行对比。

示例:对比Nginx配置文件差异

当维护多个Nginx配置时,时常会对比不同版本配置文件的差异,使运维人员更加清晰了解不同版本迭代后的更新项,实现思路是读取两个需对比的配置文件,再以换行符作为分隔符,调用difflib.HtmlDiff()生成HTML格式的差异文件。

  1. #!/usr/bin/python3
  2. import difflib
  3. import sys
  4. try:
  5. textfile1 = sys.argv[1] ##第一个配置文件路径参数
  6. textfile2 = sys.argv[2] ##第二个配置文件路径参数
  7. except Exception as e:
  8. print('Error:'+str(e))
  9. print("Usage: {} filename1 filename2".format(sys.argv[0]))
  10. sys.exit()
  11. def readfile(filename): ##定义文件读取分隔函数
  12. try:
  13. fileHandle = open(filename,'r')
  14. text = fileHandle.read().splitlines() ##读取后以行进行分隔
  15. fileHandle.close()
  16. return text
  17. except IOError as error:
  18. print('Read file Error: {}'.format(error))
  19. sys.exit()
  20. if textfile1 == "" or textfile2 == "":
  21. print("Usage: {} filename1 filename2".format(sys.argv[0]))
  22. sys.exit()
  23. text1_lines = readfile(textfile1) ##调用readfile()函数,获取分隔后的字符串
  24. text2_lines = readfile(textfile2)
  25. D = difflib.HtmlDiff() ##创建HtmlDiff()类对象
  26. diff = D.make_file(text1_lines,text2_lines) ##通过make_file方法输出HTML格式的比对结果
  27. HTML = 'ret.html'
  28. with open(HTML,'w') as f:
  29. f.write(diff)

运行以上代码即可:

  1. # python3 simple3.py nginx.conf.v1 nginx.conf.v2

二. 文件与目录差异对比方法

出于对代码审计或校验备份等目的,有时需要检测原始目录与目标目录文件及目录的一致性。Python提供了标准模块filecmp,filecmp可以实现文件,目录,遍历子目录差异对比功能。得到目录之前的差异,以及对比文件内容来确认是否为同一文件。

filecmp提供了三个操作方法:

  • cmp(单文件比较)
  • cmpfiles(多文件对比)
  • dircmp(对比目录)

2.1 单文件对比

  1. filecmp.cmp(f1,f2 [,shallow])

比较文件f1 f2,相同返回True,不同返回False,shallow默认为True,意义为根据文件os.stat()得到的基本信息(最后访问时间,修改时间,状态改变时间等)进行确认是否为同一文件,而不是比较文件内容。当shallow为False时,则os.stat()与文件内容同时校验。

  1. #!/usr/bin/python3
  2. import filecmp
  3. try:
  4. ret = filecmp.cmp('/root/1.txt','/root/1.html')
  5. except Exception as e:
  6. print('error: {}'.format(e))
  7. else:
  8. print('为同一文件' if ret else '不为同一文件')

2.2 多文件对比

  1. filecmp.cmpfiles(dir1,dir2,common[,shallow])

比较dir1与dir2目录中的common定义的文件。并返回一个包含三个列表的元组。

  • 第一个列表:两个目录中匹配的文件
  • 第二个列表:两个目录中不匹配的文件
  • 第三个列表:错误列表,包括不存在的文件,不具备读权限等无法比较的文件清单。
  1. #!/usr/bin/python3
  2. import filecmp
  3. ##定义需要对比的文件名
  4. files = ['f1.txt','f2.txt','f3.txt','f4.txt','f5.txt']
  5. ##equal:文件相同的列表
  6. ##notequal:文件不同的列表
  7. ##error:文件错误列表
  8. equal,notequal,error = filecmp.cmpfiles('/root/cba','/root/nba',files)
  9. print(equal)
  10. print(notequal)
  11. print(error)

创建文件,执行结果如下:

  1. [root@localhost filecmp-test]# ./filecap02.py
  2. ['f1.txt', 'f2.txt']
  3. ['f3.txt']
  4. ['f4.txt', 'f5.txt']

2.3 目录对比

  1. dircmp(a,b[,ignore[,hide]])

通过dircmp类创建一个目录比较对象。

  • 其中a和b是参加比较的目录名;
  • ignore代表文件名忽略的列表,并默认为[‘RCS’,‘CVS’,‘tags’];
  • hide代表隐藏的列表,默认为[os.curdir, os.pardir]。

dircmp类可以获得目录比较的详细信息,如只有在a目录中包括的文件、a与b都存在的子目录、匹配的文件等,同时支持递归。

dircmp提供了三个输出报告的方法:

  • report(),比较当前指定目录中的内容
  • report_partial_closure(),比较当前指定目录及第一级子目录中的内容;
  • report_full_closure(),递归比较所有指定目录的内容。

为输出更加详细的比较结果,dircmp类还提供了以下属性:

  • left:左目录,如类定义中的a
  • right:右目录,如类定义中的b
  • left_list:左目录中的文件及目录列表
  • right_list:右目录中的文件及目录列表
  • common:两边目录共同存在的文件或目录
  • left_only:只在左目录中的文件或目录
  • right_only:只在右目录中的文件或目录
  • common_dirs:两边目录都存在的子目录
  • common_files:两边目录都存在的子文件
  • common_funny:两边目录都存在的子目录(不同目录类型或os.stat()记录的错误)
  • same_files:匹配相同的文件
  • diff_files:不匹配的文件
  • funny_files:两边目录中都存在,但无法比较的文件
  • subdirs:将common_dirs目录名映射到新的dircmp对象,格式为字典类型。

示例:对比dir1和dir2的目录差异

通过调用dircmp()方法实现目录差异对比功能,同时输出目录对比对象所有属性信息。

  1. #!/usr/bin/python
  2. import filecmp
  3. a = '/root/test/filecmp/dir1' ##定义左目录
  4. b = '/root/test/filecmp/dir2' ##定义右目录
  5. dirobj = filecmp.dircmp(a,b,['test.py']) ##目录比较,忽略test.py文件
  6. ##输出对比结果数据报表,详细说明参考filecmp类方法及属性信息
  7. dirobj.report()
  8. dirobj.report_partial_closure()
  9. dirobj.report_full_closure()
  10. print('left_list: {}'.format(str(dirobj.left_list)))
  11. print('right_list: {}'.format(str(dirobj.right_list)))
  12. print('common: {}'.format(str(dirobj.common)))
  13. print('left_only: {}'.format(str(dirobj.left_only)))
  14. print('right_only: {}'.format(str(dirobj.right_only)))
  15. print('common_dirs: {}'.format(str(dirobj.common_dirs)))
  16. print('common_files: {}'.format(str(dirobj.common_files)))
  17. print('common_funny: {}'.format(str(dirobj.common_funny)))
  18. print('same_files: {}'.format(str(dirobj.same_files)))
  19. print('diff_files: {}'.format(str(dirobj.diff_files)))
  20. print('funny_files: {}'.format(str(dirobj.funny_files)))

查看两个目录的树结构:

  1. [root@localhost ~]# tree -C /root/test/filecmp/dir1
  2. /root/test/filecmp/dir1
  3. ├── a
  4. ├── a1
  5. └── b
  6. ├── b1
  7. ├── b2
  8. └── b3
  9. ├── f1
  10. ├── f2
  11. ├── f3
  12. ├── f4
  13. └── test.py
  14. [root@localhost ~]# tree -C /root/test/filecmp/dir2
  15. /root/test/filecmp/dir2
  16. ├── a
  17. ├── a1
  18. └── b
  19. ├── b1
  20. ├── b2
  21. └── b3
  22. ├── aa
  23. └── aa1
  24. ├── f1
  25. ├── f2
  26. ├── f3
  27. ├── f5
  28. └── test.py

运行代码输出结果如下:

  1. [root@localhost filecmp-test]# ./filecap03.py
  2. ----------------report------------------
  3. diff /root/test/filecmp/dir1 /root/test/filecmp/dir2
  4. Only in /root/test/filecmp/dir1 : ['f4']
  5. Only in /root/test/filecmp/dir2 : ['aa', 'f5']
  6. Identical files : ['f1', 'f2', 'f3']
  7. Common subdirectories : ['a']
  8. diff /root/test/filecmp/dir1 /root/test/filecmp/dir2
  9. Only in /root/test/filecmp/dir1 : ['f4']
  10. Only in /root/test/filecmp/dir2 : ['aa', 'f5']
  11. Identical files : ['f1', 'f2', 'f3']
  12. Common subdirectories : ['a']
  13. ---------------report_partial_closure--------------
  14. diff /root/test/filecmp/dir1/a /root/test/filecmp/dir2/a
  15. Identical files : ['a1']
  16. Common subdirectories : ['b']
  17. diff /root/test/filecmp/dir1 /root/test/filecmp/dir2
  18. Only in /root/test/filecmp/dir1 : ['f4']
  19. Only in /root/test/filecmp/dir2 : ['aa', 'f5']
  20. Identical files : ['f1', 'f2', 'f3']
  21. Common subdirectories : ['a']
  22. diff /root/test/filecmp/dir1/a /root/test/filecmp/dir2/a
  23. Identical files : ['a1']
  24. Common subdirectories : ['b']
  25. -------------report_full_closure-------------
  26. diff /root/test/filecmp/dir1/a/b /root/test/filecmp/dir2/a/b
  27. Identical files : ['b1', 'b2', 'b3']
  28. left_list: ['a', 'f1', 'f2', 'f3', 'f4']
  29. right_list: ['a', 'aa', 'f1', 'f2', 'f3', 'f5']
  30. common: ['a', 'f1', 'f2', 'f3']
  31. left_only: ['f4']
  32. right_only: ['aa', 'f5']
  33. common_dirs: ['a']
  34. common_files: ['f1', 'f2', 'f3']
  35. common_funny: []
  36. same_files: ['f1', 'f2', 'f3']
  37. diff_files: []
  38. funny_files: []

三. 发送电子邮件模块 smtplib

电子邮件是最流行的互联网应用之一。在系统管理领域,常常使用邮件来发送告警信息、业务质量报表等,方便运维人员第一时间了解业务的服务状态。通过Python的smtplib模块可以实现邮件的发送功能,相当于模拟了一个smtp客户端,通过与smtp服务器交互实现了发送邮件功能。

3.1 smtplib模块的常用类与方法

SMTP类定义:

  1. smtplib.SMTP([host[,port[,local_hostname[,timeout]]]])

作为SMTP的构造函数,功能是与smtp服务器建立连接,在连接建立成功后,就可以与服务器发送相关请求,比如登录、校验、发送、退出等。

  • host参数为远程smtp主机地址
  • port参数为连接端口,默认25
  • local_hostname的作用是用本地主机的FQDN发送HELO/EHLO(标识用户身份)指令
  • timeout为连接或尝试在多少秒超时

SMTP类具有如下方法:

  • SMTP.connect([host[,port]])方法,连接运城stmp主机方法,host为远程主机地址,port为远程主机smtp端口,默认25,也可以使用host:port形式来表示,例如:SMTP.connect('smtp.163.com','25')
  • SMTP.login(user,password)方法,远程smtp主机的验证方法,参数为用户名与密码,如SMTP.login('python_2020@163.com','sdjkg358')
  • SMTP.sendmail(from_addr,to_addrs,msg[,mail_options,rcpt_options])方法,实现邮件的发送功能,参数依次为发件人、收件人、邮件内容,例如:SMTP.sendmail('python_2020@163.com','demo@domail.com',body),其中body内容定义如下:
    ``` From: python_2020@163.com TO: demo@domail.com Subject: test mail

test mail body

  1. - `SMTP.starttls([keyfile[,certfile]])`方法,启用TLS(安全传输)模式,所有SMTP指令都将加密传输,例如使用gmailsmtp服务时需要启动此项才能正常发送邮件,如SMTP.starttls()。
  2. - `SMTP.quit()`方法,断开smtp服务器的连接。
  3. 测试案例:
  4. ```python
  5. #!/usr/bin/python3
  6. import smtplib
  7. HOST = "smtp.163.com" ##定义smtp主机
  8. SUBJECT = "Test email from python" ##定义邮件主题
  9. TO = "147717473@qq.com" ##定义邮件收件人
  10. FROM = "13811099675@163.com" ##定义邮件发件人
  11. text = "Python rules them all" ##邮件内容
  12. BODY = "\r\n".join(( ##组装sendmail方法的邮件主体内容,各段以“\r\n”进行分隔
  13. "From: %s" % FROM,
  14. "To: %s" % TO,
  15. "Subject: %s" % SUBJECT,
  16. "",
  17. text
  18. ))
  19. server = smtplib.SMTP() ##创建一个SMTP()对象
  20. server.connect(HOST,'25') ##通过connect方法连接smtp主机
  21. server.starttls(), ##启动安全传输模式
  22. server.login('13811099675@163.com','客户端验证码') ##邮箱账号登录校验
  23. server.sendmail(FROM, [TO], BODY) ##邮件发送
  24. server.quit() ##断开smtp连接

执行程序后将收到邮件:

2. 业务服务监控详解 - 图2

以上发送邮件只能以最简单的文本作为邮件主题,有时需要在邮件中发送图片或者附件,下面进行发送图片和附件讲解。

MIME (Multipurpose Internet Mail Extensions多用途互联网邮件扩展) 作为一种新的扩展邮件格式很好的完成了发送丰富媒体的功能。

  1. email.mime.multipart.MIMEMultipart([_subtype[,boundary[,_subparts[,,_params]]]])

作用是生成包含多个部分的邮件体的MIME对象,参数_subtype指定要添加到“Content-type:multipart/subtype”报头的可选的三种子类型,分别为mixed、related、alternative,默认值为mixed。

  • 定义mixed实现构建一个带附件的邮件体
  • 定义related实现构建内嵌资源的邮件体
  • 定义alternative则实现构建纯文本与超文本共存的邮件体

三者关系图如下所示:

2. 业务服务监控详解 - 图3

  1. email.mime.audio.MIMEAudio(_audiodata[,_subtype[,_encoder[,**_params]]])

创建包含音频数据的邮件体,_audiodata包含原始二进制音频数据的字节字符串。

  1. email.mime.image.MIMEImage(_imagedata[,_subtype[,_encoder[,**_params]]])

创建包含图片数据的邮件体,_imagedata是包含原始图片数据的字节字符串。

  1. email.mime.text.MIMEText(_text[,_subtype[,_charset]])

创建包含文本数据的邮件体,_text是包含消息负载的字符串,_subtype指定文本类型,支持plain(默认值)或html类型的字符串。

3.2 发送纯文本邮件

使用方法 : 脚本名 收信邮箱 主题 内容

  1. # ./mail03.py 147717473@qq.com 'Hi,Can you see me.' 'How are you,Fine Thanks.'
  2. send to 147717473@qq.com success!

脚本内容如下:

  1. #!/usr/bin/python3
  2. import smtplib
  3. import sys
  4. from email.mime.text import MIMEText
  5. def send_mail(rec_user,subject,content): ##定义发送邮件函数send_mail
  6. smtp_host = 'smtp.163.com'
  7. smtp_user = '13811099675@163.com'
  8. smtp_pass = '这里填写邮件客户端验证码'
  9. msg = MIMEText(content,_subtype='plain')
  10. msg['Subject'] = subject
  11. msg['From'] = smtp_user
  12. msg['To'] = rec_user
  13. server = smtplib.SMTP()
  14. server.connect(smtp_host)
  15. server.starttls()
  16. server.login(smtp_user,smtp_pass)
  17. server.sendmail(smtp_user,rec_user,msg.as_string())
  18. server.quit()
  19. if __name__ == '__main__':
  20. rec_user = sys.argv[1].strip()
  21. subject = sys.argv[2].strip()
  22. content = sys.argv[3].strip()
  23. try:
  24. send_mail(rec_user,subject,content)
  25. except Exception as e:
  26. print('send error:{}'.format(e))
  27. else:
  28. print('send to {} success!'.format(rec_user))

结果如下:

2. 业务服务监控详解 - 图4

3.3 发送图片和附件的邮件示例

  1. #!/usr/bin/python3
  2. # -*- coding: UTF-8 -*-
  3. '''注意:直接运行此脚本方法:”脚本名 收信邮箱“.'''
  4. import smtplib,sys
  5. # MIMEMultipart可以将多个MIME对象进行封装
  6. from email.mime.multipart import MIMEMultipart
  7. from email.mime.image import MIMEImage
  8. from email.mime.text import MIMEText
  9. #添加图片函数
  10. def addimg(src,imgid):
  11. fp = open(src,'rb')
  12. msgImage = MIMEImage(fp.read())
  13. fp.close()
  14. msgImage.add_header('Content-ID',imgid)
  15. return msgImage
  16. def send_mail(SMTP,SMTP_USER,SMTP_PASS,TO_USER,SUBJECT,IMG,ATTACHMENT):
  17. '''参数分别为:SMTP: smtp服务器地址如 smtp.163.com
  18. SMTP_USER :smtp用户如 hogwartslord@163.com
  19. SMTP_PASS :smtp用户的密码
  20. TO_USER :邮件发送给谁
  21. SUBJECT :邮件主题
  22. IMG :要发送的图片
  23. ATTACHMENT :要发送的附件
  24. '''
  25. msg = MIMEMultipart('mixed')
  26. msgtext = MIMEText("<font color=red> 这是一个测试:<br><img src=\"cid:testimg\"><br>详情见附件.</font>","html","utf-8")
  27. #MIMEMultipart(msg) 加入 MIMEText对象msgtext
  28. msg.attach(msgtext)
  29. #MIMEMultipart(msg) 加入 MIMEImage对象(msgImage),注意文本中的 src="cid:testimg",一定要对应图片的 ‘Content-ID'
  30. msg.attach(addimg(IMG,"testimg"))
  31. #创建附件
  32. attachment = MIMEText(open(ATTACHMENT,'rb').read(),'base64','utf-8')
  33. attachment["Content-Type"] = "application/octet-stream"
  34. #指定邮件中附件的名字
  35. attachment["Content-Disposition"] = 'attachment; filename="{}"'.format(ATTACHMENT)
  36. msg.attach(attachment)
  37. msg['Subject'] = SUBJECT
  38. msg['From'] = SMTP_USER
  39. msg['To'] = TO_USER
  40. server = smtplib.SMTP()
  41. server.connect(SMTP)
  42. server.login(SMTP_USER, SMTP_PASS)
  43. server.sendmail(SMTP_USER, TO_USER, msg.as_string())
  44. server.close()
  45. if __name__ == '__main__':
  46. TO_USER = sys.argv[1]
  47. SMTP = 'smtp.163.com'
  48. # 用户和密码,现在邮箱一般安全性比较好,无法直接使用登录密码,
  49. # 需在要邮箱后台设置第三方登录密码
  50. SMTP_USER = '13811099675@163.com'
  51. SMTP_PASS = '修改成客户端验证码'
  52. SUBJECT = r'邮件测试'
  53. IMG = r'one_punch_man.jpg'
  54. ATTACHMENT = r'mail_test.xlsx'
  55. try:
  56. send_mail(SMTP,SMTP_USER,SMTP_PASS,TO_USER,SUBJECT,IMG,ATTACHMENT)
  57. print('send mail success!')
  58. except Exception as e:
  59. print('send failure .Error: {}'.format(str(e)))

运行程序

  1. # ./mail04.py 147717473@qq.com

结果如下:

2. 业务服务监控详解 - 图5

四. 探测web服务质量方法

pycurl 是一个用C语言写的libcurl Python实现,功能非常强大,支持的协议有 FTP,HTTP,HTTPS,TELNET等,相当于linux中的curl命令。本节通过pycurl探测web服务质量(响应的HTTP状态码、请求延时、HTTP头信息、下载速度等)。

安装

  1. [root@localhost ~]# yum install libcurl-devel libcurl curl -y
  2. [root@localhost ~]# pip3 install pycurl

如果安装后import 提示如下错误:

  1. n [1]: import pycurl
  2. ---------------------------------------------------------------------------
  3. ImportError Traceback (most recent call last)
  4. <ipython-input-1-141165d68a5f> in <module>
  5. ----> 1 import pycurl
  6. ImportError: pycurl: libcurl link-time ssl backend (nss) is different from compile-time ssl backend (none/other)

解决方法:

  1. [root@localhost ~]# pip3 uninstall pycurl
  2. [root@localhost ~]# export PYCURL_SSL_LIBRARY=nss
  3. [root@localhost ~]# pip3 install pycurl

4.1 pycurl模块常用方法

pycurl.Curl()类实现创建一个libcurl包的Curl句柄对象,无参数。Curl对象几个常用方法如下:

  • close()方法,对应libcurl包中的curl_easy_cleanup方法,无参数,实现关闭、回收Curl对象。
  • perform()方法,对应libcurl包中的curl_easy_perform方法,无参数,实现Curl对象请求的提交。
  • setopt(option,value)方法,对应libcurl包中的curl_easy_setopt方法,参数option是通过libcurl的常量来指定的,参数value的值会依赖option,可以是一个字符串、整型、长整型、文件对象、列表或函数等。下面列举常用的常量列表:

    1. c = pycurl.Curl() #创建一个curl对象
    2. c.setopt(pycurl.CONNECTTIMEOUT, 5) #连接的等待时间,设置为0则不等待
    3. c.setopt(pycurl.TIMEOUT,5) #请求超时时间
    4. c.setopt(pycurl.NOPROGRESS, 0) #是否屏蔽下载进度条,非0则屏蔽
    5. c.setopt(pycurl.MAXREDIRS, 5) #指定HTTP重定向的最大数
    6. c.setopt(pycurl.FORBID_REUSE, 1) #完成交互后强制断开连接,不重用
    7. c.setopt(pycurl.FRESH_CONNECT, 1) #强制获取新的连接,即代替缓存中的连接
    8. c.setopt(pycurl.DNS_CACHE_TIMEOUT, 60) #设置保存DNS信息的时间,默认为120秒
    9. c.setopt(pycurl.URL, "http://www.baidu.com") #指定请求的URL
    10. c.setopt(pycurl.USERAGENT,"Mozilla/5.2 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50324)") #配置请求HTTP头的User-Agent
    11. c.setopt(pycurl.HEADERFUNCTION, getheader) #将返回的HTTP HEADER定向到回调函数getheader
    12. c.setopt(pycurl.WRITEFUNCTION, getbody) #将返回的内容定向到回调函数getbody
    13. c.setopt(pycurl.WRITEHEADER, fileobj) #将返回的HTTP HEADER 定向到fileobj文件对象
    14. c.setopt(pycurl.WRITEDATA, fileobj) #将返回的HTML内容定向到fileobj文件对象
  • getinfo(option)方法,对应libcurl包中的curl_easy_getinfo方法,参数option是通过libcurl的常量来指定的。下面列举常用的常量列表:

    1. c = pycurl.Curl() #创建一个curl对象
    2. c.getinfo(pycurl.HTTP_CODE) #返回的HTTP状态码
    3. c.getinfo(pycurl.TOTAL_TIME) #传输结束所消耗的总时间
    4. c.getinfo(pycurl.NAMELOOKUP_TIME) #DNS解析所消耗的时间
    5. c.getinfo(pycurl.CONNECT_TIME) #建立连接所消耗的时间
    6. c.getinfo(pycurl.PRETRANSFER_TIME) #从建立连接到准备传输所消耗的时间
    7. c.getinfo(pycurl.STARTTRANSFER_TIME) #从建立连接到传输开始消耗的时间
    8. c.getinfo(pycurl.REDIRECT_TIME) #重定向所消耗的时间
    9. c.getinfo(pycurl.SIZE_UPLOAD) #上传数据包的大小
    10. c.getinfo(pycurl.SIZE_DOWNLOAD) #下载数据包的大小
    11. c.getinfo(pycurl.SPEED_UPLOAD) #平均上传速度
    12. c.getinfo(pycurl.SPEED_DOWNLOAD) #平均下载速度
    13. c.getinfo(pycurl.HEADER_SIZE) #HTTP头部大小

4.2 实现探测web服务质量

HTTP服务质量一般有两个标准:

  • 一为服务可用性,比如是否处于正常提供服务状态,而不是出现404页面未找到或500页面错误等。
  • 二为服务响应速度,如静态类文件下载时间都要控制在毫秒级,动态CGI资源控制在秒级。

使用python+pycurl来检测网站性能,时间单位默认为秒。

注意:各个阶段时间是从客户端发起URL请求时到某个阶段的时间差,而不是某个阶段开始时间到结束时间差。

  1. total_time = curl_obj.getinfo(pycurl.TOTAL_TIME) #传输结束所消耗的总时间
  2. dns_time = curl_obj.getinfo(pycurl.NAMELOOKUP_TIME) #从发起请求到DNS解析完成所消耗的时间
  3. connect_time = curl_obj.getinfo(pycurl.CONNECT_TIME) #从发起请求到建立连接所消耗的时间
  4. redirect_time = curl_obj.getinfo(pycurl.REDIRECT_TIME) #从发起请求到重定向所消耗的时间
  5. ssl_time = curl_obj.getinfo(pycurl.APPCONNECT_TIME) #从发起请求到SSL建立握手时间
  6. pretrans_time = curl_obj.getinfo(pycurl.PRETRANSFER_TIME) #从发起请求到准备传输所消耗的时间
  7. starttrans_time = curl_obj.getinfo(pycurl.STARTTRANSFER_TIME) #从发起请求到接收第一个字节的时间

pycurl的各个阶段(根据pycurl.*_TIME统计)

依次为:DNS解析–>TCP连接–>重定向(如有)–>SSL握手(如有)–>客户端发送请求–>服务器响应–>数据传输

StringIO模块简介

读写磁盘上的文件速度是非常慢的,为了解决这一问题我们可以将文件直接写在内存中,还不需要向磁盘中写入。注意 sgringIO 只能写入字符串,如果要写入二进制文件,可以使用BytesIO。

在python2中可以直接使用StringIO模块,不过python3中将 io 相关模块全部放在了 io 包中。

  1. In [1]: from io import StringIO
  2. # from io import BytesIO
  3. #创建一个stringio对象
  4. In [2]: f = StringIO()
  5. # f = StringIO('abc 123')
  6. #向stringio对象中写入字符串
  7. In [3]: f.write('abc')
  8. Out[3]: 3
  9. In [4]: f.write('xyz\n')
  10. Out[4]: 4
  11. In [5]: ret = f.getvalue()
  12. In [6]: print(ret)
  13. abcxyz
  14. #清空StringIO对象
  15. In [7]: f.seek(0,0)
  16. Out[7]: 0
  17. In [8]: f.truncate()
  18. Out[8]: 0
  19. In [9]: ret = f.getvalue()
  20. In [10]: print(ret)

pycurl 的一个小示例:

下载一张图片,并打印一些相关信息

  1. #!/usr/bin/python3
  2. import pycurl
  3. from io import BytesIO
  4. ##定义存放响应头和响应体的文件对象
  5. theader = BytesIO()
  6. body_f = open('/root/img.jpg','wb')
  7. R = pycurl.Curl()
  8. R.setopt(pycurl.URL,'https://up.enterdesk.com/edpic/46/62/6d/46626dbde6841f2545b2027027f20e42.jpg')
  9. R.setopt(pycurl.SSL_VERIFYPEER,0) #对于某些采用HTTPS的网站,有时会因为证书验证失败而无法正常访问,pycurl模块提供了取消验证过程的功能。
  10. R.setopt(pycurl.SSL_VERIFYHOST,0)
  11. R.setopt(pycurl.WRITEFUNCTION,body_f.write)
  12. R.setopt(pycurl.HEADERFUNCTION,theader.write)
  13. R.perform()
  14. ##得到一些感兴趣的信息
  15. tmp = R.getinfo(pycurl.TOTAL_TIME)
  16. print(tmp)
  17. tmp = R.getinfo(pycurl.SIZE_DOWNLOAD)
  18. print(tmp)
  19. head = theader.getvalue()
  20. head = head.decode()
  21. print(head)
  22. R.close()

实现一个测试指定URL服务质量的脚本

  1. #!/usr/bin/python3
  2. import sys
  3. import pycurl
  4. from io import BytesIO
  5. class url_detect():
  6. def __init__(self,url):
  7. self.url = url.strip()
  8. if not (self.url.startswith('https://') or self.url.startswith('http://')):
  9. raise ValueError('url {} must start with https or http.'.format(self.url))
  10. #创建一个StringIO对象用于存放响应的内容
  11. strio = BytesIO()
  12. # 创建一个curl对象
  13. self._curl = pycurl.Curl()
  14. self._curl.setopt(pycurl.USERAGENT,'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36')
  15. #禁用CA验证
  16. self._curl.setopt(pycurl.SSL_VERIFYPEER,0)
  17. self._curl.setopt(pycurl.SSL_VERIFYHOST,0)
  18. #连接的等待时间,设置为0则不等待
  19. self._curl.setopt(pycurl.CONNECTTIMEOUT,5)
  20. ##请求超时时间
  21. self._curl.setopt(pycurl.TIMEOUT,5)
  22. ##是否屏蔽下载进度条,非0则屏蔽
  23. self._curl.setopt(pycurl.NOPROGRESS,1)
  24. ##指定HTTP重定向的最大数
  25. self._curl.setopt(pycurl.MAXREDIRS,5)
  26. ##完成交互后强制断开连接,不重用
  27. self._curl.setopt(pycurl.FORBID_REUSE,1)
  28. ##强制获取新的连接,即替代缓存中的连接
  29. self._curl.setopt(pycurl.FRESH_CONNECT,1)
  30. ##设置保存DNS信息的时间,默认为120秒
  31. self._curl.setopt(pycurl.DNS_CACHE_TIMEOUT,1)
  32. ##指定请求的URL
  33. self._curl.setopt(pycurl.URL, self.url)
  34. ##指定响应内容后的回调函数
  35. self._curl.setopt(pycurl.WRITEFUNCTION, strio.write)
  36. ##访问页面
  37. self._curl.perform()
  38. def get_relative_time(self):
  39. #从发起请求到DNS解析完成所消耗的时间
  40. dns_time = self._curl.getinfo(pycurl.NAMELOOKUP_TIME)
  41. #从发起请求到建立连接所消耗的时间
  42. connect_time = self._curl.getinfo(pycurl.CONNECT_TIME)
  43. #从发起请求到重定向所消耗的时间
  44. redirect_time = self._curl.getinfo(pycurl.REDIRECT_TIME)
  45. #从发起请求到SSL建立握手时间
  46. ssl_time = self._curl.getinfo(pycurl.APPCONNECT_TIME)
  47. #从发起请求到准备传输所消耗的时间
  48. pretrans_time = self._curl.getinfo(pycurl.PRETRANSFER_TIME)
  49. #从发起请求到接收到第一个字节的时间
  50. starttrans_time = self._curl.getinfo(pycurl.STARTTRANSFER_TIME)
  51. #传输结束所消耗的总时间
  52. total_time = self._curl.getinfo(pycurl.TOTAL_TIME)
  53. # 下面为每个阶段和发起请求之前的时间差,相对时间
  54. print('发起请求到DNS解析时间:%.3f ms' % (dns_time * 1000))
  55. print('发起请求到TCP连接完成时间:%.3f ms' % (connect_time * 1000))
  56. print('发起请求到跳转完成时间:%.3f ms' % (redirect_time * 1000))
  57. print('发起请求到SSL建立完成时间:%.3f ms' % (ssl_time * 1000))
  58. print('发起请求到客户端发送请求时间:%.3f ms' % (pretrans_time * 1000))
  59. print('发起请求到客户端接受首包时间:%.3f ms' % (starttrans_time * 1000))
  60. print('总时间为:%.3f ms' % (total_time * 1000))
  61. def get_absolute_time(self):
  62. '''获取绝对时间,即每个阶段所使用时间。需要一个pycurl对象。'''
  63. #从发起请求到DNS解析完成所消耗的时间
  64. dns_time = self._curl.getinfo(pycurl.NAMELOOKUP_TIME)
  65. #从发起请求到建立连接所消耗的时间
  66. connect_time = self._curl.getinfo(pycurl.CONNECT_TIME)
  67. #从发起请求到重定向所消耗的时间
  68. redirect_time = self._curl.getinfo(pycurl.REDIRECT_TIME)
  69. #从发起请求到SSL建立握手时间
  70. ssl_time = self._curl.getinfo(pycurl.APPCONNECT_TIME)
  71. #从发起请求到准备传输所消耗的时间
  72. pretrans_time = self._curl.getinfo(pycurl.PRETRANSFER_TIME)
  73. #从发起请求到接收第一个字节的时间
  74. starttrans_time = self._curl.getinfo(pycurl.STARTTRANSFER_TIME)
  75. #传输结束所消耗的总时间
  76. total_time = self._curl.getinfo(pycurl.TOTAL_TIME)
  77. #下面为每个阶段所用时间,绝对时间
  78. transfer_time = total_time - starttrans_time #传输时间
  79. serverreq_time = starttrans_time - pretrans_time #服务器响应时间,包括网络传输时间
  80. if ssl_time == 0:
  81. if redirect_time == 0:
  82. clientper_time = pretrans_time - connect_time #客户端准备发送数据时间
  83. redirect_time = 0
  84. else:
  85. clientper_time = pretrans_time - redirect_time
  86. redirect_time = redirect_time - connect_time
  87. ssl_time = 0
  88. else:
  89. clientper_time = pretrans_time - ssl_time
  90. if redirect_time == 0:
  91. ssl_time = ssl_time - connect_time
  92. redirect_time = 0
  93. else:
  94. ssl_time = ssl_time - redirect_time
  95. redirect_time = redirect_time - connect_time
  96. connect_time = connect_time - dns_time
  97. print('发起请求到DNS解析时间: %.3f ms' % (dns_time * 1000))
  98. print('TCP连接消耗时间:%.3f ms' % (connect_time * 1000))
  99. print('跳转消耗时间:%.3f ms' % (redirect_time * 1000))
  100. print('SSL握手消耗时间:%.3f ms' % (ssl_time * 1000))
  101. print('客户端发送请求准备时间:%.3f ms' % (clientper_time * 1000))
  102. print('服务器处理时间: %.3f ms' % (serverreq_time * 1000))
  103. print('数据传输时间:%.3f ms' % (transfer_time * 1000))
  104. def get_other_info(self):
  105. '''获取其他请求信息,需要一个pycurl对象。'''
  106. ret = {}
  107. ret['http_code'] = self._curl.getinfo(pycurl.HTTP_CODE)
  108. ret['http_sizedown'] = self._curl.getinfo(pycurl.SIZE_DOWNLOAD)
  109. ret['http_sizeheader'] = self._curl.getinfo(pycurl.HEADER_SIZE)
  110. ret['http_speeddown'] = self._curl.getinfo(pycurl.SPEED_DOWNLOAD)
  111. ret['http_effurl'] = self._curl.getinfo(pycurl.EFFECTIVE_URL)
  112. print(ret)
  113. if __name__ == '__main__':
  114. #url = r'https://www.tmall.com/'
  115. #url = r'http://p0.so.qhimgs1.com/t0156dd096f20528974.jpg'
  116. #url = 'http://echarts.baidu.com/dist/echarts.js'
  117. url = 'http://www.sina.com.cn'
  118. py_obj = url_detect(url)
  119. print('='*10 + '相关请求信息' + '='*10)
  120. py_obj.get_other_info()
  121. print('='*10 + '相关请求阶段的耗时' + '='*10)
  122. py_obj.get_absolute_time()
  123. print('='*10 + '相关请求阶段和请求开始之间时差' + '='*10)
  124. py_obj.get_relative_time()