simple_apm 1.0.13 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ecd24daac047cea581ed0cd225bb608550647c8d
4
- data.tar.gz: f764040b48e07fd37534db07ca32706aa4478045
2
+ SHA256:
3
+ metadata.gz: 20c4854a122796cdb1c34546e8808f04ad6137c8d3d5e1936b547d329822fa9f
4
+ data.tar.gz: 74a75a66e084d2d5464a42a1e594fe74575c9d5bba915570c1196b66e99eb5c7
5
5
  SHA512:
6
- metadata.gz: 96975b1996ce11aa43f172b5dcc623fafb97e4cba30e3f0c2699d19cf5654ca7bd52470efe21472f59b8ad424882a2ebbc8f6b484222510949627422e7d87676
7
- data.tar.gz: bfdaf971ad55fe4144b9c5c5f0f37d087f523804b236838fa64d02e5a895a75ef1ddf0f488435f03d54bbc478e3e51390d05cfd58a5816aec99de46d930057a8
6
+ metadata.gz: 8253d3b452494e21d4fed612fd6f1076435296ba689750ac6ea72055931bd56909968e99707fb3d448aacd9f1c6829cf28ff78c9e2d56a7b90f9125e1a296aa7
7
+ data.tar.gz: 2617e187209c01c3d3eb7413aef561dd80c295b95dd8ca10a7b1282cc918847afef4bb3b1decd5dbc85ebf0e6beddbce769b8ba6a3aecfb92260173e6d0a3a40
data/Rakefile CHANGED
@@ -17,11 +17,6 @@ end
17
17
  APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
18
  load 'rails/tasks/engine.rake'
19
19
 
20
-
21
- load 'rails/tasks/statistics.rake'
22
-
23
-
24
-
25
20
  require 'bundler/gem_tasks'
26
21
 
27
22
  require 'rake/testtask'
@@ -12,4 +12,575 @@
12
12
  *
13
13
  *= require_tree .
14
14
  *= require_self
