private static function handle_geocache_replace($c) { # Check if any relevant geocache attributes have changed. # We will pick up "our" copy of the cache from zero-zoom level. try { $cache = OkapiServiceRunner::call("services/caches/geocache", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('cache_code' => $c['object_key']['code'], 'fields' => 'internal_id|code|name|location|type|status|rating|recommendations|founds|trackables_count'))); } catch (InvalidParam $e) { # Unprobable, but possible. Ignore changelog entry. return; } # Fetch our copy of the cache. $ours = Db::fetch_row(Db::query("\n select cache_id, z21x, z21y, status, type, rating, flags, name_crc\n from okapi_tile_caches\n where\n z=0\n and cache_id = '" . Db::escape_string($cache['internal_id']) . "'\n ")); # Caches near the poles caused our computations to break here. We will # ignore such caches! list($lat, $lon) = explode("|", $cache['location']); if (floatval($lat) >= 89.98999999999999 || floatval($lat) <= -89.98999999999999) { if ($ours) { self::remove_geocache_from_cached_tiles($ours[0]); } return; } # Compute the new row for okapi_tile_caches. Compare with the old one. $theirs = TileTree::generate_short_row($cache); if (!$ours) { # Aaah, a new geocache! How nice... ;) self::add_geocache_to_cached_tiles($theirs); } elseif ($ours[1] != $theirs[1] || $ours[2] != $theirs[2]) { # Location changed. self::remove_geocache_from_cached_tiles($ours[0]); self::add_geocache_to_cached_tiles($theirs); } elseif ($ours != $theirs) { self::update_geocache_attributes_in_cached_tiles($theirs); } else { # No need to update anything. This is very common (i.e. when the # cache was simply found, not actually changed). Replicate module generates # many updates which do not influence our cache. } }
public static function call(OkapiRequest $request) { $checkpointA_started = microtime(true); # Make sure the request is internal. if (in_array($request->consumer->key, array('internal', 'facade'))) { /* Okay, these two consumers can always access it. */ } elseif ($request->consumer->hasFlag(OkapiConsumer::FLAG_MAPTILE_ACCESS)) { /* If the Consumer is aware that it is not backward-compatible, then * he may be granted permission to access it. */ } else { throw new BadRequest("Your Consumer Key has not been allowed to access this method."); } # zoom, x, y - required tile-specific parameters. $zoom = self::require_uint($request, 'z'); if ($zoom > 21) { throw new InvalidParam('z', "Maximum value for this parameter is 21."); } $x = self::require_uint($request, 'x'); $y = self::require_uint($request, 'y'); if ($x >= 1 << $zoom) { throw new InvalidParam('x', "Should be in 0.." . ((1 << $zoom) - 1) . "."); } if ($y >= 1 << $zoom) { throw new InvalidParam('y', "Should be in 0.." . ((1 << $zoom) - 1) . "."); } # Now, we will create a search set (or use one previously created). # Instead of creating a new OkapiInternalRequest object, we will pass # the current request directly. We can do that, because we inherit all # of the "save" method's parameters. $search_set = OkapiServiceRunner::call('services/caches/search/save', new OkapiInternalRequest($request->consumer, $request->token, $request->get_all_parameters_including_unknown())); $set_id = $search_set['set_id']; # Get caches which are present in the result set AND within the tile # (+ those around the borders). $rs = TileTree::query_fast($zoom, $x, $y, $set_id); $rows = array(); if ($rs !== null) { while ($row = Db::fetch_row($rs)) { $rows[] = $row; } unset($row); } OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointA", null, microtime(true) - $checkpointA_started); $checkpointB_started = microtime(true); # Add dynamic, user-related flags. if (count($rows) > 0) { # Load user-related cache ids. $cache_key = "tileuser/" . $request->token->user_id; $user = self::$USE_OTHER_CACHE ? Cache::get($cache_key) : null; if ($user === null) { $user = array(); # Ignored caches. $rs = Db::query("\n select cache_id\n from cache_ignore\n where user_id = '" . Db::escape_string($request->token->user_id) . "'\n "); $user['ignored'] = array(); while (list($cache_id) = Db::fetch_row($rs)) { $user['ignored'][$cache_id] = true; } # Found caches. $rs = Db::query("\n select distinct cache_id\n from cache_logs\n where\n user_id = '" . Db::escape_string($request->token->user_id) . "'\n and type = 1\n and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n "); $user['found'] = array(); while (list($cache_id) = Db::fetch_row($rs)) { $user['found'][$cache_id] = true; } # Own caches. $rs = Db::query("\n select distinct cache_id\n from caches\n where user_id = '" . Db::escape_string($request->token->user_id) . "'\n "); $user['own'] = array(); while (list($cache_id) = Db::fetch_row($rs)) { $user['own'][$cache_id] = true; } Cache::set($cache_key, $user, 30); } # Add extra flags to geocaches. foreach ($rows as &$row_ref) { # Add the "found" flag (to indicate that this cache needs # to be drawn as found) and the "own" flag (to indicate that # the current user is the owner). if (isset($user['found'][$row_ref[0]])) { $row_ref[6] |= TileTree::$FLAG_FOUND; } # $row[6] is "flags" if (isset($user['own'][$row_ref[0]])) { $row_ref[6] |= TileTree::$FLAG_OWN; } # $row[6] is "flags" } } # Compute the image hash/fingerprint. This will be used both for ETags # and internal cache ($cache_key). $tile = new TileRenderer($zoom, $rows); $image_fingerprint = $tile->get_unique_hash(); # Start creating response. $response = new OkapiHttpResponse(); $response->content_type = $tile->get_content_type(); $response->cache_control = "Cache-Control: private, max-age=600"; $response->etag = 'W/"' . $image_fingerprint . '"'; $response->allow_gzip = false; // images are usually compressed, prevent compression at Apache level # Check if the request didn't include the same ETag. OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointB", null, microtime(true) - $checkpointB_started); $checkpointC_started = microtime(true); if (self::$USE_ETAGS_CACHE && $request->etag == $response->etag) { # Hit. Report the content was unmodified. $response->etag = null; $response->status = "304 Not Modified"; return $response; } # Check if the image was recently rendered and is kept in image cache. $cache_key = "tile/" . $image_fingerprint; $response->body = self::$USE_IMAGE_CACHE ? Cache::get($cache_key) : null; OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointC", null, microtime(true) - $checkpointC_started); $checkpointD_started = microtime(true); if ($response->body !== null) { # Hit. We will use the cached version of the image. return $response; } # Miss. Render the image. Cache the result. $response->body = $tile->render(); Cache::set_scored($cache_key, $response->body); OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointD", null, microtime(true) - $checkpointD_started); return $response; }
/** * Retrieve object stored under the key $key. If object does not * exist or timeout expired, return null. */ public static function get($key) { $rs = Db::query("\n select value, score\n from okapi_cache\n where\n `key` = '" . Db::escape_string($key) . "'\n and expires > now()\n "); list($blob, $score) = Db::fetch_row($rs); if (!$blob) { return null; } if ($score != null) { Db::execute("\n insert into okapi_cache_reads (`cache_key`)\n values ('" . Db::escape_string($key) . "')\n "); } return unserialize(gzinflate($blob)); }
public static function call() { # Flush the stats, so the page is fresh upon every request. require_once $GLOBALS['rootpath'] . "okapi/cronjobs.php"; CronJobController::force_run("StatsWriterCronJob"); # When services/caches/map/tile method is called, it writes some extra # stats in the okapi_stats_hourly table. This page retrieves and # formats these stats in a readable manner (for debugging). $response = new OkapiHttpResponse(); $response->content_type = "text/plain; charset=utf-8"; ob_start(); $start = isset($_GET['start']) ? $_GET['start'] : date("Y-m-d 00:00:00", time() - 7 * 86400); $end = isset($_GET['end']) ? $_GET['end'] : date("Y-m-d 23:59:59"); print "From: {$start}\n"; print " To: {$end}\n\n"; $rs = Db::query("\n select\n service_name,\n sum(total_calls),\n sum(total_runtime)\n from okapi_stats_hourly\n where\n period_start >= '" . Db::escape_string($start) . "'\n and period_start < '" . Db::escape_string($end) . "'\n and service_name like '%caches/map/tile%'\n group by service_name\n "); $total_calls = 0; $total_runtime = 0.0; $calls = array('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); $runtime = array('A' => 0.0, 'B' => 0.0, 'C' => 0.0, 'D' => 0.0); while (list($name, $c, $r) = Db::fetch_row($rs)) { if ($name == 'services/caches/map/tile') { $total_calls = $c; $total_runtime = $r; } elseif (strpos($name, 'extra/caches/map/tile/checkpoint') === 0) { $calls[$name[32]] = $c; $runtime[$name[32]] = $r; } } if ($total_calls != $calls['A']) { print "Partial results. Only " . $calls['A'] . " out of {$total_calls} are covered.\n"; print "All other will count as \"unaccounted for\".\n\n"; $total_calls = $calls['A']; } $calls_left = $total_calls; $runtime_left = $total_runtime; $perc = function ($a, $b) { return $b > 0 ? sprintf("%.1f", 100 * $a / $b) . "%" : "(?)"; }; $avg = function ($a, $b) { return $b > 0 ? sprintf("%.4f", $a / $b) . "s" : "(?)"; }; $get_stats = function () use(&$calls_left, &$runtime_left, &$total_calls, &$total_runtime, &$perc) { return str_pad($perc($calls_left, $total_calls), 6, " ", STR_PAD_LEFT) . str_pad($perc($runtime_left, $total_runtime), 7, " ", STR_PAD_LEFT); }; print "%CALLS %TIME Description\n"; print "====== ====== ======================================================================\n"; print $get_stats() . " {$total_calls} responses served. Total runtime: " . sprintf("%.2f", $total_runtime) . "s\n"; print "\n"; print " All of these requests needed a TileTree build/lookup. The average runtime of\n"; print " these lookups was " . $avg($runtime['A'], $total_calls) . ". " . $perc($runtime['A'], $total_runtime) . " of total runtime was spent here.\n"; print "\n"; $runtime_left -= $runtime['A']; print $get_stats() . " All calls passed here after ~" . $avg($runtime['A'], $total_calls) . "\n"; print "\n"; print " Lookup result was then processed and \"image description\" was created. It was\n"; print " passed on to the TileRenderer to compute the ETag hash string. The average runtime\n"; print " of this part was " . $avg($runtime['B'], $total_calls) . ". " . $perc($runtime['B'], $total_runtime) . " of total runtime was spent here.\n"; print "\n"; $runtime_left -= $runtime['B']; print $get_stats() . " All calls passed here after ~" . $avg($runtime['A'] + $runtime['B'], $total_calls) . "\n"; $etag_hits = $calls['B'] - $calls['C']; print "\n"; print " {$etag_hits} of the requests matched the ETag and were served an HTTP 304 response.\n"; print "\n"; $calls_left = $calls['C']; print $get_stats() . " {$calls_left} calls passed here after ~" . $avg($runtime['A'] + $runtime['B'], $total_calls) . "\n"; $imagecache_hits = $calls['C'] - $calls['D']; print "\n"; print " {$imagecache_hits} of these calls hit the server image cache.\n"; print " " . $perc($runtime['C'], $total_runtime) . " of total runtime was spent to find these.\n"; print "\n"; $calls_left = $calls['D']; $runtime_left -= $runtime['C']; print $get_stats() . " {$calls_left} calls passed here after ~" . $avg($runtime['A'] + $runtime['B'] + $runtime['C'], $total_calls) . "\n"; print "\n"; print " These calls required the tile to be rendered. On average, it took\n"; print " " . $avg($runtime['D'], $calls['D']) . " to *render* a tile.\n"; print " " . $perc($runtime['D'], $total_runtime) . " of total runtime was spent here.\n"; print "\n"; $runtime_left -= $runtime['D']; print $perc($runtime_left, $total_runtime) . " of runtime was unaccounted for (other processing).\n"; print "Average response time was " . $avg($total_runtime, $total_calls) . ".\n\n"; print "Current okapi_cache score distribution:\n"; $rs = Db::query("\n select floor(log2(score)), count(*), sum(length(value))\n from okapi_cache\n where score is not null\n group by floor(log2(score))\n "); while (list($log2, $count, $size) = Db::fetch_row($rs)) { print $count . " elements ({$size} bytes) with score between " . pow(2, $log2) . " and " . pow(2, $log2 + 1) . ".\n"; } $response->body = ob_get_clean(); return $response; }