2019년 4월 28일 일요일

OEE (설비 종합 효율)


OEE = Overall equipment effectiveness


OEE는 제조 공정이 얼마나 효율적으로 수행되는지를 평가하기 위한 지표다. OEE 수준은 산업 별로 다르게 나타날 수 있으므로 절대적인 척도가 아니며 스스로의 과거 수준과 현 수준을 파악하는데 더 유용하다. OEE의 계산은 어렵지 않지만 metric에 대한 표준에 대해 주의를 기울일 필요가 있다.

OEE의 기본 측정 항목
- Availability : 계획된 생산 시간 대비 작업 수행 시간. (Uptime)
- Performance : 설계된 생산 속도 대비 실제 속도
- Quality : 양품율. First Pass Yield.

* 참고로 OEE에 Loading을 곱하면 TEEP(Total effective equipment performance)가 된다.
Loading = Scheduled Time / Calendar Time. (7일 중 5일을 일한다면. Loading은 5/7 = 71.4%)

OEE 식.
OEE = Availability * Performance * Quality
* 예시: (OEE=73.6%) = (Availability= 86.6%)*(Performance=93%)*(Quality=91.3%)
* Availability = Operating time / Scheduled time. (* 점심 시간과 같은 계획된 정지 시간은 빼야 한다.)
* Performance = (Parts produced * Ideal cycle time) / Operating time
* Quality = (Units produced - defective units) / (Units produced)

아니면 단순하게 이런 식의 계산도 가능하다.
1. 28,800초 동안 14,400개 생산. → 하나 만드는데 2초.
2. 최적 싸이클 타임이 1.5초인데 이 경우 14,400개 만드는덴 21,600초만 필요함. (7,200초의 Loss가 있었음.)
3. OEE = 21,600 / 28,800 = 75%

3개의 metric을 단순하게 곱해버리기 때문에 하나만 깨져도 결과가 좋지 않게 나오는 것을 볼 수 있다. 따라서 모델이 해당 산업군에 적합한지 부터 따져볼 필요가 있겠다.

-

* 조업시간 = 생산을 해야 되는 시간 (휴무 제외)
* 부하시간 = 설비를 가동해야 하는 시간 (계획가동정지 제외)
* 가동시간 = 설비가 실제 가동한 시간 (정지로스 제외)
* 정미가동시간 = 제품을 만든 시간
* 가치가동시간 = 양품을 만든 시간

2019년 4월 17일 수요일

AJAX와 CORS

AJAX (Asynchronous JavaScript And XML)


AJAX는 서버와 통신하기 위해 XMLHttpRequest 객체를 사용하는 것을 의미한다. 이 객체를 이용해 XML, JSON, HTML 그리고 일반 텍스트 형식 등을 포함한 다양한 포맷을 주고 받을 수 있다. 페이지 전체를 리프레쉬하지 않고서도 수행되는 "비동기성"은 AJAX의 가장 큰 특징으로 페이지의 일부분만을 업데이트 할 수 있게 해준다.

AJAX와 함께 자주 거론되는 개념이 있는데 그건 바로 CORS다. CORS의 도입 배경은 다음과 같다. HTTP 요청은 기본적으로 A 도메인에서 B 도메인으로 향하는 cross-site HTTP 요청이 가능하지만 <script></script>로 둘러싸인 스크립트에서 생성된 cross-site HTTP 요청은 보안상 이유로 호출이 제한된다. XMLHttpRequest도 same-origin 정책을 따르는데 XMLHttpRequest에 대해서도 cross-site HTTP 요청이 가능해야 한다는 요구가 생겨 W3C에 의해 CORS 권고안이 나오게 되었다.



CORS (Cross-Origin Resource Sharing)


CORS는 origin 도메인에서 실행중인 웹 애플리케이션이 추가 HTTP 헤더를 사용해 브라우저가 다른 출처 서버의 자원에 액세스할 수 있는 권한을 가지도록 하는 메커니즘이다.
→ CORS를 쓰면 AJAX로도 다른 도메인의 자원을 쓸 수 있다.

1. CORS 스펙에선 서버 데이터에 side-effects를 일으킬 수 있는 요청 메서드(특정 MIME 유형의 POST 등)에 대해 브라우저가 요청할 내용을 OPTIONS 메서드를 이용해 사전 전달(preflight) 하도록 하는데, 이는 서버로부터 지원 중인 메서드(GET, POST, ...) 목록을 내려 받은 뒤 승인(approval) 시에 실제 HTTP 요청을 전송하는 것을 말한다.
→ 사용자가 요청 프로세스를 직접 다루는 것이 아니라 브라우저가 알아서 처리한다.
2. 서버는 클라이언트에게 요청과 함께 자격 증명(credential)을 보내야하는지 여부를 알릴 수도 있다.