15
- */
15
+ */
16
+
17
+ :root {
18
+ --apm-bg: #f4f7fb;
19
+ --apm-surface: #ffffff;
20
+ --apm-surface-soft: #f8fafc;
21
+ --apm-ink: #172033;
22
+ --apm-muted: #667085;
23
+ --apm-border: #dce3ee;
24
+ --apm-border-strong: #c8d2df;
25
+ --apm-brand: #0f766e;
26
+ --apm-brand-dark: #0b4f4a;
27
+ --apm-accent: #2563eb;
28
+ --apm-danger: #dc2626;
29
+ --apm-warning: #d97706;
30
+ --apm-success: #15803d;
31
+ --apm-shadow: 0 16px 40px rgba(15, 23, 42, 0.08);
32
+ }
33
+
34
+ body.simple-apm {
35
+ margin: 0;
36
+ background: var(--apm-bg);
37
+ color: var(--apm-ink);
38
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
39
+ font-size: 14px;
40
+ letter-spacing: 0;
41
+ }
42
+
43
+ .simple-apm a {
44
+ color: var(--apm-accent);
45
+ }
46
+
47
+ .simple-apm a:hover {
48
+ color: #1d4ed8;
49
+ }
50
+
51
+ .apm-shell {
52
+ min-height: calc(100vh - 96px);
53
+ }
54
+
55
+ .apm-topbar {
56
+ position: sticky;
57
+ top: 0;
58
+ z-index: 1000;
59
+ background: #101827;
60
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
61
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 0.18);
62
+ }
63
+
64
+ .apm-topbar-inner {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 24px;
68
+ min-height: 64px;
69
+ width: min(1440px, calc(100% - 48px));
70
+ margin: 0 auto;
71
+ }
72
+
73
+ .apm-brand {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 10px;
77
+ color: #ffffff;
78
+ flex: 0 0 auto;
79
+ }
80
+
81
+ .apm-brand-mark {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ width: 34px;
86
+ height: 34px;
87
+ border-radius: 8px;
88
+ background: #14b8a6;
89
+ color: #052e2b;
90
+ font-weight: 800;
91
+ font-size: 13px;
92
+ }
93
+
94
+ .apm-brand-name {
95
+ font-weight: 700;
96
+ font-size: 16px;
97
+ }
98
+
99
+ .apm-nav {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 6px;
103
+ flex: 1 1 auto;
104
+ }
105
+
106
+ .apm-nav .navbar-brand {
107
+ float: none;
108
+ height: auto;
109
+ padding: 9px 12px;
110
+ color: #cbd5e1;
111
+ font-size: 13px;
112
+ line-height: 18px;
113
+ border-radius: 6px;
114
+ }
115
+
116
+ .apm-nav .navbar-brand:hover,
117
+ .apm-nav .navbar-brand:focus {
118
+ color: #ffffff;
119
+ background: rgba(255, 255, 255, 0.08);
120
+ text-decoration: none;
121
+ }
122
+
123
+ .apm-nav .navbar-brand.active {
124
+ color: #ffffff !important;
125
+ background: rgba(20, 184, 166, 0.22);
126
+ }
127
+
128
+ .apm-date-select {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 10px;
132
+ color: #cbd5e1;
133
+ font-size: 13px;
134
+ flex: 0 0 auto;
135
+ }
136
+
137
+ .apm-date-select select.form-control {
138
+ width: auto;
139
+ min-width: 138px;
140
+ height: 34px;
141
+ color: #172033;
142
+ border: 0;
143
+ border-radius: 6px;
144
+ box-shadow: none;
145
+ }
146
+
147
+ .apm-content {
148
+ width: min(1440px, calc(100% - 48px));
149
+ margin: 0 auto;
150
+ padding: 28px 0 40px;
151
+ }
152
+
153
+ .apm-page-header {
154
+ display: flex;
155
+ align-items: flex-end;
156
+ justify-content: space-between;
157
+ gap: 24px;
158
+ margin-bottom: 18px;
159
+ }
160
+
161
+ .apm-page-title {
162
+ margin: 0;
163
+ color: var(--apm-ink);
164
+ font-size: 26px;
165
+ line-height: 1.2;
166
+ font-weight: 700;
167
+ }
168
+
169
+ .apm-page-subtitle {
170
+ margin: 7px 0 0;
171
+ color: var(--apm-muted);
172
+ font-size: 13px;
173
+ }
174
+
175
+ .apm-toolbar {
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: space-between;
179
+ gap: 14px;
180
+ margin-bottom: 18px;
181
+ }
182
+
183
+ .apm-toolbar-actions {
184
+ display: flex;
185
+ align-items: center;
186
+ flex-wrap: wrap;
187
+ gap: 8px;
188
+ }
189
+
190
+ .apm-grid {
191
+ display: grid;
192
+ grid-template-columns: repeat(12, minmax(0, 1fr));
193
+ gap: 18px;
194
+ }
195
+
196
+ .apm-col-6 {
197
+ grid-column: span 6;
198
+ }
199
+
200
+ .apm-col-12 {
201
+ grid-column: span 12;
202
+ }
203
+
204
+ .apm-panel {
205
+ background: var(--apm-surface);
206
+ border: 1px solid var(--apm-border);
207
+ border-radius: 8px;
208
+ box-shadow: var(--apm-shadow);
209
+ }
210
+
211
+ .apm-panel + .apm-panel {
212
+ margin-top: 18px;
213
+ }
214
+
215
+ .apm-panel-header {
216
+ display: flex;
217
+ align-items: center;
218
+ justify-content: space-between;
219
+ gap: 16px;
220
+ padding: 16px 18px 0;
221
+ }
222
+
223
+ .apm-panel-title {
224
+ margin: 0;
225
+ color: var(--apm-ink);
226
+ font-size: 16px;
227
+ line-height: 1.3;
228
+ font-weight: 700;
229
+ }
230
+
231
+ .apm-panel-subtitle {
232
+ margin: 4px 0 0;
233
+ color: var(--apm-muted);
234
+ font-size: 12px;
235
+ }
236
+
237
+ .apm-panel-body {
238
+ padding: 18px;
239
+ }
240
+
241
+ .apm-chart {
242
+ height: 360px;
243
+ width: 100%;
244
+ }
245
+
246
+ .apm-metrics {
247
+ display: grid;
248
+ grid-template-columns: repeat(5, minmax(0, 1fr));
249
+ gap: 12px;
250
+ margin-bottom: 18px;
251
+ }
252
+
253
+ .apm-stat {
254
+ min-width: 0;
255
+ padding: 14px 16px;
256
+ background: var(--apm-surface);
257
+ border: 1px solid var(--apm-border);
258
+ border-radius: 8px;
259
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
260
+ }
261
+
262
+ .apm-stat-label {
263
+ margin: 0 0 8px;
264
+ color: var(--apm-muted);
265
+ font-size: 12px;
266
+ line-height: 1.2;
267
+ }
268
+
269
+ .apm-stat-value {
270
+ color: var(--apm-ink);
271
+ font-size: 20px;
272
+ line-height: 1.2;
273
+ font-weight: 700;
274
+ word-break: break-word;
275
+ }
276
+
277
+ .apm-stat-meta {
278
+ margin-top: 6px;
279
+ color: var(--apm-muted);
280
+ font-size: 12px;
281
+ }
282
+
283
+ .apm-detail-list {
284
+ display: grid;
285
+ grid-template-columns: 140px minmax(0, 1fr);
286
+ gap: 10px 16px;
287
+ margin: 0;
288
+ }
289
+
290
+ .apm-detail-list dt {
291
+ color: var(--apm-muted);
292
+ font-weight: 600;
293
+ }
294
+
295
+ .apm-detail-list dd {
296
+ margin: 0;
297
+ min-width: 0;
298
+ word-break: break-word;
299
+ }
300
+
301
+ .apm-table-wrap {
302
+ overflow-x: auto;
303
+ }
304
+
305
+ .simple-apm table.table,
306
+ .simple-apm table.dataTable {
307
+ width: 100% !important;
308
+ margin: 0 !important;
309
+ background: var(--apm-surface);
310
+ border-collapse: separate !important;
311
+ border-spacing: 0;
312
+ }
313
+
314
+ .simple-apm table.table > thead > tr > th,
315
+ .simple-apm table.table > thead > tr > td,
316
+ .simple-apm table.dataTable thead th {
317
+ padding: 12px 14px;
318
+ color: #475467;
319
+ background: var(--apm-surface-soft);
320
+ border-bottom: 1px solid var(--apm-border);
321
+ font-size: 12px;
322
+ font-weight: 700;
323
+ white-space: nowrap;
324
+ }
325
+
326
+ .simple-apm table.table > tbody > tr > td,
327
+ .simple-apm table.dataTable tbody td {
328
+ padding: 12px 14px;
329
+ border-top: 1px solid #eef2f7;
330
+ color: #243044;
331
+ vertical-align: middle;
332
+ }
333
+
334
+ .simple-apm table.table-bordered {
335
+ border: 1px solid var(--apm-border);
336
+ border-radius: 8px;
337
+ overflow: hidden;
338
+ }
339
+
340
+ .simple-apm table.table-bordered > thead > tr > th,
341
+ .simple-apm table.table-bordered > tbody > tr > td,
342
+ .simple-apm table.table-bordered > tbody > tr > th {
343
+ border-color: var(--apm-border);
344
+ }
345
+
346
+ .simple-apm .dataTables_wrapper {
347
+ padding: 0;
348
+ }
349
+
350
+ .simple-apm .dataTables_wrapper .dataTables_length,
351
+ .simple-apm .dataTables_wrapper .dataTables_filter {
352
+ margin: 0 0 12px;
353
+ color: var(--apm-muted);
354
+ }
355
+
356
+ .simple-apm .dataTables_wrapper .dataTables_filter input,
357
+ .simple-apm .dataTables_wrapper .dataTables_length select {
358
+ height: 32px;
359
+ border: 1px solid var(--apm-border);
360
+ border-radius: 6px;
361
+ padding: 4px 8px;
362
+ background: #ffffff;
363
+ color: var(--apm-ink);
364
+ box-shadow: none;
365
+ }
366
+
367
+ .simple-apm .dataTables_wrapper .dataTables_info,
368
+ .simple-apm .dataTables_wrapper .dataTables_paginate {
369
+ margin-top: 12px;
370
+ color: var(--apm-muted);
371
+ font-size: 12px;
372
+ }
373
+
374
+ .simple-apm .dataTables_wrapper .dataTables_paginate .paginate_button {
375
+ border: 1px solid transparent !important;
376
+ border-radius: 6px;
377
+ }
378
+
379
+ .simple-apm .dataTables_wrapper .dataTables_paginate .paginate_button.current {
380
+ background: var(--apm-brand) !important;
381
+ border-color: var(--apm-brand) !important;
382
+ color: #ffffff !important;
383
+ }
384
+
385
+ .simple-apm .btn {
386
+ border-radius: 6px;
387
+ font-weight: 600;
388
+ }
389
+
390
+ .simple-apm a.btn,
391
+ .simple-apm a.btn:hover,
392
+ .simple-apm a.btn:focus {
393
+ color: #ffffff;
394
+ text-decoration: none;
395
+ }
396
+
397
+ .simple-apm .btn-primary,
398
+ .simple-apm .btn-primary:hover,
399
+ .simple-apm .btn-primary:focus {
400
+ background: var(--apm-accent);
401
+ border-color: var(--apm-accent);
402
+ }
403
+
404
+ .simple-apm .btn-warning,
405
+ .simple-apm .btn-warning:hover,
406
+ .simple-apm .btn-warning:focus {
407
+ background: var(--apm-warning);
408
+ border-color: var(--apm-warning);
409
+ }
410
+
411
+ .simple-apm .btn-danger,
412
+ .simple-apm .btn-danger:hover,
413
+ .simple-apm .btn-danger:focus {
414
+ background: var(--apm-danger);
415
+ border-color: var(--apm-danger);
416
+ }
417
+
418
+ .apm-danger-link {
419
+ color: var(--apm-danger);
420
+ font-weight: 600;
421
+ }
422
+
423
+ .apm-status {
424
+ display: inline-flex;
425
+ align-items: center;
426
+ min-height: 24px;
427
+ padding: 3px 9px;
428
+ border-radius: 999px;
429
+ font-size: 12px;
430
+ font-weight: 700;
431
+ }
432
+
433
+ .apm-status-ok {
434
+ color: #065f46;
435
+ background: #d1fae5;
436
+ }
437
+
438
+ .apm-status-danger {
439
+ color: #991b1b;
440
+ background: #fee2e2;
441
+ }
442
+
443
+ .apm-status-warning {
444
+ color: #92400e;
445
+ background: #fef3c7;
446
+ }
447
+
448
+ .sql {
449
+ max-width: 520px;
450
+ white-space: nowrap;
451
+ text-overflow: ellipsis;
452
+ overflow: hidden;
453
+ color: var(--apm-accent);
454
+ cursor: pointer;
455
+ font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
456
+ font-size: 12px;
457
+ }
458
+
459
+ pre {
460
+ white-space: pre-wrap;
461
+ }
462
+
463
+ .apm-modal-panel {
464
+ background: #ffffff;
465
+ border-radius: 8px;
466
+ box-shadow: 0 24px 80px rgba(15, 23, 42, 0.24);
467
+ overflow: hidden;
468
+ }
469
+
470
+ .apm-modal-header {
471
+ padding: 14px 18px;
472
+ border-bottom: 1px solid var(--apm-border);
473
+ background: var(--apm-surface-soft);
474
+ }
475
+
476
+ .apm-modal-title {
477
+ color: var(--apm-ink);
478
+ font-size: 15px;
479
+ font-weight: 700;
480
+ }
481
+
482
+ .apm-modal-body {
483
+ padding: 18px;
484
+ }
485
+
486
+ .apm-modal-body pre.modal-content {
487
+ margin: 0;
488
+ padding: 14px;
489
+ border: 1px solid var(--apm-border);
490
+ border-radius: 8px;
491
+ background: #0f172a;
492
+ color: #d1e7ff;
493
+ box-shadow: none;
494
+ }
495
+
496
+ .apm-footer {
497
+ width: min(1440px, calc(100% - 48px));
498
+ margin: 0 auto;
499
+ padding: 18px 0 26px;
500
+ color: var(--apm-muted);
501
+ border-top: 1px solid var(--apm-border);
502
+ }
503
+
504
+ .apm-footer-status {
505
+ margin-bottom: 10px;
506
+ }
507
+
508
+ .apm-footer-meta {
509
+ display: flex;
510
+ flex-wrap: wrap;
511
+ gap: 10px 18px;
512
+ font-size: 12px;
513
+ }
514
+
515
+ .apm-footer-meta label {
516
+ margin: 0;
517
+ color: #475467;
518
+ }
519
+
520
+ @media (max-width: 980px) {
521
+ .apm-topbar-inner,
522
+ .apm-content,
523
+ .apm-footer {
524
+ width: min(100% - 28px, 1440px);
525
+ }
526
+
527
+ .apm-topbar-inner {
528
+ flex-wrap: wrap;
529
+ padding: 12px 0;
530
+ }
531
+
532
+ .apm-nav {
533
+ order: 3;
534
+ width: 100%;
535
+ overflow-x: auto;
536
+ }
537
+
538
+ .apm-date-select {
539
+ margin-left: auto;
540
+ }
541
+
542
+ .apm-page-header,
543
+ .apm-toolbar {
544
+ align-items: flex-start;
545
+ flex-direction: column;
546
+ }
547
+
548
+ .apm-col-6 {
549
+ grid-column: span 12;
550
+ }
551
+
552
+ .apm-metrics {
553
+ grid-template-columns: repeat(2, minmax(0, 1fr));
554
+ }
555
+ }
556
+
557
+ @media (max-width: 640px) {
558
+ .apm-brand-name {
559
+ display: none;
560
+ }
561
+
562
+ .apm-date-select {
563
+ width: 100%;
564
+ justify-content: space-between;
565
+ }
566
+
567
+ .apm-date-select select.form-control {
568
+ flex: 1 1 auto;
569
+ }
570
+
571
+ .apm-content {
572
+ padding-top: 20px;
573
+ }
574
+
575
+ .apm-page-title {
576
+ font-size: 22px;
577
+ }
578
+
579
+ .apm-metrics {
580
+ grid-template-columns: 1fr;
581
+ }
582
+
583
+ .apm-detail-list {
584
+ grid-template-columns: 1fr;
585
+ }
586
+ }
@@ -17,19 +17,20 @@ module SimpleApm
17
17
  respond_to do |format|
