iOS性能數(shù)據(jù)采集機(jī)制匯總

1. 概述

iOS 客戶端的應(yīng)用性能數(shù)據(jù)監(jiān)控一般包括如下指標(biāo)

  • 卡頓監(jiān)測(cè)
  • FPS 采集
  • CPU 采集
  • Memory 采集
  • 冷啟動(dòng)測(cè)速
  • 流量監(jiān)控

而我們關(guān)注監(jiān)控技術(shù)的目的枕屉,通常是為了開(kāi)發(fā)一套相關(guān)的監(jiān)控 SDK 或者功能,需要了解各個(gè)監(jiān)控指標(biāo)的監(jiān)控手段和原理;因此這里將記錄各個(gè)監(jiān)控指標(biāo)的基本原理和機(jī)制猫缭,不過(guò)多涉及具體的代碼實(shí)現(xiàn),大部分監(jiān)控代碼能玩的花樣不多捣染,延展出去的監(jiān)控?cái)?shù)據(jù)展示冤议、持久化與上報(bào)機(jī)制又遠(yuǎn)遠(yuǎn)比監(jiān)控本身復(fù)雜旬迹,此處就不贅述。

2. 卡頓檢測(cè)

卡頓監(jiān)控需要利用信號(hào)量求类,對(duì)主線程 Runloop 加入 observer 進(jìn)行監(jiān)聽(tīng)奔垦,通過(guò)信號(hào)量等待機(jī)制,檢測(cè)出主線程 Runloop 卡頓情況尸疆,進(jìn)行上報(bào)椿猎。

2.1 加入監(jiān)聽(tīng)

    CFRunLoopActivity observedActivities = kCFRunLoopBeforeSources | kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting;
    _runloopObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, observedActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf.semaphore != NULL) {
            dispatch_semaphore_signal(strongSelf.semaphore);
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetMain(), _runloopObserver, kCFRunLoopCommonModes);
    CFRelease(_runloopObserver);

此處主要監(jiān)聽(tīng) Runloop 的三個(gè) activity惶岭,beforeSources,beforeWaiting 和 afterWaiting犯眠,原因是根據(jù) Runloop 內(nèi)部執(zhí)行順序按灶,具體見(jiàn)下圖

Runloop執(zhí)行原理

Runloop 執(zhí)行 Source0,Source1筐咧,MainQueue鸯旁,Timer 和 Block 的階段均在這三個(gè)時(shí)機(jī)之間,因此對(duì)三個(gè)時(shí)機(jī)插點(diǎn)量蕊,就可以監(jiān)控出執(zhí)行卡頓的問(wèn)題铺罢。

2.2 信號(hào)量等待機(jī)制

    while (!self.cancelled) {
        long status = dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, self.threshold * NSEC_PER_MSEC));
        if (status != 0) {
            if (self.callback) {
                self.callback();
            }
            dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        }
    }

此處利用 dispatch_semaphore_wait 函數(shù),在一段時(shí)間內(nèi)(一般是3s-5s)等待信號(hào)量残炮,假如 Runloop 運(yùn)行正常則在上面三個(gè)時(shí)機(jī)點(diǎn)均會(huì)執(zhí)行信號(hào)量釋放操作韭赘,因此如果出現(xiàn)卡頓不能如期釋放信號(hào)量,則調(diào)用 callback 進(jìn)行卡頓處理和上報(bào)势就。

dispatch_semaphore_wait 返回為 0 代表信號(hào)量獲取成功泉瞻,否則未能獲取到信號(hào)量,此時(shí)將永久等待信號(hào)量苞冯,以確保不再重復(fù)上報(bào)卡頓袖牙。

當(dāng)然卡頓上報(bào)也可以加入次數(shù)限制,例如卡頓發(fā)生 3 次就不再上報(bào)等邏輯舅锄。

3. FPS 采集

3.1 基礎(chǔ)原理及步驟

FPS 采集完全依賴于 iOS 提供的 CADisplayLink 類鞭达,它提供了屏幕刷新時(shí)機(jī),并支持自定義回調(diào)巧娱,從而獲知到屏幕刷新的時(shí)間戳,依據(jù)如下公式就可以得到應(yīng)用的 FPS 信息烘贴。

FPS = FrameCount/Duration

因此對(duì)于 FPS 監(jiān)控的基本步驟如下

  • 初始化一個(gè) CADisplayLink
[CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]
  • 回調(diào)中記錄當(dāng)前時(shí)間戳禁添,記錄與上一幀時(shí)間戳間隔,記錄瞬時(shí) FPS桨踪,甚至可以記錄自某一時(shí)刻開(kāi)始到當(dāng)前老翘,總的幀數(shù)和總時(shí)間間隔,從而計(jì)算出平均 FPS
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    currentTimestamp = displayLink.timestamp;
    instantDuration = currentTimestamp - lastTimestamp;
    instantFPS = round(1.0/instantDuration);
    totalFrameCount++;
    totalDuration += instantDuration;
    avgFPS = totalFrameCount/totalDuration;
}

但是更進(jìn)一步锻离,除了關(guān)注整體 FPS铺峭,我們還可以考慮關(guān)注特定 VC,特定 ScrollView汽纠,自定義時(shí)機(jī)的 FPS卫键。

3.2 UIViewController 的 FPS

一個(gè) VC 的 FPS 統(tǒng)計(jì)與基礎(chǔ) FPS 統(tǒng)計(jì)無(wú)異,唯一要關(guān)注的是如何確定統(tǒng)計(jì)時(shí)機(jī)虱朵,一般選取如下時(shí)機(jī)

  • viewDidAppear 時(shí)開(kāi)啟當(dāng)前 VC 的 FPS 統(tǒng)計(jì)莉炉,關(guān)閉其他 VC 的 FPS 統(tǒng)計(jì)
  • applicationWillResignActive 退出后臺(tái)時(shí)上報(bào)數(shù)據(jù)钓账,關(guān)閉計(jì)時(shí)器
  • applicationDidBecomeActive 進(jìn)入前臺(tái)后重啟計(jì)時(shí)器,重置數(shù)據(jù)

當(dāng)然可監(jiān)控的 VC 的選取也存在一些規(guī)則絮宁,大致如下

  • 排除 UIViewController 等系統(tǒng) VC
  • 排除 UINavigationController梆暮、UITabBarController、UIInputViewController绍昂、UIAlertController 等非頁(yè)面級(jí)的 VC
  • 排除一個(gè) UIViewController 內(nèi)的子 VC
  • 排除無(wú)父 VC 且不是 present 出來(lái)的 VC

這樣排除的考慮是監(jiān)控 FPS 的實(shí)體一般只有一個(gè)啦粹,同一時(shí)刻只針對(duì)一個(gè) VC 進(jìn)行監(jiān)控,子 VC 等不排除的話可能導(dǎo)致監(jiān)控?cái)?shù)據(jù)不合理窘游。當(dāng)然如果能針對(duì)每一個(gè) VC 都加入 FPS 監(jiān)控就可以解決這一問(wèn)題唠椭,但是這樣會(huì)引入額外的統(tǒng)計(jì)時(shí)機(jī),比如 VC 的 view 需要添加到其他 VC 上以后才應(yīng)該監(jiān)控张峰。

3.3 ScrollView 的 FPS

ScrollView 是常用的展示抽象程度較高泪蔫、數(shù)目較大元素的視圖組件,也是 FPS 重災(zāi)區(qū)喘批,在數(shù)據(jù)處理撩荣、渲染、滑動(dòng)手勢(shì)等多處都可能引發(fā)掉幀現(xiàn)象饶深,因此有必要對(duì)其進(jìn)行 FPS 監(jiān)控餐曹。

ScrollView 的具體監(jiān)控依賴于 UIScrollView 的兩個(gè)屬性

  • isDragging 用戶開(kāi)始滑動(dòng) ScrollView
  • isDecelerating 用戶停止滑動(dòng),但 ScrollView 仍在滾動(dòng)中

通過(guò) CADisplayLink 回調(diào)中檢查當(dāng)前監(jiān)控的 UIScrollView 實(shí)例的兩個(gè)狀態(tài)敌厘,與其前一次狀態(tài)對(duì)比台猴,進(jìn)行如下邏輯

  • 由未滑動(dòng)進(jìn)入到滑動(dòng)狀態(tài),初始化 FPS 數(shù)據(jù)
  • 滑動(dòng)中俱两,更新統(tǒng)計(jì)幀數(shù)和統(tǒng)計(jì)總時(shí)間間隔
  • 由滑動(dòng)狀態(tài)進(jìn)入到未滑動(dòng)狀態(tài)饱狂,上報(bào) FPS 數(shù)據(jù)

