simple_apm 1.0.14 → 1.1.1

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.
@@ -12,4 +12,614 @@
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-language-switch {
148
+ display: inline-flex;
149
+ align-items: center;
150
+ gap: 4px;
151
+ padding: 3px;
152
+ background: rgba(255, 255, 255, 0.08);
153
+ border-radius: 6px;
154
+ flex: 0 0 auto;
155
+ }
156
+
157
+ .apm-language-switch a {
158
+ padding: 5px 8px;
159
+ color: #cbd5e1;
160
+ border-radius: 4px;
161
+ font-size: 12px;
162
+ line-height: 16px;
163
+ }
164
+
165
+ .apm-language-switch a:hover,
166
+ .apm-language-switch a:focus {
167
+ color: #ffffff;
168
+ text-decoration: none;
169
+ }
170
+
171
+ .apm-language-switch a.active {
172
+ color: #052e2b;
173
+ background: #5eead4;
174
+ font-weight: 700;
175
+ }
176
+
177
+ .apm-content {
178
+ width: min(1440px, calc(100% - 48px));
179
+ margin: 0 auto;
180
+ padding: 28px 0 40px;
181
+ }
182
+
183
+ .apm-page-header {
184
+ display: flex;
185
+ align-items: flex-end;
186
+ justify-content: space-between;
187
+ gap: 24px;
188
+ margin-bottom: 18px;
189
+ }
190
+
191
+ .apm-page-title {
192
+ margin: 0;
193
+ color: var(--apm-ink);
194
+ font-size: 26px;
195
+ line-height: 1.2;
196
+ font-weight: 700;
197
+ }
198
+
199
+ .apm-page-subtitle {
200
+ margin: 7px 0 0;
201
+ color: var(--apm-muted);
202
+ font-size: 13px;
203
+ }
204
+
205
+ .apm-toolbar {
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: space-between;
209
+ gap: 14px;
210
+ margin-bottom: 18px;
211
+ }
212
+
213
+ .apm-toolbar-actions {
214
+ display: flex;
215
+ align-items: center;
216
+ flex-wrap: wrap;
217
+ gap: 8px;
218
+ }
219
+
220
+ .apm-grid {
221
+ display: grid;
222
+ grid-template-columns: repeat(12, minmax(0, 1fr));
223
+ gap: 18px;
224
+ }
225
+
226
+ .apm-col-6 {
227
+ grid-column: span 6;
228
+ }
229
+
230
+ .apm-col-12 {
231
+ grid-column: span 12;
232
+ }
233
+
234
+ .apm-panel {
235
+ background: var(--apm-surface);
236
+ border: 1px solid var(--apm-border);
237
+ border-radius: 8px;
238
+ box-shadow: var(--apm-shadow);
239
+ }
240
+
241
+ .apm-panel + .apm-panel {
242
+ margin-top: 18px;
243
+ }
244
+
245
+ .apm-panel-header {
246
+ display: flex;
247
+ align-items: center;
248
+ justify-content: space-between;
249
+ gap: 16px;
250
+ padding: 16px 18px 0;
251
+ }
252
+
253
+ .apm-panel-title {
254
+ margin: 0;
255
+ color: var(--apm-ink);
256
+ font-size: 16px;
257
+ line-height: 1.3;
258
+ font-weight: 700;
259
+ }
260
+
261
+ .apm-panel-subtitle {
262
+ margin: 4px 0 0;
263
+ color: var(--apm-muted);
264
+ font-size: 12px;
265
+ }
266
+
267
+ .apm-panel-body {
268
+ padding: 18px;
269
+ }
270
+
271
+ .apm-chart {
272
+ height: 360px;
273
+ width: 100%;
274
+ }
275
+
276
+ .apm-metrics {
277
+ display: grid;
278
+ grid-template-columns: repeat(5, minmax(0, 1fr));
279
+ gap: 12px;
280
+ margin-bottom: 18px;
281
+ }
282
+
283
+ .apm-stat {
284
+ min-width: 0;
285
+ padding: 14px 16px;
286
+ background: var(--apm-surface);
287
+ border: 1px solid var(--apm-border);
288
+ border-radius: 8px;
289
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
290
+ }
291
+
292
+ .apm-stat-label {
293
+ margin: 0 0 8px;
294
+ color: var(--apm-muted);
295
+ font-size: 12px;
296
+ line-height: 1.2;
297
+ }
298
+
299
+ .apm-stat-value {
300
+ color: var(--apm-ink);
301
+ font-size: 20px;
302
+ line-height: 1.2;
303
+ font-weight: 700;
304
+ word-break: break-word;
305
+ }
306
+
307
+ .apm-stat-meta {
308
+ margin-top: 6px;
309
+ color: var(--apm-muted);
310
+ font-size: 12px;
311
+ }
312
+
313
+ .apm-detail-list {
314
+ display: grid;
315
+ grid-template-columns: 140px minmax(0, 1fr);
316
+ gap: 10px 16px;
317
+ margin: 0;
318
+ }
319
+
320
+ .apm-detail-list dt {
321
+ color: var(--apm-muted);
322
+ font-weight: 600;
323
+ }
324
+
325
+ .apm-detail-list dd {
326
+ margin: 0;
327
+ min-width: 0;
328
+ word-break: break-word;
329
+ }
330
+
331
+ .apm-table-wrap {
332
+ overflow-x: auto;
333
+ }
334
+
335
+ .simple-apm table.table,
336
+ .simple-apm table.dataTable {
337
+ width: 100% !important;
338
+ margin: 0 !important;
339
+ background: var(--apm-surface);
340
+ border-collapse: separate !important;
341
+ border-spacing: 0;
342
+ }
343
+
344
+ .simple-apm table.table > thead > tr > th,
345
+ .simple-apm table.table > thead > tr > td,
346
+ .simple-apm table.dataTable thead th {
347
+ padding: 12px 14px;
348
+ color: #475467;
349
+ background: var(--apm-surface-soft);
350
+ border-bottom: 1px solid var(--apm-border);
351
+ font-size: 12px;
352
+ font-weight: 700;
353
+ white-space: nowrap;
354
+ }
355
+
356
+ .simple-apm table.table > tbody > tr > td,
357
+ .simple-apm table.dataTable tbody td {
358
+ padding: 12px 14px;
359
+ border-top: 1px solid #eef2f7;
360
+ color: #243044;
361
+ vertical-align: middle;
362
+ }
363
+
364
+ .simple-apm table.table-bordered {
365
+ border: 1px solid var(--apm-border);
366
+ border-radius: 8px;
367
+ overflow: hidden;
368
+ }
369
+
370
+ .simple-apm table.table-bordered > thead > tr > th,
371
+ .simple-apm table.table-bordered > tbody > tr > td,
372
+ .simple-apm table.table-bordered > tbody > tr > th {
373
+ border-color: var(--apm-border);
374
+ }
375
+
376
+ .simple-apm .dataTables_wrapper {
377
+ padding: 0;
378
+ }
379
+
380
+ .simple-apm .dataTables_wrapper .dataTables_length,
381
+ .simple-apm .dataTables_wrapper .dataTables_filter {
382
+ margin: 0 0 12px;
383
+ color: var(--apm-muted);
384
+ }
385
+
386
+ .simple-apm .dataTables_wrapper .dataTables_filter input,
387
+ .simple-apm .dataTables_wrapper .dataTables_length select {
388
+ height: 32px;
389
+ border: 1px solid var(--apm-border);
390
+ border-radius: 6px;
391
+ padding: 4px 8px;
392
+ background: #ffffff;
393
+ color: var(--apm-ink);
394
+ box-shadow: none;
395
+ }
396
+
397
+ .simple-apm .dataTables_wrapper .dataTables_info,
398
+ .simple-apm .dataTables_wrapper .dataTables_paginate {
399
+ margin-top: 12px;
400
+ color: var(--apm-muted);
401
+ font-size: 12px;
402
+ }
403
+
404
+ .simple-apm .dataTables_wrapper .dataTables_paginate .paginate_button {
405
+ border: 1px solid transparent !important;
406
+ border-radius: 6px;
407
+ }
408
+
409
+ .simple-apm .dataTables_wrapper .dataTables_paginate .paginate_button.current {
410
+ background: var(--apm-brand) !important;
411
+ border-color: var(--apm-brand) !important;
412
+ color: #ffffff !important;
413
+ }
414
+
415
+ .simple-apm .btn {
416
+ border-radius: 6px;
417
+ font-weight: 600;
418
+ }
419
+
420
+ .simple-apm a.btn,
421
+ .simple-apm a.btn:hover,
422
+ .simple-apm a.btn:focus {
423
+ color: #ffffff;
424
+ text-decoration: none;
425
+ }
426
+
427
+ .simple-apm .btn-primary,
428
+ .simple-apm .btn-primary:hover,
429
+ .simple-apm .btn-primary:focus {
430
+ background: var(--apm-accent);
431
+ border-color: var(--apm-accent);
432
+ }
433
+
434
+ .simple-apm .btn-warning,
435
+ .simple-apm .btn-warning:hover,
436
+ .simple-apm .btn-warning:focus {
437
+ background: var(--apm-warning);
438
+ border-color: var(--apm-warning);
439
+ }
440
+
441
+ .simple-apm .btn-danger,
442
+ .simple-apm .btn-danger:hover,
443
+ .simple-apm .btn-danger:focus {
444
+ background: var(--apm-danger);
445
+ border-color: var(--apm-danger);
446
+ }
447
+
448
+ .apm-danger-link {
449
+ color: var(--apm-danger);
450
+ font-weight: 600;
451
+ }
452
+
453
+ .apm-status {
454
+ display: inline-flex;
455
+ align-items: center;
456
+ min-height: 24px;
457
+ padding: 3px 9px;
458
+ border-radius: 999px;
459
+ font-size: 12px;
460
+ font-weight: 700;
461
+ }
462
+
463
+ .apm-status-ok {
464
+ color: #065f46;
465
+ background: #d1fae5;
466
+ }
467
+
468
+ .apm-status-danger {
469
+ color: #991b1b;
470
+ background: #fee2e2;
471
+ }
472
+
473
+ .apm-status-warning {
474
+ color: #92400e;
475
+ background: #fef3c7;
476
+ }
477
+
478
+ .sql {
479
+ max-width: 520px;
480
+ white-space: nowrap;
481
+ text-overflow: ellipsis;
482
+ overflow: hidden;
483
+ color: var(--apm-accent);
484
+ cursor: pointer;
485
+ font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
486
+ font-size: 12px;
487
+ }
488
+
489
+ pre {
490
+ white-space: pre-wrap;
491
+ }
492
+
493
+ .apm-modal-panel {
494
+ background: #ffffff;
495
+ border-radius: 8px;
496
+ box-shadow: 0 24px 80px rgba(15, 23, 42, 0.24);
497
+ overflow: hidden;
498
+ }
499
+
500
+ .apm-modal-header {
501
+ padding: 14px 18px;
502
+ border-bottom: 1px solid var(--apm-border);
503
+ background: var(--apm-surface-soft);
504
+ }
505
+
506
+ .apm-modal-title {
507
+ color: var(--apm-ink);
508
+ font-size: 15px;
509
+ font-weight: 700;
510
+ }
511
+
512
+ .apm-modal-body {
513
+ padding: 18px;
514
+ }
515
+
516
+ .apm-modal-body pre.modal-content {
517
+ margin: 0;
518
+ padding: 14px;
519
+ border: 1px solid var(--apm-border);
520
+ border-radius: 8px;
521
+ background: #0f172a;
522
+ color: #d1e7ff;
523
+ box-shadow: none;
524
+ }
525
+
526
+ .apm-footer {
527
+ width: min(1440px, calc(100% - 48px));
528
+ margin: 0 auto;
529
+ padding: 18px 0 26px;
530
+ color: var(--apm-muted);
531
+ border-top: 1px solid var(--apm-border);
532
+ }
533
+
534
+ .apm-footer-status {
535
+ margin-bottom: 10px;
536
+ }
537
+
538
+ .apm-footer-meta {
539
+ display: flex;
540
+ flex-wrap: wrap;
541
+ gap: 10px 18px;
542
+ font-size: 12px;
543
+ }
544
+
545
+ .apm-footer-meta label {
546
+ margin: 0;
547
+ color: #475467;
548
+ }
549
+
550
+ @media (max-width: 980px) {
551
+ .apm-topbar-inner,
552
+ .apm-content,
553
+ .apm-footer {
554
+ width: min(100% - 28px, 1440px);
555
+ }
556
+
557
+ .apm-topbar-inner {
558
+ flex-wrap: wrap;
559
+ padding: 12px 0;
560
+ }
561
+
562
+ .apm-nav {
563
+ order: 3;
564
+ width: 100%;
565
+ overflow-x: auto;
566
+ }
567
+
568
+ .apm-date-select {
569
+ margin-left: auto;
570
+ }
571
+
572
+ .apm-language-switch {
573
+ margin-left: auto;
574
+ }
575
+
576
+ .apm-page-header,
577
+ .apm-toolbar {
578
+ align-items: flex-start;
579
+ flex-direction: column;
580
+ }
581
+
582
+ .apm-col-6 {
583
+ grid-column: span 12;
584
+ }
585
+
586
+ .apm-metrics {
587
+ grid-template-columns: repeat(2, minmax(0, 1fr));
588
+ }
589
+ }
590
+
591
+ @media (max-width: 640px) {
592
+ .apm-brand-name {
593
+ display: none;
594
+ }
595
+
596
+ .apm-date-select {
597
+ width: 100%;
598
+ justify-content: space-between;
599
+ }
600
+
601
+ .apm-language-switch {
602
+ width: 100%;
603
+ justify-content: center;
604
+ }
605
+
606
+ .apm-date-select select.form-control {
607
+ flex: 1 1 auto;
608
+ }
609
+
610
+ .apm-content {
611
+ padding-top: 20px;
612
+ }
613
+
614
+ .apm-page-title {
615
+ font-size: 22px;
616
+ }
617
+
618
+ .apm-metrics {
619
+ grid-template-columns: 1fr;
620
+ }
621
+
622
+ .apm-detail-list {
623
+ grid-template-columns: 1fr;
624
+ }
625
+ }
@@ -23,6 +23,7 @@ module SimpleApm
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),
@@ -53,6 +54,11 @@ module SimpleApm
53
54
  redirect_to request.referer