18
18
  format.json do
19
19
  @slow_requests = SimpleApm::SlowRequest.list(params[:count] || 200).map do |r|
20
- request = r.request
20
+ request = r.request rescue next
21
21
  [
22
22
  link_to(time_label(request.started), show_path(id: request.request_id)),
23
23
  link_to(request.action_name, action_info_path(action_name: request.action_name)),
24
24
  sec_str(request.during),
25
25
  sec_str(request.db_runtime),
26
+ request.queries_count,
26
27
  sec_str(request.view_runtime),
27
28
  sec_str(request.net_http_during),
28
29
  request.memory_during.to_f.round(1),
29
30
  request.host,
30
31
  request.remote_addr
31
32
  ]
32
- end
33
+ end.compact
33
34
  render json: {data: @slow_requests}
34
35
  end
35
36
  format.html
@@ -1,7 +1,8 @@
1
1
  # 请求 Controller#Action
2
2
  module SimpleApm
3
3
  class Action
4
- attr_accessor :name, :click_count, :time, :http_time, :slow_time, :slow_id, :fast_time, :fast_id
4
+ attr_accessor :name, :click_count, :time, :db_time, :queries_count, :cached_queries_count,
5
+ :http_time, :slow_time, :slow_id, :fast_time, :fast_id
5
6
 
