public function __construct($stackTraceId, $days) { $this->title("Last {$days} days analysis")->titleOnYAxis('count per day'); $offset = \Misc::getUserTimezoneOffsetInMinutes(); $cacheKey = "stacks-{$stackTraceId}-per-last-{$days}-days-offset-{$offset}min"; $from = new \DateTime(\Misc::userDate('Y-m-d 00:00:00')); $from->modify("-{$days} day"); $records = Cache::getOrSet($cacheKey, function () use($from, $stackTraceId, $days, $offset) { $operator = $offset < 0 ? '-' : '+'; $offsetAbs = abs($offset); $today = new \DateTime(\Misc::userDate('Y-m-d 00:00:00')); $query = \Crash\Archive::query()->field("DATE(created_at + INTERVAL {$offset} MINUTE)", 'time')->field('COUNT(*)', 'total')->where('stack_trace_id', $stackTraceId)->where('created_at', '>=', \Db::expr("'{$from->format('Y-m-d H:i:s')}' {$operator} INTERVAL {$offsetAbs} MINUTE"))->groupBy(1)->orderBy(1); return $query->fetchAllObj(); }, 3720 - date('i') * 60 + date('s')); $data = array(); foreach ($records as $r) { $data[$r->time] = $r->total; } $serieData = array(); $startMinute = (int) $from->format('i'); for ($day = $from, $today = \Misc::userDate('Y-m-d'); $day->format('Y-m-d') <= $today; $day->modify('+1 day')) { $date = $day->format('Y-m-d'); $serieData[$date] = isset($data[$date]) ? (int) $data[$date] : 0; $this->addOnXAxis($day->format('jS')); } $this->addSerie('requests', array_values($serieData)); }
public function viewAction() { $id = Url::getVar(1); // todo: ubaciti ovo u cache i odatle cupat van $cacheKey = "report-{$id}"; $crash = CrashArchive::fetchOne($id); if ($crash === false) { Application::throwError(404, 'Can not find crash report ' . $id); } $title = "Crash Report #{$id}"; $content = array(); $panel = Bootstrap::panel($title)->color('blue')->addHeaderElement(Bootstrap::button('Back')->setAttribute('onclick', 'window.history.back()')->color('red')->size('xs')); $table = Bootstrap::table(); $table->column('id', '')->column('value', ''); // time $table->row(array('id' => 'time', 'value' => Misc::userDate('Y-m-d H:i:s', $crash->created_at) . ' (' . $this->user['timezone'] . ')')); // package $e = $crash->getPackage(); $v = $crash->getPackageVersion(); $table->row(array('id' => 'package and version', 'value' => implode(' ', array($e !== null ? "{$this->getSearchLink('package_id', $e->id, $e->name)} {$this->getLabel($e->total)}" : 'unknown', $v !== null ? "{$this->getSearchLink('package_version_id', $v->id, $v->value)} {$this->getLabel($v->total)}" : 'unknown')))); // device $value = ''; $e = $crash->getBrand(); if ($e === null) { $value .= 'unknown brand<br/>'; } else { $value .= "{$this->getSearchLink('brand_id', $e->id, $e->name)} {$this->getLabel($e->total)}<br/>"; } $e = $crash->getPhoneModel(); if ($e === null) { $value .= 'unknown phone model<br/>'; } else { $value .= "{$this->getSearchLink('model_id', $e->id, $e->name)} {$this->getLabel($e->total)}<br/>"; } $table->row(array('id' => 'device', 'value' => substr($value, 0, -5))); // product $e = $crash->getProduct(); if ($e !== null) { $table->row(array('id' => 'product name', 'value' => "{$this->getSearchLink('product_id', $e->id, $e->name)} {$this->getLabel($e->total)}")); } // os $e = $crash->getOsVersion(); $table->row(array('id' => 'OS', 'value' => $e === null ? 'unknown' : "{$this->getSearchLink('os_version_id', $e->id, "{$e->os} {$e->name}")} {$this->getLabel($e->total)}")); // user comment if ($crash->user_comment !== null && trim($crash->user_comment) != '') { $table->row(array('id' => 'user comment', 'value' => $crash->user_comment)); } // user email if ($crash->user_email !== null && trim($crash->user_email) != '') { $table->row(array('id' => 'user email', 'value' => $crash->user_email)); } // app lifetime if ($crash->user_app_start_date !== null && $crash->user_crash_date !== null) { $table->row(array('id' => 'app lifetime', 'value' => "{$crash->user_app_start_date}<br/>{$crash->user_crash_date} (duration: {$this->duration($crash->user_app_start_date, $crash->user_crash_date)})")); } // memory usage $table->row(array('id' => 'available / total memory size', 'value' => Convert::bytesToString($crash->available_mem_size) . ' / ' . Convert::bytesToString($crash->total_mem_size))); // country if ($crash->country_id !== null) { $country = Country::fetchOne($crash->country_id); $table->row(array('id' => 'country', 'value' => "<img src=\"" . Url::link("img/flag/{$country->tld}.png") . "\" /> <a href=\"" . Url::href('reports', 'search', array('country_id' => $crash->country_id)) . "\">{$country->country} (" . strtoupper($country->tld) . ")</a> " . Bootstrap::label($country->total)->color('red'))); } // provider if ($crash->provider_id !== null) { $e = Provider::fetchOne($crash->provider_id); $table->row(array('id' => 'internet provider', 'value' => "{$this->getSearchLink('provider_id', $e->id, $e->name)} {$this->getLabel($e->total)}")); } $metas = $crash->getMetas(); $toTabs = array(); foreach ($metas as $key => $value) { if ($key != 'stack_trace') { if (strpos(trim($value), "\n") === false) { $table->row(array('id' => str_replace('_', ' ', $key), 'value' => trim($value) == '' ? '<em>empty</em>' : $value)); } else { $toTabs[] = $key; } } } $toTabsUnknown = array(); $unknownMetas = $crash->getUnknownMetas(); foreach ($unknownMetas as $key => $value) { if (strpos(trim($value), "\n") === false) { $table->row(array('id' => str_replace('_', ' ', $key), 'value' => (trim($value) == '' ? '<em>empty</em>' : $value) . ' ' . Bootstrap::label('unknown meta')->color('lightblue'))); } else { $toTabsUnknown[] = $key; } } if ($crash->stack_trace_id !== null) { $table->row(array('id' => 'find reports with this stack trace', 'value' => Bootstrap::anchor(\Bootstrap::icon('search'), Url::href('reports', 'search', array('stack_trace_id' => $crash->stack_trace_id)))->asButton()->color('red')->size('xs'))); } $panel->content($table); $content[] = Bootstrap::row()->add(12, $panel); $tabs = Bootstrap::nav(); if (isset($metas['stack_trace'])) { $tabs->addLink('stack trace', "<pre class=\"text-danger\">{$metas['stack_trace']}</pre>"); } else { if ($crash->stack_trace_id !== null) { $stackTrace = Stack\Trace::fetchOne($crash->stack_trace_id); $tabs->addLink('stack trace summary', "<pre class=\"text-danger\">{$stackTrace->summary}</pre>"); } } if (sizeof($toTabs) > 0) { foreach ($toTabs as $key) { $tabs->addLink(str_replace('_', ' ', $key), "<pre>{$metas[$key]}</pre>"); } } if (sizeof($toTabsUnknown) > 0) { foreach ($toTabsUnknown as $key) { $tabs->addLink(str_replace('_', ' ', $key) . ' ' . Bootstrap::label('?')->color('lightblue'), "<pre>{$unknownMetas[$key]}</pre>"); } } if ($tabs->count() > 0) { $content[] = Bootstrap::row()->add(12, $tabs); } if ($crash->stack_trace_id !== null) { $content[] = Bootstrap::row()->add(12, Bootstrap::panel('This error count per day', new Chart\StackTracesPerDay($crash->stack_trace_id, 30))->color('red')); } return View::create('base')->with('title', $title)->with('content', $content); }
/** * Rebuild stack_trace table * CAUTION!!!! If you have a lot of records, this could take hours or days do get done! */ public static function rebuild() { Status::calculationStarted(); Status::setCalculationStatus('Initializing'); Status::setCalculationStatus('Reseting stack_trace_ids to NULL'); Db::query('UPDATE crash_archive SET stack_trace_id = NULL'); Status::setCalculationStatus('Emptying stack_trace table'); Db::query('TRUNCATE TABLE stack_trace'); $stacks = array(); // foreach (Stack\Trace::all() as $r) { // $stacks[$r['hash']] = (int) $r['id']; // } $total = \Crash\Archive::count(); $index = 0; $start = 0; do { $query = new Select('crash_archive'); $query->field('id')->where('id', '>', $start)->where('id', '<=', $start + 100000); Status::setCalculationStatus("Taking IDs from {$start} to " . ($start + 100000)); $records = $query->fetchAll(); $sizeofIds = count($records); foreach ($records as $r) { $id = (int) $r['id']; $crash = new \Crash\Archive(array('id' => $id)); $stackTrace = $crash->getMeta('stack_trace'); if ($stackTrace !== null) { $summary = static::getSummary($stackTrace); $md5 = md5($summary); if (isset($stacks[$md5])) { $stackTraceId = $stacks[$md5]; } else { $tmp = \Stack\Trace::create(array('hash' => $md5, 'summary' => $summary, 'created_at' => $crash->created_at)); $stackTraceId = (int) $tmp->id; $stacks[$md5] = $stackTraceId; } $crash->stack_trace_id = $stackTraceId; $crash->save(); Log::info("Updated #{$id} with stack={$stackTraceId}"); } else { Log::info("Crash report #{$id} is skipped because stack_trace is missing in meta table"); } if ($index % 25 == 0) { $percent = round($index / $total * 100, 2); Status::setCalculationStatus("Working on {$index}/{$sizeofIds} {$percent}%"); } $index++; } $start += 100000; } while (sizeof($records) > 0); Status::setCalculationStatus('Started stack trace recalculation!'); Log::info('Started recalculation of stack_trace counts'); static::recalculate(); Status::calculationFinished('all'); }
/** * Calculate for reports * @return boolean */ public function calculate() { if (Status::isCalculationInProgress()) { Log::info('Trying to start calculation, but calculation is already in progress. Last was started on ' . Status::getLastCalculationProcessStart() . ' UTC'); return false; } ini_set('memory_limit', '512M'); set_time_limit(0); Status::calculationStarted(); Status::setCalculationStatus('Initializing'); Log::info('Calculation started'); /** * metas: 'file_path', 'build', 'environment', 'settings_global', 'settings_system', 'settings_secure', * 'device_features', 'shared_preferences', 'initial_configuration', 'crash_configuration', * 'dumpsys_meminfo', 'display', 'stack_trace', 'logcat', 'tktal_mem_size', '@evice_features', 'installation_id' */ $query = new Select(); $query->from('crash_submit')->field('id')->limit(0, 200000); $maxQuery = CrashArchive::query()->field('MAX(created_at)', 'time'); $max = $maxQuery->fetchFirstObj(); unset($maxQuery); $lastDatetime = new DateTime(gmdate('Y-m-d H:00:00')); $lastDatetime->modify('-1 hour'); $query->where('created_at', '<', $lastDatetime->format('Y-m-d H:i:s')); $ids = $query->fetchAllObj(); $sizeofIds = sizeof($ids); Log::info('Loaded ids: ' . $sizeofIds); $brandTotals = $countryTotals = $packageTotals = $packageVersionTotals = $phoneModelTotals = $productTotals = $providerTotals = $stackTraceTotals = $osVersionTotals = array(); foreach ($ids as $index => $r) { // on every 25 records, we should ask ourself: is that it? if ($index % 25 == 0) { $shouldTerminate = Status::shouldCalcuationTerminate(); if ($shouldTerminate !== false) { Log::info("Noticed request for calculation termination on {$shouldTerminate}. Aborted!"); Status::setCalculationStatus('Terminated after ' . ($index + 1) . ' records'); Status::terminateCalculation(false); return false; } } $id = $r->id; // record by record Log::info("Will now fetch id={$id}"); if ($index % 15 == 0) { $percent = round($index / $sizeofIds * 100, 2); Status::setCalculationStatus("Working; {$index}/{$sizeofIds} {$percent}%"); } $submit = CrashSubmit::fetchOne($id); if ($submit !== false && $submit->package_name !== null && trim($submit->package_name !== null) != '') { $appStartTime = strtotime($submit->user_app_start_date); $appCrashTime = strtotime($submit->user_crash_date); $appLifetime = $appCrashTime - $appStartTime; $metas = $submit->getMetas(); if ($submit->report_id !== null && trim($submit->report_id) !== '') { $metas['report_id'] = $submit->report_id; } if ($submit->file_path !== null && trim($submit->file_path) !== '') { $metas['file_path'] = $submit->file_path; } if ($submit->installation_id !== null && trim($submit->installation_id) !== '') { $metas['installation_id'] = $submit->installation_id; } $stackTrace = $submit->stack_trace; if ($stackTrace === null) { $stackTraceSummary = null; } else { $metas['stack_trace'] = $stackTrace; $stackTraceSummary = StackTrace::getSummary($stackTrace); } $packageId = $this->getPackageId($submit->package_name); $packageVersionId = $this->getPackageVersionId($this->getPackageId($submit->package_name), $submit->app_version_name); $brandId = $this->getBrandId($submit->brand); $phoneModelId = $this->getModelId($this->getBrandId($submit->brand), $submit->phone_model); $productId = $this->getProductId($this->getBrandId($submit->brand), $submit->product); $osVersionId = $this->getOsVersion($submit->os, $submit->android_version); $stackTraceId = $this->getStackTraceId($stackTraceSummary, $submit->created_at); $countryId = $this->getCountryId($submit->country); $providerId = $this->getProviderId($submit->provider); $archive = CrashArchive::create(array('created_at' => $submit->created_at, 'package_id' => $packageId, 'package_version_id' => $packageVersionId, 'brand_id' => $brandId, 'model_id' => $phoneModelId, 'product_id' => $productId, 'os' => $submit->os, 'os_version_id' => $osVersionId, 'total_mem_size' => $submit->total_mem_size, 'available_mem_size' => $submit->available_mem_size, 'user_comment' => trim($submit->user_comment) == '' ? null : trim($submit->user_comment), 'user_email' => trim($submit->user_email) == 'N/A' ? null : trim($submit->user_email), 'user_app_start_date' => $submit->user_app_start_date, 'user_crash_date' => $submit->user_crash_date, 'user_app_lifetime' => $appLifetime, 'stack_trace_id' => $stackTraceId, 'country_id' => $countryId, 'provider_id' => $providerId)); $archive->insertMeta($metas); // prepare increments for totals if ($packageId !== null) { if (!isset($packageTotals[$packageId])) { $packageTotals[$packageId] = 0; } $packageTotals[$packageId]++; } if ($packageVersionTotals !== null) { if (!isset($packageVersionTotals[$packageVersionId])) { $packageVersionTotals[$packageVersionId] = 0; } $packageVersionTotals[$packageVersionId]++; } if ($brandId !== null) { if (!isset($brandTotals[$brandId])) { $brandTotals[$brandId] = 0; } $brandTotals[$brandId]++; } if ($phoneModelId !== null) { if (!isset($phoneModelTotals[$phoneModelId])) { $phoneModelTotals[$phoneModelId] = 0; } $phoneModelTotals[$phoneModelId]++; } if ($productId !== null) { if (!isset($productTotals[$productId])) { $productTotals[$productId] = 0; } $productTotals[$productId]++; } if ($osVersionTotals !== null) { if (!isset($osVersionTotals[$osVersionId])) { $osVersionTotals[$osVersionId] = 0; } $osVersionTotals[$osVersionId]++; } if ($stackTraceId !== null) { if (!isset($stackTraceTotals[$stackTraceId])) { $stackTraceTotals[$stackTraceId] = 0; } $stackTraceTotals[$stackTraceId]++; } if ($countryId !== null) { if (!isset($countryTotals[$countryId])) { $countryTotals[$countryId] = 0; } $countryTotals[$countryId]++; } if ($providerId !== null) { if (!isset($providerTotals[$providerId])) { $providerTotals[$providerId] = 0; } $providerTotals[$providerId]++; } } } if ($sizeofIds > 0) { Log::info('Calculation done'); Status::setCalculationStatus('Starting to delete submit records'); $deleteIds = array(); foreach ($ids as $index => $r) { $deleteIds[] = $r->id; if (sizeof($deleteIds) == 100) { $percent = round($index / $sizeofIds * 100, 2); Status::setCalculationStatus("Deleting submit records {$index}/{$sizeofIds} {$percent}%"); Db::delete('crash_submit_meta')->whereIn('submit_id', $deleteIds)->exec(); Db::delete('crash_submit')->whereIn('id', $deleteIds)->exec(); $deleteIds = array(); } } if (sizeof($deleteIds) > 0) { Status::setCalculationStatus("Deleting submit records {$index}/{$sizeofIds} 100%"); Db::delete('crash_submit_meta')->whereIn('submit_id', $deleteIds)->exec(); Db::delete('crash_submit')->whereIn('id', $deleteIds)->exec(); } Status::setCalculationStatus('Started totals calculations update!'); // update calculated increments foreach ($brandTotals as $id => $total) { Status::setCalculationStatus('Brands: Updating calculated totals'); Db::update('brand')->increment('total', $total)->where('id', $id)->exec(); } foreach ($countryTotals as $id => $total) { Status::setCalculationStatus('Countries: Updating calculated totals'); Db::update('country')->increment('total', $total)->where('id', $id)->exec(); } foreach ($packageTotals as $id => $total) { Status::setCalculationStatus('Packages: Updating calculated totals'); Db::update('package')->increment('total', $total)->where('id', $id)->exec(); } foreach ($packageVersionTotals as $id => $total) { Status::setCalculationStatus('Package version: Updating calculated totals'); Db::update('package_version')->increment('total', $total)->where('id', $id)->exec(); } foreach ($phoneModelTotals as $id => $total) { Status::setCalculationStatus('Phone models: Updating calculated totals'); Db::update('phone_model')->increment('total', $total)->where('id', $id)->exec(); } foreach ($productTotals as $id => $total) { Status::setCalculationStatus('Products: Updating calculated totals'); Db::update('product')->increment('total', $total)->where('id', $id)->exec(); } foreach ($providerTotals as $id => $total) { Status::setCalculationStatus('Providers: Updating calculated totals'); Db::update('provider')->increment('total', $total)->where('id', $id)->exec(); } foreach ($stackTraceTotals as $id => $total) { Status::setCalculationStatus('Stack traces: Updating calculated totals'); Db::update('stack_trace')->increment('total', $total)->where('id', $id)->exec(); } foreach ($osVersionTotals as $id => $total) { Status::setCalculationStatus('OS version: Updating calculated totals'); Db::update('version')->increment('total', $total)->where('id', $id)->exec(); } } else { // Log::info('Calculation done, no records processed'); } Status::calculationFinished($sizeofIds); return true; }