在這一過(guò)程中也可以加入當(dāng)前 ScrollView 所屬 VC 的信息方便后續(xù)排查。

3.4 自定義 FPS 時(shí)機(jī)

自定義時(shí)機(jī)更加靈活宪彩,只需要明確統(tǒng)計(jì)開(kāi)始點(diǎn)和結(jié)束點(diǎn)休讳,即可按照 FPS 基本原理進(jìn)行統(tǒng)計(jì)。

- (void)startRecordWithIdentifier:(NSString *)identifier;
- (void)stopRecordWithIdentifier:(NSString *)identifier;

4. CPU 采集

iOS 是基于 Apple Darwin 內(nèi)核尿孔,由 kernel俊柔、XNU 和 Runtime 組成,而 XNU 是 Darwin 的內(nèi)核活合,它是“X is not UNIX”的縮寫(xiě)雏婶,是一個(gè)混合內(nèi)核,由 Mach 微內(nèi)核和 BSD 組成白指。Mach 內(nèi)核是輕量級(jí)的平臺(tái)留晚,只能完成操作系統(tǒng)最基本的職責(zé),比如:進(jìn)程和線程告嘲、虛擬內(nèi)存管理倔丈、任務(wù)調(diào)度憨闰、進(jìn)程通信和消息傳遞機(jī)制。其他的工作需五,例如文件操作和設(shè)備訪問(wèn)鹉动,都由 BSD 層實(shí)現(xiàn)。

在 Mach 層中定義了一個(gè) thread_basic_info 結(jié)構(gòu)體宏邮,提供了線程的基本信息

struct thread_basic_info {
        time_value_t    user_time;      /* user run time */
        time_value_t    system_time;    /* system run time */
        integer_t       cpu_usage;      /* scaled cpu usage percentage */
        policy_t        policy;         /* scheduling policy in effect */
        integer_t       run_state;      /* run state (see below) */
        integer_t       flags;          /* various flags (see below) */
        integer_t       suspend_count;  /* suspend count for thread */
        integer_t       sleep_time;     /* number of seconds that thread
                                           has been sleeping */
};

其中就有我們所需要的 cpu_usage 字段泽示,因此如果獲知了組成當(dāng)前應(yīng)用進(jìn)程的所有線程的 thread_basic_info,就可以統(tǒng)計(jì)出 CPU 使用情況了蜜氨。

在 Mach 層械筛,一個(gè)應(yīng)用進(jìn)程嚴(yán)格關(guān)聯(lián)一個(gè) Mach Task 對(duì)象,通過(guò)如下函數(shù)可以獲知當(dāng)前應(yīng)用所在進(jìn)程的全部線程信息

    thread_array_t         thread_list;
    mach_msg_type_number_t thread_count;
    thread_info_data_t     thinfo;
    mach_msg_type_number_t thread_info_count;
    thread_basic_info_t basic_info_th;
    kern_return_t kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }

接下來(lái)遍歷整個(gè) thread_list飒炎,算出 CPU 總和

    CGFloat total_cpu = 0;
    for (int j = 0; j < thread_count; j++)
    {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,(thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        basic_info_th = (thread_basic_info_t)thinfo;
        
        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
            total_cpu = total_cpu + basic_info_th->cpu_usage / (CGFloat)TH_USAGE_SCALE * 100.0;
        }
    }

這里通過(guò) thread_info 函數(shù)埋哟,將一個(gè) thread 的基礎(chǔ)信息(BASIC_INFO)讀入到 thinfo 中,最終獲取到的 cpu_usage 還需要除以 TH_USAGE_SCALE (CPU處理總頻率)郎汪,從而得到 CPU 占比赤赊。

此處由于我們創(chuàng)建了一個(gè) thread_list 結(jié)構(gòu)體,因此需要手動(dòng)釋放掉煞赢,以避免泄漏內(nèi)存

    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);

獲得了瞬時(shí) CPU 占比抛计,可以啟動(dòng)一個(gè)定時(shí)器定期(1s)采集數(shù)據(jù),最終匯總出最大占比和平均占比等數(shù)據(jù)照筑。

5. Memory 采集

上一節(jié)提到一個(gè)應(yīng)用進(jìn)程對(duì)應(yīng)于一個(gè) Mach Task吹截,而 thread_info 也可以獲取到當(dāng)前進(jìn)程的所有數(shù)據(jù),它們均定義在一個(gè) mach_task_basic_info 結(jié)構(gòu)體中

