2013年11月24日星期日

LightDM不能启动

开了20多天的Ubuntu 12.04,重启后LightDM不能开机自动启动了。决定查个究竟。

根据开机后的画面显示,目测是LightDM的Upstart脚本没有执行。看了看/etc/init/lightdm.conf,里面有相关的依赖服务,但我不知道是哪一步出问题了。折腾了几下,包括检查plymouth, dbus服务,删除和重装lightdm包后,lightdm服务竟然手动启动都会失败了。

还好LightDM在/var/log/lightdm/lightdm.log记录了日志,里面有/usr/share/xgreeters/default.desktop文件找不到的错误。在/usr/share/xgreeters/只有unity-greeter.desktop文件。之前正常工作的时候,/etc/lightdm/lightdm.conf里面指定了unity-greeter的,现在没有lightdm.conf这个文件,所以就出了上述的错误。我就做了一个符号链接,再次sudo start lightdm,结果还是没有起来,不过lightdm.log里面有记录启动greeter出错了,并友好地提示greeter的日志记在/var/log/lightdm/x-0-greeter.log里面。在这个文件里面看到具体的错误是没有权限打开/var/lib/lightdm/.Xauthority文件。我记得之前用Apt删除lightdm的时候,显示/var/lib/lightdm目录未能删除,现在我就把/var/lib/lightdm里面的所有文件都删除了,之后lightdm服务就能成功启动了。

从这个故事我得到的体会是,应用设计里面重要的一点,就是要有关键的日志记录,特别是错误记录,这样在事后分析的时候容易找到问题产生的原因。LightDM的日志文件就是比较清楚的,特别是在lightdm.log里面,写入其它日志文件x-0-greeter.log时,也会记下这条,这样看日志的人立刻就知道还有别的日志文件;如果安静地写到另一个日志文件里面而不提示,就会增加用户检索的成本。

2013年9月29日星期日

批量改照片名为拍摄时间

去台北玩照的七百多张照片,是用手机和数码相机照的。摩托罗拉手机(Atrix 2)照的照片命名方式是YYYY-MM-DD_HH-MM-SS_XXX.jpg,例如2013-06-11_00-15-48_100.jpg,最后的100可能是指00:15:48秒的第100毫秒;而佳能相机(EOS 5D)照的照片命名方式是IMG_XXXX.JPG,例如IMG_5845.JPG。这些照片放到一个相册里面,按照名字排序后是混乱的,这让我在几百张照片里面很难快速找到某张照片。我用的相册软件(Picasa)不支持按拍摄时间排序,否则按照拍摄时间排序就可以了。

这么对比,才发现传统的数码相机给照片起名的方式有点蠢。我第一台数码相机是惠普的,前缀是hpim。索尼是DSC,莱卡是L,后面都跟着照片序列号。IMG_5845.JPG这样的名字没有任何有用的信息,而2013-06-11_00-15-48_100.jpg却给出了非常重要的照片元信息。在浏览相册时,看到名字就知道了这张照片是什么时候照的。

于是写了个批量改名的脚本,把佳能相机照的照片都改名为照片拍摄的时间。这样就能和手机照的照片放到一个相册里面,按名字排序,也就自然是按照拍摄时间排序了,浏览时候就很方便了。我觉得数码相机照的照片都可以这么处理,方便浏览,不过这要求数码相机的时间是准确的。

2013年9月25日星期三

MongoDB的高可用

一个应用的数据源是MongoDB,DBA已经配置了多机的Replication Set,但是在客户端程序用Pymongo连接的时候还指定的是一个IP。结果在网络升级的时候的网络瞬断,让MongoDB自动切换了master,导致客户端连接失败了,出现Master has changed的异常。其实早就计划要修改客户端配置,支持RS的,但是一直没有完成,终于掉进这个坑里面了。

有意思的是,如果MongoDB没有做RS,那么网络瞬断只会让服务短暂不可用,网络恢复的时候服务即可恢复;而服务端做了高可用,客户端没有进行相应的配置,可用性反而降低了。看来在服务端和客户端都进行正确配置的情况下才能实现真正的高可用性。

2013年9月17日星期二

性能比较

以下两个Python 3函数的性能差10倍。

def upgrade(url):
    """Upgrade HTTP URL to HTTPS URL."""
    components = urllib.parse.urlsplit(url)
    if components.scheme.lower() == 'http':
        return urllib.parse.urlunsplit(['https'] + list(components[1:5]))
    else:
        return url

def upgrade1(url):
    """Optimized version of upgrade, 10x faster."""
    if url[:5].lower() == 'http:':
        return 'https' + url[4:]
    else:
        return url

timeit分别测试100万次,耗时分别如下:
4.574674844741821
0.46399807929992676
什么时候用库函数,什么时候用基本的函数实现,需要综合考虑开发速度、灵活性和性能等因素。

2013年9月16日星期一

用Python进行HTTP基本认证

curl调用公司Jira需要基本认证(Basic access authentication)的REST API没有问题,但是用Python3的urllib.request按照文档操作,总是返回401

我怀疑是Jira的API需要某些特殊的header,我又用GitHub的API尝试,仍然返回401错误。我看到GitHub的API调用要求必须要有User-Agent,我以为urllib没有设UA,我又尝试设置UA,仍然是401。而实际上urllib是设置了UA的。

Google后发现不是urllib的问题,而是API服务器不遵守RFC的原因,原文在此urllib第一次发送不带验证信息的请求,在401之后会根据服务器响应的WWW-Authenticate头来进行BA。Jira的401是有这个头,可是返回的是Outh的challenge,所以BA也会失败:
WWW-Authenticate: OAuth realm="...snip..."
而GitHub的401完全没有这个头,没有遵守RFC 2616里面对这个头的"Must"要求。

解决办法是请求前就把Authorization头加上,这样直接进行BA,就可以规避服务器不返回正确WWW-Authenticate头的问题。

2013年8月22日星期四

Django的单元测试

运行Django的单元测试:python manage.py test,出错。经查发现某些列的数据是中文,而Django自动创建的测试数据库编码是latin1。需要用数据库的TEST_CHARSET变量来指定测试数据库的编码,设为utf8就可以了。

运行一次完整的测试很慢。用time python manage.py test这里的测试用例大部分都是Django默认的,跑完432个测试总共用了2分7秒,而执行测试的时间只有36秒,大部分时间在创建和销毁数据库了。

Ran 432 tests in 36.068s

FAILED (errors=1, skipped=1)
Destroying test database for alias 'default'...

real 2m7.215s
user 0m25.922s
sys 0m1.028s

如果用SQLite做测试数据库,则完全使用的内存数据库,想必会比MySQL快。按照这个帖子,把测试数据库改为SQLite后,测试速度大大提高,同样的测试只用了18秒,其中16秒都是跑测试的时间。

Ran 432 tests in 15.844s

FAILED (errors=1, skipped=1)
Destroying test database for alias 'default'...

real 0m17.866s
user 0m17.121s
sys 0m0.432s

以后测试就用SQLite了。

2013年8月19日星期一

Python程序的日志顺序混乱

Python 2程序的日志打在文件里面,但是文件中日志出现的顺序和打印日志语句的执行顺序不一致,是混乱的。

研究发现,有的日志是用logging模块打出的,而有的用print语句打出。查看logging模块的源码,对stream输出,每次emit一条record,就会对stream进行flush。而print用的是系统默认的缓存方式,对没有tty的文件的输出,默认是块缓存。

程序启动时候用python -u就可以让stdinstdout不缓存了,应该就不会混乱了。

参考12