├── .github
└── workflows
│ ├── matomo-tests.yml
│ └── phpcs.yml
├── .gitignore
├── CHANGELOG.md
├── Commands
├── LockStatus.php
├── Monitor.php
├── PrintQueuedRequests.php
├── Process.php
└── Test.php
├── Configuration.php
├── LICENSE
├── Queue.php
├── Queue
├── Backend.php
├── Backend
│ ├── MySQL.php
│ ├── Redis.php
│ ├── RedisCluster.php
│ └── Sentinel.php
├── Factory.php
├── Lock.php
├── LockExpiredException.php
├── Manager.php
├── Processor.php
└── Processor
│ └── Handler.php
├── QueuedTracking.php
├── README.md
├── Settings
└── NumWorkers.php
├── SystemCheck.php
├── SystemSettings.php
├── Tasks.php
├── Tracker
├── Handler.php
└── Response.php
├── Updates
├── 3.2.0.php
├── 3.3.4.php
└── 5.1.0.php
├── config
└── test.php
├── docs
├── How_it_works.png
└── faq.md
├── lang
├── am.json
├── ar.json
├── az.json
├── be.json
├── bg.json
├── bn.json
├── bs.json
├── ca.json
├── cs.json
├── cy.json
├── da.json
├── de.json
├── dv.json
├── el.json
├── en.json
├── eo.json
├── es-ar.json
├── es.json
├── et.json
├── eu.json
├── fa.json
├── fi.json
├── fr.json
├── ga.json
├── gl.json
├── gu.json
├── he.json
├── hi.json
├── hr.json
├── hu.json
├── hy.json
├── id.json
├── is.json
├── it.json
├── ja.json
├── ka.json
├── ko.json
├── ku.json
├── lb.json
├── lt.json
├── lv.json
├── ms.json
├── nb.json
├── nl.json
├── nn.json
├── pl.json
├── pt-br.json
├── pt.json
├── ro.json
├── ru.json
├── si.json
├── sk.json
├── sl.json
├── sq.json
├── sr.json
├── sv.json
├── ta.json
├── te.json
├── th.json
├── tl.json
├── tr.json
├── tzm.json
├── uk.json
├── ur.json
├── vi.json
├── zh-cn.json
└── zh-tw.json
├── libs
└── credis
│ ├── Client.php
│ ├── Cluster.php
│ ├── LICENSE
│ ├── Module.php
│ ├── README.markdown
│ ├── Sentinel.php
│ └── composer.json
├── phpcs.xml
├── plugin.json
├── pull_request_template.md
├── screenshots
└── Settings.png
└── tests
├── Framework
├── Mock
│ ├── ForcedException.php
│ ├── Tracker.php
│ └── Tracker
│ │ └── Response.php
└── TestCase
│ └── IntegrationTestCase.php
├── Integration
├── Queue
│ ├── Backend
│ │ ├── MysqlTest.php
│ │ ├── RedisTest.php
│ │ └── SentinelTest.php
│ ├── FactoryTest.php
│ ├── LockTest.php
│ ├── ManagerTest.php
│ ├── Processor
│ │ └── HandlerTest.php
│ └── ProcessorTest.php
├── QueueTest.php
├── QueuedTrackingTest.php
├── Settings
│ └── NumWorkersTest.php
├── SettingsTest.php
├── SystemCheckTest.php
└── Tracker
│ └── HandlerTest.php
├── System
├── CheckDirectDependencyUseCommandTest.php
└── TrackerTest.php
├── UI
├── QueuedTrackingSettings_spec.js
└── expected-ui-screenshots
│ ├── QueuedTrackingSettings_settings_page.png
│ ├── QueuedTrackingSettings_settings_page_sentinel.png
│ └── QueuedTrackingSettings_settings_save_error.png
└── Unit
├── ConfigurationTest.php
├── Queue
├── Processor
│ └── HandlerTest.php
└── Requests
│ └── TestQueuedRequest.json
└── QueueTest.php
/.github/workflows/matomo-tests.yml:
--------------------------------------------------------------------------------
1 | # Action for running tests
2 | # This file has been automatically created.
3 | # To recreate it you can run this command
4 | # ./console generate:test-action --plugin="QueuedTracking" --php-versions="7.2,8.4" --enable-redis --schedule-cron="0 2 * * 6"
5 |
6 | name: Plugin QueuedTracking Tests
7 |
8 | on:
9 | pull_request:
10 | types: [opened, synchronize]
11 | push:
12 | branches:
13 | - '**.x-dev'
14 | workflow_dispatch:
15 | schedule:
16 | - cron: "0 2 * * 6"
17 |
18 | permissions:
19 | actions: read
20 | checks: none
21 | contents: read
22 | deployments: none
23 | issues: read
24 | packages: none
25 | pull-requests: read
26 | repository-projects: none
27 | security-events: none
28 | statuses: none
29 |
30 | concurrency:
31 | group: php-${{ github.ref }}
32 | cancel-in-progress: true
33 |
34 | jobs:
35 | PluginTests:
36 | runs-on: ubuntu-24.04
37 | strategy:
38 | fail-fast: false
39 | matrix:
40 | php: [ '7.2', '8.4' ]
41 | target: ['minimum_required_matomo', 'maximum_supported_matomo']
42 | steps:
43 | - uses: actions/checkout@v3
44 | with:
45 | lfs: true
46 | persist-credentials: false
47 | - name: Install package ripgrep
48 | run: sudo apt-get install ripgrep
49 | - name: Run tests
50 | uses: matomo-org/github-action-tests@main
51 | with:
52 | plugin-name: 'QueuedTracking'
53 | php-version: ${{ matrix.php }}
54 | test-type: 'PluginTests'
55 | matomo-test-branch: ${{ matrix.target }}
56 | redis-service: true
57 | artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
58 | upload-artifacts: ${{ matrix.php == '7.2' && matrix.target == 'maximum_supported_matomo' }}
59 | UI:
60 | runs-on: ubuntu-24.04
61 | steps:
62 | - uses: actions/checkout@v3
63 | with:
64 | lfs: true
65 | persist-credentials: false
66 | - name: running tests
67 | uses: matomo-org/github-action-tests@main
68 | with:
69 | plugin-name: 'QueuedTracking'
70 | matomo-test-branch: 'maximum_supported_matomo'
71 | test-type: 'UI'
72 | php-version: '7.2'
73 | node-version: '16'
74 | redis-service: true
75 | artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
76 | upload-artifacts: true
77 |
--------------------------------------------------------------------------------
/.github/workflows/phpcs.yml:
--------------------------------------------------------------------------------
1 | name: PHPCS check
2 |
3 | on: pull_request
4 |
5 | permissions:
6 | actions: read
7 | checks: read
8 | contents: read
9 | deployments: none
10 | issues: read
11 | packages: none
12 | pull-requests: read
13 | repository-projects: none
14 | security-events: none
15 | statuses: read
16 |
17 | jobs:
18 | phpcs:
19 | name: PHPCS
20 | runs-on: ubuntu-24.04
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | lfs: false
25 | persist-credentials: false
26 | - name: Setup PHP
27 | uses: shivammathur/setup-php@v2
28 | with:
29 | php-version: '7.4'
30 | tools: cs2pr
31 | - name: Install dependencies
32 | run:
33 | composer init --name=matomo/queuedtracking --quiet;
34 | composer --no-plugins config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true -n;
35 | composer config repositories.matomo-coding-standards vcs https://github.com/matomo-org/matomo-coding-standards -n;
36 | composer require matomo-org/matomo-coding-standards:dev-master;
37 | composer install --dev --prefer-dist --no-progress --no-suggest
38 | - name: Check PHP code styles
39 | id: phpcs
40 | run: ./vendor/bin/phpcs --report-full --standard=phpcs.xml --report-checkstyle=./phpcs-report.xml
41 | - name: Show PHPCS results in PR
42 | if: ${{ always() && steps.phpcs.outcome == 'failure' }}
43 | run: cs2pr ./phpcs-report.xml --prepend-filename
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /tests/UI/processed-ui-screenshots
2 | /screenshot-diffs
3 | .DS_Store
4 | .idea
5 | .Spotlight-V100
6 | .Trashes
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Changelog
2 |
3 | 5.1.1 - 2021-10-23
4 | - Fixed fatal error when running queuedtracking:monitor command on Windows
5 |
6 | 5.1.0 - 2021-10-21
7 | - Increased number of queue tracking workers to 4096
8 | - Enhance queue monitor and process commands
9 | - Added Redis cluster option
10 |
11 | 5.0.7 - 2024-08-26
12 | - Fixed old redis sentinel config issue
13 |
14 | 5.0.6
15 | - Fixed unable to configure redis socket with port 0
16 |
17 | 5.0.5
18 | - Improved plugin reliability
19 | - Added system settings to list of text that can be translated
20 |
21 | 5.0.4
22 | - Added plugin category for Marketplace
23 |
24 | 5.0.3
25 | - Added code to skip maxmemory config check when --skip-max-memory-config-check=1
26 |
27 | 5.0.2
28 | - Fix merge issue in previous release
29 |
30 | 5.0.1
31 | - Compatibility with Matomo 5.0.0-b4
32 |
33 | 5.0.0
34 | - Compatibility with Matomo 5.0
35 |
36 | 4.0.7
37 | - Improved error handling for tracking requests
38 |
39 | 4.0.6
40 | - Started using flushDB instead of flushAll for Redis
41 |
42 | 4.0.5
43 | - Translation changes
44 |
45 | 4.0.4
46 | - Clarify inline help for "Queue enabled" config setting
47 |
48 | 4.0.3
49 | - Replace Redis::delete() with Redis::del() and fix a warning
50 |
51 | 4.0.2
52 | - Support new option `--force-num-requests-process-at-once` to the process command
53 |
54 | 4.0.1
55 | - Improve compatibility with PHP 7.4
56 |
57 | 4.0.0
58 | - Compatibility with Matomo 4.0
59 |
60 | 3.3.5
61 | - Improve update script to first add primary key and then remove index
62 |
63 | 3.3.4
64 | - Use primary key instead of a unique index for mysql backend for better replication
65 |
66 | 3.3.3
67 | - Add possibility to ignore queued tracking handler and track request directly into the database
68 |
69 | 3.3.2
70 | - Send branded HTML email
71 |
72 | 3.3.1
73 | - Support MySQLi adapter
74 |
75 | 3.3.0
76 | - When using 3rd party cookies, the 3rd party cookie value will not be overwritten by local site visitor id values
77 |
78 | 3.2.1
79 | - Faster queue locking
80 | - More debug output while processing
81 |
82 | 3.2.0
83 | - Added possibility to use a MySQL backend instead of redis
84 | - New option `queue-id` for the `queuedtracking:process` command which may improve processing speed as the command would only focus on one queue instead of trying to get the lock for a random queue.
85 | - Various other minor performance improvements
86 | - New feature: Get notified by email when a single queue reaches a specific threshold
87 |
88 | 3.0.2
89 |
90 | - Ensure do not track cookie works
91 |
92 | 3.0.1
93 |
94 | - Added possibility to define a unix socket instead of a host and path.
95 |
96 | 3.0.0
97 |
98 | - Compatibility with Piwik 3.0
99 |
100 | 0.3.2
101 |
102 | - Fixes a bug in the lock-status command where it may report a queue as locked while it was not
103 |
104 | 0.3.1
105 |
106 | - Fixed Redis Sentinel was not working properly. Sentinel can be now configured via the UI and not via config. Also
107 | multiple servers can be configured now.
108 |
109 | 0.3.0
110 |
111 | - Added support to use Redis Sentinel for automatic failover
112 |
113 | 0.2.6
114 |
115 | - When a request takes more than 2 seconds and debug tracker mode is enabled, log information about the request.
116 |
117 | 0.2.5
118 |
119 | - Use a better random number generator if available on the system to more evenly process queues.
120 |
121 | 0.2.4
122 |
123 | - The command `queuedtracking:monitor` will now work even when the queue is disabled
124 |
125 | 0.2.3
126 |
127 | - Added more tests and information to the `queuedtracking:test` command
128 | - It is now possible to configure up to 16 workers
129 |
130 | 0.2.2
131 |
132 | - Improved output for the new `test` command
133 | - New FAQ entries
134 |
135 | 0.2.1
136 |
137 | - Added a new command to test the connection to Redis. To test yor connection use `./console queuedtracking:test`
138 |
139 | 0.2.0
140 |
141 | - Compatibility w/ Piwik 2.15.
142 |
143 | 0.1.6
144 |
145 | - For bulk requests we do no longer skip all tracking requests after a tracking request that has an invalid `idSite` set. The same behaviour was changed in Piwik 2.14 for regular bulk requests.
146 |
147 | 0.1.5
148 |
149 | - Fixed a notice in case an incompatible Redis version is used.
150 |
151 | 0.1.4
152 |
153 | - It is now possible to start multiple workers for faster insertion from Redis to the database. This can be configured in the "Plugin Settings"
154 | - Monitor does now output information whether a processor is currently processing the queue.
155 | - Added a new command `queuedtracking:lock-status` that outputs the status of each queue lock. This command can also unlock a queue by using the option `--unlock`.
156 | - Added a new command `queuedtracking:print-queued-requests` that outputs the next requests to process in each queue.
157 | - If someone passes the option `-vvv` to `./console queuedtracking:process` the Tracker debug mode will be enabled and additional information will be printed to the screen.
158 |
159 | 0.1.2
160 |
161 | - Updated description on Marketplace
162 |
163 | 0.1.0
164 |
165 | - Initial Release
166 |
--------------------------------------------------------------------------------
/Commands/LockStatus.php:
--------------------------------------------------------------------------------
1 | setName('queuedtracking:lock-status');
22 | $this->setDescription('Outputs information for the status of each locked queue. Unlocking a queue is possible as well.');
23 | $this->addRequiredValueOption('unlock', null, 'If set will unlock the given queue.');
24 | }
25 |
26 | /**
27 | * @return int
28 | */
29 | protected function doExecute(): int
30 | {
31 | $input = $this->getInput();
32 | $output = $this->getOutput();
33 | $settings = Queue\Factory::getSettings();
34 | if ($settings->isRedisBackend()) {
35 | $systemCheck = new SystemCheck();
36 | $systemCheck->checkRedisIsInstalled();
37 | }
38 |
39 | $backend = Queue\Factory::makeBackend();
40 | $lock = Queue\Factory::makeLock($backend);
41 | $keys = $lock->getAllAcquiredLockKeys();
42 |
43 | $keyToUnlock = $input->getOption('unlock');
44 |
45 | if ($keyToUnlock && in_array($keyToUnlock, $keys)) {
46 | $backend->delete($keyToUnlock);
47 | $this->writeSuccessMessage(array(sprintf('Key %s unlocked', $keyToUnlock)));
48 | } elseif ($keyToUnlock) {
49 | $output->writeln(sprintf('%s is not or no longer locked', $keyToUnlock));
50 | $output->writeln(' ');
51 | }
52 |
53 | foreach ($keys as $lockKey) {
54 | $time = $backend->getTimeToLive($lockKey);
55 | if (!empty($time)) {
56 | $output->writeln(sprintf('"%s" is locked for %d ms', $lockKey, $time));
57 | $output->writeln(sprintf('Set option --unlock=%s to unlock the queue.', $lockKey));
58 | $output->writeln(' ');
59 | }
60 | }
61 |
62 | return self::SUCCESS;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Commands/PrintQueuedRequests.php:
--------------------------------------------------------------------------------
1 | setName('queuedtracking:print-queued-requests');
22 | $this->setDescription('Prints the requests of each queue that will be processed next.');
23 | $this->addRequiredValueOption('queue-id', null, 'If set, will print only requests of that queue');
24 | }
25 |
26 | /**
27 | * @return int
28 | */
29 | protected function doExecute(): int
30 | {
31 | $input = $this->getInput();
32 | $output = $this->getOutput();
33 | $settings = Queue\Factory::getSettings();
34 | if ($settings->isRedisBackend()) {
35 | $systemCheck = new SystemCheck();
36 | $systemCheck->checkRedisIsInstalled();
37 | }
38 |
39 | $backend = Queue\Factory::makeBackend();
40 | $manager = Queue\Factory::makeQueueManager($backend);
41 |
42 | $queueId = $input->getOption('queue-id');
43 |
44 | foreach ($manager->getAllQueues() as $index => $queue) {
45 | $thisQueueId = $queue->getId();
46 |
47 | if (isset($queueId) && $queueId != $thisQueueId) {
48 | continue;
49 | }
50 |
51 | $output->writeln(sprintf('Showing requests of queue %s. Use --queue-id=%s to print only information for this queue.', $thisQueueId, $thisQueueId));
52 |
53 | $requests = $queue->getRequestSetsToProcess();
54 | $output->writeln(var_export($requests, 1));
55 |
56 | $output->writeln(sprintf('These were the requests of queue %s. Use --queue-id=%s to print only information for this queue.', $thisQueueId, $thisQueueId));
57 | }
58 |
59 | return self::SUCCESS;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Commands/Process.php:
--------------------------------------------------------------------------------
1 | setName('queuedtracking:process');
27 | $this->addRequiredValueOption(
28 | 'queue-id',
29 | null,
30 | 'If set, will only work on that specific queue. For example "0" or "1" (if there are multiple queues). Not recommended when only one worker is in use. If for example 4 workers are in use, you may want to use 0, 1, 2, or 3.'
31 | );
32 | $this->addRequiredValueOption(
33 | 'force-num-requests-process-at-once',
34 | null,
35 | 'If defined, it overwrites the setting of how many requests will be picked out of the queue and processed at once. Must be a number which is >= 1. By default, the configured value from the settings will be used.' .
36 | ' This can be useful for example if you want to process every single request within the queue.' .
37 | ' If otherwise a batch size of say 100 is configured, then there may be otherwise 99 requests left in the queue. It can be also useful for testing purposes.'
38 | );
39 | $this->addRequiredValueOption('cycle', 'c', 'The proccess will automatically loop for "n" cycle time(s), set "0" to infinite.', 1);
40 | $this->addRequiredValueOption('sleep', 's', 'Take a nap for "n" second(s) before recycle, minimum is 1 second.', 1);
41 | $this->addRequiredValueOption('delay', 'd', 'Delay before finished', 0);
42 | $this->setDescription('Processes all queued tracking requests in case there are enough requests in the queue and in case they are not already in process by another script. To keep track of the queue use the --verbose' .
43 | ' option or execute the queuedtracking:monitor command.');
44 | }
45 |
46 | /**
47 | * @return int
48 | */
49 | protected function doExecute(): int
50 | {
51 | $input = $this->getInput();
52 | $output = $this->getOutput();
53 | $settings = Queue\Factory::getSettings();
54 | if ($settings->isRedisBackend()) {
55 | $systemCheck = new SystemCheck();
56 | $systemCheck->checkRedisIsInstalled();
57 | }
58 |
59 | $queueId = $input->getOption('queue-id');
60 | if (empty($queueId) && $queueId !== 0 && $queueId !== '0') {
61 | $queueId = null;
62 | } elseif (!is_numeric($queueId)) {
63 | throw new \Exception('queue-id needs to be numeric');
64 | } else {
65 | $queueId = (int) $queueId;
66 | $output->writeln("Forcing queue ID: " . $queueId);
67 | }
68 |
69 | if ($output->isVeryVerbose()) {
70 | $GLOBALS['PIWIK_TRACKER_DEBUG'] = true;
71 | }
72 |
73 | $trackerEnvironment = new Environment('tracker');
74 | $trackerEnvironment->init();
75 |
76 | Log::unsetInstance();
77 | $trackerEnvironment->getContainer()->get('Piwik\Access')->setSuperUserAccess(false);
78 | $trackerEnvironment->getContainer()->get('Piwik\Plugin\Manager')->setTrackerPluginsNotToLoad(array('Provider'));
79 | Tracker::loadTrackerEnvironment();
80 |
81 | $backend = Queue\Factory::makeBackend();
82 | $queueManager = Queue\Factory::makeQueueManager($backend);
83 | $queueManager->setForceQueueId($queueId);
84 |
85 | $forceNumRequests = $input->getOption('force-num-requests-process-at-once');
86 | if (!empty($forceNumRequests) && is_numeric($forceNumRequests) && $forceNumRequests >= 1) {
87 | $output->writeln("Force proccess num requests at once: " . $forceNumRequests);
88 | $queueManager->setNumberOfRequestsToProcessAtSameTime($forceNumRequests);
89 | } elseif (!empty($forceNumRequests)) {
90 | throw new \Exception('Number of requests to process must be a number and at least 1');
91 | }
92 |
93 | register_shutdown_function(function () use ($queueManager) {
94 | $queueManager->unlock();
95 | });
96 |
97 |
98 |
99 | $numberOfProcessCycle = $input->getOption('cycle');
100 | if (!is_numeric($numberOfProcessCycle)) {
101 | throw new \Exception('"cycle" needs to be numeric');
102 | }
103 | $numberOfProcessCycle = (int)$numberOfProcessCycle;
104 | $infiniteCycle = $numberOfProcessCycle == 0;
105 |
106 | $delayedBeforeFinish = (int)$input->getOption('delay');
107 |
108 | $napster = max(1, $input->getOption('sleep'));
109 | if (!is_numeric($napster)) {
110 | throw new \Exception('"nap" needs to be numeric');
111 | }
112 | $napster = (int)$napster;
113 |
114 | $lastTimeGotMoreThanZeroTrackedReq = microtime(true);
115 | $originalNumberOfRequestsToProcessAtSameTime = $queueManager->getNumberOfRequestsToProcessAtSameTime();
116 |
117 | while ($numberOfProcessCycle > 0 || $infiniteCycle) {
118 | $wipingOutQueue = false;
119 | if (microtime(true) - $lastTimeGotMoreThanZeroTrackedReq > 10) {
120 | $queueManager->setNumberOfRequestsToProcessAtSameTime(1);
121 | $wipingOutQueue = true;
122 | $lastTimeGotMoreThanZeroTrackedReq = microtime(true);
123 | }
124 |
125 | if ($wipingOutQueue) {
126 | $output->writeln(" TRYING TO WIPE OUT THE QUEUE >");
127 | }
128 | $output->writeln("Starting to process request sets, this can take a while");
129 |
130 | $startTime = microtime(true);
131 | $processor = new Processor($queueManager);
132 | $processor->setNumberOfMaxBatchesToProcess(500);
133 | $tracker = $processor->process();
134 |
135 | $neededTime = (microtime(true) - $startTime);
136 | $numRequestsTracked = $tracker->getCountOfLoggedRequests();
137 | $requestsPerSecond = $this->getNumberOfRequestsPerSecond($numRequestsTracked, $neededTime);
138 |
139 | $this->writeSuccessMessage(
140 | array(sprintf('This worker finished queue processing with %sreq/s (%s requests in %02.2f seconds)', $requestsPerSecond, $numRequestsTracked, $neededTime))
141 | );
142 | Piwik::postEvent('Tracker.end');
143 |
144 | if ($numRequestsTracked > 0) {
145 | $lastTimeGotMoreThanZeroTrackedReq = microtime(true);
146 | }
147 |
148 | if (!$infiniteCycle) {
149 | $numberOfProcessCycle--;
150 | }
151 | if ($numberOfProcessCycle > 0 || $infiniteCycle) {
152 | $cTogo = $infiniteCycle ? "infinite" : $numberOfProcessCycle;
153 | $output->writeln("===========================================================================");
154 | $output->writeln("Taking a nap for {$napster} second(s), before re-running the process. ({$cTogo}) cyle(s) to go.");
155 | $output->writeln("===========================================================================");
156 | sleep($napster);
157 | }
158 |
159 | if ($wipingOutQueue) {
160 | $queueManager->setNumberOfRequestsToProcessAtSameTime($originalNumberOfRequestsToProcessAtSameTime);
161 | }
162 | }
163 |
164 | // Piwik::postEvent('Tracker.end');
165 | $trackerEnvironment->destroy();
166 |
167 | if ($delayedBeforeFinish > 0) {
168 | sleep($delayedBeforeFinish);
169 | }
170 |
171 | return self::SUCCESS;
172 | }
173 |
174 | private function getNumberOfRequestsPerSecond($numRequestsTracked, $neededTimeInSeconds)
175 | {
176 | if (empty($neededTimeInSeconds)) {
177 | $requestsPerSecond = $numRequestsTracked;
178 | } else {
179 | $requestsPerSecond = round($numRequestsTracked / $neededTimeInSeconds, 2);
180 | }
181 |
182 | return $requestsPerSecond;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/Configuration.php:
--------------------------------------------------------------------------------
1 | getConfig();
27 |
28 | if (empty($config->QueuedTracking)) {
29 | $config->QueuedTracking = array();
30 | }
31 | $reports = $config->QueuedTracking;
32 |
33 | // we make sure to set a value only if none has been configured yet, eg in common config.
34 | if (empty($reports[self::KEY_NOTIFY_THRESHOLD])) {
35 | $reports[self::KEY_NOTIFY_THRESHOLD] = self::DEFAULT_NOTIFY_THRESHOLD;
36 | }
37 | if (empty($reports[self::KEY_NOTIFY_EMAILS])) {
38 | $reports[self::KEY_NOTIFY_EMAILS] = self::$DEFAULT_NOTIFY_EMAILS;
39 | }
40 | $config->QueuedTracking = $reports;
41 |
42 | $config->forceSave();
43 | }
44 |
45 | public function uninstall()
46 | {
47 | $config = $this->getConfig();
48 | $config->QueuedTracking = array();
49 | $config->forceSave();
50 | }
51 |
52 | /**
53 | * @return int
54 | */
55 | public function getNotifyThreshold()
56 | {
57 | $value = $this->getConfigValue(self::KEY_NOTIFY_THRESHOLD, self::DEFAULT_NOTIFY_THRESHOLD);
58 |
59 | if ($value === false || $value === '' || $value === null) {
60 | $value = self::DEFAULT_NOTIFY_THRESHOLD;
61 | }
62 |
63 | return (int) $value;
64 | }
65 |
66 | /**
67 | * @return array
68 | */
69 | public function getNotifyEmails()
70 | {
71 | $value = $this->getConfigValue(self::KEY_NOTIFY_EMAILS, self::$DEFAULT_NOTIFY_EMAILS);
72 |
73 | if (empty($value)) {
74 | $value = self::$DEFAULT_NOTIFY_EMAILS;
75 | }
76 | if (!is_array($value)) {
77 | $value = array($value);
78 | }
79 |
80 | return $value;
81 | }
82 |
83 | public function shouldLogFailedTrackingRequestsBody(): bool
84 | {
85 | return (bool) $this->getConfigValue(self::LOG_FAILED_TRACKING_REQUESTS, self::LOG_FAILED_TRACKING_REQUESTS_DEFAULT);
86 | }
87 |
88 | private function getConfig()
89 | {
90 | return Config::getInstance();
91 | }
92 |
93 | private function getConfigValue($name, $default)
94 | {
95 | $config = $this->getConfig();
96 | $attribution = $config->QueuedTracking;
97 | if (isset($attribution[$name])) {
98 | return $attribution[$name];
99 | }
100 | return $default;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Queue.php:
--------------------------------------------------------------------------------
1 | backend = $backend;
36 |
37 | $this->id = $id;
38 | $this->key = self::PREFIX;
39 |
40 | if (!empty($id)) {
41 | $this->key .= '_' . $id;
42 | }
43 | }
44 |
45 | public function getId()
46 | {
47 | return $this->id;
48 | }
49 |
50 | public function getKey()
51 | {
52 | return $this->key;
53 | }
54 |
55 | public function setNumberOfRequestsToProcessAtSameTime($numRequests)
56 | {
57 | $this->numRequestsToProcessInBulk = $numRequests;
58 | }
59 |
60 | public function getNumberOfRequestsToProcessAtSameTime()
61 | {
62 | return $this->numRequestsToProcessInBulk;
63 | }
64 |
65 | public function getNumberOfRequestSetsInQueue()
66 | {
67 | return $this->backend->getNumValuesInList($this->key);
68 | }
69 |
70 | public function addRequestSet(RequestSet $requests)
71 | {
72 | if (!$requests->hasRequests()) {
73 | return;
74 | }
75 |
76 | $value = $requests->getState();
77 | $value = json_encode($value);
78 |
79 | $this->backend->appendValuesToList($this->key, array($value));
80 | }
81 |
82 | public function delete()
83 | {
84 | return $this->backend->delete($this->key);
85 | }
86 |
87 | /**
88 | * @return RequestSet[]
89 | */
90 | public function getRequestSetsToProcess()
91 | {
92 | $values = $this->backend->getFirstXValuesFromList($this->key, $this->numRequestsToProcessInBulk);
93 |
94 | $requests = array();
95 | foreach ($values as $value) {
96 | $params = json_decode($value, true);
97 | $this->ensureJsonVarsAreStrings($params);
98 |
99 | $request = new RequestSet();
100 | $request->restoreState($params);
101 | $requests[] = $request;
102 | }
103 |
104 | return $requests;
105 | }
106 |
107 | public function shouldProcess()
108 | {
109 | return $this->backend->hasAtLeastXRequestsQueued($this->key, $this->numRequestsToProcessInBulk);
110 | }
111 |
112 | public function markRequestSetsAsProcessed()
113 | {
114 | $this->backend->removeFirstXValuesFromList($this->key, $this->numRequestsToProcessInBulk);
115 | }
116 |
117 | /**
118 | * Check to make sure that we don't have any params that have already been decoded when a JSON string is expected.
119 | * The request array is passed by reference so that any changes are made to the original array. If any params are
120 | * found, this simply encodes them into a JSON string again.
121 | *
122 | * @param array $requestArray
123 | * @return void
124 | */
125 | public function ensureJsonVarsAreStrings(&$requestArray)
126 | {
127 | if (!is_array($requestArray) || !is_array($requestArray['requests']) || empty($requestArray['requests'][0])) {
128 | return;
129 | }
130 |
131 | $params = $requestArray['requests'][0];
132 | foreach (self::JSON_STRING_PARAMS as $var) {
133 | if (!empty($params[$var]) && !is_string($params[$var])) {
134 | $requestArray['requests'][0][$var] = json_encode($params[$var]);
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Queue/Backend.php:
--------------------------------------------------------------------------------
1 | connectIfNeeded();
36 | return 'TEST' === $this->redis->echo('TEST');
37 | } catch (\Exception $e) {
38 | Log::debug($e->getMessage());
39 | }
40 |
41 | return false;
42 | }
43 |
44 | public function getServerVersion()
45 | {
46 | $this->connectIfNeeded();
47 |
48 | $server = $this->redis->info('server');
49 |
50 | if (empty($server)) {
51 | return '';
52 | }
53 |
54 | $version = $server['redis_version'];
55 |
56 | return $version;
57 | }
58 |
59 | public function getLastError()
60 | {
61 | $this->connectIfNeeded();
62 |
63 | return $this->redis->getLastError();
64 | }
65 |
66 | public function getMemoryStats()
67 | {
68 | $this->connectIfNeeded();
69 |
70 | $memory = $this->redis->info('memory');
71 |
72 | return $memory;
73 | }
74 |
75 | /**
76 | * Returns the time to live of a key that can expire in ms.
77 | * @param $key
78 | * @return int
79 | */
80 | public function getTimeToLive($key)
81 | {
82 | $this->connectIfNeeded();
83 |
84 | $ttl = $this->redis->pttl($key);
85 |
86 | if ($ttl == -1) {
87 | // key exists but has no associated expire
88 | return 99999999;
89 | }
90 |
91 | if ($ttl == -2) {
92 | // key does not exist
93 | return 0;
94 | }
95 |
96 | return $ttl;
97 | }
98 |
99 | public function appendValuesToList($key, $values)
100 | {
101 | $this->connectIfNeeded();
102 |
103 | foreach ($values as $value) {
104 | $this->redis->rPush($key, gzcompress($value));
105 | }
106 |
107 | // usually we would simply do call_user_func_array(array($redis, 'rPush'), $values); as rpush supports multiple values
108 | // at once but it seems to be not implemented yet see https://github.com/nicolasff/phpredis/issues/366
109 | // doing it in one command should be much faster as it requires less tcp communication. Anyway, we currently do
110 | // not write multiple values at once ... so it is ok!
111 | }
112 |
113 | public function getFirstXValuesFromList($key, $numValues)
114 | {
115 | if ($numValues <= 0) {
116 | return array();
117 | }
118 |
119 | $this->connectIfNeeded();
120 | $values = $this->redis->lRange($key, 0, $numValues - 1);
121 | foreach ($values as $key => $value) {
122 | $tmpValue = @gzuncompress($value); // Avoid warning if not compressed
123 |
124 | // if empty, string is not compressed. Use original value
125 | if (empty($tmpValue)) {
126 | $values[$key] = $value;
127 | } else {
128 | $values[$key] = $tmpValue;
129 | }
130 | }
131 |
132 | return $values;
133 | }
134 |
135 | public function removeFirstXValuesFromList($key, $numValues)
136 | {
137 | if ($numValues <= 0) {
138 | return;
139 | }
140 |
141 | $this->connectIfNeeded();
142 | $this->redis->ltrim($key, $numValues, -1);
143 | }
144 |
145 | public function hasAtLeastXRequestsQueued($key, $numValuesRequired)
146 | {
147 | if ($numValuesRequired <= 0) {
148 | return true;
149 | }
150 |
151 | $numActual = $this->getNumValuesInList($key);
152 |
153 | return $numActual >= $numValuesRequired;
154 | }
155 |
156 | public function getNumValuesInList($key)
157 | {
158 | $this->connectIfNeeded();
159 |
160 | return $this->redis->lLen($key);
161 | }
162 |
163 | public function setIfNotExists($key, $value, $ttlInSeconds)
164 | {
165 | $this->connectIfNeeded();
166 | $wasSet = $this->redis->set($key, $value, array('nx', 'ex' => $ttlInSeconds));
167 |
168 | return $wasSet;
169 | }
170 |
171 | /**
172 | * @internal for tests only
173 | * @return \Redis
174 | */
175 | public function getConnection()
176 | {
177 | return $this->redis;
178 | }
179 |
180 | /**
181 | * @internal for tests only
182 | */
183 | public function delete($key)
184 | {
185 | $this->connectIfNeeded();
186 |
187 | return $this->redis->del($key) > 0;
188 | }
189 |
190 | public function deleteIfKeyHasValue($key, $value)
191 | {
192 | if (empty($value)) {
193 | return false;
194 | }
195 |
196 | $this->connectIfNeeded();
197 |
198 | // see http://redis.io/topics/distlock
199 | $script = 'if redis.call("GET",KEYS[1]) == ARGV[1] then
200 | return redis.call("DEL",KEYS[1])
201 | else
202 | return 0
203 | end';
204 |
205 | // ideally we would use evalSha to reduce bandwidth!
206 | return (bool) $this->evalScript($script, array($key), array($value));
207 | }
208 |
209 | protected function evalScript($script, $keys, $args)
210 | {
211 | return $this->redis->eval($script, array_merge($keys, $args), count($keys));
212 | }
213 |
214 | public function getKeysMatchingPattern($pattern)
215 | {
216 | $this->connectIfNeeded();
217 |
218 | return $this->redis->keys($pattern) ?: [];
219 | }
220 |
221 | public function expireIfKeyHasValue($key, $value, $ttlInSeconds)
222 | {
223 | if (empty($value)) {
224 | return false;
225 | }
226 |
227 | $this->connectIfNeeded();
228 |
229 | $script = 'if redis.call("GET",KEYS[1]) == ARGV[1] then
230 | return redis.call("EXPIRE",KEYS[1], ARGV[2])
231 | else
232 | return 0
233 | end';
234 | // ideally we would use evalSha to reduce bandwidth!
235 | return (bool) $this->evalScript($script, array($key), array($value, (int) $ttlInSeconds));
236 | }
237 |
238 | public function get($key)
239 | {
240 | $this->connectIfNeeded();
241 |
242 | return $this->redis->get($key);
243 | }
244 |
245 | /**
246 | * @internal
247 | */
248 | public function flushAll()
249 | {
250 | $this->connectIfNeeded();
251 | $this->redis->flushDB();
252 | }
253 |
254 | private function connectIfNeeded()
255 | {
256 | if (!$this->isConnected()) {
257 | $this->connect();
258 | }
259 | }
260 |
261 | protected function connect()
262 | {
263 | $this->redis = new \Redis();
264 | $success = $this->redis->connect($this->host, $this->port, $this->timeout, null, 100);
265 |
266 | if ($success && !empty($this->password)) {
267 | $success = $this->redis->auth($this->password);
268 | }
269 |
270 | if (!empty($this->database) || 0 === $this->database) {
271 | $this->redis->select($this->database);
272 | }
273 |
274 | return $success;
275 | }
276 |
277 | public function setConfig($host, $port, $timeout, $password)
278 | {
279 | $this->disconnect();
280 |
281 | $this->host = $host;
282 | $this->port = $port;
283 | $this->timeout = $timeout;
284 |
285 | if (!empty($password)) {
286 | $this->password = $password;
287 | }
288 | }
289 |
290 | private function disconnect()
291 | {
292 | if ($this->isConnected()) {
293 | $this->redis->close();
294 | }
295 |
296 | $this->redis = null;
297 | }
298 |
299 | private function isConnected()
300 | {
301 | return isset($this->redis);
302 | }
303 |
304 | public function setDatabase($database)
305 | {
306 | $this->database = $database;
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/Queue/Backend/Sentinel.php:
--------------------------------------------------------------------------------
1 | host);
28 | $ports = explode(',', $this->port);
29 |
30 | if (count($hosts) !== count($ports)) {
31 | throw new Exception(Piwik::translate('QueuedTracking_NumHostsNotMatchNumPorts'));
32 | }
33 |
34 | foreach ($hosts as $index => $host) { // Sort or randomize as appropriate
35 | try {
36 | $configuredClient = new \Credis_Client($host, $ports[$index], $timeout = 0.5, $persistent = false);
37 | $configuredClient->forceStandalone();
38 | $configuredClient->connect();
39 | $configuredSentinel = new \Credis_Sentinel($configuredClient);
40 | $master = $configuredSentinel->getMasterAddressByName($this->masterName);
41 |
42 | if (!empty($master)) {
43 | if (!class_exists('\Redis') && $this->timeout == 0) {
44 | $this->timeout = 0.05;
45 | }
46 |
47 | $client = new \Credis_Client($master[0], $master[1], $this->timeout, $persistent = false, $this->database, $this->password);
48 | $client->connect();
49 |
50 | $this->redis = $client;
51 |
52 | return true;
53 | }
54 | } catch (Exception $e) {
55 | Log::debug($e->getMessage());
56 | }
57 | }
58 |
59 | throw new Exception('Could not receive an actual master from sentinel');
60 | }
61 |
62 | public function setSentinelMasterName($name)
63 | {
64 | $this->masterName = $name;
65 | }
66 |
67 | protected function evalScript($script, $keys, $args)
68 | {
69 | return $this->redis->eval($script, $keys, $args);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Queue/Factory.php:
--------------------------------------------------------------------------------
1 | setNumberOfAvailableQueues($settings->numQueueWorkers->getValue());
37 | $manager->setNumberOfRequestsToProcessAtSameTime($settings->numRequestsToProcess->getValue());
38 |
39 | return $manager;
40 | }
41 |
42 | public static function makeLock(Backend $backend)
43 | {
44 | return new Lock($backend);
45 | }
46 |
47 | /**
48 | * @return \Piwik\Plugins\QueuedTracking\SystemSettings
49 | */
50 | public static function getSettings()
51 | {
52 | return StaticContainer::get('Piwik\Plugins\QueuedTracking\SystemSettings');
53 | }
54 |
55 | public static function makeBackendFromSettings(SystemSettings $settings)
56 | {
57 | if ($settings->isMysqlBackend()) {
58 | return new Queue\Backend\MySQL();
59 | }
60 |
61 | $host = $settings->redisHost->getValue();
62 | $port = $settings->redisPort->getValue();
63 | $timeout = $settings->redisTimeout->getValue();
64 | $password = $settings->redisPassword->getValue();
65 | $database = $settings->redisDatabase->getValue();
66 |
67 | if ($settings->isUsingSentinelBackend()) {
68 | $masterName = $settings->getSentinelMasterName();
69 | if (empty($masterName)) {
70 | throw new Exception('You must configure a sentinel master name via `sentinel_master_name="mymaster"` to use the sentinel backend');
71 | } else {
72 | $redis = new Queue\Backend\Sentinel();
73 | $redis->setSentinelMasterName($masterName);
74 | $redis->setDatabase($database);
75 | }
76 | } elseif ($settings->isUsingClusterBackend()) {
77 | $redis = new Queue\Backend\RedisCluster();
78 | } else {
79 | $redis = new Queue\Backend\Redis();
80 | $redis->setDatabase($database);
81 | }
82 |
83 | $redis->setConfig($host, $port, $timeout, $password);
84 | return $redis;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Queue/Lock.php:
--------------------------------------------------------------------------------
1 | 0,
53 | '1' => 1,
54 | '2' => 2,
55 | '3' => 3,
56 | '4' => 4,
57 | '5' => 5,
58 | '6' => 6,
59 | '7' => 7,
60 | '8' => 8,
61 | '9' => 9,
62 | 'a' => 10,
63 | 'b' => 11,
64 | 'c' => 12,
65 | 'd' => 13,
66 | 'e' => 14,
67 | 'f' => 15,
68 | );
69 |
70 | public function __construct(Backend $backend, Lock $lock)
71 | {
72 | $this->backend = $backend;
73 | $this->lock = $lock;
74 | }
75 |
76 | public function setForceQueueId($queueId)
77 | {
78 | $this->forceQueueId = $queueId;
79 | }
80 |
81 | public function setNumberOfAvailableQueues($numQueues)
82 | {
83 | $this->numQueuesAvailable = (int) $numQueues;
84 | }
85 |
86 | public function getNumberOfAvailableQueues()
87 | {
88 | return $this->numQueuesAvailable;
89 | }
90 |
91 | public function moveSomeQueuesIfNeeded($newNumberOfAvailableWorkers, $oldNumberOfAvailableWorkers)
92 | {
93 | if ($newNumberOfAvailableWorkers >= $oldNumberOfAvailableWorkers) {
94 | // not needed to move requests into another queue
95 | return false;
96 | }
97 |
98 | $queueIdsToMove = range($newNumberOfAvailableWorkers, $oldNumberOfAvailableWorkers);
99 |
100 | $this->setNumberOfAvailableQueues($newNumberOfAvailableWorkers);
101 | $this->moveRequestsIntoAnotherQueue($queueIdsToMove);
102 |
103 | return true;
104 | }
105 |
106 | private function moveRequestsIntoAnotherQueue($queueIdsToMove)
107 | {
108 | foreach ($queueIdsToMove as $queueId) {
109 | $queue = $this->createQueue($queueId);
110 |
111 | while ($requestSets = $queue->getRequestSetsToProcess()) {
112 | foreach ($requestSets as $requestSet) {
113 | $this->addRequestSetToQueues($requestSet);
114 | }
115 |
116 | $queue->markRequestSetsAsProcessed();
117 | }
118 |
119 | $queue->delete();
120 | }
121 | }
122 |
123 | public function setNumberOfRequestsToProcessAtSameTime($numRequests)
124 | {
125 | $this->numRequestsToProcessInBulk = $numRequests;
126 | }
127 |
128 | public function getNumberOfRequestsToProcessAtSameTime()
129 | {
130 | return $this->numRequestsToProcessInBulk;
131 | }
132 |
133 | public function addRequestSetToQueues(RequestSet $requestSet)
134 | {
135 | /** @var RequestSet[][] $queues */
136 | $queues = array();
137 |
138 | // make sure the requests within a bulk request go into the correct queue
139 | foreach ($requestSet->getRequests() as $request) {
140 | $visitorId = $this->getVisitorIdFromRequest($request);
141 | $queueId = $this->getQueueIdForVisitor($visitorId);
142 |
143 | if (!isset($queues[$queueId])) {
144 | $queues[$queueId] = array();
145 | }
146 |
147 | $queues[$queueId][] = $request;
148 | }
149 |
150 | foreach ($queues as $queueId => $requests) {
151 | $requestSet->setRequests($requests);
152 |
153 | $queue = $this->createQueue($queueId);
154 | $queue->addRequestSet($requestSet);
155 | }
156 | }
157 |
158 | public function getNumberOfRequestSetsInAllQueues()
159 | {
160 | $total = 0;
161 |
162 | foreach ($this->getAllQueues() as $queue) {
163 | $total += $queue->getNumberOfRequestSetsInQueue();
164 | }
165 |
166 | return $total;
167 | }
168 |
169 | /**
170 | * External use only for tests
171 | * @internal
172 | *
173 | * @param int $id
174 | * @return Queue
175 | */
176 | public function createQueue($id)
177 | {
178 | $queue = new Queue($this->backend, $id);
179 | $queue->setNumberOfRequestsToProcessAtSameTime($this->numRequestsToProcessInBulk);
180 | return $queue;
181 | }
182 |
183 | /**
184 | * @return Queue[]
185 | */
186 | public function getAllQueues()
187 | {
188 | $queues = array();
189 |
190 | for ($i = 0; $i < $this->numQueuesAvailable; $i++) {
191 | $queues[] = $this->createQueue($i);
192 | }
193 |
194 | return $queues;
195 | }
196 |
197 | private function getVisitorIdFromRequest(Tracker\Request $request)
198 | {
199 | try {
200 | $visitorId = $request->getVisitorId();
201 | } catch (InvalidRequestParameterException $e) {
202 | $visitorId = null;
203 | }
204 |
205 | if (empty($visitorId)) {
206 | // we create a md5 otherwise IP's starting with 1 or 2 would be likely moved into same queue
207 | $visitorId = md5($request->getIpString());
208 | } else {
209 | $visitorId = bin2hex($visitorId);
210 | }
211 |
212 | return $visitorId;
213 | }
214 |
215 | protected function getQueueIdForVisitor($visitorId)
216 | {
217 | $visitorId = strtolower(substr($visitorId, 0, 3));
218 | if (ctype_xdigit($visitorId) === true) {
219 | $id = hexdec($visitorId);
220 | } else {
221 | $pos1 = ord($visitorId);
222 | $pos2 = isset($visitorId[1]) ? ord($visitorId[1]) : $pos1;
223 | $pos3 = isset($visitorId[2]) ? ord($visitorId[2]) : $pos2;
224 | $id = $pos1 + $pos2 + $pos3;
225 | }
226 |
227 | return $id % $this->numQueuesAvailable;
228 | }
229 |
230 | public function canAcquireMoreLocks()
231 | {
232 | return $this->lock->getNumberOfAcquiredLocks() < $this->numQueuesAvailable;
233 | }
234 |
235 | private function getRandomQueueId()
236 | {
237 | static $useMtRand;
238 |
239 | if (!isset($useMtRand)) {
240 | $useMtRand = function_exists('mt_rand');
241 | }
242 |
243 | if ($useMtRand) {
244 | $rand = mt_rand(0, $this->numQueuesAvailable - 1);
245 | } else {
246 | $rand = rand(0, $this->numQueuesAvailable - 1);
247 | }
248 |
249 | return $rand;
250 | }
251 |
252 | /**
253 | * @return Queue
254 | */
255 | public function lockNext()
256 | {
257 | $this->unlock();
258 |
259 | if (isset($this->forceQueueId) && $this->forceQueueId <= $this->numQueuesAvailable) {
260 | $queue = $this->createQueue($this->forceQueueId);
261 |
262 | $shouldProcess = $queue->shouldProcess();
263 |
264 | if ($shouldProcess && $this->lock->acquireLock($this->forceQueueId)) {
265 | return $queue;
266 | }
267 |
268 | // do not try to acquire a different lock
269 | return;
270 | }
271 |
272 | if ($this->currentQueueId < 0) {
273 | // we just want to avoid to always start looking for the queue at position 0
274 | $this->currentQueueId = $this->getRandomQueueId();
275 | }
276 |
277 | // here we look for all available queues whether at least one should and can be processed
278 |
279 | $start = $this->currentQueueId + 1; // this way we make sure to rotate through all queues
280 | $end = $start + $this->numQueuesAvailable;
281 |
282 | for (; $start < $end; $start++) {
283 | $this->currentQueueId = $start % $this->numQueuesAvailable;
284 | $queue = $this->createQueue($this->currentQueueId);
285 |
286 | $shouldProcess = $queue->shouldProcess();
287 |
288 | if ($shouldProcess && $this->lock->acquireLock($this->currentQueueId)) {
289 | return $queue;
290 | }
291 | }
292 | }
293 |
294 | public function unlock()
295 | {
296 | $this->lock->unlock();
297 | }
298 |
299 | public function expireLock($ttlInSeconds)
300 | {
301 | return $this->lock->expireLock($ttlInSeconds);
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/Queue/Processor/Handler.php:
--------------------------------------------------------------------------------
1 | requestSetsToRetry = array();
34 | $this->hasError = false;
35 | $this->numTrackedRequestsBeginning = $tracker->getCountOfLoggedRequests();
36 | $this->transactionId = $this->getDb()->beginTransaction();
37 | }
38 |
39 | public function process(Tracker $tracker, RequestSet $requestSet)
40 | {
41 | $requestSet->restoreEnvironment();
42 |
43 | $this->count = 0;
44 |
45 | foreach ($requestSet->getRequests() as $request) {
46 | try {
47 | $startMs = round(microtime(true) * 1000);
48 |
49 | $tracker->trackRequest($request);
50 |
51 | $diffInMs = round(microtime(true) * 1000) - $startMs;
52 | if ($diffInMs > 2000) {
53 | Common::printDebug(sprintf('The following request took more than 2 seconds (%d ms) to be tracked: %s', $diffInMs, var_export($request->getParams(), 1)));
54 | }
55 |
56 | $this->count++;
57 | } catch (UnexpectedWebsiteFoundException $ex) {
58 | // empty
59 | } catch (\Throwable $th) {
60 | // Log the error to help with debugging and visibility
61 | $message = "There was an error while trying to process a queued tracking request.";
62 | $message .= "\nError:\n" . $th->getMessage() . "\nStack trace:\n" . $th->getTraceAsString();
63 | $configuration = new Configuration();
64 | if ($configuration->shouldLogFailedTrackingRequestsBody()) {
65 | // Since the config should only be enabled during debugging, we should be alright using plain text
66 | $message .= "\nFailed request set:\n" . json_encode($requestSet->getState());
67 | }
68 |
69 | StaticContainer::get(\Piwik\Log\LoggerInterface::class)->warning($message);
70 |
71 | // Wrap any throwables so that they are caught by the try/catch in Processor, which is expecting Exceptions
72 | throw ($th instanceof \Exception ? $th : new \Exception($th->getMessage(), $th->getCode(), $th));
73 | }
74 | }
75 |
76 | $this->requestSetsToRetry[] = $requestSet;
77 | }
78 |
79 | public function onException(RequestSet $requestSet, Exception $e)
80 | {
81 | // todo: how do we want to handle DbException or RedisException?
82 | $this->hasError = true;
83 |
84 | Common::printDebug('Got exception: ' . $e->getMessage());
85 |
86 | // retry if a deadlock or a lock wait timeout happened
87 | if (Db::get()->isErrNo($e, 1213) || Db::get()->isErrNo($e, 1205)) {
88 | $this->requestSetsToRetry[] = $requestSet;
89 | Common::printDebug('Added deadlocked requestSet to requestSetsToRetry');
90 | return;
91 | }
92 |
93 | if ($this->count > 0) {
94 | // remove the first one that failed and all following (standard bulk tracking behavior)
95 | $insertedRequests = array_slice($requestSet->getRequests(), 0, $this->count);
96 | $requestSet->setRequests($insertedRequests);
97 | $this->requestSetsToRetry[] = $requestSet;
98 | }
99 | }
100 |
101 | public function hasErrors()
102 | {
103 | return $this->hasError;
104 | }
105 |
106 | public function rollBack(Tracker $tracker)
107 | {
108 | $tracker->setCountOfLoggedRequests($this->numTrackedRequestsBeginning);
109 | $this->getDb()->rollBack($this->transactionId);
110 | }
111 |
112 | /**
113 | * @return RequestSet[]
114 | */
115 | public function getRequestSetsToRetry()
116 | {
117 | return $this->requestSetsToRetry;
118 | }
119 |
120 | public function commit()
121 | {
122 | $this->getDb()->commit($this->transactionId);
123 | $this->requestSetsToRetry = array();
124 | }
125 |
126 | protected function getDb()
127 | {
128 | return Tracker::getDatabase();
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/QueuedTracking.php:
--------------------------------------------------------------------------------
1 | 'replaceHandlerIfQueueIsEnabled',
26 | 'Db.getTablesInstalled' => 'getTablesInstalled'
27 | );
28 | }
29 |
30 | /**
31 | * Register the new tables, so Matomo knows about them.
32 | *
33 | * @param array $allTablesInstalled
34 | */
35 | public function getTablesInstalled(&$allTablesInstalled)
36 | {
37 | $allTablesInstalled[] = Common::prefixTable('queuedtracking_queue');
38 | }
39 |
40 | public function install()
41 | {
42 | $mysql = new MySQL();
43 | $mysql->install();
44 |
45 | $configuration = new Configuration();
46 | $configuration->install();
47 | }
48 |
49 | public function uninstall()
50 | {
51 | $mysql = new MySQL();
52 | $mysql->uninstall();
53 |
54 | $configuration = new Configuration();
55 | $configuration->uninstall();
56 | }
57 |
58 | public function isTrackerPlugin()
59 | {
60 | return true;
61 | }
62 |
63 | public function replaceHandlerIfQueueIsEnabled(&$handler)
64 | {
65 | $useQueuedTracking = Common::getRequestVar('queuedtracking', 1, 'int');
66 | if (!$useQueuedTracking) {
67 | return;
68 | }
69 |
70 | $settings = Queue\Factory::getSettings();
71 |
72 | if ($settings->queueEnabled->getValue()) {
73 | $handler = new Handler();
74 |
75 | if ($settings->processDuringTrackingRequest->getValue()) {
76 | $handler->enableProcessingInTrackerMode();
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Matomo QueuedTracking Plugin
2 |
3 | [](https://github.com/matomo-org/plugin-QueuedTracking/actions/workflows/matomo-tests.yml)
4 |
5 | ## Description
6 |
7 | This plugin writes all tracking requests into a [Redis](http://redis.io/) instance or a MySQL queue instead of directly into the database.
8 | This is useful if you have too many requests per second and your server cannot handle all of them directly (eg too many connections in nginx or MySQL).
9 | It is also useful if you experience peaks sometimes. Those peaks can be handled much better by using this queue.
10 | Writing a tracking request into the queue is very fast (a tracking request takes in total a few milliseconds) compared to a regular tracking request (that takes multiple hundreds of milliseconds). The queue makes sure to process the tracking requests whenever possible even if it takes a while to process all requests after there was a peak.
11 |
12 | Have a look at the FAQ for more information.
13 |
14 | ## Support
15 |
16 | In case of any issues with the plugin or feature wishes create a new issues here:
17 | https://github.com/matomo-org/plugin-QueuedTracking/issues . In case you experience
18 | any problems please post the output of `./console queuedtracking:test` in the issue.
19 |
20 | ## TODO
21 |
22 | For usage with multiple Redis servers we should lock differently:
23 | http://redis.io/topics/distlock e.g. using https://github.com/ronnylt/redlock-php
24 |
--------------------------------------------------------------------------------
/Settings/NumWorkers.php:
--------------------------------------------------------------------------------
1 | oldValue;
25 | }
26 |
27 | public function setValue($value)
28 | {
29 | $this->oldValue = $this->getValue();
30 |
31 | parent::setValue($value);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SystemCheck.php:
--------------------------------------------------------------------------------
1 | testConnection()) {
27 | throw new \Exception('Connection to Redis failed. Please verify Redis host and port');
28 | }
29 |
30 | $version = $backend->getServerVersion();
31 |
32 | if (version_compare($version, '2.8.0') < 0) {
33 | throw new \Exception('At least Redis server 2.8.0 is required');
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Tasks.php:
--------------------------------------------------------------------------------
1 | config = $configuration;
29 | }
30 |
31 | public function schedule()
32 | {
33 | $this->daily('optimizeQueueTable', null, self::LOWEST_PRIORITY);
34 | $this->hourly('notifyQueueSize', null, self::LOWEST_PRIORITY);
35 | }
36 |
37 | /**
38 | * run eg using ./console core:run-scheduled-tasks "Piwik\Plugins\QueuedTracking\Tasks.notifyQueueSize"
39 | */
40 | public function notifyQueueSize()
41 | {
42 | $settings = Queue\Factory::getSettings();
43 |
44 | if (!$settings->queueEnabled->getValue()) {
45 | // not needed to check anything
46 | return;
47 | }
48 |
49 | $emailsToNotify = $this->config->getNotifyEmails();
50 | $threshold = $this->config->getNotifyThreshold();
51 |
52 | if (empty($emailsToNotify) || empty($threshold) || $threshold <= 0) {
53 | // nobody to notify or no threshold defined
54 | return;
55 | }
56 |
57 | $backend = Queue\Factory::makeBackend();
58 | $queueManager = Queue\Factory::makeQueueManager($backend);
59 |
60 | $larger = "";
61 | $smaller = "";
62 |
63 | foreach ($queueManager->getAllQueues() as $queue) {
64 | $size = $queue->getNumberOfRequestSetsInQueue();
65 | $entriesMessage = sprintf("Queue ID %s has %s entries.
", $queue->getId(), $size);
66 | if ($size >= $threshold) {
67 | $larger .= $entriesMessage;
68 | } else {
69 | $smaller .= $entriesMessage;
70 | }
71 | }
72 |
73 | if (!empty($larger)) {
74 | $message = sprintf("This is a notification that the threshold %s for a single queue has been reached.
The following queue sizes are greater than the threshold:
%s", $threshold, $larger);
75 |
76 | if (!empty($smaller)) {
77 | $message .= sprintf("
The remaining queue sizes, which are below the threshold, are listed below:
%s", $smaller);
78 | }
79 |
80 | $message = $message . "
Sent from " . SettingsPiwik::getPiwikUrl();
81 | $mail = new Mail();
82 | $mail->setDefaultFromPiwik();
83 | foreach ($emailsToNotify as $emailToNotify) {
84 | $mail->addTo($emailToNotify);
85 | }
86 | $mail->setSubject('Queued Tracking - queue size has reached your threshold');
87 | $mail->setWrappedHtmlBody($message);
88 | $mail->send();
89 | }
90 | }
91 |
92 | /**
93 | * run eg using ./console core:run-scheduled-tasks "Piwik\Plugins\QueuedTracking\Tasks.optimizeQueueTable"
94 | */
95 | public function optimizeQueueTable()
96 | {
97 | $settings = Queue\Factory::getSettings();
98 | if ($settings->isMysqlBackend() && $settings->queueEnabled->getValue()) {
99 | $db = Db::get();
100 | $prefix = Common::prefixTable(MySQL::QUEUED_TRACKING_TABLE_PREFIX);
101 | $tables = $db->fetchCol("SHOW TABLES LIKE '" . $prefix . "%'");
102 |
103 | $force = Db::isOptimizeInnoDBSupported();
104 | // if supported, then we want to force it, as it is quite important to execute this
105 | Db::optimizeTables($tables, $force);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Tracker/Handler.php:
--------------------------------------------------------------------------------
1 | setResponse(new Response());
37 | }
38 |
39 | // here we write add the tracking requests to a list
40 | public function process(Tracker $tracker, RequestSet $requestSet)
41 | {
42 | $queueManager = $this->getQueueManager();
43 | $queueManager->addRequestSetToQueues($requestSet);
44 | $tracker->setCountOfLoggedRequests($requestSet->getNumberOfRequests());
45 |
46 | $requests = $requestSet->getRequests();
47 | foreach ($requests as $request) {
48 | $visitorId = $request->getVisitorIdForThirdPartyCookie();
49 | if (!$visitorId) {
50 | $visitorId = \Piwik\Common::hex2bin(\Piwik\Tracker\Visit::generateUniqueVisitorId());
51 | }
52 | $request->setThirdPartyCookie($visitorId);
53 | }
54 |
55 | $this->sendResponseNow($tracker, $requestSet);
56 |
57 | if ($this->isAllowedToProcessInTrackerMode() && $queueManager->canAcquireMoreLocks()) {
58 | $this->processQueue($queueManager);
59 | }
60 | }
61 |
62 | private function sendResponseNow(Tracker $tracker, RequestSet $requestSet)
63 | {
64 | $response = $this->getResponse();
65 | $response->outputResponse($tracker);
66 | $response->sendResponseToBrowserDirectly();
67 | }
68 |
69 | /**
70 | * @internal
71 | */
72 | public function isAllowedToProcessInTrackerMode()
73 | {
74 | return $this->isAllowedToProcessInTrackerMode;
75 | }
76 |
77 | public function enableProcessingInTrackerMode()
78 | {
79 | $this->isAllowedToProcessInTrackerMode = true;
80 | }
81 |
82 | private function processQueue(Queue\Manager $queueManager)
83 | {
84 | Common::printDebug('We are going to process the queue');
85 | set_time_limit(0);
86 |
87 | try {
88 | $processor = new Processor($queueManager);
89 | $processor->process();
90 | } catch (Exception $e) {
91 | Common::printDebug('Failed to process queue: ' . $e->getMessage());
92 | // TODO how could we report errors better as the response is already sent? also monitoring ...
93 | }
94 |
95 | $queueManager->unlock();
96 | }
97 |
98 | private function getBackend()
99 | {
100 | if (is_null($this->backend)) {
101 | $this->backend = Queue\Factory::makeBackend();
102 | }
103 |
104 | return $this->backend;
105 | }
106 |
107 | private function getQueueManager()
108 | {
109 | $backend = $this->getBackend();
110 | $queue = Queue\Factory::makeQueueManager($backend);
111 |
112 | return $queue;
113 | }
114 |
115 | public function finish(Tracker $tracker, RequestSet $requestSet)
116 | {
117 | return $this->getResponse()->getOutput();
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Tracker/Response.php:
--------------------------------------------------------------------------------
1 | 1) {
21 | ob_end_flush();
22 | }
23 |
24 | Common::sendHeader("Connection: close\r\n", true);
25 | Common::sendHeader("Content-Encoding: none\r\n", true);
26 | Common::sendHeader('Content-Length: ' . ob_get_length(), true);
27 | ob_end_flush();
28 | flush();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Updates/3.2.0.php:
--------------------------------------------------------------------------------
1 | migration = $factory;
27 | }
28 |
29 | public function getMigrations(Updater $updater)
30 | {
31 | $migration1 = $this->migration->db->createTable('queuedtracking_queue', array(
32 | 'queue_key' => 'VARCHAR(70) NOT NULL',
33 | 'queue_value' => 'VARCHAR(255) NULL DEFAULT NULL',
34 | 'expiry_time' => 'BIGINT UNSIGNED DEFAULT 9999999999'
35 | ));
36 | $migration2 = $this->migration->db->addUniqueKey('queuedtracking_queue', array('queue_key'), 'unique_queue_key');
37 |
38 | return array(
39 | $migration1, $migration2
40 | );
41 | }
42 |
43 | public function doUpdate(Updater $updater)
44 | {
45 | $updater->executeMigrations(__FILE__, $this->getMigrations($updater));
46 |
47 | $config = new Configuration();
48 | $config->install();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Updates/3.3.4.php:
--------------------------------------------------------------------------------
1 | migration = $factory;
30 | }
31 |
32 | public function getMigrations(Updater $updater)
33 | {
34 | $migration1 = $this->migration->db->addPrimaryKey('queuedtracking_queue', array('queue_key'));
35 | $migration2 = $this->migration->db->dropIndex('queuedtracking_queue', 'unique_queue_key');
36 |
37 | return array(
38 | $migration1,
39 | $migration2
40 | );
41 | }
42 |
43 | public function doUpdate(Updater $updater)
44 | {
45 | $updater->executeMigrations(__FILE__, $this->getMigrations($updater));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Updates/5.1.0.php:
--------------------------------------------------------------------------------
1 | getValue() == 0) {
33 | $tmp_useWhatRedisBackendType->setValue($old_useSentinelBackend->getValue() == true ? 2 : 1);
34 | $tmp_useWhatRedisBackendType->save();
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/config/test.php:
--------------------------------------------------------------------------------
1 | Piwik\DI::decorate(function (\Piwik\Plugins\QueuedTracking\SystemSettings $settings) {
6 |
7 | if ($settings->redisHost->isWritableByCurrentUser()) {
8 | $settings->redisHost->setValue('127.0.0.1');
9 | $settings->redisPort->setValue(6379);
10 | $settings->redisPassword->setValue('');
11 | $settings->redisDatabase->setValue(15);
12 | $settings->numQueueWorkers->setValue(4);
13 | }
14 |
15 | return $settings;
16 | }),
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/docs/How_it_works.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matomo-org/plugin-QueuedTracking/d319dec89f914bb26afeb101a0dd46a130c26ee8/docs/How_it_works.png
--------------------------------------------------------------------------------
/lang/am.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "عدد الأجهزة المضيفة المضبوطة لا يتطابق مع عدد المنافذ المضبوطة.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "لا يمكن ضبط تعدد الأجهزة المضيفة أو المنافذ إلا حين تمكين مراقِب Redis. ألق نظرة على ملف اقرأني الخاص بهذه الإضافة لتتعلم كيفية تمكين المراقب."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/az.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/be.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/bg.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Няколко хоста или портове могат да бъдат конфигурирани само когато Redis Sentinel е включен. Плъгинът README ще Ви каже как да го направите.",
4 | "NumHostsNotMatchNumPorts": "Броят на конфигурираните хостове не съвпада с броя на конфигурираните портове."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/bn.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/bs.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/ca.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "Clúster",
4 | "AvailableRedisBackendTypeSentinel": "Sentinella",
5 | "AvailableRedisBackendTypeStandAlone": "Autònom",
6 | "BackendSettingFieldHelp": "Seleccioneu el backend que voleu utilitzar per a aquesta funció. Si no teniu cap experiència amb Redis o no està disponible al vostre servidor, us recomanem que utilitzeu Mysql.",
7 | "BackendSettingFieldTitle": "Backend",
8 | "ExceptionValueIsNotInt": "El valor no és un nombre enter",
9 | "MasterNameFieldHelp": "El nom del Sentinel Master només s'ha de configurar si Sentinel està habilitat.",
10 | "MasterNameFieldTitle": "Nom de Redis Sentinel Master",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Només es poden configurar múltiples hostes o ports quan Redis Sentinel està activat. El connector README us indicarà com fer-ho.",
12 | "NumHostsNotMatchNumPorts": "El nombre d'hostes configurats no coincideix amb el nombre de ports configurats.",
13 | "NumRequestsToProcessFieldHelp": "Defineix quantes sol·licituds es recolliran de la cua i es processaran alhora. Introduïu un número que sigui >= 1.",
14 | "NumRequestsToProcessFieldTitle": "Nombre de sol·licituds que es processen en un lot",
15 | "NumberOfQueueWorkersFieldHelp": "Nombre màxim de treballadors de cua permesos. Accepta un número entre 1 i 16. La millor pràctica és establir el nombre de CPU que voleu que estiguin disponibles per al processament de la cua. Tingueu en compte que heu d'assegurar-vos d'iniciar els treballadors manualment. Us recomanem que no utilitzeu entre 9 i 15 treballadors, sinó que en utilitzeu 8 o 16, ja que és possible que la cua no es distribueixi uniformement en diferents cues.",
16 | "NumberOfQueueWorkersFieldHelpNew": "Nombre màxim de treballadors de cua permesos. Accepta un nombre entre 1 i 4096. La millor pràctica és establir el nombre de CPU que voleu que estiguin disponibles per al processament de la cua. Tingueu en compte que heu d'assegurar-vos d'iniciar els treballadors manualment. Us recomanem que no utilitzeu entre 9 i 15 treballadors, sinó que en feu servir 8 o 16, ja que és possible que la cua no es distribueixi uniformement en diferents cues.",
17 | "NumberOfQueueWorkersFieldTitle": "Nombre de treballadors de la cua",
18 | "ProcessDuringRequestFieldHelp": "Si està activat, processarem totes les sol·licituds d'una cua durant una sol·licitud de seguiment normal quan hi hagi prou sol·licituds a la cua. Això no retardarà la sol·licitud de seguiment. Si està desactivat, heu de configurar un cronjob que executi l'ordre de la consola %1$s./console queuedtracking:process%2$s, per exemple, cada minut per processar la cua.",
19 | "ProcessDuringRequestFieldTitle": "Procés durant la sol·licitud de seguiment",
20 | "QueueEnabledFieldHelp": "Si està activat, totes les sol·licituds de seguiment s'escriuran en una cua en comptes de directament a la base de dades. Requereix un servidor Redis i una extensió PHP phpredis si s'utilitza Redis com a backend.",
21 | "QueueEnabledFieldTitle": "S'ha activat la cua",
22 | "RedisDatabaseFieldHelp": "En cas que utilitzeu Redis per a la memòria cau, assegureu-vos d'utilitzar una base de dades diferent.",
23 | "RedisDatabaseFieldTitle": "Base de dades Redis",
24 | "RedisHostFieldHelp": "Amfitrió remot o sòcol Unix del servidor Redis. Es permeten un màxim de 500 caràcters.",
25 | "RedisHostFieldHelpExtended": "Si el vostre amfitrió requereix una connexió TLS, podeu anteposar el protocol TLS al vostre amfitrió com: tls://example-host.com. Si això no funciona, assegureu-vos que els certificats estiguin configurats correctament al vostre servidor Matomo.",
26 | "RedisHostFieldHelpExtendedSentinel": "Quan utilitzeu Redis Sentinel, podeu utilitzar una llista separada per comes. Assegureu-vos d'especificar tants hosts com ports heu especificat. Per exemple, per configurar dos servidors \"127.0.0.1:26379\" i \"127.0.0.2:26879\" especifiqueu \"127.0.0.1,127.0.0.2\" com a host i \"26379,26879\" com a ports.",
27 | "RedisHostFieldTitle": "Amfitrió Redis o sòcol Unix",
28 | "RedisPasswordFieldHelp": "Contrasenya establerta al servidor Redis, si n'hi ha. Es pot indicar a Redis que requereixi una contrasenya abans de permetre que els clients executin ordres.",
29 | "RedisPasswordFieldTitle": "Contrasenya de Redis",
30 | "RedisPortFieldHelp": "Port on el servidor Redis s'està executant. El valor ha d'estar entre 1 i 65535. Utilitzeu 0 si utilitzeu un sòcol Unix per connectar-vos al servidor Redis.",
31 | "RedisPortFieldTitle": "Port de Redis",
32 | "RedisTimeoutFieldHelp": "Temps d'espera de connexió Redis en segons. \"0.0\" significa il·limitat. Pot ser un flotant, per exemple, \"2,5\" per a un temps d'espera de connexió de 2,5 segons.",
33 | "RedisTimeoutFieldTitle": "Temps d'espera de Redis",
34 | "UseSentinelFieldHelp": "Si està activada, s'utilitzarà la funció Redis Sentinel. Assegureu-vos d'actualitzar l'amfitrió i el port si cal. Un cop hàgiu habilitat i desat el canvi, podreu especificar diversos hosts i ports separats per comes.",
35 | "UseSentinelFieldTitle": "Activa Redis Sentinel",
36 | "WhatRedisBackEndType": "Seleccioneu quin tipus de redis voleu utilitzar. Assegureu-vos d'actualitzar l'amfitrió i el port si cal. Un cop hàgiu seleccionat i desat el canvi, podreu especificar diversos hosts i ports utilitzant llistes separades per comes només per al tipus \"Sentinel\" i \"Cluster\"."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/cs.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "Počet nakonfigurovaných hostů neodpovídá počtu nakonfigurovaných portů.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Více hostů či portů lze nakonfigurovat pouze při povolení modulu Redis Sentinel. Více informací, jak Sentinel povolit, najdete v souboru readme tohoto modulu."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/cy.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/da.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "Antallet af konfigurerede hosts passer ikke til antallet at konfigurerede porte",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Flere hosts og porte kan kun konfigureres når Redis Sentinel er aktiveret. Læs ReadMe filen for programtilføjelsen for at lære hvordan du aktiverer Sentinel"
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "Cluster",
4 | "AvailableRedisBackendTypeSentinel": "Sentinel",
5 | "AvailableRedisBackendTypeStandAlone": "Eigenständig",
6 | "BackendSettingFieldHelp": "Wählen Sie das Backend aus, das Sie für dieses Feature verwenden möchten. Wenn Sie keine Erfahrung mit Redis haben oder es auf Ihrem Server nicht verfügbar ist, empfehlen wir die Verwendung von Mysql.",
7 | "BackendSettingFieldTitle": "Backend",
8 | "ExceptionValueIsNotInt": "Der Wert ist keine Ganzzahl",
9 | "MasterNameFieldHelp": "Der Name des Sentinel-Masters muss nur konfiguriert werden, wenn Sentinel aktiviert ist.",
10 | "MasterNameFieldTitle": "Name des Redis Sentinel Masters",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Mehrere Hosts oder Ports können erst konfiguriert werden, wenn Redis Sentinel aktiviert wurde. In der README des Plugins erfahren Sie, wie Sie dies tun können.",
12 | "NumHostsNotMatchNumPorts": "Die Anzahl der konfigurierten Hosts stimmt nicht mit der Anzahl der konfigurierten Ports überein.",
13 | "NumRequestsToProcessFieldHelp": "Definiert, wie viele Anfragen gleichzeitig aus der Warteschlange ausgewählt und bearbeitet werden. Geben Sie eine Zahl ein, die >= 1 ist.",
14 | "NumRequestsToProcessFieldTitle": "Anzahl der Anfragen, die in einer Charge verarbeitet werden",
15 | "NumberOfQueueWorkersFieldHelp": "Anzahl der maximal zulässigen Warteschlangenarbeiter. Akzeptiert eine Zahl zwischen 1 und 16. Die beste Vorgehensweise besteht darin, die Anzahl der CPUs festzulegen, die Sie für die Warteschlangenverarbeitung zur Verfügung stellen möchten. Beachten Sie, dass Sie sicherstellen müssen, die Arbeiter manuell zu starten. Wir empfehlen, nicht 9-15 Arbeiter zu verwenden, sondern eher 8 oder 16, da die Warteschlange möglicherweise nicht gleichmäßig auf verschiedene Warteschlangen verteilt wird.",
16 | "NumberOfQueueWorkersFieldHelpNew": "Anzahl der maximal zulässigen Warteschlangenarbeiter. Akzeptiert eine Zahl zwischen 1 und 4096. Die beste Vorgehensweise besteht darin, die Anzahl der CPUs festzulegen, die Sie für die Warteschlangenverarbeitung zur Verfügung stellen möchten. Beachten Sie, dass Sie sicherstellen müssen, die Arbeiter manuell zu starten. Wir empfehlen, nicht 9-15 Arbeiter zu verwenden, sondern eher 8 oder 16, da die Warteschlange möglicherweise nicht gleichmäßig auf verschiedene Warteschlangen verteilt wird.",
17 | "NumberOfQueueWorkersFieldTitle": "Anzahl der Warteschlangenarbeiter",
18 | "ProcessDuringRequestFieldHelp": "Wenn aktiviert, werden wir alle Anfragen innerhalb einer Warteschlange während einer normalen Aufzeichnungsanfrage verarbeiten, sobald genügend Anfragen in der Warteschlange sind. Dies wird die Aufzeichnungsanfrage nicht verlangsamen. Wenn deaktiviert, müssen Sie einen Cronjob einrichten, der den %1$s./console queuedtracking:process%2$s Konsolenbefehl z.B. jede Minute ausführt, um die Warteschlange zu verarbeiten.",
19 | "QueueEnabledFieldHelp": "Wenn aktiviert, werden alle Aufzeichnungsanfragen in eine Warteschlange geschrieben, anstatt direkt in die Datenbank. Erfordert einen Redis-Server und die phpredis PHP-Erweiterung, wenn Redis als Backend verwendet wird.",
20 | "QueueEnabledFieldTitle": "Warteschlange aktiviert",
21 | "RedisDatabaseFieldHelp": "Falls Sie Redis für das Caching verwenden, stellen Sie sicher, dass Sie eine andere Datenbank verwenden.",
22 | "RedisDatabaseFieldTitle": "Redis Datenbank",
23 | "RedisHostFieldHelp": "Remote-Host oder Unix-Socket des Redis-Servers. Maximal 500 Zeichen sind erlaubt.",
24 | "RedisHostFieldHelpExtended": "Wenn Ihr Host eine TLS-Verbindung erfordert, können Sie das TLS-Protokoll Ihrem Host voranstellen, wie folgt: tls://beispiel-host.com. Wenn das nicht funktioniert, stellen Sie sicher, dass die Zertifikate auf Ihrem Matomo-Server korrekt konfiguriert sind.",
25 | "RedisHostFieldHelpExtendedSentinel": "Da Sie Redis Sentinel verwenden, können Sie eine durch Kommas getrennte Liste verwenden. Stellen Sie sicher, dass Sie so viele Hosts angeben, wie Sie Ports angegeben haben. Um zum Beispiel zwei Server zu konfigurieren \"127.0.0.1:26379\" und \"127.0.0.2:26879\", geben Sie \"127.0.0.1,127.0.0.2\" als Host und \"26379,26879\" als Ports an.",
26 | "RedisHostFieldTitle": "Redis Host oder Unix-Socket",
27 | "RedisPasswordFieldHelp": "Passwort des Redis-Servers, falls vorhanden. Redis kann angewiesen werden, ein Passwort zu verlangen, bevor Clients Befehle ausführen dürfen.",
28 | "RedisPasswordFieldTitle": "Redis Passwort",
29 | "RedisPortFieldHelp": "Der Port, auf dem der Redis-Server läuft. Der Wert sollte zwischen 1 und 65535 liegen. Verwenden Sie 0, wenn Sie eine Unix-Socket-Verbindung zum Redis-Server verwenden.",
30 | "RedisPortFieldTitle": "Redis-Port",
31 | "RedisTimeoutFieldHelp": "Redis Verbindungs-Zeitlimit in Sekunden. \"0.0\" bedeutet unbegrenzt. Kann eine Fließkommazahl sein, z.B. \"2.5\" für ein Verbindungs-Zeitlimit von 2,5 Sekunden.",
32 | "RedisTimeoutFieldTitle": "Redis Zeitlimit",
33 | "UseSentinelFieldHelp": "Wenn aktiviert, wird das Redis Sentinel Feature verwendet. Stellen Sie sicher, dass Sie Host und Port bei Bedarf aktualisieren. Sobald Sie die Veränderung aktiviert und gespeichert haben, können Sie mehrere Hosts und Ports durch Kommas getrennt angeben.",
34 | "UseSentinelFieldTitle": "Redis Sentinel aktivieren",
35 | "WhatRedisBackEndType": "Wählen Sie aus, welchen Typ von Redis Sie verwenden möchten. Stellen Sie sicher, dass Sie Host und Port bei Bedarf aktualisieren. Sobald Sie die Veränderung ausgewählt und gespeichert haben, können Sie mehrere Hosts und Ports nur für den \"Sentinel\"- und \"Cluster\"-Typ mit kommagetrennten Listen angeben."
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lang/dv.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/el.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "Cluster",
4 | "AvailableRedisBackendTypeSentinel": "Sentinel",
5 | "AvailableRedisBackendTypeStandAlone": "Stand-alone",
6 | "BackendSettingFieldHelp": "Επιλέξτε το πίσω μέρος που θέλετε να χρησιμοποιείτε για αυτό το χαρακτηριστικό. Αν δεν έχετε εμπειρία με το Redis ή αν δεν είναι διαθέσιμο στο διακομιστή σας, προτείνουμε τη χρήση της Mysql.",
7 | "BackendSettingFieldTitle": "Πίσω μέρος (backend)",
8 | "ExceptionValueIsNotInt": "Η τιμή δεν είναι ακέραιος",
9 | "MasterNameFieldHelp": "Αν το Sentinel είναι ενεργό χρειάζεται να παραμετροποιηθεί μόνο το όνομα του κυρίου Sentinel.",
10 | "MasterNameFieldTitle": "Όνομα Κύριου Redis Sentinel",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Πολλαπλοί διακομιστές ή θύρες μπορούν να παραμετροποιηθούν όταν το Redis Sentinel είναι ενεργοποιημένο. Δείτε στο αρχείο README του πρόσθετου για να μάθετε πώς να το κάνετε.",
12 | "NumHostsNotMatchNumPorts": "Ο αριθμός διακομιστών για τους οποίους έγινε ρύθμιση δεν συμφωνεί με τον αριθμό των θυρών που παραμετροποιήθηκαν.",
13 | "NumRequestsToProcessFieldHelp": "Καθορίζει πόσες αιτήσεις θα επιλέγονται από την ουρά και θα επεξεργάζονται σε κάθε παρτίδα. Εισάγετε ένα αριθμό >=1.",
14 | "NumRequestsToProcessFieldTitle": "Αριθμός αιτήσεων που θα επεξεργάζονται σε μια παρτίδα",
15 | "NumberOfQueueWorkersFieldHelp": "Μέγιστος αριθμός επιτρεπόμενων εργατών ουράς. Δέχεται αριθμό μεταξύ 1 και 16. Καλύτερη πρακτική θεωρείται να βάλετε τον αριθμό των επεξεργαστών που θέλετε να έχετε διαθέσιμους για επεξεργασία ουράς. Βεβαιωθείτε ότι πρέπει να ξεκινάτε τους εργάτες χειροκίνητα. Προτείνουμε να μην χρησιμοποιείτε εργάτες μεταξύ 9 και 15, αντίθετα χρησιμοποιήστε 8 ή 16 καθώς η ουρά ενδέχεται να μην μοιράζεται ισόποσα σε διαφορετικές ουρές.",
16 | "NumberOfQueueWorkersFieldHelpNew": "Ο αριθμός των μέγιστων επιτρεπόμενων εργατών ουράς. Δέχεται ένα αριθμό μεταξύ 1 και 4096. Η βέλτιστη πρακτική είναι ο ορισμός του αριθμού των επεξεργαστών που θέλετε να κάνετε διαθέσιμους για την επεξεργασία ουράς. Έχετε το νου σας ότι οι εργάτες θα πρέπει να ξεκινούν χειροκίνητα. Δεν προτείνουμε τη χρήση εργατών μεταξύ 9-15, αλλά καλύτερα να χρησιμοποιήσετε μεταξύ 8 και 16, καθώς η ουρά δύναται να μην μοιράζεται ισόποσα σε διαφορετικές ουρές.",
17 | "NumberOfQueueWorkersFieldTitle": "Αριθμός εργατών ουράς",
18 | "ProcessDuringRequestFieldHelp": "Αν είναι ενεργό, θα γίνεται επεξεργασία των αιτήσεων μέσα σε μια ουρά στη διάρκεια μιας κανονικής αίτησης ιχνηλάτησης από τη στιγμή που θα υπάρχουν αιτήσεις στην ουρά. Αυτό δε θα καθυστερήσει την αίτηση ιχνηλάτησης. Αν είναι ανενεργό, πρέπει να ορίσετε μια εργασία cronjob που θα εκτελεί την εντολή κονσόλας %1$s./console queuedtracking:process%2$s κάθε λεπτό για να γίνεται επεξεργασία της ουράς.",
19 | "ProcessDuringRequestFieldTitle": "Επεξεργασία στην περίοδο αίτησης ιχνηλάτησης",
20 | "QueueEnabledFieldHelp": "Αν είναι ενεργό, όλες οι αιτήσεις ιχνηλάτησης θα γράφονται σε ουρά αντί για κατευθείαν στη βάση δεδομένων. Αυτό απαιτεί ένα διακομιστή Redis με την επέκταση phpredis αν θα χρησιμοποιείται το Redis ως πίσω μέρος.",
21 | "QueueEnabledFieldTitle": "Η ουρά ενεργοποιήθηκε",
22 | "RedisDatabaseFieldHelp": "Στην περίπτωση που χρησιμοποιείτε Redis για προσωρινή μνήμη βεβαιωθείτε να χρησιμοποιήστε διαφορετική βάση δεδομένων.",
23 | "RedisDatabaseFieldTitle": "Βάση δεδομένων Redis",
24 | "RedisHostFieldHelp": "Απομακρυσμένος διακομιστής ή unix socket του διακομιστή Redis. Επιτρέπονται μέγιστο 500 χαρακτήρες.",
25 | "RedisHostFieldHelpExtended": "Αν ο διακομιστής σας απαιτεί σύνδεση TLS, μπορείτε να βάλετε από μπροστά το TLS πρωτόκολλο στο διακομιστή σας όπως στο tls://example-host.com. Αν αυτό δε δουλέψει, βεβαιωθείτε ότι τα πιστοποιητικά έχουν εγκατασταθεί σωστά στον διακομιστή σας του Matomo.",
26 | "RedisHostFieldHelpExtendedSentinel": "Αν χρησιμοποιείτε το Redis Sentinel, μπορείτε να χρησιμοποιήσετε μια λίστα χωρισμένη με κόμμα. Βεβαιωθείτε να ορίσετε τόσους διακομιστές όσους έχετε και στις αντίστοιχες θύρες. Για παράδειγμα, για να παραμετροποιήσετε δύο διακομιστές “127.0.0.1:26379” και “127.0.0.2:26879” καθορίστε το “127.0.0.1,127.0.0.2” ως διακομιστή και το “26379,26879” ως θύρες.",
27 | "RedisHostFieldTitle": "Διακομιστής Redis ή unix socket",
28 | "RedisPasswordFieldHelp": "Ορισμός συνθηματικού στο διακομιστή Redis, αν υπάρχει. Το Redis μπορεί να παραμετροποιηθεί να απαιτεί συνθηματικό προτού επιτρέψει τους πελάτες να εκτελούν εντολές.",
29 | "RedisPasswordFieldTitle": "Συνθηματικό Redis",
30 | "RedisPortFieldHelp": "Η θύρα στην οποία εκτελείται ο διακομιστής Redis. Η τιμή θα πρέπει να είναι μεταξύ του 1 και του 65535. Χρησιμοποιήστε το 0 αν χρησιμοποιείτε unix soxket για να συνδεθείτε στο διακομιστή Redis.",
31 | "RedisPortFieldTitle": "Θύρα Redis",
32 | "RedisTimeoutFieldHelp": "Ο χρόνος λήξης απόκρισης του Redis σε δευτερόλεπτα. Το \"0.0\" σημαίνει χωρίς όριο. Μπορεί να είναι δεκαδικός αριθμός, πχ \"2.5\" για χρόνο λήξης απόκρισης 2.5 δευτερόλεπτα.",
33 | "RedisTimeoutFieldTitle": "Χρόνος λήξης απόκρισης του Redis",
34 | "UseSentinelFieldHelp": "Αν είναι ενεργό, θα χρησιμοποιηθεί το χαρακτηριστικό Redis Sentinel. Βεβαιωθείτε να ενημερώσετε το όνομα διακομιστή και τη θύρα αν απαιτηθεί. Από τη στιγμή που θα το ενεργοποιήσετε και αποθηκεύσετε την αλλαγή, θα μπορείτε να καθορίζετε πολλαπλούς διακομιστές και θύρες χωρισμένα με κόμμα.",
35 | "UseSentinelFieldTitle": "Ενεργοποίηση του Redis Sentinel",
36 | "WhatRedisBackEndType": "Επιλέξτε ποιο τύπο redis θα χρησιμοποιήσετε. Βεβαιωθείτε να ενημερώσετε το διακομιστή και θύρα αν χρειάζεται. Αφού έχετε κάνει και αποθηκεύσει την αλλαγή, θα μπορείτε να καθορίσετε πολλαπλούς διακομιστές και θύρες με λίστες και χρήση του κόμματος ως διαχωριστή μόνο για τους τύπους \"Sentinel\" και \"Cluster\"."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "ExceptionValueIsNotInt": "The value is not an integer",
4 | "RedisHostFieldTitle": "Redis host or unix socket",
5 | "RedisHostFieldHelp": "Remote host or unix socket of the Redis server. Max 500 characters are allowed.",
6 | "RedisHostFieldHelpExtended": "If your host requires a TLS connection, you can prepend the TLS protocol to your host like: tls://example-host.com. If that does not work, make sure that certificates are configured correctly on your Matomo server.",
7 | "RedisHostFieldHelpExtendedSentinel": "As you are using Redis Sentinel, you can use a comma separated list. Make sure to specify as many hosts as you have specified ports. For example to configure two servers “127.0.0.1:26379” and “127.0.0.2:26879” specify “127.0.0.1,127.0.0.2” as host and “26379,26879” as ports.",
8 | "RedisPortFieldTitle": "Redis port",
9 | "RedisPortFieldHelp": "Port the Redis server is running on. Value should be between 1 and 65535. Use 0 if you are using unix socket to connect to Redis server.",
10 | "RedisTimeoutFieldTitle": "Redis timeout",
11 | "RedisTimeoutFieldHelp": "Redis connection timeout in seconds. “0.0” meaning unlimited. Can be a float eg “2.5” for a connection timeout of 2.5 seconds.",
12 | "NumberOfQueueWorkersFieldTitle": "Number of queue workers",
13 | "NumberOfQueueWorkersFieldHelp": "Number of allowed maximum queue workers. Accepts a number between 1 and 16. Best practice is to set the number of CPUs you want to make available for queue processing. Be aware you need to make sure to start the workers manually. We recommend to not use 9-15 workers, rather use 8 or 16 as the queue might not be distributed evenly into different queues.",
14 | "NumberOfQueueWorkersFieldHelpNew": "Number of allowed maximum queue workers. Accepts a number between 1 and 4096. Best practice is to set the number of CPUs you want to make available for queue processing. Be aware you need to make sure to start the workers manually. We recommend to not use 9-15 workers, rather use 8 or 16 as the queue might not be distributed evenly into different queues.",
15 | "WhatRedisBackEndType": "Select which type of redis to use. Make sure to update host and port if needed. Once you have selected and saved the change, you will be able to specify multiple hosts and ports using comma separated lists for \"Sentinel\" and \"Cluster\" type only.",
16 | "AvailableRedisBackendTypeStandAlone": "Stand-alone",
17 | "AvailableRedisBackendTypeSentinel": "Sentinel",
18 | "AvailableRedisBackendTypeCluster": "Cluster",
19 | "RedisPasswordFieldTitle": "Redis password",
20 | "RedisPasswordFieldHelp": "Password set on the Redis server, if any. Redis can be instructed to require a password before allowing clients to execute commands.",
21 | "RedisDatabaseFieldTitle": "Redis database",
22 | "RedisDatabaseFieldHelp": "In case you are using Redis for caching make sure to use a different database.",
23 | "QueueEnabledFieldTitle": "Queue enabled",
24 | "QueueEnabledFieldHelp": "If enabled, all tracking requests will be written into a queue instead of the directly into the database. Requires a Redis server and phpredis PHP extension if using Redis as a backend.",
25 | "NumRequestsToProcessFieldTitle": "Number of requests that are processed in one batch",
26 | "NumRequestsToProcessFieldHelp": "Defines how many requests will be picked out of the queue and processed at once. Enter a number which is >= 1.",
27 | "ProcessDuringRequestFieldTitle": "Process during tracking request",
28 | "ProcessDuringRequestFieldHelp": "If enabled, we will process all requests within a queue during a normal tracking request once there are enough requests in the queue. This will not slow down the tracking request. If disabled, you have to setup a cronjob that executes the %1$s./console queuedtracking:process%2$s console command eg every minute to process the queue.",
29 | "BackendSettingFieldTitle": "Backend",
30 | "BackendSettingFieldHelp": "Select the backend you want to use for this feature. If you do not have any experience with Redis or it is not available on your server, we recommend using Mysql.",
31 | "UseSentinelFieldTitle": "Enable Redis Sentinel",
32 | "UseSentinelFieldHelp": "If enabled, the Redis Sentinel feature will be used. Make sure to update host and port if needed. Once you have enabled and saved the change, you will be able to specify multiple hosts and ports comma separated.",
33 | "MasterNameFieldTitle": "Redis Sentinel Master name",
34 | "MasterNameFieldHelp": "The sentinel master name only needs to be configured if Sentinel is enabled.",
35 | "NumHostsNotMatchNumPorts": "The number of configured hosts doesn't match the number of configured ports.",
36 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Multiple hosts or ports can be only configured when Redis Sentinel is on. The plugin README will tell you how to do so."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/eo.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/es-ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "El número de servidores configurados no coincide con el número de puertos configurados.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Múltiples servidores o puertos sólo pueden ser configurados cuando Redis Sentinel está habilitado. Pegale un vistazo al archivo \"readme\" del plugin para aprender cómo habilitar Sentinel."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Sólo se pueden configurar múltiples hosts o puertos cuando Redis Sentinel está activado. El README del plugin le dirá cómo hacerlo.",
4 | "NumHostsNotMatchNumPorts": "El número de hosts configurados no coincide con el número de puertos configurados."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/et.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/eu.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Hainbat ostalari edo ataka konfiguratu daitezke Redis Sentinel gaituta dagoenean. Pluginaren READMEan ikus dezakezu hori nola egin.",
4 | "NumHostsNotMatchNumPorts": "Konfiguratutako ostalari kopurua ez dator bat konfiguratutako ataka kopuruarekin."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/fa.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/fi.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "Asetettujen osoitteiden määrä ei täsmää porttien määrään.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Redis Sentinel täytyy olla asennettuna, jotta useita osoitteita tai portteja voi ottaa käyttöön. Löydät lisätietoa lisäosan readme-tiedostosta."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "Cluster",
4 | "AvailableRedisBackendTypeSentinel": "Sentinel",
5 | "AvailableRedisBackendTypeStandAlone": "Autonome",
6 | "BackendSettingFieldHelp": "Sélectionnez le backend que vous souhaitez utiliser pour cette fonctionnalité. Si vous n’avez aucune expérience avec Redis ou si Redis n’est pas disponible sur votre serveur, nous vous recommandons d’utiliser Mysql.",
7 | "BackendSettingFieldTitle": "Backend",
8 | "ExceptionValueIsNotInt": "La valeur n'est pas un entier",
9 | "MasterNameFieldHelp": "Le nom du maître Sentinel doit uniquement être configuré si Sentinel est activé.",
10 | "MasterNameFieldTitle": "Nom du maître Redis Sentinel",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Plusieurs hôtes ou ports ne peuvent être configurés que lorsque Redis Sentinel est activé. Le plugin README vous dira comment faire.",
12 | "NumHostsNotMatchNumPorts": "Le nombre d'hôtes configurés est différent du nombre de ports configurés.",
13 | "NumRequestsToProcessFieldHelp": "Définit combien de requêtes seront extraites de la file d’attente et traitées en une seule fois. Saisissez un nombre supérieur ou égal à 1.",
14 | "NumRequestsToProcessFieldTitle": "Nombre de requêtes traitées par lot",
15 | "NumberOfQueueWorkersFieldHelp": "Nombre maximum de travailleurs de la file d’attente autorisés. Accepte une valeur comprise entre 1 et 16. La bonne pratique consiste à définir le nombre de CPU que vous souhaitez allouer au traitement de la file d’attente. Attention, vous devez démarrer manuellement les travailleurs. Nous recommandons de ne pas utiliser entre 9 et 15 travailleurs ; privilégiez plutôt 8 ou 16, car la répartition de la file d’attente pourrait ne pas être équilibrée entre les différentes files.",
16 | "NumberOfQueueWorkersFieldHelpNew": "Nombre maximum de travailleurs de la file d’attente autorisés. Accepte une valeur comprise entre 1 et 4096. La bonne pratique consiste à définir le nombre de CPU que vous souhaitez allouer au traitement de la file d’attente. Attention, vous devez démarrer manuellement les travailleurs. Nous recommandons de ne pas utiliser entre 9 et 15 travailleurs ; privilégiez plutôt 8 ou 16, car la file d’attente pourrait ne pas être répartie de manière équilibrée entre les différentes files.",
17 | "NumberOfQueueWorkersFieldTitle": "Nombre de travailleurs de la file d’attente",
18 | "ProcessDuringRequestFieldHelp": "Si activé, toutes les requêtes de la file d’attente seront traitées pendant une requête de suivi normale, dès qu’il y aura suffisamment de requêtes dans la file. Cela ne ralentira pas la requête de suivi. Si désactivé, vous devrez configurer une tâche cron qui exécute la commande console %1$s./console queuedtracking:process%2$s, par exemple chaque minute, afin de traiter la file d’attente.",
19 | "ProcessDuringRequestFieldTitle": "Traitement pendant la requête de suivi",
20 | "QueueEnabledFieldHelp": "Si activé, toutes les requêtes de suivi seront enregistrées dans une file d’attente au lieu d’être directement écrites dans la base de données. Nécessite un serveur Redis et l’extension PHP phpredis si vous utilisez Redis comme backend.",
21 | "QueueEnabledFieldTitle": "File d’attente activée",
22 | "RedisDatabaseFieldHelp": "Si vous utilisez Redis pour la mise en cache, assurez-vous d’utiliser une base de données différente.",
23 | "RedisDatabaseFieldTitle": "Base de données Redis",
24 | "RedisHostFieldHelp": "Hôte distant ou socket unix du serveur Redis. Un maximum de 500 caractères est autorisé.",
25 | "RedisHostFieldHelpExtended": "Si votre hébergeur nécessite une connexion TLS, vous pouvez ajouter le protocole TLS devant votre hôte, par exemple : tls://exemple-hote.com. Si cela ne fonctionne pas, assurez-vous que les certificats sont correctement configurés sur votre serveur Matomo.",
26 | "RedisHostFieldHelpExtendedSentinel": "Puisque vous utilisez Redis Sentinel, vous pouvez utiliser une liste séparée par des virgules. Assurez-vous de spécifier autant d’hôtes que de ports. Par exemple, pour configurer deux serveurs \"127.0.0.1:26379\" et \"127.0.0.2:26879\", indiquez \"127.0.0.1,127.0.0.2\" comme hôtes et \"26379,26879\" comme ports.",
27 | "RedisHostFieldTitle": "Hôte Redis ou socket unix",
28 | "RedisPasswordFieldHelp": "Mot de passe défini sur le serveur Redis, le cas échéant. Redis peut être configuré pour exiger un mot de passe avant d’autoriser les clients à exécuter des commandes.",
29 | "RedisPasswordFieldTitle": "Mot de passe Redis",
30 | "RedisPortFieldHelp": "Port sur lequel le serveur Redis est en cours d’exécution. La valeur doit être comprise entre 1 et 65535. Utilisez 0 si vous utilisez un socket Unix pour vous connecter au serveur Redis.",
31 | "RedisPortFieldTitle": "Port Redis",
32 | "RedisTimeoutFieldHelp": "Délai d’expiration de la connexion Redis en secondes. \"0.0\" signifie illimité. Peut être un nombre à virgule flottante, par exemple \"2.5\" pour un délai de connexion de 2,5 secondes.",
33 | "RedisTimeoutFieldTitle": "Timeout Redis",
34 | "UseSentinelFieldHelp": "Si activé, la fonctionnalité Redis Sentinel sera utilisée. Assurez-vous de mettre à jour l’hôte et le port si nécessaire. Une fois l’option activée et la modification enregistrée, vous pourrez spécifier plusieurs hôtes et ports séparés par des virgules.",
35 | "UseSentinelFieldTitle": "Activer Redis Sentinel",
36 | "WhatRedisBackEndType": "Sélectionnez le type de Redis à utiliser. Assurez-vous de mettre à jour l’hôte et le port si nécessaire. Une fois la sélection effectuée et enregistrée, vous pourrez spécifier plusieurs hôtes et ports à l’aide de listes séparées par des virgules, uniquement pour les types \"Sentinel\" et \"Cluster\"."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/ga.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "Braisle",
4 | "AvailableRedisBackendTypeSentinel": "Fairtheoir",
5 | "AvailableRedisBackendTypeStandAlone": "Neamhspleách",
6 | "BackendSettingFieldHelp": "Roghnaigh an t-inneall is mian leat a úsáid don ghné seo. Mura bhfuil aon taithí agat le Redis nó mura bhfuil sé ar fáil ar do fhreastalaí, molaimid Mysql a úsáid.",
7 | "BackendSettingFieldTitle": "Inneall",
8 | "ExceptionValueIsNotInt": "Ní slánuimhir é an luach",
9 | "MasterNameFieldHelp": "Ní gá an máistirainm fairtheora a chumrú ach amháin má tá Sentinel cumasaithe.",
10 | "MasterNameFieldTitle": "Redis Sentinel ainm Máistir",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Ní féidir le hóstach iolrach nó poirt a chumrú ach amháin nuair atá Redis Sentinel ar siúl. Inseoidh an breiseán README duit conas é sin a dhéanamh.",
12 | "NumHostsNotMatchNumPorts": "Ní hionann líon na n-óstach cumraithe agus líon na bport cumraithe.",
13 | "NumRequestsToProcessFieldHelp": "Sainmhíníonn sé cé mhéad iarratas a phiocadh amach as an scuaine agus a phróiseáil ag an am céanna. Cuir isteach uimhir atá >= 1.",
14 | "NumRequestsToProcessFieldTitle": "Líon na n-iarratas a phróiseáiltear in aon bhaisc amháin",
15 | "NumberOfQueueWorkersFieldHelp": "Líon na n-oibrithe scuaine uasta a cheadaítear. Glacann sé le huimhir idir 1 agus 16. Is é an cleachtas is fearr ná líon na LAP a theastaíonn uait a chur ar fáil le haghaidh próiseála scuaine a shocrú. Bí ar an eolas go gcaithfidh tú a chinntiú na hoibrithe a thosú de láimh. Molaimid gan 9-15 oibrí a úsáid, ach 8 nó 16 a úsáid mar go mb’fhéidir nach mbeadh an scuaine roinnte go cothrom i scuainí éagsúla.",
16 | "NumberOfQueueWorkersFieldHelpNew": "Líon na n-oibrithe scuaine uasta a cheadaítear. Glacann sé le huimhir idir 1 agus 4096. Is é an cleachtas is fearr ná líon na LAP a theastaíonn uait a chur ar fáil le haghaidh próiseála scuaine a shocrú. Bí ar an eolas go gcaithfidh tú a chinntiú na hoibrithe a thosú de láimh. Molaimid gan 9-15 oibrí a úsáid, ach 8 nó 16 a úsáid mar go mb’fhéidir nach mbeadh an scuaine roinnte go cothrom i scuainí éagsúla.",
17 | "NumberOfQueueWorkersFieldTitle": "Líon na n-oibrithe scuaine",
18 | "ProcessDuringRequestFieldHelp": "Má tá sé cumasaithe, próiseálfaimid gach iarratas laistigh de scuaine le linn gnáthiarratas rianaithe a luaithe a bheidh dóthain iarratas sa scuaine. Ní mhoilleoidh sé seo an t-iarratas rianaithe. Má tá tú díchumasaithe, caithfidh tú cronjob a shocrú a fheidhmíonn an %1$s./console queuedtracking:próiseáil %2$s ordú consóil m.sh. gach nóiméad chun an scuaine a phróiseáil.",
19 | "ProcessDuringRequestFieldTitle": "Próiseas le linn iarratais rianaithe",
20 | "QueueEnabledFieldHelp": "Má tá sé cumasaithe, scríobhfar gach iarratas rianaithe isteach i scuaine seachas iad go díreach isteach sa bhunachar sonraí. Teastaíonn freastalaí Redis agus síneadh PHP phpredis má tá Redis á úsáid mar inneall.",
21 | "QueueEnabledFieldTitle": "Tá scuaine cumasaithe",
22 | "RedisDatabaseFieldHelp": "I gcás go bhfuil tú ag baint úsáide as Redis le haghaidh taisce a dhéanamh cinnte a úsáid bunachar sonraí eile.",
23 | "RedisDatabaseFieldTitle": "Bunachar sonraí Redis saor in aisce,",
24 | "RedisHostFieldHelp": "Cianóstach nó soicéad unix den fhreastalaí Redis. Ceadaítear 500 carachtar ar a mhéad.",
25 | "RedisHostFieldHelpExtended": "Má tá nasc TLS ag teastáil ó d’óstach, is féidir leat an prótacal TLS a ligean ar aghaidh chuig d’óstach mar: tls://example-host.com. Mura n-oibríonn sin, cinntigh go bhfuil na deimhnithe cumraithe i gceart ar do fhreastalaí Matomo.",
26 | "RedisHostFieldHelpExtendedSentinel": "Toisc go bhfuil Redis Sentinel á úsáid agat, is féidir leat liosta scartha le camóg a úsáid. Déan cinnte an oiread óstach a shonrú agus a bhfuil calafoirt sonraithe agat. Mar shampla chun dhá fhreastalaí a chumrú “127.0.0.1:26379” agus “127.0.0.2:26879” sonraigh “127.0.0.1,127.0.0.2” mar óstach agus “26379,26879” mar phoirt.",
27 | "RedisHostFieldTitle": "Redis ósta nó soicéad unix",
28 | "RedisPasswordFieldHelp": "Pasfhocal socraithe ar an bhfreastalaí Redis, más ann dó. Is féidir treoir a thabhairt do Redis pasfhocal a éileamh sula gceadaítear do chliaint orduithe a fhorghníomhú.",
29 | "RedisPasswordFieldTitle": "Redis pasfhocal",
30 | "RedisPortFieldHelp": "Port an fhreastalaí Redis ar siúl. Ba chóir go mbeadh an luach idir 1 agus 65535. Bain úsáid as 0 má tá tú ag baint úsáide as soicéad unix chun ceangal le freastalaí Redis.",
31 | "RedisPortFieldTitle": "Redis port",
32 | "RedisTimeoutFieldHelp": "Redis am istigh ceangail i soicindí. Ciallaíonn “0.0” gan teorainn. Is féidir leis a bheith ina snámhán m.sh. “2.5” le haghaidh teorainn ama ceangail 2.5 soicind.",
33 | "RedisTimeoutFieldTitle": "Teorainn ama Redis",
34 | "UseSentinelFieldHelp": "Má tá sé cumasaithe, úsáidfear an ghné Redis Sentinel. Déan cinnte an t-óstach agus an port a nuashonrú más gá. Nuair a bheidh an t-athrú cumasaithe agus sábháilte agat, beidh tú in ann óstach iolrach agus port scartha le camóga a shonrú.",
35 | "UseSentinelFieldTitle": "Cumasaigh Redis Sentinel",
36 | "WhatRedisBackEndType": "Roghnaigh cén cineál redis a úsáidfear. Déan cinnte an t-óstach agus an port a nuashonrú más gá. Nuair a bheidh an t-athrú roghnaithe agus sábháilte agat, beidh tú in ann óstaigh agus poirt iolracha a shonrú ag baint úsáide as liostaí scartha le camóg don chineál “Sentinel” agus “Cnuasach” amháin."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/gl.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Só se poden configurar varios hosts ou portos cando Redis Sentinel está activado. O complemento README indicarache como facelo.",
4 | "NumHostsNotMatchNumPorts": "O número de hosts configurados non coincide co número de portos configurados."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/gu.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "જ્યારે રેડીસ સેન્ટીનેલ ચાલુ હોય ત્યારે જ બહુવિધ હોસ્ટ અથવા પોર્ટ રૂપરેખાંકિત શકાય છે. પ્લગઇનની README તમને કહેશે કે આવું કેવી રીતે કરવું.",
4 | "NumHostsNotMatchNumPorts": "રૂપરેખાંકિત હોસ્ટની સંખ્યા રૂપરેખાંકિત પોર્ટની સંખ્યા સાથે મેળ ખાતી નથી."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/he.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/hi.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "एकाधिक होस्ट या पोर्ट केवल तभी कॉन्फ़िगर किए जा सकते हैं जब रेडिस सेंटिनल चालू हो। प्लगइन README आपको बताएगा कि ऐसा कैसे करना है।",
4 | "NumHostsNotMatchNumPorts": "कॉन्फ़िगर किए गए होस्ट की संख्या कॉन्फ़िगर किए गए पोर्ट की संख्या से मेल नहीं खाती है।"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/hr.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/hu.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/hy.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/id.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Beberapa host atau porta hanya dapat dikonfigurasi saat Redis Sentinel aktif. Pengaya README akan memberi tahu Anda cara untuk melakukannya.",
4 | "NumHostsNotMatchNumPorts": "Jumlah host yang dikonfigurasi tidak cocok dengan jumlah porta yang dikonfigurasi."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/is.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "ExceptionValueIsNotInt": "Il valore non è un intero",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Host o porte multipli possono essere configurati solamente quando è abilitato Redis Sentinel. Il file readme del plugin ti dirà come farlo.",
5 | "NumHostsNotMatchNumPorts": "Il numero di host configurati con corrisponde al numero di porte configurate.",
6 | "RedisHostFieldHelp": "Rimuovi host o socket unit del server Redis. Sono ammessi massimo 500 caratteri.",
7 | "RedisHostFieldHelpExtended": "Se il tuo host richiede connessione TLS, puoi anteporre il protocollo TLS al tuo host, es: tls://example-host.com. Se non funziona, accertati che i certificati siano configurati correttamente nel tuo server Matomo.",
8 | "RedisHostFieldTitle": "Host Redis o socket unix"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lang/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "複数のホストまたはポートは、Redis Sentinel がオンの場合にのみ構成できます。 プラグインの README にその方法が記載されています。",
4 | "NumHostsNotMatchNumPorts": "構成されたホスト数が、構成済みのポート数と一致しません。"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/ka.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Redis Sentinel이 켜져 있을 때만 여러 호스트 또는 포트를 구성할 수 있습니다. 플러그인 README가 방법을 알려줄 것입니다.",
4 | "NumHostsNotMatchNumPorts": "구성된 호스트의 수가 구성된 포트의 수와 일치하지 않습니다."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/ku.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/lb.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/lt.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/lv.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/ms.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Berbilang hos atau port hanya boleh dikonfigurasikan apabila Redis Sentinel dihidupkan. Pemalam README akan memberitahu anda cara berbuat demikian.",
4 | "NumHostsNotMatchNumPorts": "Bilangan hos yang dikonfigurasikan tidak sepadan dengan bilangan port yang dikonfigurasikan."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/nb.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "Antallet konfigurerte tjenere matcher ikke antallet konfigurerte porter.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Flere tjenere eller porter kan kun konfigureres når Redis Sentinel er aktivert. Les utvidelsens readme-fil for å lære mer om Sentinel."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/nl.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "Cluster",
4 | "AvailableRedisBackendTypeSentinel": "Sentinel",
5 | "AvailableRedisBackendTypeStandAlone": "Stand-alone",
6 | "BackendSettingFieldHelp": "Selecteer de backend die je voor deze functie wilt gebruiken. Als je geen ervaring hebt met Redis of het is niet beschikbaar op uw server, raden wij u aan Mysql te gebruiken.",
7 | "BackendSettingFieldTitle": "Backend",
8 | "ExceptionValueIsNotInt": "De waarde is geen geheel getal",
9 | "MasterNameFieldHelp": "De Sentinel-masternaam hoeft alleen te worden geconfigureerd als Sentinel is ingeschakeld.",
10 | "MasterNameFieldTitle": "Redis Sentinel Master naam",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Meerdere hosts of poorten kunnen enkel geconfigureerd worden indien Redis Sentinel actief is. De plugin README beschrijft hoe u dit doet.",
12 | "NumHostsNotMatchNumPorts": "Het aantal geconfigureerde hosts komt niet overeen met het aantal geconfigureerde poorten.",
13 | "NumRequestsToProcessFieldHelp": "Definieert hoeveel aanvragen uit de wachtrij worden gehaald en in één keer worden verwerkt. Voer een getal in dat >= 1 is.",
14 | "NumRequestsToProcessFieldTitle": "Aantal verzoeken dat in één batch wordt verwerkt",
15 | "NumberOfQueueWorkersFieldHelp": "Toegestane maximum aantal wachtrij verwerkers. Accepteert een getal tussen 1 en 16. De beste praktijk is om het aantal CPU's in te stellen die je beschikbaar wilt maken voor wachtrijverwerking. Houd er rekening mee dat je ervoor moet zorgen dat je de verwerkers handmatig start. We raden aan om geen 9-15 verwerkers te gebruiken, maar liever 8 of 16, omdat de wachtrij mogelijk niet gelijkmatig over verschillende wachtrijen wordt verdeeld.",
16 | "NumberOfQueueWorkersFieldHelpNew": "Aantal toegestane maximale wachtrij verwerkers. Accepteert een getal tussen 1 en 4096. De beste praktijk is om het aantal CPU's in te stellen die je beschikbaar wilt maken voor wachtrijverwerking. Houd er rekening mee dat je ervoor moet zorgen dat je de verwerkers handmatig start. We raden aan om geen 9-15 verwerkers te gebruiken, maar liever 8 of 16, omdat de wachtrij mogelijk niet gelijkmatig over verschillende wachtrijen wordt verdeeld.",
17 | "NumberOfQueueWorkersFieldTitle": "Aantal wachtrij verwerkers",
18 | "ProcessDuringRequestFieldHelp": "Indien ingeschakeld, verwerken we alle verzoeken binnen een wachtrij tijdens een normaal trackingverzoek zodra er voldoende verzoeken in de wachtrij staan. Dit zal het trackingverzoek niet vertragen. Indien uitgeschakeld, moet u een cronjob instellen die het console commando %1$s./console queuedtracking:process%2$s uitvoert, bijvoorbeeld om de wachtrij elke minuut te verwerken.",
19 | "ProcessDuringRequestFieldTitle": "Verwerken tijdens trackingverzoek",
20 | "QueueEnabledFieldHelp": "Indien ingeschakeld, worden alle trackingverzoeken in een wachtrij geschreven in plaats van rechtstreeks in de database. Vereist een Redis-server en een PHP-extensie van PHPredis indien je Redis als backend gebruikt.",
21 | "QueueEnabledFieldTitle": "Wachtrij ingeschakeld",
22 | "RedisDatabaseFieldHelp": "Indien je Redis gebruikt voor caching, zorg er dan voor dat je een andere database gebruikt.",
23 | "RedisDatabaseFieldTitle": "Redis-database",
24 | "RedisHostFieldHelp": "Externe host of Unix-socket van de Redis-server. Er zijn maximaal 500 tekens toegestaan.",
25 | "RedisHostFieldHelpExtended": "Als uw host een TLS-verbinding vereist, kan je het TLS-protocol aan uw host toevoegen, zoals: tls://example-host.com. Als dat niet werkt, zorg er dan voor dat de certificaten correct zijn geconfigureerd op uw Matomo-server.",
26 | "RedisHostFieldHelpExtendedSentinel": "Omdat je Redis Sentinel gebruikt, kun je een door komma's gescheiden lijst gebruiken. Zorg ervoor dat je evenveel hosts opgeeft als je poorten heeft opgegeven. Om bijvoorbeeld twee servers “127.0.0.1:26379” en “127.0.0.2:26879” te configureren, specificeer je “127.0.0.1,127.0.0.2” als host en “26379,26879” als poorten.",
27 | "RedisHostFieldTitle": "Redis-host of Unix-socket",
28 | "RedisPasswordFieldHelp": "Wachtwoord ingesteld op de Redis-server, indien aanwezig. Redis kan de opdracht krijgen om een wachtwoord te vereisen voordat clients opdrachten kunnen uitvoeren.",
29 | "RedisPasswordFieldTitle": "Redis-wachtwoord",
30 | "RedisPortFieldHelp": "Poort waarop de Redis-server draait. De waarde moet tussen 1 en 65535 liggen. Gebruik 0 als je een Unix-socket gebruikt om verbinding te maken met de Redis-server.",
31 | "RedisPortFieldTitle": "Redis-poort",
32 | "RedisTimeoutFieldHelp": "Time-out voor de Redis-verbinding in seconden. “0,0” betekent onbeperkt. Dit kan een decimale waarde zijn, bijvoorbeeld “2,5” voor een verbindingstime-out van 2,5 seconden.",
33 | "RedisTimeoutFieldTitle": "Redis-time-out",
34 | "UseSentinelFieldHelp": "Indien ingeschakeld, wordt de Redis Sentinel-functie gebruikt. Zorg ervoor dat je de host en poort indien nodig bijwerkt. Zodra je de wijziging ingeschakeld en opgeslagen hebt, kun je meerdere hosts en poorten met komma's gescheiden opgeven.",
35 | "UseSentinelFieldTitle": "Schakel Redis Sentinel in",
36 | "WhatRedisBackEndType": "Selecteer welk type Redis je wilt gebruiken. Zorg ervoor dat je de host en poort indien nodig bijwerkt. Zodra je de wijziging geselecteerd en opgeslagen hebt, kun je meerdere hosts en poorten opgeven met behulp van door komma's gescheiden lijsten voor alleen de typen \"Sentinel\" en \"Cluster\"."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/nn.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/pl.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Wiele hostów lub portów może być skonfigurowanych tylko wtedy, gdy Redis Sentinel jest włączony. Plik README wtyczki powie ci, jak to zrobić.",
4 | "NumHostsNotMatchNumPorts": "Liczba skonfigurowanych hostów nie pokrywa się z liczbą skonfigurowanych portów."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/pt-br.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Vários hosts ou portas só podem ser configurados quando o Redis Sentinel está ativado. O plugin README lhe dirá como fazer isso.",
4 | "NumHostsNotMatchNumPorts": "O número de hosts configurados não coincide com o número de portas configuradas."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/pt.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "ExceptionValueIsNotInt": "O valor não é um inteiro",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Vários hosts ou portas só podem ser configurados quando o Redis Sentinel está ativado. O plugin README lhe dirá como fazer isso.",
5 | "NumHostsNotMatchNumPorts": "O número de servidores configurados não coincide com o número de portas configuradas."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lang/ro.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "Число настроенных хостов не совпадает с числом настроенных портов.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Несколько хостов или портов могут быть настроены только при включенном Redis Sentinel. Посмотрите файл readme плагина, чтобы узнать, как включить Sentinel."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/si.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/sk.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/sl.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/sq.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeSentinel": "Sentinel",
4 | "AvailableRedisBackendTypeStandAlone": "Më vete",
5 | "BackendSettingFieldHelp": "Përzgjidhni mekanizmin e pasmë që doni të përdoret për këtë veçori. Nëse s’keni përvojë me Redis, ose s’është i pranishëm në shërbyesin tuaj, rekomandojmë përdorimin e Mysql-së.",
6 | "BackendSettingFieldTitle": "Mekanizëm i pasmë",
7 | "ExceptionValueIsNotInt": "Vlera s’është numër i plotë",
8 | "MasterNameFieldHelp": "Emri për master sentinel lypset të formësohet vetëm nëse është aktivizuar Sentinel-i.",
9 | "MasterNameFieldTitle": "Emër Master Sentinel Redis",
10 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Disa strehë ose porta njëherësh mund të formësohen vetëm kur Redis Sentinel është i aktivizuar. README i shtojcës do t’ju mësojë si ta bëni.",
11 | "NumHostsNotMatchNumPorts": "Numri i strehëve të formësuara nuk përputhet me numrin e portave të formësuara.",
12 | "NumRequestsToProcessFieldHelp": "Përcakton sa kërkesa do të merren prej radhës dhe përpunohen në një herë. Jepni një numër që është >= 1.",
13 | "NumRequestsToProcessFieldTitle": "Numër kërkesash që përpunohen në një herë",
14 | "NumberOfQueueWorkersFieldHelp": "Numri maksimum i lejuar për “queue workers”. Pranon një numër mes 1 dhe 16. Praktika më e mirë është të vini numrin e CPU-ve që doni të përdoren për përpunim radhësh. Kini parasysh se lypset të garantoni nisjen dorazi të workers-ve. Rekomandojmë të mos përdorni 9-15 “worker”s, përdorni më mirë 8 ose 16, ngaqë radha mund të mos shpërndahet në mënyrë të njëtrajtshme në radhë të ndryshme.",
15 | "NumberOfQueueWorkersFieldHelpNew": "Numri maksimum i lejuar për “queue workers”. Pranon një numër mes 1 dhe 4096. Praktika më e mirë është të vini numrin e CPU-ve që doni të përdoren për përpunim radhësh. Kini parasysh se lypset të garantoni nisjen dorazi të workers-ve. Rekomandojmë të mos përdorni 9-15 “worker”s, përdorni më mirë 8 ose 16, ngaqë radha mund të mos shpërndahet në mënyrë të njëtrajtshme në radhë të ndryshme.",
16 | "NumberOfQueueWorkersFieldTitle": "Numër për “queue workers”",
17 | "ProcessDuringRequestFieldHelp": "Në u aktivizoftë, do të përpunojmë krejt kërkesat brenda një radhe gjatë një kërkese normale për ndjekje, pasi të ketë kërkesa të mjafta te radha. Kjo s’do të ngadalësojë kërkesën për ndjekje. Në u aktivizoftë, duhet të ujdisni një akt cron që ekzekuton urdhrin e konsolës %1$s./console queuedtracking:process%2$s p.sh., çdo minutë, që të përpunoni radhën.",
18 | "ProcessDuringRequestFieldTitle": "Përpunoje gjatë ndjekjes së kërkesës",
19 | "QueueEnabledFieldHelp": "Në u aktivizoftë, krejt kërkesat e gjurmimit do të shkruhen në një radhë, në vend se drejt e te baza e të dhënave. Lyp një shërbyes Redis dhe zgjerimin PHP phpredis, nëse përdoret Redis si mekanizëm i pasmë.",
20 | "QueueEnabledFieldTitle": "Radhë e aktivizuar",
21 | "RedisDatabaseFieldHelp": "Në rast se po përdorni Redis për ruajtje në fshehtinë, sigurohuni të përdorni një tjetër bazë të dhënash.",
22 | "RedisDatabaseFieldTitle": "Bazë të dhënash Redis",
23 | "RedisHostFieldHelp": "Strehë e largët, ose socket Unix e shërbyesit Redis. Lejohen deri në 500 shenja e shumta.",
24 | "RedisHostFieldHelpExtended": "Nëse streha juaj lyp një lidhje TLS, mund t’i vini përpara strehës tuaj protokollin TLS, kështu: tls://example-host.com. Nëse kjo s’bën punë, sigurohuni se dëshmitë janë formësuar saktë te shërbyesi juaj Matomo.",
25 | "RedisHostFieldHelpExtendedSentinel": "Meqë po përdorni Redis Sentinel, mund të përdorni një listë zërash ndarë me presje. Garantoni të specifikoni aq strehë sa keni porta të specifikuara. Për shembull, për të formësuar dy shërbyes “127.0.0.1:26379” dhe “127.0.0.2:26879”, jepni “127.0.0.1,127.0.0.2” si strehë dhe “26379,26879” si porta.",
26 | "RedisHostFieldTitle": "Strehë Redis, ose socket Unix",
27 | "RedisPasswordFieldHelp": "Fjalëkalim i caktuar te shërbyesi Redis, në pastë. Redis mund të udhëzohet të kërkojë një fjalëkalim, para se të lejojë klientë të ekzekutojnë urdhra.",
28 | "RedisPasswordFieldTitle": "Fjalëkalim për Redis",
29 | "RedisPortFieldHelp": "Porta në të cilën xhiron shërbyesi Redis. Vlera duhet të jetë mes 1 dhe 65535.Përdorni 0, nëse po përdorni një socket Unix për t’u lidhur me shërbyesin Redis.",
30 | "RedisPortFieldTitle": "Portë Redis",
31 | "RedisTimeoutFieldHelp": "Mbarim kohe lidhjeje Redis, në sekonda. “0.0” do të thotë e pakufizuar. Mund të jetë numër dhjetor, p.sh., “2.5” për një mbarim kohe prej 2.5 sekondash për lidhjen.",
32 | "RedisTimeoutFieldTitle": "Mbarim kohe për Redis",
33 | "UseSentinelFieldHelp": "Në u aktivizoftë, do të përdoret veçoria Redis Sentinel. Sigurohuni të përditësoni strehë dhe portë, në u dashtë. Pasi të keni aktivizuar dhe ruajtur ndryshimin, do të jeni në gjendje të specifikoni shumë strehë njëherësh dhe porta, ndarë me presje.",
34 | "UseSentinelFieldTitle": "Aktivizo Redis Sentinel",
35 | "WhatRedisBackEndType": "Përzgjidhni cili lloj Redis të përdoret. Mos harroni të përditësoni strehën dhe portën, në u dashtë. Pasi të keni përzgjedhur dhe ruajtur ndryshimet, do të jeni në gjendje të tregoni strehë dhe porta të shumta, duke përdorur lista elementësh të ndarë me presje, vetëm për llojin “Sentinel” dhe “Cluster”."
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lang/sr.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "Broj hostova se ne poklapa sa brojem portova.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Višestruki hostovi i portovi se mogu podesiti samo kada se omogući Redis Sentinel. Pogledajte dokumentaciju dodatka kako biste saznali kako se omogućuje Sentinel."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "ExceptionValueIsNotInt": "Värdet är inte ett heltal",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Flera värdar eller portar kan bara konfigureras när Redis Sentinel är på. Insticksprogrammets README kommer att förklara hur du gör det.",
5 | "NumHostsNotMatchNumPorts": "Antalet konfigurerade värdar matchar inte antalet konfigurerade portar.",
6 | "NumRequestsToProcessFieldHelp": "Definierar hur många förfrågningar som väljs ut ur kön för att bearbetas i klump. Ange ett nummer större än 1.",
7 | "NumRequestsToProcessFieldTitle": "Antal förfrågningar som bearbetas i en klump",
8 | "QueueEnabledFieldTitle": "Kö aktiverad",
9 | "RedisDatabaseFieldHelp": "Om du använder Redis som cache behöver du använda en annan databas.",
10 | "RedisDatabaseFieldTitle": "Redis-databas",
11 | "RedisHostFieldHelp": "Värd eller unix-socket för Redis-server. Maximalt 500 tecken godtas.",
12 | "RedisHostFieldTitle": "Redis-värd eller unix-socket",
13 | "RedisPasswordFieldHelp": "Lösenordet sätts på Redis-servern, om något nu finns. Redis kan instrueras att kräva ett lösenord före den tillåter klienter att köra kommandon.",
14 | "RedisPasswordFieldTitle": "Redis-lösenord",
15 | "RedisPortFieldHelp": "Porten som Redis-servern körs på. Värdet ska vara mellan 1 och 65535. Använd 0 ifall du använder en unix-socket för att ansluta till Redis-servern.",
16 | "RedisPortFieldTitle": "Redis-port",
17 | "UseSentinelFieldTitle": "Aktivera Redis Sentinel"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lang/ta.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "கொத்து",
4 | "AvailableRedisBackendTypeSentinel": "காவலாளி",
5 | "AvailableRedisBackendTypeStandAlone": "தனியாக",
6 | "BackendSettingFieldHelp": "இந்த அம்சத்திற்கு நீங்கள் பயன்படுத்த விரும்பும் பின்தளத்தில் தேர்ந்தெடுக்கவும். ரெடிசுடன் உங்களுக்கு எந்த அனுபவமும் இல்லை அல்லது அது உங்கள் சேவையகத்தில் கிடைக்கவில்லை என்றால், MySQL ஐப் பயன்படுத்த பரிந்துரைக்கிறோம்.",
7 | "BackendSettingFieldTitle": "பின்தளத்தில்",
8 | "ExceptionValueIsNotInt": "மதிப்பு ஒரு முழு எண் அல்ல",
9 | "MasterNameFieldHelp": "சென்டினல் இயக்கப்பட்டிருந்தால் மட்டுமே சென்டினல் மாச்டர் பெயர் கட்டமைக்கப்பட வேண்டும்.",
10 | "MasterNameFieldTitle": "ரெடிச் சென்டினல் மாச்டர் பெயர்",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "ரெடிச் சென்டினல் இயக்கத்தில் இருக்கும்போது மட்டுமே பல ஓச்ட்கள் அல்லது துறைமுகங்கள் கட்டமைக்க முடியும். சொருகி ரீட்மே அதை எப்படி செய்வது என்று உங்களுக்குச் சொல்லும்.",
12 | "NumHostsNotMatchNumPorts": "கட்டமைக்கப்பட்ட ஓச்ட்களின் எண்ணிக்கை உள்ளமைக்கப்பட்ட துறைமுகங்களின் எண்ணிக்கையுடன் பொருந்தவில்லை.",
13 | "NumRequestsToProcessFieldHelp": "எத்தனை கோரிக்கைகள் வரிசையில் இருந்து எடுக்கப்பட்டு ஒரே நேரத்தில் செயலாக்கப்படும் என்பதை வரையறுக்கிறது. > = 1 என்ற எண்ணை உள்ளிடவும்.",
14 | "NumRequestsToProcessFieldTitle": "ஒரு தொகுப்பில் செயலாக்கப்படும் கோரிக்கைகளின் எண்ணிக்கை",
15 | "NumberOfQueueWorkersFieldHelp": "அனுமதிக்கப்பட்ட அதிகபட்ச வரிசை தொழிலாளர்களின் எண்ணிக்கை. 1 மற்றும் 16 க்கு இடையில் ஒரு எண்ணை ஏற்றுக்கொள்கிறது. வரிசை செயலாக்கத்திற்கு நீங்கள் கிடைக்க விரும்பும் சிபியு களின் எண்ணிக்கையை அமைப்பதே சிறந்த நடைமுறை. தொழிலாளர்களை கைமுறையாக தொடங்குவதை உறுதி செய்ய வேண்டும் என்பதை அறிந்து கொள்ளுங்கள். 9-15 தொழிலாளர்களைப் பயன்படுத்த வேண்டாம் என்று நாங்கள் பரிந்துரைக்கிறோம், மாறாக 8 அல்லது 16 ஐப் பயன்படுத்தவும், ஏனெனில் வரிசை வெவ்வேறு வரிசைகளில் சமமாக விநியோகிக்கப்படாது.",
16 | "NumberOfQueueWorkersFieldHelpNew": "அனுமதிக்கப்பட்ட அதிகபட்ச வரிசை தொழிலாளர்களின் எண்ணிக்கை. 1 முதல் 4096 க்கு இடையில் ஒரு எண்ணை ஏற்றுக்கொள்கிறது. வரிசை செயலாக்கத்திற்கு நீங்கள் கிடைக்க விரும்பும் சிபியு களின் எண்ணிக்கையை அமைப்பதே சிறந்த நடைமுறை. தொழிலாளர்களை கைமுறையாக தொடங்குவதை உறுதி செய்ய வேண்டும் என்பதை அறிந்து கொள்ளுங்கள். 9-15 தொழிலாளர்களைப் பயன்படுத்த வேண்டாம் என்று நாங்கள் பரிந்துரைக்கிறோம், மாறாக 8 அல்லது 16 ஐப் பயன்படுத்தவும், ஏனெனில் வரிசை வெவ்வேறு வரிசைகளில் சமமாக விநியோகிக்கப்படாது.",
17 | "NumberOfQueueWorkersFieldTitle": "வரிசை தொழிலாளர்களின் எண்ணிக்கை",
18 | "ProcessDuringRequestFieldHelp": "இயக்கப்பட்டிருந்தால், வரிசையில் போதுமான கோரிக்கைகள் வந்தவுடன் சாதாரண கண்காணிப்பு கோரிக்கையின் போது அனைத்து கோரிக்கைகளையும் வரிசையில் செயலாக்குவோம். இது கண்காணிப்பு கோரிக்கையை குறைக்காது. முடக்கப்பட்டால், நீங்கள் %1$s./கன்சோல் கியூயட் டிராக்கிங்: செயல்முறை %2$s கன்சோல் கட்டளை எ.கா.",
19 | "ProcessDuringRequestFieldTitle": "கண்காணிப்பு கோரிக்கையின் போது செயல்முறை",
20 | "QueueEnabledFieldHelp": "இயக்கப்பட்டால், அனைத்து கண்காணிப்பு கோரிக்கைகளும் நேரடியாக தரவுத்தளத்திற்கு பதிலாக வரிசையில் எழுதப்படும். ரெடிசை பின்தளத்தில் பயன்படுத்தினால் ரெடிச் சேவையகம் மற்றும் PHPREDIS பிஎச்பி நீட்டிப்பு தேவைப்படுகிறது.",
21 | "QueueEnabledFieldTitle": "வரிசை இயக்கப்பட்டது",
22 | "RedisDatabaseFieldHelp": "கேச்சிங்கிற்கு நீங்கள் ரெடிசைப் பயன்படுத்துகிறீர்கள் என்றால், வேறு தரவுத்தளத்தைப் பயன்படுத்துவதை உறுதிசெய்க.",
23 | "RedisDatabaseFieldTitle": "ரெடிச் தரவுத்தளம்",
24 | "RedisHostFieldHelp": "ரெடிச் சேவையகத்தின் தொலை புரவலன் அல்லது யுனிக்ச் சாக்கெட். அதிகபட்சம் 500 எழுத்துக்கள் அனுமதிக்கப்படுகின்றன.",
25 | "RedisHostFieldHelpExtended": "உங்கள் ஓச்டுக்கு டி.எல்.எச் இணைப்பு தேவைப்பட்டால், டி.எல்.எச் நெறிமுறையை உங்கள் ஓச்டுக்கு தயாரிக்கலாம்: டி.எல்.எச்: //example-host.com. அது வேலை செய்யவில்லை என்றால், உங்கள் மாடோமோ சேவையகத்தில் சான்றிதழ்கள் சரியாக கட்டமைக்கப்பட்டுள்ளதா என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்.",
26 | "RedisHostFieldHelpExtendedSentinel": "நீங்கள் ரெடிச் சென்டினலைப் பயன்படுத்துவதால், நீங்கள் கமாவைப் பிரித்த பட்டியலைப் பயன்படுத்தலாம். நீங்கள் குறிப்பிட்ட துறைமுகங்களைக் கொண்ட பல ஓச்ட்களைக் குறிப்பிடுவதை உறுதிசெய்க. எடுத்துக்காட்டாக “127.0.0.1:26379” மற்றும் “127.0.0.2:26879” இரண்டு சேவையகங்களை ஓச்டாகவும் “26379,26879” ஆகவும் “127.0.0.1,127.0.0.0.0.0.2” என்று குறிப்பிடவும்.",
27 | "RedisHostFieldTitle": "ரெடிச் புரவலன் அல்லது யுனிக்ச் சாக்கெட்",
28 | "RedisPasswordFieldHelp": "கடவுச்சொல் ரெடிச் சேவையகத்தில் அமைக்கவும். வாடிக்கையாளர்களை கட்டளைகளை இயக்க அனுமதிப்பதற்கு முன் கடவுச்சொல் தேவை என்று ரெடிசுக்கு அறிவுறுத்தலாம்.",
29 | "RedisPasswordFieldTitle": "ரெடிச் கடவுச்சொல்",
30 | "RedisPortFieldHelp": "துறைமுகம் ரெடிச் சேவையகம் இயங்குகிறது. மதிப்பு 1 முதல் 65535 வரை இருக்க வேண்டும். ரெடிச் சேவையகத்துடன் இணைக்க நீங்கள் யூனிக்ச் சாக்கெட்டைப் பயன்படுத்தினால் 0 ஐப் பயன்படுத்தவும்.",
31 | "RedisPortFieldTitle": "ரெடிச் துறைமுகம்",
32 | "RedisTimeoutFieldHelp": "ரெடிச் இணைப்பு நேரம் நொடிகளில். “0.0” அதாவது வரம்பற்றது. 2.5 வினாடிகளின் இணைப்பு காலக்கெடுவுக்கு “2.5” ஒரு மிதவை எ.கா.",
33 | "RedisTimeoutFieldTitle": "ரெடிச் நேரம் முடிந்தது",
34 | "UseSentinelFieldHelp": "இயக்கப்பட்டால், ரெடிச் சென்டினல் நற்பொருத்தம் பயன்படுத்தப்படும். தேவைப்பட்டால் புரவலன் மற்றும் போர்ட்டைப் புதுப்பிப்பதை உறுதிசெய்க. மாற்றத்தை நீங்கள் இயக்கியதும் சேமித்ததும், பல ஓச்ட்கள் மற்றும் துறைமுகங்கள் கமாவைப் பிரிக்கலாம்.",
35 | "UseSentinelFieldTitle": "ரெடிச் சென்டினலை இயக்கவும்",
36 | "WhatRedisBackEndType": "எந்த வகை ரெடிசைப் பயன்படுத்த வேண்டும் என்பதைத் தேர்ந்தெடுக்கவும். தேவைப்பட்டால் புரவலன் மற்றும் போர்ட்டைப் புதுப்பிப்பதை உறுதிசெய்க. மாற்றத்தைத் தேர்ந்தெடுத்து சேமித்ததும், \"சென்டினல்\" மற்றும் \"கிளச்டர்\" வகைக்கு கமா பிரிக்கப்பட்ட பட்டியல்களைப் பயன்படுத்தி பல ஓச்ட்கள் மற்றும் துறைமுகங்களை நீங்கள் குறிப்பிட முடியும்."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/te.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/th.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/tl.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/tr.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "AvailableRedisBackendTypeCluster": "Küme",
4 | "AvailableRedisBackendTypeSentinel": "Yedek",
5 | "AvailableRedisBackendTypeStandAlone": "Bağımsız",
6 | "BackendSettingFieldHelp": "Bu özellik için kullanmak istediğiniz arka ucu seçin. Redis ile herhangi bir deneyiminiz yoksa veya sunucunuzda kullanılamıyorsa, Mysql kullanmanızı öneririz.",
7 | "BackendSettingFieldTitle": "Arka uç",
8 | "ExceptionValueIsNotInt": "Değer bir tam sayı değil",
9 | "MasterNameFieldHelp": "Sentinel Master adının yalnızca Sentinel açıksa yapılandırılması gerekir.",
10 | "MasterNameFieldTitle": "Redis Sentinel Master adı",
11 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Redis Sentinel eklentisi etkinleştirildiğinde birden çok sunucu ya da bağlantı noktası yapılandırılabilir. Gerekli bilgileri eklentinin README dosyasında bulabilirsiniz.",
12 | "NumHostsNotMatchNumPorts": "Yapılandırılmış sunucuların sayısı yapılandırılmış bağlantı noktaları ile eşleşmiyor.",
13 | "NumRequestsToProcessFieldHelp": "Bir kerede kuyruktan kaç isteğin alınacağını ve işleneceğini belirler. 1 değerine eşit veya büyük olan bir sayı yazın.",
14 | "NumRequestsToProcessFieldTitle": "Bir kerede işlenecek istek sayısı",
15 | "NumberOfQueueWorkersFieldHelp": "İzin verilen en fazla kuyruk işlemi sayısı. 1 ile 16 arasında bir sayı olabilir. En iyi uygulama, kuyruk işlenmesine ayırmak istediğiniz işlemci sayısını ayarlamaktır. İşlemleri elle başlattığınızdan emin olmanız gerektiğini unutmayın. Kuyruk farklı kuyruklara eşit olarak dağıtılamayabileceğinden 9-15 işlem kullanmamanızı, bunun yerine 8 veya 16 işlem kullanmanızı öneririz.",
16 | "NumberOfQueueWorkersFieldHelpNew": "İzin verilen en fazla kuyruk işlemi sayısı. 1 ile 4096 arasında bir sayı olabilir. En iyi uygulama, kuyruk işlenmesine ayırmak istediğiniz işlemci sayısını ayarlamaktır. İşlemleri elle başlattığınızdan emin olmanız gerektiğini unutmayın. Kuyruk farklı kuyruklara eşit olarak dağıtılamayabileceğinden 9-15 işlem kullanmamanızı, bunun yerine 8 veya 16 işlem kullanmanızı öneririz.",
17 | "NumberOfQueueWorkersFieldTitle": "Kuyruk işlemlerinin sayısı",
18 | "ProcessDuringRequestFieldHelp": "Açıksa, kuyrukta yeterli istek olduğunda, normal bir izleme isteği sırasında kuyruktaki tüm istekler işlenir. Bu seçenek, izleme isteğini yavaşlatmaz. Kapalıysa, kuyruğu işlemek için örneğin 1 dakikada bir %1$s./console queuedtracking:process%2$s konsol komutunu yürüten bir zamanlanmış görev ayarlamanız gerekir.",
19 | "ProcessDuringRequestFieldTitle": "İzleme isteği sırasında işlensin",
20 | "QueueEnabledFieldHelp": "Açıksa, tüm izleme istekleri doğrudan veri tabanı yerine bir kuyruğa yazılır. Arka uç olarak Redis kullanılıyorsa bir Redis sunucusu ve phpredis PHP eklentisi gerekir.",
21 | "QueueEnabledFieldTitle": "Kuyruk açık",
22 | "RedisDatabaseFieldHelp": "Ön bellek için Redis kullanıyorsanız farklı bir veri tabanı kullandığınızdan emin olun.",
23 | "RedisDatabaseFieldTitle": "Redis veri tabanı",
24 | "RedisHostFieldHelp": "Uzak sunucu ya da Redis sunucusunun unix soketi. En fazla 500 karakter uzunluğunda olabilir.",
25 | "RedisHostFieldHelpExtended": "Sunucunuz için bir TLS bağlantısı gerekiyorsa, TLS iletişim kuralını sunucunuza şu şekilde ekleyebilirsiniz: tls://ornek-sunucu.com. Bu işe yaramazsa, Matomo sunucunuzda sertifikaların doğru yapılandırıldığından emin olun.",
26 | "RedisHostFieldHelpExtendedSentinel": "Redis Sentinel kullandığınız için virgülle ayrılmış bir liste kullanabilirsiniz. Belirttiğiniz bağlantı noktası sayısı kadar sunucu belirttiğinizden emin olun. Örneğin “127.0.0.1:26379” ve “127.0.0.2:26879” sunucularını yapılandırmak için sunucu olarak “127.0.0.1,127.0.0.2” ve bağlantı noktası olarak “26379,26879” belirtin.",
27 | "RedisHostFieldTitle": "Redis sunucusu ya da unix soketi",
28 | "RedisPasswordFieldHelp": "Varsa, Redis sunucusunda ayarlanmış parola. İstemcilerin komutları yürütmesine izin vermeden önce Redis sunucusu için bir parola gerektiği belirtilebilir.",
29 | "RedisPasswordFieldTitle": "Redis parolası",
30 | "RedisPortFieldHelp": "Redis sunucusunun çalıştığı bağlantı noktası. Değer 1 ile 65535 arasında olmalıdır. Redis sunucusuna bağlanmak için unix soketi kullanıyorsanız 0 kullanın.",
31 | "RedisPortFieldTitle": "Redis bağlantı noktası",
32 | "RedisTimeoutFieldHelp": "Saniye olarak Redis bağlantı zaman aşımı. “0.0” sınırsız anlamına gelir. Bir ondalık sayı olabilir, örneğin 2.5 saniyelik bir bağlantı zaman aşımı için “2.5”.",
33 | "RedisTimeoutFieldTitle": "Redis zaman aşımı",
34 | "UseSentinelFieldHelp": "Açıksa, Redis Sentinel özelliği kullanılır. Gerekirse sunucu ve bağlantı noktasını güncellediğinizden emin olun. Açtıktan ve değişikliği kaydettikten sonra, virgülle ayrılmış birden fazla sunucu ve bağlantı noktası belirtebilirsiniz.",
35 | "UseSentinelFieldTitle": "Redis Sentinel kullanılsın",
36 | "WhatRedisBackEndType": "Kullanılacak Redis türünü seçin. Gerekirse sunucuyı ve bağlantı noktasını güncellemeyi unutmayın. Seçimi yaptıktan ve değişikliği kaydettikten sonra, yalnızca \"Yedek\" ve \"Küme\" türü için virgül ile ayrılmış listeler kullanarak birden fazla sunucu ve bağlantı noktası belirleyebilirsiniz."
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lang/tzm.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/uk.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Кілька хостів або портів можна налаштувати лише якщо увімкнено Redis Sentinel. Плагін README підкаже, як це зробити.",
4 | "NumHostsNotMatchNumPorts": "Число налаштованих хостів не збігається з числом налаштованих портів."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lang/ur.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lang/vi.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "Số máy chủ không phù hợp với số cổng đã cấu hình.",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "Các máy chủ hoặc cổng chỉ được cấu hình khi plugin Redis Sentinel được kích hoạt. Để kích hoạt plugin Sentinel, hãy đọc qua tập tin readme."
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/zh-cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "NumHostsNotMatchNumPorts": "配置的主机数量与配置的端口数量不匹配。",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "仅在启用Redis Sentinel时才能配置多个主机或端口。 查看插件自述文件,以了解如何启用Sentinel。"
5 | }
6 | }
--------------------------------------------------------------------------------
/lang/zh-tw.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueuedTracking": {
3 | "ExceptionValueIsNotInt": "該值不是整數",
4 | "MultipleServersOnlyConfigurableIfSentinelEnabled": "只有在啟用 Redis Sentinel 時才能設定多個主機或端口。參閲外掛的 README 檔案來了解如何進行設定。",
5 | "NumHostsNotMatchNumPorts": "設定的域名數量和端口數量不相符。"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/libs/credis/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 Justin Poliey
2 | Copyright (c) 2011 Colin Mollenhour
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/libs/credis/Module.php:
--------------------------------------------------------------------------------
1 |
9 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License
10 | * @package Credis_Module
11 | */
12 | class Credis_Module
13 | {
14 | const MODULE_COUNTING_BLOOM_FILTER = 'CBF';
15 |
16 | /** @var Credis_Client */
17 | protected $client;
18 |
19 | /** @var string */
20 | protected $moduleName;
21 |
22 | /**
23 | * @param Credis_Client $client
24 | * @param string $module
25 | */
26 | public function __construct(Credis_Client $client, $module = null)
27 | {
28 | $client->forceStandalone(); // Redis Modules command not currently supported by phpredis
29 | $this->client = $client;
30 |
31 | if (isset($module)) {
32 | $this->setModule($module);
33 | }
34 | }
35 |
36 | /**
37 | * Clean up client on destruct
38 | */
39 | public function __destruct()
40 | {
41 | $this->client->close();
42 | }
43 |
44 | /**
45 | * @param $moduleName
46 | * @return $this
47 | */
48 | public function setModule($moduleName)
49 | {
50 | $this->moduleName = (string) $moduleName;
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * @param string $name
57 | * @param string $args
58 | * @return mixed
59 | */
60 | public function __call($name, $args)
61 | {
62 | if ($this->moduleName === null) {
63 | throw new \LogicException('Module must be set.');
64 | }
65 |
66 | return call_user_func(array($this->client, sprintf('%s.%s', $this->moduleName, $name)), $args);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/libs/credis/README.markdown:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/colinmollenhour/credis)
2 |
3 | # Credis
4 |
5 | Credis is a lightweight interface to the [Redis](http://redis.io/) key-value store which wraps the [phpredis](https://github.com/nicolasff/phpredis)
6 | library when available for better performance. This project was forked from one of the many redisent forks.
7 |
8 | ## Getting Started
9 |
10 | Credis_Client uses methods named the same as Redis commands, and translates return values to the appropriate
11 | PHP equivalents.
12 |
13 | ```php
14 | require 'Credis/Client.php';
15 | $redis = new Credis_Client('localhost');
16 | $redis->set('awesome', 'absolutely');
17 | echo sprintf('Is Credis awesome? %s.\n', $redis->get('awesome'));
18 |
19 | // When arrays are given as arguments they are flattened automatically
20 | $redis->rpush('particles', array('proton','electron','neutron'));
21 | $particles = $redis->lrange('particles', 0, -1);
22 | ```
23 | Redis error responses will be wrapped in a CredisException class and thrown.
24 |
25 | Credis_Client also supports transparent command renaming. Write code using the original command names and the
26 | client will send the aliased commands to the server transparently. Specify the renamed commands using a prefix
27 | for md5, a callable function, individual aliases, or an array map of aliases. See "Redis Security":http://redis.io/topics/security for more info.
28 |
29 | ## Supported connection string formats
30 |
31 | ```php
32 | $redis = new Credis_Client(/* connection string */);
33 | ```
34 |
35 | ### Unix socket connection string
36 |
37 | `unix:///path/to/redis.sock`
38 |
39 | ### TCP connection string
40 |
41 | `tcp://host[:port][/persistence_identifier]`
42 |
43 | ### TLS connection string
44 |
45 | `tls://host[:port][/persistence_identifier]`
46 |
47 | #### Enable transport level security (TLS)
48 |
49 | Use TLS connection string `tls://127.0.0.1:6379` instead of TCP connection `tcp://127.0.0.1:6379` string in order to enable transport level security.
50 |
51 | ```php
52 | require 'Credis/Client.php';
53 | $redis = new Credis_Client('tls://127.0.0.1:6379');
54 | $redis->set('awesome', 'absolutely');
55 | echo sprintf('Is Credis awesome? %s.\n', $redis->get('awesome'));
56 |
57 | // When arrays are given as arguments they are flattened automatically
58 | $redis->rpush('particles', array('proton','electron','neutron'));
59 | $particles = $redis->lrange('particles', 0, -1);
60 | ```
61 |
62 | ## Clustering your servers
63 |
64 | Credis also includes a way for developers to fully utilize the scalability of Redis with multiple servers and [consistent hashing](http://en.wikipedia.org/wiki/Consistent_hashing).
65 | Using the [Credis_Cluster](Cluster.php) class, you can use Credis the same way, except that keys will be hashed across multiple servers.
66 | Here is how to set up a cluster:
67 |
68 | ### Basic clustering example
69 | ```php
70 | '127.0.0.1', 'port' => 6379, 'alias'=>'alpha'),
76 | array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'beta')
77 | ));
78 | $cluster->set('key','value');
79 | echo "Alpha: ".$cluster->client('alpha')->get('key').PHP_EOL;
80 | echo "Beta: ".$cluster->client('beta')->get('key').PHP_EOL;
81 | ```
82 |
83 | ### Explicit definition of replicas
84 |
85 | The consistent hashing strategy stores keys on a so called "ring". The position of each key is relative to the position of its target node. The target node that has the closest position will be the selected node for that specific key.
86 |
87 | To avoid an uneven distribution of keys (especially on small clusters), it is common to duplicate target nodes. Based on the number of replicas, each target node will exist *n times* on the "ring".
88 |
89 | The following example explicitly sets the number of replicas to 5. Both Redis instances will have 5 copies. The default value is 128.
90 |
91 | ```php
92 | '127.0.0.1', 'port' => 6379, 'alias'=>'alpha'),
99 | array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'beta')
100 | ), 5
101 | );
102 | $cluster->set('key','value');
103 | echo "Alpha: ".$cluster->client('alpha')->get('key').PHP_EOL;
104 | echo "Beta: ".$cluster->client('beta')->get('key').PHP_EOL;
105 | ```
106 |
107 | ## Master/slave replication
108 |
109 | The [Credis_Cluster](Cluster.php) class can also be used for [master/slave replication](http://redis.io/topics/replication).
110 | Credis_Cluster will automatically perform *read/write splitting* and send the write requests exclusively to the master server.
111 | Read requests will be handled by all servers unless you set the *write_only* flag to true in the connection string of the master server.
112 |
113 | ### Redis server settings for master/slave replication
114 |
115 | Setting up master/slave replication is simple and only requires adding the following line to the config of the slave server:
116 |
117 | ```
118 | slaveof 127.0.0.1 6379
119 | ```
120 |
121 | ### Basic master/slave example
122 | ```php
123 | '127.0.0.1', 'port' => 6379, 'alias'=>'master', 'master'=>true),
129 | array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'slave')
130 | ));
131 | $cluster->set('key','value');
132 | echo $cluster->get('key').PHP_EOL;
133 | echo $cluster->client('slave')->get('key').PHP_EOL;
134 |
135 | $cluster->client('master')->set('key2','value');
136 | echo $cluster->client('slave')->get('key2').PHP_EOL;
137 | ```
138 |
139 | ### No read on master
140 |
141 | The following example illustrates how to disable reading on the master server. This will cause the master server only to be used for writing.
142 | This should only happen when you have enough write calls to create a certain load on the master server. Otherwise this is an inefficient usage of server resources.
143 |
144 | ```php
145 | '127.0.0.1', 'port' => 6379, 'alias'=>'master', 'master'=>true, 'write_only'=>true),
151 | array('host' => '127.0.0.1', 'port' => 6380, 'alias'=>'slave')
152 | ));
153 | $cluster->set('key','value');
154 | echo $cluster->get('key').PHP_EOL;
155 | ```
156 | ## Automatic failover with Sentinel
157 |
158 | [Redis Sentinel](http://redis.io/topics/sentinel) is a system that can monitor Redis instances. You register master servers and Sentinel automatically detects its slaves.
159 |
160 | When a master server dies, Sentinel will make sure one of the slaves is promoted to be the new master. This autofailover mechanism will also demote failed masters to avoid data inconsistency.
161 |
162 | The [Credis_Sentinel](Sentinel.php) class interacts with the *Redis Sentinel* instance(s) and acts as a proxy. Sentinel will automatically create [Credis_Cluster](Cluster.php) objects and will set the master and slaves accordingly.
163 |
164 | Sentinel uses the same protocol as Redis. In the example below we register the Sentinel server running on port *26379* and assign it to the [Credis_Sentinel](Sentinel.php) object.
165 | We then ask Sentinel the hostname and port for the master server known as *mymaster*. By calling the *getCluster* method we immediately get a [Credis_Cluster](Cluster.php) object that allows us to perform basic Redis calls.
166 |
167 | ```php
168 | getMasterAddressByName('mymaster');
175 | $cluster = $sentinel->getCluster('mymaster');
176 |
177 | echo 'Writing to master: '.$masterAddress[0].' on port '.$masterAddress[1].PHP_EOL;
178 | $cluster->set('key','value');
179 | echo $cluster->get('key').PHP_EOL;
180 | ```
181 | ### Additional parameters
182 |
183 | Because [Credis_Sentinel](Sentinel.php) will create [Credis_Cluster](Cluster.php) objects using the *"getCluster"* or *"createCluster"* methods, additional parameters can be passed.
184 |
185 | First of all there's the *"write_only"* flag. You can also define the selected database and the number of replicas. And finally there's a *"selectRandomSlave"* option.
186 |
187 | The *"selectRandomSlave"* flag is used in setups for masters that have multiple slaves. The Credis_Sentinel will either select one random slave to be used when creating the Credis_Cluster object or to pass them all and use the built-in hashing.
188 |
189 | The example below shows how to use these 3 options. It selects database 2, sets the number of replicas to 10, it doesn't select a random slave and doesn't allow reading on the master server.
190 |
191 | ```php
192 | getCluster('mymaster',2,10,false,true);
199 | $cluster->set('key','value');
200 | echo $cluster->get('key').PHP_EOL;
201 | ```
202 |
203 | ## About
204 |
205 | © 2011 [Colin Mollenhour](http://colin.mollenhour.com)
206 | © 2009 [Justin Poliey](http://justinpoliey.com)
207 |
--------------------------------------------------------------------------------
/libs/credis/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "colinmollenhour/credis",
3 | "type": "library",
4 | "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.",
5 | "homepage": "https://github.com/colinmollenhour/credis",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Colin Mollenhour",
10 | "email": "colin@mollenhour.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.4.0"
15 | },
16 | "autoload": {
17 | "classmap": [
18 | "Client.php",
19 | "Cluster.php",
20 | "Sentinel.php",
21 | "Module.php"
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Matomo Coding Standard for QueuedTracking plugin
5 |
6 |
7 |
8 | .
9 |
10 | tests/javascript/*
11 | */vendor/*
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | tests/*
20 |
21 |
22 |
23 |
24 | Updates/*
25 |
26 |
27 |
28 |
29 | tests/*
30 |
31 |
32 |
33 |
34 | tests/*
35 |
36 |
--------------------------------------------------------------------------------
/plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "QueuedTracking",
3 | "version": "5.1.1",
4 | "description": "Scale your large traffic Matomo service by queuing tracking requests in Redis or MySQL for better performance and reliability when experiencing peaks.",
5 | "theme": false,
6 | "keywords": ["tracker", "tracking", "queue", "redis"],
7 | "license": "GPL v3+",
8 | "homepage": "https://matomo.org",
9 | "require": {
10 | "matomo": ">=5.0.0-b1,<6.0.0-b1"
11 | },
12 | "support": {
13 | "email": "hello@matomo.org",
14 | "issues": "https://github.com/matomo-org/plugin-QueuedTracking/issues",
15 | "forum": "https://forum.matomo.org",
16 | "source": "https://github.com/matomo-org/plugin-QueuedTracking",
17 | "wiki": "https://github.com/matomo-org/plugin-QueuedTracking/wiki"
18 | },
19 | "authors": [
20 | {
21 | "name": "Matomo",
22 | "email": "hello@matomo.org",
23 | "homepage": "https://matomo.org"
24 | }
25 | ],
26 | "category": "development"
27 | }
28 |
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
5 |
6 | ## Issue No
7 |
13 |
14 | ## Steps to Replicate the Issue
15 | 1.
16 | 2.
17 | 3.
18 |
19 |
20 |
21 | ## Checklist
22 | - [✔/✖] Tested locally or on demo2/demo3?
23 | - [✔/✖/NA] New test case added/updated?
24 | - [✔/✖/NA] Are all newly added texts included via translation?
25 | - [✔/✖/NA] Are text sanitized properly? (Eg use of v-text v/s v-html for vue)
26 | - [✔/✖/NA] Version bumped?
--------------------------------------------------------------------------------
/screenshots/Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matomo-org/plugin-QueuedTracking/d319dec89f914bb26afeb101a0dd46a130c26ee8/screenshots/Settings.png
--------------------------------------------------------------------------------
/tests/Framework/Mock/ForcedException.php:
--------------------------------------------------------------------------------
1 | getRawParams();
19 | if (!empty($allParams['forceThrow'])) {
20 | throw new ForcedException("forced exception");
21 | }
22 |
23 | return parent::trackRequest($request);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Framework/Mock/Tracker/Response.php:
--------------------------------------------------------------------------------
1 | isResponseSentDirectly = true;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Framework/TestCase/IntegrationTestCase.php:
--------------------------------------------------------------------------------
1 | testRequiresRedis && !self::isRedisAvailable()) {
29 | $this->markTestSkipped('Redis extension is not installed, skipping test');
30 | }
31 |
32 | parent::setUp();
33 |
34 | $this->disableRedisSentinel();
35 | }
36 |
37 | protected function enableRedisSentinel($master = 'mymaster')
38 | {
39 | Config::getInstance()->QueuedTracking = array('useWhatRedisBackendType' => '2', 'sentinelMasterName' => $master);
40 | }
41 |
42 | protected function disableRedisSentinel()
43 | {
44 | Config::getInstance()->QueuedTracking = array();
45 | }
46 |
47 | public static function isRedisAvailable()
48 | {
49 | return class_exists('\Redis', false) && extension_loaded('redis');
50 | }
51 |
52 | protected function clearBackend()
53 | {
54 | if (self::isRedisAvailable()) {
55 | $backend = $this->createRedisBackend();
56 | $backend->flushAll();
57 | }
58 | $backend = $this->createMySQLBackend();
59 | $backend->flushAll();
60 | }
61 |
62 | protected function createMySQLBackend()
63 | {
64 | return new Queue\Backend\MySQL();
65 | }
66 |
67 | protected function createRedisBackend()
68 | {
69 | return Queue\Factory::makeBackend();
70 | }
71 |
72 | protected function buildRequestSet($numberOfRequestSets)
73 | {
74 | $requests = array();
75 |
76 | for ($i = 0; $i < $numberOfRequestSets; $i++) {
77 | $requests[] = new Request(array('idsite' => '1', 'index' => $i));
78 | }
79 |
80 | $set = new RequestSet();
81 | $set->setRequests($requests);
82 |
83 | return $set;
84 | }
85 |
86 | protected function assertRequestsAreEqual(PiwikRequestSet $expected, PiwikRequestSet $actual)
87 | {
88 | $eState = $expected->getState();
89 | $aState = $actual->getState();
90 |
91 | $eTime = $eState['time'];
92 | $aTime = $aState['time'];
93 |
94 | unset($eState['time']);
95 | unset($aState['time']);
96 |
97 | if (array_key_exists('REQUEST_TIME_FLOAT', $eState['env']['server'])) {
98 | unset($eState['env']['server']['REQUEST_TIME_FLOAT']);
99 | }
100 |
101 | if (array_key_exists('REQUEST_TIME_FLOAT', $aState['env']['server'])) {
102 | unset($aState['env']['server']['REQUEST_TIME_FLOAT']);
103 | }
104 |
105 | $this->assertGreaterThan(100000, $aTime);
106 | $this->assertTrue(($aTime - 5 < $eTime) && ($aTime + 5 > $eTime), "$eTime is not nearly $aTime");
107 | $this->assertEquals($eState, $aState);
108 | }
109 |
110 | protected function buildRequestSetContainingError($numberOfRequestSets, $indexThatShouldContainError, $useInvalidSiteError = false)
111 | {
112 | $requests = array();
113 |
114 | for ($i = 0; $i < $numberOfRequestSets; $i++) {
115 | if ($i === $indexThatShouldContainError) {
116 | if ($useInvalidSiteError) {
117 | $requests[] = new Request(array('idsite' => '0', 'index' => $i));
118 | } else {
119 | $requests[] = new Request(array('idsite' => '1', 'index' => $i, 'forceThrow' => 1));
120 | }
121 | } else {
122 | $requests[] = new Request(array('idsite' => '1', 'index' => $i));
123 | }
124 | }
125 |
126 | $set = new RequestSet();
127 | $set->setRequests($requests);
128 |
129 | return $set;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/tests/Integration/Queue/Backend/SentinelTest.php:
--------------------------------------------------------------------------------
1 | QueuedTracking = [];
28 | parent::tearDown();
29 | }
30 |
31 | protected function createRedisBackend()
32 | {
33 | $settings = Factory::getSettings();
34 |
35 | $this->enableRedisSentinel();
36 | $this->assertTrue($settings->isUsingSentinelBackend());
37 |
38 | $settings->redisPort->setValue('26379');
39 |
40 | $sentinel = Factory::makeBackend();
41 |
42 | $this->assertTrue($sentinel instanceof Sentinel);
43 |
44 | return $sentinel;
45 | }
46 |
47 | public function test_canCreateInstanceWithMultipleSentinelAndFallback()
48 | {
49 | $settings = Factory::getSettings();
50 |
51 | $this->enableRedisSentinel();
52 | $this->assertTrue($settings->isUsingSentinelBackend());
53 |
54 | $settings->redisHost->setValue('127.0.0.1,127.0.0.2,127.0.0.1');
55 | $settings->redisPort->setValue('26378,26379,26379');
56 |
57 | $sentinel = Factory::makeBackendFromSettings($settings);
58 | $this->assertTrue($sentinel->testConnection());
59 | }
60 |
61 | public function test_connect_ShouldThrowException_IfNotExactSameHostAndPortNumbersGiven()
62 | {
63 | $this->expectException(\Exception::class);
64 | $this->expectExceptionMessage('QueuedTracking_NumHostsNotMatchNumPorts');
65 |
66 | $this->enableRedisSentinel();
67 |
68 | $settings = Factory::getSettings();
69 | $this->assertTrue($settings->isUsingSentinelBackend());
70 |
71 | $settings->redisHost->setValue('127.0.0.1,127.0.0.1');
72 | $settings->redisPort->setValue('26378,26379,26379');
73 |
74 | $sentinel = Factory::makeBackendFromSettings($settings);
75 | $sentinel->get('test');
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Integration/Queue/FactoryTest.php:
--------------------------------------------------------------------------------
1 | createRedisBackend());
28 | $this->assertTrue($queue instanceof Queue\Manager);
29 | }
30 |
31 | public function test_makeQueueMananger_shouldConfigureTheNumberOfRequestsToProcess()
32 | {
33 | Factory::getSettings()->numRequestsToProcess->setValue(31);
34 | $queue = Factory::makeQueueManager($this->createRedisBackend());
35 | $this->assertSame(31, $queue->getNumberOfRequestsToProcessAtSameTime());
36 | }
37 |
38 | public function test_makeQueueMananger_shouldConfigureTheNumberOfWorkers()
39 | {
40 | $redis = $this->createRedisBackend();
41 | Factory::getSettings()->numQueueWorkers->setValue(7);
42 |
43 | $queue = Factory::makeQueueManager($redis);
44 | $this->assertSame(7, $queue->getNumberOfAvailableQueues());
45 | }
46 |
47 | public function test_makeLock_shouldReturnALockInstance()
48 | {
49 | $backend = Factory::makeBackend();
50 | $lock = Factory::makeLock($backend);
51 | $this->assertTrue($lock instanceof Queue\Lock);
52 | }
53 |
54 | public function test_makeBackend_shouldReturnARedisInstance()
55 | {
56 | $backend = Factory::makeBackend();
57 | $this->assertTrue($backend instanceof Queue\Backend\Redis);
58 | $this->assertFalse($backend instanceof Queue\Backend\Sentinel);
59 | }
60 |
61 | public function test_makeBackend_shouldFailToCreateASentinelInstance_IfNotFullyConfigured()
62 | {
63 | $this->expectException(\Exception::class);
64 | $this->expectExceptionMessage('You must configure a sentinel master name');
65 |
66 | Config::getInstance()->QueuedTracking = array('useWhatRedisBackendType' => '2', 'sentinelMasterName' => '');
67 | Factory::makeBackend();
68 | }
69 |
70 | public function test_makeBackend_shouldReturnASentinelInstanceIfConfigured()
71 | {
72 | Config::getInstance()->QueuedTracking = array('useWhatRedisBackendType' => '2', 'sentinelMasterName' => 'mymaster');
73 | $backend = Factory::makeBackend();
74 | Config::getInstance()->QueuedTracking = array();
75 | $this->assertTrue($backend instanceof Queue\Backend\Sentinel);
76 | }
77 |
78 | public function test_makeBackend_shouldConfigureRedis()
79 | {
80 | $success = Factory::makeBackend()->testConnection();
81 | $this->assertTrue($success);
82 | }
83 |
84 | public function test_getSettings_shouldReturnARedisInstance()
85 | {
86 | $settings = Factory::getSettings();
87 | $this->assertTrue($settings instanceof SystemSettings);
88 | }
89 |
90 | public function test_getSettings_shouldReturnASingleton()
91 | {
92 | $settings = Factory::getSettings();
93 | $settings->redisTimeout->setIsWritableByCurrentUser(true);
94 | $settings->redisTimeout->setValue(0.7);
95 |
96 | // it would not return the same value usually as $settings->save() is not called
97 |
98 | $settings = Factory::getSettings();
99 | $this->assertEquals(0.7, $settings->redisTimeout->getValue());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tests/Integration/Queue/LockTest.php:
--------------------------------------------------------------------------------
1 | createMySQLBackend();
36 | $this->lock = $this->createLock($redis);
37 | }
38 |
39 | public function tearDown(): void
40 | {
41 | $this->clearBackend();
42 | parent::tearDown();
43 | }
44 |
45 | public function test_acquireLock_ShouldLockInCaseItIsNotLockedYet()
46 | {
47 | $this->assertTrue($this->lock->acquireLock(0));
48 | $this->assertFalse($this->lock->acquireLock(0));
49 |
50 | $this->lock->unlock();
51 |
52 | $this->assertTrue($this->lock->acquireLock(0));
53 | $this->assertFalse($this->lock->acquireLock(0));
54 | }
55 |
56 | public function test_acquireLock_ShouldBeAbleToLockMany()
57 | {
58 | $this->assertTrue($this->lock->acquireLock(0));
59 | $this->assertFalse($this->lock->acquireLock(0));
60 | $this->assertTrue($this->lock->acquireLock(1));
61 | $this->assertTrue($this->lock->acquireLock(2));
62 | $this->assertFalse($this->lock->acquireLock(1));
63 | }
64 |
65 | public function test_isLocked_ShouldDetermineWhetherAQueueIsLocked()
66 | {
67 | $this->assertFalse($this->lock->isLocked());
68 | $this->lock->acquireLock(0);
69 |
70 | $this->assertTrue($this->lock->isLocked());
71 |
72 | $this->lock->unlock();
73 |
74 | $this->assertFalse($this->lock->isLocked());
75 | }
76 |
77 | public function test_unlock_OnlyUnlocksTheLastOne()
78 | {
79 | $this->assertTrue($this->lock->acquireLock(0));
80 | $this->assertTrue($this->lock->acquireLock(1));
81 | $this->assertTrue($this->lock->acquireLock(2));
82 |
83 | $this->lock->unlock();
84 |
85 | $this->assertFalse($this->lock->acquireLock(0));
86 | $this->assertFalse($this->lock->acquireLock(1));
87 | $this->assertTrue($this->lock->acquireLock(2));
88 | }
89 |
90 | public function test_expireLock_ShouldReturnTrueOnSuccess()
91 | {
92 | $this->lock->acquireLock(0);
93 | $this->assertTrue($this->lock->expireLock(2));
94 | }
95 |
96 | public function test_expireLock_ShouldReturnFalseIfNoTimeoutGiven()
97 | {
98 | $this->lock->acquireLock(0);
99 | $this->assertFalse($this->lock->expireLock(0));
100 | }
101 |
102 | public function test_expireLock_ShouldReturnFalseIfNotLocked()
103 | {
104 | $this->assertFalse($this->lock->expireLock(2));
105 | }
106 |
107 | public function test_getNumberOfAcquiredLocks_shouldReturnNumberOfLocks()
108 | {
109 | $this->assertNumberOfLocksEquals(0);
110 |
111 | $this->lock->acquireLock(0);
112 | $this->assertNumberOfLocksEquals(1);
113 |
114 | $this->lock->acquireLock(4);
115 | $this->lock->acquireLock(5);
116 | $this->assertNumberOfLocksEquals(3);
117 |
118 | $this->lock->unlock();
119 | $this->assertNumberOfLocksEquals(2);
120 | }
121 |
122 | public function test_getAllAcquiredLockKeys_shouldReturnUsedKeysThatAreLocked()
123 | {
124 | $this->assertSame(array(), $this->lock->getAllAcquiredLockKeys());
125 |
126 | $this->lock->acquireLock(0);
127 | $this->assertSame(array('QueuedTrackingLock0'), $this->lock->getAllAcquiredLockKeys());
128 |
129 | $this->lock->acquireLock(4);
130 | $this->lock->acquireLock(5);
131 |
132 | $locks = $this->lock->getAllAcquiredLockKeys();
133 | sort($locks);
134 | $this->assertSame(array('QueuedTrackingLock0', 'QueuedTrackingLock4', 'QueuedTrackingLock5'), $locks);
135 | }
136 |
137 | private function assertNumberOfLocksEquals($numExpectedLocks)
138 | {
139 | $this->assertSame($numExpectedLocks, $this->lock->getNumberOfAcquiredLocks());
140 | }
141 |
142 | private function createLock($redis)
143 | {
144 | return new Lock($redis);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/tests/Integration/QueuedTrackingTest.php:
--------------------------------------------------------------------------------
1 | plugin = new QueuedTracking();
34 |
35 | Factory::getSettings()->queueEnabled->setValue(true);
36 | }
37 |
38 | public function test_replaceHandler_ShouldReplaceHandlerWithQueueHandler_IfEnabled()
39 | {
40 | $handler = null;
41 | $this->plugin->replaceHandlerIfQueueIsEnabled($handler);
42 |
43 | $this->assertTrue($handler instanceof Handler);
44 | }
45 |
46 | public function test_replaceHandler_ShouldEnableProcessingInTrackerModeByDefault()
47 | {
48 | $handler = null;
49 | $this->plugin->replaceHandlerIfQueueIsEnabled($handler);
50 |
51 | $this->assertTrue($handler->isAllowedToProcessInTrackerMode());
52 | }
53 |
54 | public function test_replaceHandler_ShouldNotReplaceHandlerWithQueueHandler_IfDisabled()
55 | {
56 | Factory::getSettings()->queueEnabled->setValue(false);
57 |
58 | $handler = null;
59 | $this->plugin->replaceHandlerIfQueueIsEnabled($handler);
60 |
61 | $this->assertNull($handler);
62 | }
63 |
64 | public function test_replaceHandler_ShouldNotEnableProcessingInTrackerModeIfDisabled()
65 | {
66 | Factory::getSettings()->processDuringTrackingRequest->setValue(false);
67 |
68 | $handler = null;
69 | $this->plugin->replaceHandlerIfQueueIsEnabled($handler);
70 |
71 | $this->assertFalse($handler->isAllowedToProcessInTrackerMode());
72 | }
73 |
74 | public function test_getListHooksRegistered_shouldListenToNewTrackerEventAndCreateQueueHandler()
75 | {
76 | $handler = DefaultHandler\Factory::make();
77 |
78 | $this->assertTrue($handler instanceof Handler);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/Integration/Settings/NumWorkersTest.php:
--------------------------------------------------------------------------------
1 | clearBackend();
34 |
35 | $container = self::$fixture->piwikEnvironment->getContainer();
36 | $this->settings = $container->get('Piwik\Plugins\QueuedTracking\SystemSettings');
37 | }
38 |
39 | public function tearDown(): void
40 | {
41 | $this->clearBackend();
42 | parent::tearDown();
43 | }
44 |
45 | public function test_numQueueWorkers_WhenChangingAValueItMovesRequestsIntoDifferentQueues()
46 | {
47 | $oldNumWorkers = 4;
48 | $newNumWorkers = 2;
49 |
50 | $this->settings->numQueueWorkers->setValue($oldNumWorkers);
51 |
52 | $manager = Factory::makeQueueManager(Factory::makeBackend());
53 |
54 | $requestSet = new RequestSet();
55 | $requestSet->setRequests(array('idsite' => '1', '_id' => 1));
56 |
57 | $queues = $manager->getAllQueues();
58 | foreach ($queues as $queue) {
59 | $queue->addRequestSet($requestSet);
60 | }
61 |
62 | $this->assertSame(4, $manager->getNumberOfRequestSetsInAllQueues());
63 | $this->assertSame(1, $queues[0]->getNumberOfRequestSetsInQueue());
64 | $this->assertSame(1, $queues[1]->getNumberOfRequestSetsInQueue());
65 | $this->assertSame(1, $queues[2]->getNumberOfRequestSetsInQueue());
66 | $this->assertSame(1, $queues[3]->getNumberOfRequestSetsInQueue());
67 |
68 | $this->settings->numQueueWorkers->setValue($newNumWorkers);
69 | $this->settings->save();
70 |
71 | $this->assertSame(4, $manager->getNumberOfRequestSetsInAllQueues());
72 | $this->assertGreaterThanOrEqual(1, $queues[0]->getNumberOfRequestSetsInQueue());
73 | $this->assertGreaterThanOrEqual(1, $queues[1]->getNumberOfRequestSetsInQueue());
74 | $this->assertSame(0, $queues[2]->getNumberOfRequestSetsInQueue());
75 | $this->assertSame(0, $queues[3]->getNumberOfRequestSetsInQueue());
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Integration/SystemCheckTest.php:
--------------------------------------------------------------------------------
1 | systemCheck = new SystemCheck();
33 | }
34 |
35 | public function test_checkIsInstalled_shouldNotFailOnSystemsWherePhpRedisIsAvailable()
36 | {
37 | $this->systemCheck->checkRedisIsInstalled();
38 |
39 | $this->assertTrue(true);
40 | }
41 |
42 | public function test_checkConnectionDetails_shouldFailIfServerIsWrong()
43 | {
44 | $this->expectException(\Exception::class);
45 | $this->expectExceptionMessage('Connection to Redis failed. Please verify Redis host and port');
46 |
47 | $backend = $this->makeBackend('192.168.123.234', 6379, 0.2, null);
48 | $this->systemCheck->checkConnectionDetails($backend);
49 | }
50 |
51 | public function test_checkConnectionDetails_shouldFailIfPortIsWrong()
52 | {
53 | $this->expectException(\Exception::class);
54 | $this->expectExceptionMessage('Connection to Redis failed. Please verify Redis host and port');
55 |
56 | $backend = $this->makeBackend('127.0.0.1', 6370, 0.2, null);
57 | $this->systemCheck->checkConnectionDetails($backend);
58 | }
59 |
60 | public function test_checkConnectionDetails_shouldNotFailIfConnectionDataIsCorrect()
61 | {
62 | $backend = $this->makeBackend('127.0.0.1', 6379, 0.2, null);
63 | $this->systemCheck->checkConnectionDetails($backend);
64 | $this->assertTrue(true);
65 | }
66 |
67 | private function makeBackend($host, $port, $timeout, $password)
68 | {
69 | $settings = Factory::getSettings();
70 | $settings->redisHost->setValue($host);
71 | $settings->redisPort->setValue($port);
72 | $settings->redisTimeout->setIsWritableByCurrentUser(true);
73 | $settings->redisTimeout->setValue($timeout);
74 | $settings->redisPassword->setValue($password);
75 |
76 | return Factory::makeBackendFromSettings($settings);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/System/CheckDirectDependencyUseCommandTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('tests:check-direct-dependency-use is not available in this version');
24 | }
25 |
26 | $pluginName = 'QueuedTracking';
27 |
28 | $checkDirectDependencyUse = new CheckDirectDependencyUse();
29 |
30 | $console = new \Piwik\Console(self::$fixture->piwikEnvironment);
31 | $console->addCommands([$checkDirectDependencyUse]);
32 | $command = $console->find('tests:check-direct-dependency-use');
33 | $arguments = [
34 | 'command' => 'tests:check-direct-dependency-use',
35 | '--plugin' => $pluginName,
36 | '--grep-vendor',
37 | ];
38 |
39 | $inputObject = new ArrayInput($arguments);
40 | $command->run($inputObject, new NullOutput());
41 |
42 | $this->assertEquals([
43 | 'Symfony\Component\Console' => [
44 | 'QueuedTracking/tests/System/CheckDirectDependencyUseCommandTest.php'
45 | ],
46 | ], $checkDirectDependencyUse->usesFoundList[$pluginName]);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/System/TrackerTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Redis extension is not installed, skipping test');
43 | }
44 |
45 | self::$fixture->performSetup();
46 |
47 | $idSite = 1;
48 | $dateTime = '2014-01-01 00:00:01';
49 |
50 | if (!Fixture::siteCreated($idSite)) {
51 | Fixture::createWebsite($dateTime);
52 | }
53 |
54 | $this->tracker = Fixture::getTracker($idSite, $dateTime, $defaultInit = true);
55 | $this->enableQueue();
56 | }
57 |
58 | public function tearDown(): void
59 | {
60 | $this->createRedisBackend()->flushAll();
61 |
62 | self::$fixture->performTearDown();
63 |
64 | parent::tearDown();
65 | }
66 |
67 | public function test_response_ShouldReturnBulkTrackingResponse_IfQueueIsDisabledAndProcessNormallyAndUsesBulk()
68 | {
69 | $this->disableQueue();
70 |
71 | $response = $this->doTrackNumberOfRequests(2);
72 |
73 | $this->assertEquals('{"status":"success","tracked":2,"invalid":0,"invalid_indices":[]}', $response);
74 |
75 | // verify nothing in queue
76 | $this->assertNumEntriesInQueue(0);
77 | }
78 |
79 | public function test_response_ShouldReturnNormalTrackingResponse_IfQueueIsDisabledAndProcessNormally()
80 | {
81 | $this->disableQueue();
82 |
83 | $response = $this->doTrackNumberOfRequests(2, false);
84 |
85 | Fixture::checkResponse($response);
86 |
87 | // verify nothing in queue
88 | $this->assertNumEntriesInQueue(0);
89 | }
90 |
91 | public function test_response_ShouldContainTrackingGifIfTrackedViaQueue()
92 | {
93 | $response = $this->doTrackNumberOfRequests(2, false);
94 |
95 | Fixture::checkResponse($response);
96 | }
97 |
98 | public function test_response_ShouldContainJsonResponseIfTrackedViaQueue_InBulk()
99 | {
100 | $response = $this->doTrackNumberOfRequests(2);
101 |
102 | Fixture::checkResponse($response);
103 | }
104 |
105 | public function test_response_ShouldSetThirdPartyCookieIfEnabled()
106 | {
107 | $this->enableThirdPartyCookie();
108 |
109 | $response = $this->doTrackNumberOfRequests(1);
110 |
111 | $this->disableThirdPartyCookie();
112 |
113 | $cookieName = $this->getThirdPartyCookieName();
114 | $this->assertNotEmpty($this->tracker->getIncomingTrackerCookie($cookieName));
115 | }
116 |
117 | public function test_response_ShouldAcceptThirdPartyCookieIfPresent()
118 | {
119 | $this->enableThirdPartyCookie();
120 |
121 | $cookieName = $this->getThirdPartyCookieName();
122 |
123 | $response = $this->doTrackNumberOfRequests(1);
124 | $cookieValueOne = $this->tracker->getIncomingTrackerCookie($cookieName);
125 |
126 | $this->tracker->setNewVisitorId();
127 | $this->tracker->setOutgoingTrackerCookie($cookieName, $cookieValueOne);
128 |
129 | $response = $this->doTrackNumberOfRequests(1);
130 | $cookieValueTwo = $this->tracker->getIncomingTrackerCookie($cookieName);
131 |
132 | $this->disableThirdPartyCookie();
133 |
134 | $this->assertEquals($cookieValueOne, $cookieValueTwo);
135 | }
136 |
137 | public function test_response_ShouldActuallyAddRequestsToQueue()
138 | {
139 | $this->doTrackNumberOfRequests(2);
140 | $this->assertNumEntriesInQueue(1);
141 |
142 | $this->doTrackNumberOfRequests(1);
143 | $this->assertNumEntriesInQueue(2);
144 | }
145 |
146 | public function test_response_ShouldNotTrackAnythingUnlessQueueStartedProcessing()
147 | {
148 | for ($i = 1; $i < $this->requestProcessLimit; $i++) {
149 | $this->doTrackNumberOfRequests(1);
150 | }
151 |
152 | $this->assertEmpty($this->getIdVisit(1));
153 | }
154 |
155 | /**
156 | * @medium
157 | */
158 | public function test_response_ShouldStartProcessingRequestsOnceLimitAchieved()
159 | {
160 | for ($i = 1; $i < $this->requestProcessLimit; $i++) {
161 | $response = $this->doTrackNumberOfRequests(2);
162 | Fixture::checkResponse($response);
163 | $this->assertNumEntriesInQueue($i);
164 | }
165 |
166 | $this->assertEmpty($this->getIdVisit(1)); // make sure nothing tracked yet
167 |
168 | // the last request should trigger processing them
169 | $this->doTrackNumberOfRequests(1);
170 | // it sends us the response before actually processing them
171 |
172 | $queue = $this->createQueue();
173 | while ($queue->getNumberOfRequestSetsInAllQueues() !== 0) {
174 | usleep(100);
175 | }
176 |
177 | $this->assertNumEntriesInQueue(0);
178 |
179 | // make sure actually tracked
180 | $this->assertNotEmpty($this->getIdVisit(1));
181 | $this->assertActionEquals('Test', 1);
182 | }
183 |
184 | private function doTrackNumberOfRequests($numRequests, $inBulk = true)
185 | {
186 | $inBulk && $this->tracker->enableBulkTracking();
187 |
188 | for ($i = 0; $i < $numRequests; $i++) {
189 | $response = $this->tracker->doTrackPageView('Test');
190 | }
191 |
192 | if ($inBulk) {
193 | $response = $this->tracker->doBulkTrack();
194 | }
195 |
196 | return $response;
197 | }
198 |
199 | protected function enableQueue()
200 | {
201 | $settings = Queue\Factory::getSettings();
202 | $settings->queueEnabled->setValue(true);
203 | $settings->numRequestsToProcess->setValue($this->requestProcessLimit);
204 | $settings->processDuringTrackingRequest->setValue(true);
205 | $settings->numQueueWorkers->setValue(1);
206 | $settings->redisDatabase->setValue(15);
207 | $settings->redisHost->setValue('127.0.0.1');
208 | $settings->redisPort->setValue(6379);
209 | $settings->save();
210 | }
211 |
212 | protected function disableQueue()
213 | {
214 | $settings = new SystemSettings();
215 | $settings->queueEnabled->setValue(false);
216 | $settings->save();
217 | }
218 |
219 | protected function createQueue()
220 | {
221 | $backend = $this->createRedisBackend();
222 |
223 | return Queue\Factory::makeQueueManager($backend);
224 | }
225 |
226 | protected function enableThirdPartyCookie()
227 | {
228 | $testingEnvironment = new TestingEnvironmentVariables();
229 | $testingEnvironment->overrideConfig('Tracker', 'use_third_party_id_cookie', 1);
230 | $testingEnvironment->save();
231 | }
232 |
233 | protected function disableThirdPartyCookie()
234 | {
235 | $testingEnvironment = new TestingEnvironmentVariables();
236 | $testingEnvironment->overrideConfig('Tracker', 'use_third_party_id_cookie', 0);
237 | $testingEnvironment->save();
238 | }
239 |
240 | protected function getThirdPartyCookieName()
241 | {
242 | return Config::getInstance()->Tracker['cookie_name'];
243 | }
244 |
245 | protected function clearRedisDb()
246 | {
247 | $this->createRedisBackend()->flushAll();
248 | }
249 |
250 | protected function createRedisBackend()
251 | {
252 | return Queue\Factory::makeBackend();
253 | }
254 |
255 | private function assertActionEquals($expected, $idaction)
256 | {
257 | $actionName = Db::fetchOne("SELECT name FROM " . Common::prefixTable('log_action') . " WHERE idaction = ?", array($idaction));
258 | $this->assertEquals($expected, $actionName);
259 | }
260 |
261 | private function getIdVisit($idVisit)
262 | {
263 | return Db::fetchRow("SELECT * FROM " . Common::prefixTable('log_visit') . " WHERE idvisit = ?", array($idVisit));
264 | }
265 |
266 | private function assertNumEntriesInQueue($numRequestSets)
267 | {
268 | $queue = $this->createQueue();
269 | $this->assertSame($numRequestSets, $queue->getNumberOfRequestSetsInAllQueues());
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/tests/UI/QueuedTrackingSettings_spec.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Matomo - free/libre analytics platform
3 | *
4 | * Screenshot integration tests.
5 | *
6 | * @link https://matomo.org
7 | * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 | */
9 |
10 | describe("QueuedTrackingSettings", function () {
11 | this.timeout(0);
12 |
13 | var selector = '.card-content:contains(\'QueuedTracking\')';
14 | var url = "?module=CoreAdminHome&action=generalSettings&idSite=1&period=day&date=yesterday";
15 |
16 | beforeEach(function () {
17 | if (testEnvironment.configOverride.QueuedTracking) {
18 | delete testEnvironment.configOverride.QueuedTracking;
19 | }
20 | testEnvironment.save();
21 | });
22 |
23 | after(function () {
24 | if (testEnvironment.configOverride.QueuedTracking) {
25 | delete testEnvironment.configOverride.QueuedTracking;
26 | }
27 | testEnvironment.save();
28 | });
29 |
30 | it("should display the settings page", async function () {
31 | await page.goto(url);
32 | await page.mouse.move(-10, -10);
33 | expect(await page.screenshotSelector(selector)).to.matchImage('settings_page');
34 | });
35 |
36 | it("should show an error if queue is enabled and redis connection is wrong", async function () {
37 | await page.click('#queueEnabled + span');
38 | await page.type('input[name="redisPort"]', '1');
39 | await (await page.jQuery('.card-content:contains(\'QueuedTracking\') .pluginsSettingsSubmit')).click();
40 | await page.type('.confirm-password-modal input[type=password]', superUserPassword);
41 | await page.click('.confirm-password-modal .modal-close.btn');
42 | await page.waitForNetworkIdle();
43 | // hide all cards, except of QueueTracking
44 | await page.evaluate(function(){
45 | $('.card-content').hide();
46 | $('.card-content:contains(\'QueuedTracking\')').show();
47 | });
48 | await page.mouse.move(-10, -10);
49 | expect(await page.screenshotSelector(selector + ',#ajaxError,#notificationContainer')).to.matchImage('settings_save_error');
50 | });
51 |
52 | it("should display the settings page with sentinel enabled", async function () {
53 |
54 | testEnvironment.overrideConfig('QueuedTracking', {
55 | useWhatRedisBackendType: '2'
56 | });
57 | testEnvironment.save();
58 |
59 | await page.goto(url);
60 | await page.mouse.move(-10, -10);
61 | expect(await page.screenshotSelector(selector)).to.matchImage('settings_page_sentinel');
62 | });
63 |
64 | });
65 |
--------------------------------------------------------------------------------
/tests/UI/expected-ui-screenshots/QueuedTrackingSettings_settings_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matomo-org/plugin-QueuedTracking/d319dec89f914bb26afeb101a0dd46a130c26ee8/tests/UI/expected-ui-screenshots/QueuedTrackingSettings_settings_page.png
--------------------------------------------------------------------------------
/tests/UI/expected-ui-screenshots/QueuedTrackingSettings_settings_page_sentinel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matomo-org/plugin-QueuedTracking/d319dec89f914bb26afeb101a0dd46a130c26ee8/tests/UI/expected-ui-screenshots/QueuedTrackingSettings_settings_page_sentinel.png
--------------------------------------------------------------------------------
/tests/UI/expected-ui-screenshots/QueuedTrackingSettings_settings_save_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matomo-org/plugin-QueuedTracking/d319dec89f914bb26afeb101a0dd46a130c26ee8/tests/UI/expected-ui-screenshots/QueuedTrackingSettings_settings_save_error.png
--------------------------------------------------------------------------------
/tests/Unit/ConfigurationTest.php:
--------------------------------------------------------------------------------
1 | configuration = new Configuration();
32 | }
33 |
34 | public function test_shouldInstallConfig()
35 | {
36 | $this->configuration->install();
37 |
38 | $QueuedTracking = Config::getInstance()->QueuedTracking;
39 | $this->assertEquals(array(
40 | 'notify_queue_threshold_single_queue' => Configuration::DEFAULT_NOTIFY_THRESHOLD,
41 | 'notify_queue_threshold_emails' => Configuration::$DEFAULT_NOTIFY_EMAILS
42 | ), $QueuedTracking);
43 | }
44 |
45 | public function test_getNotifyThreshold_shouldReturnDefaultThreshold()
46 | {
47 | $this->assertEquals(250000, $this->configuration->getNotifyThreshold());
48 | }
49 |
50 | public function test_getNotifyThreshold_shouldBePossibleToChangeValue()
51 | {
52 | Config::getInstance()->QueuedTracking = array(
53 | Configuration::KEY_NOTIFY_THRESHOLD => 150
54 | );
55 | $this->assertEquals(150, $this->configuration->getNotifyThreshold());
56 | }
57 |
58 | public function test_getNotifyThreshold_noConfig_shouldReturnDefault()
59 | {
60 | Config::getInstance()->QueuedTracking = array();
61 | $this->assertEquals(Configuration::DEFAULT_NOTIFY_THRESHOLD, $this->configuration->getNotifyThreshold());
62 | }
63 |
64 |
65 | public function test_getNotifyEmails_shouldReturnDefaultThreshold()
66 | {
67 | $this->assertEquals(array(), $this->configuration->getNotifyEmails());
68 | }
69 |
70 | public function test_getNotifyEmails_shouldBePossibleToChangeValue()
71 | {
72 | Config::getInstance()->QueuedTracking = array(
73 | Configuration::KEY_NOTIFY_EMAILS => ['test@matomo.org']
74 | );
75 | $this->assertEquals(['test@matomo.org'], $this->configuration->getNotifyEmails());
76 | }
77 |
78 | public function test_getNotifyEmails_noConfig_shouldReturnDefault()
79 | {
80 | Config::getInstance()->QueuedTracking = array();
81 | $this->assertEquals(Configuration::$DEFAULT_NOTIFY_EMAILS, $this->configuration->getNotifyEmails());
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tests/Unit/Queue/Processor/HandlerTest.php:
--------------------------------------------------------------------------------
1 | handlerDb;
24 | }
25 |
26 | public function setDb($db)
27 | {
28 | $this->handlerDb = $db;
29 | }
30 |
31 | public function getTransactionId()
32 | {
33 | return $this->transactionId;
34 | }
35 | }
36 |
37 | /**
38 | * @group QueuedTracking
39 | * @group HandlerTest
40 | * @group Plugins
41 | */
42 | class HandlerTest extends UnitTestCase
43 | {
44 | /**
45 | * @var TestHandler
46 | */
47 | private $handler;
48 |
49 | /**
50 | * @var Tracker
51 | */
52 | private $tracker;
53 |
54 | /**
55 | * @var Db
56 | */
57 | private $db;
58 |
59 | private $transactionId = 'my4929transactionid';
60 |
61 | public function setUp(): void
62 | {
63 | parent::setUp();
64 | $this->handler = new TestHandler();
65 | $this->tracker = new Tracker();
66 | $this->db = new Db(array());
67 | $this->handler->setDb($this->db);
68 | }
69 |
70 | public function test_init_ShouldStartADatabaseTransaction()
71 | {
72 | $this->assertFalse($this->db->beganTransaction);
73 |
74 | $this->handler->init($this->tracker);
75 |
76 | $this->assertEquals($this->transactionId, $this->handler->getTransactionId());
77 | $this->assertTrue($this->db->beganTransaction);
78 | }
79 |
80 | public function test_commit_ShouldCommitTransaction()
81 | {
82 | $this->handler->init($this->tracker);
83 |
84 | $this->handler->commit($this->tracker);
85 |
86 | $this->assertEquals($this->transactionId, $this->db->commitTransactionId);
87 | $this->assertFalse($this->db->rollbackTransactionId);
88 | }
89 |
90 | public function test_rollback_ShouldRollbackTransaction()
91 | {
92 | $this->handler->init($this->tracker);
93 | $this->handler->rollBack($this->tracker);
94 |
95 | $this->assertEquals($this->transactionId, $this->db->rollbackTransactionId);
96 | $this->assertFalse($this->db->commitTransactionId);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/Unit/Queue/Requests/TestQueuedRequest.json:
--------------------------------------------------------------------------------
1 | {"requests":[{"e_c":"NodeName: H1","e_a":"ClickId: ","e_n":"ClickText: *******","e_v":"","ca":"1","idsite":"1","rec":"1","r":"666433","h":"9","m":"55","s":"28","url":"https:\/\/testsite\/#","urlref":"https:\/\/testsite\/","_id":"5ed0ce934bfebe39","_idn":"0","send_image":"0","_refts":"1668025608","_ref":"https:\/\/tagassistant.google.com\/","pdf":"1","qt":"0","realp":"0","wma":"0","fla":"0","java":"0","ag":"0","cookie":"1","res":"2560x1440","dimension1":"This is my super secret data in the DOM.","dimension2":"dimensionValue","pv_id":"1kNpqu","uadata":"{\"brands\":[{\"brand\":\"Brave\",\"version\":\"107\"},{\"brand\":\"Chromium\",\"version\":\"107\"},{\"brand\":\"Not=A?Brand\",\"version\":\"24\"}],\"platform\":\"Linux\"}"}],"env":{"server":{"USER":"www-data","HOME":"\/var\/www","SCRIPT_NAME":"\/matomo.php","REQUEST_URI":"\/matomo.php?e_c=NodeName%3A%20H1&e_a=ClickId%3A%20&e_n=ClickText%3A%20*******&e_v=&ca=1&idsite=1&rec=1&r=666433&h=9&m=55&s=28&url=https%3A%2F%2Ftestsite%2F%23&urlref=https%3A%2F%2Ftestsite%2F&_id=5ed0ce934bfebe39&_idn=0&send_image=0&_refts=1668025608&_ref=https%3A%2F%2Ftagassistant.google.com%2F&pdf=1&qt=0&realp=0&wma=0&fla=0&java=0&ag=0&cookie=1&res=2560x1440&dimension1=This%20is%20my%20super%20secret%20data%20in%20the%20DOM.&dimension2=dimensionValue&pv_id=1kNpqu&uadata=%7B%22brands%22%3A%5B%7B%22brand%22%3A%22Brave%22%2C%22version%22%3A%22107%22%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%22107%22%7D%2C%7B%22brand%22%3A%22Not%3DA%3FBrand%22%2C%22version%22%3A%2224%22%7D%5D%2C%22platform%22%3A%22Linux%22%7D","QUERY_STRING":"e_c=NodeName%3A%20H1&e_a=ClickId%3A%20&e_n=ClickText%3A%20*******&e_v=&ca=1&idsite=1&rec=1&r=666433&h=9&m=55&s=28&url=https%3A%2F%2Ftestsite%2F%23&urlref=https%3A%2F%2Ftestsite%2F&_id=5ed0ce934bfebe39&_idn=0&send_image=0&_refts=1668025608&_ref=https%3A%2F%2Ftagassistant.google.com%2F&pdf=1&qt=0&realp=0&wma=0&fla=0&java=0&ag=0&cookie=1&res=2560x1440&dimension1=This%20is%20my%20super%20secret%20data%20in%20the%20DOM.&dimension2=dimensionValue&pv_id=1kNpqu&uadata=%7B%22brands%22%3A%5B%7B%22brand%22%3A%22Brave%22%2C%22version%22%3A%22107%22%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%22107%22%7D%2C%7B%22brand%22%3A%22Not%3DA%3FBrand%22%2C%22version%22%3A%2224%22%7D%5D%2C%22platform%22%3A%22Linux%22%7D","REQUEST_METHOD":"POST","SERVER_PROTOCOL":"HTTP\/1.1","GATEWAY_INTERFACE":"CGI\/1.1","REMOTE_PORT":"44162","SCRIPT_FILENAME":"\/home\/jacobr\/projects\/matomo\/matomo.php","SERVER_ADMIN":"admin@example.com","CONTEXT_DOCUMENT_ROOT":"\/home\/jacobr\/projects\/matomo","CONTEXT_PREFIX":"","REQUEST_SCHEME":"https","DOCUMENT_ROOT":"\/home\/jacobr\/projects\/matomo","REMOTE_ADDR":"127.0.0.1","SERVER_PORT":"443","SERVER_ADDR":"127.0.0.1","SERVER_NAME":"matomo.com","SERVER_SOFTWARE":"Apache\/2.4.52 (Ubuntu)","SERVER_SIGNATURE":"Apache\/2.4.52 (Ubuntu) Server at matomo.com Port 443<\/address>\n","PATH":"\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin:\/snap\/bin","HTTP_ACCEPT_LANGUAGE":"en-GB,en-US;q=0.9,en;q=0.8","HTTP_ACCEPT_ENCODING":"gzip, deflate, br","HTTP_REFERER":"https:\/\/testsite\/","HTTP_SEC_FETCH_DEST":"empty","HTTP_SEC_FETCH_MODE":"no-cors","HTTP_SEC_FETCH_SITE":"cross-site","HTTP_ORIGIN":"https:\/\/testsite","HTTP_SEC_GPC":"1","HTTP_ACCEPT":"*\/*","CONTENT_TYPE":"application\/x-www-form-urlencoded; charset=utf-8","HTTP_USER_AGENT":"Mozilla\/5.0 (X11; Linux x86_64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/107.0.0.0 Safari\/537.36","CONTENT_LENGTH":"0","HTTP_CONNECTION":"keep-alive","HTTP_HOST":"matomo.com","proxy-nokeepalive":"1","SSL_TLS_SNI":"matomo.com","HTTPS":"on","FCGI_ROLE":"RESPONDER","PHP_SELF":"\/matomo.php","REQUEST_TIME_FLOAT":1668027329.120046,"REQUEST_TIME":1668027329},"cookie":[]},"tokenAuth":false,"time":1668027329}
--------------------------------------------------------------------------------
/tests/Unit/QueueTest.php:
--------------------------------------------------------------------------------
1 | queue = new Queue(new Queue\Backend\MySQL(), 1);
33 |
34 | $this->requestsString = file_get_contents(__DIR__ . '/Queue/Requests/TestQueuedRequest.json');
35 | }
36 |
37 | public function test_ensureJsonVarsAreStrings()
38 | {
39 | $requests = json_decode($this->requestsString, true);
40 | $this->queue->ensureJsonVarsAreStrings($requests);
41 | $this->assertEquals(json_decode($this->requestsString, true), $requests, 'The requests should not have changed');
42 | }
43 |
44 | public function test_ensureJsonVarsAreStrings_shouldFixUadataType()
45 | {
46 | $requests = json_decode($this->requestsString, true);
47 | $params = $requests['requests'];
48 | $requests['requests'][0]['uadata'] = json_decode($params[0]['uadata']);
49 | $this->queue->ensureJsonVarsAreStrings($requests);
50 | $this->assertEquals(json_decode($this->requestsString, true), $requests, 'The requests should not have changed');
51 | }
52 | }
53 |
--------------------------------------------------------------------------------