177 lines
7.8 KiB
PHP
177 lines
7.8 KiB
PHP
<?php
|
|
|
|
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;
|
|
use CodeIgniter\I18n\Time;
|
|
use RuntimeException;
|
|
|
|
class CollectorService extends CommonService
|
|
{
|
|
private $_form = null;
|
|
private $_helper = null;
|
|
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';
|
|
// Counter32의 최대값 + 1 (오버플로우 보정을 위해 사용)
|
|
const MAX_COUNTER_32BIT = 4294967296;
|
|
public function __construct(CollectorModel $model)
|
|
{
|
|
parent::__construct($model);
|
|
$this->addClassPaths('Collector');
|
|
}
|
|
public function getFormService(): CollectorForm
|
|
{
|
|
if ($this->_form === null) {
|
|
$this->_form = new CollectorForm();
|
|
$this->_form->setAttributes([
|
|
'pk_field' => $this->model->getPKField(),
|
|
'title_field' => $this->model->getTitleField(),
|
|
'table' => $this->model->getTable(),
|
|
'useAutoIncrement' => $this->model->useAutoIncrement(),
|
|
'class_path' => $this->getClassPaths(false),
|
|
]);
|
|
}
|
|
return $this->_form;
|
|
}
|
|
public function getHelper(): CollectorHelper
|
|
{
|
|
if ($this->_helper === null) {
|
|
$this->_helper = new CollectorHelper();
|
|
$this->_helper->setAttributes([
|
|
'pk_field' => $this->model->getPKField(),
|
|
'title_field' => $this->model->getTitleField(),
|
|
'table' => $this->model->getTable(),
|
|
'useAutoIncrement' => $this->model->useAutoIncrement(),
|
|
'class_path' => $this->getClassPaths(false),
|
|
]);
|
|
}
|
|
return $this->_helper;
|
|
}
|
|
//기본 기능부분
|
|
public function create(object $dto): CollectorEntity
|
|
{
|
|
if (!$dto instanceof CollectorDTO) {
|
|
throw new RuntimeException(__METHOD__ . "에서 오류발생:" . get_class($dto) . "는 사용할수 없습니다.");
|
|
}
|
|
return parent::create($dto);
|
|
}
|
|
public function modify($uid, object $dto): CollectorEntity
|
|
{
|
|
if (!$dto instanceof CollectorDTO) {
|
|
throw new RuntimeException(__METHOD__ . "에서 오류발생:" . get_class($dto) . "는 사용할수 없습니다.");
|
|
}
|
|
return parent::modify($uid, $dto);
|
|
}
|
|
//List 검색용
|
|
//FormFilter 조건절 처리
|
|
//검색어조건절처리
|
|
public function setSearchWord(string $word): void
|
|
{
|
|
$this->model->orLike($this->model->getTable() . "." . $this->model->getTitleField(), $word, 'both');
|
|
parent::setSearchWord($word);
|
|
}
|
|
//Chart용
|
|
public function getAggregateDatas(TrafficEntity $trafficEntity, string $startDate, string $endDate): array
|
|
{
|
|
$entities = [];
|
|
foreach ($this->getEntities(["trafficinfo_uid" => $trafficEntity->getPK(), "created_at >=" => $startDate, "created_at <=" => $endDate]) as $entity) {
|
|
$entities[] = ['in_kbits' => $entity->getIn(), 'out_kbits' => $entity->getOut(), 'created_at' => $entity->getCreatedAt()];
|
|
}
|
|
return $entities;
|
|
}
|
|
//SNMP연결
|
|
private function getSNMPOctets(TrafficEntity $trafficEntity, string $oid): ?int
|
|
{
|
|
$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;
|
|
}
|
|
// 💡 정규식 수정: /\d+$/ (문자열 끝의 숫자만 추출)
|
|
if (preg_match('/\d+$/', $result, $matches)) {
|
|
// SNMP Counter는 BigInt가 될 수 있으므로 intval 대신 (int) 캐스팅을 사용하여
|
|
// 최대한 큰 정수로 변환합니다. PHP 64비트 환경이 필요합니다.
|
|
return (int)$matches[0];
|
|
}
|
|
return null;
|
|
}
|
|
public function getCalculatedData(TrafficEntity $trafficEntity): array
|
|
{
|
|
$currentInOctets = $this->getSNMPOctets($trafficEntity, self::OID_IF_IN_OCTETS);
|
|
$currentOutOctets = $this->getSNMPOctets($trafficEntity, self::OID_IF_OUT_OCTETS);
|
|
|
|
if ($currentInOctets === null || $currentOutOctets === null) {
|
|
$message = "트래픽 수집 실패: {$trafficEntity->getIP()} - IF{$trafficEntity->getInterface()} (UID: {$trafficEntity->getPK()})";
|
|
log_message('warning', $message);
|
|
throw new \Exception($message);
|
|
}
|
|
|
|
// 이전 데이터를 조회하여 Rate 계산에 사용
|
|
// $this->model은 TrafficDataModel의 인스턴스라고 가정
|
|
$lastEntity = $this->model->getLastEntity($trafficEntity->getPK());
|
|
|
|
$inKbitsSec = 0.0;
|
|
$outKbitsSec = 0.0;
|
|
|
|
// 이전 데이터가 있어야만 Rate 계산 가능
|
|
if ($lastEntity !== null) {
|
|
$lastTime = Time::parse($lastEntity->getCreatedAt())->getTimestamp();
|
|
$deltaTime = Time::now()->getTimestamp() - $lastTime;
|
|
|
|
if ($deltaTime > 0) {
|
|
$lastIn = $lastEntity->getRawIn();
|
|
$lastOut = $lastEntity->getRawOut();
|
|
|
|
// 💡 1. 인바운드 Octets 차분 계산 (오버플로우 처리)
|
|
if ($currentInOctets < $lastIn) {
|
|
// 카운터 롤오버 발생: Delta = (MAX - Last) + Current
|
|
$deltaInOctets = (self::MAX_COUNTER_32BIT - $lastIn) + $currentInOctets;
|
|
log_message('info', "Inbound Rollover Detected for UID: {$trafficEntity->getPK()}. Delta: {$deltaInOctets}");
|
|
} else {
|
|
// 정상적인 차분
|
|
$deltaInOctets = $currentInOctets - $lastIn;
|
|
}
|
|
|
|
// 💡 2. 아웃바운드 Octets 차분 계산 (오버플로우 처리)
|
|
if ($currentOutOctets < $lastOut) {
|
|
// 카운터 롤오버 발생
|
|
$deltaOutOctets = (self::MAX_COUNTER_32BIT - $lastOut) + $currentOutOctets;
|
|
log_message('info', "Outbound Rollover Detected for UID: {$trafficEntity->getPK()}. Delta: {$deltaOutOctets}");
|
|
} else {
|
|
// 정상적인 차분
|
|
$deltaOutOctets = $currentOutOctets - $lastOut;
|
|
}
|
|
|
|
// Kbit/s 계산: (Delta_Octets * 8 bits) / Delta_Time_Seconds / 1000 (-> Kbit/s)
|
|
// 실수(float) 연산으로 정확도를 높입니다.
|
|
$inKbitsSec = ($deltaInOctets * 8.0) / $deltaTime / 1000.0;
|
|
$outKbitsSec = ($deltaOutOctets * 8.0) / $deltaTime / 1000.0;
|
|
} else {
|
|
log_message('error', "시간 차이 오류 발생: {$trafficEntity->getIP()} - {$deltaTime}초 (UID: {$trafficEntity->getPK()})");
|
|
}
|
|
}
|
|
|
|
// DB에 저장할 데이터를 배열로 반환
|
|
return [
|
|
// 💡 요청에 따라 'in'과 'out' 값을 정수형으로 캐스팅하여 소수점 이하를 버림
|
|
'trafficinfo_uid' => (int)$trafficEntity->getPK(),
|
|
'in' => (int)$inKbitsSec, // 정수형으로 반환
|
|
'out' => (int)$outKbitsSec, // 정수형으로 반환
|
|
'raw_in' => (int)$currentInOctets,
|
|
'raw_out' => (int)$currentOutOctets,
|
|
];
|
|
}
|
|
}
|