struct mach_task_basic_info {
        mach_vm_size_t  virtual_size;       /* virtual memory size (bytes) */
        mach_vm_size_t  resident_size;      /* resident memory size (bytes) */
        mach_vm_size_t  resident_size_max;  /* maximum resident memory size (bytes) */
        time_value_t    user_time;          /* total user run time for
                                               terminated threads */
        time_value_t    system_time;        /* total system run time for
                                               terminated threads */
        policy_t        policy;             /* default policy for new threads */
        integer_t       suspend_count;      /* suspend count for task */
};

注釋寫(xiě)的也很清楚凝危,這里 resident_size 即代表了物理內(nèi)存使用情況波俄。

所以獲取方式如下

    struct mach_task_basic_info info;
    mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
    kern_return_t kr = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)& info, &count);
    return (kr == KERN_SUCCESS) ? info.resident_size : 0;

這里我們返回的是 Byte 單位的內(nèi)存占用,因而還需要進(jìn)行一些數(shù)學(xué)運(yùn)算以簡(jiǎn)化數(shù)字展示蛾默。

但是實(shí)際上通過(guò)此方法并不能夠獲取到與 Xcode 上的 Memory 一樣的參數(shù)懦铺,就觀察來(lái)看它比 Xcode 的統(tǒng)計(jì)數(shù)據(jù)要大很多。這里還有另一種 方法趴生,它獲取到的內(nèi)存占用值更加貼合于 Xcode 的統(tǒng)計(jì)值

+ (double)getMemoryUsage {
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    if(task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count) == KERN_SUCCESS) {
        return (double)vmInfo.phys_footprint;
    } else {
        return -1.0;
    }
}

而 iOS 的內(nèi)存殺手 Jetsam 也是通過(guò) phys_footprint 這一參數(shù)來(lái)獲知內(nèi)存使用是否達(dá)到上界的阀趴。

6. 冷啟動(dòng)測(cè)速

冷啟動(dòng)測(cè)速很多時(shí)候都與打點(diǎn)密不可分昏翰,通常來(lái)說(shuō)我們會(huì)在以下一系列地方進(jìn)行打點(diǎn)獲知啟動(dòng)流程

  • main 函數(shù)
  • AppDelegate 代理方法
  • homePage 首頁(yè)

但是在 main 函數(shù)執(zhí)行前其實(shí)也有很大一部分耗時(shí)工作需要執(zhí)行苍匆,例如

  • 加載可執(zhí)行文件
  • 加載動(dòng)態(tài)鏈接庫(kù)
  • 初始化 Runtime
  • +load 函數(shù)

完整示意圖如下

冷啟動(dòng)過(guò)程

所以從 main 函數(shù)開(kāi)始計(jì)時(shí)是與真實(shí)情況不夠貼合的,更早的時(shí)間點(diǎn)獲取方式有以下 3 種

  • 以可執(zhí)行文件中任意一個(gè)類的 +load 方法的執(zhí)行時(shí)間作為起始點(diǎn)
  • 分析 dylib 的依賴關(guān)系棚菊,找到葉子節(jié)點(diǎn)的 dylib浸踩,然后以其中某個(gè)類的 +load 方法的執(zhí)行時(shí)間作為起始點(diǎn)
  • 以 App 的進(jìn)程創(chuàng)建時(shí)間(即 exec 函數(shù)執(zhí)行時(shí)間)作為冷啟動(dòng)的起始時(shí)間,通過(guò) sysctl 函數(shù)獲取

這三者里统求,第三個(gè)方式的時(shí)間戳統(tǒng)計(jì)最早检碗,而且目前未發(fā)現(xiàn)更早更準(zhǔn)確且更有意義的起始點(diǎn)

#import <sys/sysctl.h>
#import <mach/mach.h>

+ (BOOL)processInfoForPID:(int)pid procInfo:(struct kinfo_proc*)procInfo
{
    int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
    size_t size = sizeof(*procInfo);
    return sysctl(cmd, sizeof(cmd)/sizeof(*cmd), procInfo, &size, NULL, 0) == 0;
}

+ (NSTimeInterval)processStartTime
{
    struct kinfo_proc kProcInfo;
    if ([self processInfoForPID:[[NSProcessInfo processInfo] processIdentifier] procInfo:&kProcInfo]) {
        return kProcInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + kProcInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;
    } else {
        NSAssert(NO, @"無(wú)法取得進(jìn)程的信息");
        return 0;
    }
}

