Hertz: A Mobile App Performance Monitoring Solution
Hertz, Meituan Waimai’s mobile‑app performance monitoring framework, automatically detects development anomalies, generates test reports, and streams production metrics—such as FPS, CPU, memory, UI jank, page‑load time, and network traffic—using platform‑specific instrumentation and configurable modes to pinpoint and resolve performance bottlenecks across the app lifecycle.
Performance problems are a major cause of user churn in mobile apps. Issues such as crashes, network errors, slow response, UI jank, high traffic, and battery drain often stem from developers misusing threads, locks, system APIs, programming paradigms, or data structures.
Meituan Waimai summarized common performance pitfalls and, after studying monitoring techniques from WeChat and 360, built a mobile performance monitoring solution called Hertz.
Hertz aims to achieve three functions: detect performance anomalies during development and notify developers; generate performance test reports during testing; and report performance data in production for online issue localization.
Key monitoring metrics include FPS, CPU usage, memory consumption, jank, page load time, and network traffic. Some metrics are easy to collect (FPS, CPU, memory), while others require more effort (jank, page load time, traffic).
Example of obtaining FPS on iOS:
- (void)tick:(CADisplayLink *)link
{
NSTimeInterval deltaTime = link.timestamp - self.lastTime;
self.currentFPS = 1 / deltaTime;
self.lastTime = link.timestamp;
}Example of obtaining memory usage on Android:
public long useSize() {
Runtime runtime = Runtime.getRuntime();
long totalSize = runtime.maxMemory() >> 10;
this.memoryUsage = (runtime.totalMemory() - runtime.freeMemory()) >> 10;
this.memoryUsageRate = this.memoryUsage * 100 / totalSize;
}To collect network traffic, iOS registers a custom NSURLProtocol:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self.client URLProtocolDidFinishLoading:self];
self.data = nil;
if (connection.originalRequest) {
WMNetworkUsageDataInfo *info = [[WMNetworkUsageDataInfo alloc] init];
self.connectionEndTime = [[NSDate date] timeIntervalSince1970];
info.responseSize = self.responseDataLength;
info.requestSize = connection.originalRequest.HTTPBody.length;
info.contentType = [WMNetworkUsageURLProtocol getContentTypeByURL:connection.originalRequest.URL andMIMEType:self.MIMEType];
[[WMNetworkMeter sharedInstance] setLastDataInfo:info];
[[WMNetworkUsageManager sharedManager] recordNetworkUsageDataInfo:info];
}
}Android uses an AspectJ‑based AOP approach to intercept network requests:
@Pointcut("target(java.net.URLConnection) && !within(retrofit.appengine.UrlFetchClient) && !within(okio.Okio) && !within(butterknife.internal.ButterKnifeProcessor) && !within(com.flurry.sdk.hb) && !within(rx.internal.util.unsafe.*) && !within(net.sf.cglib..*) && !within(com.huawei.android..*) && !within(com.sankuai.android.nettraffic..*) && !within(roboguice..*) && !within(com.alipay.sdk..*)")
protected void baseCondition() {}
@Pointcut("call (org.apache.http.HttpResponse org.apache.http.client.HttpClient.execute(org.apache.http.client.methods.HttpUriRequest)) && target(org.apache.http.client.HttpClient) && args(request) && !within(com.sankuai.android.nettraffic.factory..*) && baseClientCondition()")
void httpClientExecute(HttpUriRequest request) {}Traffic is further classified by domain regexes. Example for iOS:
- (NSString *) regApiHost {
return _regApiHost ? _regApiHost : @"^(.*\\.)?(meituan\\.com|maoyan\\.com|dianping\\.com|kuxun\\.cn)$";
}
- (NSString *) regResHost {
return _regResHost ? _regResHost : @"^(.*\\.)?(meituan\\.net|dpfile\\.com)$";
}
- (NSString *) regWebHost {
return _regWebHost ? _regWebHost : @"^(.*\\.)?(meituan\\.com|maoyan\\.com|dianping\\.com|kuxun\\.cn|meituan\\.net|dpfile\\.com)$";
}Page load time measurement abstracts the loading process into three timestamps: T1 (initial UI appearance), T2 (network request), T3 (data rendering). Configuration maps pages to APIs, e.g.:
[
{"page": "MainActivity", "api": ["/poi/filter", "/home/head", "/home/rcmdboard"]},
{"page": "RestaurantActivity", "api": ["/poi/food"]}
]Android detects UI jank by overriding dispatchDraw in the root view:
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (!mIsComplete) {
mIsComplete = mCallback.onDrawEnd(this, mKey);
}
}iOS uses a tag‑based approach with CADisplayLink to determine when a page has finished rendering:
- (void)tick:(CADisplayLink *)link
{
[_currentTrackRecordArray enumerateObjectsUsingBlock:^(WMHertzPageTrackRecord * _Nonnull record, NSUInteger idx, BOOL * _Nonnull stop) {
if ([self findTag:record.configItem.tag inViewHierarchy:record.rootView]) {
[self endPageRenderEvent:record];
}
}];
}Jank detection relies on measuring main‑thread loop execution time. When the loop exceeds a threshold, a jank event is recorded. Example detection logic:
Runnable loopRunnable = new Runnable() {
@Override
public void run() {
if (mStartedDetecting && !isCatched) {
nowLaggyCount++;
if (nowLaggyCount >= N) {
blockHandler.onBlockEvent();
isCatched = true;
...
}
}
}
};
public void onMainLoopFinish(){
if(isCatched){
blockHandler.onBlockFinishEvent(loopStartTime,loopEndTime);
}
resetStatus();
...
}When a jank is detected, Hertz captures the call stack and logs to help locate the problematic code. Stack classification follows a simple rule‑based approach, matching class name prefixes.
Hertz is designed for extensibility and ease of use. Its architecture consists of three layers: an interface layer exposing minimal APIs, a business layer handling page speed testing, jank detection, and data collection, and a data‑adapter layer that formats data for various output channels.
Three runtime modes are provided: development, testing, and production. Each mode presets parameters such as sampling frequency, jank thresholds, and reporting switches. Example initialization on Android:
final HertzConfiguration configuration = new HertzConfiguration.Builder(this)
.mode(HertzMode.HERTZ_MODE_DEBUG)
.appId(APP_ID)
.unionId(UNION_ID)
.build();
Hertz.getInstance().init(configuration);In production, collected metrics are uploaded to Meituan’s internal CAT monitoring system, enabling aggregation, filtering by version, OS, or device, and facilitating root‑cause analysis.
Since its integration, Hertz has helped Meituan Waimai discover and locate performance issues across development, testing, and production stages, covering FPS, CPU, memory, jank, page load time, and network traffic. Future work includes adding power consumption, cold‑start time, exception monitoring, adaptive thresholds, and richer visualization tools.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Meituan Technology Team
Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
