|
3 | 3 |
|
4 | 4 | #if NET6_0_OR_GREATER |
5 | 5 |
|
6 | | -using System.Diagnostics; |
7 | 6 | using System.Globalization; |
8 | | -using System.Text.RegularExpressions; |
9 | 7 | using Microsoft.LinuxTracepoints.Provider; |
10 | 8 | using OpenTelemetry.Exporter.Geneva.Transports; |
| 9 | +using OpenTelemetry.Tests; |
11 | 10 | using Xunit; |
12 | 11 | using Xunit.Abstractions; |
13 | 12 |
|
@@ -246,231 +245,6 @@ private static void EnsureUserEventsEnabled() |
246 | 245 | throw new NotSupportedException("Kernel does not support user_events. Verify your distribution/kernel supports user_events: https://docs.kernel.org/trace/user_events.html."); |
247 | 246 | } |
248 | 247 | } |
249 | | - |
250 | | - private sealed class ConsoleCommand : IDisposable |
251 | | - { |
252 | | - private readonly Process process; |
253 | | - private readonly List<string> output = []; |
254 | | - private readonly List<string> errors = []; |
255 | | - |
256 | | - private ConsoleCommand( |
257 | | - string command, |
258 | | - string arguments, |
259 | | - Action<string>? onOutputReceived, |
260 | | - Action<string>? onErrorReceived) |
261 | | - { |
262 | | - Console.WriteLine($"{command} {arguments}"); |
263 | | - |
264 | | - var process = new Process |
265 | | - { |
266 | | - StartInfo = new() |
267 | | - { |
268 | | - FileName = command, |
269 | | - Arguments = arguments, |
270 | | - RedirectStandardOutput = true, |
271 | | - RedirectStandardError = true, |
272 | | - RedirectStandardInput = false, |
273 | | - }, |
274 | | - }; |
275 | | - |
276 | | - process.OutputDataReceived += (sender, args) => |
277 | | - { |
278 | | - if (!string.IsNullOrEmpty(args.Data)) |
279 | | - { |
280 | | - this.output.Add(args.Data); |
281 | | - Console.WriteLine($"{command} {arguments} [OUT] {args.Data}"); |
282 | | - |
283 | | - onOutputReceived?.Invoke(args.Data); |
284 | | - } |
285 | | - }; |
286 | | - |
287 | | - process.ErrorDataReceived += (sender, args) => |
288 | | - { |
289 | | - if (!string.IsNullOrEmpty(args.Data)) |
290 | | - { |
291 | | - this.errors.Add(args.Data); |
292 | | - Console.WriteLine($"[ERR] {args.Data}"); |
293 | | - |
294 | | - onErrorReceived?.Invoke(args.Data); |
295 | | - } |
296 | | - }; |
297 | | - |
298 | | - process.Start(); |
299 | | - |
300 | | - process.BeginOutputReadLine(); |
301 | | - process.BeginErrorReadLine(); |
302 | | - |
303 | | - this.process = process; |
304 | | - } |
305 | | - |
306 | | - public IEnumerable<string> Output => this.output; |
307 | | - |
308 | | - public IEnumerable<string> Errors => this.errors; |
309 | | - |
310 | | - public static IEnumerable<string> Run( |
311 | | - string command, |
312 | | - string arguments) |
313 | | - { |
314 | | - Run(command, arguments, out _, out var errors); |
315 | | - |
316 | | - return errors; |
317 | | - } |
318 | | - |
319 | | - public static void Run( |
320 | | - string command, |
321 | | - string arguments, |
322 | | - out IEnumerable<string> output, |
323 | | - out IEnumerable<string> errors) |
324 | | - { |
325 | | - var consoleCommand = new ConsoleCommand(command, arguments, onOutputReceived: null, onErrorReceived: null); |
326 | | - consoleCommand.Dispose(); |
327 | | - |
328 | | - output = consoleCommand.Output; |
329 | | - errors = consoleCommand.Errors; |
330 | | - } |
331 | | - |
332 | | - public static ConsoleCommand Start( |
333 | | - string command, |
334 | | - string arguments, |
335 | | - Action<string>? onOutputReceived = null, |
336 | | - Action<string>? onErrorReceived = null) |
337 | | - => new(command, arguments, onOutputReceived, onErrorReceived); |
338 | | - |
339 | | - public void Kill() |
340 | | - { |
341 | | - this.process.Kill(); |
342 | | - } |
343 | | - |
344 | | - public void Dispose() |
345 | | - { |
346 | | - this.process.WaitForExit(); |
347 | | - |
348 | | - this.process.CancelOutputRead(); |
349 | | - this.process.CancelErrorRead(); |
350 | | - |
351 | | - this.process.Dispose(); |
352 | | - } |
353 | | - } |
354 | | - |
355 | | - // Warning: Do NOT use this class/design to listen/read user_events in prod. |
356 | | - // It is a hack to workaround lack of decent bits for listening. Hopefully |
357 | | - // this can be removed if/when |
358 | | - // https://github.com/microsoft/LinuxTracepoints-Net/ has listening bits or |
359 | | - // dotnet/runtime supports user_events (both reading & writing) directly. |
360 | | - private sealed class PerfTracepointListener : IDisposable |
361 | | - { |
362 | | - private readonly string name; |
363 | | - private readonly PerfTracepoint tracepoint; |
364 | | - private readonly ConsoleCommand catCommand; |
365 | | - private readonly Regex eventRegex = new("(\\w+?)=([\\w\\(\\) .,-]*)( |$)", RegexOptions.Compiled); |
366 | | - |
367 | | - public PerfTracepointListener(string name, string nameArgs) |
368 | | - { |
369 | | - this.name = name; |
370 | | - |
371 | | - this.tracepoint = new PerfTracepoint(nameArgs); |
372 | | - |
373 | | - // EACCES (13): Permission denied |
374 | | - if (this.tracepoint.RegisterResult == 13) |
375 | | - { |
376 | | - throw new UnauthorizedAccessException($"Tracepoint could not be registered: '{this.tracepoint.RegisterResult}'. Permission denied."); |
377 | | - } |
378 | | - |
379 | | - if (this.tracepoint.RegisterResult != 0) |
380 | | - { |
381 | | - throw new NotSupportedException($"Tracepoint could not be registered: '{this.tracepoint.RegisterResult}'"); |
382 | | - } |
383 | | - |
384 | | - this.catCommand = ConsoleCommand.Start("cat", "/sys/kernel/debug/tracing/trace_pipe", onOutputReceived: this.OnCatOutputReceived); |
385 | | - if (this.catCommand.Errors.Any()) |
386 | | - { |
387 | | - throw new InvalidOperationException($"Could not read '{name}' tracepoints"); |
388 | | - } |
389 | | - } |
390 | | - |
391 | | - public List<Dictionary<string, string>> Events { get; } = []; |
392 | | - |
393 | | - public bool IsEnabled() |
394 | | - { |
395 | | - ConsoleCommand.Run( |
396 | | - "cat", |
397 | | - $"/sys/kernel/tracing/events/user_events/{this.name}/enable", |
398 | | - out var output, |
399 | | - out var errors); |
400 | | - |
401 | | - return errors.Any() || output.Count() != 1 |
402 | | - ? throw new InvalidOperationException($"Could not determine if '{this.name}' tracepoint is enabled") |
403 | | - : output.First() != "0"; |
404 | | - } |
405 | | - |
406 | | - public void Enable() |
407 | | - { |
408 | | - var errors = ConsoleCommand.Run("sh", @$"-c ""echo '1' > /sys/kernel/tracing/events/user_events/{this.name}/enable"""); |
409 | | - |
410 | | - if (errors.Any()) |
411 | | - { |
412 | | - throw new InvalidOperationException($"Could not enable '{this.name}' tracepoint"); |
413 | | - } |
414 | | - } |
415 | | - |
416 | | - public void Disable() |
417 | | - { |
418 | | - var errors = ConsoleCommand.Run("sh", @$"-c ""echo '0' > /sys/kernel/tracing/events/user_events/{this.name}/enable"""); |
419 | | - |
420 | | - if (errors.Any()) |
421 | | - { |
422 | | - throw new InvalidOperationException($"Could not disable '{this.name}' tracepoint"); |
423 | | - } |
424 | | - } |
425 | | - |
426 | | - public void Dispose() |
427 | | - { |
428 | | - try |
429 | | - { |
430 | | - if (this.catCommand != null) |
431 | | - { |
432 | | - if (this.catCommand.Errors.Any()) |
433 | | - { |
434 | | - throw new InvalidOperationException($"Could not read '{this.name}' tracepoints"); |
435 | | - } |
436 | | - |
437 | | - this.catCommand.Kill(); |
438 | | - this.catCommand.Dispose(); |
439 | | - } |
440 | | - } |
441 | | - finally |
442 | | - { |
443 | | - this.tracepoint.Dispose(); |
444 | | - } |
445 | | - } |
446 | | - |
447 | | - private void OnCatOutputReceived(string output) |
448 | | - { |
449 | | - var name = $": {this.name}:"; |
450 | | - |
451 | | - var startingPosition = output.IndexOf(name, StringComparison.Ordinal); |
452 | | - if (startingPosition < 0) |
453 | | - { |
454 | | - return; |
455 | | - } |
456 | | - |
457 | | - startingPosition += name.Length; |
458 | | - |
459 | | - var matches = this.eventRegex.Matches(output, startingPosition); |
460 | | - |
461 | | - if (matches.Count > 0) |
462 | | - { |
463 | | - Dictionary<string, string> eventData = new(matches.Count); |
464 | | - |
465 | | - foreach (Match match in matches) |
466 | | - { |
467 | | - eventData[match.Groups[1].Value] = match.Groups[2].Value; |
468 | | - } |
469 | | - |
470 | | - this.Events.Add(eventData); |
471 | | - } |
472 | | - } |
473 | | - } |
474 | 248 | } |
475 | 249 |
|
476 | 250 | #endif |
0 commit comments