6
7
  def initialize(h)
7
8
  h.each do |k, v|
@@ -25,6 +26,18 @@ module SimpleApm
25
26
  http_time.to_f/click_count.to_i
26
27
  end
27
28
 
29
+ def avg_db_time
30
+ db_time.to_f/click_count.to_i
31
+ end
32
+
33
+ def avg_queries_count
34
+ queries_count.to_f/click_count.to_i
35
+ end
36
+
37
+ def avg_cached_queries_count
38
+ cached_queries_count.to_f/click_count.to_i
39
+ end
40
+
28
41
  def avg_time
29
42
  time.to_f/click_count.to_i
30
43
  end
@@ -43,6 +56,12 @@ module SimpleApm
43
56
  SimpleApm::Redis.hincrby _key, 'click_count', 1
44
57
  # 总时间
45
58
  SimpleApm::Redis.hincrbyfloat _key, 'time', h['during']
59
+ # 数据库访问时间
60
+ SimpleApm::Redis.hincrbyfloat _key, 'db_time', h['db_runtime']
61
+ # SQL执行次数
62
+ SimpleApm::Redis.hincrby _key, 'queries_count', h['queries_count'].to_i
63
+ # SQL缓存命中次数
64
+ SimpleApm::Redis.hincrby _key, 'cached_queries_count', h['cached_queries_count'].to_i
46
65
  # 外部http访问时间
