hp2pretty 0.7 → 0.8
raw patch · 12 files changed
+299/−61 lines, 12 filesdep +optparse-applicativedep ~base
Dependencies added: optparse-applicative
Dependency ranges changed: base
Files
- NEWS +10/−0
- README +26/−2
- THANKS +1/−0
- hp2pretty.cabal +32/−13
- src/Args.hs +75/−0
- src/Graphics.hs +10/−2
- src/Main.hs +25/−17
- src/Pattern.hs +44/−0
- src/Print.hs +12/−7
- src/Prune.hs +32/−8
- src/SVG.hs +23/−6
- src/Total.hs +9/−6
NEWS view
@@ -1,3 +1,13 @@+v0.8 2017-06-29 patterns++New flags and features:+ --sort=size|stddev|name (order bands by this property)+ --reverse (reverse sort order)+ --trace=1.0 (trace elements threshold percentage)+ --bands=15 (limit number of bands)+ --pattern (pattern fill, better for printing)++ v0.7 2017-06-27 bugfix A bugfix in header parsing (thanks to Pepe Iborra). An
README view
@@ -6,8 +6,32 @@ the aims of being maintainable, with more flexible output, and more beautiful output. Currently hp2pretty outputs Scalable Vector Graphics (SVG) only, though PostScript (PS) is planned.-Also none (count'em) of hp2ps' options are implemented yet in-hp2pretty.+Not all of hp2ps' options are implemented yet in hp2pretty.+++Usage+=====++ hp2pretty - generate pretty graphs from heap profiles+ + Usage: hp2pretty [--uniform-scale AXES] [--sort FIELD] [--reverse]+ [--trace PERCENT] [--bands COUNT] [--pattern] FILES...+ Convert heap profile FILES.hp to pretty graphs FILES.svg+ + Available options:+ --uniform-scale AXES Whether to use a uniform scale for all outputs. One+ of: none (default), time, memory, both.+ --sort FIELD How to sort the bands. One of: size (default),+ stddev, name.+ --reverse Reverse the order of bands.+ --trace PERCENT Percentage of trace elements to+ combine. (default: 1.0)+ --bands COUNT Maximum number of bands to draw (0 for+ unlimited). (default: 15)+ --pattern Use patterns instead of solid colours to fill bands.+ FILES... Heap profiles (FILE.hp will be converted to+ FILE.svg).+ -h,--help Show this help text Benchmarks
THANKS view
@@ -1,2 +1,3 @@ Ian Lynagh (label text XML escaping bugfix patch) Edward Z. Yang (feature requests for stable colours and uniform scales)+Pepe Iborra (parsing bugfix)
hp2pretty.cabal view
@@ -1,13 +1,25 @@ Name: hp2pretty-Version: 0.7+Version: 0.8 Synopsis: generate pretty graphs from heap profiles Description: hp2pretty is a rewrite of hp2ps, implemented in Haskell, with the aims of being maintainable, with more flexible output, and more beautiful output. Currently hp2pretty outputs Scalable Vector Graphics (SVG) only, though PostScript (PS) is planned.- Also none (count'em) of hp2ps' options are implemented yet in- hp2pretty.+ Not all of hp2ps' options are implemented yet in hp2pretty. .+ In hp2pretty-0.8 output filtering and sorting flags are added,+ as well as low-ink pattern fills for printing:+ .+ > hp2pretty --trace=1 *.hp+ > hp2pretty --bands=15 *.hp+ > hp2pretty --sort=size *.hp+ > hp2pretty --sort=stddev *.hp+ > hp2pretty --sort=name *.hp+ > hp2pretty --reverse *.hp+ > hp2pretty --pattern *.hp+ .+ In hp2pretty-0.7 a parsing bug is fixed.+ . In hp2pretty-0.6 ByteString is replaced by Text, fixing bugs with Unicode. .@@ -16,13 +28,10 @@ . In hp2pretty-0.4 usage changed since the previous release: .- hp2pretty *.hp- .- hp2pretty --uniform-scale=time *.hp- .- hp2pretty --uniform-scale=memory *.hp- .- hp2pretty --uniform-scale=both *.hp+ > hp2pretty *.hp+ > hp2pretty --uniform-scale=time *.hp+ > hp2pretty --uniform-scale=memory *.hp+ > hp2pretty --uniform-scale=both *.hp . Colours also changed: now they are based on a hash of the cost label, which should make colours have stable semantics@@ -40,11 +49,20 @@ Cabal-version: >=1.6 Executable hp2pretty- Build-depends: base >= 4 && < 5, array, attoparsec, containers, filepath, floatshow, mtl, text+ Build-depends: base >= 4 && < 5,+ array,+ attoparsec,+ containers,+ filepath,+ floatshow,+ mtl,+ optparse-applicative,+ text GHC-options: -Wall -rtsopts HS-source-dirs: src Main-is: Main.hs- Other-modules: Types+ Other-modules: Args+ Types Total Prune Bands@@ -52,6 +70,7 @@ Print SVG Graphics+ Pattern Source-repository head type: git@@ -60,4 +79,4 @@ Source-repository this type: git location: https://code.mathr.co.uk/hp2pretty.git- tag: v0.7+ tag: v0.8
+ src/Args.hs view
@@ -0,0 +1,75 @@+module Args (args, Args(..), Uniform(..), Sort(..)) where++import Options.Applicative+import Data.Semigroup ((<>))++data Uniform = None | Time | Memory | Both deriving (Eq)+data Sort = Size | StdDev | Name++data Args = Args+ { uniformity :: Uniform+ , sorting :: Sort+ , reversing :: Bool+ , tracePercent :: Double+ , nBands :: Int+ , patterned :: Bool+ , files :: [String]+ }++argParser :: Parser Args+argParser = Args+ <$> option parseUniform+ ( long "uniform-scale"+ <> help "Whether to use a uniform scale for all outputs. One of: none (default), time, memory, both."+ <> value None+ <> metavar "AXES" )+ <*> option parseSort+ ( long "sort"+ <> help "How to sort the bands. One of: size (default), stddev, name."+ <> value Size+ <> metavar "FIELD" )+ <*> switch+ ( long "reverse"+ <> help "Reverse the order of bands." )+ <*> option auto+ ( long "trace"+ <> help "Percentage of trace elements to combine."+ <> value 1+ <> showDefault+ <> metavar "PERCENT" )+ <*> option auto+ ( long "bands"+ <> help "Maximum number of bands to draw (0 for unlimited)."+ <> value 15+ <> showDefault+ <> metavar "COUNT" )+ <*> switch+ ( long "pattern"+ <> help "Use patterns instead of solid colours to fill bands." )+ <*> some (argument str+ ( help "Heap profiles (FILE.hp will be converted to FILE.svg)."+ <> metavar "FILES..." ))+ +parseUniform :: ReadM Uniform+parseUniform = eitherReader $ \s -> case s of+ "none" -> Right None+ "time" -> Right Time+ "memory" -> Right Memory+ "both" -> Right Both+ _ -> Left "expected one of: none, time, memory, both"+ +parseSort :: ReadM Sort+parseSort = eitherReader $ \s -> case s of+ "size" -> Right Size+ "stddev" -> Right StdDev+ "name" -> Right Name+ _ -> Left "expected one of: size, stddev, name"+++args :: IO Args+args = execParser opts+ where+ opts = info (argParser <**> helper)+ ( fullDesc+ <> progDesc "Convert heap profile FILES.hp to pretty graphs FILES.svg"+ <> header "hp2pretty - generate pretty graphs from heap profiles" )
src/Graphics.hs view
@@ -5,6 +5,8 @@ import Data.Bits (shiftR) import Data.Int (Int32, Int64) +import Pattern (patterns)+ type Point = (Double, Double) type Size = (Double, Double) type Angle = Double@@ -16,14 +18,17 @@ type Opacity = Double data RGB = RGB Double Double Double +type PatternID = Text+ data Graphics = Graphics { text :: Maybe Angle -> Anchor -> FontSize -> Point -> [Text] -> [Text] , rect :: Point -> Size -> [Text] , line :: Point -> Point -> [Text] , polygon :: [Point] -> [Text]- , visual :: Maybe RGB -> Maybe Opacity -> Maybe RGB -> Maybe StrokeWidth -> [Text] -> [Text]- , document :: Size -> [Text] -> [Text]+ , pattern :: Text -> (PatternID, [Text])+ , visual :: Maybe (Either PatternID RGB) -> Maybe Opacity -> Maybe RGB -> Maybe StrokeWidth -> [Text] -> [Text]+ , document :: Size -> [Text] -> [Text] -> [Text] } rescalePoint :: (Point,Point) -> (Point,Point) -> Point -> Point@@ -60,6 +65,9 @@ 3 -> RGB p q v 4 -> RGB t p v _ -> RGB v p q++patternIx :: Text -> Int+patternIx s = fromIntegral (hashText 0x54321 s * 555) `mod` length patterns -- hashing functions copied and modified from BSD-style LICENSE'd -- base-4.3.1.0:Data.HashTable (c) The University of Glasgow 2003
src/Main.hs view
@@ -5,14 +5,16 @@ import Data.Text.IO (readFile, hPutStr) import Control.Monad (forM_, when) import Data.List (foldl1', isPrefixOf, partition)+import Data.Tuple (swap) import Data.Monoid (Last(..), mconcat) import System.Environment (getArgs) import System.Exit (exitSuccess) import System.FilePath (replaceExtension) import System.IO (withFile, IOMode(WriteMode)) +import Args (args, Args(..), Uniform(..), Sort(..)) import Total (total)-import Prune (prune)+import Prune (prune, cmpName, cmpSize, cmpStdDev) import Bands (bands) import Pretty (pretty) import Print (print)@@ -21,35 +23,41 @@ main :: IO () main = do- args <- getArgs- let (flags, files) = partition (isPrefixOf "--") args- uniformity "--uniform-scale=none" = Last (Just (False, False))- uniformity "--uniform-scale=time" = Last (Just (True, False))- uniformity "--uniform-scale=memory" = Last (Just (False, True))- uniformity "--uniform-scale=both" = Last (Just (True, True))- uniformity _ = Last Nothing- Last (Just (uniformTime, uniformMemory)) = mconcat (Last (Just (False, False)) : map uniformity flags)- when (null files) exitSuccess+ a <- args+ let uniformTime = uniformity a `elem` [Time, Both]+ uniformMemory = uniformity a `elem` [Memory, Both]+ sorting' = case sorting a of+ Name -> cmpName+ Size -> cmpSize+ StdDev -> cmpStdDev+ reversing' = if reversing a then swap else id+ cmp = fst $ reversing' sorting'+ when (null (files a)) exitSuccess if not (uniformTime || uniformMemory)- then forM_ files $ \file -> do+ then forM_ (files a) $ \file -> do input <- readFile file let (header, totals) = total input- keeps = prune totals+ keeps = prune cmp (tracePercent a) (bound $ nBands a) totals (times, vals) = bands header keeps input ((sticks, vticks), (labels, coords)) = pretty header vals keeps- outputs = print svg header sticks vticks labels times coords+ outputs = print svg (patterned a) header sticks vticks labels times coords withFile (replaceExtension file "svg") WriteMode $ \h -> mapM_ (hPutStr h) outputs else do- inputs <- mapM readFile files+ inputs <- mapM readFile (files a) let hts0 = map total inputs (smima, vmima) = foldl1' (\((!smi, !sma), (!vmi, !vma)) ((!smi', !sma'), (!vmi', !vma')) -> ((smi`min`smi', sma`max`sma'), (vmi`min`vmi', vma`max`vma'))) . map (\(h, _) -> (hSampleRange h, hValueRange h)) $ hts0 hts1 | uniformTime = map (\(h, t) -> (h{ hSampleRange = smima }, t)) hts0 | otherwise = hts0 hts | uniformMemory = map (\(h, t) -> (h{ hValueRange = vmima }, t)) hts1 | otherwise = hts1- forM_ (zip3 files inputs hts) $ \(file, input, (header, totals)) -> do- let keeps = prune totals+ forM_ (zip3 (files a) inputs hts) $ \(file, input, (header, totals)) -> do+ let keeps = prune cmp (tracePercent a) (bound $ nBands a) totals (times, vals) = bands header keeps input ((sticks, vticks), (labels, coords)) = pretty header vals keeps- outputs = print svg header sticks vticks labels times coords+ outputs = print svg (patterned a) header sticks vticks labels times coords withFile (replaceExtension file "svg") WriteMode $ \h -> mapM_ (hPutStr h) outputs++bound :: Int -> Int+bound n+ | n <= 0 = maxBound+ | otherwise = n
+ src/Pattern.hs view
@@ -0,0 +1,44 @@+module Pattern (patterns, Pattern) where++import Data.Tuple (swap)++type Point = (Double, Double)++type Pattern = [(Point, Point)]++loop :: [Point] -> [(Point, Point)]+loop ps = zip ps (tail (cycle ps))++patterns :: [Pattern]+patterns =+ -- short and long dashes in 4 directions+ [ [ ((5, 8), (11, 8)) ]+ , [ ((3, 8), (13, 8)) ]+ , [ ((8, 5), (8, 11)) ]+ , [ ((8, 3), (8, 13)) ]+ , [ ((5, 5), (11, 11)) ]+ , [ ((3, 3), (13, 13)) ]+ , [ ((5, 11), (11, 5)) ]+ , [ ((3, 13), (13, 3)) ]+ -- small and large crosses in 2 directions+ , [ ((5, 8), (11, 8)), ((8, 5), (8, 11)) ]+ , [ ((3, 8), (13, 8)), ((8, 3), (8, 13)) ]+ , [ ((5, 5), (11, 11)), ((5, 11), (11, 5)) ]+ , [ ((3, 3), (13, 13)), ((3, 13), (13, 3)) ]+ , [ ((3, 8), (13, 8)), ((8, 3), (8, 13)), ((3, 3), (13, 13)), ((3, 13), (13, 3)) ]+ , [ ((5, 8), (11, 8)), ((8, 5), (8, 11)), ((5, 5), (11, 11)), ((5, 11), (11, 5)) ]+ -- small and large triangles in 4 directions+ , loop [ (5, 5), (11, 5), (8, 11) ]+ , loop [ (3, 3), (13, 3), (8, 13) ]+ , loop [ (5, 11), (11, 11), (8, 5) ]+ , loop [ (3, 13), (13, 13), (8, 3) ]+ , loop (map swap [ (5, 5), (11, 5), (8, 11) ])+ , loop (map swap [ (3, 3), (13, 3), (8, 13) ])+ , loop (map swap [ (5, 11), (11, 11), (8, 5) ])+ , loop (map swap [ (3, 13), (13, 13), (8, 3) ])+ -- small and large squares in 2 directions+ , loop [ (5, 5), (11, 5), (11, 11), (5, 11) ]+ , loop [ (3, 3), (13, 3), (13, 13), (3, 13) ]+ , loop [ (5, 8), (8, 5), (11, 8), (8, 11) ]+ , loop [ (3, 8), (8, 3), (13, 8), (8, 13) ]+ ]
src/Print.hs view
@@ -8,12 +8,17 @@ import Types import Graphics -print :: Graphics -> Header -> [Double] -> [Double] -> [Text] -> UArray Int Double -> UArray (Int, Int) Double -> [Text]-print gfx header sticks vticks labels times coords =+first :: (a -> c) -> (a, b) -> (c, b)+first f (a, b) = (f a, b)++print :: Graphics -> Bool -> Header -> [Double] -> [Double] -> [Text] -> UArray Int Double -> UArray (Int, Int) Double -> [Text]+print gfx patterned header sticks vticks labels times coords = let bands = toPoints (bounds coords) times coords filled c = visual gfx (Just c) Nothing Nothing Nothing labels' = reverse labels- colours = map colour labels'+ (colours, defs)+ | patterned = first (map Left) . unzip $ map (pattern gfx) labels'+ | otherwise = (map (Right . colour) labels', []) polygons = concat . zipWith (\c ps -> filled c (polygon gfx ps)) colours . map (map p) $ bands key = concat . zipWith3 (keyBox (gW + border * 2.5) (border * 1.5) (gH / 16)) [(0::Int) ..] colours $ labels' keyBox x y0 dy i c l =@@ -31,8 +36,8 @@ gRange@((gx0,gy0),(gx1,gy1)) = ((border*1.5, gH + border*1.5), (gW + border*1.5, border*1.5)) p = rescalePoint ((xMin, yMin), (xMax, yMax)) gRange title = text gfx Nothing Middle 25 (w / 2, border * 0.75) [hJob header, pack " (", hDate header, pack ")"]- background = filled white $ rect gfx (0,0) (w,h)- box = filled white $ rect gfx (gx0,gy1) (gW,gH)+ background = filled (Right white) $ rect gfx (0,0) (w,h)+ box = filled (Right white) $ rect gfx (gx0,gy1) (gW,gH) leftLabel = text gfx (Just (-90)) Middle 20 (border/2, (gy0 + gy1)/2) [hValueUnit header] leftTicks = concatMap (\(y,l) -> let { (x1, y1) = p (xMin, y) ; (x2, y2) = p (xMax, y) } in line gfx (x1 - border/2, y1) (x2, y2) ++@@ -43,9 +48,9 @@ line gfx (x1, y1 + border/2) (x2, y2) ++ if l then [] else text gfx Nothing Start 15 (x1 + textOffset, y1+2*textOffset) (showSI x) ) (zip sticks (replicate (length sticks - 1) False ++ [True]))- in document gfx (w,h) . concat $+ in document gfx (w,h) (concat defs) . concat $ [ background- , visual gfx (Just black) Nothing (Just black) (Just 1) $ concat+ , visual gfx (Just (Right black)) Nothing (Just black) (Just 1) $ concat [ title , leftLabel , bottomLabel
src/Prune.hs view
@@ -1,19 +1,43 @@-module Prune (prune) where+module Prune+ ( prune+ , Compare+ , cmpName+ , cmpSize+ , cmpStdDev+ ) where import Data.Text (Text) import Data.List (foldl', sortBy) import Data.Ord (comparing) import Data.Map (Map, toList, fromList) -prune :: Map Text Double -> Map Text Int-prune ts =- let ccTotals = sortBy (flip $ comparing snd) (toList ts)- sizes = map snd ccTotals+type Compare a = a -> a -> Ordering++cmpName, cmpSize, cmpStdDev+ :: (Compare (Text, (Double, Double)), Compare (Text, (Double, Double)))+cmpName = (cmpNameAscending, cmpNameDescending)+cmpSize = (cmpSizeDescending, cmpSizeAscending)+cmpStdDev = (cmpStdDevDescending, cmpStdDevAscending)++cmpNameAscending, cmpNameDescending,+ cmpStdDevAscending, cmpStdDevDescending,+ cmpSizeAscending, cmpSizeDescending :: Compare (Text, (Double, Double))+cmpNameAscending = comparing fst+cmpNameDescending = flip cmpNameAscending+cmpStdDevAscending = comparing (snd . snd)+cmpStdDevDescending = flip cmpStdDevAscending+cmpSizeAscending = comparing (fst . snd)+cmpSizeDescending = flip cmpSizeAscending++prune :: Compare (Text, (Double, Double)) -> Double -> Int -> Map Text (Double, Double) -> Map Text Int+prune cmp tracePercent nBands ts =+ let ccTotals = sortBy cmpSizeDescending (toList ts)+ sizes = map (fst . snd) ccTotals total = sum' sizes- limit = 0.99 * total+ limit = (1 - tracePercent / 100) * total bigs = takeWhile (< limit) . scanl (+) 0 $ sizes- bands = zipWith const ccTotals $ take 15 bigs- ccs = map fst bands+ bands = zipWith const ccTotals $ take nBands bigs+ ccs = map fst (sortBy cmp bands) in fromList (reverse ccs `zip` [1 ..]) sum' :: [Double] -> Double
src/SVG.hs view
@@ -3,11 +3,14 @@ import Data.Text (Text, pack) import qualified Data.Text as T+import Data.List (intersperse)+import Data.Monoid ((<>)) import Numeric (showHex) import Text.FShow.RealFloat (fshow, Double7(D7)) -import Graphics (Graphics(Graphics), Point, Size, Angle, FontSize, Anchor(..), StrokeWidth, Opacity, RGB(..))+import Graphics (Graphics(Graphics), Point, Size, Angle, FontSize, Anchor(..), StrokeWidth, Opacity, RGB(..), PatternID, colour, patternIx) import qualified Graphics as G+import Pattern (Pattern, patterns) svg :: Graphics svg =@@ -16,6 +19,7 @@ , G.rect = rect , G.line = line , G.polygon = polygon+ , G.pattern = pattern , G.visual = visual , G.document = document }@@ -44,20 +48,33 @@ line :: Point -> Point -> [Text] line (x1,y1) (x2,y2) = ["<line x1='" , showF x1, "' x2='", showF x2, "' y1='", showF y1, "' y2='", showF y2, "' />\n"] -visual :: Maybe RGB -> Maybe Opacity -> Maybe RGB -> Maybe StrokeWidth -> [Text] -> [Text]+pattern :: Text -> (PatternID, [Text])+pattern t = ("url(#" <> pid <> ")",+ [ "<pattern id='", pid, "' x='0' y='0' width='16' height='16' patternUnits='userSpaceOnUse'>\n"+ , " <path fill='none' stroke-width='2' stroke='" , s, "' line-cap='round' d='"] ++ intersperse " " (map p ps) ++ ["' />\n"+ , "</pattern>\n"])+ where+ pid = mconcat ["P", pack (show ix), "p", T.tail s]+ s = showRGB rgb+ p ((x0, y0), (x1, y1)) = pack $ "M " ++ show x0 ++ "," ++ show y0 ++ " L " ++ show x1 ++ "," ++ show y1+ rgb = colour t+ ix = patternIx t+ ps = patterns !! ix++visual :: Maybe (Either PatternID RGB) -> Maybe Opacity -> Maybe RGB -> Maybe StrokeWidth -> [Text] -> [Text] visual mfill mfillo mstroke mstrokew inner =- let fill = maybe [] (\f -> [" fill='", showRGB f, "'"]) mfill+ let fill = maybe [] (\f -> [" fill='", either id showRGB f, "'"]) mfill fillo = maybe [] (\o -> [" fill-opacity='", showF o, "'"]) mfillo stroke = maybe [] (\s -> [" stroke='", showRGB s, "'"]) mstroke strokew = maybe [] (\w -> [" stroke-width='", showF w, "'"]) mstrokew in ["<g"] ++ fill ++ fillo ++ stroke ++ strokew ++ [">\n"] ++ inner ++ ["</g>\n"] -document :: Size -> [Text] -> [Text]-document (w,h) inner =+document :: Size -> [Text] -> [Text] -> [Text]+document (w,h) defs inner = [ "<?xml version='1.0' encoding='UTF-8' ?>\n" , "<svg xmlns='http://www.w3.org/2000/svg' version='1.0'" , " width='", showF w, "' height='", showF h, "'>\n"- ] ++ inner ++ ["</svg>\n"]+ ] ++ ["<defs>\n"] ++ defs ++ ["</defs>\n"] ++ inner ++ ["</svg>\n"] polygon :: [Point] -> [Text] polygon ps = ["<path d='"] ++ path ps ++ ["' />"]
src/Total.hs view
@@ -13,7 +13,7 @@ data Parse = Parse { symbols :: !(Map Text Text) -- intern symbols to save RAM- , totals :: !(Map Text Double ) -- compute running totals+ , totals :: !(Map Text (Double, Double)) -- compute running totass and total of squares , sampleMin :: !Double , sampleMax :: !Double , valueMin :: !Double@@ -24,7 +24,7 @@ parse0 :: Parse parse0 = Parse{ symbols = empty, totals = empty, sampleMin = 0, sampleMax = 0, valueMin = 0, valueMax = 0, count = 0 } -total :: Text -> (Header, Map Text Double)+total :: Text -> (Header, Map Text (Double, Double)) total s = let ls = lines s (hs, ss) = splitAt 4 ls@@ -40,9 +40,12 @@ , hValueRange = (valueMin parse1, valueMax parse1) , hCount = count parse1 }- , totals parse1+ , fmap (stddev $ fromIntegral (count parse1)) (totals parse1) ) +stddev :: Double -> (Double, Double) -> (Double, Double)+stddev s0 (s1, s2) = (s1, sqrt (s0 * s2 - s1 * s1) / s0)+ header :: Text -> Text -> Text header name h = if name `isPrefixOf` h@@ -86,9 +89,9 @@ put $! p'{ totals = alter (accum v) k' (totals p') } return $! v -accum :: Double -> Maybe Double -> Maybe Double-accum x Nothing = Just x-accum x (Just y) = Just $! x + y+accum :: Double -> Maybe (Double, Double) -> Maybe (Double, Double)+accum x Nothing = Just $! (((,) $! x) $! (x * x))+accum x (Just (y, yy)) = Just $! (((,) $! (x + y)) $! (x * x + yy)) sampleTime :: Text -> Text -> Double sampleTime name h =