AJAX 요청의 흐름.

기본적으로 비동기이므로 요청 전에 서버로 보낸 요청에 대한 응답을 받았을 때 처리할 함수를 미리 지정해주어야 한다.
onreadystatechange property에 지정하면 된다.
그리고 나서 open()과 send()를 차례대로 요청하면 된다.


httpRequest.onreadystatechange = function () {
  // ...
};

// 쓸 일은 없겠으나 open의 셋째 파라미터를 false로 지정하면 동기적으로 동작한다.
// 이 경우엔 onreadystatechange는 필요하지 않다.
httpRequest.open('GET', 'http://www.example.org/some.file', true); 

// send()의 파라미터가 null인 까닭은 'GET' 방식의 호출이기 때문이다. 'POST'인 경우엔 서버로 보낼 데이터를 send() 파라미터에 지정하면 된다.
// 다만 서버가 데이터의 형식을 알 필요가 있기 때문에 request에 MIME type을 먼저 설정해야 한다.
httpRequest.send(null);
httpRequest.setRequestHEader('Content-Type', 'application/x-www-form-urlencoded');

위와 같이 서버 응답에 대한 처리는 onreadystatechange에 지정한 함수에서 이루어진다.
일반적으로 httpRequest.readyState의 값이 XMLHttpRequest.DONE(4) 이면서 httpRequest.status의 값이 200 OK인 경우 호출-응답의 프로세스가 성공한 것이다.

받은 데이터는 responseText 또는 responseXML property로 읽어오면 된다. responseXML의 경우 서버의 응답을 XMLDocument 객체로 반환하며 DOM API를 통해 이 객체를 다룰 수 있을 것이다.

기본적인 예제는 아래를 참고하자.

<button id="ajaxButton" type="button">Make a request</button>

<script>
(function() {
  var httpRequest; // httpRequest 변수는 지역 변수로 선언해야 한다. 
                   // 전역으로 사용되면 여러 요청에 의한 경쟁 상태(race condition)이 발생할 수 있다.
  document.getElementById("ajaxButton").addEventListener('click', makeRequest);

  function makeRequest() {
    httpRequest = new XMLHttpRequest();

    if(!httpRequest) {
      // Can not create instance.
      return false;
    }
    httpRequest.onreadystatechange = alertContents;
    httpRequest.open('GET', 'test.html');
    httpRequest.send();
  }

  function alertContents() {
    if (httpRequest.readyState === XMLHttpRequest.DONE) {
      if (httpRequest.status === 200) {
        alert(httpRequest.responseText);
      } else {
        // Error
      }
    }
  }
})();
</script>

https://developer.mozilla.org/ko/docs/Web/Guide/AJAX/Getting_Started
https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS#%EA%B0%9C%EC%9A%94

2019년 4월 15일 월요일

WPF - Binding Proxy

WPF에서 DataContext 속성은 할당된 요소의 자식에게 자동으로 상속되므로 편리하다. 따라서 바인딩하려는 각 요소에 대해 다시 DataContext를 지정하지 않아도 된다.

그러나 WPF에서 바인딩 대상이 Target Object와 다르거나 Visual Tree가 다른 경우 참조식을 작성할 때 불필요하게 식이 길어지는 경우가 종종 있다. (DataGrid, ItemsControl 내 엘리먼트가 ViewModel의 프로퍼티를 참조하는 경우)

이런 상황에 놓이면 사용자가 해당 요소에 DataContext를 직접 알려주어야 한다.
이러한 경우 Binding Proxy를 만들어 이용하면 코드도 간결해지고 표현도 명확해진다.

public class BindingProxy : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy));
}

Freezable을 구현하는 까닭은 WPF에서 Freezable 객체는 Visual Tree 또는 Logical Tree가 다른 경우에도 DataContext를 상속할 수 있기 때문이다. 사용법은 아래와 같은데, proxy 객체의 Data attached 프로퍼티에 바인딩 된 뷰모델을 바로 참조할 수 있다. (물론 단순한 경우엔 x:Name ~ x:Reference, RelativeSource ~ FindAncestor가 나을지도 모르겠다.)


    <DataGrid>
        <DataGrid.Resources>
            <local:BindingProxy x:Key="proxy" Data="{Binding}"/>
        </DataGrid.Resources>
        <DataGrid.Columns>
            <DataGridTextColumn Visibility="{Binding Data.MyColumnVisibility,
                                                Source={StaticResource proxy}}"/>
        </DataGrid.Columns>
    </DataGrid>


https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

2019년 4월 10일 수요일

WPF의 Dependency Property와 Attached Property.

Dependency Properties와 Attatched Properties는 WPF가 제공하는 UI 시스템이 효율적으로 운영되게 하는 장치라 봐야겠다.


