修复 Breakpad 不能启用 Full Minidump

发布分支为 chrome-58 的 google-breakpad 存在无法启用 full minidump 的问题,表现症状是,一旦启用 MiniDumpWithFullMemory 标志,则输出的 dump 文件为 0 字节,但是整个 dump 生成流程没有任何其他异常,相关返回值甚至是 true

经过一个上午的跟踪调试,发现问题出现在,breakpad 针对 MiniDumpWithFullMemory 做了特殊处理(会生成一个普通的 minidump 和一个 full dump,后者的文件名多了一个full 后缀),但是这个版本(chrome-58)里却忘了调用生成 full dump 的生成函数…..

解决方法:对 master 和 ) 分支的 CrashGenerationServer::GenerateDumpchrome-58 分支做一个 diff,将缺的代码加回来…

缺失的代码就是下面这段…

1
2
3
4
5
6
7
8
9
// If the client requests a full memory dump, we will write a normal mini
// dump and a full memory dump. Both dump files use the same uuid as file
// name prefix.
if (client.dump_type() & MiniDumpWithFullMemory) {
std::wstring full_dump_path;
if (!dump_generator.GenerateFullDumpFile(&full_dump_path)) {
return false;
}
}

另外,开启了 full minidump 模式之后,完整的 dump 体积会暴增到 200MB 上下,要做好心理准备。

用 FFMpeg 生成视频缩略图

除了可以使用 ffmpeg 压制视频外,还能利用 ffmpeg 生成某个视频的缩略图。

利用命令行:

1
ffmpeg.exe -skip_frame nokey -i "some_video.mp4" -vsync 0 -vframes 9 -c:v mjpeg "output_dir\thumb_%d.jpg"

就可以在 output_dir 这个目录下为视频文件 some_video.mp4 从头开始生成最多不超过9张关键帧缩略图,文件名分别是 thumb_1.jpg ~ thumb_9.jpg

这个默认策略适合长度较短的视频(比如半分钟或一分钟内),对于长视频,很可能跑完9张关键帧缩略图,正片都还没开始…

所以,这个时候的生成策略应该考虑能够尽可能的均匀分布于视频内容。

首先利用这个 post 里提到的做法,用 ffprobe.exe 获得视频的长度,记为 T(单位,秒)。

T 均分为 10(9+1)个片段,取 $ t_i = \frac{T}{10} * i, i = 1, 2, \cdots 9 $,这么做的原因是避免取到开头和结尾。

最后针对每个 $ t_i $,运行 ffmpeg

1
ffmpeg.exe -ss t_i -skip_frame nokey -i "some_video.mp4" -vsync 0 -vframes 1 -c:v mjpeg "output_dir\thumb_i.jpg"

唯一麻烦的地方在于输出文件名序列需要我们自己指定。

Monthly Read Posts in Apr 2017

The Cost of Conditional Moves and Branches

Conditional moves 指令并不一定能提升性能,有时候甚至会导致性能衰减。

Post 里直接援引了 StackOverflow 上的这个案例


Cpu Performance Counters on Windows

The ETW(Event Tracing for Windows) now supports CPU performance counters.


GitHub Restful API References
RESTful API最佳实践
撰写安全合格的REST API
跟着 Github 学习 Restful HTTP API 设计
RESTful API 编写指南

如何编写正确的 Restful APIs 以及,一个优秀的 Restful APIs 的范例


Tech Talk: How to Speed up a Python Program 114,000 times

讲道理这个 tech talk 我跳着看了一部分就放弃了,原因很简单,如果真的需要 talk 里提到的那些方法来克服 python 的性能缺点,我选择使用 C++ 重写…

使用 ffmpeg 压制视频

帮主站重写完投稿工具的上传模块后,Neo 和我说,我们这期版本还是得带上视频压制功能…

这是我第一次知道原来 ffmpeg 还可以压制视频。因为重构的缘故,老版本的代码完全不能用(就算不考虑换上层 UI 框架的事儿,老版本那个代码质量…),所以只能抄一下他们的压制相关的驱动参数,自己从头把功能实现一遍。

