Reoo88 commited on
Commit
d2bbc4e
·
verified ·
1 Parent(s): 50594d5

fix: قراءة واحدة مشتركة للمعالج/الرام من cgroup

Browse files
Files changed (1) hide show
  1. app.py +79 -21
app.py CHANGED
@@ -632,30 +632,81 @@ def _cgroup_quota_cpus():
632
  pass
633
  return None
634
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
  _cpu_lock=threading.Lock()
636
  _cpu_sample={'t':None,'ns':None}
 
 
 
 
 
637
 
638
  def resource_snapshot():
639
- """Host loadavg (shared machine) plus this Space cgroup quota (cpu-basic = 2)."""
640
- out={'load1':None,'cpus':os.cpu_count() or 1,'mem_total_mb':None,'mem_avail_mb':None,
641
- 'quota_cpus':None,'cpu_cores_used':None,'cpu_pct_of_quota':None}
642
- try:
643
- out['load1']=float(open('/proc/loadavg').read().split()[0])
644
- except Exception:
645
- pass
646
- try:
647
- info={}
648
- for line in open('/proc/meminfo'):
649
- parts=line.split()
650
- if len(parts)>=2:info[parts[0].rstrip(':')]=int(parts[1])
651
- out['mem_total_mb']=info.get('MemTotal',0)//1024
652
- out['mem_avail_mb']=info.get('MemAvailable', info.get('MemFree',0))//1024
653
- except Exception:
654
- pass
655
- out['quota_cpus']=_cgroup_quota_cpus()
656
- ns=_cgroup_usage_ns()
657
- now=time.monotonic()
658
  with _cpu_lock:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  prev=_cpu_sample
660
  if prev['t'] is not None and prev['ns'] is not None and ns is not None and now-prev['t']>=0.4:
661
  cores=(ns-prev['ns'])/1e9/(now-prev['t'])
@@ -664,9 +715,15 @@ def resource_snapshot():
664
  q=out['quota_cpus']
665
  if q and q>0:
666
  out['cpu_pct_of_quota']=round(min(999.0, max(0.0, 100.0*cores/q)),2)
 
 
 
 
667
  _cpu_sample['t']=now
668
  _cpu_sample['ns']=ns
669
- return out
 
 
670
 
671
  def _check_account(user):
672
  """One monitor cycle. Starts DirectRelay when OAuth automation is off."""
@@ -934,6 +991,7 @@ def state():
934
  try:active=sum(1 for e in youtube.senders.values() if e.get('thread') and e['thread'].is_alive())
935
  except Exception:pass
936
  last_error, last_error_user=_pick_last_error(tasks)
 
