ddapm-test-agent 1.36.0__py3-none-any.whl → 1.38.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="session-detail-page">
5
+ <div class="page-header">
6
+ <h2>Session {{ token }}</h2>
7
+ </div>
8
+
9
+ {% if error %}
10
+ <div class="error-message">
11
+ <p>{{ error }}</p>
12
+ </div>
13
+ {% else %}
14
+ <div class="session-details">
15
+ <div class="session-info">
16
+ <h3>Session Information</h3>
17
+ <p><strong>Token:</strong> {{ token }}</p>
18
+ <p><strong>Traces:</strong> {{ traces|length }}</p>
19
+ </div>
20
+
21
+ {% if traces %}
22
+ <div class="session-traces">
23
+ <h3>Traces in this Session</h3>
24
+ {% for trace in traces %}
25
+ <div class="trace-summary">
26
+ <p><strong>Spans:</strong> {{ trace|length }}</p>
27
+ {% if trace %}
28
+ <p><strong>Root Service:</strong> {{ trace[0].get('service', 'N/A') }}</p>
29
+ {% endif %}
30
+ </div>
31
+ {% endfor %}
32
+ </div>
33
+ {% endif %}
34
+ </div>
35
+ {% endif %}
36
+ </div>
37
+ {% endblock %}
@@ -0,0 +1,23 @@
1
+ {% extends "base.html" %}
2
+ {% from "macros.html" import page_layout, empty_state, action_button %}
3
+
4
+ {% block content %}
5
+ {% call page_layout("Sessions") %}
6
+ {% if sessions %}
7
+ <div class="sessions-list">
8
+ {% for session in sessions %}
9
+ <div class="session-item">
10
+ <div class="session-header">
11
+ <h3>{{ session }}</h3>
12
+ </div>
13
+ <div class="session-actions">
14
+ {{ action_button("/sessions/" + session, "View Details") }}
15
+ </div>
16
+ </div>
17
+ {% endfor %}
18
+ </div>
19
+ {% else %}
20
+ {{ empty_state("No active sessions") }}
21
+ {% endif %}
22
+ {% endcall %}
23
+ {% endblock %}
@@ -0,0 +1,410 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="snapshot-detail-page">
5
+ <div class="page-header">
6
+ <h2>{{ filename }}</h2>
7
+ <div class="breadcrumb">
8
+ <a href="/snapshots" class="breadcrumb-link">Snapshots</a>
9
+ <span class="breadcrumb-separator">/</span>
10
+ <span class="breadcrumb-current">{{ filename }}</span>
11
+ </div>
12
+ </div>
13
+
14
+ {% if error %}
15
+ <div class="error-message">
16
+ <p>{{ error }}</p>
17
+ </div>
18
+ {% else %}
19
+ <div class="snapshot-meta">
20
+ <div class="meta-card">
21
+ <h3>File Information</h3>
22
+ <div class="meta-details">
23
+ <div class="meta-item">
24
+ <span class="meta-label">Filename:</span>
25
+ <span class="meta-value">{{ file_info.filename }}</span>
26
+ </div>
27
+ <div class="meta-item">
28
+ <span class="meta-label">Size:</span>
29
+ <span class="meta-value">{{ "%.1f" | format(file_info.size / 1024) }} KB</span>
30
+ </div>
31
+ <div class="meta-item">
32
+ <span class="meta-label">Modified:</span>
33
+ <span class="meta-value">{{ file_info.modified | timestamp_format }}</span>
34
+ </div>
35
+ <div class="meta-item">
36
+ <span class="meta-label">JSON Valid:</span>
37
+ <span class="meta-value {{ 'valid' if is_valid_json else 'invalid' }}">
38
+ {{ 'Yes' if is_valid_json else 'No' }}
39
+ </span>
40
+ </div>
41
+ {% if is_valid_json %}
42
+ <div class="meta-item">
43
+ <span class="meta-label">Traces:</span>
44
+ <span class="meta-value">{{ trace_count }}</span>
45
+ </div>
46
+ <div class="meta-item">
47
+ <span class="meta-label">Spans:</span>
48
+ <span class="meta-value">{{ span_count }}</span>
49
+ </div>
50
+ {% endif %}
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ {% if parse_error %}
56
+ <div class="error-message">
57
+ <p><strong>JSON Parse Error:</strong> {{ parse_error }}</p>
58
+ </div>
59
+ {% endif %}
60
+
61
+ <div class="snapshot-content">
62
+ <div class="content-header">
63
+ <h3>Content</h3>
64
+ <div class="content-actions">
65
+ {% if is_valid_json and trace_count > 0 %}
66
+ <button id="view-toggle" class="action-button" onclick="toggleView()">JSON View</button>
67
+ {% endif %}
68
+ <button class="action-button" onclick="copyToClipboard()">Copy</button>
69
+ <button class="action-button" onclick="toggleWrap()">Toggle Wrap</button>
70
+ </div>
71
+ </div>
72
+
73
+ {% if is_valid_json and trace_count > 0 %}
74
+ <div id="waterfall-view" class="waterfall-container">
75
+ <!-- Waterfall visualization will be generated here -->
76
+ </div>
77
+
78
+ <div id="json-view" class="code-container" style="display: none;">
79
+ <pre id="code-content" class="code-block"><code>{{ raw_content }}</code></pre>
80
+ </div>
81
+ {% else %}
82
+ <div id="json-view" class="code-container">
83
+ <pre id="code-content" class="code-block"><code>{{ raw_content }}</code></pre>
84
+ </div>
85
+ {% endif %}
86
+ </div>
87
+ {% endif %}
88
+ </div>
89
+
90
+ <script>
91
+ {% if is_valid_json and trace_count > 0 %}
92
+ const traceData = {{ trace_data | safe }};
93
+ let currentView = 'waterfall';
94
+
95
+ // Service color mapping
96
+ const serviceColorMap = new Map();
97
+ let nextColorIndex = 0;
98
+
99
+ function getServiceColor(serviceName) {
100
+ if (!serviceColorMap.has(serviceName)) {
101
+ serviceColorMap.set(serviceName, nextColorIndex % 8);
102
+ nextColorIndex++;
103
+ }
104
+ return serviceColorMap.get(serviceName);
105
+ }
106
+
107
+ // Generate waterfall view on page load
108
+ document.addEventListener('DOMContentLoaded', function() {
109
+ generateWaterfallView();
110
+ });
111
+
112
+ function toggleView() {
113
+ const toggleButton = document.getElementById('view-toggle');
114
+ const waterfallView = document.getElementById('waterfall-view');
115
+ const jsonView = document.getElementById('json-view');
116
+
117
+ if (currentView === 'waterfall') {
118
+ // Switch to JSON view
119
+ waterfallView.style.display = 'none';
120
+ jsonView.style.display = 'block';
121
+ toggleButton.textContent = 'Waterfall View';
122
+ currentView = 'json';
123
+ } else {
124
+ // Switch to waterfall view
125
+ if (!waterfallView.hasChildNodes()) {
126
+ generateWaterfallView();
127
+ }
128
+ waterfallView.style.display = 'block';
129
+ jsonView.style.display = 'none';
130
+ toggleButton.textContent = 'JSON View';
131
+ currentView = 'waterfall';
132
+ }
133
+ }
134
+
135
+ function generateWaterfallView() {
136
+ const container = document.getElementById('waterfall-view');
137
+
138
+ console.log('Generating waterfall view, traceData:', traceData);
139
+
140
+ if (!traceData) {
141
+ container.innerHTML = '<p class="empty-state">No trace data provided</p>';
142
+ return;
143
+ }
144
+
145
+ // Handle both single trace (object) and multiple traces (array) formats
146
+ let tracesToProcess = [];
147
+ if (Array.isArray(traceData)) {
148
+ if (traceData.length === 0) {
149
+ container.innerHTML = '<p class="empty-state">No trace data available for waterfall view</p>';
150
+ return;
151
+ }
152
+ tracesToProcess = traceData;
153
+ } else if (typeof traceData === 'object') {
154
+ // Single trace or single span
155
+ tracesToProcess = [traceData];
156
+ } else {
157
+ container.innerHTML = '<p class="empty-state">Invalid trace data format</p>';
158
+ return;
159
+ }
160
+
161
+ let html = '<div class="waterfall-traces">';
162
+ let hasValidTraces = false;
163
+
164
+ tracesToProcess.forEach((trace, traceIndex) => {
165
+ console.log('Processing trace:', trace);
166
+
167
+ let spans = [];
168
+
169
+ // Handle different trace formats
170
+ if (Array.isArray(trace)) {
171
+ if (trace.length === 0) return;
172
+ spans = trace;
173
+ } else if (typeof trace === 'object' && trace.span_id) {
174
+ // Single span
175
+ spans = [trace];
176
+ } else {
177
+ console.log('Skipping trace with unsupported format:', trace);
178
+ return;
179
+ }
180
+
181
+ hasValidTraces = true;
182
+
183
+ // Find the earliest start time and calculate total duration for this trace
184
+ const validSpans = spans.filter(span => span && typeof span === 'object');
185
+ if (validSpans.length === 0) return;
186
+
187
+ let minStart = Math.min(...validSpans.map(span => span.start || 0));
188
+ let maxEnd = Math.max(...validSpans.map(span => (span.start || 0) + (span.duration || 0)));
189
+ let totalDuration = maxEnd - minStart;
190
+
191
+ // Handle case where all spans have 0 duration
192
+ if (totalDuration === 0) {
193
+ totalDuration = Math.max(...validSpans.map(span => span.duration || 0));
194
+ }
195
+
196
+ const traceId = validSpans[0].trace_id !== undefined ? validSpans[0].trace_id : traceIndex;
197
+
198
+ html += `<div class="waterfall-trace">
199
+ <div class="trace-header" onclick="toggleTrace(${traceIndex})">
200
+ <div class="trace-header-left">
201
+ <span class="trace-toggle" id="trace-toggle-${traceIndex}">▼</span>
202
+ <h4>Trace ${traceId}</h4>
203
+ </div>
204
+ <span class="trace-duration">${formatDuration(totalDuration)}</span>
205
+ </div>
206
+ <div class="spans-timeline" id="trace-spans-${traceIndex}">`;
207
+
208
+ // Sort spans by start time and build hierarchy
209
+ const sortedSpans = [...validSpans].sort((a, b) => (a.start || 0) - (b.start || 0));
210
+ const spanHierarchy = buildSpanHierarchy(sortedSpans);
211
+
212
+ spanHierarchy.forEach(spanInfo => {
213
+ html += renderSpan(spanInfo, minStart, totalDuration, 0);
214
+ });
215
+
216
+ html += '</div></div>';
217
+ });
218
+
219
+ html += '</div>';
220
+
221
+ if (!hasValidTraces) {
222
+ container.innerHTML = '<p class="empty-state">No valid trace data found for waterfall view</p>';
223
+ } else {
224
+ container.innerHTML = html;
225
+ }
226
+ }
227
+
228
+ function buildSpanHierarchy(spans) {
229
+ const spanMap = new Map();
230
+ const rootSpans = [];
231
+
232
+ // Create span objects with children arrays
233
+ spans.forEach(span => {
234
+ spanMap.set(span.span_id, { ...span, children: [] });
235
+ });
236
+
237
+ // Build parent-child relationships
238
+ spans.forEach(span => {
239
+ if (span.parent_id && span.parent_id !== 0 && spanMap.has(span.parent_id)) {
240
+ spanMap.get(span.parent_id).children.push(spanMap.get(span.span_id));
241
+ } else {
242
+ rootSpans.push(spanMap.get(span.span_id));
243
+ }
244
+ });
245
+
246
+ return rootSpans;
247
+ }
248
+
249
+ function renderSpan(span, traceStart, totalDuration, depth) {
250
+ const start = span.start || 0;
251
+ const duration = span.duration || 0;
252
+ const relativeStart = start - traceStart;
253
+ const startPercent = totalDuration > 0 ? (relativeStart / totalDuration) * 100 : 0;
254
+ const widthPercent = totalDuration > 0 ? (duration / totalDuration) * 100 : 0;
255
+
256
+ const service = span.service || 'unknown';
257
+ const resource = span.resource || span.name || 'unknown';
258
+ const spanClass = span.error ? 'span-error' : 'span-normal';
259
+ const serviceColorClass = `service-color-${getServiceColor(service)}`;
260
+ const indentStyle = `margin-left: ${depth * 20}px;`;
261
+
262
+ const spanId = `span-${span.trace_id || 0}-${span.span_id || Math.random()}`;
263
+
264
+ let html = `
265
+ <div class="waterfall-span ${spanClass}" style="${indentStyle}">
266
+ <div class="span-row">
267
+ <div class="span-info" onclick="toggleSpan('${spanId}')">
268
+ <div class="span-label">
269
+ <span class="span-toggle" id="toggle-${spanId}">▶</span>
270
+ <div class="span-names">
271
+ <span class="service-name">${service}</span>
272
+ <span class="operation-name">${resource}</span>
273
+ </div>
274
+ </div>
275
+ <div class="span-timing">
276
+ <span class="span-duration">${formatDuration(duration)}</span>
277
+ </div>
278
+ </div>
279
+ <div class="span-bar-container">
280
+ <div class="span-bar ${spanClass} ${serviceColorClass}"
281
+ style="left: ${startPercent}%; width: ${Math.max(widthPercent, 0.1)}%;"
282
+ title="${service}.${span.resource || span.name} - ${formatDuration(duration)}">
283
+ </div>
284
+ </div>
285
+ </div>
286
+ <div class="span-details" id="details-${spanId}" style="display: none;">
287
+ ${renderSpanMetadata(span)}
288
+ </div>
289
+ </div>`;
290
+
291
+ // Render children recursively
292
+ span.children.forEach(child => {
293
+ html += renderSpan(child, traceStart, totalDuration, depth + 1);
294
+ });
295
+
296
+ return html;
297
+ }
298
+
299
+ function formatDuration(nanoseconds) {
300
+ if (nanoseconds < 1000) return `${nanoseconds}ns`;
301
+ if (nanoseconds < 1000000) return `${(nanoseconds / 1000).toFixed(1)}μs`;
302
+ if (nanoseconds < 1000000000) return `${(nanoseconds / 1000000).toFixed(1)}ms`;
303
+ return `${(nanoseconds / 1000000000).toFixed(2)}s`;
304
+ }
305
+
306
+ function toggleTrace(traceIndex) {
307
+ const toggle = document.getElementById(`trace-toggle-${traceIndex}`);
308
+ const spans = document.getElementById(`trace-spans-${traceIndex}`);
309
+
310
+ if (spans.style.display === 'none') {
311
+ spans.style.display = 'block';
312
+ toggle.textContent = '▼';
313
+ } else {
314
+ spans.style.display = 'none';
315
+ toggle.textContent = '▶';
316
+ }
317
+ }
318
+
319
+ function toggleSpan(spanId) {
320
+ const toggle = document.getElementById(`toggle-${spanId}`);
321
+ const details = document.getElementById(`details-${spanId}`);
322
+
323
+ if (details.style.display === 'none') {
324
+ details.style.display = 'block';
325
+ toggle.textContent = '▼';
326
+ } else {
327
+ details.style.display = 'none';
328
+ toggle.textContent = '▶';
329
+ }
330
+ }
331
+
332
+ function renderSpanMetadata(span) {
333
+ const fields = [
334
+ { label: 'Span ID', value: span.span_id },
335
+ { label: 'Trace ID', value: span.trace_id },
336
+ { label: 'Parent ID', value: span.parent_id || 'None' },
337
+ { label: 'Service', value: span.service || 'Not set' },
338
+ { label: 'Name', value: span.name },
339
+ { label: 'Resource', value: span.resource },
340
+ { label: 'Type', value: span.type || 'Not set' },
341
+ { label: 'Start', value: span.start ? new Date(span.start / 1000000).toISOString() : 'Not set' },
342
+ { label: 'Duration', value: span.duration ? formatDuration(span.duration) : 'Not set' },
343
+ { label: 'Error', value: span.error ? 'Yes' : 'No' }
344
+ ];
345
+
346
+ let html = '<div class="span-metadata">';
347
+ html += '<h5>Span Details</h5>';
348
+ html += '<div class="metadata-grid">';
349
+
350
+ fields.forEach(field => {
351
+ if (field.value !== undefined && field.value !== null && field.value !== '') {
352
+ html += `
353
+ <div class="metadata-item">
354
+ <span class="metadata-label">${field.label}:</span>
355
+ <span class="metadata-value">${field.value}</span>
356
+ </div>`;
357
+ }
358
+ });
359
+
360
+ // Add meta tags if present
361
+ if (span.meta && Object.keys(span.meta).length > 0) {
362
+ html += '<div class="metadata-section"><h6>Meta</h6>';
363
+ for (const [key, value] of Object.entries(span.meta)) {
364
+ html += `
365
+ <div class="metadata-item">
366
+ <span class="metadata-label">${key}:</span>
367
+ <span class="metadata-value">${value}</span>
368
+ </div>`;
369
+ }
370
+ html += '</div>';
371
+ }
372
+
373
+ // Add metrics if present
374
+ if (span.metrics && Object.keys(span.metrics).length > 0) {
375
+ html += '<div class="metadata-section"><h6>Metrics</h6>';
376
+ for (const [key, value] of Object.entries(span.metrics)) {
377
+ html += `
378
+ <div class="metadata-item">
379
+ <span class="metadata-label">${key}:</span>
380
+ <span class="metadata-value">${value}</span>
381
+ </div>`;
382
+ }
383
+ html += '</div>';
384
+ }
385
+
386
+ html += '</div></div>';
387
+ return html;
388
+ }
389
+ {% endif %}
390
+
391
+ function copyToClipboard() {
392
+ const content = document.getElementById('code-content').textContent;
393
+ navigator.clipboard.writeText(content).then(function() {
394
+ alert('Content copied to clipboard!');
395
+ }).catch(function(err) {
396
+ console.error('Failed to copy content: ', err);
397
+ alert('Failed to copy content');
398
+ });
399
+ }
400
+
401
+ function toggleWrap() {
402
+ const codeBlock = document.getElementById('code-content');
403
+ if (codeBlock.style.whiteSpace === 'pre-wrap') {
404
+ codeBlock.style.whiteSpace = 'pre';
405
+ } else {
406
+ codeBlock.style.whiteSpace = 'pre-wrap';
407
+ }
408
+ }
409
+ </script>
410
+ {% endblock %}
@@ -0,0 +1,86 @@
1
+ {% extends "base.html" %}
2
+ {% from "macros.html" import page_layout, empty_state, action_button, error_message %}
3
+
4
+ {% block content %}
5
+ {% set extra_header %}
6
+ <div class="snapshot-info">
7
+ <span class="snapshot-dir">Directory: {{ snapshot_dir }}</span>
8
+ </div>
9
+ {% endset %}
10
+
11
+ {% call page_layout("Snapshots", extra_header=extra_header) %}
12
+ {% if error %}
13
+ {{ error_message(error) }}
14
+ {% endif %}
15
+
16
+ {% if snapshots %}
17
+ <div class="snapshots-controls">
18
+ <div class="snapshots-stats">
19
+ <p><strong><span id="total-count">{{ snapshots|length }}</span></strong> snapshot files found (<span id="filtered-count">{{ snapshots|length }}</span> shown)</p>
20
+ </div>
21
+ <div class="filter-controls">
22
+ <input type="text" id="filename-filter" placeholder="Filter by filename..." class="filter-input">
23
+ </div>
24
+ </div>
25
+ <div class="snapshots-table">
26
+ <table>
27
+ <thead>
28
+ <tr>
29
+ <th>Filename</th>
30
+ <th>Size</th>
31
+ <th>Modified</th>
32
+ </tr>
33
+ </thead>
34
+ <tbody>
35
+ {% for snapshot in snapshots %}
36
+ <tr class="snapshot-row" onclick="window.location.href='/snapshots/{{ snapshot.filename }}'" style="cursor: pointer;">
37
+ <td class="filename-cell">{{ snapshot.filename }}</td>
38
+ <td class="size-cell">{{ "%.1f" | format(snapshot.size / 1024) }} KB</td>
39
+ <td class="modified-cell">{{ snapshot.modified | timestamp_format }}</td>
40
+ </tr>
41
+ {% endfor %}
42
+ </tbody>
43
+ </table>
44
+ </div>
45
+ {% else %}
46
+ {% if error %}
47
+ {{ empty_state("Unable to load snapshots") }}
48
+ {% else %}
49
+ {{ empty_state("No snapshot files found in " + snapshot_dir) }}
50
+ {% endif %}
51
+ {% endif %}
52
+ {% endcall %}
53
+
54
+ <script>
55
+ document.addEventListener('DOMContentLoaded', function() {
56
+ const filterInput = document.getElementById('filename-filter');
57
+ const filteredCountSpan = document.getElementById('filtered-count');
58
+ const table = document.querySelector('.snapshots-table table tbody');
59
+
60
+ if (filterInput && table) {
61
+ filterInput.addEventListener('input', function() {
62
+ const filterValue = this.value.toLowerCase().trim();
63
+ const rows = table.getElementsByTagName('tr');
64
+ let visibleCount = 0;
65
+
66
+ for (let i = 0; i < rows.length; i++) {
67
+ const filenameCell = rows[i].querySelector('.filename-cell');
68
+ if (filenameCell) {
69
+ const filename = filenameCell.textContent.toLowerCase();
70
+ if (filename.includes(filterValue)) {
71
+ rows[i].style.display = '';
72
+ visibleCount++;
73
+ } else {
74
+ rows[i].style.display = 'none';
75
+ }
76
+ }
77
+ }
78
+
79
+ if (filteredCountSpan) {
80
+ filteredCountSpan.textContent = visibleCount;
81
+ }
82
+ });
83
+ }
84
+ });
85
+ </script>
86
+ {% endblock %}
@@ -0,0 +1,37 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="trace-detail-page">
5
+ <div class="page-header">
6
+ <h2>Trace {{ trace_id }}</h2>
7
+ </div>
8
+
9
+ {% if error %}
10
+ <div class="error-message">
11
+ <p>{{ error }}</p>
12
+ </div>
13
+ {% elif trace %}
14
+ <div class="trace-details">
15
+ <div class="spans-list">
16
+ {% for span in trace %}
17
+ <div class="span-item">
18
+ <div class="span-header">
19
+ <h4>{{ span.get('name', 'Unknown') }}</h4>
20
+ <span class="span-id">ID: {{ span.get('span_id') }}</span>
21
+ </div>
22
+ <div class="span-details">
23
+ <p><strong>Service:</strong> {{ span.get('service', 'N/A') }}</p>
24
+ <p><strong>Resource:</strong> {{ span.get('resource', 'N/A') }}</p>
25
+ <p><strong>Duration:</strong> {{ span.get('duration', 'N/A') }}</p>
26
+ </div>
27
+ </div>
28
+ {% endfor %}
29
+ </div>
30
+ </div>
31
+ {% else %}
32
+ <div class="empty-state">
33
+ <p>No trace data available</p>
34
+ </div>
35
+ {% endif %}
36
+ </div>
37
+ {% endblock %}