Skip to content

Commit

Permalink
Support online artifacts for plotting (#494)
Browse files Browse the repository at this point in the history
  • Loading branch information
kleinreact authored Apr 3, 2024
1 parent 9544202 commit 99482c5
Show file tree
Hide file tree
Showing 10 changed files with 424 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
CLEAR_AFTER=f"{CLEAR_AFTER_DAYS}d00h00m00s"
TOUCH_AFTER=datetime.timedelta(days=1)

GLOBAL_CACHE_BUST = 0
GLOBAL_CACHE_BUST = 1

CARGO_CACHE_BUST = 2
CARGO_KEY_PREFIX = f"cargo-g{GLOBAL_CACHE_BUST}-l{CARGO_CACHE_BUST}-"
Expand Down
40 changes: 20 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ jobs:
uses: actions/download-artifact@v4
with:
# Note that all artifacts are downloaded, if no 'name' is specified.
# This is still required for the generation of the simulation plots.
# Unfortunately, this is the only working solution that github offers
# right at the moment. Alternative options exist, like
# https://github.com/marketplace/actions/download-multiple-workflow-artifacts,
Expand All @@ -292,32 +293,31 @@ jobs:
run: |
mkdir -p reports
mkdir -p plot-sources
mkdir -p hitl-plots/hw
./cargo.sh build --frozen --release
cabal run -- bittide-tools:cc-plot artifacts/_build-fullMeshHwCcTest-debug/Bittide.Instances.Hitl.FullMeshHwCc.fullMeshHwCcTest/ila-data/CC/ hitl-plots/hw
cp .github/hitl/FullMeshHwCcReportTemplate.tex hitl-plots/hw/report.tex
cp .github/hitl/topology.tikz hitl-plots/hw/topology.tikz
export BITTIDE_ARTIFACT_ACCESS_TOKEN="${{ secrets.GITHUB_TOKEN }}"
export RUNREF="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo "$(date +'%Y-%m-%d %H:%M:%S')" > hitl-plots/hw/datetime
echo "\\textit{\\href{$RUNREF}{$RUNREF}}" > hitl-plots/hw/runref
cd hitl-plots/hw
cabal run -- bittide-tools:cc-plot ${{ github.run_id }}:fullMeshHwCcTest _build/plot hitl-plots/hw
cp .github/hitl/FullMeshHwCcReportTemplate.tex hitl-plots/hw/CC/report.tex
cp .github/hitl/topology.tikz hitl-plots/hw/CC/topology.tikz
echo "$(date +'%Y-%m-%d %H:%M:%S')" > hitl-plots/hw/CC/datetime
echo "\\textit{\\href{$RUNREF}{$RUNREF}}" > hitl-plots/hw/CC/runref
cd hitl-plots/hw/CC
lualatex report.tex
rm -Rf report.{aux,log,out,tex} runref datetime topology.tikz elasticbuffers.pdf clocks.pdf
cd ../..
mv hitl-plots/hw/report.pdf reports/HITL-FullMeshHwCc-Report.pdf
mv hitl-plots/hw plot-sources/FullMeshHwCc
mkdir -p hitl-plots/sw
cabal run -- bittide-tools:cc-plot artifacts/_build-fullMeshSwCcTest-debug/Bittide.Instances.Hitl.FullMeshSwCc.fullMeshSwCcTest/ila-data/CC/ hitl-plots/sw
cp .github/hitl/FullMeshSwCcReportTemplate.tex hitl-plots/sw/report.tex
cp .github/hitl/topology.tikz hitl-plots/sw/topology.tikz
echo "$(date +'%Y-%m-%d %H:%M:%S')" > hitl-plots/sw/datetime
echo "\\textit{\\href{$RUNREF}{$RUNREF}}" > hitl-plots/sw/runref
cd hitl-plots/sw
cd ../../..
mv hitl-plots/hw/CC/report.pdf reports/HITL-FullMeshHwCc-Report.pdf
mv hitl-plots/hw/CC plot-sources/FullMeshHwCc
cabal run -- bittide-tools:cc-plot ${{ github.run_id }}:fullMeshSwCcTest _build/plot hitl-plots/sw
cp .github/hitl/FullMeshSwCcReportTemplate.tex hitl-plots/sw/CC/report.tex
cp .github/hitl/topology.tikz hitl-plots/sw/CC/topology.tikz
echo "$(date +'%Y-%m-%d %H:%M:%S')" > hitl-plots/sw/CC/datetime
echo "\\textit{\\href{$RUNREF}{$RUNREF}}" > hitl-plots/sw/CC/runref
cd hitl-plots/sw/CC
lualatex report.tex
rm -Rf report.{aux,log,out,tex} runref datetime topology.tikz elasticbuffers.pdf clocks.pdf
cd ../..
mv hitl-plots/sw/report.pdf reports/HITL-FullMeshSwCc-Report.pdf
mv hitl-plots/sw plot-sources/FullMeshSwCc
cd ../../..
mv hitl-plots/sw/CC/report.pdf reports/HITL-FullMeshSwCc-Report.pdf
mv hitl-plots/sw/CC plot-sources/FullMeshSwCc
- name: Generate final simulation report
run: |
Expand Down
2 changes: 1 addition & 1 deletion .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Source: https://github.com/google-research/bittide/
# Copyright: $YEAR $NAME <$CONTACT>
# License: ...
Files: cabal.project.freeze
Copyright: 2023 Google LLC
Copyright: 2023-2024 Google LLC
License: CC0-1.0

Files: .vscode/*
Expand Down
9 changes: 8 additions & 1 deletion bittide-experiments/bittide-experiments.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,21 @@ library
clash-cores,
clash-lib,
containers,
directory,
filepath,
happy-dot,
http-conduit,
http-types,
matplotlib,
optparse-applicative,
process,
random,
temporary,
text,
typelits-witnesses
typelits-witnesses,
vector
exposed-modules:
Bittide.Github.Artifacts
Bittide.Hitl
Bittide.Plot
Bittide.Simulate
Expand Down
167 changes: 167 additions & 0 deletions bittide-experiments/src/Bittide/Github/Artifacts.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
-- SPDX-FileCopyrightText: 2024 Google LLC
--
-- SPDX-License-Identifier: Apache-2.0

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ImplicitPrelude #-}
module Bittide.Github.Artifacts
( RunId
, ArtifactName
, ArtifactAccessError
, retrieveArtifact
) where

import Control.Exception (throwIO)
import Control.Monad (forM)
import Data.Aeson
( FromJSON(..), Value(..), Result(..), (.:)
, fromJSON, withObject, withArray, decodeFileStrict
)
import Data.Map.Strict (Map)
import Network.HTTP.Conduit (requestHeaders)
import Network.HTTP.Simple
( JSONException(..)
, getResponseBody, httpJSONEither, parseRequest, getResponseStatus
)
import Network.HTTP.Types.Header (hUserAgent)
import Network.HTTP.Types.Status (Status(..))
import System.Directory (createDirectoryIfMissing, listDirectory)
import System.Environment (lookupEnv, getProgName)
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import System.Process (callCommand)

import qualified Data.Map.Strict as Map (fromList, lookup)
import qualified Data.ByteString.Char8 as ByteString (pack)
import qualified Data.Text as Text (unpack)
import qualified Data.Vector as Vector (toList)

-- | The environment variable used to share the artifact access token.
accessTokenEnvVar :: String
accessTokenEnvVar = "BITTIDE_ARTIFACT_ACCESS_TOKEN"

-- | The Bittide repository on Github.
bittideRepo :: String
bittideRepo = "bittide/bittide-hardware"

-- | Offers the artifacts list of a given run via the Github API.
githubApiArtifacts :: String -> String -> String
githubApiArtifacts repo run = "https://api.github.com/repos/"
<> repo <> "/actions/runs/" <> run <> "/artifacts?per_page=100"

-- | The unique identifier of the Github Action run.
type RunId = String

-- | The name of the artifact to be downloaded.
type ArtifactName = String

-- | Everything that can go wrong while trying to download an artifact
-- from the Bittide Github repository with 'retrieveArtifact' that has
-- its origin on some invalid user input passed to 'retrieveArtifact'.
data ArtifactAccessError =
NoAccessToken
| InvalidAccessToken
| RunNotFound RunId
| ArtifactNotFound RunId ArtifactName

instance Show ArtifactAccessError where
show = \case
NoAccessToken ->
"No access token found. A valid access token must be set via\n"
<> "the " <> accessTokenEnvVar <> " environment variable."
InvalidAccessToken ->
"The provided access token has no access to the Bittide artifacts."
RunNotFound runId ->
"Invalid run ID \"" <> runId <> "\". Cannot access the data for\n"
<> "the provided ID."
ArtifactNotFound runId artifactName ->
"There is no artifact named \"" <> artifactName <> "\" for the\n"
<> "run with ID " <> runId <> "."

-- | A newtype wrapper for extracting the "artifact name -> download
-- url" mapping of a run via the Github API.
newtype ArtifactDownloadUrl = ArtifactDownloadUrl (Map String String)

instance FromJSON ArtifactDownloadUrl where
parseJSON =
withObject "root" $ \root ->
(root .: "artifacts" >>=) $
withArray "artifacts" $ \as ->
fmap (ArtifactDownloadUrl . Map.fromList) $
forM (Vector.toList as) $
withObject "artifact" $ \artifact -> do
String name <- artifact .: "name"
String url <- artifact .: "archive_download_url"
return (Text.unpack name, Text.unpack url)

-- | A newtype wrapper for reading back curl response messages
newtype CurlResponseMessage = CurlResponseMessage String

instance FromJSON CurlResponseMessage where
parseJSON =
(CurlResponseMessage . Text.unpack <$>) .
withObject "root" (.: "message")

-- | Retrieve the artifact with the given name for the given run id
-- and save it at the provided location. An 'ArtifactAccessError' is
-- returned on failure with respect to the provided arguments. If the
-- arguments are valid, but there is some external problem with the
-- utilized process, then that error gets reported via an exception
-- instead.
retrieveArtifact ::
RunId -> ArtifactName -> FilePath -> IO (Maybe ArtifactAccessError)
retrieveArtifact runId artifactName destination = do
appName <- getProgName
request <- parseRequest $ githubApiArtifacts bittideRepo runId
response <- httpJSONEither request
{ -- Github requires to set the User Agent header, as the request
-- will always be rejected otherwise
requestHeaders = [(hUserAgent, ByteString.pack appName)]
}
case getResponseBody response of
Left err@(JSONParseException {}) -> throwIO err
Left err@(JSONConversionException _ resp _) ->
if statusCode (getResponseStatus resp ) == 404
then return $ Just $ RunNotFound runId
else throwIO err
Right (ArtifactDownloadUrl downloadUrls) -> do
case Map.lookup artifactName downloadUrls of
Nothing -> return $ Just $ ArtifactNotFound runId artifactName
Just downloadUrl -> lookupEnv accessTokenEnvVar >>= \case
Nothing -> return $ Just NoAccessToken
Just accessToken ->
withSystemTempDirectory "retrieve-artifact" $ \path -> do
let file = path </> "artifact.zip"
putStrLn $ "Retrieving " <> artifactName <> ".zip"
putStrLn "---"
callCommand $ unwords
[ "curl"
, "--location"
, "--header", "\"authorization: Bearer " <> accessToken <> "\""
, "--output", file
, downloadUrl
]
putStr "---"
-- if the downloaded file is a JSON instead of zip, then
-- something went wrong
decodeFileStrict file >>= \case
Just jsonValue -> do
putStrLn " Failed."
if case fromJSON jsonValue of
Success (CurlResponseMessage msg) ->
msg == "Bad credentials"
_ -> False
then return $ Just InvalidAccessToken
else do
req <- parseRequest downloadUrl
throwIO $ JSONConversionException
req (jsonValue <$ response) "curl download failed"
Nothing -> do
putStrLn " Success."
callCommand $ unwords ["unzip", "-q", file, "-d", path]
callCommand $ unwords ["rm", file]
createDirectoryIfMissing True destination
(>>=) (listDirectory path) $ mapM_ $ \x -> do
callCommand $ unwords ["rm", "-Rf", destination </> x]
callCommand $ unwords ["mv", path </> x, destination </> x]
return Nothing
24 changes: 14 additions & 10 deletions bittide-instances/src/Bittide/Instances/Hitl/IlaPlot.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2023 Google LLC
-- SPDX-FileCopyrightText: 2023-2024 Google LLC
--
-- SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -30,6 +30,7 @@ module Bittide.Instances.Hitl.IlaPlot
, PlotData(..)
, RfStageChange(..)
-- * ILA Plot
, ilaProbeNames
, ilaPlotSetup
, callistoClockControlWithIla
-- * Helpers
Expand Down Expand Up @@ -211,6 +212,17 @@ data IlaControl dom =
-- ^ Synchronized pulse counter
}

-- | Names of the additional ILA plot probes.
ilaProbeNames :: Vec 6 String
ilaProbeNames =
"trigger_1"
:> "capture_1"
:> "condition"
:> "global"
:> "local"
:> "data"
:> Nil

-- | The ILA plot setup controller.
ilaPlotSetup ::
forall dom. HasCallStack =>
Expand Down Expand Up @@ -531,15 +543,7 @@ callistoClockControlWithIla dynClk clk rst ccc IlaControl{..} mask ebs =
ilaInstance :: Signal sys ()
ilaInstance =
setName @"ilaPlot" $ ila
( ilaConfig
$ "trigger_1"
:> "capture_1"
:> "condition"
:> "global"
:> "local"
:> "data"
:> Nil
) { depth = D16384 }
(ilaConfig ilaProbeNames) { depth = D16384 }
-- the ILA must run on a stable clock
clk
-- trigger as soon as we start
Expand Down
1 change: 1 addition & 0 deletions bittide-tools/bittide-tools.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ executable cc-plot
import: common-options
main-is: clockcontrol/plot/Main.hs
build-depends:
aeson,
array,
bittide,
bittide-experiments,
Expand Down
Loading

0 comments on commit 99482c5

Please sign in to comment.