有了起始點(diǎn)据块,其他打點(diǎn)就可以依次相減得到每一段的具體耗時(shí)了。

這里需要補(bǔ)充一點(diǎn)折剃,假如應(yīng)用執(zhí)行了安裝后啟動(dòng)的操作另假,例如模擬器上進(jìn)行編譯調(diào)試,sysctl 獲取的時(shí)間戳?xí)陌惭b起始點(diǎn)開(kāi)始計(jì)算怕犁,當(dāng)然這對(duì)于實(shí)際使用來(lái)說(shuō)影響不大边篮。

7. 流量監(jiān)控

流量監(jiān)控主要需要關(guān)注的點(diǎn)有以下四個(gè)

  • URL,毋庸置疑奏甫,監(jiān)控出問(wèn)題后需要 URL 來(lái)排查
  • requestSize戈轿,請(qǐng)求大小,具體包括 URL 長(zhǎng)度阵子、 header 長(zhǎng)度和 body 長(zhǎng)度思杯,實(shí)際上嚴(yán)格意義上 Method 字段和 Version 字段也需要考慮,但是考慮到它們都是固定長(zhǎng)度且占比較小所以不計(jì)
    NSURL *URL = request.URL;
    NSUInteger URLLength = URL.absoluteString.length;
    NSUInteger requestHeaderLength = 0;
    if (request && [NSJSONSerialization isValidJSONObject:[request allHTTPHeaderFields]]) {
        requestHeaderLength = [NSJSONSerialization dataWithJSONObject:[request allHTTPHeaderFields] options:0 error:NULL].length;
    }
    NSUInteger requestBodyLength = request.HTTPBody.length;
    NSUInteger requestSize = URLLength + requestHeaderLength + requestBodyLength;
  • responseSize挠进,響應(yīng)大小色乾,具體包括 header 長(zhǎng)度和 body 長(zhǎng)度
    NSUInteger responseHeaderLength = 0;
    if (response && [NSJSONSerialization isValidJSONObject:[response allHeaderFields]]) {
        responseHeaderLength = [NSJSONSerialization dataWithJSONObject:[(NSHTTPURLResponse *)response allHeaderFields] options:0 error:NULL].length;
    }
    NSUInteger responseSize = responseHeaderLength + responseDataLength;
  • type,請(qǐng)求類型奈梳,具體可以分為
    • Web - H5頁(yè)面杈湾,一般來(lái)說(shuō)它的 MIMEType 會(huì)是這幾種 "text/css","text/html"攘须,"application/x-javascript"漆撞,"application/javascript"
    • API - Native 側(cè)進(jìn)行 API 接口請(qǐng)求
    • Resource - Native 側(cè)進(jìn)行多媒體資源等資源數(shù)據(jù)請(qǐng)求,與 API 的區(qū)分需要從 URLHost 上著手
    • Other

當(dāng)然流量數(shù)據(jù)的特點(diǎn)是頻率高于宙、次數(shù)多浮驳、體積不定,所以做好緩存和批次上報(bào)捞魁、壓縮上報(bào)等工作也是必不可少的至会。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谱俭,隨后出現(xiàn)的幾起案子奉件,更是在濱河造成了極大的恐慌,老刑警劉巖昆著,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件县貌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡凑懂,警方通過(guò)查閱死者的電腦和手機(jī)煤痕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人摆碉,你說(shuō)我怎么就攤上這事塘匣。” “怎么了巷帝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵忌卤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我楞泼,道長(zhǎng)埠巨,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任现拒,我火速辦了婚禮辣垒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘印蔬。我一直安慰自己勋桶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布侥猬。 她就那樣靜靜地躺著例驹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪退唠。 梳的紋絲不亂的頭發(fā)上鹃锈,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音瞧预,去河邊找鬼屎债。 笑死,一個(gè)胖子當(dāng)著我的面吹牛垢油,可吹牛的內(nèi)容都是我干的盆驹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼滩愁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼躯喇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起硝枉,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤廉丽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后妻味,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體正压,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年弧可,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蔑匣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棕诵,死狀恐怖裁良,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情校套,我是刑警寧澤价脾,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站笛匙,受9級(jí)特大地震影響侨把,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜妹孙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一秋柄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蠢正,春花似錦骇笔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至雹舀,卻和暖如春芦劣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背说榆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工虚吟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人签财。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓稍味,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親荠卷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子模庐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359