Dependency Properties


DP는 일반적인 프로퍼티와 다르게 인스턴스의 값이 인스턴스에 저장되는 것이 아니고, DependencyObject에 사전(dictionary) 형태로 기록된다. 값을 읽을 때에도 DependencyObject의 GetValue() 메서드를 통한다.

DependencyObject는 특정 UI Control에 대한 기본 속성 값을 가지고 있는다. DependencyObject는 어떤 사용자가 Button을 100개 선언한다고 하여도 Button 100개에 대한 속성 각각을 따로 따로 메모리에 기록하지 않고, 기본 속성 값에서 변경되는 값만 따로 기록하는 방식을 채택해 메모리 낭비를 줄인다. 또한 Value inheritance를 지원해 선언되지 않은 속성 값은 Visual Tree 상 부모의 값을 따르게 하며 PropertyMetadata를 통해 값의 변경, 밸리데이션에 대한 통지를 해준다.

등록하는 DP 이름의 끝에 Property가 붙는 것은 관례다.


// Dependency Property
public static readonly DependencyProperty CurrentTimeProperty = 
     DependencyProperty.Register( "CurrentTime", typeof(DateTime),
     typeof(MyClockControl), new FrameworkPropertyMetadata(DateTime.Now));
 
// .NET Property wrapper
public DateTime CurrentTime
{
    get { return (DateTime)GetValue(CurrentTimeProperty); }
    set { SetValue(CurrentTimeProperty, value); }
}

new FrameworkPropertyMetadata( DateTime.Now, 
                       OnCurrentTimePropertyChanged,  // Value Changed Callback
                       OnCoerceCurrentTimeProperty ), // Coerce Value Callback (Range Check,...)
                       OnValidateCurrentTimeProperty ); // Validation Callback (Type Checking,...)



Attached Properties


Attached Properties는 DP의 특수한 형태로 프로퍼티가 선언된 곳과 다른 곳에서 쓰일 수 있도록 하는 장치다. DP 사례의 Register() 대신에 RegisterAttatched()에 의해 등록된다.

Attached Properties의 경우 예를 떠올리면 이해가 편하다.
Panel.ZIndex 또는 Grid.Row와 같은 것. 이런 프로퍼티는 Button이나 TextBox 같은 컨트롤에서 쓰일 수 있지만 실제 프로퍼티의 정의는 Panel이나 Grid에 되어 있다. 다른 인스턴스에 'attached'되는 것이다.

DP와 다르게 static 메서드 형태로 Set / Get을 정의한다. 당연한게 선언된 곳과 다른 인스턴스에서 호출되어야 하기 때문이다.


<Canvas>
    <Button Canvas.Top="20" Canvas.Left="20" Content="Click me!"/>
</Canvas>

public static readonly DependencyProperty TopProperty =
    DependencyProperty.RegisterAttached("Top", 
    typeof(double), typeof(Canvas),
    new FrameworkPropertyMetadata(0d,
        FrameworkPropertyMetadataOptions.Inherits));
 
public static void SetTop(UIElement element, double value)
{
    element.SetValue(TopProperty, value);
}
 
public static double GetTop(UIElement element)
{
    return (double)element.GetValue(TopProperty);
}


2019년 4월 2일 화요일

[Netty] 클라이언트 예제

Client 초기화시엔 read/write를 처리할 worker thread만 지정해 주면 된다. 빌더 패턴으로 부트스트래핑 하는데 인코더/디코더를 어떤걸 쓸건지, 어떤 옵션을 택할건지를 빼면 코드는 거의 비슷하다.

아래 예제에선 채널 파이프라인에 ObjectEncoderObjectDecoder를 지정해 주었다. ObjectEncoder는 Java 객체를 ByteBuf로 serialize 하는 역할을 수행하고, ObjectDecoder는 수신한 ByteBuf를 Java 객체로 deserialize 해준다.

싱글턴으로 XXXXClient를 구현하고 핸들러에 메시지를 전송할 수 있는 메서드를 열어두면 간단하게 소켓 클라이언트를 구현할 수 있다. Handler로 전달된 참조형 객체(ByteBuf)의 해제는 핸들러의 책임인 것으로 알고 있다. 읽는 쪽의 어딘가에서 ReferenceCountUtil.release() 해주어야 한다. 메시지를 쓰기 위해 필요한 ChannelHandlerContextchannelActive 시점에 셋 해주면 될 것이다.


public class XXXXClient {
    private EventLoopGroup workerGroup = new NioEventLoopGroup();
    private XXXXHandler handler = new XXXXHandler(...);

    // Singleton.
 