937
  return {'tasks':task_list,'logs':events[-80:],'version':APP_VERSION,'last_error_user':last_error_user,
938
  'max_accounts':MAX_ACCOUNTS if MAX_ACCOUNTS is not None else 'بلا حد',
939
  'tiktok':tiktok_state,'check_interval':INTERVAL,
@@ -941,7 +999,7 @@ def state():
941
  'monitor_running':_monitor_alive.is_set(),
942
  'active_relays':hub.active_count(),'last_monitor_cycle':_last_monitor_cycle,
943
  'max_concurrent_relays':max_concurrent_relays(),
944
- **{k:resource_snapshot().get(k) for k in ('load1','cpus','mem_total_mb','mem_avail_mb','quota_cpus','cpu_cores_used','cpu_pct_of_quota')},
945
  'last_error':last_error,'rtmp_configured':rtmp_configured(),
946
  'oauth_configured':youtube.api.configured(),
947
  'metadata_automation':_oauth_automation_on(),
 
632
  pass
633
  return None
634
 
635
+ def _cgroup_mem():
636
+ """حدّ الذاكرة والمستهلك منها **لهذه الحاوية** بالميغابايت (لا للخادم المشترك).
637
+
638
+ /proc/meminfo يصف الآلة المضيفة كاملة (16 نواة و126 غيغا) بينما لوحة Hugging
639
+ Face تعرض حصّة الـSpace نفسها (2 vCPU و16 غيغا) — فكان الرقمان يصفان شيئين
640
+ مختلفين. cgroup v2 يعطي ما تراه الحاوية فعلًا، فيتطابق الرقمان.
641
+ """
642
+ def _read(path):
643
+ try:
644
+ return open(path).read().strip()
645
+ except Exception:
646
+ return ''
647
+ limit=(_read('/sys/fs/cgroup/memory.max') or _read('/sys/fs/cgroup/memory.high')
648
+ or _read('/sys/fs/cgroup/memory/memory.limit_in_bytes'))
649
+ if limit=='max':
650
+ limit=_read('/sys/fs/cgroup/memory.high')
651
+ used=_read('/sys/fs/cgroup/memory.current') or _read('/sys/fs/cgroup/memory/memory.usage_in_bytes')
652
+ try:
653
+ used_mb=int(used)//1048576 if used else None
654
+ except ValueError:
655
+ used_mb=None
656
+ try:
657
+ limit_mb=int(limit)//1048576 if limit and limit!='max' else None
658
+ except ValueError:
659
+ limit_mb=None
660
+ # قيمة خيالية (أكبر من ذاكرة المضيف) تعني «بلا حدّ» على هذا النظام.
661
+ if limit_mb and limit_mb>1024*1024:
662
+ limit_mb=None
663
+ return limit_mb,used_mb
664
+
665
+
666
  _cpu_lock=threading.Lock()
667
  _cpu_sample={'t':None,'ns':None}
668
+ # قراءة واحدة يتشاركها كل القرّاء خلال هذه النافذة. السبب: العيّنة ذات حالة
669
+ # (فرقٌ عن الاستدعاء السابق)، وكان /health و/api/state يصفّر كلٌّ منهما أساس
670
+ # الآخر ⇒ رقمان مختلفان للحظة واحدة، أو «—» إذا قلّ الفاصل عن 0.4ث.
671
+ _RES_TTL=2.0
672
+ _res_cache={'t':None,'data':None}
673
 
674
  def resource_snapshot():
675
+ """حصّة هذا الـSpace من المعالج والذاكرة (cgroup)، لا عتاد الخادم المشترك.
676
+
677
+ القفل يغطّي الدالة كاملة: استدعاءان متزامنان يعيدان نفس القراءة بدل أن
678
+ يُفسد كلٌّ منهما عيّنة الآخر.
679
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  with _cpu_lock:
681
+ now=time.monotonic()
682
+ cached=_res_cache['data']
683
+ if cached is not None and _res_cache['t'] is not None and now-_res_cache['t']<_RES_TTL:
684
+ return dict(cached)
685
+ out={'load1':None,'cpus':os.cpu_count() or 1,'mem_total_mb':None,'mem_avail_mb':None,
686
+ 'quota_cpus':None,'cpu_cores_used':None,'cpu_pct_of_quota':None}
687
+ try:
688
+ out['load1']=float(open('/proc/loadavg').read().split()[0])
689
+ except Exception:
690
+ pass
691
+ lim,used=_cgroup_mem()
692
+ if lim:
693
+ out['mem_total_mb']=lim
694
+ out['mem_avail_mb']=max(0,lim-(used or 0))
695
+ else:
696
+ try:
697
+ info={}
698
+ for line in open('/proc/meminfo'):
699
+ parts=line.split()
700
+ if len(parts)>=2:info[parts[0].rstrip(':')]=int(parts[1])
701
+ out['mem_total_mb']=info.get('MemTotal',0)//1024
702
+ out['mem_avail_mb']=info.get('MemAvailable', info.get('MemFree',0))//1024
703
+ except Exception:
704
+ pass
705
+ out['quota_cpus']=_cgroup_quota_cpus()
706
+ if out['quota_cpus']:
707
+ # «عدد الأنوية» في اللوحة هو حصّة الـSpace لا أنوية الخادم المشترك.
708
+ out['cpus']=out['quota_cpus']
709
+ ns=_cgroup_usage_ns()
710
  prev=_cpu_sample
711
  if prev['t'] is not None and prev['ns'] is not None and ns is not None and now-prev['t']>=0.4:
712
  cores=(ns-prev['ns'])/1e9/(now-prev['t'])
 
715
  q=out['quota_cpus']
716
  if q and q>0:
717
  out['cpu_pct_of_quota']=round(min(999.0, max(0.0, 100.0*cores/q)),2)
718
+ elif cached is not None:
719
+ # نافذة أقصر من 0.4ث: أعد آخر نسبة معروفة بدل «—».
720
+ out['cpu_cores_used']=cached.get('cpu_cores_used')
721
+ out['cpu_pct_of_quota']=cached.get('cpu_pct_of_quota')
722
  _cpu_sample['t']=now
723
  _cpu_sample['ns']=ns
724
+ _res_cache['t']=now
725
+ _res_cache['data']=dict(out)
726
+ return dict(out)
727
 
728
  def _check_account(user):
729
  """One monitor cycle. Starts DirectRelay when OAuth automation is off."""
 
991
  try:active=sum(1 for e in youtube.senders.values() if e.get('thread') and e['thread'].is_alive())
992
  except Exception:pass
993
  last_error, last_error_user=_pick_last_error(tasks)
994
+ _res=resource_snapshot()
995
  return {'tasks':task_list,'logs':events[-80:],'version':APP_VERSION,'last_error_user':last_error_user,
996
  'max_accounts':MAX_ACCOUNTS if MAX_ACCOUNTS is not None else 'بلا حد',
997
  'tiktok':tiktok_state,'check_interval':INTERVAL,
 
999
  'monitor_running':_monitor_alive.is_set(),
1000
  'active_relays':hub.active_count(),'last_monitor_cycle':_last_monitor_cycle,
1001
  'max_concurrent_relays':max_concurrent_relays(),
1002
+ **{k:_res.get(k) for k in ('load1','cpus','mem_total_mb','mem_avail_mb','quota_cpus','cpu_cores_used','cpu_pct_of_quota')},
1003
  'last_error':last_error,'rtmp_configured':rtmp_configured(),
1004
  'oauth_configured':youtube.api.configured(),
1005
  'metadata_automation':_oauth_automation_on(),