花了差不多刚好一周的时间提供了一个底层压制模块后,我发现,因为产品需求的因素,实现这部分功能居然可以很好的做为熟悉一个语言涉及操作层面的 roadmap,整理一下 feature list,算算差不多至少要能够提供:

  • 运行子进程,并且能够通过 IPC 读取并解析子进程的 stdout 和 stderr 数据;通常情况下,这意味着需要熟悉 Windows 的 Pipe
  • 压制首先涉及到利用 ffprobe.exe 检测视频的 meta-info,符合压制条件并且用户确认后利用 meta-info 作为压制的部分输入信息;中间涉及到几个线程间的交互
  • 因为压制功能和视频上传在逻辑上具有先后顺序,并且都需要有专门的 worker thread,因此他们可以共用一个线程池;而支持多任务并行操作,又要求线程池的调度策略要足够“及时”(事实上,这个要求有一部分锅来自 chromium net 的 URLFetcher,which requires 网络操作运行的线程在 URLFetcher context 生命周期内是唯一且固定的,因此会出现一个上传任务只要不结束,就会一直霸占一个线程的情况;另有一部分锅来自 chromium base 的 scoped_reptr,居然没有考虑对外暴露自己 ref-count 信息,无形中加大了实现线程池的困难)

因此在项目提测结束,基本验收通过之后,我想籍由这个功能,重新回忆一下 C# 和 WPF (噫,怎么老是再回忆这个…),所以花了一个周末,就有了 SimpleVideoEncoder

但是在实现过程中我发现,纵使有 async/await,它也不是银弹,也有他覆盖不了的 case;而我对 C#/.NET 的多线程模型实在是完全不懂,导致某些细节完全没法下手(只能说某些case 下,CSP 真是好用到想哭,还好 WPF 的 UI-thread 提供了一个 Dispatcher 支持了对 UI 线程的 CSP;再将需求简化到 Demo 程度后,勉强实现了出来。

上层界面基于 WPF,但是没有走 MVVM。

实话说,经过一年半(距离上次写 EasyKeeper),自己主导过两个不大不小的项目后,对于为什么需要有 MVC/MVP/MVVM 有了一些更实际的想法,并且也能理解有时候过分追求这些反而是一种坑。

考虑到目前应该是没法公开为投稿工具写的那部分代码(压制模块算起来大概有 1K+ lines of code),所以这个 SimpleVideoEncoder 的意义也大大折扣,基本只能作为一个 ffmpeg 压制功能的简单展示…

不过最后还是要赞一下 C# 对 Pipe IPC 的封装,几行代码做了需要几十行 C++ 代码的事情;并且基于 event 的通知机制比起传统的 observer,更能准确的体现 OOP 中对象级别消息通信的内涵。

Monthly Read Posts in Mar 2017

Generate lambdas for clarity and performance

Generate a class of lambdas with auto-return-type deduction.

唯一比较遗憾的是特性需要 C++ 14 的支持,C++ 11 里估计需要用 std::function<> 来做 workaround


Appending to a File from Multiple Processes

经验问题,写客户端的 logger 轮子时应该会经常需要考虑到这个问题。

文章针对的是 POSIX 系统,不过不同系统在一些具体策略/细节上也有区别。

另,Windows 上这个场景有直接的 API 调用保证。

不过有点比较奇怪,chromium 的 base::logging 只提供了 Windows 下的多进程无锁 appending,POSIX/Mach 下依然用的是线程同步锁和全局锁,可能考虑的原因是实现起来坑比较大?


OAuth 2.0 Protocol

  1. oauth2 authorization
  2. principle-of-oauth2
  3. oauth_2_0

第二篇文章基本可以看作第一篇的翻译,但是很多地方都没有翻译出来,所以推荐主要看第一篇文章,把第二、三篇作为补充材料


Inheritance Is The Base Class of Evil

C9 上的一个 Tech Talk,speaker 是 Adobe 的技术大佬,但是我觉得有标题党之嫌。

Talk 中展现出来的哲学观挺有意思的,比如那句 There is no polymorphic type, just polymorphic use of types


Learning STL Multithreading

虽然标题是 STL Multithreading,但是里面的核心内容基本都是原理性的,通用的。

对于 mutex/condition-variable 这类比较高级的 synchronization primitives 和低级的 atomic variables 均有涉及,并且也给了实践建议。

话说右边那个胖胖的小哥的口音听起来真的是容易懂…

