summaryrefslogtreecommitdiff
path: root/classes
diff options
context:
space:
mode:
Diffstat (limited to 'classes')
-rwxr-xr-xclasses/api.php2
-rwxr-xr-xclasses/article.php34
-rw-r--r--classes/config.php27
-rw-r--r--classes/counters.php11
-rw-r--r--classes/digest.php3
-rw-r--r--classes/diskcache.php58
-rwxr-xr-xclasses/feeds.php82
-rwxr-xr-xclasses/handler/public.php6
-rw-r--r--classes/icatchall.php4
-rwxr-xr-xclasses/pluginhost.php39
-rwxr-xr-xclasses/pref/feeds.php4
-rwxr-xr-xclasses/rpc.php3
-rwxr-xr-xclasses/rssutils.php23
-rw-r--r--classes/sanitizer.php3
-rw-r--r--classes/tracer.php69
-rw-r--r--classes/urlhelper.php51
16 files changed, 336 insertions, 83 deletions
diff --git a/classes/api.php b/classes/api.php
index b482a70eb..255de52d4 100755
--- a/classes/api.php
+++ b/classes/api.php
@@ -99,7 +99,7 @@ class API extends Handler {
}
function isLoggedIn(): bool {
- return $this->_wrap(self::STATUS_OK, array("status" => $_SESSION["uid"] != ''));
+ return $this->_wrap(self::STATUS_OK, array("status" => (bool)($_SESSION["uid"] ?? '')));
}
function getUnread(): bool {
diff --git a/classes/article.php b/classes/article.php
index 609ddeebe..63469ccd2 100755
--- a/classes/article.php
+++ b/classes/article.php
@@ -239,8 +239,7 @@ class Article extends Handler_Protected {
print json_encode(["id" => (int)$id, "tags" => $this->_get_tags($id)]);
}
-
- /*function completeTags() {
+ function completeTags(): void {
$search = clean($_REQUEST["search"]);
$sth = $this->pdo->prepare("SELECT DISTINCT tag_name FROM ttrss_tags
@@ -250,12 +249,14 @@ class Article extends Handler_Protected {
$sth->execute([$_SESSION['uid'], "$search%"]);
- print "<ul>";
+ $results = [];
+
while ($line = $sth->fetch()) {
- print "<li>" . $line["tag_name"] . "</li>";
+ array_push($results, $line["tag_name"]);
}
- print "</ul>";
- }*/
+
+ print json_encode($results);
+ }
function assigntolabel(): void {
$this->_label_ops(true);
@@ -297,6 +298,8 @@ class Article extends Handler_Protected {
* @return array{'formatted': string, 'entries': array<int, array<string, mixed>>}
*/
static function _format_enclosures(int $id, bool $always_display_enclosures, string $article_content, bool $hide_images = false): array {
+ $scope = Tracer::start(__METHOD__);
+
$enclosures = self::_get_enclosures($id);
$enclosures_formatted = "";
@@ -323,6 +326,7 @@ class Article extends Handler_Protected {
$enclosures_formatted, $enclosures, $id, $always_display_enclosures, $article_content, $hide_images);
if (!empty($enclosures_formatted)) {
+ $scope->close();
return [
'formatted' => $enclosures_formatted,
'entries' => []
@@ -366,6 +370,7 @@ class Article extends Handler_Protected {
}
}
+ $scope->close();
return $rv;
}
@@ -373,6 +378,7 @@ class Article extends Handler_Protected {
* @return array<int, string>
*/
static function _get_tags(int $id, int $owner_uid = 0, ?string $tag_cache = null): array {
+ $scope = Tracer::start(__METHOD__);
$a_id = $id;
@@ -421,6 +427,7 @@ class Article extends Handler_Protected {
$sth->execute([$tags_str, $id, $owner_uid]);
}
+ $scope->close();
return $tags;
}
@@ -515,6 +522,8 @@ class Article extends Handler_Protected {
* @return array<int, array<int, int|string>>
*/
static function _get_labels(int $id, ?int $owner_uid = null): array {
+ $scope = Tracer::start(__METHOD__, []);
+
$rv = array();
if (!$owner_uid) $owner_uid = $_SESSION["uid"];
@@ -560,6 +569,8 @@ class Article extends Handler_Protected {
else
Labels::update_cache($owner_uid, $id, array("no-labels" => 1));
+ $scope->close();
+
return $rv;
}
@@ -570,6 +581,7 @@ class Article extends Handler_Protected {
* @return array<int, Article::ARTICLE_KIND_*|string>
*/
static function _get_image(array $enclosures, string $content, string $site_url, array $headline) {
+ $scope = Tracer::start(__METHOD__);
$article_image = "";
$article_stream = "";
@@ -648,6 +660,8 @@ class Article extends Handler_Protected {
if ($article_stream && $cache->exists(sha1($article_stream)))
$article_stream = $cache->get_url(sha1($article_stream));
+ $scope->close();
+
return [$article_image, $article_stream, $article_kind];
}
@@ -661,6 +675,8 @@ class Article extends Handler_Protected {
if (count($article_ids) == 0)
return [];
+ $scope = Tracer::start(__METHOD__);
+
$entries = ORM::for_table('ttrss_entries')
->table_alias('e')
->join('ttrss_user_entries', ['ref_id', '=', 'id'], 'ue')
@@ -680,6 +696,8 @@ class Article extends Handler_Protected {
}
}
+ $scope->close();
+
return array_unique($rv);
}
@@ -691,6 +709,8 @@ class Article extends Handler_Protected {
if (count($article_ids) == 0)
return [];
+ $scope = Tracer::start(__METHOD__);
+
$entries = ORM::for_table('ttrss_entries')
->table_alias('e')
->join('ttrss_user_entries', ['ref_id', '=', 'id'], 'ue')
@@ -703,6 +723,8 @@ class Article extends Handler_Protected {
array_push($rv, $entry->feed_id);
}
+ $scope->close();
+
return array_unique($rv);
}
}
diff --git a/classes/config.php b/classes/config.php
index a865266ca..afdb7b8ef 100644
--- a/classes/config.php
+++ b/classes/config.php
@@ -192,6 +192,12 @@ class Config {
/** delay updates for this feed if received HTTP 429 (Too Many Requests) for this amount of seconds (base value, actual delay is base...base*2) */
const HTTP_429_THROTTLE_INTERVAL = "HTTP_429_THROTTLE_INTERVAL";
+ /** host running Jaeger collector to receive traces (disabled if empty) */
+ const JAEGER_REPORTING_HOST = "JAEGER_REPORTING_HOST";
+
+ /** Jaeger service name */
+ const JAEGER_SERVICE_NAME = "JAEGER_SERVICE_NAME";
+
/** default values for all global configuration options */
private const _DEFAULTS = [
Config::DB_TYPE => [ "pgsql", Config::T_STRING ],
@@ -249,6 +255,8 @@ class Config {
Config::HTTP_USER_AGENT => [ 'Tiny Tiny RSS/%s (https://tt-rss.org/)',
Config::T_STRING ],
Config::HTTP_429_THROTTLE_INTERVAL => [ 3600, Config::T_INT ],
+ Config::JAEGER_REPORTING_HOST => [ "", Config::T_STRING ],
+ Config::JAEGER_SERVICE_NAME => [ "tt-rss", Config::T_STRING ],
];
/** @var Config|null */
@@ -303,7 +311,11 @@ class Config {
static function get_version_html() : string {
$version = self::get_version(false);
- return sprintf("<span title=\"%s\">%s</span>", date("Y-m-d H:i:s", ($version['timestamp'] ?? 0)), $version['version']);
+ return sprintf("<span title=\"%s\n%s\n%s\">%s</span>",
+ date("Y-m-d H:i:s", ($version['timestamp'] ?? 0)),
+ $version['commit'],
+ $version['branch'] ?? '',
+ $version['version']);
}
/**
@@ -315,8 +327,16 @@ class Config {
if (empty($this->version)) {
$this->version["status"] = -1;
- if (PHP_OS === "Darwin") {
- $ttrss_version["version"] = "UNKNOWN (Unsupported, Darwin)";
+ if (getenv("CI_COMMIT_SHORT_SHA") && getenv("CI_COMMIT_TIMESTAMP")) {
+
+ $this->version["branch"] = getenv("CI_COMMIT_BRANCH");
+ $this->version["timestamp"] = strtotime(getenv("CI_COMMIT_TIMESTAMP"));
+ $this->version["version"] = sprintf("%s-%s", date("y.m", $this->version["timestamp"]), getenv("CI_COMMIT_SHORT_SHA"));
+ $this->version["commit"] = getenv("CI_COMMIT_SHORT_SHA");
+ $this->version["status"] = 0;
+
+ } else if (PHP_OS === "Darwin") {
+ $this->version["version"] = "UNKNOWN (Unsupported, Darwin)";
} else if (file_exists("$root_dir/version_static.txt")) {
$this->version["version"] = trim(file_get_contents("$root_dir/version_static.txt")) . " (Unsupported)";
} else if (ini_get("open_basedir")) {
@@ -352,6 +372,7 @@ class Config {
$rv = [
"status" => -1,
"version" => "",
+ "branch" => "",
"commit" => "",
"timestamp" => 0,
];
diff --git a/classes/counters.php b/classes/counters.php
index 41bb1b9ae..fcf28f938 100644
--- a/classes/counters.php
+++ b/classes/counters.php
@@ -145,6 +145,7 @@ class Counters {
* @return array<int, array<string, int|string>>
*/
private static function get_feeds(array $feed_ids = null): array {
+ $scope = Tracer::start(__METHOD__);
$ret = [];
@@ -211,6 +212,8 @@ class Counters {
}
+ $scope->close();
+
return $ret;
}
@@ -218,6 +221,8 @@ class Counters {
* @return array<int, array<string, int|string>>
*/
private static function get_global(): array {
+ $scope = Tracer::start(__METHOD__);
+
$ret = [
[
"id" => "global-unread",
@@ -234,6 +239,8 @@ class Counters {
"counter" => $subcribed_feeds
]);
+ $scope->close();
+
return $ret;
}
@@ -241,6 +248,7 @@ class Counters {
* @return array<int, array<string, int|string>>
*/
private static function get_virt(): array {
+ $scope = Tracer::start(__METHOD__);
$ret = [];
@@ -287,6 +295,7 @@ class Counters {
}
}
+ $scope->close();
return $ret;
}
@@ -295,6 +304,7 @@ class Counters {
* @return array<int, array<string, int|string>>
*/
static function get_labels(array $label_ids = null): array {
+ $scope = Tracer::start(__METHOD__);
$ret = [];
@@ -346,6 +356,7 @@ class Counters {
array_push($ret, $cv);
}
+ $scope->close();
return $ret;
}
}
diff --git a/classes/digest.php b/classes/digest.php
index b19c37c5f..d77a83b8c 100644
--- a/classes/digest.php
+++ b/classes/digest.php
@@ -2,6 +2,7 @@
class Digest
{
static function send_headlines_digests(): void {
+ $scope = Tracer::start(__METHOD__);
$user_limit = 15; // amount of users to process (e.g. emails to send out)
$limit = 1000; // maximum amount of headlines to include
@@ -75,6 +76,8 @@ class Digest
}
}
}
+
+ $scope->close();
Debug::log("All done.");
}
diff --git a/classes/diskcache.php b/classes/diskcache.php
index 2a3f8c8d7..6a9038289 100644
--- a/classes/diskcache.php
+++ b/classes/diskcache.php
@@ -221,7 +221,11 @@ class DiskCache implements Cache_Adapter {
}
public function remove(string $filename): bool {
- return $this->adapter->remove($filename);
+ $scope = Tracer::start(__METHOD__, ['filename' => $filename]);
+ $rc = $this->adapter->remove($filename);
+ $scope->close();
+
+ return $rc;
}
public function set_dir(string $dir) : void {
@@ -244,14 +248,22 @@ class DiskCache implements Cache_Adapter {
}
public function exists(string $filename): bool {
- return $this->adapter->exists(basename($filename));
+ $scope = Tracer::start(__METHOD__, ['filename' => $filename]);
+ $rc = $this->adapter->exists(basename($filename));
+ $scope->close();
+
+ return $rc;
}
/**
* @return int|false -1 if the file doesn't exist, false if an error occurred, size in bytes otherwise
*/
public function get_size(string $filename) {
- return $this->adapter->get_size(basename($filename));
+ $scope = Tracer::start(__METHOD__, ['filename' => $filename]);
+ $rc = $this->adapter->get_size(basename($filename));
+ $scope->close();
+
+ return $rc;
}
/**
@@ -260,7 +272,11 @@ class DiskCache implements Cache_Adapter {
* @return int|false Bytes written or false if an error occurred.
*/
public function put(string $filename, $data) {
- return $this->adapter->put(basename($filename), $data);
+ $scope = Tracer::start(__METHOD__);
+ $rc = $this->adapter->put(basename($filename), $data);
+ $scope->close();
+
+ return $rc;
}
/** @deprecated we can't assume cached files are local, and other storages
@@ -304,11 +320,16 @@ class DiskCache implements Cache_Adapter {
}
public function send(string $filename) {
+ $scope = Tracer::start(__METHOD__, ['filename' => $filename]);
+
$filename = basename($filename);
if (!$this->exists($filename)) {
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
echo "File not found.";
+
+ $scope->getSpan()->setTag('error', '404 not found');
+ $scope->close();
return false;
}
@@ -317,6 +338,9 @@ class DiskCache implements Cache_Adapter {
if (($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? '') == $gmt_modified || ($_SERVER['HTTP_IF_NONE_MATCH'] ?? '') == $file_mtime) {
header('HTTP/1.1 304 Not Modified');
+
+ $scope->getSpan()->setTag('error', '304 not modified');
+ $scope->close();
return false;
}
@@ -334,6 +358,9 @@ class DiskCache implements Cache_Adapter {
header("Content-type: text/plain");
print "Stored file has disallowed content type ($mimetype)";
+
+ $scope->getSpan()->setTag('error', '400 disallowed content type');
+ $scope->close();
return false;
}
@@ -355,7 +382,13 @@ class DiskCache implements Cache_Adapter {
header_remove("Pragma");
- return $this->adapter->send($filename);
+ $scope->getSpan()->setTag('mimetype', $mimetype);
+
+ $rc = $this->adapter->send($filename);
+
+ $scope->close();
+
+ return $rc;
}
public function get_full_path(string $filename): string {
@@ -384,8 +417,14 @@ class DiskCache implements Cache_Adapter {
// plugins work on original source URLs used before caching
// NOTE: URLs should be already absolutized because this is called after sanitize()
static public function rewrite_urls(string $str): string {
+ $scope = Tracer::start(__METHOD__);
+
$res = trim($str);
- if (!$res) return '';
+
+ if (!$res) {
+ $scope->close();
+ return '';
+ }
$doc = new DOMDocument();
if (@$doc->loadHTML('<?xml encoding="UTF-8">' . $res)) {
@@ -397,6 +436,8 @@ class DiskCache implements Cache_Adapter {
$need_saving = false;
foreach ($entries as $entry) {
+ $e_scope = Tracer::start('entry', ['tagName' => $entry->tagName]);
+
foreach (array('src', 'poster') as $attr) {
if ($entry->hasAttribute($attr)) {
$url = $entry->getAttribute($attr);
@@ -428,6 +469,8 @@ class DiskCache implements Cache_Adapter {
$entry->setAttribute("srcset", RSSUtils::encode_srcset($matches));
}
+
+ $e_scope->close();
}
if ($need_saving) {
@@ -437,6 +480,9 @@ class DiskCache implements Cache_Adapter {
$res = $doc->saveHTML();
}
}
+
+ $scope->close();
+
return $res;
}
}
diff --git a/classes/feeds.php b/classes/feeds.php
index 002a9eae7..68619302f 100755
--- a/classes/feeds.php
+++ b/classes/feeds.php
@@ -49,12 +49,6 @@ class Feeds extends Handler_Protected {
const NEVER_GROUP_FEEDS = [ Feeds::FEED_RECENTLY_READ, Feeds::FEED_ARCHIVED ];
const NEVER_GROUP_BY_DATE = [ Feeds::FEED_PUBLISHED, Feeds::FEED_STARRED, Feeds::FEED_FRESH ];
- /** @var int|float int on 64-bit, float on 32-bit */
- private $viewfeed_timestamp;
-
- /** @var int|float int on 64-bit, float on 32-bit */
- private $viewfeed_timestamp_last;
-
function csrf_ignore(string $method): bool {
$csrf_ignored = array("index");
@@ -71,7 +65,7 @@ class Feeds extends Handler_Protected {
$disable_cache = false;
- $this->_mark_timestamp("init");
+ $scope = Tracer::start(__METHOD__, [], func_get_args());
$reply = [];
$rgba_cache = [];
@@ -157,8 +151,6 @@ class Feeds extends Handler_Protected {
$qfh_ret = $this->_get_headlines($params);
}
- $this->_mark_timestamp("db query");
-
$vfeed_group_enabled = get_pref(Prefs::VFEED_GROUP_BY_FEED) &&
!(in_array($feed, self::NEVER_GROUP_FEEDS) && !$cat_view);
@@ -176,6 +168,8 @@ class Feeds extends Handler_Protected {
$reply['search_query'] = [$search, $search_language];
$reply['vfeed_group_enabled'] = $vfeed_group_enabled;
+ $p_scope = Tracer::start('plugin_menu_items');
+
$plugin_menu_items = "";
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM2,
function ($result) use (&$plugin_menu_items) {
@@ -208,13 +202,15 @@ class Feeds extends Handler_Protected {
},
$feed, $cat_view, $qfh_ret);
- $this->_mark_timestamp("object header");
+ $p_scope->close();
+
+ $a_scope = Tracer::start('articles');
$headlines_count = 0;
if ($result instanceof PDOStatement) {
while ($line = $result->fetch(PDO::FETCH_ASSOC)) {
- $this->_mark_timestamp("article start: " . $line["id"] . " " . $line["title"]);
+ $aa_scope = Tracer::start('article', ['id' => $line['id']]);
++$headlines_count;
@@ -232,8 +228,6 @@ class Feeds extends Handler_Protected {
$line, $max_excerpt_length);
}
- $this->_mark_timestamp(" hook_query_headlines");
-
$id = $line["id"];
// frontend doesn't expect pdo returning booleans as strings on mysql
@@ -281,8 +275,6 @@ class Feeds extends Handler_Protected {
array_push($topmost_article_ids, $id);
}
- $this->_mark_timestamp(" labels");
-
$line["feed_title"] = $line["feed_title"] ?? "";
$button_doc = new DOMDocument();
@@ -312,6 +304,7 @@ class Feeds extends Handler_Protected {
$line);
$line["buttons"] = "";
+
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_ARTICLE_BUTTON,
function ($result, $plugin) use (&$line, &$button_doc) {
if ($result && $button_doc->loadXML($result)) {
@@ -335,13 +328,9 @@ class Feeds extends Handler_Protected {
},
$line);
- $this->_mark_timestamp(" pre-sanitize");
-
$line["content"] = Sanitizer::sanitize($line["content"],
$line['hide_images'], null, $line["site_url"], $highlight_words, $line["id"]);
- $this->_mark_timestamp(" sanitize");
-
if (!get_pref(Prefs::CDM_EXPANDED)) {
$line["cdm_excerpt"] = "<span class='collapse'>
<i class='material-icons' onclick='return Article.cdmUnsetActive(event)'
@@ -352,8 +341,6 @@ class Feeds extends Handler_Protected {
}
}
- $this->_mark_timestamp(" pre-enclosures");
-
if ($line["num_enclosures"] > 0) {
$line["enclosures"] = Article::_format_enclosures($id,
sql_bool_to_bool($line["always_display_enclosures"]),
@@ -363,16 +350,12 @@ class Feeds extends Handler_Protected {
$line["enclosures"] = [ 'formatted' => '', 'entries' => [] ];
}
- $this->_mark_timestamp(" enclosures");
-
$line["updated_long"] = TimeHelper::make_local_datetime($line["updated"],true);
$line["updated"] = TimeHelper::make_local_datetime($line["updated"], false, null, false, true);
$line['imported'] = T_sprintf("Imported at %s",
TimeHelper::make_local_datetime($line["date_entered"], false));
- $this->_mark_timestamp(" local-datetime");
-
if ($line["tag_cache"])
$tags = explode(",", $line["tag_cache"]);
else
@@ -382,14 +365,12 @@ class Feeds extends Handler_Protected {
//$line["tags"] = Article::_get_tags($line["id"], false, $line["tag_cache"]);
- $this->_mark_timestamp(" tags");
-
$line['has_icon'] = self::_has_icon($feed_id);
//setting feed headline background color, needs to change text color based on dark/light
$fav_color = $line['favicon_avg_color'] ?? false;
- $this->_mark_timestamp(" pre-color");
+ $c_scope = Tracer::start('colors');
require_once "colors.php";
@@ -405,22 +386,16 @@ class Feeds extends Handler_Protected {
$line['feed_bg_color'] = 'rgba(' . implode(",", $rgba_cache[$feed_id]) . ',0.3)';
}
- $this->_mark_timestamp(" color");
- $this->_mark_timestamp(" pre-hook_render_cdm");
+ $c_scope->close();
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_RENDER_ARTICLE_CDM,
function ($result, $plugin) use (&$line) {
$line = $result;
- $this->_mark_timestamp(" hook: " . get_class($plugin));
},
$line);
- $this->_mark_timestamp(" hook_render_cdm");
-
$line['content'] = DiskCache::rewrite_urls($line['content']);
- $this->_mark_timestamp(" disk_cache_rewrite");
-
/* we don't need those */
foreach (["date_entered", "guid", "last_published", "last_marked", "tag_cache", "favicon_avg_color",
@@ -429,11 +404,11 @@ class Feeds extends Handler_Protected {
array_push($reply['content'], $line);
- $this->_mark_timestamp("article end");
+ $aa_scope->close();
}
}
- $this->_mark_timestamp("end of articles");
+ $a_scope->close();
if (!$headlines_count) {
@@ -494,7 +469,7 @@ class Feeds extends Handler_Protected {
}
}
- $this->_mark_timestamp("end");
+ $scope->close();
return array($topmost_article_ids, $headlines_count, $feed, $disable_cache, $reply);
}
@@ -987,6 +962,7 @@ class Feeds extends Handler_Protected {
* @throws PDOException
*/
static function _get_counters($feed, bool $is_cat = false, bool $unread_only = false, ?int $owner_uid = null): int {
+ $scope = Tracer::start(__METHOD__, [], func_get_args());
$n_feed = (int) $feed;
$need_entries = false;
@@ -1010,11 +986,14 @@ class Feeds extends Handler_Protected {
$handler = PluginHost::getInstance()->get_feed_handler($feed_id);
if (implements_interface($handler, 'IVirtualFeed')) {
/** @var IVirtualFeed $handler */
+ $scope->close();
return $handler->get_unread($feed_id);
} else {
+ $scope->close();
return 0;
}
} else if ($n_feed == Feeds::FEED_RECENTLY_READ) {
+ $scope->close();
return 0;
// tags
} else if ($feed != "0" && $n_feed == 0) {
@@ -1028,6 +1007,7 @@ class Feeds extends Handler_Protected {
$row = $sth->fetch();
// Handle 'SUM()' returning null if there are no results
+ $scope->close();
return $row["count"] ?? 0;
} else if ($n_feed == Feeds::FEED_STARRED) {
@@ -1061,6 +1041,7 @@ class Feeds extends Handler_Protected {
$label_id = Labels::feed_to_label_id($feed);
+ $scope->close();
return self::_get_label_unread($label_id, $owner_uid);
}
@@ -1080,6 +1061,7 @@ class Feeds extends Handler_Protected {
$sth->execute([$owner_uid]);
$row = $sth->fetch();
+ $scope->close();
return $row["unread"];
} else {
@@ -1092,6 +1074,7 @@ class Feeds extends Handler_Protected {
$sth->execute([$feed, $owner_uid]);
$row = $sth->fetch();
+ $scope->close();
return $row["unread"];
}
}
@@ -1489,6 +1472,8 @@ class Feeds extends Handler_Protected {
*/
static function _get_headlines($params): array {
+ $scope = Tracer::start(__METHOD__, [], func_get_args());
+
$pdo = Db::pdo();
// WARNING: due to highly dynamic nature of this query its going to quote parameters
@@ -1981,8 +1966,9 @@ class Feeds extends Handler_Protected {
$res = $pdo->query($query);
}
- return array($res, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id, $vfeed_query_part != "", $query_error_override);
+ $scope->close();
+ return array($res, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id, $vfeed_query_part != "", $query_error_override);
}
/**
@@ -2495,23 +2481,5 @@ class Feeds extends Handler_Protected {
return [$query, $skip_first_id];
}
- private function _mark_timestamp(string $label): void {
-
- if (empty($_REQUEST['timestamps']))
- return;
-
- if (!$this->viewfeed_timestamp) $this->viewfeed_timestamp = hrtime(true);
- if (!$this->viewfeed_timestamp_last) $this->viewfeed_timestamp_last = hrtime(true);
-
- $timestamp = hrtime(true);
-
- printf("[%4d ms, %4d abs] %s\n",
- ($timestamp - $this->viewfeed_timestamp_last) / 1e6,
- ($timestamp - $this->viewfeed_timestamp) / 1e6,
- $label);
-
- $this->viewfeed_timestamp_last = $timestamp;
- }
-
}
diff --git a/classes/handler/public.php b/classes/handler/public.php
index d776e27cd..d7a7010fe 100755
--- a/classes/handler/public.php
+++ b/classes/handler/public.php
@@ -416,10 +416,10 @@ class Handler_Public extends Handler {
$_SESSION["login_error_msg"] ??= __("Incorrect username or password");
}
- $return = clean($_REQUEST['return']);
+ $return = clean($_REQUEST['return'] ?? '');
- if ($_REQUEST['return'] && mb_strpos($return, Config::get_self_url()) === 0) {
- header("Location: " . clean($_REQUEST['return']));
+ if ($return && mb_strpos($return, Config::get_self_url()) === 0) {
+ header("Location: $return");
} else {
header("Location: " . Config::get_self_url());
}
diff --git a/classes/icatchall.php b/classes/icatchall.php
new file mode 100644
index 000000000..29954d35a
--- /dev/null
+++ b/classes/icatchall.php
@@ -0,0 +1,4 @@
+<?php
+interface ICatchall {
+ function catchall(string $method): void;
+}
diff --git a/classes/pluginhost.php b/classes/pluginhost.php
index ab26780c7..bdbecca13 100755
--- a/classes/pluginhost.php
+++ b/classes/pluginhost.php
@@ -339,10 +339,13 @@ class PluginHost {
*/
function chain_hooks_callback(string $hook, Closure $callback, &...$args): void {
$method = strtolower((string)$hook);
+ $scope = Tracer::start(__METHOD__, ['hook' => $hook]);
foreach ($this->get_hooks((string)$hook) as $plugin) {
//Debug::log("invoking: " . get_class($plugin) . "->$hook()", Debug::$LOG_VERBOSE);
+ $p_scope = Tracer::start("$hook - " . get_class($plugin));
+
try {
if ($callback($plugin->$method(...$args), $plugin))
break;
@@ -351,7 +354,11 @@ class PluginHost {
} catch (Error $err) {
user_error($err, E_USER_WARNING);
}
+
+ $p_scope->close();
}
+
+ $scope->close();
}
/**
@@ -431,6 +438,8 @@ class PluginHost {
* @param PluginHost::KIND_* $kind
*/
function load(string $classlist, int $kind, int $owner_uid = null, bool $skip_init = false): void {
+ $scope = Tracer::start(__METHOD__);
+
$plugins = explode(",", $classlist);
$this->owner_uid = (int) $owner_uid;
@@ -439,18 +448,21 @@ class PluginHost {
$class = trim($class);
$class_file = strtolower(basename(clean($class)));
+ $p_scope = Tracer::start("loading $class_file");
+
// try system plugin directory first
$file = dirname(__DIR__) . "/plugins/$class_file/init.php";
if (!file_exists($file)) {
$file = dirname(__DIR__) . "/plugins.local/$class_file/init.php";
- if (!file_exists($file))
+ if (!file_exists($file)) {
+ $p_scope->close();
continue;
+ }
}
if (!isset($this->plugins[$class])) {
-
// WIP hack
// we can't catch incompatible method signatures via Throwable
// this also enables global tt-rss safe mode in case there are more plugins like this
@@ -464,6 +476,8 @@ class PluginHost {
$_SESSION["safe_mode"] = 1;
+ $p_scope->getSpan()->setTag('error', 'plugin is blacklisted');
+ $p_scope->close();
continue;
}
@@ -474,16 +488,21 @@ class PluginHost {
} catch (Error $err) {
user_error($err, E_USER_WARNING);
+
+ $p_scope->getSpan()->setTag('error', $err);
+ $p_scope->close();
continue;
}
if (class_exists($class) && is_subclass_of($class, "Plugin")) {
-
$plugin = new $class($this);
$plugin_api = $plugin->api_version();
if ($plugin_api < self::API_VERSION) {
user_error("Plugin $class is not compatible with current API version (need: " . self::API_VERSION . ", got: $plugin_api)", E_USER_WARNING);
+
+ $p_scope->getSpan()->setTag('error', 'plugin is not compatible with API version');
+ $p_scope->close();
continue;
}
@@ -492,6 +511,8 @@ class PluginHost {
_bind_textdomain_codeset($class, "UTF-8");
}
+ $i_scope = Tracer::start('init and register plugin');
+
try {
switch ($kind) {
case $this::KIND_SYSTEM:
@@ -516,11 +537,17 @@ class PluginHost {
} catch (Error $err) {
user_error($err, E_USER_WARNING);
}
+
+ $i_scope->close();
+
}
}
+ $p_scope->close();
}
$this->load_data();
+
+ $scope->close();
}
function is_system(Plugin $plugin): bool {
@@ -613,6 +640,8 @@ class PluginHost {
}
private function load_data(): void {
+ $scope = Tracer::start(__METHOD__);
+
if ($this->owner_uid && !$this->data_loaded && get_schema_version() > 100) {
$sth = $this->pdo->prepare("SELECT name, content FROM ttrss_plugin_storage
WHERE owner_uid = ?");
@@ -624,10 +653,13 @@ class PluginHost {
$this->data_loaded = true;
}
+
+ $scope->close();
}
private function save_data(string $plugin): void {
if ($this->owner_uid) {
+ $scope = Tracer::start(__METHOD__);
if (!$this->pdo_data)
$this->pdo_data = Db::instance()->pdo_connect();
@@ -655,6 +687,7 @@ class PluginHost {
}
$this->pdo_data->commit();
+ $scope->close();
}
}
diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php
index ed6560fd3..fb7e01af8 100755
--- a/classes/pref/feeds.php
+++ b/classes/pref/feeds.php
@@ -1104,12 +1104,16 @@ class Pref_Feeds extends Handler_Protected {
* @return array<string, mixed>
*/
private function feedlist_init_feed(int $feed_id, ?string $title = null, bool $unread = false, string $error = '', string $updated = ''): array {
+ $scope = Tracer::start(__METHOD__, []);
+
if (!$title)
$title = Feeds::_get_title($feed_id, false);
if ($unread === false)
$unread = Feeds::_get_counters($feed_id, false, true);
+ $scope->close();
+
return [
'id' => 'FEED:' . $feed_id,
'name' => $title,
diff --git a/classes/rpc.php b/classes/rpc.php
index 204b002d5..afd3c0c79 100755
--- a/classes/rpc.php
+++ b/classes/rpc.php
@@ -106,6 +106,8 @@ class RPC extends Handler_Protected {
}
function getAllCounters(): void {
+ $scope = Tracer::start(__METHOD__);
+
@$seq = (int) $_REQUEST['seq'];
$feed_id_count = (int) ($_REQUEST["feed_id_count"] ?? -1);
@@ -132,6 +134,7 @@ class RPC extends Handler_Protected {
'seq' => $seq
];
+ $scope->close();
print json_encode($reply);
}
diff --git a/classes/rssutils.php b/classes/rssutils.php
index 385ab31e6..d1096cc85 100755
--- a/classes/rssutils.php
+++ b/classes/rssutils.php
@@ -68,6 +68,8 @@ class RSSUtils {
* @param array<string, false|string> $options
*/
static function update_daemon_common(int $limit = 0, array $options = []): int {
+ $scope = Tracer::start(__METHOD__);
+
if (!$limit) $limit = Config::get(Config::DAEMON_FEED_LIMIT);
if (Config::get_schema_version() != Config::SCHEMA_VERSION) {
@@ -283,6 +285,8 @@ class RSSUtils {
// Send feed digests by email if needed.
Digest::send_headlines_digests();
+ $scope->close();
+
return $nf;
}
@@ -349,6 +353,7 @@ class RSSUtils {
static function update_rss_feed(int $feed, bool $no_cache = false) : bool {
+ $scope = Tracer::start(__METHOD__, [], func_get_args());
Debug::log("start", Debug::LOG_VERBOSE);
$pdo = Db::pdo();
@@ -383,16 +388,19 @@ class RSSUtils {
if ($user) {
if ($user->access_level == UserHelper::ACCESS_LEVEL_READONLY) {
Debug::log("error: denied update for $feed: permission denied by owner access level");
+ $scope->close();
return false;
}
} else {
// this would indicate database corruption of some kind
Debug::log("error: owner not found for feed: $feed");
+ $scope->close();
return false;
}
} else {
Debug::log("error: feeds table record not found for feed: $feed");
+ $scope->close();
return false;
}
@@ -550,6 +558,7 @@ class RSSUtils {
$feed_obj->save();
}
+ $scope->close();
return $error_message == "";
}
@@ -675,7 +684,7 @@ class RSSUtils {
]);
$feed_obj->save();
-
+ $scope->close();
return true; // no articles
}
@@ -684,6 +693,8 @@ class RSSUtils {
$tstart = time();
foreach ($items as $item) {
+ $a_scope = Tracer::start('article');
+
$pdo->beginTransaction();
Debug::log("=================================================================================================================================",
@@ -1276,6 +1287,7 @@ class RSSUtils {
Debug::log("article processed.", Debug::LOG_VERBOSE);
$pdo->commit();
+ $a_scope->close();
}
Debug::log("=================================================================================================================================",
@@ -1317,10 +1329,12 @@ class RSSUtils {
unset($rss);
Debug::log("update failed.", Debug::LOG_VERBOSE);
+ $scope->close();
return false;
}
Debug::log("update done.", Debug::LOG_VERBOSE);
+ $scope->close();
return true;
}
@@ -1485,6 +1499,8 @@ class RSSUtils {
* @return array<int, array<string, string>> An array of filter action arrays with keys "type" and "param"
*/
static function get_article_filters(array $filters, string $title, string $content, string $link, string $author, array $tags, array &$matched_rules = null, array &$matched_filters = null): array {
+ $scope = Tracer::start(__METHOD__);
+
$matches = array();
foreach ($filters as $filter) {
@@ -1526,6 +1542,9 @@ class RSSUtils {
$match = @preg_match("/$reg_exp/iu", $author);
break;
case "tag":
+ if (count($tags) == 0)
+ array_push($tags, ''); // allow matching if there are no tags
+
foreach ($tags as $tag) {
if (@preg_match("/$reg_exp/iu", $tag)) {
$match = true;
@@ -1565,6 +1584,8 @@ class RSSUtils {
}
}
+ $scope->close();
+
return $matches;
}
diff --git a/classes/sanitizer.php b/classes/sanitizer.php
index 8b4584a28..28874d16f 100644
--- a/classes/sanitizer.php
+++ b/classes/sanitizer.php
@@ -63,6 +63,7 @@ class Sanitizer {
* @return false|string The HTML, or false if an error occurred.
*/
public static function sanitize(string $str, ?bool $force_remove_images = false, int $owner = null, string $site_url = null, array $highlight_words = null, int $article_id = null) {
+ $scope = Tracer::start(__METHOD__);
if (!$owner && isset($_SESSION["uid"]))
$owner = $_SESSION["uid"];
@@ -223,6 +224,8 @@ class Sanitizer {
$res = $doc->saveHTML();
+ $scope->close();
+
/* strip everything outside of <body>...</body> */
$res_frag = array();
diff --git a/classes/tracer.php b/classes/tracer.php
new file mode 100644
index 000000000..5a23dfeba
--- /dev/null
+++ b/classes/tracer.php
@@ -0,0 +1,69 @@
+<?php
+use OpenTracing\GlobalTracer;
+use OpenTracing\Scope;
+
+class Tracer {
+ /** @var Tracer $instance */
+ private static $instance;
+
+ public function __construct() {
+ $jaeger_host = Config::get(Config::JAEGER_REPORTING_HOST);
+
+ if ($jaeger_host) {
+ $config = new \Jaeger\Config(
+ [
+ 'sampler' => [
+ 'type' => \Jaeger\SAMPLER_TYPE_CONST,
+ 'param' => true,
+ ],
+ 'logging' => true,
+ "local_agent" => [
+ "reporting_host" => $jaeger_host,
+ "reporting_port" => 6832
+ ],
+ 'dispatch_mode' => \Jaeger\Config::JAEGER_OVER_BINARY_UDP,
+ ],
+ Config::get(Config::JAEGER_SERVICE_NAME)
+ );
+
+ $config->initializeTracer();
+
+ register_shutdown_function(function() {
+ $tracer = GlobalTracer::get();
+ $tracer->flush();
+ });
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param array<string>|array<string, array<string, mixed>> $tags
+ * @param array<string> $args
+ * @return Scope
+ */
+ private function _start(string $name, array $tags = [], array $args = []): Scope {
+ $tracer = GlobalTracer::get();
+
+ $tags['args'] = json_encode($args);
+
+ return $tracer->startActiveSpan($name, ['tags' => $tags]);
+ }
+
+ /**
+ * @param string $name
+ * @param array<string>|array<string, array<string, mixed>> $tags
+ * @param array<string> $args
+ * @return Scope
+ */
+ public static function start(string $name, array $tags = [], array $args = []) : Scope {
+ return self::get_instance()->_start($name, $tags, $args);
+ }
+
+ public static function get_instance() : Tracer {
+ if (self::$instance == null)
+ self::$instance = new self();
+
+ return self::$instance;
+ }
+
+}
diff --git a/classes/urlhelper.php b/classes/urlhelper.php
index dc47f5ad8..6300e7346 100644
--- a/classes/urlhelper.php
+++ b/classes/urlhelper.php
@@ -185,10 +185,14 @@ class UrlHelper {
* @return false|string
*/
static function resolve_redirects(string $url, int $timeout, int $nest = 0) {
+ $scope = Tracer::start(__METHOD__, ['url' => $url]);
// too many redirects
- if ($nest > 10)
+ if ($nest > 10) {
+ $scope->getSpan()->setTag('error', 'too many redirects');
+ $scope->close();
return false;
+ }
$context_options = array(
'http' => array(
@@ -222,9 +226,12 @@ class UrlHelper {
}
}
+ $scope->close();
return $url;
}
+ $scope->getSpan()->setTag('error', 'request failed');
+ $scope->close();
// request failed?
return false;
}
@@ -270,8 +277,10 @@ class UrlHelper {
"useragent" => @func_get_arg(7)
); */
}
-
$url = $options["url"];
+
+ $scope = Tracer::start(__METHOD__, ['url' => $url]);
+
$type = isset($options["type"]) ? $options["type"] : false;
$login = isset($options["login"]) ? $options["login"] : false;
$pass = isset($options["pass"]) ? $options["pass"] : false;
@@ -293,6 +302,9 @@ class UrlHelper {
if (!$url) {
self::$fetch_last_error = "Requested URL failed extended validation.";
+
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -301,6 +313,9 @@ class UrlHelper {
if (!$ip_addr || strpos($ip_addr, "127.") === 0) {
self::$fetch_last_error = "URL hostname failed to resolve or resolved to a loopback address ($ip_addr)";
+
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -310,7 +325,12 @@ class UrlHelper {
$ch = curl_init($url);
- if (!$ch) return false;
+ if (!$ch) {
+ self::$fetch_last_error = "curl_init() failed";
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
+ return false;
+ }
$curl_http_headers = [];
@@ -412,6 +432,8 @@ class UrlHelper {
if (!self::validate(self::$fetch_effective_url, true)) {
self::$fetch_last_error = "URL received after redirection failed extended validation.";
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -420,6 +442,8 @@ class UrlHelper {
if (!self::$fetch_effective_ip_addr || strpos(self::$fetch_effective_ip_addr, "127.") === 0) {
self::$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address (".self::$fetch_effective_ip_addr.")";
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -435,6 +459,9 @@ class UrlHelper {
self::$fetch_last_error_content = $contents;
curl_close($ch);
+
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -445,6 +472,9 @@ class UrlHelper {
self::$fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
}
curl_close($ch);
+
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -458,6 +488,8 @@ class UrlHelper {
if ($tmp) $contents = $tmp;
}
+ $scope->close();
+
return $contents;
} else {
@@ -511,6 +543,8 @@ class UrlHelper {
if (!self::validate(self::$fetch_effective_url, true)) {
self::$fetch_last_error = "URL received after redirection failed extended validation.";
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -519,6 +553,8 @@ class UrlHelper {
if (!self::$fetch_effective_ip_addr || strpos(self::$fetch_effective_ip_addr, "127.") === 0) {
self::$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address (".self::$fetch_effective_ip_addr.")";
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -526,6 +562,9 @@ class UrlHelper {
if ($data === false) {
self::$fetch_last_error = "'file_get_contents' failed.";
+
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -561,6 +600,8 @@ class UrlHelper {
self::$fetch_last_error_content = $data;
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
@@ -573,9 +614,13 @@ class UrlHelper {
if ($tmp) $data = $tmp;
}
+ $scope->close();
return $data;
} else {
self::$fetch_last_error = 'Successful response, but no content was received.';
+
+ $scope->getSpan()->setTag('error', self::$fetch_last_error);
+ $scope->close();
return false;
}
}