47
66
  SimpleApm::Redis.hincrbyfloat _key, 'http_time', h['net_http_during']
48
67
  _slow = SimpleApm::Redis.hget _key, 'slow_time'
@@ -3,6 +3,7 @@ module SimpleApm
3
3
  class Request
4
4
  attr_accessor :request_id, :action_name,
5
5
  :during, :started, :db_runtime, :view_runtime,
6
+ :queries_count, :cached_queries_count,
6
7
  :controller, :action, :format, :method,
7
8
  :host, :remote_addr, :url, :completed_memory, :memory_during,
8
9
  :exception, :status, :net_http_during
@@ -24,7 +25,7 @@ module SimpleApm
24
25
  class << self
25
26
 
26
27
  def find(id)
27
- SimpleApm::Request.new JSON.parse(SimpleApm::Redis.hget(key, id))
28
+ SimpleApm::Request.new JSON.parse(SimpleApm::Redis.hget(key, id)) rescue nil
28
29
  end
29
30
 
30
31
  def create(h)
@@ -0,0 +1,32 @@
1
+ module SimpleApm
2
+ class Summary
3
+ @@results = {}
4
+
5
+ def initialize(date)
6
+ @date = date
7
+ end
8
+
9
+ def result
10
+ return @@results[@date] if @@results[@date] && Time.parse(@date) < Date.today
11
+ @@results[@date] = SimpleApm::RedisKey.set_query_date(@date) do
12
+ res = SimpleApm::Hit.day_info(@date)
13
+ actions = SimpleApm::Action.all_names.map{|n|SimpleApm::Action.find(n)}
14
+ most_hits_5 = actions.sort_by{|x|x.click_count.to_i}.reverse.take(5)
15
+ most_hits_5.map! do |action|
16
+ {
17
+ name: action.name,
18
+ avg_time: action.avg_time,
19
+ hits: action.click_count.to_i,
20
+ slow_avg_time: action.slow_requests.sum(&:during)/action.slow_requests.length
21
+ }
22
+ end
23
+ {
24
+ hits: res[:hits],
25
+ avg_time: res[:avg_time],
26
+ actions: most_hits_5
27
+ }
28
+ end
29
+
30
+ end
31
+ end
32
+ end