    public void start(...) {
        new Thread(() -> {
            try {
                Bootstrap b = new Bootstrap();
                ChannelFuture f = b.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(...)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(
                                new ObjectEncoder(),
                                new ObjectDecoder(ClassResolvers.cacheDisabled(null)),
                                handler);
                        }
                    })
                    .connect(ip, port).sync();
                f.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                // ...
            } finally {
                workerGroup.shutdownGracefully();
            }
        }).start();
    }

    public void stop() {
            workerGroup.shutdownGracefully();
    }

    public void sendMessage(Object msg) {
         handler.sendMessage(msg);
    }
}

public class XXXXHandler extends ChannelInboundHandlerAdapter {
    private ChannelHandlerContext context;

    public void channelActive(...) {
         super.channelActive(ctx);
         this.context = ctx;
    }

    public void channelRead(...) {
        if (msg instanceof YYYY) {
            ...
        }

        ReferenceCountUtil.release(msg);
    }

    public void channelReadComplete(...) {
        ctx.flush();
    }

    public void sendMessage(Object msg) {
        if (context != null && context.channel().isWritable()) {
            context.writeAndFlush(msg);
        } 
    }
}

동작은 하는데 이렇게 하는게 맞는진 잘 모르겠다. :(

이벤트-드리븐 아키텍쳐 (Event-Driven Architecture)

TCP 연결을 처리하는 방식에서 비교되는 개념으로 Thread-Based Architecture와 Event-Driven Architecture가 있다. 이 글에선 Event-Driven Architecture에 대해서 정리하고자 한다.

이벤트-드리븐 아키텍쳐의 구동방식은 일반적으로 아래의 두 가지 형태로 구분된다.


1. 이벤트 리스너 방식.
  대상 객체의 특정 이벤트 리스너에 이벤트 처리 로직을 가진 이벤트 메서드를 등록하는 방식. 객체의 이벤트가 발생할 때 이벤트 처리 스레드에서 등록된 메서드를 수행한다. 등록된 메서드가 없으면 이벤트는 씹힌다. 대부분의 UI 프레임워크가 쓰는 방식이다.


2. 이벤트 큐를 이용하는 방식.
  이벤트 발생 객체(Event Emitter)는 이벤트를 큐에 쌓고, 이벤트를 처리하는 객체(Event Loop)는 큐에 입력된 이벤트가 있을 때 이벤트를 꺼내 이벤트를 실행하는 방식. 위의 방법보다 일반적이나 구현 난이도는 높다.

  이 방법에선 구현 목적에 따라 이벤트 처리 스레드를 하나로 둘 수도 있고, 여러 개로 둘 수도 있다. trade-off가 있다.

  2.1 싱글 스레드 기반 : 이벤트를 순서대로 처리할 수 있다. 다중 코어 CPU의 자원을 효과적으로 사용할 수 없다.
  2.2 멀티 스레드 기반 : 다중 코어 CPU 자원을 효과적으로 사용할 수 있다. 이벤트 처리 순서를 보장할 수 없다. 이벤트 큐에 대한 스레드 경합이 발생한다.

→ Netty 같은 I/O 프레임워크에선 채널(하나의 연결) 별로 전용 이벤트 큐를 할당시키는 방식을 채택한다. 전체 메시지의 처리 순서를 보장할 순 없어도 특정 채널에서 발생하는 메시지의 처리 순서는 보장해준다.
(채널과 큐의 관계는 1:1은 아니고, N:1의 관계.)

두 번째 방식을 좀더 구체화한 아키텍쳐 패턴으로 Reactor Pattern이 있다.

Reactor Pattern은 이벤트에 반응하는 객체(Reactor)가 이벤트가 발생할 때 Event Handler에게 전달해주고, Event Handler는 알맞은 method를 사용해 처리하는 구조를 갖는다.

Reactor가 계속해서 이벤트를 Dispatching 할 수 있게 해줘야 하므로 Event Handler는 새로운 스레드(스레드 풀)에서 실행되는 것이 일반적이다.

Reactor Pattern은 아래 링크에서 친절하게 설명되어 있다.
http://jeewanthad.blogspot.com/2013/02/reactor-pattern-explained-part-1.html

The beauty of non blocking architecture is that we can write the server to run in a single Thread while catering all the requests from clients. Just forget about the Thread pool for a while. Naturally when concurrency is not used to design a server it should obviously be less responsive to events. In this scenario when the system runs in a single Thread the Reactor will not respond to other events until the Handler to which the event is dispatched is done with the event. Why? Because we are using one Thread to handle all the events. We naturally have to go one by one. 
We can add concurrency to our design to make the system more responsive and faster. When the Reactor dispatches the event to a Handler, it can start the Handler in a new Thread so that the Reactor can happily continue to deal with other events. This will always be a better design when performance is concerned. To limit the number of Threads in the system and to make things more organized, a Thread pool can be used.