54
55
  end
55
56
 
57
+ def set_locale
58
+ session[:simple_apm_locale] = params[:locale] if AVAILABLE_LOCALES.include?(params[:locale])
59
+ redirect_back fallback_location: dashboard_path
60
+ end
61
+
56
62
  def data
57
63
  @data = SimpleApm::Redis.in_apm_days.map {|x| SimpleApm::Hit.day_info(x)}
58
64
  end
@@ -60,21 +66,21 @@ module SimpleApm
60
66
  def data_delete
61
67
  if params[:date].is_a?(String)
62
68
  r = SimpleApm::Redis.clear_data(params[:date])
63
- flash[:notice] = r[:success] ? '删除成功!' : r[:msg]
69
+ flash[:notice] = r[:success] ? t("simple_apm.flash.deleted") : t("simple_apm.flash.no_data_for_day")
64
70
  elsif params[:type]=='month'
65
71
  del_count = SimpleApm::Redis.clear_data_before_time(Time.now.at_beginning_of_day - 1.month)
66
- flash[:notice] = "成功删除#{del_count}条数据"
72
+ flash[:notice] = t("simple_apm.flash.deleted_count", count: del_count)
67
73
  elsif params[:type]=='week'
68
74
  del_count = SimpleApm::Redis.clear_data_before_time(Time.now.at_beginning_of_day - 1.week)
