Revision | c53b77c930cba0138049a5ea78f6ee961c5f9e65 (tree) |
---|---|
Time | 2018-02-27 00:10:34 |
Author | umorigu <umorigu@gmai...> |
Commiter | umorigu |
BugTrack/560 tracker_list speeding up with cache
* Implement 2-layer caching
* Get only updated/deleted page contents
* Detect page updates by file or page timestamps
* Refer cache/recent.dat file to detect updated pages
* Refer RecentDeleted page to detect deleted pages
* Handle page links properly on tracker_list table
* Save contents cache on cache/(TrackerBase).tracker
How to invalidate cache:
Execute one of following actions.
* Change tracker config file
* Change tracker list file
* Delete cache/*.tracker file manually
@@ -1,7 +1,7 @@ | ||
1 | 1 | <?php |
2 | 2 | // PukiWiki - Yet another WikiWikiWeb clone |
3 | 3 | // tracker.inc.php |
4 | -// Copyright 2003-2017 PukiWiki Development Team | |
4 | +// Copyright 2003-2018 PukiWiki Development Team | |
5 | 5 | // License: GPL v2 or (at your option) any later version |
6 | 6 | // |
7 | 7 | // Issue tracker plugin (See Also bugtrack plugin) |
@@ -15,6 +15,9 @@ define('TRACKER_LIST_EXCLUDE_PATTERN','#^SubMenu$|/#'); | ||
15 | 15 | // 項目の取り出しに失敗したページを一覧に表示する |
16 | 16 | define('TRACKER_LIST_SHOW_ERROR_PAGE',TRUE); |
17 | 17 | |
18 | +// Use cache | |
19 | +define('TRACKER_LIST_USE_CACHE', TRUE); | |
20 | + | |
18 | 21 | function plugin_tracker_convert() |
19 | 22 | { |
20 | 23 | global $vars; |
@@ -684,13 +687,13 @@ function plugin_tracker_list_action() | ||
684 | 687 | } |
685 | 688 | function plugin_tracker_getlist($page,$refer,$config_name,$list,$order='',$limit=NULL) |
686 | 689 | { |
687 | - $config = new Config('plugin/tracker/'.$config_name); | |
690 | + global $whatsdeleted; | |
688 | 691 | |
692 | + $config = new Config('plugin/tracker/'.$config_name); | |
689 | 693 | if (!$config->read()) |
690 | 694 | { |
691 | 695 | return "<p>config file '".htmlsc($config_name)."' is not exist.</p>"; |
692 | 696 | } |
693 | - | |
694 | 697 | $config->config_name = $config_name; |
695 | 698 | |
696 | 699 | if (!is_page($config->page.'/'.$list)) |
@@ -698,9 +701,79 @@ function plugin_tracker_getlist($page,$refer,$config_name,$list,$order='',$limit | ||
698 | 701 | return "<p>config file '".make_pagelink($config->page.'/'.$list)."' not found.</p>"; |
699 | 702 | } |
700 | 703 | |
701 | - $list = new Tracker_list($page,$refer,$config,$list); | |
702 | - $list->sort($order); | |
703 | - return $list->toString($limit); | |
704 | + $cache_enabled = defined('TRACKER_LIST_USE_CACHE') && TRACKER_LIST_USE_CACHE && | |
705 | + defined('JSON_UNESCAPED_UNICODE') && defined('PKWK_UTF8_ENABLE'); | |
706 | + $cache_filepath = CACHE_DIR . encode($page) . '.tracker'; | |
707 | + $cachedata = null; | |
708 | + $cache_format_version = 1; | |
709 | + if ($cache_enabled && file_exists($cache_filepath)) { | |
710 | + $config_filetime = get_filetime($config->page); | |
711 | + $config_list_filetime = get_filetime($config->page.'/'. $list); | |
712 | + $json_cached = pkwk_file_get_contents($cache_filepath); | |
713 | + if ($json_cached) { | |
714 | + $wrapdata = json_decode($json_cached, true); | |
715 | + if (is_array($wrapdata)) { | |
716 | + if (isset($wrapdata['version'], $wrapdata['html'], $wrapdata['refreshed_at'])) { | |
717 | + $cache_time_prev = $wrapdata['refreshed_at']; | |
718 | + if ($cache_format_version === $wrapdata['version']) { | |
719 | + if ($config_filetime === $wrapdata['config_updated_at'] && | |
720 | + $config_list_filetime === $wrapdata['config_list_updated_at']) { | |
721 | + $cachedata = $wrapdata; | |
722 | + } else { | |
723 | + // (Ignore) delete file | |
724 | + unlink($cache_filepath); | |
725 | + } | |
726 | + } | |
727 | + } | |
728 | + } | |
729 | + } | |
730 | + } | |
731 | + // Check recent.dat timestamp | |
732 | + $recent_dat_filemtime = filemtime(CACHE_DIR . PKWK_MAXSHOW_CACHE); | |
733 | + // Check RecentDeleted timestamp | |
734 | + $recent_deleted_filetime = get_filetime($whatsdeleted); | |
735 | + if (is_null($cachedata)) { | |
736 | + $cachedata = array(); | |
737 | + } else { | |
738 | + if ($recent_dat_filemtile !== false) { | |
739 | + if ($recent_dat_filemtime === $cachedata['recent_dat_filemtime'] && | |
740 | + $recent_deleted_filetime === $cachedata['recent_deleted_filetime'] && | |
741 | + $order === $cachedata['order']) { | |
742 | + // recent.dat is unchanged | |
743 | + // RecentDeleted is unchanged | |
744 | + // order is unchanged | |
745 | + return $cachedata['html']; | |
746 | + } | |
747 | + } | |
748 | + } | |
749 | + $cache_holder = $cachedata; | |
750 | + $tracker_list = new Tracker_list($page,$refer,$config,$list,$cache_holder); | |
751 | + if ($order === $cache_holder['order'] && | |
752 | + empty($tracker_list->newly_deleted_pages) && | |
753 | + empty($tracker_list->newly_updated_pages) && | |
754 | + !$tracker_list->link_update_required) { | |
755 | + $result = $cache_holder['html']; | |
756 | + } else { | |
757 | + $tracker_list->sort($order); | |
758 | + $result = $tracker_list->toString($limit); | |
759 | + } | |
760 | + if ($cache_enabled) { | |
761 | + $refreshed_at = time(); | |
762 | + $json = array( | |
763 | + 'refreshed_at' => $refreshed_at, | |
764 | + 'rows' => $tracker_list->rows, | |
765 | + 'html' => $result, | |
766 | + 'order' => $order, | |
767 | + 'config_updated_at' => $config_filetime, | |
768 | + 'config_list_updated_at' => $config_list_filetime, | |
769 | + 'recent_dat_filemtime' => $recent_dat_filemtime, | |
770 | + 'recent_deleted_filetime' => $recent_deleted_filetime, | |
771 | + 'link_pages' => $tracker_list->link_pages, | |
772 | + 'version' => $cache_format_version); | |
773 | + $cache_body = json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); | |
774 | + file_put_contents($cache_filepath, $cache_body, LOCK_EX); | |
775 | + } | |
776 | + return $result; | |
704 | 777 | } |
705 | 778 | |
706 | 779 | // 一覧クラス |
@@ -715,13 +788,16 @@ class Tracker_list | ||
715 | 788 | var $rows; |
716 | 789 | var $order; |
717 | 790 | var $sort_keys; |
791 | + var $newly_deleted_pages = array(); | |
792 | + var $newly_updated_pages = array(); | |
718 | 793 | |
719 | - function Tracker_list($page,$refer,&$config,$list) | |
794 | + function Tracker_list($page,$refer,&$config,$list,&$cache_holder) | |
720 | 795 | { |
721 | - $this->__construct($page, $refer, $config, $list); | |
796 | + $this->__construct($page, $refer, $config, $list, $cache_holder); | |
722 | 797 | } |
723 | - function __construct($page,$refer,&$config,$list) | |
798 | + function __construct($page,$refer,&$config,$list,&$cache_holder) | |
724 | 799 | { |
800 | + global $whatsdeleted; | |
725 | 801 | $this->page = $page; |
726 | 802 | $this->config = &$config; |
727 | 803 | $this->list = $list; |
@@ -746,22 +822,188 @@ class Tracker_list | ||
746 | 822 | $this->pattern .= '(.*?)'; |
747 | 823 | } |
748 | 824 | } |
749 | - // ページの列挙と取り込み | |
750 | - $this->rows = array(); | |
751 | - $pattern = "$page/"; | |
752 | - $pattern_len = strlen($pattern); | |
753 | - foreach (get_existpages() as $_page) | |
754 | - { | |
755 | - if (strpos($_page,$pattern) === 0) | |
825 | + if (empty($cache_holder)) { | |
826 | + // List pages and get contents (non-cache behavior) | |
827 | + $this->rows = array(); | |
828 | + $pattern = "$page/"; | |
829 | + $pattern_len = strlen($pattern); | |
830 | + foreach (get_existpages() as $_page) | |
756 | 831 | { |
757 | - $name = substr($_page,$pattern_len); | |
758 | - if (preg_match(TRACKER_LIST_EXCLUDE_PATTERN,$name)) | |
832 | + if (substr($_page, 0, $pattern_len) === $pattern) | |
759 | 833 | { |
760 | - continue; | |
834 | + $name = substr($_page,$pattern_len); | |
835 | + if (preg_match(TRACKER_LIST_EXCLUDE_PATTERN,$name)) | |
836 | + { | |
837 | + continue; | |
838 | + } | |
839 | + $this->add($_page,$name); | |
840 | + } | |
841 | + } | |
842 | + $this->link_pages = $this->get_filetimes($this->get_all_links()); | |
843 | + } else { | |
844 | + // Cache-available behavior | |
845 | + // Check RecentDeleted timestamp | |
846 | + $cached_rows = $this->decode_cached_rows($cache_holder['rows']); | |
847 | + $updated_linked_pages = array(); | |
848 | + $newly_deleted_pages = array(); | |
849 | + $pattern = "$page/"; | |
850 | + $pattern_len = strlen($pattern); | |
851 | + $recent_deleted_filetime = get_filetime($whatsdeleted); | |
852 | + $deleted_page_list = array(); | |
853 | + if ($recent_deleted_filetime !== $cache_holder['recent_deleted_filetime']) { | |
854 | + //pkwk_log(print_r()); | |
855 | + foreach (plugin_tracker_get_source($whatsdeleted) as $line) { | |
856 | + $m = null; | |
857 | + if (preg_match('#\[\[([^\]]+)\]\]#', $line, $m)) { | |
858 | + $_page = $m[1]; | |
859 | + if (is_pagename($_page)) { | |
860 | + $deleted_page_list[] = $m[1]; | |
861 | + } | |
862 | + } | |
863 | + } | |
864 | + foreach ($deleted_page_list as $_page) { | |
865 | + if (substr($_page, 0, $pattern_len) === $pattern) { | |
866 | + $name = substr($_page, $pattern_len); | |
867 | + if (!is_page($_page) && isset($cached_rows[$name]) && | |
868 | + !preg_match(TRACKER_LIST_EXCLUDE_PATTERN, $name)) { | |
869 | + // This page was just deleted | |
870 | + array_push($newly_deleted_pages, $_page); | |
871 | + unset($cached_rows[$name]); | |
872 | + } | |
873 | + } | |
761 | 874 | } |
762 | - $this->add($_page,$name); | |
763 | 875 | } |
876 | + $this->newly_deleted_pages = $newly_deleted_pages; | |
877 | + $updated_pages = array(); | |
878 | + $this->rows = $cached_rows; | |
879 | + // Check recent.dat timestamp | |
880 | + $recent_dat_filemtime = filemtime(CACHE_DIR . PKWK_MAXSHOW_CACHE); | |
881 | + $updated_page_list = array(); | |
882 | + if ($recent_dat_filemtime !== $cache_holder['recent_dat_filemtime']) { | |
883 | + // recent.dat was updated. Search which page was updated. | |
884 | + $target_pages = array(); | |
885 | + // Active page file time (1 hour before timestamp of recent.dat) | |
886 | + $target_filetime = $cache_holder['recent_dat_filemtime'] - LOCALZONE - 60 * 60; | |
887 | + foreach (get_recent_files() as $_page=>$time) { | |
888 | + if ($time <= $target_filetime) { | |
889 | + // Older updated pages | |
890 | + break; | |
891 | + } | |
892 | + $updated_page_list[$_page] = $time; | |
893 | + $name = substr($_page, $pattern_len); | |
894 | + if (substr($_page, 0, $pattern_len) === $pattern) { | |
895 | + $name = substr($_page, $pattern_len); | |
896 | + if (preg_match(TRACKER_LIST_EXCLUDE_PATTERN, $name)) { | |
897 | + continue; | |
898 | + } | |
899 | + // Tracker target page | |
900 | + if (isset($this->rows[$name])) { | |
901 | + // Existing page | |
902 | + $row = $this->rows[$name]; | |
903 | + if ($row['_update'] === get_filetime($_page)) { | |
904 | + // Same as cache | |
905 | + continue; | |
906 | + } else { | |
907 | + // Found updated page | |
908 | + $updated_pages[] = $_page; | |
909 | + unset($this->rows[$name]); | |
910 | + $this->add($_page, $name); | |
911 | + } | |
912 | + } else { | |
913 | + // Add new page | |
914 | + $updated_pages[] = $_page; | |
915 | + $this->add($_page, $name); | |
916 | + } | |
917 | + } | |
918 | + } | |
919 | + } | |
920 | + $this->newly_updated_pages = $updated_pages; | |
921 | + $new_link_names = $this->get_all_links(); | |
922 | + $old_link_map = array(); | |
923 | + foreach ($cache_holder['link_pages'] as $link_page) { | |
924 | + $old_link_map[$link_page['page']] = $link_page['filetime']; | |
925 | + } | |
926 | + $new_link_map = $old_link_map; | |
927 | + $link_update_required = false; | |
928 | + foreach ($deleted_page_list as $_page) { | |
929 | + if (in_array($_page, $new_link_names)) { | |
930 | + if (isset($old_link_map[$_page])) { | |
931 | + // This link keeps existing | |
932 | + if (!is_page($_page)) { | |
933 | + // OK. Confirmed the page doesn't exist | |
934 | + if ($old_link_map[$_page] === 0) { | |
935 | + // Do nothing (From no-page to no-page) | |
936 | + } else { | |
937 | + // This page was just deleted | |
938 | + $new_link_map[$_page] = get_filetime($_page); | |
939 | + $link_update_required = true; | |
940 | + } | |
941 | + } | |
942 | + } else { | |
943 | + // This link was just added | |
944 | + $new_link_map[$_page] = get_filetime($_page); | |
945 | + $link_update_required = true; | |
946 | + } | |
947 | + } | |
948 | + } | |
949 | + foreach ($updated_page_list as $_page=>$time) { | |
950 | + if (in_array($_page, $new_link_names)) { | |
951 | + if (isset($old_link_map[$_page])) { | |
952 | + // This link keeps existing | |
953 | + if (is_page($_page)) { | |
954 | + // OK. Confirmed the page now exists | |
955 | + if ($old_link_map[$_page] === 0) { | |
956 | + // This page was just added | |
957 | + $new_link_map[$_page] = get_filetime($_page); | |
958 | + $link_update_required = true; | |
959 | + } else { | |
960 | + // Do nothing (existing-page to existing-page) | |
961 | + } | |
962 | + } | |
963 | + } else { | |
964 | + // This link was just added | |
965 | + $new_link_map[$_page] = get_filetime($_page); | |
966 | + $link_update_required = true; | |
967 | + } | |
968 | + } | |
969 | + } | |
970 | + $new_link_pages = array(); | |
971 | + foreach ($new_link_map as $_page => $time) { | |
972 | + $new_link_pages[] = array( | |
973 | + 'page' => $_page, | |
974 | + 'filetime' => $time, | |
975 | + ); | |
976 | + } | |
977 | + $this->link_pages = $new_link_pages; | |
978 | + $this->link_update_required = $link_update_required; | |
979 | + } | |
980 | + } | |
981 | + function decode_cached_rows($decoded_rows) | |
982 | + { | |
983 | + $ar = array(); | |
984 | + foreach ($decoded_rows as $row) { | |
985 | + $ar[$row['_real']] = $row; | |
986 | + } | |
987 | + return $ar; | |
988 | + } | |
989 | + function get_all_links() { | |
990 | + $ar = array(); | |
991 | + foreach ($this->rows as $row) { | |
992 | + foreach ($row['_links'] as $link) { | |
993 | + $ar[$link] = 0; | |
994 | + } | |
995 | + } | |
996 | + return array_keys($ar); | |
997 | + } | |
998 | + function get_filetimes($pages) { | |
999 | + $filetimes = array(); | |
1000 | + foreach ($pages as $page) { | |
1001 | + $filetimes[] = array( | |
1002 | + 'page' => $page, | |
1003 | + 'filetime' => get_filetime($page), | |
1004 | + ); | |
764 | 1005 | } |
1006 | + return $filetimes; | |
765 | 1007 | } |
766 | 1008 | function add($page,$name) |
767 | 1009 | { |
@@ -786,22 +1028,38 @@ class Tracker_list | ||
786 | 1028 | } |
787 | 1029 | $source = join('',preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/','$1$2',$source)); |
788 | 1030 | |
789 | - // デフォルト値 | |
790 | - $this->rows[$name] = array( | |
1031 | + // Default value | |
1032 | + $page_filetime = get_filetime($page); | |
1033 | + $row = array( | |
791 | 1034 | '_page' => "[[$page]]", |
792 | 1035 | '_refer' => $this->page, |
793 | 1036 | '_real' => $name, |
794 | - '_update'=> get_filetime($page), | |
795 | - '_past' => get_filetime($page) | |
1037 | + '_update'=> $page_filetime, | |
1038 | + '_past' => $page_filetime, | |
796 | 1039 | ); |
797 | - if ($this->rows[$name]['_match'] = preg_match("/{$this->pattern}/s",$source,$matches)) | |
1040 | + $links = array(); | |
1041 | + if ($row['_match'] = preg_match("/{$this->pattern}/s",$source,$matches)) | |
798 | 1042 | { |
799 | 1043 | array_shift($matches); |
800 | 1044 | foreach ($this->pattern_fields as $key=>$field) |
801 | 1045 | { |
802 | - $this->rows[$name][$field] = trim($matches[$key]); | |
1046 | + $row[$field] = trim($matches[$key]); | |
1047 | + if ($field === '_refer') { | |
1048 | + continue; | |
1049 | + } | |
1050 | + $lmatch = null; | |
1051 | + if (preg_match('/\[\[([^\]\]]+)\]/', $row[$field], $lmatch)) { | |
1052 | + $link = $lmatch[1]; | |
1053 | + if (is_pagename($link) && $link !== $this->page && $link !== $page) { | |
1054 | + if (!in_array($link, $links)) { | |
1055 | + $links[] = $link; | |
1056 | + } | |
1057 | + } | |
1058 | + } | |
803 | 1059 | } |
804 | 1060 | } |
1061 | + $row['_links'] = $links; | |
1062 | + $this->rows[$name] = $row; | |
805 | 1063 | } |
806 | 1064 | function compare($a, $b) |
807 | 1065 | { |
@@ -1,7 +1,7 @@ | ||
1 | 1 | <?php |
2 | 2 | // PukiWiki - Yet another WikiWikiWeb clone |
3 | 3 | // tracker_list.inc.php |
4 | -// Copyright 2003-2017 PukiWiki Development Team | |
4 | +// Copyright 2003-2018 PukiWiki Development Team | |
5 | 5 | // License: GPL v2 or (at your option) any later version |
6 | 6 | // |
7 | 7 | // Issue tracker list plugin (a part of tracker plugin) |