0votos

Diseñar aplicacion con funciones - python en Haskell

por josejuan hace 2 años

En un vetusto ordenador se pueden procesar millones de filas de registro en unos pocos segundos.

Ficheros Logs. Disponemos de un fichero de visitas de un servidor apache access.log, ya filtrado, que está formado por los campos siguientes: dirección IP o dominio, fecha, hora, zona horaria, petición de recurso, código de estado y tamaño en bytes de información descargada. Los campos están separados por el símbolo ; y cada registro está en una línea diferente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
{-# LANGUAGE TupleSections, OverloadedStrings, TemplateHaskell #-} 
import Control.Lens 
import Control.Monad 
import Control.Monad.IO.Class 
import Control.Monad.Trans.Class 
import Control.Monad.Trans.State 
import Data.Conduit 
import System.Environment 
import System.IO 
import qualified Data.Conduit.Binary      as CB 
import qualified Data.Conduit.List        as CL 
import qualified Data.Conduit.Text        as CT 
import qualified Data.Map as Map 
import qualified Data.Set as Set 
import qualified Data.Text as Text 
 
-- La información relevante es la IP, la fecha, la hora y la transferencia 
data Row = Row { _ip :: 𝐓.𝐓, _dia :: 𝐓.𝐓, _hora :: !Int, _size :: !Int } deriving Show 
makeLenses ''Row 
 
-- Si puede parsea una línea de log 
parseRow ∷ 𝐓.𝐓 β†’ 𝐌 Row 
parseRow r = case 𝐓.split (' '≑) r of 
               (ip:_:_:fecha:_:_:_:_:_:size:_) β†’ (Ξ»(d, h) β†’ Row ip d h) β†₯ parseDiaHora fecha βŠ› parseNumbero size 
               _                               β†’ 𝑁 
 
-- Si puede parsea la fecha y hora 
parseDiaHora ∷ 𝐓.𝐓 β†’ 𝐌 (𝐓.𝐓, Int) 
parseDiaHora r = case 𝐓.split (':'≑) r of 
                  (f:h:_) β†’ (𝐓.tail f,) β†₯ parseNumbero h 
                  _       β†’ 𝑁 
 
-- Si puede parsea un número 
parseNumbero ∷ 𝐓.𝐓 β†’ 𝐌 Int 
parseNumbero r = case (reads ∘ 𝐓.unpack) r of 
                   [(n, "")] β†’ 𝐽 n 
                   _         β†’ 𝑁 
 
-- Acumularemos todas las IP (si son muuuuchas puede usarse un algoritmo aproximado), accesos por horas y conteos 
data Acc = Acc { _ips :: Set.Set 𝐓.𝐓, _aph :: Map.Map Int Int, _sz :: !β„€, _bad :: !Int, _rn :: !Int } deriving Show 
makeLenses ''Acc 
 
-- Acumulamos para cada línea de registro 
acc ∷ (𝐓.𝐓 β†’ 𝔹) β†’ (𝐓.𝐓 β†’ 𝔹) β†’ Conduit 𝐓.𝐓 (StateT Acc IO) () 
acc fip ffe = awaitForever $ Ξ»r β†’ lift $ do 
  s ← get 
  when (s^.rn `mod` 1000 ≑ 0) $ io $ putStr (show (s^.rn) β§Ί "\r") » hFlush stdout 
  rn += 1 
  case parseRow r of 
    𝑁   β†’ bad += 1 
    𝐽 r β†’ when (r^.ip.to fip ∧ r^.dia.to ffe) $ do 
            ips .= Set.insert (r^.ip) (s^.ips) 
            aph .= Map.insertWith (+) (r^.hora) 1 (s^.aph) 
            sz  += fromIntegral (r^.size) 
 
-- Procesamos entrada estandar permitiendo filtrar 
main = do 
  hSetBuffering stdout LineBuffering 
 
  -- Posibles filtros a aplicar 
  args ← getArgs 
  let f "" = const 𝑇 
      f xs = (≑) (𝐓.pack xs) 
      (fi, ff) = case args of { [a, b] β†’ (f a , f b ); _ β†’ (f "", f "") } 
 
  -- Procesamos toda la entrada estandar 
  s ← execStateT (CB.sourceHandle stdin $$ CT.decode CT.utf8 =$ CT.lines =$ acc fi ff =$ CL.consume) (Acc Set.βˆ… Map.βˆ… 0 0 0) 
 
  -- Mostramos estadísticas 
  putStrLn $ show (s^.rn) β§Ί " filas totales procesadas" 
  putStrLn $ show (s^.bad) β§Ί " filas que no se han podido leer" 
  putStrLn $ show (Set.size (s^.ips)) β§Ί " ips únicas" 
  putStrLn $ show (s^.sz ÷ (1024 × 1024)) β§Ί " Mbytes totales enviados" 
  putStrLn "Accesos por franja horaria:" 
  ↱_(Ξ»(h, n) β†’ putStrLn $ show h β§Ί " - " β§Ί show n) $ Map.assocs $ s^.aph 
 
 
 
{- 
 
    Por ejemplo, en varios ficheros de log tomados por ahí y juntándolos para formar 
    un único log de 108Mbytes con 475.283 filas, le toma, a un vetusto ordenador (más de 10 años) 
    14 segundos procesar y generar las estadísticas: 
     
$ time ./access < /home/josejuan/tmp/access.log 
475283 filas totales procesadas 
2950 filas que no se han podido leer 
29828 ips únicas 
124590 Mbytes totales enviados 
Accesos por franja horaria: 
0 - 12964 
1 - 13677 
2 - 10097 
3 - 11205 
4 - 11168 
5 - 12568 
6 - 14084 
7 - 17141 
8 - 18503 
9 - 18512 
10 - 20010 
11 - 17971 
12 - 26376 
13 - 26306 
14 - 26415 
15 - 27461 
16 - 24644 
17 - 27499 
18 - 24557 
19 - 24880 
20 - 25496 
21 - 24290 
22 - 21991 
23 - 14518 
 
real    0m14.261s 
user    0m13.520s 
sys     0m0.700s 
 
 
 
Para filtrar por IP: 
 
$ ./access 111.22.33.444 "" < /home/josejuan/tmp/access.log 
 
Para filtrar por fecha: 
 
$ ./access "" "27/Aug/2014" < /home/josejuan/tmp/access.log 
 
Para filtrar por ambos: 
 
$ ./access 111.22.33.444 "27/Aug/2014" < /home/josejuan/tmp/access.log 
 
-} 

Comenta la soluciΓ³n

Tienes que identificarte para poder publicar tu comentario.