另外有个挺有意思的细节,快到最后的时候胖小哥提到了 asyncfuture,他说希望大家能优先考虑这俩玩意儿,但是说了几句话又欲言又止,最后无奈地说 we will discuss these later。。。估计他想了想,就目前标准给的设施和实现情况,要做到 task-based paralleilism,那坑大的都可以把自己埋进去了。

在 C++ 11 中实现 apply

Why apply matters

假设我们要实现一个和具体业务无关的 HTTP(S) 网络 (Restful)API 请求设施 RequestConnection,我们至少要提供两个实现:

  • 客户代码的请求结束时的回调 callback/handler
  • 针对具体业务的 response parser;因为 HTTP 是文本协议,无论 response data 基于何种格式(例如 JSON),我们都要针对具体的业务逻辑场景进行解析

假设我们希望请求的 callback 足够友好,直接将 client 需要的数据通过函数原型暴露出来,例如

1
void OnCheckUpdateComplete(bool succeeded, int last_build_no);

那么我们的 reponse parser 也要相应的能够返回符合 callback signature 的数据,在 C++ 中,我们可以用 std::tuple 来保存 parsed result:

1
std::tuple<bool, int> ParseCheckUpdateResponse(const std::string& data);

但是很不巧的,RequestConnection 是业务无关的,而业务相关的 callback/response-parser 则是通过依赖注入的方式(例如通过模板参数)进入 RequestConnection;这意味着我们需要一个通用的方法能够在编译期完成如下事情:

1
2
3
// Presume magic function Apply
std::string response_data;
Apply(ResponseHandler, ResponseParser(response_data));

神奇函数 Apply() 能够调用一个函数,并且将一个 std::tuple 里的元素按照顺序转换为对应的实参。

简言之,这就是 C++ 17 要引入的 std::apply 所做的事情。

但是如果我们的 codebase 是 C++ 11的,那又如何呢?

How to implement apply in C++ 11

在 C++ 11 里实现 apply(),我们要做两步:

  1. 实现 index-sequence (C++ 14 开始标准库附带了这个实现)
  2. 实现 apply
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <tuple>
#include <type_traits>
template<size_t... Ints>
struct index_sequence {
using type = index_sequence;
using value_type = size_t;
static std::size_t size()
{
return sizeof...(Ints);
}
};
template<class Sequence1, class Sequence2>
struct merge_and_renumber;
template<size_t... I1, size_t... I2>
struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>>
: index_sequence<I1..., (sizeof...(I1) + I2)...>
{};
template<size_t N>
struct make_index_sequence
: merge_and_renumber<typename make_index_sequence<N / 2>::type,
typename make_index_sequence<N - N / 2>::type>
{};
template<>
struct make_index_sequence<0> : index_sequence<>
{};
template<>
struct make_index_sequence<1> : index_sequence<0>
{};
template<typename Func, typename Tuple, std::size_t... index>
auto apply_helper(Func&& func, Tuple&& tuple, index_sequence<index...>) ->
decltype(func(std::get<index>(std::forward<Tuple>(tuple))...))
{
return func(std::get<index>(std::forward<Tuple>(tuple))...);
}
template<typename Func, typename Tuple>
auto apply(Func&& func, Tuple&& tuple) ->
decltype(apply_helper(std::forward<Func>(func),
std::forward<Tuple>(tuple),
make_index_sequence<std::tuple_size<typename std::decay<Tuple>::type>::value>{}))
{
return apply_helper(std::forward<Func>(func),
std::forward<Tuple>(tuple),
make_index_sequence<std::tuple_size<typename std::decay<Tuple>::type>::value>{});
}

以上代码就是我在某直播姬的网络框架里所用的实现;根据实际是用来看,quite awesome

Monthly Read Posts in Feb 2017

RESTful架构风格下的4大常见安全问题

  • Don’t forget to check correlations between resources in request URL
  • Some rare used HTTP headers might be helpful
  • Restrict API request frequency

这篇略水,某些常见的问题反而都没有提到,例如大木老师遇到的这个问题


Safe Bitfields in C++

Main observation: Use union to implement/simulate bitfields.

Each bitfield is implemented by using a single union member with mask and bit-length being set up initially.

非常 stunning 的一个实现,但是很可惜,根据 comments 这个实现仍然是 undefined behavior,because it call member functions on inactive union members.