Completed
Push — master ( 83d93d...f27f45 )
by Joas
29:23
created
lib/private/Profiler/FileProfilerStorage.php 2 patches
Indentation   +291 added lines, -291 removed lines patch added patch discarded remove patch
@@ -14,295 +14,295 @@
 block discarded – undo
14 14
  * Storage for profiler using files.
15 15
  */
16 16
 class FileProfilerStorage {
17
-	// Folder where profiler data are stored.
18
-	private string $folder;
19
-
20
-	/** @psalm-suppress UndefinedClass */
21
-	public const allowedClasses = [
22
-		\OCA\Profiler\DataCollector\EventLoggerDataProvider::class,
23
-		\OCA\Profiler\DataCollector\HttpDataCollector::class,
24
-		\OCA\Profiler\DataCollector\MemoryDataCollector::class,
25
-		\OCA\User_LDAP\DataCollector\LdapDataCollector::class,
26
-		\OC\Memcache\ProfilerWrapperCache::class,
27
-		\OC\Profiler\RoutingDataCollector::class,
28
-		\OC\DB\DbDataCollector::class,
29
-	];
30
-
31
-	/**
32
-	 * Constructs the file storage using a "dsn-like" path.
33
-	 *
34
-	 * Example : "file:/path/to/the/storage/folder"
35
-	 *
36
-	 * @throws \RuntimeException
37
-	 */
38
-	public function __construct(string $folder) {
39
-		$this->folder = $folder;
40
-
41
-		if (!is_dir($this->folder) && @mkdir($this->folder, 0777, true) === false && !is_dir($this->folder)) {
42
-			throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
43
-		}
44
-	}
45
-
46
-	public function find(?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null): array {
47
-		$file = $this->getIndexFilename();
48
-
49
-		if (!file_exists($file)) {
50
-			return [];
51
-		}
52
-
53
-		$file = fopen($file, 'r');
54
-		fseek($file, 0, \SEEK_END);
55
-
56
-		$result = [];
57
-		while (\count($result) < $limit && $line = $this->readLineFromFile($file)) {
58
-			$values = str_getcsv($line);
59
-			[$csvToken, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values;
60
-			$csvTime = (int)$csvTime;
61
-
62
-			if (($url && !str_contains($csvUrl, $url))
63
-				|| ($method && !str_contains($csvMethod, $method))
64
-				|| ($statusCode && !str_contains($csvStatusCode, $statusCode))) {
65
-				continue;
66
-			}
67
-
68
-			if ($start !== null && $csvTime < $start) {
69
-				continue;
70
-			}
71
-
72
-			if ($end !== null && $csvTime > $end) {
73
-				continue;
74
-			}
75
-
76
-			$result[$csvToken] = [
77
-				'token' => $csvToken,
78
-				'method' => $csvMethod,
79
-				'url' => $csvUrl,
80
-				'time' => $csvTime,
81
-				'parent' => $csvParent,
82
-				'status_code' => $csvStatusCode,
83
-			];
84
-		}
85
-
86
-		fclose($file);
87
-
88
-		return array_values($result);
89
-	}
90
-
91
-	public function purge(): void {
92
-		$flags = \FilesystemIterator::SKIP_DOTS;
93
-		$iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
94
-		$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
95
-
96
-		foreach ($iterator as $file) {
97
-			$path = $file->getPathname();
98
-			if (is_file($path)) {
99
-				unlink($path);
100
-			} else {
101
-				rmdir($path);
102
-			}
103
-		}
104
-	}
105
-
106
-	public function read(string $token): ?IProfile {
107
-		if (!$token || !file_exists($file = $this->getFilename($token))) {
108
-			return null;
109
-		}
110
-
111
-		$h = fopen($file, 'r');
112
-		flock($h, \LOCK_SH);
113
-		$data = stream_get_contents($h);
114
-		flock($h, \LOCK_UN);
115
-		fclose($h);
116
-
117
-		if (\function_exists('gzdecode')) {
118
-			$data = @gzdecode($data) ?: $data;
119
-		}
120
-
121
-		if (!$data = unserialize($data, ['allowed_classes' => self::allowedClasses])) {
122
-			return null;
123
-		}
124
-
125
-		return $this->createProfileFromData($token, $data);
126
-	}
127
-
128
-	/**
129
-	 * @throws \RuntimeException
130
-	 */
131
-	public function write(IProfile $profile): bool {
132
-		$file = $this->getFilename($profile->getToken());
133
-
134
-		$profileIndexed = is_file($file);
135
-		if (!$profileIndexed) {
136
-			// Create directory
137
-			$dir = \dirname($file);
138
-			if (!is_dir($dir) && @mkdir($dir, 0777, true) === false && !is_dir($dir)) {
139
-				throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
140
-			}
141
-		}
142
-
143
-		$profileToken = $profile->getToken();
144
-		// when there are errors in sub-requests, the parent and/or children tokens
145
-		// may equal the profile token, resulting in infinite loops
146
-		$parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
147
-		$childrenToken = array_filter(array_map(function (IProfile $p) use ($profileToken) {
148
-			return $profileToken !== $p->getToken() ? $p->getToken() : null;
149
-		}, $profile->getChildren()));
150
-
151
-		// Store profile
152
-		$data = [
153
-			'token' => $profileToken,
154
-			'parent' => $parentToken,
155
-			'children' => $childrenToken,
156
-			'data' => $profile->getCollectors(),
157
-			'method' => $profile->getMethod(),
158
-			'url' => $profile->getUrl(),
159
-			'time' => $profile->getTime(),
160
-			'status_code' => $profile->getStatusCode(),
161
-		];
162
-
163
-		$data = serialize($data);
164
-
165
-		if (\function_exists('gzencode')) {
166
-			$data = gzencode($data, 3);
167
-		}
168
-
169
-		if (file_put_contents($file, $data, \LOCK_EX) === false) {
170
-			return false;
171
-		}
172
-
173
-		if (!$profileIndexed) {
174
-			// Add to index
175
-			if (false === $file = fopen($this->getIndexFilename(), 'a')) {
176
-				return false;
177
-			}
178
-
179
-			fputcsv($file, array_map([$this, 'escapeFormulae'], [
180
-				$profile->getToken(),
181
-				$profile->getMethod(),
182
-				$profile->getUrl(),
183
-				$profile->getTime(),
184
-				$profile->getParentToken(),
185
-				$profile->getStatusCode(),
186
-			]), escape: '');
187
-			fclose($file);
188
-		}
189
-
190
-		return true;
191
-	}
192
-
193
-	protected function escapeFormulae(?string $value): ?string {
194
-		if ($value !== null && preg_match('/^[=+\-@\t\r]/', $value)) {
195
-			return "'" . $value;
196
-		}
197
-		return $value;
198
-	}
199
-
200
-	/**
201
-	 * Gets filename to store data, associated to the token.
202
-	 *
203
-	 * @return string The profile filename
204
-	 */
205
-	protected function getFilename(string $token): string {
206
-		// Uses 4 last characters, because first are mostly the same.
207
-		$folderA = substr($token, -2, 2);
208
-		$folderB = substr($token, -4, 2);
209
-
210
-		return $this->folder . '/' . $folderA . '/' . $folderB . '/' . $token;
211
-	}
212
-
213
-	/**
214
-	 * Gets the index filename.
215
-	 *
216
-	 * @return string The index filename
217
-	 */
218
-	protected function getIndexFilename(): string {
219
-		return $this->folder . '/index.csv';
220
-	}
221
-
222
-	/**
223
-	 * Reads a line in the file, backward.
224
-	 *
225
-	 * This function automatically skips the empty lines and do not include the line return in result value.
226
-	 *
227
-	 * @param resource $file The file resource, with the pointer placed at the end of the line to read
228
-	 *
229
-	 * @return ?string A string representing the line or null if beginning of file is reached
230
-	 */
231
-	protected function readLineFromFile($file): ?string {
232
-		$line = '';
233
-		$position = ftell($file);
234
-
235
-		if ($position === 0) {
236
-			return null;
237
-		}
238
-
239
-		while (true) {
240
-			$chunkSize = min($position, 1024);
241
-			$position -= $chunkSize;
242
-			fseek($file, $position);
243
-
244
-			if ($chunkSize === 0) {
245
-				// bof reached
246
-				break;
247
-			}
248
-
249
-			$buffer = fread($file, $chunkSize);
250
-
251
-			if (false === ($upTo = strrpos($buffer, "\n"))) {
252
-				$line = $buffer . $line;
253
-				continue;
254
-			}
255
-
256
-			$position += $upTo;
257
-			$line = substr($buffer, $upTo + 1) . $line;
258
-			fseek($file, max(0, $position), \SEEK_SET);
259
-
260
-			if ($line !== '') {
261
-				break;
262
-			}
263
-		}
264
-
265
-		return $line === '' ? null : $line;
266
-	}
267
-
268
-	protected function createProfileFromData(string $token, array $data, ?IProfile $parent = null): IProfile {
269
-		$profile = new Profile($token);
270
-		$profile->setMethod($data['method']);
271
-		$profile->setUrl($data['url']);
272
-		$profile->setTime($data['time']);
273
-		$profile->setStatusCode($data['status_code']);
274
-		$profile->setCollectors($data['data']);
275
-
276
-		if (!$parent && $data['parent']) {
277
-			$parent = $this->read($data['parent']);
278
-		}
279
-
280
-		if ($parent) {
281
-			$profile->setParent($parent);
282
-		}
283
-
284
-		foreach ($data['children'] as $token) {
285
-			if (!$token || !file_exists($file = $this->getFilename($token))) {
286
-				continue;
287
-			}
288
-
289
-			$h = fopen($file, 'r');
290
-			flock($h, \LOCK_SH);
291
-			$data = stream_get_contents($h);
292
-			flock($h, \LOCK_UN);
293
-			fclose($h);
294
-
295
-			if (\function_exists('gzdecode')) {
296
-				$data = @gzdecode($data) ?: $data;
297
-			}
298
-
299
-			if (!$data = unserialize($data, ['allowed_classes' => self::allowedClasses])) {
300
-				continue;
301
-			}
302
-
303
-			$profile->addChild($this->createProfileFromData($token, $data, $profile));
304
-		}
305
-
306
-		return $profile;
307
-	}
17
+    // Folder where profiler data are stored.
18
+    private string $folder;
19
+
20
+    /** @psalm-suppress UndefinedClass */
21
+    public const allowedClasses = [
22
+        \OCA\Profiler\DataCollector\EventLoggerDataProvider::class,
23
+        \OCA\Profiler\DataCollector\HttpDataCollector::class,
24
+        \OCA\Profiler\DataCollector\MemoryDataCollector::class,
25
+        \OCA\User_LDAP\DataCollector\LdapDataCollector::class,
26
+        \OC\Memcache\ProfilerWrapperCache::class,
27
+        \OC\Profiler\RoutingDataCollector::class,
28
+        \OC\DB\DbDataCollector::class,
29
+    ];
30
+
31
+    /**
32
+     * Constructs the file storage using a "dsn-like" path.
33
+     *
34
+     * Example : "file:/path/to/the/storage/folder"
35
+     *
36
+     * @throws \RuntimeException
37
+     */
38
+    public function __construct(string $folder) {
39
+        $this->folder = $folder;
40
+
41
+        if (!is_dir($this->folder) && @mkdir($this->folder, 0777, true) === false && !is_dir($this->folder)) {
42
+            throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
43
+        }
44
+    }
45
+
46
+    public function find(?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null): array {
47
+        $file = $this->getIndexFilename();
48
+
49
+        if (!file_exists($file)) {
50
+            return [];
51
+        }
52
+
53
+        $file = fopen($file, 'r');
54
+        fseek($file, 0, \SEEK_END);
55
+
56
+        $result = [];
57
+        while (\count($result) < $limit && $line = $this->readLineFromFile($file)) {
58
+            $values = str_getcsv($line);
59
+            [$csvToken, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values;
60
+            $csvTime = (int)$csvTime;
61
+
62
+            if (($url && !str_contains($csvUrl, $url))
63
+                || ($method && !str_contains($csvMethod, $method))
64
+                || ($statusCode && !str_contains($csvStatusCode, $statusCode))) {
65
+                continue;
66
+            }
67
+
68
+            if ($start !== null && $csvTime < $start) {
69
+                continue;
70
+            }
71
+
72
+            if ($end !== null && $csvTime > $end) {
73
+                continue;
74
+            }
75
+
76
+            $result[$csvToken] = [
77
+                'token' => $csvToken,
78
+                'method' => $csvMethod,
79
+                'url' => $csvUrl,
80
+                'time' => $csvTime,
81
+                'parent' => $csvParent,
82
+                'status_code' => $csvStatusCode,
83
+            ];
84
+        }
85
+
86
+        fclose($file);
87
+
88
+        return array_values($result);
89
+    }
90
+
91
+    public function purge(): void {
92
+        $flags = \FilesystemIterator::SKIP_DOTS;
93
+        $iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
94
+        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
95
+
96
+        foreach ($iterator as $file) {
97
+            $path = $file->getPathname();
98
+            if (is_file($path)) {
99
+                unlink($path);
100
+            } else {
101
+                rmdir($path);
102
+            }
103
+        }
104
+    }
105
+
106
+    public function read(string $token): ?IProfile {
107
+        if (!$token || !file_exists($file = $this->getFilename($token))) {
108
+            return null;
109
+        }
110
+
111
+        $h = fopen($file, 'r');
112
+        flock($h, \LOCK_SH);
113
+        $data = stream_get_contents($h);
114
+        flock($h, \LOCK_UN);
115
+        fclose($h);
116
+
117
+        if (\function_exists('gzdecode')) {
118
+            $data = @gzdecode($data) ?: $data;
119
+        }
120
+
121
+        if (!$data = unserialize($data, ['allowed_classes' => self::allowedClasses])) {
122
+            return null;
123
+        }
124
+
125
+        return $this->createProfileFromData($token, $data);
126
+    }
127
+
128
+    /**
129
+     * @throws \RuntimeException
130
+     */
131
+    public function write(IProfile $profile): bool {
132
+        $file = $this->getFilename($profile->getToken());
133
+
134
+        $profileIndexed = is_file($file);
135
+        if (!$profileIndexed) {
136
+            // Create directory
137
+            $dir = \dirname($file);
138
+            if (!is_dir($dir) && @mkdir($dir, 0777, true) === false && !is_dir($dir)) {
139
+                throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
140
+            }
141
+        }
142
+
143
+        $profileToken = $profile->getToken();
144
+        // when there are errors in sub-requests, the parent and/or children tokens
145
+        // may equal the profile token, resulting in infinite loops
146
+        $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
147
+        $childrenToken = array_filter(array_map(function (IProfile $p) use ($profileToken) {
148
+            return $profileToken !== $p->getToken() ? $p->getToken() : null;
149
+        }, $profile->getChildren()));
150
+
151
+        // Store profile
152
+        $data = [
153
+            'token' => $profileToken,
154
+            'parent' => $parentToken,
155
+            'children' => $childrenToken,
156
+            'data' => $profile->getCollectors(),
157
+            'method' => $profile->getMethod(),
158
+            'url' => $profile->getUrl(),
159
+            'time' => $profile->getTime(),
160
+            'status_code' => $profile->getStatusCode(),
161
+        ];
162
+
163
+        $data = serialize($data);
164
+
165
+        if (\function_exists('gzencode')) {
166
+            $data = gzencode($data, 3);
167
+        }
168
+
169
+        if (file_put_contents($file, $data, \LOCK_EX) === false) {
170
+            return false;
171
+        }
172
+
173
+        if (!$profileIndexed) {
174
+            // Add to index
175
+            if (false === $file = fopen($this->getIndexFilename(), 'a')) {
176
+                return false;
177
+            }
178
+
179
+            fputcsv($file, array_map([$this, 'escapeFormulae'], [
180
+                $profile->getToken(),
181
+                $profile->getMethod(),
182
+                $profile->getUrl(),
183
+                $profile->getTime(),
184
+                $profile->getParentToken(),
185
+                $profile->getStatusCode(),
186
+            ]), escape: '');
187
+            fclose($file);
188
+        }
189
+
190
+        return true;
191
+    }
192
+
193
+    protected function escapeFormulae(?string $value): ?string {
194
+        if ($value !== null && preg_match('/^[=+\-@\t\r]/', $value)) {
195
+            return "'" . $value;
196
+        }
197
+        return $value;
198
+    }
199
+
200
+    /**
201
+     * Gets filename to store data, associated to the token.
202
+     *
203
+     * @return string The profile filename
204
+     */
205
+    protected function getFilename(string $token): string {
206
+        // Uses 4 last characters, because first are mostly the same.
207
+        $folderA = substr($token, -2, 2);
208
+        $folderB = substr($token, -4, 2);
209
+
210
+        return $this->folder . '/' . $folderA . '/' . $folderB . '/' . $token;
211
+    }
212
+
213
+    /**
214
+     * Gets the index filename.
215
+     *
216
+     * @return string The index filename
217
+     */
218
+    protected function getIndexFilename(): string {
219
+        return $this->folder . '/index.csv';
220
+    }
221
+
222
+    /**
223
+     * Reads a line in the file, backward.
224
+     *
225
+     * This function automatically skips the empty lines and do not include the line return in result value.
226
+     *
227
+     * @param resource $file The file resource, with the pointer placed at the end of the line to read
228
+     *
229
+     * @return ?string A string representing the line or null if beginning of file is reached
230
+     */
231
+    protected function readLineFromFile($file): ?string {
232
+        $line = '';
233
+        $position = ftell($file);
234
+
235
+        if ($position === 0) {
236
+            return null;
237
+        }
238
+
239
+        while (true) {
240
+            $chunkSize = min($position, 1024);
241
+            $position -= $chunkSize;
242
+            fseek($file, $position);
243
+
244
+            if ($chunkSize === 0) {
245
+                // bof reached
246
+                break;
247
+            }
248
+
249
+            $buffer = fread($file, $chunkSize);
250
+
251
+            if (false === ($upTo = strrpos($buffer, "\n"))) {
252
+                $line = $buffer . $line;
253
+                continue;
254
+            }
255
+
256
+            $position += $upTo;
257
+            $line = substr($buffer, $upTo + 1) . $line;
258
+            fseek($file, max(0, $position), \SEEK_SET);
259
+
260
+            if ($line !== '') {
261
+                break;
262
+            }
263
+        }
264
+
265
+        return $line === '' ? null : $line;
266
+    }
267
+
268
+    protected function createProfileFromData(string $token, array $data, ?IProfile $parent = null): IProfile {
269
+        $profile = new Profile($token);
270
+        $profile->setMethod($data['method']);
271
+        $profile->setUrl($data['url']);
272
+        $profile->setTime($data['time']);
273
+        $profile->setStatusCode($data['status_code']);
274
+        $profile->setCollectors($data['data']);
275
+
276
+        if (!$parent && $data['parent']) {
277
+            $parent = $this->read($data['parent']);
278
+        }
279
+
280
+        if ($parent) {
281
+            $profile->setParent($parent);
282
+        }
283
+
284
+        foreach ($data['children'] as $token) {
285
+            if (!$token || !file_exists($file = $this->getFilename($token))) {
286
+                continue;
287
+            }
288
+
289
+            $h = fopen($file, 'r');
290
+            flock($h, \LOCK_SH);
291
+            $data = stream_get_contents($h);
292
+            flock($h, \LOCK_UN);
293
+            fclose($h);
294
+
295
+            if (\function_exists('gzdecode')) {
296
+                $data = @gzdecode($data) ?: $data;
297
+            }
298
+
299
+            if (!$data = unserialize($data, ['allowed_classes' => self::allowedClasses])) {
300
+                continue;
301
+            }
302
+
303
+            $profile->addChild($this->createProfileFromData($token, $data, $profile));
304
+        }
305
+
306
+        return $profile;
307
+    }
308 308
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -1,6 +1,6 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 
3
-declare(strict_types = 1);
3
+declare(strict_types=1);
4 4
 /**
5 5
  * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
6 6
  * SPDX-License-Identifier: AGPL-3.0-or-later
@@ -57,7 +57,7 @@  discard block
 block discarded – undo
57 57
 		while (\count($result) < $limit && $line = $this->readLineFromFile($file)) {
58 58
 			$values = str_getcsv($line);
59 59
 			[$csvToken, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values;
60
-			$csvTime = (int)$csvTime;
60
+			$csvTime = (int) $csvTime;
61 61
 
62 62
 			if (($url && !str_contains($csvUrl, $url))
63 63
 				|| ($method && !str_contains($csvMethod, $method))
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
 		// when there are errors in sub-requests, the parent and/or children tokens
145 145
 		// may equal the profile token, resulting in infinite loops
146 146
 		$parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
147
-		$childrenToken = array_filter(array_map(function (IProfile $p) use ($profileToken) {
147
+		$childrenToken = array_filter(array_map(function(IProfile $p) use ($profileToken) {
148 148
 			return $profileToken !== $p->getToken() ? $p->getToken() : null;
149 149
 		}, $profile->getChildren()));
150 150
 
@@ -192,7 +192,7 @@  discard block
 block discarded – undo
192 192
 
193 193
 	protected function escapeFormulae(?string $value): ?string {
194 194
 		if ($value !== null && preg_match('/^[=+\-@\t\r]/', $value)) {
195
-			return "'" . $value;
195
+			return "'".$value;
196 196
 		}
197 197
 		return $value;
198 198
 	}
@@ -207,7 +207,7 @@  discard block
 block discarded – undo
207 207
 		$folderA = substr($token, -2, 2);
208 208
 		$folderB = substr($token, -4, 2);
209 209
 
210
-		return $this->folder . '/' . $folderA . '/' . $folderB . '/' . $token;
210
+		return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token;
211 211
 	}
212 212
 
213 213
 	/**
@@ -216,7 +216,7 @@  discard block
 block discarded – undo
216 216
 	 * @return string The index filename
217 217
 	 */
218 218
 	protected function getIndexFilename(): string {
219
-		return $this->folder . '/index.csv';
219
+		return $this->folder.'/index.csv';
220 220
 	}
221 221
 
222 222
 	/**
@@ -249,12 +249,12 @@  discard block
 block discarded – undo
249 249
 			$buffer = fread($file, $chunkSize);
250 250
 
251 251
 			if (false === ($upTo = strrpos($buffer, "\n"))) {
252
-				$line = $buffer . $line;
252
+				$line = $buffer.$line;
253 253
 				continue;
254 254
 			}
255 255
 
256 256
 			$position += $upTo;
257
-			$line = substr($buffer, $upTo + 1) . $line;
257
+			$line = substr($buffer, $upTo + 1).$line;
258 258
 			fseek($file, max(0, $position), \SEEK_SET);
259 259
 
260 260
 			if ($line !== '') {
Please login to merge, or discard this patch.