69
- flash[:notice] = "成功删除#{del_count}条数据"
75
+ flash[:notice] = t("simple_apm.flash.deleted_count", count: del_count)
70
76
  elsif params[:type]=='stop_data'
71
77
  SimpleApm::Redis.stop!
72
- flash[:notice] = '设置成功!'
78
+ flash[:notice] = t("simple_apm.flash.updated")
73
79
  elsif params[:type]=='rerun_data'
74
80
  SimpleApm::Redis.rerun!
75
- flash[:notice] = '设置成功!'
81
+ flash[:notice] = t("simple_apm.flash.updated")
76
82
  else
77
- flash[:notice] = '未知操作!'
83
+ flash[:notice] = t("simple_apm.flash.unknown_action")
78
84
  # r = params[:date].map{|d|SimpleApm::Redis.clear_data(d)}
79
85
  # suc, fail = r.partition{|x|x[:success]}
80
86
  # flash[:notice] = "成功删除#{suc.length}"
@@ -1,10 +1,24 @@
1
1
  module SimpleApm
2
2
  class ApplicationController < ActionController::Base
3
+ AVAILABLE_LOCALES = %w[en zh-CN].freeze
4
+
3
5
  protect_from_forgery with: :exception
4
- helper_method :apm_date
6
+ around_action :switch_simple_apm_locale
7
+ helper_method :apm_date, :simple_apm_locale
5
8
 
6
9
  def apm_date
7
10
  session[:apm_date].presence || Time.now.strftime("%Y-%m-%d")
8
11
  end
12
+
13
+ def simple_apm_locale
14
+ locale = session[:simple_apm_locale].presence
15
+ AVAILABLE_LOCALES.include?(locale) ? locale : "en"
16
+ end
17
+
18
+ private
19
+
20
+ def switch_simple_apm_locale(&action)
21
+ I18n.with_locale(simple_apm_locale, &action)
22
+ end
9
23
  end
10
24
  end
@@ -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'