From 2489c426e26a7a5429a8c7e40992a1f3470498ae Mon Sep 17 00:00:00 2001 From: "choi.jh" Date: Tue, 11 Nov 2025 08:52:20 +0900 Subject: [PATCH] trafficmonitor init...2 --- app/DTOs/CollectorDTO.php | 8 +++- app/Database/traffic_test1.sql | 33 ++++++++++++++- app/Entities/TrafficEntity.php | 28 +++++++++++++ app/Entities/UserEntity.php | 9 ---- app/Forms/CollectorForm.php | 10 +++++ app/Language/en/Collector.php | 6 ++- app/Models/CollectorModel.php | 2 + app/Services/CollectorService.php | 69 +++++++++++++++---------------- 8 files changed, 115 insertions(+), 50 deletions(-) diff --git a/app/DTOs/CollectorDTO.php b/app/DTOs/CollectorDTO.php index f4d33dc..0ec2757 100644 --- a/app/DTOs/CollectorDTO.php +++ b/app/DTOs/CollectorDTO.php @@ -6,8 +6,10 @@ class CollectorDTO extends CommonDTO { public ?int $uid = null; public ?string $trafficinfo_uid = null; - public ?string $in = null; - public ?string $out = null; + public ?int $in = null; + public ?int $out = null; + public ?int $raw_in = null; + public ?int $raw_out = null; public function __construct(array $datas = []) { @@ -26,6 +28,8 @@ class CollectorDTO extends CommonDTO 'trafficinfo_uid' => $this->trafficinfo_uid, 'in' => $this->in, 'out' => $this->out, + 'raw_in' => $this->raw_in, + 'raw_out' => $this->raw_out, ]; } } diff --git a/app/Database/traffic_test1.sql b/app/Database/traffic_test1.sql index 284b11d..1e84df0 100644 --- a/app/Database/traffic_test1.sql +++ b/app/Database/traffic_test1.sql @@ -15,6 +15,36 @@ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +-- +-- Table structure for table `collectorinfo` +-- + +DROP TABLE IF EXISTS `collectorinfo`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `collectorinfo` ( + `uid` int(11) NOT NULL AUTO_INCREMENT, + `trafficinfo_uid` int(11) NOT NULL, + `in` int(11) NOT NULL DEFAULT 0 COMMENT 'IN KBit/SEC', + `out` int(11) NOT NULL DEFAULT 0 COMMENT 'OUT KBit/SEC', + `raw_in` int(11) NOT NULL DEFAULT 0 COMMENT 'RAW IN ', + `raw_out` int(11) NOT NULL DEFAULT 0 COMMENT 'RAW OUT', + `updated_at` timestamp NULL DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT current_timestamp(), + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='수집정보'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `collectorinfo` +-- + +LOCK TABLES `collectorinfo` WRITE; +/*!40000 ALTER TABLE `collectorinfo` DISABLE KEYS */; +/*!40000 ALTER TABLE `collectorinfo` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `mylog` -- @@ -53,6 +83,7 @@ CREATE TABLE `trafficinfo` ( `uid` int(11) NOT NULL AUTO_INCREMENT, `client` varchar(50) NOT NULL, `switch` varchar(10) NOT NULL, + `community` varchar(20) NOT NULL DEFAULT 'IDC-JP', `ip` char(16) NOT NULL, `interface` varchar(20) NOT NULL, `status` varchar(20) NOT NULL DEFAULT 'available', @@ -116,4 +147,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2025-11-10 14:43:07 +-- Dump completed on 2025-11-10 17:22:27 diff --git a/app/Entities/TrafficEntity.php b/app/Entities/TrafficEntity.php index 95f1ba8..7e722bb 100644 --- a/app/Entities/TrafficEntity.php +++ b/app/Entities/TrafficEntity.php @@ -4,9 +4,37 @@ namespace App\Entities; use App\Models\TrafficModel; +/** + * 모니터링 대상 장비/인터페이스 정보를 담는 엔티티 + */ class TrafficEntity extends CommonEntity { const PK = TrafficModel::PK; const TITLE = TrafficModel::TITLE; //기본기능용 + protected $casts = [ + // 'role' => 'json-array', // 🚫 CSV 형식 저장을 위해 제거 + ]; + + public function getIP(): string + { + return $this->attributes['ip']; + } + public function getInterface(): string + { + return $this->attributes['interface']; + } + public function getCommunity(): string + { + return $this->attributes['community'] ?? 'public'; + } + // Setter 예시: IP 주소 설정 시 유효성 검사 또는 로직 추가 가능 + public function setIP(string $ip): void + { + // 예시: IP 주소 유효성 검사 로직 추가 가능 + if (filter_var($ip, FILTER_VALIDATE_IP) === false) { + throw new \InvalidArgumentException("유효하지 않은 IP 주소입니다."); + } + $this->attributes['ip'] = $ip; + } } diff --git a/app/Entities/UserEntity.php b/app/Entities/UserEntity.php index 8f0b440..347801b 100644 --- a/app/Entities/UserEntity.php +++ b/app/Entities/UserEntity.php @@ -16,18 +16,14 @@ class UserEntity extends CommonEntity // 'role' => 'json-array', // 🚫 CSV 형식 저장을 위해 제거 ]; - // --- Getter Methods --- - public function getID(): string { return (string) $this->attributes['id']; } - public function getPassword(): string { return $this->attributes['passwd']; } - /** * 사용자의 역할을 배열 형태로 반환합니다. * DB의 JSON 또는 CSV 형식 데이터를 모두 배열로 복구할 수 있는 로직을 포함합니다. @@ -36,12 +32,10 @@ class UserEntity extends CommonEntity public function getRole(): array { $role = $this->attributes['role'] ?? null; - // 1. 이미 배열인 경우 (방어적 코딩) if (is_array($role)) { return array_filter($role); } - // 2. 문자열 데이터인 경우 처리 if (is_string($role) && !empty($role)) { // 2-a. JSON 디코딩 시도 (기존 DB의 JSON 형식 처리) @@ -49,18 +43,15 @@ class UserEntity extends CommonEntity if (json_last_error() === JSON_ERROR_NONE && is_array($decodedRole)) { return $decodedRole; } - // 2-b. JSON이 아니면 CSV로 가정하고 변환 $parts = explode(',', $role); // 각 요소의 불필요한 공백과 따옴표 제거 $cleanedRoles = array_map(fn($item) => trim($item, " \t\n\r\0\x0B\""), $parts); return array_filter($cleanedRoles); } - // 3. 변환에 실패했거나 데이터가 없는 경우 빈 배열 반환 return []; } - // --- Setter Methods --- public function setPasswd(string $password) diff --git a/app/Forms/CollectorForm.php b/app/Forms/CollectorForm.php index 3809c66..6f8705a 100644 --- a/app/Forms/CollectorForm.php +++ b/app/Forms/CollectorForm.php @@ -21,6 +21,8 @@ class CollectorForm extends CommonForm 'trafficinfo_uid', 'in', 'out', + 'raw_in', + 'raw_out', ]; break; case 'modify': @@ -30,6 +32,8 @@ class CollectorForm extends CommonForm 'trafficinfo_uid', 'in', 'out', + 'raw_in', + 'raw_out', ]; break; case 'view': @@ -38,6 +42,8 @@ class CollectorForm extends CommonForm 'trafficinfo_uid', 'in', 'out', + 'raw_in', + 'raw_out', 'created_at', ]; break; @@ -47,6 +53,8 @@ class CollectorForm extends CommonForm 'trafficinfo_uid', 'in', 'out', + 'raw_in', + 'raw_out', 'created_at', ]; break; @@ -63,6 +71,8 @@ class CollectorForm extends CommonForm case "trafficinfo_uid": case "in": case "out": + case "raw_in": + case "raw_out": $rule = "required|trim|neumeric"; $rules[$field] = $rule; break; diff --git a/app/Language/en/Collector.php b/app/Language/en/Collector.php index ace697d..d201ed2 100644 --- a/app/Language/en/Collector.php +++ b/app/Language/en/Collector.php @@ -3,8 +3,10 @@ return [ 'title' => "수집정보", 'label' => [ 'trafficinfo_uid' => "고객명", - 'in' => "IN", - 'out' => "OUT", + 'in' => "IN Kbit/s", + 'out' => "OUT Kbit/s", + 'raw_in' => "IN Octets", + 'raw_out' => "OUT Octets", 'updated_at' => "수정일", 'created_at' => "생성일", 'deleted_at' => "삭제일", diff --git a/app/Models/CollectorModel.php b/app/Models/CollectorModel.php index b3b5dfb..d66c29f 100644 --- a/app/Models/CollectorModel.php +++ b/app/Models/CollectorModel.php @@ -18,6 +18,8 @@ class CollectorModel extends CommonModel "trafficinfo_uid", "in", "out", + "raw_in", + "raw_out", "updated_at" ]; public function __construct() diff --git a/app/Services/CollectorService.php b/app/Services/CollectorService.php index 1f0bb2e..4f77dde 100644 --- a/app/Services/CollectorService.php +++ b/app/Services/CollectorService.php @@ -4,6 +4,7 @@ namespace App\Services; use App\DTOs\CollectorDTO; use App\Entities\CollectorEntity; +use App\Entities\TrafficEntity; use App\Forms\CollectorForm; use App\Helpers\CollectorHelper; use App\Models\CollectorModel; @@ -15,7 +16,6 @@ class CollectorService extends CommonService const OID_IF_IN_OCTETS = '1.3.6.1.2.1.2.2.1.10.'; // ifInOctets (Raw Octets) const OID_IF_OUT_OCTETS = '1.3.6.1.2.1.2.2.1.16.'; // ifOutOctets (Raw Octets) const SNMP_VERSION = '2c'; - const SNMP_COMMNUNITY = 'IDC-JP'; public function __construct(CollectorModel $model) { parent::__construct($model); @@ -93,54 +93,45 @@ class CollectorService extends CommonService parent::setSearchWord($word); } - /** - * SNMP GET 요청을 실행하고 Octet 값을 추출합니다. - * @param string $ip 장비 IP - * @param int $ifIndex 인터페이스 인덱스 - * @param string $community SNMP 커뮤니티 문자열 - * @param string $oid OID (접두사) - * @return int|null 수집된 Octet 값 또는 오류 시 null - */ - protected function getOctets(string $ip, int $ifIndex, string $community, string $oid): ?int + private function getSNMPOctets(TrafficEntity $trafficEntity, string $oid): ?int { - $fullOid = $oid . $ifIndex; - // snmp2_get을 사용하여 SNMP v2c로 요청 (64비트 카운터 지원에 유리) - $result = @snmp2_get($ip, $community, $fullOid, 100000, 3); // 3초 타임아웃 + $fullOid = $oid . $trafficEntity->getInterface(); + $community = $trafficEntity->getCommunity(); + $ip = $trafficEntity->getIP(); + // snmp2_get을 사용하여 SNMP v2c로 요청 + // 💡 snmp2_get() 함수가 존재하지 않는다는 LSP 오류를 무시하기 위해 @suppress 태그 사용 + /** @phpstan-ignore-next-line */ + $result = @snmp2_get($ip, $community, $fullOid, 100000, 3); if ($result === false || $result === null) { log_message('error', "SNMP 통신 실패: {$ip} ({$fullOid}, Community: {$community})"); return null; } // 결과 문자열에서 숫자 값만 추출하여 정수로 변환 if (preg_match('/\d+/', $result, $matches)) { + // SNMP Counter는 BigInt가 될 수 있으므로 intval 대신 (int) 캐스팅을 사용하여 + // 최대한 큰 정수로 변환합니다. PHP 64비트 환경이 필요합니다. return (int)$matches[0]; } return null; } - /** - * 트래픽을 수집하고 Kbit/s를 계산하여 DB에 저장합니다. - * @param int $trafficInfoUid traffic_info 테이블의 기본 키 (연계 키) - * @param string $ip 장비 IP - * @param int $ifIndex 인터페이스 인덱스 - * @param string $community SNMP 커뮤니티 문자열 - */ - public function collectAndCalculate(int $trafficInfoUid, string $ip, int $ifIndex, string $community): void + public function getCalculatedData(TrafficEntity $trafficEntity): array { - $currentInOctets = $this->getOctets($ip, $ifIndex, $community, self::OID_IF_IN_OCTETS); - $currentOutOctets = $this->getOctets($ip, $ifIndex, $community, self::OID_IF_OUT_OCTETS); + $currentInOctets = $this->getSNMPOctets($trafficEntity, self::OID_IF_IN_OCTETS); + $currentOutOctets = $this->getSNMPOctets($trafficEntity, self::OID_IF_OUT_OCTETS); $currentTime = Time::now()->toDateTimeString(); if ($currentInOctets === null || $currentOutOctets === null) { - log_message('warning', "트래픽 수집 실패: {$ip} - IF{$ifIndex} (UID: {$trafficInfoUid})"); - return; + $message = "트래픽 수집 실패: {$trafficEntity->getIP()} - IF{$trafficEntity->getInterface()} (UID: {$trafficEntity->getPK()})"; + log_message('warning', $message); + throw new \Exception($message); } - // 이전 데이터를 조회하여 Rate 계산에 사용 - $lastEntry = $this->model->getLastEntryByInfoUid($trafficInfoUid); + // 이전 데이터를 조회하여 Rate 계산에 사용 (trafficModel 사용) + $lastEntry = $this->model->getLastEntryByInfoUid($trafficEntity->getPK()); $inKbitsSec = 0.0; $outKbitsSec = 0.0; // 이전 데이터가 있어야만 Rate 계산 가능 if ($lastEntry !== null) { $lastTime = Time::parse($lastEntry['created_at'])->getTimestamp(); $deltaTime = Time::now()->getTimestamp() - $lastTime; - if ($deltaTime > 0) { // Raw Octets 값의 차분 계산 $deltaInOctets = $currentInOctets - $lastEntry['raw_in_octets']; @@ -150,19 +141,25 @@ class CollectorService extends CommonService $inKbitsSec = ($deltaInOctets * 8) / $deltaTime / 1000; $outKbitsSec = ($deltaOutOctets * 8) / $deltaTime / 1000; } else { - log_message('error', "시간 차이 오류 발생: {$ip} - {$deltaTime}초 (UID: {$trafficInfoUid})"); + log_message('error', "시간 차이 오류 발생: {$trafficEntity->getIP()} - {$deltaTime}초 (UID: {$trafficEntity->getPK()})"); } } - // Collector DB에 결과 저장 - $this->model->insert([ - 'trafficinfo_uid' => $trafficInfoUid, + // DB에 저장할 데이터를 배열로 반환 + return [ + 'trafficinfo_uid' => $trafficEntity->getPK(), 'in_kbits_sec' => round($inKbitsSec, 2), 'out_kbits_sec' => round($outKbitsSec, 2), - 'raw_in_octets' => $currentInOctets, // 다음 계산을 위해 Raw 값 저장 - 'raw_out_octets' => $currentOutOctets, // 다음 계산을 위해 Raw 값 저장 + 'raw_in_octets' => $currentInOctets, + 'raw_out_octets' => $currentOutOctets, 'created_at' => $currentTime, - ]); - log_message('info', "트래픽 계산 및 저장 완료 (UID: {$trafficInfoUid}), In: {$inKbitsSec} Kb/s"); + ]; + } + public function collectAndSave(TrafficEntity $trafficEntity): void + { + $data = $this->getCalculatedData($trafficEntity); + // Collector DB에 결과 저장 + $this->model->insert($data); + log_message('info', "트래픽 계산 및 저장 완료 (UID: {$trafficEntity->getPK()}), In: {$data['in_kbits_sec']} Kb/s"); } }